EKS で永続ボリュームを利用する

はじめに

  • KubernetesK8sクラスタ上のアプリケーションのデータの保全性を確保するために利用するのが永続ボリューム(Persistent Volume = PV)です。
    • 外部ストレージシステムと連携することで、データの損失、破損、想定外の変更などからデータ資産を守ることが出来ます。
  • 本記事では、K8s の PV を Amazon EKS で利用するための基本事項をまとめ、実際に EBS と EFS での利用を試行します。

K8s で利用可能なボリュームの種類

  • K8s クラスタ内部で利用可能なボリュームには、emptyDir, hostPath, 外部ストレージの 3 種類があります。
    • emptyDirhostPath はノード内で利用できる簡便なボリュームです。
    • emptyDir は同一ポッドのコンテナ間でのみボリュームを共有できます。異なるポッド間での共有はできず、ポッドが終了すると一緒に削除されます。
    • hostPath では、異なるポッド間でも同じ永続ボリュームとして共有が可能です。ポッドとともに削除されることはありませんが、異なるノードに配置されたポッド間での共有はできません。
      • 外部ストレージを準備できない簡易的手段として利用できます。
    • 外部ストレージサービスと連携する場合、すべてのノードに外部ストレージシステムへのアクセス経路が実装されている必要があります。
      • ノード停止時でもポッドを他のノードへ退避すれば、アプリケーションの稼働を継続できます。

f:id:linkode-okazaki:20200515110529p:plain

EKS で利用可能なストレージシステム

  • EKS では以下の AWS のサービスを外部ストレージシステムとして利用することが出来ます。

f:id:linkode-okazaki:20200515105927p:plain

Amazon Elastic Block Store(EBS)

  • 公式サイト
  • オンプレで言うところのディスクに相当するものです。
    • ブロックレベルのストレージサービスです。
    • EC2 と組み合わせることで、NFS サーバとして稼働させることが可能です。
  • 単一 AZ の単一 EC2 インスタンスからアクセスが可能です。
  • メリットとしては、パフォーマンスの高さ、バックアップ・リストアの容易さ(スナップショット)、オンプレミスから移行の容易さが挙げられます。
  • デメリットとしては、EC2 が単一障害点になってしまう点、拡張が少し面倒な点(EBS ボリュームの容量を増やす操作が必要になる)、定額課金である点(コストが確保した、EBS の容量分かかる)が挙げられます。

Amazon Elastic File System(EFS)

  • 公式サイト
  • オンプレで言うところの NAS に相当するものです。
  • 複数 AZ の 1~数千の EC2 インスタンスから同時にアクセスすることが可能です。
  • EBS と比較したときのメリットとしては、拡張性の高さ、信頼性の高さ、可用性の高さ、パフォーマンスの高さコスト効率が良さ(従量課金制)が挙げられます。
  • デメリットとしては、EBS に比べるとコストが高い点です(EBSの 3 倍)。

EKS で永続ボリュームの利用方法

  • EKS で永続ボリュームの利用方法を検証します。
  • EC2 に EKS ワーカーノードを作成しておきます。
    • 後に作成する試行用アプリケーションのためにノード数を 6 で設定しておきます。
$ eksctl create cluster \
--name [クラスター名] \
--region [リージョン名] \
--nodegroup-name standard-workers \
--node-type t3.medium \
--nodes 6 \
--nodes-min 1 \
--nodes-max 6 \
--ssh-access \
--ssh-public-key my-public-key.pub

EBS を利用する場合

1. ストレージクラスの作成

  • 上記のコマンドでクラスターを作成した場合、デフォルトで gp2 という名前のストレージクラスが作成されていることがわかります。
$ kubectl get storageclass
NAME            PROVISIONER             AGE
gp2 (default)   kubernetes.io/aws-ebs   11m
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: [ストレージクラス名]
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
  iopsPerGB: "10"
  fsType: ext4 
  • デフォルトのストレージクラスを変更する場合は、下記のコマンドで変更します。
    • アノテーション storageclass.kubernetes.io/is-default-class=true を指定するストレージクラスに付与します。
