Charmed Kubernetes の永続ストレージ

前回の記事では、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 は有力な選択肢だと言えるでしょう。