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で永続ボリュームが欲しくなったときはお試しください👐
2021年8月27日金曜日
AWS App Mesh をEKSで試してみた(仮想ゲートウェイ)
前回に続き、EKS上でのAWS App Meshをやっていきます。
今回は、仮想ゲートウェイです。
ゲートウェイがあれば、メッシュ上のリソースにメッシュ外のpodやWebなどからアクセスできるようになります💁♂️
今回の内容もドキュメント等いまいち見つからなかったので正確さなどは怪しいところがありますがご了承ください🙇♂️
Virtual gateways - AWS App Mesh
- 仮想ゲートウェイ作成
まずは仮想ゲートウェイとそのルートを作成します。
リスナーのポートは後で作るゲートウェイの実体と番号を合わせれば何番でもよさそうです。ルートは、全てのリクエストを前回作った仮想サービス(svc-v)へ流すよう指定してみました。
apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualGateway
metadata:
namespace: mesh-test
name: gateway-v
spec:
namespaceSelector:
matchLabels:
mesh: mesh-1
podSelector:
matchLabels:
for: gateway
listeners:
- portMapping:
port: 8088
protocol: http
connectionPool:
http:
maxConnections: 1024
logging:
accessLog:
file:
path: /dev/stdout
---
apiVersion: appmesh.k8s.aws/v1beta2
kind: GatewayRoute
metadata:
namespace: mesh-test
name: gateway-v-route-v
spec:
httpRoute:
action:
target:
virtualService:
virtualServiceRef:
name: svc-v
match:
prefix: /
- ゲートウェイ作成
ゲートウェイの実体となる、envoyイメージのコンテナと type: LoadBalancer
な サービスを作成します。
envoyのcontainerPortとサービスのtargetPortは仮想ゲートウェイのリスナにしているポートと等しくする必要があります。
また、envoyの環境変数としてリージョンと仮想ゲートウェイARNを与える必要があるようです。
apiVersion: apps/v1
kind: Deployment
metadata:
name: gateway-v
namespace: mesh-test
spec:
replicas: 1
selector:
matchLabels:
for: gateway
template:
metadata:
labels:
for: gateway
spec:
serviceAccountName: appmesh-proxyauth
containers:
- name: gateway
image: public.ecr.aws/appmesh/aws-apmesh-envoy:v1.19.0.0-prod
env:
- name: AWS_REGION
value: ap-northeast-1
- name: APPMESH_RESOURCE_ARN
value: arn:aws:appmesh:ap-northeast-1:123456780000:mesh/mesh-1/virtualGateway/gateway-v_mesh-test
ports:
- containerPort: 8088
resources:
limits:
cpu: 10m
memory: 32Mi
---
apiVersion: v1
kind: Service
metadata:
name: gateway-v
namespace: mesh-test
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
spec:
type: LoadBalancer
ports:
- name: http
port: 80
targetPort: 8088
selector:
for: gateway
以上をapplyすると、ロードバランサーが作られて仮想ゲートウェイとして動くようになります。
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
gateway-v LoadBalancer 10.100.184.34 aae2598dca4e348b2a934f4cd88b7982-607b093417cc2f4d.elb.ap-northeast-1.amazonaws.com 80:31248/TCP
AWSコンソールでロードバランサーを見てみると、NLBができていました。
コンソールのApp Meshでもゲートウェイが表示されています。
ゲートウェイへのアクセスですが、クラスター内からは
$ curl gateway-v.mesh-test.svc.cluster.local
外からは
$ curl <NLBのドメイン名>
でリクエストを送って、仮想サービスsvc-vに送られることが確認できました。
ということで、App Mesh作ってみるシリーズでした。
今回はごく基本的な部分しか試していませんが、各リソースで設定できる機能はいろいろあるので公式ドキュメントやCustomResourceDefinitionを参照してみてください🙏
eks-charts/crds.yaml at master · aws/eks-charts
ちなみに、App Meshには(他のサービスと同様に)作れるリソース数の制限があります。
ほとんどはサポートセンターに頼めば引き上げ可能なようですが、メッシュ設計の際はちょっと気にしておいたほうがいいかもしれないですね。
AWS App Mesh エンドポイントとクォータ - AWS 全般のリファレンス
2021年6月17日木曜日
AWS App Mesh をEKSで試してみた(仮想ルーター、仮想サービス)
前回に続き、EKS上でのAWS App Meshをやっていきます。
今回は、仮想ルーターと仮想サービスです。
前回作った仮想ノードにアクセスするには仮想ルーターか仮想ゲートウェイが必要になるので、いよいよメッシュができていきます💪
あまりしっかり説明されたドキュメントが見つからず経験ベースな部分が多いので、正しい説明になっているかどうか怪しい部分もありますがご了承ください🙇
- 仮想ルーター作成
仮想ノードへトラフィックを振り分ける仮想ルーターを作成します。
振り分け先は weightedTargets
の virtualNodeRef.name
で仮想ノード名を指定します。
プロトコルはHTTPにしましたが、他にもGRPC, HTTP2, TCPが設定できるようです。
apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualRouter
metadata:
namespace: mesh-test
name: vrouter-1
spec:
listeners:
- portMapping:
port: 80
protocol: http
routes:
- name: route-1
httpRoute:
match:
prefix: /
action:
weightedTargets:
- virtualNodeRef:
name: vnode-1
weight: 1
- virtualNodeRef:
name: vnode-2
weight: 1
- 仮想サービス用サービス作成
仮想サービスの実体にするserviceを作成します。
selecter
は、仮想サービスを介してアクセスする先のpodが全て含まれるように指定する必要があるみたいです。
今回の例だと、仮想サービス → 仮想ルーター(vrouter-1) → 仮想ノード(vnode-1, vnode-2) というトラフィックの流れになるので、vnode-1とvnode-2の実体になるpodに app: sample1
のラベルをつけてあります。
apiVersion: v1
kind: Service
metadata:
name: svc-v
namespace: mesh-test
labels:
app: sample1
spec:
type: ClusterIP
selector:
app: sample1
ports:
- protocol: TCP
port: 80
targetPort: 80
- 仮想サービス作成
先の svc-v
を実体とする仮想サービスを作成します。
name
は上で仮想サービス用に作ったサービス名と等しくする必要があるみたいです。
provider
には仮想サービスを通してアクセスする先を指定を指定します。単一のvirtualNodeかvirtualRouterを選択できるので、トラフィックを複数ノードに分けたいならvirtualRouter、分けなくていいならvirtualNodeにするとよさそうです。
apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualService
metadata:
namespace: mesh-test
name: svc-v
spec:
provider:
virtualRouter:
virtualRouterRef:
name: vrouter-1
- 仮想サービスをバックエンドに設定
作った仮想サービスにアクセスするには、「仮想ノードのバックエンド」にその仮想サービスを指定する必要があります。
仮想ノードのyamlで、backends
に仮想サービスを追加しておきます。
apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualNode
metadata:
name: vnode-x
namespace: mesh-test
spec:
# 主要部分は省略
backends:
- virtualService:
virtualServiceRef:
name: svc-v
仮想ノード化されたPodから仮想サービスへリクエストを送ると、
ルーティングに従ったレスポンスが返ってくるはずです。
$ kubectl exec -it somepod-524b1761b4-vg8kw -- bash
root@somepod-524b1761b4-vg8kw:/# curl svc-v.mesh-test.svc.cluster.local
いろいろ試してみたところ、「バックエンドにその仮想サービスを指定したpod」で「仮想サービス名.名前空間.svc.cluster.local
へトラフィックを送る」場合に仮想サービスで設定したトラフィック振り分けが行われる仕様のようです。
それ以外の条件、例えばバックエンドを指定していないpodからのリクエストや仮想サービス名だけのホスト名にトラフィックを送る($ curl svc-v
)場合は(仮想ではない)Serviceの svc-v
に行くようです。
なかなかややこしいですね。
2021年6月3日木曜日
AWS App Mesh をEKSで試してみた(準備~仮想ノードまで)
突然ですが、AWS App Meshは皆さんお使いでしょうか?
AWS製のサービスメッシュ構築サービスですね。
サービスメッシュといえばIstioが有名ですが、AWS App MeshはAWS製だけあってEKSはもちろんEC2やECSのインスタンス・コンテナもメッシュに組み込むことができるのが強みという印象です。
そんなApp MeshをEKS上でいろいろと実験してみたので、自分の理解の反芻のためにもまとめてみたいと思います。
試した環境は kubernetes 1.20, App Meshコントローラ 1.3.0 でした。
- 準備
ドキュメントに従ってコントローラをインストールします。
Getting started with AWS App Mesh and Kubernetes - AWS App Mesh
手順はドキュメントの通りなのでここでは書きませんが、完了するとappmesh-system
ネームスペースでコントローラのポッドができていました。
$ kubectl get pod -n appmesh-system
NAME READY STATUS RESTARTS AGE
appmesh-controller-6c55b46558-8s363 1/1 Running 0 1d
- mesh作成
メッシュを作成します。
egressFilter
はメッシュ内部から外部へのアクセスを許可するかどうかですね。許可しない場合はDROP_ALLにします。
namespaceSelecter
も何でもいいので書いておきましょう。
apiVersion: appmesh.k8s.aws/v1beta2
kind: Mesh
metadata:
name: mesh-1
spec:
egressFilter:
type: ALLOW_ALL
namespaceSelector:
matchLabels:
mesh: sitateru-mesh
- namespace作成
メッシュのリソースを配置するネームスペースを宣言します。
上のMeshで指定した namespaceSelector
とラベルを合わせておきましょう。
App Meshの機能を使うにはappmesh.k8s.aws/sidecarInjectorWebhook
をenabledにしてEnvoyサイドカーが入れられるようにしておく必要があります。
apiVersion: v1
kind: Namespace
metadata:
name: mesh-test
labels:
mesh: mesh-1
appmesh.k8s.aws/sidecarInjectorWebhook: enabled
- サービスアカウント作成
IAMで以下のポリシーを作成します。
今回は動作検証なので mesh-1
メッシュの全ての仮想ノードと仮想ゲートウェイに対して StreamAggregatedResources
を許可していますが、実使用の際には適宜リソース名を指定するほうが望ましいですね。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "appmesh:StreamAggregatedResources",
"Resource": [
"arn:aws:appmesh:ap-northeast-1:111122223333:mesh/mesh-1/virtualNode/*",
"arn:aws:appmesh:ap-northeast-1:111122223333:mesh/mesh-1/virtualGateway/*"
]
}
]
}
続いてこのポリシーをアタッチしたサービスアカウントをEKSクラスターに作成します。
$ eksctl create iamserviceaccount \
--cluster test-cluster \
--namespace mesh-test \
--name appmesh-proxyauth \
--attach-policy-arn arn:aws:iam::111122223333:policy/appmesh-proxyauth \
--override-existing-serviceaccounts \
--approve
- 仮想ノード用サービス作成
仮想ノードの実体にするdeploymentとserviceを作成します。コンテナイメージは何でもいいのですが、今回はただのnginxにしています。
serviceAccountName
には👆で作成したサービスアカウント名を指定します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-1
namespace: mesh-test
labels:
app: sample0
spec:
selector:
matchLabels:
app: sample0
template:
metadata:
labels:
app: sample0
server: nginx
spec:
serviceAccountName: appmesh-proxyauth
containers:
- name: nginx
image: nginx:1.19.0
ports:
- containerPort: 80
resources:
limits:
cpu: 10m
memory: 10Mi
---
apiVersion: v1
kind: Service
metadata:
name: svc-1
namespace: mesh-test
labels:
app: sample0
spec:
selector:
app: sample0
server: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
- 仮想ノード作成
今作ったsvc-1
を実体とする仮想ノードを作成します。
serviceDiscovery
で仮想ノードとする実体を指定するのですが、ここでは目的のサービスのDNSホスト名( サービス名.名前空間.svc.cluster.local
)を指定します。
ServiceとPodに対するDNS | Kubernetes
ちなみにDNSではなくCloud Mapを使って指定することもできるみたいです。
apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualNode
metadata:
name: vnode-1
namespace: mesh-test
spec:
podSelector:
matchLabels:
app: sample0
listeners:
- portMapping:
port: 80
protocol: http
serviceDiscovery:
dns:
hostname: svc-1.mesh-test.svc.cluster.local
うまくできていればAWSコンソールでもメッシュや仮想ノードが表示されているはずです。
できていない場合は appmesh-controller
ポッドのログを見れば何かわかるかもしれないのでチェックしてみてください。
そこそこ長くなってきたのでここまでにしようと思います。
ここまで作ったものだけではぜんぜんサービスメッシュになっていないのですが、続きは近いうちにまた書きます!
ちなみに、メッシュのリソース作成はkubernetes的にはカスタムリソースの作成になっています。
そのカスタムリソースの定義は👇なので、yamlをどう書けばいいのかこちらを見つつやるのがいいのではないかと思います!
eks-charts/crds.yaml at master · aws/eks-charts