
技術顧問室の緑川です。
半年ほど自身でEKSを運用していく中で、どこまでをTerraform管理にするか、どういったコンポーネントに分割すると運用・変更しやすいのかといった答えが一旦出たので、チュートリアル資料としてまとめてみました。

このチュートリアルでは、EKSクラスタをTerraformで構築し、サンプルのサービスとしてKeycloakをデプロイし、ALBで公開するところまでを行います。
ソースコード
チュートリアルのソースコードはこちらです。このチュートリアルはvscodeのDevcontainerを起動して実施する前提となっています。
チュートリアル自体の内容は以下のリポジトリに任せて、この記事では構成や実際に出来上がるものを見せたいと思います。
github.com設計
普段Terraformを実装するときは一発ですべてのリソースが出来上がるように実装するのですが、EKSはネットワーク, EKSクラスタ, ノードグループ, アドオン, Helmチャート, アプリケーションなど、様々なライフサイクル(生存期間)の要素で構成され、それらがKubernetesという仕組みを介して複雑に結合しているため、依存関係の問題で不整合が頻繁に発生します。
当初は一発ですべてのリソースが出来上がるような設計でTerraformを実装しましたが、運用中たびたび発生する不整合があまりにも辛かったので、今回のチュートリアルではリソースをライフサイクルごとに分類し、ライフサイクルが近いリソースをコンポーネントにまとめ、独立して変更・デプロイできるようにしました。
今回作成するリソースをライフサイクル毎のレイヤーで整理すると下記のようになります。
注目してほしいのは、上のレイヤーになるほど頻繁に変更される点で、レイヤーごとににリソースの生存期間が異なります。コンポーネントはこの生存期間を意識して分割していきます。

ライフサイクルが近いリソースをまとめて下記のコンポーネントに分類しました。
| コンポーネント | 要素 |
|---|---|
| base | いろいろなコンポーネントで利用される共通変数など |
| network | VPC, サブネットなど |
| cluster | EKSクラスタ |
| node-group | EKSのノードグループ, 起動テンプレートなど |
| addon | EKSのアドオン |
| plugin | helmでインストールするチャートに付随するリソース |
| service | EKSにデプロイするサービスに付随するリソース |
これらのコンポーネントを循環依存がないように依存関係を構築していきます。

