Amazon Web Services ブログ

Kubernetes における永続ストレージ

この記事は Persistent storage for Kubernetes (記事公開日: 2022 年 11 月 22 日) を翻訳したものです。

ステートフルアプリケーションを適切に実行するためには、データが永続化され取得できることが必要です。Kubernetes を使用してステートフルアプリケーションを実行する場合、コンテナ、Pod、ノードのクラッシュや終了に関係なく、状態を永続化する必要があります。これには、永続ストレージ、すなわち、コンテナ、Pod、またはノードの生存期間を超えて存続するストレージが必要です。

このブログ記事では、Kubernetes 環境における永続ストレージのコンセプトと、Kubernetes の世界におけるストレージオプションについて取り上げます。そして、ホスト、Pod、コンテナレベルで障害や終了が発生した場合のデータ損失の懸念を軽減する、Kubernetes 上でのステートフルアプリケーションの設計と構築について説明します。

Kubernetes の永続ストレージは、特にストレージの初心者や、Kubernetes を使い始めようとしている人にとっては複雑なトピックです。そのため、私たちはこの 2 つのブログ記事シリーズを作成しました。最初に概念と用語について説明し、次に実用的なユースケースに踏み込みます。

  • Part 1 [このブログ記事]: この最初のパートでは、Kubernetes の永続ストレージの概念を説明し、Amazon Elastic Kubernetes Service (Amazon EKS) と永続ストレージとして Amazon Elastic File System (Amazon EFS) を使用して、基本的なワークロードにこれらの概念を適用する方法について解説します。
  • Part 2: Amazon EKS 上で Kubeflow を使用して実行される、永続ストレージを必要とする機械学習ワークロードの例を詳しく掘り下げます。

Kubernetes におけるデータの永続化

ステートフルアプリケーションを実行し、永続ストレージがない場合、データは Pod やコンテナのライフサイクルにに関連付けられます。Pod がクラッシュしたり終了したりすると、データは失われます。

Kubernetes と Pod

このデータ損失を防ぎ、Kubernetes 上でステートフルなアプリケーションを実行するには、以下の 3 つのシンプルな要件に従うストレージが必要です。

  1. ストレージは、Pod のライフサイクルに依存しないようにする必要があります。
  2. ストレージは、Kubernetes クラスター内のすべての Pod とノードから利用可能である必要があります。
  3. ストレージは、クラッシュやアプリケーションの障害に関係なく、高い可用性を持つ必要があります。

Kubernetes ボリューム

Kubernetes にはいくつかの種類のストレージオプションが用意されていますが、そのすべてが永続的であるわけではありません。

エフェメラルストレージ

コンテナは一時的なファイルシステムを使用してファイルの読み取りと書き込みを行うことができます。しかし、このエフェメラルストレージはストレージに必要な 3 つの要件を満たしていません。コンテナがクラッシュした場合、一時的なファイルシステムは失われ、コンテナは再びまっさらな状態から開始されます。また、複数のコンテナで一時的なファイルシステムを共有することはできません。

エフェメラルボリューム

Kubernetes のエフェメラルボリュームは、一時的なファイルシステムが直面する両方の課題を解決します。エフェメラルボリュームの生存期間は Pod に連動します。これにより、コンテナの安全な再起動と、Pod 内のコンテナ間でのデータの共有が可能になります。しかし、Pod が削除されるとすぐにエフェメラルボリュームも削除されるため、依然として 3 つの要件を満たしていません。

Pod とボリューム

一時的なファイルシステムはコンテナのライフサイクルに連動し、エフェメラルボリュームは Pod のライフサイクルに連動します。

ストレージから Pod を切り離す: 永続ボリューム

Kubernetes は永続ボリューム (Persistent Volume) もサポートしています。Persistent Volume を使用すると、アプリケーション、コンテナ、Pod、ノード、あるいはクラスター自体のライフサイクルに関係なく、データが永続化されます。Persistent Volume は、先に説明した 3 つの要件を満たします。

Persistent Volume (PV) オブジェクトは、アプリケーションデータを永続化するために使用されるストレージボリュームを表します。PV には、Kubernetes Pod のライフサイクルとは異なる、独自のライフサイクルがあります。

PV は基本的に 2 つの異なるもので構成されいます。

  • PersistentVolume と呼ばれるバックエンドストレージ技術
  • ボリュームのマウント方法を Kubernetes に指示するアクセスモード

バックエンドストレージ技術

