EKS (on Fargate) + ALB Ingress Controller環境をスクリプト1つで構築する

# はじめに

昨年の12月、みなさんが待ちに待ったEKS on Fargateが[正式発表](https://aws.amazon.com/jp/blogs/news/amazon-eks-on-aws-fargate-now-generally-available/)されました。
これによりノード管理から解放され、Fargateを利用するようになる人も増えるのではないかと思います。

ただ、そこで問題になるのが EKS on Fargate におけるいくつかの制限です。

> - 各ポッドが使用できるのは最大 4 vCPU と 30Gb のメモリです。
> - パーシステントボリュームやファイルシステムで必要とされるステートフルなワークロードのサポートは、現時点ではありません。
> - Daemonsets、Privileged ポッド、または HostNetwork または HostPort を使用するポッドは実行できません。
> - 利用できるロードバランサーは、Application Load Balancer だけです。

公式ブログ参考: [AWS Fargate 上の Amazon EKS を一般公開](https://aws.amazon.com/jp/blogs/news/amazon-eks-on-aws-fargate-now-generally-available/)

このうち、個人的にまず困ったのは「ロードバランサーで使用できるものがApplication Load Balancerだけ」ということです。

一見すると問題ないように感じますが、EKS on Fargate においてALBを使うということは、[ALB Ingress Controller](https://github.com/kubernetes-sigs/aws-alb-ingress-controller)を導入するということと同義なので、Terraformで別管理などができません。

回避するには従来の方法とのハイブリッド型を選択する必要がありますが、どうせならそこも楽にしたく、本記事では潔くALB Ingress Controllerに乗っかることにするとして、これを導入するための手順がまたちょい手間です。

手順通りに構築したつもりでも何か抜け落ちている可能性があったり、毎回構築するたびに同じ手順を繰り返すのはあれなので、せめて**スクリプト化したい**と考えていました。

今回は、その際に作成したシェルスクリプトを紹介していければと思います。

# 環境

|使用|バージョン|
|:-:|:-:|
|Kubernetes|1.14|
|ALB Ingress Controller|1.1.4|
|eksctl|0.13.0|
|helm|3.0.1|

# シェルスクリプト

前置きが長かったため、早速本題のシェルスクリプトを貼ります。

基本的には、[AWSのドキュメント](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/alb-ingress.html)に沿った流れとなります。
ので、EKS on Fargateがもとで生まれた本スクリプトですが、従来通りのパターンでも活用できるかと思います。

```sh
#!/bin/bash -e

# Step1: 情報の取得
if [ ! -e $CLUSTER_YAML_PATH ]; then
  echo "yamlファイルが存在しません: ${CLUSTER_YAML_PATH}"
  exit 1
fi
cluster_name=$(yq r $CLUSTER_YAML_PATH metadata.name)
region=$(yq r $CLUSTER_YAML_PATH metadata.region)


# Step2: クラスタの作成
eksctl create cluster -f $CLUSTER_YAML_PATH


# Step3: 権限の割り当て
eksctl utils associate-iam-oidc-provider \
  --region $region \
  --cluster $cluster_name \
  --approve
policy_name='ALBIngressControllerIAMPolicy'
policy_arn=$(aws iam list-policies --output json | jq -r ".Policies[] | select(.PolicyName == \"${policy_name}\").Arn")
if [ -z "$policy_arn" ]; then
  policy_arn=$(aws iam create-policy \
    --policy-name $policy_name \
    --policy-document https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/iam-policy.json \
    --output json | jq -r .Policy.Arn)
fi
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/rbac-role.yaml
eksctl create iamserviceaccount \
  --region $region \
  --name alb-ingress-controller \
  --namespace kube-system \
  --cluster $cluster_name \
  --attach-policy-arn $policy_arn \
  --override-existing-serviceaccounts \
  --approve


# Step4: ALB Ingress Controllerのインストール
vpc_id=$(eksctl get cluster \
  --name $cluster_name \
  --output json | jq -r .[].ResourcesVpcConfig.VpcId)
helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
helm install incubator/aws-alb-ingress-controller \
  --set clusterName=$cluster_name \
  --set awsRegion=$region \
  --set awsVpcID=$vpc_id \
  --set rbac.create=false \
  --set rbac.serviceAccountName=alb-ingress-controller \
  --generate-name \
  --namespace kube-system \
  --version 0.1.12
```
元: https://gist.github.com/yukiarrr/df476d0928c358ea01052f8226231e2f

EKS on Fargateの構築には、eksctlを使用しています。
冪等性がないとはいえ、1コマンドで環境が構築できてしまうのはやはり強力ですね。

## Step1: 情報の取得

```sh
# Step1: 情報の取得
if [ ! -e $CLUSTER_YAML_PATH ]; then
  echo "yamlファイルが存在しません: ${CLUSTER_YAML_PATH}"
  exit 1
fi
cluster_name=$(yq r $CLUSTER_YAML_PATH metadata.name)
region=$(yq r $CLUSTER_YAML_PATH metadata.region)
```

eksctlを使用する場合、基本的には設定をyamlで記述するかと思いますので、ここでは環境変数`$CLUSTER_YAML_PATH`として、yamlのパスを受け取ることを想定してます。

そして、そのyamlからyqを使いクラスタ名とリージョンを取得します。
これらはクラスタを作成後にALB Ingress Controllerをインストールするために必要となります。

## Step2: クラスタの作成

```sh
# Step2: クラスタの作成
eksctl create cluster -f $CLUSTER_YAML_PATH
```

yamlの情報をもとに、クラスタを生成します。
（結構時間かかります）

この後[AWSのドキュメント](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/alb-ingress.html)では、サブネットに対するタグ付けを必要としていますが、eksctlを使った場合は勝手にやってくれるので、無視で大丈夫です。

## Step3: 権限の割り当て

```sh
# Step3: 権限の割り当て
eksctl utils associate-iam-oidc-provider \
  --region $region \
  --cluster $cluster_name \
  --approve
policy_name='ALBIngressControllerIAMPolicy'
policy_arn=$(aws iam list-policies --output json | jq -r ".Policies[] | select(.PolicyName == \"${policy_name}\").Arn")
if [ -z "$policy_arn" ]; then
  policy_arn=$(aws iam create-policy \
    --policy-name $policy_name \
    --policy-document https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/iam-policy.json \
    --output json | jq -r .Policy.Arn)
fi
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/rbac-role.yaml
eksctl create iamserviceaccount \
  --region $region \
  --name alb-ingress-controller \
  --namespace kube-system \
  --cluster $cluster_name \
  --attach-policy-arn $policy_arn \
  --override-existing-serviceaccounts \
  --approve
```

ALB Ingress Controllerでは名前の通りAWSのALBを制御するため、制御するPod側には専用の権限が必要となります。
ここでは、AWS上のIAMポリシー一覧から、以前同じ名前のポリシーを作成していないか確認し、存在しなければeksctlより提供されているサンプルをもとに作成します。
そして、そのポリシーと紐付いたServiceAccountを作成し、以後はこれを用いてALBを制御するようにします。

## Step4: ALB Ingress Controllerのインストール

```sh
# Step4: ALB Ingress Controllerのインストール
vpc_id=$(eksctl get cluster \
  --name $cluster_name \
  --output json | jq -r .[].ResourcesVpcConfig.VpcId)
helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
helm install incubator/aws-alb-ingress-controller \
  --set clusterName=$cluster_name \
  --set awsRegion=$region \
  --set awsVpcID=$vpc_id \
  --set rbac.create=false \
  --set rbac.serviceAccountName=alb-ingress-controller \
  --generate-name \
  --namespace kube-system \
  --version 0.1.12
```

最後に、ALB Ingress Controller自体のインストールです。
ここでは、面倒なVPC IDなどの挿入を避けるため、helmを使用しています。

これで一通りの環境構築は完了します。
その他マニフェストファイルについてなどは[ドキュメント](https://kubernetes-sigs.github.io/aws-alb-ingress-controller/)を参考にしてください。

# マニフェストファイルでの注意

FargateでALB Ingress Controllerを使用する場合、IP Modeを使用する必要があります。
IP Modeとは、Fargateで起動したPodにIPアドレスを使い直接ルーティングする方法で、annotationsで使用を宣言する必要があります。

具体的に、実際に使用したIngressのannotationsを抜粋します。

```yaml
annotations:
  kubernetes.io/ingress.class: alb # AWSドキュメントより必須項目
  alb.ingress.kubernetes.io/scheme: internet-facing # インターネット接続する際に必要
  alb.ingress.kubernetes.io/target-type: ip # IP Mode
```

# まとめ

これで、スクリプト1つ実行すれば簡単に環境構築することができるようになりました。
若干環境に依存してしまっているところはありますが、手で毎回やるよりはマシになったかなと思います。

今後もできる限り、楽にしていきたいと思います。

GitHub: [@yukiarrr](https://github.com/yukiarrr)
Twitter: [@yukiarrr](https://twitter.com/yukiarrr)

# 参考リンク
- [EKS on FargateでALBからアプリにアクセスする](https://839.hateblo.jp/entry/2019/12/08/172020)