サンプルコードのデプロイ手順
前準備
cd sample/
tfstateを保存するs3バケットの作成
TFSTATE_BUCKET="tfstateの保存先バケット" aws s3api create-bucket \ --bucket $TFSTATE_BUCKET \ --region ap-northeast-1 \ --create-bucket-configuration LocationConstraint=ap-northeast-1
tfvarsファイルに tfstateの保存先バケットを設置
sample/terraform/components/tfvars/common.tfvars
tfstate_region = "ap-northeast-1" # tfstateの保存先バケット tfstate_bucket = "tfstateの保存先バケット"
sample/terraform/components/tfvars/backend.tfvars
region = "ap-northeast-1" # tfstateの保存先バケット bucket = "tfstateの保存先バケット" # tfstateのロック use_lockfile = true # tfstateの暗号化 encrypt = true
プロジェクト名とステージの定義
# プロジェクト名 PROJECT_NAME=sample # ステージ名 STAGE=dev
baseコンポーネントのデプロイ
共通変数などを定義します。
# trerraformのデプロイ make tf-plan PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=base make tf-apply PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=base
networkコンポーネントのデプロイ
eksを構築するvpcとサブネットを構築します。
# trerraformのデプロイ make tf-plan PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=network make tf-apply PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=network
clusterコンポーネントのデプロイ
eksクラスタを作成します。
# trerraformのデプロイ make tf-plan PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=cluster make tf-apply PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=cluster # クラスタ名を変数として控えておく CLUSTER_COMPONENT_DIR=$PROJECT_DIR/sample/terraform/components/cluster CLUSTER_NAME=$(terraform -chdir=$CLUSTER_COMPONENT_DIR output -raw cluster_name) aws eks update-kubeconfig --name $CLUSTER_NAME
node-groupコンポーネントのデプロイ
node-groupとその周辺リソースを作成します。
make tf-plan PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=node-group make tf-apply PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=node-group
addonコンポーネントのデプロイ
Pod Identity AgentやEBS CSI Driverなどのアドオンをインストールします。
make tf-plan PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=addon make tf-apply PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=addon
pluginコンポーネントのデプロイ
AWS Load Balancer Controllerや Secrets Store CSI Driverなど、Helmでインストールするプラグインに必要なAWSリソースを作成します。
make tf-plan PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=plugin make tf-apply PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=plugin
metrics-serverをHelmでインストール
# リポジトリ追加 helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/ # リポジトリのアップデート helm repo update metrics-server # インストール helm upgrade --install metrics-server metrics-server/metrics-server \ --version "3.12.2" \ --namespace "kube-system" \ --create-namespace
AWS Load Balancer ControllerをHelmでインストール
# リポジトリ追加 helm repo add eks https://aws.github.io/eks-charts # リポジトリのアップデート helm repo update eks # インストール helm upgrade --install aws-load-balancer-controller eks/aws-load-balancer-controller \ --version "1.11.0" \ --namespace "kube-system" \ --create-namespace \ --values $PROJECT_DIR/sample/plugin/albc/tmp/values.yaml
Secrets Store CSI DriverをHelmでインストール
# リポジトリ追加 helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts # リポジトリのアップデート helm repo update # インストール helm upgrade --install csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver \ --version "1.4.7" \ --namespace kube-system \ --create-namespace \ --set "syncSecret.enabled=true" \ --set "enableSecretRotation=true"
ASCP (aws secrets store csi provider)をHelmでインストール
# リポジトリ追加 helm repo add aws-secrets-manager https://aws.github.io/secrets-store-csi-driver-provider-aws # リポジトリのアップデート helm repo update # インストール helm upgrade --install secrets-provider-aws aws-secrets-manager/secrets-store-csi-driver-provider-aws \ --version "0.3.10" \ --namespace kube-system \ --create-namespace
serviceコンポーネントのデプロイ
keycloakに必要なAWSリソース(RDS, SecretsManagerなど)をデプロイします。
make tf-plan PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=service make tf-apply PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=service
keycloakをEKSにデプロイします。
kubectl apply -f $PROJECT_DIR/sample/service/keycloak/tmp/app.yaml
keycloakはデフォルトでhttpでログインできないので、ログインできるように設定します。
# k9sでkeycloak コンテナのshellを起動
k9s
keycloakコンテナのshellでsslRequired設定を無効化します。
# SecretsManager (/<app_name>/<ステージ>/keycloak) のユーザー名とパスワードでログイン /opt/keycloak/bin/kcadm.sh config credentials \ --server http://localhost:8080 \ --realm master \ --user $KEYCLOAK_ADMIN \ --password $KEYCLOAK_ADMIN_PASSWORD # sslRequiredを無効化 /opt/keycloak/bin/kcadm.sh update realms/master -s sslRequired=NONE exit
動作確認
ALBのエンドポイントにブラウザでアクセスしてログインできることを確認します。
# ALBのURLを確認 kubectl -n keycloak get ing # ログイン情報を確認 CLUSTER_NAME=$(terraform -chdir=$PROJECT_DIR/sample/terraform/components/base output -raw cluster_name) aws secretsmanager get-secret-value --secret-id /$CLUSTER_NAME/keycloak --query "SecretString" --output text | jq "."
後片付け
keycloakの削除
kubectl delete -f $PROJECT_DIR/sample/service/keycloak/tmp/app.yaml
チャートの削除
helm uninstall -n kube-system csi-secrets-store helm uninstall -n kube-system secrets-provider-aws helm uninstall -n kube-system aws-load-balancer-controller helm uninstall -n kube-system metrics-server
Terraformリソースの削除
# プロジェクト名 PROJECT_NAME=sample # ステージ名 STAGE=dev make tf-destroy PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=service && \ make tf-destroy PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=plugin && \ make tf-destroy PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=addon && \ make tf-destroy PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=node-group && \ make tf-destroy PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=cluster && \ make tf-destroy PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=network && \ make tf-destroy PROJECT_NAME=$PROJECT_NAME STAGE=$STAGE COMPONENT=base