PV は抽象的なコンポーネントであり、実際の物理的なストレージはどこかから提供される必要があります。以下はいくつかの例です。

  • csi: Container Storage Interface (CSI) → (例: Amazon EFSAmazon EBSAmazon FSx など)
  • iscsi: iSCSI (SCSI over IP) ストレージ
  • local: ノードにマウントされたローカルストレージデバイス
  • nfs: Network File System (NFS) ストレージ

Kubernetes は汎用性が高く、さまざまなタイプの PV をサポートしています。Kubernetes は基盤となるストレージの内部構造を気にしません。実際のストレージへのインターフェースとして PV コンポーネントを提供するだけです。

PV には 3 つの大きなメリットがあります。

  • PV は Pod のライフサイクルにバインドされていません。PV オブジェクトにアタッチされた Pod を削除しても、PV は存続します。
  • Pod がクラッシュした場合にも前述の記述は有効です。すなわち、PV オブジェクトは Pod がクラッシュしても PV は存続し、クラスターから削除されません。
  • PV はクラスター全体で利用可能です。クラスター内の任意のノードで動作している任意の Pod にアタッチできます。

さまざまなバックエンドストレージ技術には、それぞれ独自のパフォーマンス特性とトレードオフがあります。このため、本番環境の Kubernetes では、アプリケーションに応じてさまざなタイプの PV が使用されます。

アクセスモード

アクセスモードは PV 作成時に設定され、ボリュームのマウント方法を Kubernetes に伝えます。Persistent Volume は以下の 3 つのアクセスモードをサポートします。

  • ReadWriteOnce: ボリュームは、同時に 1 つのノードのみによる読み取り/書き込みが可能です。
  • ReadOnlyMany: ボリュームは、多くのノードによる読み取り専用モードを同時に許可します。
  • ReadWriteMany: ボリュームは、同時に複数のノードによる読み取り/書き込みが可能です。

訳注: 他に ReadWriteOncePod というアクセスモードがあり、Kubernetes 1.27 からベータ版となっています。

すべての PersistentVolume タイプがすべてのアクセスモードをサポートしているわけではありません。

永続ボリューム要求 (Persistent Volume Claim)

PersistentVolume (PV) は、実際のストレージボリュームを表します。Kubernetes には、PV を Pod にアタッチするために必要な追加の抽象化レイヤーである PersistentVolumeClaim (PVC) があります。

PV は実際のストレージボリュームを表し、PVC は実際のストレージを取得するために Pod が行うストレージのリクエストを表します。

PV と PVC の分離は、Kubernetes 環境においては 2 つの異なる役割が存在するという考えに関連しています。

  • Kubernetes 管理者: クラスターの保守および運用し、永続ストレージなどの計算リソースの追加を行います。
  • Kubernetes アプリケーション開発者: アプリケーションの開発とデプロイを行います。

簡単に言えば、アプリケーション開発者は管理者が提供する計算リソースを消費します。Kubernetes は、PV オブジェクトはクラスター管理者のスコープに属し、PVC オブジェクトはアプリケーション開発者のスコープに属するという考えに基づいて構築されています。

基本的に、Pod は PV オブジェクトを直接マウントできません。明示的に要求する必要があります。そしてその要求動作は、PVC オブジェクトを作成して Pod にアタッチすることで実現します。これが、この抽象化レイヤーが存在する唯一の理由です。PVC と PV は 1 対 1 の対応関係にあります (PV は 1 つの PVC にしか関連付けることができません) 。

Persistent Volume と Persistent Volume Claim

このブログ記事では、Pod に永続ストレージをアタッチするこのプロセスをデモします。しかし、その前に、CSI ドライバーに関する背景を説明する必要があります。

Container Storage Interface (CSI) ドライバー

Container Storage Interface (CSI) は、さまざまなストレージソリューションを Kubernetes において使いやすくするために設計された抽象化です。さまざまなストレージベンダーは、CSI 標準を実装する独自のドライバーを開発し、ストレージソリューションが (基盤となるストレージソリューションの内部構造に関係なく) Kubernetes と連携できるするようにすることができます。AWS には、Amazon EBSAmazon EFSAmazon FSx for Lustre などのための CSI プラグインがあります。

静的プロビジョニング

「永続ボリューム要求 (Persistent Volume Claim)」セクションで説明した内容では、まず管理者が 1 つまたは複数の PV を作成し、次にアプリケーション開発者が PVC を作成します。これは静的プロビジョニングと呼ばれます。Kubernetes で PV と PVC を手動で作成する必要があるため、静的です。規模が大きくなると、特に数百の PV や PVC を管理する場合、この方法では管理が困難になる可能性があります。

