金額的に自分の趣味でEKSやFargateを使うことはないのですが、興味があったので少し触ってみました。
現時点ではドキュメントが揃っておらず、試行錯誤が必要だったので記事に残しておきます。
...と思ったのですが、AWS Advent Calendarが代わりに投稿できそうだったので7日目として投稿します。
qiita.com
この記事で紹介されていることは以下のリポジトリで簡潔に確認することができます。
GitHub - 8398a7/eks-on-fargate
書いてあること
今回紹介する内容のまとめです。
検証はmacOS上から行っています。
- 書いてあること
- ツールの準備
- クラスタの準備
- ALB Ingress Controllerのセットアップ
- target-type: ipの挙動
- hostの割当時間
- iam-for-pods
- ハマったポイント
- 所感
ツールの準備
下記のドキュメントを読んでaws-cli, kubectl, eksctlを利用できるようにしてください。
eksctl の開始方法 - Amazon EKS
eksctlが既に入っている場合でもfargateクラスタを作成するためには0.11以上が必要です。
必要に応じてbrew upgradeしてください。
$ > eksctl version [ℹ] version.Info{BuiltAt:"", GitCommit:"", GitTag:"0.11.1"}
クラスタの準備
eksctl create cluster poc --fargate
eksctlで作成するとそのクラスタで利用するVPCやsubnetの設定を行ってくれます。
下記で触れるドキュメントでsubnet tagにannotationをする手順が書かれていますが、eksctl経由で作成した場合は既に付与されているため不要な手順です。
具体的には以下の部分です。
Key Value
kubernetes.io/role/elb 1
kubernetes.io/role/internal-elb 1
クラスタの作成は割と時間がかかるので、とりあえず叩いて他事をするのがお勧めです。
クラスタが作成された直後はcorednsのpodが動いており、これらもFargateで動作しています。
$ > kubectl get po -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-6d75bbbf58-5vhpf 1/1 Running 0 5m18s kube-system coredns-6d75bbbf58-zttkt 1/1 Running 0 5m18s $ > kubectl get node NAME STATUS ROLES AGE VERSION fargate-ip-192-168-175-13.ap-northeast-1.compute.internal Ready <none> 5m1s v1.14.8-eks fargate-ip-192-168-179-161.ap-northeast-1.compute.internal Ready <none> 5m17s v1.14.8-eks
Fargateで動かすためにはnamespace単位で設定する必要がありますが、eksctlで作成した場合はdefault, kube-systemのpodがFargateで起動するようになっています。
上記以外のnamespaceでpodを動かす場合は現在はnodeが存在しないのでpendingのまま待たされる、といった挙動になります。
ALB Ingress Controllerのセットアップ
公式ドキュメントは ALB Ingress Controller on Amazon EKS - Amazon EKS ですが、EC2で動かす前提で書かれており、Fargateのみの構成では動きません。
具体的にはFargateのpodでIAMの権限を渡す方法がEC2の場合とFargateの場合で異なるのが原因です。
一旦簡易的な方法としてalb-ingress-controllerのDeploymentに直接key/secretを書く方法を紹介します。
prodでは推奨されていない方式なので、ちゃんと使う場合はiam-for-podsを利用しましょう。
refs: Authentication Issues On EKS Cluster with Fargate Policy · Issue #1092 · kubernetes-sigs/aws-alb-ingress-controller · GitHub
iam-for-podsを利用する方式は後述しますが、一旦key/secret方式で続けます。
以下の手順でkey/secretを取得します。
jqを利用しているので、予め brew install jq
を済ませておいてください。
# ALBを操作するためのpolicyを作成 curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/iam-policy.json policyArn=$(aws iam create-policy \ --policy-name ALBIngressControllerIAMPolicy \ --policy-document file://iam-policy.json | jq -r .Policy.Arn) rm iam-policy.json # ALBを操作するためのkey/secret払い出しユーザの作成 aws iam create-user --user-name pocUser # 先程作成したユーザにALBのpolicyを紐付け aws iam attach-user-policy --user-name pocUser --policy-arn $policyArn # ユーザのkey/secretを払い出す aws iam create-access-key --user-name pocUser
最後のコマンドで以下のような出力が得られるのでAccessKeyIdとSecretAccessKeyをメモしておいてください。
{ "AccessKey": { "UserName": "pocUser", "AccessKeyId": "key", "Status": "Active", "SecretAccessKey": "secret", "CreateDate": "2019-12-07T08:30:11Z" } }
ここまで整ったらrbac-roleとalb-ingress-controllerをdeployしていきます。
rbac-roleに関してはドキュメント通りの手順でdeployします。
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/rbac-role.yaml
alb-ingress-controllerに関しては以下のような修正をしたyamlを手元に用意しdeployしてください。 修正点は3点です。
- vpc_idの指定
- keyの指定
- secretの指定
apiVersion: apps/v1 kind: Deployment metadata: labels: app.kubernetes.io/name: alb-ingress-controller name: alb-ingress-controller namespace: kube-system spec: selector: matchLabels: app.kubernetes.io/name: alb-ingress-controller template: metadata: labels: app.kubernetes.io/name: alb-ingress-controller spec: containers: - name: alb-ingress-controller image: docker.io/amazon/aws-alb-ingress-controller:v1.1.4 args: - --ingress-class=alb - --cluster-name=poc - --aws-region=ap-northeast-1 - --aws-vpc-id=vpc-xxxx # eksctlで作成されたVPCのid env: - name: AWS_ACCESS_KEY_ID value: # さっきメモしたkey - name: AWS_SECRET_ACCESS_KEY value: # さっきメモしたsecret resources: {} serviceAccountName: alb-ingress-controller
GitHub上ではv1.1.4タグでもv1.1.3のイメージを利用するようになっているので、v1.1.4に書き換えてあります。
修正できたら以下のコマンドでdeployし、alb-ingress-controllerがRunningになるまで待ちましょう。
kubectl apply -f alb-ingress-controller.yaml watch -n1 kubectl get po -n kube-system
次にnginxをALBからアクセスするためのyamlを記述します。
--- apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: nginx annotations: kubernetes.io/ingress.class: alb alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/target-type: ip labels: app: nginx spec: rules: - http: paths: - path: /* backend: serviceName: nginx servicePort: 80 --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: selector: matchLabels: app: nginx replicas: 1 template: metadata: labels: app: nginx spec: containers: - image: nginx name: nginx ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx spec: ports: - port: 80 targetPort: 80 protocol: TCP type: ClusterIP selector: app: nginx
ポイントは alb.ingress.kubernetes.io/target-type: ip
のannotationsをつけることです。
ingressリソースが作られるとalb-ingress-controllerに以下のようなログが出ます。
ログは kubectl logs -n kube-system $(kubectl get po -n kube-system -o name | grep alb | cut -d/ -f2) -f
で確認してください。
default/nginx: granting inbound permissions to securityGroup sg-0c4ff2ae847363695: [{ FromPort: 80, IpProtocol: "tcp", IpRanges: [{ CidrIp: "0.0.0.0/0", Description: "Allow ingress on port 80 from 0.0.0.0/0" }], ToPort: 80 }] default/nginx: creating LoadBalancer 0d836fa6-default-nginx-ef8b
ingressの結果を確認してブラウザでアクセスしてみましょう。
$ > kubectl get ing NAME HOSTS ADDRESS PORTS AGE nginx * 0d836fa6-default-nginx-ef8b-28953798.ap-northeast-1.elb.amazonaws.com 80 4m2s $ > open http://$(kubectl get ing -o jsonpath='{.items[].status.loadBalancer.ingress[].hostname}')
nginxのページが表示できたら成功です。
ここまでで起動されたhost数は4台でした。
$ > kubectl get node NAME STATUS ROLES AGE VERSION fargate-ip-192-168-111-46.ap-northeast-1.compute.internal Ready <none> 9m26s v1.14.8-eks fargate-ip-192-168-126-238.ap-northeast-1.compute.internal Ready <none> 8m4s v1.14.8-eks fargate-ip-192-168-151-139.ap-northeast-1.compute.internal Ready <none> 16m v1.14.8-eks fargate-ip-192-168-170-198.ap-northeast-1.compute.internal Ready <none> 16m v1.14.8-eks
以下の手順でリソースを掃除してください。
kubectl delete -f app.yaml # nginxのサンプルアプリ kubectl delete -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/rbac-role.yaml kubectl delete -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/alb-ingress-controller.yaml eksctl delete cluster poc userId=$(aws sts get-caller-identity | jq -r .UserId) aws iam delete-policy --policy-arn arn:aws:iam::${userId}:policy/ALBIngressControllerIAMPolicy
pocUserに関してはaws consoleから手動で削除をお願いします。
target-type: ipの挙動
alb.ingress.kubernetes.io/target-type: ip
というannotationが初見だったので、どういう挙動なのか見てみました。
LBが作られた際にターゲットグループが種類: ipで作られてFargate hostのprivate ipがroutingされていました。
試しにdeploymentのreplicasを1->2に更新してみると以下のようにログが出て登録済みターゲットが自動的に更新されていました。
Adding targets to arn:aws:elasticloadbalancing:ap-northeast-1:xxx:targetgroup/yyy/zzz modifying rule 1 on arn:aws:elasticloadbalancing:ap-northeast-1:xxx:listener/app/yyy/zzz default/nginx: rule 1 modified with conditions [{ Field: "path-pattern", Values: ["/*"] }]
replicasを1に戻すとちゃんと外してくれます。
default/nginx: Removing targets from arn:aws:elasticloadbalancing:ap-northeast-1:xxx:targetgroup/yyy/zzz: 192.168.147.236:80, 192.168.106.177:80 default/nginx: modifying rule 1 on arn:aws:elasticloadbalancing:ap-northeast-1:xxx:listener/app/yyy/zzz default/nginx: rule 1 modified with conditions [{ Field: "path-pattern", Values: ["/*"] }]
この辺は当たり前とはいえば当たり前ですが、ちゃんとintegrationされてて良いですね。
実装を読んでいないですが、target-type: ipのannotationがついたingressをwatchしてsvc->deployまで辿ってreplicasを監視しているのでしょうか。
ターゲットグループ設定を見てみると、登録解除の遅延がデフォルト300秒で作られており、外されるまでの時間差が少し気になりました。
annotationで指定できるのかな、と調べてみたところドキュメントにちゃんと載っており、試してみると反映されていました。
default/nginx: Modifying TargetGroup arn:aws:elasticloadbalancing:ap-northeast-1:xxx:targetgroup/yyy/zzz attributes to [{ Key: "deregistration_delay.timeout_seconds", Value: "30" }]. default/nginx: modifying rule 1 on arn:aws:elasticloadbalancing:ap-northeast-1:xxx:listener/app/yyy/zzz default/nginx: rule 1 modified with conditions [{ Field: "path-pattern", Values: ["/*"] }]
alb.ingress.kubernetes.io/target-group-attributes: deregistration_delay.timeout_seconds=30
refs: Annotation - AWS ALB Ingress Controller
300秒は長すぎ、短すぎという場合もちゃんと設定できるようになってて良いですね。
ここでpodが消えるタイミングとLBから外されるタイミングは「いい感じ」になっているのかと思って調べてみました。
ここで言う「いい感じ」とは
- ターゲットグループから外されるまでpodのterminateが待機される
- ターゲットグループから外されたことを確認してpodのterminateが実行される
ということです。
実際試してみると、podが削除と同時にターゲットグループの削除処理が走るようになっていました。
これでは外されるまでデフォルトの300秒delayのときに正常に動作しないのでは?と確かめてみたところ、一瞬504 gateway timeoutが出る挙動を確認しました。
ただし一瞬だけですぐに200しか返ってこなくなりました。
ターゲットグループをあまり使ったことがないので推測になりますが、deregistrationが走るとアクセスが新規に来ることはないものの、
podの削除が先行して走り、LB側で外す前にアクセスを流されてしまうと504が出るケースがあるのではないかと思います。
HPAをしっかり使っているアクセスが多いサービスのケースだと504はそこそこ出てしまうかもしれません。
これは「ing->svc->deployのreplicas数が変更していたらターゲットグループ変更」という検知パターンと相性が悪そうだと思います。
検知パターンの周期の合間に入ってpodが削除される方が先になるケースは多々あると思うので、504は避けにくいです。
deployment定義の preStop
でalb-ingress-controller側が十分検知できる時間のsleepを挟んでpodを止めるようにする、などがワークアラウンドになるかもしれません。
十分に検証できていないポイントなのでもっと良い方法があればコメント等をもらえると嬉しいです。
hostの割当時間
大体30秒弱でステータスがPendingからContainerCreatingになります。
そこからイメージのpullが始まり、Runningになるまで大体1分弱といった感じでした。
今回はnginxイメージで非常に小さいサイズなのでpull時間があまり影響しませんでしたが、1GB+のイメージpullは少し時間がかかるかもしれません。
とはいっても、EKSというよりFargateの特性に由来するものなのでECSを使われている場合は既知かもしれませんが…。
iam-for-pods
先にkey/secretを使った方法を紹介しましたが、iam-for-podsを使った方法も紹介します。
もしもkey/secretを使う方式で試していたら一旦alb-ingress-controllerのリソースを念の為削除してください。
kubectl delete -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/rbac-role.yaml kubectl delete -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/alb-ingress-controller.yaml
クラスタに対してoidc providerを紐付けてからALBのIAM権限を付与します。
policyArnは上で作成したALBIngressControllerIAMPolicyが存在していればそれを流用してください。
eksctl utils associate-iam-oidc-provider --region=ap-northeast-1 --cluster=poc --approve curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/iam-policy.json policyArn=$(aws iam create-policy \ --policy-name ALBIngressControllerIAMPolicy \ --policy-document file://iam-policy.json | jq -r .Policy.Arn) eksctl create iamserviceaccount --name alb-ingress-controller \ --namespace kube-system \ --cluster poc \ --attach-policy-arn ${policyArn} \ --approve --override-existing-serviceaccounts
作成すると、以下のような情報が取れます。
$ > kubectl get sa -n kube-system alb-ingress-controller -o jsonpath="{.metadata.annotations['eks\.amazonaws\.com/role-arn']}" arn:aws:iam::xxx:role/eksctl-poc-addon-iamserviceaccount-ku-Role1-CBGA2Q1975Q9
次にalb-ingress-controller.yamlをデプロイします。
先ほどと違ってenvでkey/secretの指定を行いません。
apiVersion: apps/v1 kind: Deployment metadata: labels: app.kubernetes.io/name: alb-ingress-controller name: alb-ingress-controller namespace: kube-system spec: selector: matchLabels: app.kubernetes.io/name: alb-ingress-controller template: metadata: labels: app.kubernetes.io/name: alb-ingress-controller spec: serviceAccountName: alb-ingress-controller containers: - name: alb-ingress-controller image: docker.io/amazon/aws-alb-ingress-controller:v1.1.4 args: - --ingress-class=alb - --cluster-name=poc - --aws-region=ap-northeast-1 - --aws-vpc-id=vpc-xxxx # eksctlで作成されたVPCのid resources: {}
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/rbac-role.yaml kubectl apply -f alb-ingress-controller.yaml
alb-ingress-controllerのpodが作成されたら上記で記載したnginxを動かすためのリソースをapplyしてください。
envでkey/secretを指定しない場合と同様にLBが作られ、アクセスできるようになっています。
最初はoidc id providerの概念がややこしそうだったので敬遠していたのですが、このあたりも割と楽に設定できるようになっていますね。
eksctlでiamserviceaccountを作ると裏側でCloudFormationが動いているのですが、中ではoidc id providerへの紐付けをやってくれているようです。
{ "AWSTemplateFormatVersion": "2010-09-09", "Description": "IAM role for serviceaccount \"kube-system/alb-ingress-controller\" [created and managed by eksctl]", "Resources": { "Role1": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { "Statement": [ { "Action": [ "sts:AssumeRoleWithWebIdentity" ], "Condition": { "StringEquals": { "oidc.eks.ap-northeast-1.amazonaws.com/id/yyy:aud": "sts.amazonaws.com", "oidc.eks.ap-northeast-1.amazonaws.com/id/yyy:sub": "system:serviceaccount:kube-system:alb-ingress-controller" } }, "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::xxx:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/yyy } } ], "Version": "2012-10-17" }, "ManagedPolicyArns": [ "arn:aws:iam::xxx:policy/ALBIngressControllerIAMPolicy" ] } } }, "Outputs": { "Role1": { "Value": { "Fn::GetAtt": "Role1.Arn" } } } }
eksctl経由で作成したSAはrbac-role.yamlをapplyすることによりannotationが消えるのではないか、と思っていましたが残っていました。
ハマったポイント
- v.1.1.4のタグでalb-ingress-controller.yamlをデプロイしたらv.1.1.3のイメージが使われるようになっている
- fargate profileのIAMにALB作成権限があればALBが作られると思ったら作られていない
- iam-for-pods or envにkey/secret指定する必要があると知る
- Authentication Issues On EKS Cluster with Fargate Policy · Issue #1092 · kubernetes-sigs/aws-alb-ingress-controller · GitHub
- eksctl delete clusterでcfnがコケて削除されない
- 推測ですが、自分でapplyしたリソースが残っているとfargate profile側でpodを削除->deployで作られるの繰り返しが起きるのではないかと...
- 手動で適当にVPCとかを消し始めてfargate profileもなかなか削除されない挙動になっており、頭を抱えました
- 20分ぐらい待ったらちゃんと削除されました
- リソースを全部削除してeksctl delete clusterを叩いてもVPCの削除でコケているのを確認
- リトライでも成功しないのでVPCは手動で消しました
- これっぽい=>eksctl resources not really deleted after deleting cluster · Issue #1651 · weaveworks/eksctl · GitHub
The vpc 'vpc-xxx' has dependencies and cannot be deleted. (Service: AmazonEC2; Status Code: 400; Error Code: DependencyViolation; Request ID: u-u-i-d-x)
と出る
所感
EC2を管理したくないという観点ではFargateで完結できるようになっているので良さそうです。
LBのターゲットグループからの削除処理とpodが消えるタイミングでエラーが出るのは現状ワークアラウンドが必要そうだと感じました。
実際全部Fargateにするかと言われるとコスパはあまり良くないように感じました。
alb ingress controllerを始めとしたk8sの中を整えるようなpodは1host 1podがオーバースペックなので、そういったpodはまとめて1つのnodeで動かしたいように感じます。
他の記事でも言及されていますが、Daemonsetでfluentdを動かしておく、みたいなこともできないのでログ収集をsidecarで配置する必要があるというのも実運用では手間になるかもしれません。