sitateru tech blog: kubernetes

sitateru tech blog

シタテルの技術やエンジニアの取り組みを紹介するテックブログです。

ラベル kubernetes の投稿を表示しています。 すべての投稿を表示
ラベル kubernetes の投稿を表示しています。 すべての投稿を表示

2021年10月14日木曜日

Amazon EFSを使ってEKSに永続ボリュームを導入した

10月 14, 2021

AWS EKSで動かしているアプリケーション用にEFSを使って永続ボリュームを用意してみたので、その方法をまとめてみようと思います。

とあるテスト用Kubernetes環境でMySQLのイメージを使ってデータベースを動かしているのですが、何かの拍子にpodが一旦削除などされてしまうとデータベースの中身のデータが全て無くなってしまいます。
そこで永続ボリュームを確保しておいて、MySQLデータ用のディレクトリをマウントしたボリュームにすればOKというわけです。

手順はほぼこのドキュメントどおりですが、一部変えないと動かなかった箇所があるのでその点も触れつつ書いてみようと思います。
Amazon EFS CSI ドライバー

ドキュメントに従っても動かないよ!という件のissueはこちら。
Missing permission in the example IAM policy file · Issue #489 · kubernetes-sigs/aws-efs-csi-driver · GitHub

また、今回やってみた環境は以下のようになっています。
Kubernetes v1.21
EKSプラットフォーム eks.2
EFS CSI ドライバー v1.3.4


  1. Amazon EFS CSI ドライバー

まずはIAMポリシーを作成します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "elasticfilesystem:DescribeAccessPoints",
                "elasticfilesystem:DescribeFileSystems",
                "elasticfilesystem:DescribeMountTargets",
                "ec2:DescribeAvailabilityZones"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticfilesystem:CreateAccessPoint"
            ],
            "Resource": "*",
            "Condition": {
                "StringLike": {
                    "aws:RequestTag/efs.csi.aws.com/cluster": "true"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": "elasticfilesystem:DeleteAccessPoint",
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "aws:ResourceTag/efs.csi.aws.com/cluster": "true"
                }
            }
        }
    ]
}

AmazonEKS_EFS_CSI_Driver_Policy というポリシーを作成してこのjsonでアクセス権限を設定します。

AWSのドキュメントからリンクされているポリシーと比べると"elasticfilesystem:DescribeMountTargets", "ec2:DescribeAvailabilityZones"が増えているのですが、これを追加しないと権限不足のエラーが出てドライバーが動きませんでした。🤔

このポリシーでサービスアカウントを作成します。<cluster-name><Account ID>, <region>はお使いの環境に合わせて置き換えてください。

$ eksctl create iamserviceaccount \
    --name efs-csi-controller-sa \
    --namespace kube-system \
    --cluster <cluster-name> \
    --attach-policy-arn arn:aws:iam::<Account ID>:policy/AmazonEKS_EFS_CSI_Driver_Policy \
    --approve \
    --override-existing-serviceaccounts \
    --region <region>
$ eksctl create iamserviceaccount \
    --name efs-csi-node-sa \
    --namespace kube-system \
    --cluster <cluster-name> \
    --attach-policy-arn arn:aws:iam::<Account ID>:policy/AmazonEKS_EFS_CSI_Driver_Policy \
    --approve \
    --override-existing-serviceaccounts \
    --region <region>

Helmを使ってドライバーをクラスターにインストールします。
クラスターのリージョンによってimage.repositoryは変えないといけないようなので↓を参照してください。
Amazon EKS アドオンコンテナイメージのアドレス - Amazon EKS

$ helm repo add aws-efs-csi-driver https://kubernetes-sigs.github.io/aws-efs-csi-driver/
$ helm repo update
$ helm upgrade -i aws-efs-csi-driver aws-efs-csi-driver/aws-efs-csi-driver \
    --namespace kube-system \
    --set image.repository=602401143452.dkr.ecr.ap-northeast-1.amazonaws.com/eks/aws-efs-csi-driver \
    --set controller.serviceAccount.create=false \
    --set controller.serviceAccount.name=efs-csi-controller-sa \
    --set node.serviceAccount.create=false \
    --set node.serviceAccount.name=efs-csi-node-sa

AWSのドキュメントでは efs-csi-node-sa は出てこないのですが、こちらも試した環境ではこのサービスアカウントを作成してhelmで指定しないと動きませんでした。🤔


  1. EFS ファイルシステム作成

ここはインフラ管理の都合上terraformで行いました。
セキュリティグループ、ファイルシステム、マウントターゲット(AWSコンソールでは「ネットワーク」という表示名になっています)を作成します。
VPCに複数のプライベートサブネットがある場合は aws_efs_mount_target も同様に複数作っておきましょう。

resource "aws_security_group" "efs" {
  name   = "eks-${var.cluster_name}-efs"
  # ↓クラスターがあるVPCのid
  vpc_id = var.vpc_id
  ingress {
    from_port   = 2049
    to_port     = 2049
    protocol    = "tcp"
    # ↓VPCのCIDR
    cidr_blocks = [var.vpc_cidr]
  }
}
resource "aws_efs_file_system" "fs" {
  creation_token = "eks-${var.cluster_name}-fs"
  tags = {
    "Name" = "eks-${var.cluster_name}-fs"
  }
}
resource "aws_efs_mount_target" "az" {
  file_system_id  = aws_efs_file_system.fs.id
  # ↓プライベートサブネットのID
  subnet_id       = var.subnet_private.id
  security_groups = [aws_security_group.efs.id]
}

  1. クラスターでStorageClassを追加