例えば、Amazon EFS ファイルシステムを作成して PV オブジェクトとしてマウントしたいとします。静的プロビジョニングを使用する場合、次のようにする必要があります。

  • Kubernetes 管理者のタスク
    • Amazon EFS ファイルシステムを作成します。
    • ファイルシステム ID をコピーして PV の YAML 定義ファイルに貼り付けます。
    • YAML ファイルを使用して PV を作成します。
  • Kubernetes アプリケーション開発者のタスク
    • この PV を要求する PVC を作成します。
    • Pod の YAML 定義ファイルにの Pod オブジェクトに PVC をマウントします。

この方法は機能しますが、規模が大きくなると時間がかかります。

動的プロビジョニング

動的プロビジョニングでは、PV オブジェクトを作成する必要はありません。その代わり、PVC を作成すると自動的に PV が作成されます。Kubernetes はストレージクラス (Storage Class) と呼ばれる別のオブジェクトを使ってこれを行います。

Storage Class は、コンテナアプリケーションに使用されるバックエンドの永続ストレージ (Amazon EFS ファイルストレージ、Amazon EBS ブロックストレージなど) のクラスを定義する抽象化です。

Storage Class には基本的に次の 2 つのものが含まれます。

  • Name: これは、Storage Class オブジェクトを一意に識別する名前です。
  • Provisioner: 基盤となるストレージ技術を定義します。例えば、Amazon EFS の場合は efs.csi.aws.com、Amazon EBS の場合は ebs.csi.aws.com となります。

Kubernetes が非常に多くの異なるストレージ技術を扱うことができるのは、Storage Class オブジェクトのおかげです。Pod の観点からは、それが EFS ファイルシステムであろうと、EBS ボリュームであろうと、NFS ドライブであろうと、その他何であろうと、Pod には PVC オブジェクトしか見えません。実際のストレージ技術を扱うすべての基盤となるロジックは、Storage Class オブジェクトが使用する Provisioner によって実装されます。

動的プロビジョニング

Amazon EKS と Amazon EFS を使用した静的プロビジョニングと動的プロビジョニングのデモ

では、学んだことをすべて実践してみましょう。GitHub のこのページを参考に、このデモセクションのための作業環境をセットアップしてください。

訳注: 日本語翻訳にあたり、EKS 1.28 においてデモが動作することを検証しています。バージョンを適宜置き換えて実施してください。

次のコードスニペットでは、5 つのノードを持つ Amazon EKS クラスターを確認できます。

$ kubectl get nodes
NAME                                           STATUS   ROLES    AGE    VERSION
ip-192-168-12-218.us-west-1.compute.internal   Ready    <none>   2d3h   v1.21.5-eks-9017834
ip-192-168-24-116.us-west-1.compute.internal   Ready    <none>   2d3h   v1.21.5-eks-9017834
ip-192-168-46-22.us-west-1.compute.internal    Ready    <none>   2d3h   v1.21.5-eks-9017834
ip-192-168-63-240.us-west-1.compute.internal   Ready    <none>   2d3h   v1.21.5-eks-9017834
ip-192-168-7-195.us-west-1.compute.internal    Ready    <none>   2d3h   v1.21.5-eks-9017834

Kubernetes クラスターの永続ストレージとして Amazon EFS ファイルシステムを使用します。そのためには、まず Amazon EFS CSI ドライバーをインストールする必要があります。

訳注: こちらのドキュメントに従い、EFS CSI ドライバーをインストールしてください。2023 年 8 月のアップデートで、Amazon EFS CSI ドライバーは EKS アドオンとしてインストールすることができるようになりました。

Amazon EFS を使用した静的プロビジョニング

訳注: マネジメントコンソールから EFS ファイルシステムを作成する場合、事前にセキュリティグループを作成し、EFS ファイルシステムのマウントターゲットと関連付ける必要があります。eksctl によって作成された VPC (eksctl-efsworkshop-eksctl-cluster/VPC) に、セキュリティグループを作成し、クラスターセキュリティグループ (eks-cluster-sg-efsworkshop-eksctl-XXXXXXXX) から TCP 2049 ポートへのアクセスを許可するインバウンドルールを作成してください。

