StatefulSetとPersistentVolumeを使ってステートフルアプリケーションを動かす
はじめに
前回は、アプリケーションのマニフェスト管理でkustomizeを使って複数環境のマニフェストを整理しました。
ところで、デプロイしたアプリケーションはデータベースにPostgreSQLを稼働しています。データベースのように、システムの状態を保存する役割を持ったアプリケーションを「ステートフルアプリケーション」と呼びます。今回はステートフルアプリケーションをKubernetes上で利用するための仕組みである「StatefulSet」と「PersistentVolume」などを紹介します。
Kubernetes上での
ステートフルアプリケーションについて
ステートフルアプリケーションは、状態としてのデータを保持することが大きな責務です。細かな要件はシステムによって異なりますが、データを確実に保存し、失わないために、ステートレスアプリケーションよりも考慮事項が多くなります。
ステートフルアプリケーションは、ステートレスアプリケーションに比べると、要件によって変わる部分が多く、可用性・整合性など特に高い要件を満たそうとすると十分なアプリケーションやエコシステムがそろっているとは言えない部分もあり、公開された知見も少なくなります。既にKubernetesを運用している組織でも、ステートフルなアプリケーションは実績のあるベアメタルサーバーやVM、マネージドサービスを利用しているケースは多いです。
ただ、検証用途や高いレベルでの可用性を求めないのであれば、コンテナアプリケーションとして稼働することで、製品を「とりあえず使える」状態で立ち上げることは簡単にできます。検証で「主目的である検証を早く実施したい(冗長構成の構築やチューニングは後回しにしたい)」という状況はよくあると思いますが、そういった場合にコンテナアプリケーションが利用できれば最適でしょう。
現状としては以上の通りですが、今回はKubernetesで用意されているステートフルアプリケーションのための仕組みを確認しながら、前回デプロイした開発用マニフェストに含まれるPostgreSQLのマニフェストを修正してPodを再起動してもデータが保持されるように変更してみます。
Pod-Lifecycleと紐づいた
Volumeとその課題
ステートフルアプリケーションを実行するにあたって、状態を保存するストレージの利用は必須です。ホストファイルシステムやネットワークストレージをPodから利用する方法として「Volume」という仕組みがあります。まず、簡単にVolumeを紹介し、最後にVolumeではステートフルアプリケーションにとっての永続ストレージとして不十分であることを説明します。
以下に、Volumeとしてホスト上の領域をPod/コンテナにマウントするマニフェストの例を示します。
apiVersion: v1 kind: Pod metadata: name: postgres spec: containers: - name: db image: postgres:latest volumeMounts: - name: data mountPath: /mnt/data/ volumes: - name: data emptyDir: {}
このマニフェストにより、ホスト上に確保された空のディレクトリがPodに割り当てられ、Pod内のdbコンテナの/mnt/data/にマウントされます。
コンテナ内のファイルシステムはコンテナが再起動すると初期状態に戻ります。しかし、コンテナよりも長いライフサイクルを持つPodからすると、Podのライフサイクルに合わせたストレージが欲しくなることがあります。例えばPod内の複数のコンテナ間でファイルを共有するとき、一部のコンテナの再起動により共有ファイルがリセットされてしまうと、ほかのアプリケーションは(必ずしもとは言えませんが)たいてい混乱することになるでしょう。こういった場合にVolumeを利用して共有するファイルのライフタイムをPodのライフタイムまで延長します。
では、Volumeを使えば永続ストレージとして十分でしょうか。残念ながら、Volumeは基本的にそのライフタイムをPodと共有しており、PodはKubernetes上でEphemeralなリソースと考えられているため、永続ストレージとして使われるべきではありません。
emptyDirではPodが削除されたときにその内容が破棄されます。つまりPodのライフサイクルを超えてVolume上のデータを利用することは物理的にできません。また、ネットワークファイルシステムやクラウドストレージを利用すると内容を破棄しないようにすることもできますが、Podが削除されると利用していたストレージそのものの情報がKubernetes上から消えてしまうため、ストレージ管理で重要なデータの破棄や、以前使われていたデータが引き継がれていることがKubernetes API上で保証・確認できません。
- (問題点1) VolumeはPodのライフタイムに依存しており、ストレージのライフサイクル管理ができない
また、Volume設定ではNFS(Network File System)やクラウドストレージを具体的に指定して利用するため、クラスタ管理者が適切に各ストレージを管理する必要があります。したがって、管理者は開発チームなどが必要になったときに割り当てのための管理作業を手動で行う必要が発生し、迅速な割り当てが難しくなります。
- (問題点2) 具体的なストレージの情報を直接扱うため、ストレージとストレージの種類の抽象化が難しく、ライフサイクル管理の自動化ができない
こういった問題を解決するために、Kubernetes上で提供されているリソースを次節以降で見ていきます。
永続ストレージをコンテナに提供する
PersistentVolume/PersistentVolumeClaim
Kubernetesでは、永続ストレージを利用する場合にPersistentVolume(以下、PV)とPersistentVolumeClaim(以下、PVC)を用いてPodにストレージを紐づけます。PVは具体的なストレージを表しており、ストレージ容量や、ネットワークファイルシステムであればアクセスするためのアドレスなども持っています。
NFSを利用する場合のPVの例を見ていきましょう。
apiVersion: v1 kind: PersistentVolume metadata: labels: release: stable name: pv0003 spec: capacity: storage: 5Gi volumeMode: Filesystem accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle storageClassName: slow mountOptions: - hard - nfsvers=4.1 nfs: path: /tmp server: 172.17.0.2
- ストレージの種類によらない設定:
release: stable
:リソースのグルーピングのためのラベルstorage: 5Gi
:利用したいストレージ容量volumeMode: Filesystem
:ボリュームの利用方法accessModes:
:このPVをマウントできるアクセスモード。アクセスモードには3種類がある(詳細)persistentVolumeReclaimPolicy: Recycle
:PVCにバインドされ、その後PVCが(利用を終了して)削除された後の扱いを指定(詳細)storageClassName: slow
:このボリュームが所属するStorageClassの名前を指定。StorageClassについては次節で解説 - ストレージの種類による設定:
mountOptions:
:ストレージの種類によって必要なオプションを指定(詳細)nfs:
:NFSで提供されるストレージを指す。NFS固有の設定としてNFSサーバのアドレスとサーバ上のパスを指定
PVCはどのようなPVを必要とするかを記述するためのリソースであり、開発チームなどが抽象的な要求(容量・共有アクセスの可否・ボリュームの種類など)を記述します。KubernetesはPVCが要求した内容に対して最終的にPVをバインド(割り当て)することにより、PodでPVを利用できるようになります。
実際のPVCの例を見てみましょう。
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: myclaim spec: accessModes: - ReadWriteOnce volumeMode: Filesystem resources: requests: storage: 8Gi storageClassName: slow selector: matchLabels: release: "stable"
- 以下は、バインドするPVが提供するべき機能について指定しており、PVの対応するフィールドが要求を満たせる値である必要があります。
accessModes:
volumeMode: Filesystem
storageClassName: slow
resources:
:バインドするPVが提供するべきストレージ容量を設定。要求量であるため、ここで指定したものより大きいものが割り当てられる可能性もあるselector:
:このフィールドでPVのラベルに関する条件を指定することで、より柔軟にPVを絞り込む
PVやPVCはPodとは独立したリソースとしてKubernetes上で管理されるため、前節の(問題点1)が解消され、ストレージそのものの抽象化もPVという形で表現されているため、(問題点2)も一部解消されています。
では、PVCが作成されたときにバインドされるPVはどこから来るのでしょうか。1つの方法は、クラスタ管理者がバックエンドとなるストレージを準備し、それに対応するPVを手動でKubernetes上に作成しておく方法です。事前に作成されたPVの中からPVCの要求を満たすPVが自動的にKubernetesによって選択されバインドされます。
しかし、この方法には(問題点2)と同じ問題があります。すなわちPVの作成とストレージのプロビジョニングのために人手の作業を必要としており、まだ十分に自動化されていません。
永続ストレージのプロビジョニングを自動化する
ProvisionerとStorageClass
前節で説明した「クラスタ管理者が事前に必要なPVを設定し、利用時はPVCを通じてPodに割り当てる」というプロビジョニングの手順を「Static Provisioning」(静的プロビジョニング)と呼びます。対して、プロビジョニングを自動化して「クラスタ管理者はプロビジョニングに必要な情報だけを事前に設定して利用時にPVCに応じて必要なPVを作成し、PVCに割り当てる」という手順を「Dynamic Provisioning」(動的プロビジョニング)と呼びます。
動的プロビジョニングにおいてさまざまなストレージに対応し、実際にストレージのプロビジョニングを行うため、Kubernetesは「Provisioner」というコンポーネントを持っています。Provisionerには大別して2種類あります。1つは「in-tree provisioner」と呼ばれる、Kubernetesのコアリポジトリ内で開発され、Kubernetesのコンポーネントに組み込まれたProvisionerです。もう1つはContainer Storage Interface(CSI)というインターフェース仕様に基づいてKubernetesコミュニティ以外でも開発されているCSI Driverを用いたProvisionerです。CSI仕様はプロビジョニング以外の機能も標準化しているためCSI Driverを用いるほうが高機能ですが、in-tree provisionerのほうが導入に手間が少なく、比較的手軽に利用できます。
Provisionerは利用するストレージによってさまざまありますが、実際にクラスタ管理者が提供するストレージの詳細は「StorageClass」というKubernetes上のリソースで表現します。StorageClassリソースとして、AWSの「Amazon EBS」というクラウドストレージサービスを利用する例を挙げます(AWS上で稼働するKubernetesクラスタを前提とする例)。
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: slow provisioner: kubernetes.io/aws-ebs parameters: type: io1 iopsPerGB: "10" fsType: ext4
name: slow
:iopsPerGB(後述)などで、それほど高くない性能を指定しているため分かりやすい名前を設定。 name: low-costなどでも良いprovisioner: kubernetes.io/aws-ebs
:Amazon EBSにアクセスして必要なストレージのプロビジョニングを行うin-tree provisionerを指定。当然、異なるストレージを使う場合はそれに合わせたProvisionerを指定する必要があるが、同じ種類のストレージを利用する場合でもCSI Driverを使うときは異なるProvisionerを指定することがあるparameters:
:この配下はProvisionerによって異なるパラメータが設定される。今回の例ではAmazon EBSに固有のパラメータで性能やファイルシステムを指定しているtype: io1
:Amazon EBSが提供するストレージの種類を指定iopsPerGB: "10"
/fsType: ext4
:Amazon EBSが提供するストレージの性能やファイルシステムの種類などを指定。ストレージの種類やProvisionerによっても指定できるパラメータや指定方法が変わることがある
動的プロビジョニングのまとめ
ここで、一度永続ストレージと動的プロビジョニングに関する部分をまとめておきます。関係するリソース/コンポーネントは以下の通りです。
- PersistentVolume(PV)
・Podリソースに直接依存せずにストレージを表すリソース
・PVCにバインドされてPodから利用する
・静的プロビジョニングではクラスタ管理者が作成する
・動的プロビジョニングではStorageClassの設定とPersistenceVolumeClaimの要求に応じてProvisionerが作成する - PersistentVolumeClaim(PVC)
・必要なストレージの要求を表現するためのリソース
・主に抽象的な機能の指定を行う
・PVが自動的にバインドされて利用可能になる
・動的プロビジョニングを利用する場合は管理者が作成したStorageClassを参照することで利用するストレージの詳細を指定する - Provisioner
・要求に対して自動的にストレージのプロビジョニングを行うKubernetesのコントローラ - StorageClass
・クラスタにおいて自動プロビジョニングによって提供できるストレージを表すリソース
・動的プロビジョニングを利用するためにクラスタ管理者が作成する
動的プロビジョニングによってPodにストレージがマウントされるまで流れの簡単に追うと、以下の通りです。
- クラスタ管理者が実際のストレージ装置や利用するためのStorageClassリソースを用意する。必要に応じてCSI Driver等もセットアップする
- ストレージ利用
1. Pod/PVCリソースを利用者が作成
2. ProvisionerがPVCを検知する
3. Provisionerがストレージ領域を作成する
4. Provisionerがストレージ利用に必要なPVを作成する
5. 作成したPVにPVCをバインドする
6. PodがPVCをマウントしてストレージを利用する
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- Oracle Cloud Hangout Cafe Season7 #1「Kubnernetes 超入門」(2023年6月7日開催)
- CSIによるKubernetesのストレージ機能
- 詳解KubernetesにおけるPersistentVolume
- CNDT2020シリーズ:ヤフージャパンのインフラを支えるゼットラボが語るKubernetesストレージの深い話
- kustomizeで復数環境のマニフェストファイルを簡単整理
- KubernetesのWorkloadsリソース(その2)
- OpenShift:アプリケーションの構成と運用
- KubernetesのマニフェストをMagnumで実行する
- KubernetesのConfig&Storageリソース(その1)
- Kubernetesアプリケーションのモニタリングことはじめ