まずはストレージクラスの宣言です。StorageClassはnamespaceに属していないので、一度applyしておけばOKです。
parametersについてはこちらを参考にしてください👇

aws-efs-csi-driver/examples/kubernetes/dynamic_provisioning at master · kubernetes-sigs/aws-efs-csi-driver · GitHub

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: efs-sc
provisioner: efs.csi.aws.com
parameters:
  provisioningMode: efs-ap
  # EFSで作成したファイルシステムのID
  fileSystemId: fs-00abcdef123456789
  directoryPerms: "700"

  1. アプリケーションで永続ボリュームを確保、マウント

これで準備ができたので、PersistentVolumeを使うアプリケーションでのyamlを書いていきます。

まずはPVC(PersistentVolumeClaim)です。
storageClassName は3.で宣言したStorageClassのnameですね。

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

このPVCで確保したボリュームをコンテナにマウントします。
以下のyamlはdeploymentでやる場合のvolumeに関するところを書き出したものです。 claimName がPVCリソースのnameですね。

apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
        - name: mysql
          image: mysql:8.0.25
          volumeMounts:
            - name: persistent-storage
              mountPath: /var/lib/mysql
      volumes:
        - name: persistent-storage
          persistentVolumeClaim:
            claimName: efs-claim

と、このような作業で永続ボリュームを利用できました。

冒頭で触れたドキュメント不完全問題もあって少し手間取りましたが、振り返ってまとめてみるとそう難しいことはなさそうですね。

EKSで永続ボリュームが欲しくなったときはお試しください👐

2019年12月19日木曜日

安全にkubectl applyするコマンドを作った

12月 19, 2019
どうも朝野です。
最近 本番環境でやらかしちゃった人 Advent Calendar が話題ですね。 私も楽しく読ませてもらっています。

かくいう私も先日、kubernetes環境で作成中のアプリケーションを間違って別アプリケーション用のnamespaceにデプロイしてしまうという事故をやってしまいました😇

何をどうミスしたかというと、
  • 新しく作るnamespace以下にkubectl applyでアプリケーションをデプロイしようとした
  • 対象のyamlファイルは別プロジェクトのソースファイルをコピーして作ったものだった
  • yaml内に書かれているapply先namespaceが別プロジェクトのnamespaceのままだった
ということだったのです。
普段kubernetes環境へのapplyは原則CircleCI内で行っているのですが、今回は環境構築中なアプリケーションだったので手動で実行しようとしていたのでした。

上書きしてしまったほうもデモ用環境のようなもので利用者もいなかったため、大事には至らなかったのは不幸中の幸いでした。
実行前に確認しなかった私が悪いのですが、これは何とかして防げるようにしたいなあということで、applyする前にyaml内のnamespaceをチェックしておかしかったら止める、というシェルスクリプトを作りました。

作るにあたっては以下のような前提があります。
  • 環境の切り替え管理にkustomizeを使っている
  • 各環境のyamlは k8s/overlays/{環境名} ディレクトリに置いて、kubectl apply -k k8s/overlays/{環境名}でデプロイ
  • デプロイ先namespaceは {リポジトリ名}-{環境名} に統一する
というわけで作ってみたのがこちら。

#!/bin/sh
# check whether namespace is {repository}-{env} correctly or not

kubectl kustomize $1 > /dev/null 2> /dev/null
if [ $? != 0 ]; then
  /bin/echo 'usage: kubeapply [kustomize_dir]'
  exit 1
fi

PROCEED=0
REPOSITORY_NAME=$(git rev-parse --show-toplevel | rev | cut -f1 -d'/' | rev)
NAMESPACES=$(kubectl kustomize $1 | grep namespace | sed -E 's/namespace:(.*)/\1/' | tr -d ' ' | sort | uniq)
OVERLAY_DIR=$(echo $1/ | tr -s '/' | rev | cut -f2 -d'/' | rev)
FOUND=()

for NAMESPACE in $NAMESPACES
do
  if [ ${NAMESPACE} != ${REPOSITORY_NAME}-${OVERLAY_DIR} ]; then
    FOUND+=("${NAMESPACE}")
    PROCEED=0
  else
    PROCEED=1
  fi
done

if [ ${PROCEED} = 0 ]; then
  /bin/echo "expected namespace is '${REPOSITORY_NAME}-${OVERLAY_DIR}'"
  /bin/echo "but, namaspace '${FOUND[@]}' found in kustomize"
  /bin/echo -n "proceed apply? (yes/No): "
  read ANSWER
  if [ ${ANSWER} = 'yes' ]; then
    PROCEED=1
  fi
fi

if [ ${PROCEED} = 1 ]; then
  kubectl apply -k $@
else
  echo "stop applying"
fi

これを私はkubeapplyという名前にしてPATHが通っているところに置いてます。
$ kubeapply k8s/overlays/dev のように実行すればOKです。

やっていることは、
  1. 引数がkustomizeディレクトリとして正しいかチェック
  2. kustomize結果内のnamespaceを抽出
  3. 抽出されたnamespaceが{リポジトリ名}-{環境名(=引数のディレクトリ名)}と一致しているか確認
  4. すべて一致していれば kubectl apply -k {引数} を実行
  5. 一致していないものがあればプロンプトを表示、yesと回答したらapplyを実行
というフローとなります。

力技っぽさもある気がしますが、これで同じようなパターンの事故はそうそう再発しなくなるので、一歩前進です🤜🏻

kubernetesはyamlを食わせるだけでアプリケーションに必要なものが一式出来上がるのが便利だなあとずっと思っているのですが、その分気軽に環境を吹き飛ばすこともできてしまうので注意して作業しないといけないですね。