まず AWS マネジメントコンソールで Amazon EFS ファイルシステム (myEFS1) を作成しましょう。次のステップで PV を作成する際に必要になるので、FileSystemId を控えておきましょう。

EFS ファイルシステムの作成

すべてをデフォルトのままにして、ファイルシステムを作成します。

訳注: 「カスタマイズ」を選択し、VPC は eksctl-efsworkshop-eksctl-cluster/VPC を選択し、マウントターゲットのセキュリティグループは事前に作成したセキュリティグループを選択してください。

次に、PV のマニフェストファイルを作成し、新しく作成したファイルシステムの FileSystemId を指定します。

#pv.yaml
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: efs-pv
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: ""
  persistentVolumeReclaimPolicy: Retain
  csi:
    driver: efs.csi.aws.com
    volumeHandle: fs-073d77123471b2917

この pv.yaml ファイルにあるように、spec.capacity.storage のサイズを 5 ギビバイト (GiB) としました。これは、PV を作成する際に必須であり、Kubernetes を満足させるための単なるプレースホルダー値です。バックエンドには Amazon EFS ファイルシステムを使用しています。これは完全に伸縮性があり、スケーラブルなファイルシステムです。使用量に応じて自動的にスケールアップまたはスケールダウンされるため、容量を心配する必要はありません。

$ kubectl apply -f pv.yaml 
persistentvolume/efs-pv created

$ kubectl get pv efs-pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
efs-pv   5Gi        RWO            Retain           Available                                   45s

PV の ステータスは Available ですが、まだどの Persistent Volume Claim (PVC) ともバインドされていません。次に、PVC を作成します。

#pvc.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: efs-claim
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: ""
  resources:
    requests:
      storage: 5Gi
$ kubectl apply -f pvc.yaml 
persistentvolumeclaim/efs-claim created

では、PV と PVC のステータスを確認してみましょう。

$ kubectl get pv efs-pv                           
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
efs-pv   5Gi        RWO            Retain           Bound    default/efs-claim                           15m

$ kubectl get pvc efs-claim
NAME        STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
efs-claim   Bound    efs-pv   5Gi        RWO                           103s

PV のステータスが Available から Bound に変わりました。これは、Kubernetes が PVC にマッチするボリュームを見つけることができ、ボリュームがバインドされたことを意味します。

ここで別の PVC を作成しようとすると、PV がもう残っていないため失敗します (PV は 1 つの PVC にのみバインドできます) 。そこで、動的プロビジョニングが役に立ちます。動的プロビジョニングに移る前に、この PVC を使ってサンプルアプリケーション (efs-app) を作成し、データがどのように永続化されるかを確認しましょう。

#pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: efs-app
spec:
  containers:
  - name: app
    image: centos
    command: ["/bin/sh"]
    args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 2; done"]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: efs-claim
$ kubectl apply -f pod.yaml 
pod/efs-app created

kubectl を使用して、データが Amazon EFS ファイルシステムに書き込まれたことが確認できます。

$  kubectl exec -ti efs-app -- tail -f /data/out.txt
Mon Mar 21 23:33:05 UTC 2022
Mon Mar 21 23:33:07 UTC 2022
Mon Mar 21 23:33:09 UTC 2022

以下は静的プロビジョニングのまとめです。

静的プロビジョニング

Amazon EFS を使用した動的プロビジョニング

Amazon EFS CSI ドライバーは、動的プロビジョニングと静的プロビジョニングの両方をサポートしています。EFS の場合、動的プロビジョニングでは各 PV のアクセスポイントが作成されます。つまり、手動で Amazon EFS ファイルシステムを作成し、それを Storage Class パラメータの入力として提供する必要があります。

デフォルトでは、動的プロビジョニングによって作成された各アクセスポイントは、EFS 上の異なるディレクトリにファイルを書き込み、各アクセスポイントは異なる POSIX uid/gid を使用して EFS にファイルを書き込みます。これにより、複数のアプリケーションが同じ EFS ファイルシステムを永続ストレージとして使用しながら、アプリケーション間の分離が実現します。

それでは、動的プロビジョニングに使用する新しい Amazon EFS ファイルシステム (myEFS2) を作成しましょう。

訳注: 静的プロビジョニングで実施した手順と同様に、「カスタマイズ」を選択し、VPC は eksctl-efsworkshop-eksctl-cluster/VPC を選択し、マウントターゲットのセキュリティグループは事前に作成したセキュリティグループを選択してください。

EFS ファイルシステム