$ kubectl patch storageclass [ストレージクラス名] -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

2. IAM ポリシーの作成とアタッチ

example-iam-policy.json の中身

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:AttachVolume",
        "ec2:CreateSnapshot",
        "ec2:CreateTags",
        "ec2:CreateVolume",
        "ec2:DeleteSnapshot",
        "ec2:DeleteTags",
        "ec2:DeleteVolume",
        "ec2:DescribeInstances",
        "ec2:DescribeSnapshots",
        "ec2:DescribeTags",
        "ec2:DescribeVolumes",
        "ec2:DetachVolume"
      ],
      "Resource": "*"
    }
  ]
}

  • 上記の内容で Amazon_EBS_CSI_Driver という IAM ポリシーを作成します。
$ aws iam create-policy --policy-name Amazon_EBS_CSI_Driver \
  --policy-document file://example-iam-policy.json
  • 作成した IAM ポリシーを NodeInstanceRole にアタッチします。NodeInstanceRole の ARN は下記のコマンドで取得します。
$ kubectl -n kube-system describe configmap aws-auth
Name:         aws-auth
Namespace:    kube-system
Labels:       <none>
Annotations:  <none>

Data
====
mapRoles:
----
- groups:
  - system:bootstrappers
  - system:nodes
  rolearn: arn:aws:iam::123456789012:role/eksctl-[クラスター名]-nodegroup-standard-w-NodeInstanceRole-XXXXXXXXXXXXX
  username: system:node:{{EC2PrivateDNSName}}

mapUsers:
----
  • 上記コマンドの実行結果の rolearneksctl- 以下の値を利用して、下記のコマンドを実行し、ポリシーをアタッチします。
$ aws iam attach-role-policy \
--policy-arn arn:aws:iam::123456789012:policy/Amazon_EBS_CSI_Driver \
--role-name eksctl-alb-nodegroup-ng-xxxxxx-NodeInstanceRole-xxxxxxxxxx

3. Amazon EBS CSI ドライバーのデプロイ

$ kubectl apply -k "github.com/kubernetes-sigs/aws-ebs-csi-driver/deploy/kubernetes/overlays/stable/?ref=master"
serviceaccount/ebs-csi-controller-sa created
clusterrole.rbac.authorization.k8s.io/ebs-external-attacher-role created
clusterrole.rbac.authorization.k8s.io/ebs-external-provisioner-role created
clusterrolebinding.rbac.authorization.k8s.io/ebs-csi-attacher-binding created
clusterrolebinding.rbac.authorization.k8s.io/ebs-csi-provisioner-binding created
deployment.apps/ebs-csi-controller created
daemonset.apps/ebs-csi-node created
csidriver.storage.k8s.io/ebs.csi.aws.com created

Amazon EBS CSI ドライバーをテストする

$ git clone https://github.com/kubernetes-sigs/aws-ebs-csi-driver.git
  • Amazon EBS ドライバーのテストファイルが含まれているフォルダに移動します。
$ cd aws-ebs-csi-driver/examples/kubernetes/dynamic-provisioning/
  • 下記のコマンドで、テストに必要な K8s リソースを作成します。
$ kubectl apply -f specs/
ここで適用されているマニフェストファイルの中身

strorageclass.yml

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: ebs-sc
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer

claim.yml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ebs-claim
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: ebs-sc
  resources:
    requests:
      storage: 4Gi

pod.yml - このポッドでは、5 秒毎に/data/out.txt に時刻を追記していきます。

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

  • 下記のコマンドで永続ボリュームの確認をします。
$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
pvc-794b3a2c-b276-4e9a-9e0b-7456d94a818a   4Gi        RWO            Delete           Bound    default/ebs-claim   ebs-sc                  80m
  • 次のコマンドで、永続ボリュームの詳細を確認できます。

永続ボリュームの詳細確認コマンドとその結果

