EKS on FargateでAWS Load Balancer Controllerを動作させる(eksctlでやる⇒Terraform化する)
EKSをTerraformでクラスタやRDS含めて基盤を作っていろいろやろうとして敗北したので、eksctlでちゃんとチュートリアルをやってみたうえで再度Terraform化にチャレンジしたいと思います。
Terraform化したいモチベーションはIaCを使ってブラックボックス化せずに、Terraformのコードを見ればなんとなくそのインフラ構成がわかるようにして、CI/CDの仕組みに乗せて継続的な改善ができるようにしたいなという想いがあるからです。
とはいえ、AWS Load Balancer Controllerのところで敗北して、何が悪いのかわからなくなり、詰んでしまったので、ちゃんとeksctlで動くものを作ってから応用編に突入しようと思っています。
今回は以下のバージョンでやってみます。
・kubectl:1.24.7
・eksctl:0.134.0
・EKS:1.24
・Helm:3.11.2
環境はCloudShellです。
デフォルトでAWS CLIとかちゃんと入っていたり、IAMの権限はAdministratorの権限がついていてなんでもできる状態です。
参考にするチュートリアルは以下です。
いろいろ準備系のコマンド(各所から寄せ集めなので、ちゃんとしてなくてすみません)
※どうせ入れるのでTerraformも入れてます(なんか依存関係で入ってくるライブラリが必要だったりするので、入れておきます)
#--------------------------
# Install Terraform
#--------------------------
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo
sudo yum -y install terraform
#--------------------------
# Install kubectl
#--------------------------
# 永続化領域が${HOME}なのでちょっと特殊な位置にインストール
mkdir -p $HOME/.local/bin
cd $HOME/.local/bin
# download kubectl
curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl"
# add executable permission to kubectl
chmod +x ./kubectl
# add PATH
PATH=$PATH:$HOME/.local/bin:$HOME/bin
export PATH
#--------------------------
# Install Helm
#--------------------------
# Cloudshellのインストール用ディレクトリを作成
mkdir ${HOME}/.local/helm
cd ${HOME}/.local/helm
# Download Install script
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
# Install
cd ${HOME}/.local/helm
./get_helm.sh
#--------------------------
# Install eksctl
#--------------------------
# バイナリをダウンロードして解凍
curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
# 移動
sudo mv /tmp/eksctl /usr/local/bin
#--------------------------
#確認
#--------------------------
terraform version
kubectl version
helm version
eksctl version
↑、初回だけホームディレクトリにダウンロード必要なやつと、2回目以降は単にコピーだけでよかったりする系を分けておいて、スクリプト化とかしたら、CloudShellのセットアップ楽になりそうなので、余裕があったらやりたい感ある
EKSクラスターを作る(ついでにVPCとかも)
eksctl create cluster --name eksHandson --version 1.24 --fargate
お昼休憩をはさんでいる間に終わってました。
こんな感じでFargateのノードができています。(ちなみにこれはcoreDNSのPodみたいです)
kubectl get nodes
NAME STATUS ROLES AGE VERSION
fargate-ip-192-168-172-98.ap-northeast-1.compute.internal Ready <none> 8m1s v1.24.9-eks-300e41d
fargate-ip-192-168-181-237.ap-northeast-1.compute.internal Ready <none> 8m3s v1.24.9-eks-300e41d
Fargateプロファイルは3AZにまたがっていて、Pod Selectorはdefaultとkube-systemの名前空間が割り当てられています。
サブネットはプライベートサブネットが選択されています。
ちなみにサブネットはこんな感じでした
$ aws ec2 describe-subnets --query "Subnets[].[Tags[?Key=='Name'] | [0].Value,SubnetId,AvailabilityZone,CidrBlock]" --filter "Name=vpc-id,Values=${EKS_VPC_ID}" --output table
-----------------------------------------------------------------------------------------------------------------------------
| DescribeSubnets |
+-------------------------------------------------------+---------------------------+------------------+--------------------+
| eksctl-eksHandson-cluster/SubnetPrivateAPNORTHEAST1C | subnet-0e911c6dc138a100d | ap-northeast-1c | 192.168.160.0/19 |
| eksctl-eksHandson-cluster/SubnetPublicAPNORTHEAST1A | subnet-00a134258813a1f36 | ap-northeast-1a | 192.168.32.0/19 |
| eksctl-eksHandson-cluster/SubnetPublicAPNORTHEAST1C | subnet-0c004a333f85f4d9b | ap-northeast-1c | 192.168.64.0/19 |
| eksctl-eksHandson-cluster/SubnetPublicAPNORTHEAST1D | subnet-088318dd5d1cad36e | ap-northeast-1d | 192.168.0.0/19 |
| eksctl-eksHandson-cluster/SubnetPrivateAPNORTHEAST1A | subnet-0610ba67feefa2591 | ap-northeast-1a | 192.168.128.0/19 |
| eksctl-eksHandson-cluster/SubnetPrivateAPNORTHEAST1D | subnet-0df6ee8f2449e41c8 | ap-northeast-1d | 192.168.96.0/19 |
+-------------------------------------------------------+---------------------------+------------------+--------------------+
※VPCは192.168.0.0/16で確保されていて、パブリックサブネットとプライベートサブネットとNATがついた状態でサブネットが作成されています。(/19のネットマスクで各AZごとに作成されていました)
OpenID ConnectorでサービスアカウントがIAMを使えるようにする
クラスターがサービスアカウントのために AWS Identity and Access Management (IAM) を使用することを許可します。
eksctl utils associate-iam-oidc-provider --cluster eksHandson --approve
これで、サービスアカウントを見てみましょう。
aws iam list-open-id-connect-providers
export OIDC_ARN=`aws iam list-open-id-connect-providers --output text --query "OpenIDConnectProviderList[0].Arn"`
aws iam get-open-id-connect-provider --open-id-connect-provider-arn $OIDC_ARN
コンソールで見てもいいです。
AWS Load Balancer Controller がユーザーに代わって AWS API を呼び出すことを許可する
IAMポリシーを作成
# 一時ディレクトリに移動しておく
cd /tmp
# ポリシードキュメントのダウンロード
curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.4/docs/install/iam_policy.json
# IAMポリシーを作成する
aws iam create-policy \
--policy-name AWSLoadBalancerControllerIAMPolicy \
--policy-document file://iam_policy.json
サービスアカウントを作成
EKSのPodの利用するOIDCから利用可能なIAMロールを作っていきます。(という解釈であっているはず…)。そのIAMロールを利用できるService Accountも作成されています。
サービスアカウントとは正確に言うと「Kubernetes API を使用して作成、管理される Kubernetes リソースであり、Kubernetes API サーバーまたは外部サービスでの認証を行うことを目的として、クラスタ内で Kubernetes によって作成された Pod などのエンティティが使用するためのものです。」とGKEのドキュメントで紹介されています。
# まずはAWSのアカウントIDを環境変数に取得しておきます
export AWS_ACCOUNT_ID=`aws sts get-caller-identity --output text --query "Account"`
# サービスアカウントを作成する
eksctl create iamserviceaccount \
--cluster=eksHandson \
--namespace=kube-system \
--name=aws-load-balancer-controller \
--attach-policy-arn=arn:aws:iam::${AWS_ACCOUNT_ID}:policy/AWSLoadBalancerControllerIAMPolicy \
--override-existing-serviceaccounts \
--approve
確認
EKSのコンソールで作成されたService Accountを見てみると
こんな感じでannotationsでロールを指定しています。
コマンドでも確認できます。
eksctl get iamserviceaccount --cluster eksHandson --name aws-load-balancer-controller --namespace kube-system
Helm を使用して AWS Load Balancer Controller をインストール
注意点に関する記載について
何やらHelmだとcert-managerの問題があるらしいです。読んでもよくわからなかったので、Bingさんに聞いてみました。
「Cert-manager issues with fargate について教えてください」
↓回答です。
(笑顔の絵文字がついていてかわいいですね。こんにちは)
これをベースに読んでいきます。
From their investigation, the issue mentioned is more related to cert-manager setup. The cert-manager-webhook deployment uses port 10250 which is also used for kubelet on the Fargate pods. Therefore when the connection was made to the cert-manager-webhook service it was reporting the error with ip address corresponding to pod cert-manager-webhook.
https://github.com/cert-manager/cert-manager/issues/3237
we tested the helm chart and I am aware that it's working on fargate as it's not using cert-manager and creating a self signed cert , we just need the documentation to reflect that it's the only way right now to deploy the controller on fargate pods to avoid confusion
https://github.com/kubernetes-sigs/aws-load-balancer-controller/issues/1606
DeepLさんもCopilotとして拡張機能として加えているので、訳してもらいました。
helmチャートをテストしたところ、cert-managerを使用せず、自己署名証明書を作成しているので、fargateで動作していることは認識しています。混乱を避けるために、fargate podsにコントローラーを展開する唯一の方法であることをドキュメントに反映させる必要があります。
とりあえず、Helmをちゃんと使うのが大事そうです。Helmは同じ轍を踏まないようにできるので本当にありがたいですね。
Helmでインストールする
Helmのリポジトリを追加
helm repo add eks https://aws.github.io/eks-charts
TargetGroupBinding カスタムリソース定義 (CRD) をインストール
kubectl apply -k "github.com/aws/eks-charts/stable/aws-load-balancer-controller//crds?ref=master"
Helm チャートをインストール
# VPC IDを取得(仕様が変わるかもしれないので、ちゃんとechoで確認しましょう
export EKS_VPC_ID=`aws ec2 describe-vpcs --filters "Name=tag:Name,Values=eksctl-eksHandson-cluster/VPC" --query "Vpcs[0].VpcId" --output text`
echo $EKS_VPC_ID
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
--set clusterName=eksHandson \
--set serviceAccount.create=false \
--set region=ap-northeast-1 \
--set vpcId=${EKS_VPC_ID} \
--set serviceAccount.name=aws-load-balancer-controller \
-n kube-system
こんな感じにあなれば成功。
$ kubectl get deployment -n kube-system aws-load-balancer-controller
NAME READY UP-TO-DATE AVAILABLE AGE
aws-load-balancer-controller 2/2 2 2 17m
※私はコマンドの環境変数(--set vpcId=${EKS_VPC_ID}
)の指定を失敗したので、helm upgrade ...
で再度デプロイしなおしました。
トラブルシューティングはポッドを確認してログを見る感じです。
# podを確認してみます
kubectl get pod -n kube-system
# ↑のコマンドの実行結果から
# こんな感じのPodの名前を確認する(STATUSがCrashLoopBackOffになっていたら失敗している)
# aws-load-balancer-controller-5944755c59-n7ssp
# Podの情報を確認する
kubectl describe pod -n kube-system aws-load-balancer-controller-5944755c59-n7ssp
# ログを確認する
kubectl logs -n kube-system aws-load-balancer-controller-5944755c59-n7ssp
AWS Load Balancer Controller をテストする
ゲームのデプロイに必要な Fargate プロファイルを作成する
eksctl create fargateprofile --cluster eksHandson --region ap-northeast-1 --name your-alb-sample-app --namespace game-2048
こんな感じです
サンプルゲームをデプロイし、AWS Load Balancer Controller が ALB Ingress リソースを作成することを確認
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.4/docs/examples/2048/2048_full.yaml
数分後、次のコマンドを実行することによって、Ingress リソースが作成されたことを検証
kubectl get ingress/ingress-2048 -n game-2048
ログの確認方法
kubectl logs -n kube-system deployment.apps/aws-load-balancer-controller
マネジメントコンソールを見てみたら、ALBがちゃんとできていました。
ちゃんとHTTPのエンドポイントでアクセスしたら接続できました。
2048には到達できませんでしたが…
ちゃんとうまく言った理由を確認していく
さて、ここからが本番です。
まずは、今回デプロイしたアプリケーションのマニュフェストを見てみましょう。
---
apiVersion: v1
kind: Namespace
metadata:
name: game-2048
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: game-2048
name: deployment-2048
spec:
selector:
matchLabels:
app.kubernetes.io/name: app-2048
replicas: 5
template:
metadata:
labels:
app.kubernetes.io/name: app-2048
spec:
containers:
- image: public.ecr.aws/l6m2t8p7/docker-2048:latest
imagePullPolicy: Always
name: app-2048
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
namespace: game-2048
name: service-2048
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
type: NodePort
selector:
app.kubernetes.io/name: app-2048
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: game-2048
name: ingress-2048
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-2048
port:
number: 80
後で違いを見直せるように各サービスの状態を取得していきます。
デプロイメントについて
$ kubectl describe deployment -n game-2048
Name: deployment-2048
Namespace: game-2048
CreationTimestamp: Tue, 21 Mar 2023 05:51:16 +0000
Labels: <none>
Annotations: deployment.kubernetes.io/revision: 1
Selector: app.kubernetes.io/name=app-2048
Replicas: 5 desired | 5 updated | 5 total | 5 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app.kubernetes.io/name=app-2048
Containers:
app-2048:
Image: public.ecr.aws/l6m2t8p7/docker-2048:latest
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: deployment-2048-6bc9fd6bf5 (5/5 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 30m deployment-controller Scaled up replica set deployment-2048-6bc9fd6bf5 to 5
次にServiceについてです
$ kubectl describe service -n game-2048
Name: service-2048
Namespace: game-2048
Labels: <none>
Annotations: <none>
Selector: app.kubernetes.io/name=app-2048
Type: NodePort
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.100.5.175
IPs: 10.100.5.175
Port: <unset> 80/TCP
TargetPort: 80/TCP
NodePort: <unset> 32613/TCP
Endpoints: 192.168.113.22:80,192.168.115.156:80,192.168.125.159:80 + 2 more...
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
最後にIngressについて見ていきましょう
$ kubectl describe ingress -n game-2048
Name: ingress-2048
Labels: <none>
Namespace: game-2048
Address: k8s-game2048-ingress2-a36001719e-1225590979.ap-northeast-1.elb.amazonaws.com
Ingress Class: alb
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
*
/ service-2048:80 (192.168.113.22:80,192.168.115.156:80,192.168.125.159:80 + 2 more...)
Annotations: alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfullyReconciled 35m ingress Successfully reconciled
一旦サンプルアプリの2048ゲームは削除しておきます
kubectl delete -f https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.4/docs/examples/2048/2048_full.yaml
久しぶりに2048ゲームやりましたが、思ったよりも楽しかったですね。あんまり考えすぎないで頭空っぽにできるのでたまにやるにはちょうどいいゲームです(^^♪
名残惜しいですが、お金もかかるので退場してもらいましょう。
一旦ちゃんと動く構成も確認できたので、敗北した時の構成との違いに関してメモにしたためた後にクラスタとかを削除しちゃいます。
とりあえず気づいたこと
クラスターのサブネットにパブリックサブネットも含まれるようになっていました。
量が多いのでこっちにはのせずにローカルに管理していきます。
# サービスアカウントの削除
eksctl delete iamserviceaccount \
--cluster=eksHandson \
--namespace=kube-system \
--name=aws-load-balancer-controller
# IAMポリシーの削除
aws iam delete-policy \
--policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/AWSLoadBalancerControllerIAMPolicy
# 一応念のためEXTERNAL-IPを持つサービスがいないことを確認
kubectl get svc --all-namespaces
# クラスターの削除
eksctl delete cluster --name eksHandson
Terraform化に向けた準備
ちゃんと座学しましょう。
ちょっと古いのですが、Classmethodさんの記事をみてみます。
Terraform化の範囲
ちょっと時間かかりそうですが、terraformのコードを作成する戦略は以下です。
・VPCを作る
・EKSを作る
・サービスアカウント(IRSA: IAM Role for Service Accounts)
※いまさらですが、そもそもサービスアカウントとかIRSAって?という話はこちら
・Kubernetes Provider
Terraform以外のところ
HelmでAWS Load Balancer ControllerをデプロイするところからはTerraformの管理外(EKSで完結させる範囲)とします。
Terraform開発環境
CloudShellだと開発環境としてはしんどいので、Terraformの開発環境はローカルのVSCode + devcontainerを作って開発環境を整えます。
私のマシンはWindows 11 Home 22H2です。
WSL2はセットアップ済み
Docker Desktopもセットアップ済み
VSCodeもインストール済み
Dockerfileはこちら
FROM amazonlinux:2023
# Install tar, unzip, git for install tools
RUN yum install -y tar unzip git
# Install Terraform
RUN yum install -y yum-utils
RUN yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo
RUN yum -y install terraform
# Install kubectl
WORKDIR /usr/local/bin
RUN curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl"
RUN chmod +x ./kubectl
# Install Helm
WORKDIR /tmp
ENV VERIFY_CHECKSUM=false
RUN curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
RUN chmod 700 get_helm.sh
RUN ./get_helm.sh
# Install eksctl
RUN curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
RUN mv /tmp/eksctl /usr/local/bin
# Install AWS CLI v2
WORKDIR /tmp
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
RUN unzip awscliv2.zip
RUN ./aws/install
RUN rm awscliv2.zip
【2023/5/8追記】
※新しいバージョンのget_helm.shでチェックサムをするようになった影響でこけるようになりました。checksumを実行するためのものを入れるか、RUN ./get_helm.sh
の前で、ENV VERIFY_CHECKSUM=false
を書いておくようにしておく必要があります。上は修正しています。
AWS CLIはv2を導入しているのでAWS Identity Center (旧SSO) を利用しています。
そろそろIdentity Centerに寄せていこうかしらと思っている今日この頃です。
TerraformでAWS SSOの権限で実行するときにはちょっと癖がある
aws configure sso
でSSOを実行するときにセッション名を入れたりするのですが、これがTerraformだとサポートされていないようでした。
Initializing modules...
╷
│ Error: error configuring S3 Backend: Error creating AWS session: profile "default" is configured to use SSO but is missing required configuration: sso_region, sso_start_url
│
│
以下の形式にconfigファイルを修正して再度aws sso login
をし直したらいけました。
[default]
sso_account_id = <account_id>
sso_role_name = <sso_role_name>
region = ap-northeast-1
output = json
sso_start_url = https://<your_sso_id>.awsapps.com/start#
sso_region = ap-northeast-1
Terraform化したコードはこちら
動作確認まで完了しました。
これで敗北したAWS Load Balancer Controllerがちゃんと動作する環境まで作れるようになったので、このEKSの環境をベースに検証していきたいと思います。