次に、Storage Class を作成し、新しく作成されたファイルシステムの FileSystemId を指定します。

#sc.yaml
----
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: efs-sc
provisioner: efs.csi.aws.com
parameters:
  provisioningMode: efs-ap
  fileSystemId: fs-026bb4e33bea77857
  directoryPerms: "700"

Storage Class を作成して確認します。

$ kubectl apply -f sc.yaml 
storageclass.storage.k8s.io/efs-sc created

$ kubectl get sc
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
efs-sc          efs.csi.aws.com         Delete          Immediate              false                  4s
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  2d5h

「動的プロビジョニング」セクションで述べたように、アプリケーションをデプロイする前に PV を作成する必要はありません。したがって、先に進んで PVC と Pod を作成できます。

#pvc_pod_1.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: efs-claim-1
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: efs-sc
  resources:
    requests:
      storage: 5Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: efs-app-1
spec:
  containers:
    - name: app
      image: centos
      command: ["/bin/sh"]
      args: ["-c", "while true; do echo $(date -u) >> /data/out; sleep 5; done"]
      volumeMounts:
        - name: persistent-storage
          mountPath: /data
  volumes:
    - name: persistent-storage
      persistentVolumeClaim:
        claimName: efs-claim-1

PVC と Pod をデプロイしましょう。

$ kubectl apply -f pvc_pod_1.yaml 
persistentvolumeclaim/efs-claim-1 created
pod/efs-app-1 created

$ kubectl get pvc
NAME          STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
efs-claim-1   Bound    pvc-82da05b5-ff85-40a5-8135-50428480fd22   5Gi        RWX            efs-sc         89s
 
$ kubectl get pv | grep efs-sc
pvc-7994fdce-5711-4346-aefb-85e10155ec7c   5Gi        RWX            Delete  

$ kubectl get pods
NAME        READY   STATUS    RESTARTS   AGE
efs-app-1   1/1     Running   0          95s

$ kubectl exec -ti efs-app-1 -- tail -f /data/out
Tue Mar 22 00:38:08 UTC 2022
Tue Mar 22 00:38:13 UTC 2022
Tue Mar 22 00:38:18 UTC 2022

efs-app-1 が正常に実行され、EFS CSI ドライバーによって PV が自動的に作成されたことが確認できました。AWS マネジメントコンソールでは、それぞれのアクセスポイントが確認できます。

EFS ファイルシステムのアクセスポイント

このプロセスを繰り返して別のアプリケーション (efs-app-2) を作成すると、自動的に別のアクセスポイントが作成されます。PV について心配する必要はありません。

以下は、動的プロビジョニングのまとめです。

動的プロビジョニング

クリーンアップ

すべてのエクササイズが終わったら、すべてのリソースを削除しましょう。

  1. すべての Kubernetes リソース (PV、PVC、Pod、Storage Class など) を削除します。
$ kubectl delete -f pod.yaml 
$ kubectl delete -f pvc.yaml 
$ kubectl delete -f pv.yaml
$ kubectl delete -f pvc_pod_1.yaml
$ kubectl delete -f sc.yaml
  1. AWS マネジメントコンソールまたは AWS CLI で EFS ファイルシステム (myEFS1myEFS2) を削除します。
  2. EKS クラスターを削除 (eksctl delete cluster efsworkshop-eksctl) します。

訳注: KMS キー (efsworkshop) 、Cloud9 環境 (efsworkshop) 、IAM ロール (efsworkshop-admin) も削除してください。

まとめ

このブログ記事では、Kubernetes の永続ストレージの基本原則について説明しました。Persistent Volume を使用することで、Pod のクラッシュや終了に関係なくデータが永続化されるステートフルなアプリケーションを Kubernetes 上に作成できます。Persistent Volume は、静的プロビジョニングまたは動的プロビジョニングのいずれかを使用してプロビジョニングできます。基盤となるストレージとして Amazon EFS を使用して、EKS クラスター上で両方のデモを行いました。

基本的なことはすべて学んだので、次はより実用的なユースケースに移ります。このブログ記事シリーズの Part 2 では、Kubeflow を使って機械学習ワークロードを実行するために永続ストレージを利用します。

このブログ記事をお読みいただきありがとうございます。コンテナでの EFS の使用に関するその他のチュートリアルについては、amazon-efs-developer-zone GitHub リポジトリにアクセスしてください。

翻訳はプロフェッショナルサービスの杉田が担当しました。原文はこちらです。