$ kubectl describe pv pvc-794b3a2c-b276-4e9a-9e0b-7456d94a818a
Name:              pvc-794b3a2c-b276-4e9a-9e0b-7456d94a818a
Labels:            <none>
Annotations:       pv.kubernetes.io/provisioned-by: ebs.csi.aws.com
Finalizers:        [kubernetes.io/pv-protection external-attacher/ebs-csi-aws-com]
StorageClass:      ebs-sc
Status:            Bound
Claim:             default/ebs-claim
Reclaim Policy:    Delete
Access Modes:      RWO
VolumeMode:        Filesystem
Capacity:          4Gi
Node Affinity:
  Required Terms:
    Term 0:        topology.ebs.csi.aws.com/zone in [us-east-2a]
Message:
Source:
    Type:              CSI (a Container Storage Interface (CSI) volume source)
    Driver:            ebs.csi.aws.com
    FSType:            ext4
    VolumeHandle:      vol-054704dd57632d9c0
    ReadOnly:          false
    VolumeAttributes:      storage.kubernetes.io/csiProvisionerIdentity=1589341279006-8081-ebs.csi.aws.com
Events:                <none>

  • 下記のコマンドで、ポッドが正常に動作していることを確認します。
$ kubectl exec -it app -- cat /data/out.txt
Wed May 13 06:09:19 UTC 2020
Wed May 13 06:09:24 UTC 2020
Wed May 13 06:09:29 UTC 2020
Wed May 13 06:09:35 UTC 2020
Wed May 13 06:09:40 UTC 2020
  • 確認が完了したら、下記のコマンドでアプリを削除します。
$ kubectl delete -f specs/
persistentvolumeclaim "ebs-claim" deleted
pod "app" deleted
storageclass.storage.k8s.io "ebs-sc" deleted

EFS を利用する場合

1. Amazon EFS CSI ドライバーをデプロイする

$ kubectl apply -k "github.com/kubernetes-sigs/aws-efs-csi-driver/deploy/kubernetes/overlays/stable/?ref=master"

2. Amazon EKS クラスターの VPC ID を確認

  • 下記のコマンドを実行します
$ aws eks describe-cluster --name [クラスター名] --query "cluster.resourcesVpcConfig.vpcId" --output text

3. クラスターの VPC の CIDR 範囲を確認

  • 下記のコマンドを実行します。
    • [vpc-id] は 2. で確認した VPC ID です。
$ aws ec2 describe-vpcs --vpc-ids [vpc-id] --query "Vpcs[].CidrBlock" --output text

4. セキュリティグループの作成

$ aws ec2 create-security-group --description efs-test-sg --group-name efs-sg --vpc-id [vpc-id]
{
    "GroupId": "sg-06ac2d516ee57cd85"
}

5. NFS インバウンドルールを追加

  • VPC のリソースが EFS と通信できるように、NFS インバウンドルールを追加します。
    • 下記のコマンドのうち、[group-id] は 4. で出力されたクループ ID、[vpc-cidr] は 3. で確認した CIDR を指定します。
$ aws ec2 authorize-security-group-ingress --group-id [group-id] --protocol tcp --port 2049 --cidr [vpc-cidr]

6. Amazon EKS クラスターに Amazon EFS ファイルシステムを作成

f:id:linkode-okazaki:20200515105932p:plain

Amazon EFS CSI ドライバーをテストする

$ git clone https://github.com/kubernetes-sigs/aws-efs-csi-driver.git
  • Amazon EFS ドライバーのテストファイルが含まれているフォルダに移動します。
$ cd aws-efs-csi-driver/examples/kubernetes/multiple_pods/
$ aws efs describe-file-systems --query "FileSystems[*].FileSystemId" --output text
  • specs/pv.yaml ファイルを編集し、volumeHandle 値を 3. で確認したファイルシステム ID に置き換えます(下記の yaml ファイルの中身も参照)。

  • サンプルアプリケーションをデプロイします。

