2021年10月14日木曜日
Amazon EFSを使ってEKSに永続ボリュームを導入した
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
- 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で指定しないと動きませんでした。🤔
- 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]
}
- クラスターでStorageClassを追加
まずはストレージクラスの宣言です。StorageClassはnamespaceに属していないので、一度applyしておけばOKです。
parametersについてはこちらを参考にしてください👇
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"
- アプリケーションで永続ボリュームを確保、マウント
これで準備ができたので、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するコマンドを作った
最近 本番環境でやらかしちゃった人 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です。やっていることは、
- 引数がkustomizeディレクトリとして正しいかチェック
- kustomize結果内のnamespaceを抽出
- 抽出されたnamespaceが{リポジトリ名}-{環境名(=引数のディレクトリ名)}と一致しているか確認
- すべて一致していれば
kubectl apply -k {引数}
を実行 - 一致していないものがあればプロンプトを表示、yesと回答したらapplyを実行