前回の記事では、Canonical Kubernetes ディストリビューション Charmed Kubernetes の特長、構築方法などを紹介しました。
Kubernetes を用いて簡単にコンテナとしてアプリケーションをデプロイ・削除をできるのがメリットですが、コンテナを削除するとコンテナ内のデータも削除されてしまいます。そのため、データベースのようなステートフルアプリケーションを利用する場合には、データの永続化が必要となります。
今回は Charmed Kubernetes の永続ストレージ機能を紹介し、Charmed Kubernetes 上でのステートフルアプリケーションの構築について紹介します。
Kubernetes の永続ストレージ
Kubernetes の Podを削除すると Pod 内に保存されていたデータも一緒に削除されてしまいますので、データの永続化はできません。Kubernetes ノード上の領域を Pod にマウントすることで、Pod を削除した後もデータはノードに残るため、データの永続化は可能ですが、Pod が別ノードに再スケジュールされた場合にはデータの再利用ができないというデメリットがあります。
Kubernetes ノードとは別に外部ストレージを利用することで、Pod が削除されてもデータは外部ストレージに存在するため、データの永続化が可能です。また、外部ストレージはノードとは独立して存在しており、Pod が別のノードで再起動したとしてもデータが外部に存在するため、データの再利用が可能です。
Kubernetes でデータ永続化を実現するために、以下のリソースを利用します。
- PersistentVolume (PV)
- PersistentVolumeClaim (PVC)
- StorageClass
事前に永続ボリューム (PersistentVolume) リソースを Kubernetes 上に作成しておき、Pod デプロイ時に PersistentVolumeClaim によって PersistentVolume が要求されると、Kubernetes は事前に作成しておいた PersistentVolume から要求に適したものを Pod に割り当てます。このような方法では、予め PersistentVolume を作成する手間がかかり、また、PersistentVolumeClaim の要求以上の容量の PersistentVolume が割り当てられてしまうという問題があります。
これに対して、PersistentVolume を動的に作成する仕組み Dynamic Provisioning を利用することができます。Dynamic Provisioning は StorageClass を利用し、PersistentVolumeClaim によって PersistentVolume が要求されたタイミングで PersistentVolume を動的に作成し、Pod に割り当てます。Dynamic Provisioning を利用することで、事前に PersistentVolume を作成する手間が省けます。また、PersistentVolumeClaim の要求よりも大きい容量の PersistentVolume が作成されることも回避できます。
本稿では Charmed Kubernetes 上で Dynamic Provisioning を利用したデータ永続化のソリューションを紹介します。
Charmed Kubernetes の永続ストレージ
Charmed Kubernetes で以下の方法を用いてデータの永続化を実現することができます。
クラウドのストレージソリューション
Charmed Kubernetes のクラウド基盤統合機能を利用すると、クラウドベンダーによって提供されているストレージソリューションをすぐに使用できます。
例えば、OpenStack 統合機能を有効にして Charmed Kubernetes をデプロイすると、cdk-cinder という名前の StorageClass が自動的に作成されます。
Ceph
Ceph Charm を用いてデプロイした Ceph クラスタを外部ストレージとして使用し、PersistentVolume を作成します。
NFS
NFS Charm を用いてデプロイした NFS サーバを外部ストレージとして使用し、PersistentVolume を作成します。
ベアメタルにデプロイされた Kubernetes では、クラウドの機能を利用できないため、Ceph や NFS を利用することになります。本稿では、Juju を用いて Charmed Kubernetes 上に Ceph を構築し、ステートフルアプリケーションのデータの永続化を実現する方法を紹介します。
前提条件
Charmed Kubernetes のデプロイが既に完了していることが前提となっています。今回は AWS 上にデプロイした Charmed Kubernetes を利用します。(デプロイ方法はこちらを参照)
Ceph ストレージの構築
Ceph はオープンソースの分散ストレージソフトウェアです。
Kubernetes の永続ストレージに Ceph を利用すると、拡張可能な高可用ストレージを実現することができます。本節では、Juju を用いた Ceph ストレージの構築方法を説明します。
デプロイ済みの Charmed Kubernetes クラスタにアクセスし、ノード状態を確認します。まだデプロイしていない場合は、こちらのデプロイ手順を参照してください。
$ kubectl get node NAME STATUS ROLES AGE VERSION ip-172-31-54-236 Ready <none> 9m48s v1.23.4 ip-172-31-7-84 Ready <none> 9m43s v1.23.4 ip-172-31-90-192 Ready <none> 9m43s v1.23.4
セキュリティの観点から、モデルを分けることをお勧めしますが、本稿は Charmed Kubernetes 上で Ceph による Dynamic Provisioning を検証することが目的なので、Kubernetes と Ceph を同一 モデルにデプロイします。
Charmed Kubernetes がデプロイされているモデルに切り替えます。
$ juju switch k8s
Ceph Monitor ノードをデプロイします。
Ceph Monitor はクラスタの管理、状態監視を行うコンポーネントです。高可用性を実現するために、3台以上の奇数台が必要です。ここでは、3台の Ceph Monitor ノードをデプロイします。
$ juju deploy -n 3 ceph-mon
Ceph OSD ノードをデプロイします。
Ceph OSD はデータの保存、データの冗長化などを行うコンポーネントです。
Juju の ceph-osd Charm では、--storage
オプションに2種類のストレージを設定できます。
osd-devices
: データを格納するストレージosd-journals
: ジャーナルを格納するストレージ
--storage
オプションは <storage pool>,<size>,<number>
形式で指定します。
storage pool
: ストレージプールを指定します。ストレージプールはクラウド基盤に依存します。指定しない場合は、クラウド基盤のデフォルトのストレージプールを使用します。AWS のデフォルトのストレージプールは ebs となります。size
: 各ボリュームのサイズを指定します。number
: ボリュームの数を指定します。
ここでは、3台の Ceph OSD ノードをデプロイします。各 OSD ノードに2つのデータ用のボリューム(50GB)と1つのジャーナル用のボリューム(10GB)を作成します。
$ juju deploy -n 3 ceph-osd --storage osd-devices=50G,2 --storage osd-journals=10G,1
Ceph Monitor と Ceph OSD の関連付けを行います。
$ juju add-relation ceph-osd ceph-mon
RBD で作成した PersistentVolume のアクセスモードが ReadWriteOnce となりますので、アクセスモード ReadWriteMany の PersistentVolume を利用する場合には、CephFS をデプロイする必要があります。利用する場合には、以下のコマンドを実行し、CephFS のデプロイおよび関連付けの設定を行います。
$ juju deploy -n 1 ceph-fs $ juju add-relation ceph-fs ceph-mon
Charmed Kubernetes で Ceph ストレージを利用するために、関連付けの設定を行います。
$ juju add-relation ceph-mon:admin kubernetes-master $ juju add-relation ceph-mon:client kubernetes-master
Charmed Kubernetes と Ceph の関連付けの設定が完了したら、kubectl get sc
を実行し、StorageClass が表示されていることを確認します。
※cephfs ストレージクラスは、ceph-fs をデプロイした場合にのみ表示されます。
$ kubectl get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE ceph-ext4 rbd.csi.ceph.com Delete Immediate true 2m39s ceph-xfs (default) rbd.csi.ceph.com Delete Immediate true 2m39s cephfs cephfs.csi.ceph.com Delete Immediate true 2m39s
Pod の状態を確認します。
※cephfs の Pod は、ceph-fs をデプロイした場合にのみ表示されます。
$ kubectl get pod NAME READY STATUS RESTARTS AGE csi-cephfsplugin-927cv 3/3 Running 0 18s csi-cephfsplugin-b6q66 3/3 Running 0 22s csi-cephfsplugin-provisioner-6f7ffd894d-b2sh4 6/6 Running 0 101s csi-cephfsplugin-provisioner-6f7ffd894d-d5b9x 6/6 Running 0 101s csi-cephfsplugin-provisioner-6f7ffd894d-kqvxn 6/6 Running 0 101s csi-cephfsplugin-vqgtb 3/3 Running 0 15s csi-rbdplugin-fsh7g 3/3 Running 0 15s csi-rbdplugin-nt2lq 3/3 Running 0 22s csi-rbdplugin-provisioner-7dffff7f4d-8l8l5 7/7 Running 0 101s csi-rbdplugin-provisioner-7dffff7f4d-wmvct 7/7 Running 0 101s csi-rbdplugin-provisioner-7dffff7f4d-z7nm9 7/7 Running 0 101s csi-rbdplugin-ts8rw 3/3 Running 0 19s
これで Ceph クラスタを Charmed Kubernetes の外部ストレージとして使用し Dynamic Provisioning を実現するための準備が完了です。
スケールアウト
Ceph のスケールアウトとは、新しいノードまたはデバイスを追加することによって利用可能な容量を拡張することです。本節では Ceph のスケールアウト手順について説明します。
Ceph ストレージの容量を拡張するには、Ceph OSD ノードを追加するか、既存の Ceph OSD ノードにストレージを追加します。
まず、既存のストレージの割り当てを確認します。
$ juju storage Unit Storage id Type Pool Size Status Message ceph-osd/0 osd-devices/0 block ebs 50GiB attached ceph-osd/0 osd-devices/1 block ebs 50GiB attached ceph-osd/0 osd-journals/2 block ebs 10GiB attached ceph-osd/1 osd-devices/3 block ebs 50GiB attached ceph-osd/1 osd-devices/4 block ebs 50GiB attached ceph-osd/1 osd-journals/5 block ebs 10GiB attached ceph-osd/2 osd-devices/6 block ebs 50GiB attached ceph-osd/2 osd-devices/7 block ebs 50GiB attached ceph-osd/2 osd-journals/8 block ebs 10GiB attached
新しい Ceph OSD ノードを追加します。
$ juju add-unit ceph-osd -n 1
新しいノード ceph-osd/3 が追加されていることを確認します。
$ juju storage Unit Storage id Type Pool Size Status Message ceph-osd/0 osd-devices/0 block ebs 50GiB attached ceph-osd/0 osd-devices/1 block ebs 50GiB attached ceph-osd/0 osd-journals/2 block ebs 10GiB attached ceph-osd/1 osd-devices/3 block ebs 50GiB attached ceph-osd/1 osd-devices/4 block ebs 50GiB attached ceph-osd/1 osd-journals/5 block ebs 10GiB attached ceph-osd/2 osd-devices/6 block ebs 50GiB attached ceph-osd/2 osd-devices/7 block ebs 50GiB attached ceph-osd/2 osd-journals/8 block ebs 10GiB attached ceph-osd/3 osd-devices/10 block ebs 50GiB attached ceph-osd/3 osd-devices/9 block ebs 50GiB attached ceph-osd/3 osd-journals/11 block ebs 10GiB attached $ juju status ceph-osd Model Controller Cloud/Region Version SLA Timestamp k8s k8s-controller aws/us-east-1 2.9.22 unsupported 15:39:18+09:00 App Version Status Scale Charm Store Channel Rev OS Message ceph-osd 15.2.14 active 4 ceph-osd charmhub stable 513 ubuntu Unit is ready (2 OSD) Unit Workload Agent Machine Public address Ports Message ceph-osd/0* active idle 13 3.83.100.242 Unit is ready (2 OSD) ceph-osd/1 active idle 14 3.238.52.73 Unit is ready (2 OSD) ceph-osd/2 active idle 15 3.235.171.220 Unit is ready (2 OSD) ceph-osd/3 active idle 17 54.82.234.9 Unit is ready (2 OSD) Machine State DNS Inst id Series AZ Message 13 started 3.83.100.242 i-074dcc388f5b35c1b focal us-east-1d running 14 started 3.238.52.73 i-0b2d721800fd923d6 focal us-east-1c running 15 started 3.235.171.220 i-0e9916138314d204b focal us-east-1f running 17 started 54.82.234.9 i-0cf8cad9824868068 focal us-east-1a running
既存の Ceph OSD ノードにストレージを追加するために、juju add-storage
コマンドを実行します。以下のコマンドを実行し、ceph-osd/0 ノードに 50GB の新しいボリュームを追加します。
$ juju add-storage ceph-osd/0 osd-devices=50G,1
ceph-osd/0 ノードに 50GB のボリュームが追加されていることを確認します。
$ juju storage Unit Storage id Type Pool Size Status Message ceph-osd/0 osd-devices/0 block ebs 50GiB attached ceph-osd/0 osd-devices/1 block ebs 50GiB attached ceph-osd/0 osd-devices/12 block ebs 50GiB attached ceph-osd/0 osd-journals/2 block ebs 10GiB attached ceph-osd/1 osd-devices/3 block ebs 50GiB attached ceph-osd/1 osd-devices/4 block ebs 50GiB attached ceph-osd/1 osd-journals/5 block ebs 10GiB attached ceph-osd/2 osd-devices/6 block ebs 50GiB attached ceph-osd/2 osd-devices/7 block ebs 50GiB attached ceph-osd/2 osd-journals/8 block ebs 10GiB attached ceph-osd/3 osd-devices/10 block ebs 50GiB attached ceph-osd/3 osd-devices/9 block ebs 50GiB attached ceph-osd/3 osd-journals/11 block ebs 10GiB attached
アプリケーションをデプロイしてみる
Charmed Kubernetes の永続ストレージの動的プロビジョニング機能を検証するために、ステートフルアプリケーションをデプロイしてみます。
本稿では、ステートフルアプリケーションとして PostgreSQL を扱います。
Kubernetes 上で PostgreSQL を動かすために、PostgreSQL Operator が必要になります。PostgreSQL Operator は PostgreSQL のデプロイや管理を自動化するためのソフトウェアです。
今回は検証目的で Charmed Kubernetes 環境向けに開発された PostgreSQL Charmを利用して PostgreSQL を構築します。現時点では、PostgreSQL Charm はスケーリングとフェイルオーバにのみ対応しており、バックアップやリストア・PITR などの管理は手動で行う必要があるため、本番環境での利用はお勧めしません。
本番環境で PostgreSQL をデプロイする場合は、以前の記事で紹介した Crunchy PostgreSQL Operator または Zalando PostgreSQL Operator を利用すると良いと思います。
Charmed Kubernetes 上に Juju チャームをデプロイするために、デプロイ済みの Charmed Kubernetes を Juju に登録する必要があります。
juju add-k8s
コマンドを実行し、Charmed Kubernetes 環境を登録します。ここでは、my-k8s という名前で 登録します。
$ juju add-k8s my-k8s
pg-controller という名前でコントローラーをデプロイします。
$ juju bootstrap my-k8s pg-controller
PostgreSQL をデプロイするためのモデルを作成します。モデル名と同じ名前の Namespace が Kubernetes に作成され、PostgreSQL がこの Namespace にデプロイされます。
ここで pg-model という名前のモデルを作成します。
$ juju add-model pg-model
次に、PostgreSQL をデプロイします。
PostgreSQL Charm の –storage オプションに2種類のストレージを設定できます。
pgdata
: データベースのデータを格納するストレージlogs
: PostgreSQL のログを格納するストレージ
$ juju deploy postgresql-k8s -n 3 --storage pgdata=20G --storage logs=1G
上記コマンドを実行すると、primary 1台、replica 2台 の PostgreSQL クラスタが作成されます。
$ kubectl get pod -n pg-model NAME READY STATUS RESTARTS AGE modeloperator-7896f7f67d-pwch5 1/1 Running 0 4m17s postgresql-k8s-0 1/1 Running 0 2m14s postgresql-k8s-1 1/1 Running 0 2m14s postgresql-k8s-2 1/1 Running 0 2m14s postgresql-k8s-operator-0 1/1 Running 0 3m8s
PersistentVolumeClaim (PVC) と PersistentVolume (PV) を確認してみましょう。
PostgreSQL Charm の PVC によって PV が自動的に作成されていることを確認します。
$ kubectl get pvc -n pg-model NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE logs-2c7ce802-postgresql-k8s-0 Bound pvc-265cd94c-77ec-4862-b021-b9d199326ef6 1Gi RWO ceph-xfs 2m52s logs-2c7ce802-postgresql-k8s-1 Bound pvc-6f71aa9a-77fd-4de5-93a8-bcd4e88fe35d 1Gi RWO ceph-xfs 2m52s logs-2c7ce802-postgresql-k8s-2 Bound pvc-cbca4843-f12f-49e3-ae89-5436d9791819 1Gi RWO ceph-xfs 2m52s pgdata-2c7ce802-postgresql-k8s-0 Bound pvc-7b40a98c-6f00-4f64-8c0a-701ccc0515d3 20Gi RWO ceph-xfs 2m52s pgdata-2c7ce802-postgresql-k8s-1 Bound pvc-cd6415e2-2322-4b5f-89f2-640b8d65d515 20Gi RWO ceph-xfs 2m52s pgdata-2c7ce802-postgresql-k8s-2 Bound pvc-5eb9547b-e14a-42a3-9746-e012411121a7 20Gi RWO ceph-xfs 2m52s $ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-2572bd8d-286f-4ffd-89b6-e22a95a11873 20Gi RWO Delete Bound controller-pg-controller/storage-controller-0 ceph-xfs 6m30s pvc-265cd94c-77ec-4862-b021-b9d199326ef6 1Gi RWO Delete Bound pg-model/logs-2c7ce802-postgresql-k8s-0 ceph-xfs 3m8s pvc-5eb9547b-e14a-42a3-9746-e012411121a7 20Gi RWO Delete Bound pg-model/pgdata-2c7ce802-postgresql-k8s-2 ceph-xfs 3m6s pvc-6f71aa9a-77fd-4de5-93a8-bcd4e88fe35d 1Gi RWO Delete Bound pg-model/logs-2c7ce802-postgresql-k8s-1 ceph-xfs 3m7s pvc-7b40a98c-6f00-4f64-8c0a-701ccc0515d3 20Gi RWO Delete Bound pg-model/pgdata-2c7ce802-postgresql-k8s-0 ceph-xfs 3m8s pvc-cbca4843-f12f-49e3-ae89-5436d9791819 1Gi RWO Delete Bound pg-model/logs-2c7ce802-postgresql-k8s-2 ceph-xfs 3m5s pvc-cd6415e2-2322-4b5f-89f2-640b8d65d515 20Gi RWO Delete Bound pg-model/pgdata-2c7ce802-postgresql-k8s-1 ceph-xfs 3m7s
Replica の数を減らして、データが残るか検証します。
$ juju remove-unit postgresql-k8s --num-units 1
Pod が削除されても PVC と PV が削除されないことを確認します。
$ kubectl get pod -n pg-model NAME READY STATUS RESTARTS AGE modeloperator-7896f7f67d-pwch5 1/1 Running 0 6m22s postgresql-k8s-0 1/1 Running 0 4m19s postgresql-k8s-1 1/1 Running 0 4m19s postgresql-k8s-operator-0 1/1 Running 0 5m13s $ kubectl get pvc -n pg-model NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE logs-2c7ce802-postgresql-k8s-0 Bound pvc-265cd94c-77ec-4862-b021-b9d199326ef6 1Gi RWO ceph-xfs 4m37s logs-2c7ce802-postgresql-k8s-1 Bound pvc-6f71aa9a-77fd-4de5-93a8-bcd4e88fe35d 1Gi RWO ceph-xfs 4m37s logs-2c7ce802-postgresql-k8s-2 Bound pvc-cbca4843-f12f-49e3-ae89-5436d9791819 1Gi RWO ceph-xfs 4m37s pgdata-2c7ce802-postgresql-k8s-0 Bound pvc-7b40a98c-6f00-4f64-8c0a-701ccc0515d3 20Gi RWO ceph-xfs 4m37s pgdata-2c7ce802-postgresql-k8s-1 Bound pvc-cd6415e2-2322-4b5f-89f2-640b8d65d515 20Gi RWO ceph-xfs 4m37s pgdata-2c7ce802-postgresql-k8s-2 Bound pvc-5eb9547b-e14a-42a3-9746-e012411121a7 20Gi RWO ceph-xfs 4m37s $ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-2572bd8d-286f-4ffd-89b6-e22a95a11873 20Gi RWO Delete Bound controller-pg-controller/storage-controller-0 ceph-xfs 8m30s pvc-265cd94c-77ec-4862-b021-b9d199326ef6 1Gi RWO Delete Bound pg-model/logs-2c7ce802-postgresql-k8s-0 ceph-xfs 5m8s pvc-5eb9547b-e14a-42a3-9746-e012411121a7 20Gi RWO Delete Bound pg-model/pgdata-2c7ce802-postgresql-k8s-2 ceph-xfs 5m6s pvc-6f71aa9a-77fd-4de5-93a8-bcd4e88fe35d 1Gi RWO Delete Bound pg-model/logs-2c7ce802-postgresql-k8s-1 ceph-xfs 5m7s pvc-7b40a98c-6f00-4f64-8c0a-701ccc0515d3 20Gi RWO Delete Bound pg-model/pgdata-2c7ce802-postgresql-k8s-0 ceph-xfs 5m8s pvc-cbca4843-f12f-49e3-ae89-5436d9791819 1Gi RWO Delete Bound pg-model/logs-2c7ce802-postgresql-k8s-2 ceph-xfs 5m5s pvc-cd6415e2-2322-4b5f-89f2-640b8d65d515 20Gi RWO Delete Bound pg-model/pgdata-2c7ce802-postgresql-k8s-1 ceph-xfs 5m7s
おわりに
2回に渡って Canonical Kubernetes ディストリビューション Charmed Kubernetes を紹介しました。
エンタープライズ向けの機能を備えた Charmed Kubernetes は幅広いプラットフォームに対応しており、特にオンプレミスやプライベートクラウドにおいても容易に Kubernetes の環境を構築・管理できるという点は Charmed Kubernetes ならではの強みと言えます。オンプレミスやプライベートクラウドでエンタープライズ向けの Kubernetes を構築したい場合、Charmed Kubernetes は有力な選択肢だと言えるでしょう。