$ kubectl apply -f specs/
ここで適用されているマニフェストファイルの中身

storageclass.yaml - EBS のときとの違いは、provisioner の値です。

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: efs-sc
provisioner: efs.csi.aws.com

claim.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: efs-claim
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: efs-sc
  resources:
    requests:
      storage: 5Gi

pv.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: efs-pv
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: efs-sc
  csi:
    driver: efs.csi.aws.com
    volumeHandle: [ファイルシステム ID]

pod1.yamlpod2.yaml

apiVersion: v1
kind: Pod
metadata:
  name: app1
spec:
  containers:
  - name: app1
    image: busybox
    command: ["/bin/sh"]
    args: ["-c", "while true; do echo $(date -u) >> /data/out1.txt; sleep 5; done"]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: efs-claim
apiVersion: v1
kind: Pod
metadata:
  name: app2
spec:
  containers:
  - name: app2
    image: busybox
    command: ["/bin/sh"]
    args: ["-c", "while true; do echo $(date -u) >> /data/out2.txt; sleep 5; done"]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: efs-claim

  • 下記のコマンドでポッドが 2 つとも「Running」になるまで待ちます。
$ kubectl get pods --watch
  • デフォルトの名前空間の永続ボリュームを一覧表示します。
$ kubectl get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
efs-pv   5Gi        RWX            Retain           Bound    default/efs-claim   efs-sc                  43s
  • 永続ボリュームの詳細を表示します。

永続ボリュームの詳細確認コマンドとその結果

$ kubectl describe pv efs-pv
Name:            efs-pv
Labels:          <none>
Annotations:     pv.kubernetes.io/bound-by-controller: yes
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    efs-sc
Status:          Bound
Claim:           default/efs-claim
Reclaim Policy:  Retain
Access Modes:    RWX
VolumeMode:      Filesystem
Capacity:        5Gi
Node Affinity:   <none>
Message:
Source:
    Type:              CSI (a Container Storage Interface (CSI) volume source)
    Driver:            efs.csi.aws.com
    FSType:
    VolumeHandle:      [ファイルシステム ID]
    ReadOnly:          false
    VolumeAttributes:  <none>
Events:                <none>

  • ポッドが2つとも正常に動作していることを確認します。
$ kubectl exec -ti app1 -- tail /data/out1.txt
Thu May 14 08:33:50 UTC 2020
Thu May 14 08:33:55 UTC 2020
Thu May 14 08:34:00 UTC 2020
Thu May 14 08:34:05 UTC 2020
Thu May 14 08:34:10 UTC 2020
Thu May 14 08:34:15 UTC 2020
Thu May 14 08:34:20 UTC 2020
$ kubectl exec -ti app2 -- tail /data/out1.txt
Thu May 14 08:33:50 UTC 2020
Thu May 14 08:33:55 UTC 2020
Thu May 14 08:34:00 UTC 2020
Thu May 14 08:34:05 UTC 2020
Thu May 14 08:34:10 UTC 2020
Thu May 14 08:34:15 UTC 2020
Thu May 14 08:34:20 UTC 2020
Thu May 14 08:34:26 UTC 2020
Thu May 14 08:34:31 UTC 2020
  • 確認が完了したら、下記のコマンドでアプリを削除します。
$ kubectl delete -f specs/
persistentvolumeclaim "efs-claim" deleted
pod "app1" deleted
pod "app2" deleted
persistentvolume "efs-pv" deleted
storageclass.storage.k8s.io "efs-sc" deleted
  • 必要なくなった場合は、クラスター自体も削除しておきます。
$ eksctl delete cluster --name [クラスター名]

まとめ

  • K8s のデータの資産を守る永続ボリュームに関する基本事項のまとめと、EKS で利用する試行をしました。
  • EBS と EFS での利用にあたって、各サービスの特徴も確認しました。
  • 次回、K8s のデプロイメントを利用した高可用性構成を EKS 上でやってみたいと思います。

参考資料