EKSの構築～接続までをTerraformでやりきる

# はじめに

今更ですがEKSを触り始めました。いざ作ってみると考えることが多かったので、記事にしました。
それと最近IAM Identity Centerユーザーを使い始めたので、AWS CLIを使うためのトークンの発行方法など調べました。

# 作成するAWSリソース概要

後述しますが、以下に今回作成したtfファイルを配置しています。

https://github.com/kohei39san/mystudy-handson/tree/main/tf-manifests/eks

Terraformで作る主なAWSリソースは以下の通り

|terraform resource.name|リソース種類|備考|
|---|---|---|
|aws_eks_cluster.cluster|EKSクラスタ|エンドポイントはプライベートとしています（`endpoint_private_access = true`,`endpoint_public_access  = false`）。セキュリティグループにて追加設定しています（※1）|
|aws_eks_node_group.node_group|ノードグループ|コンテナを動かすノードになります。|
|aws_instance.kubectl_instance|kubectlを実行するEC2|SSH接続に接続します。|
|aws_ec2_instance_connect_endpoint.eice|EC2 Instance Connect Endpoint（EICE）|kubectl_instanceにSSH接続するために使います（※2）|
|aws_iam_role.kubectl_role|kubectl_instanceのIAMロール|kubectl_instanceに紐づけます。|
|aws_iam_policy.kubectl_policy|kubectl_instanceのIAMポリシー|`sts:GetCallerIdentity`だけkubectl_roleに付けています。（※3）|

※1
EKSクラスタのエンドポイントにローカルVPCからアクセスできるよう、追加設定します。プライベートアクセスでは、VPCのプライベートDNSを使って、443ポートを使っています。
```tf
resource "aws_security_group" "cluster-sg" {
  name   = "eks-cluster-sg"
  vpc_id = aws_vpc.vpc.id

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [var.vpc_cidr_block]
  }
}
```
デフォルトで必要なセキュリティグループは作成されるので、上記のセキュリティグループではegressの許可は入れていません。

※2
最近、EICEを知ったので使ってみることにしました。
以下のドキュメントにある通り、AWS CLIからSSHキーを気にせず接続できるようになります。
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/ec2-instance-connect-methods.html#connect-linux-inst-eic-cli-ssh
Terraform内でEC2に接続するには、60秒の期限付きでSSHキーを登録して接続します。登録するSSHキーはあらかじめ作成しておく必要があります。

※3
いつもコンソールで使っているIAMユーザー権限では強すぎたり、使いにくいので新しく作ることにしました。
以下のドキュメントより、kubectlを実行するために`sts:GetCallerIdentity`権限が必要になります。
（https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/cluster-auth.html より引用）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1474054/11017e47-2f32-249a-21fe-68aba93276e7.png)

後述しますが、出来上がったクラスタで試しにPODを作ってみます。

# アクセス制限について確認しておく

作成前にアクセス制限がどうなっているか確認します。

* EKSクラスタへのアクセス制限
プライベートアクセスにしているので、同一VPCからアクセスできるようにします。セキュリティグループは以下のようにしています。

|方向|アクセス|ソース|送信先|
|---|---|---|---|
|インバウンド|全プロトコル、全ポート|クラスタセキュリティグループ|-|
|インバウンド|HTTPS,443ポート|VPCのCIDR（10.0.0.0/16）|-|
|アウトバウンド|全プロトコル、全ポート|-|0.0.0.0/0|

* ノードへのアクセス制限
今回はコストの都合上、パブリックサブネットにノードプールを作っています。

|方向|アクセス|ソース|送信先|
|---|---|---|---|
|インバウンド|全プロトコル、全ポート|クラスタセキュリティグループ|-|
|アウトバウンド|全プロトコル、全ポート|-|0.0.0.0/0|

* kubectlを実行するEC2へのアクセス制限
SSH接続用に許可します。

|方向|アクセス|ソース|送信先|
|---|---|---|---|
|インバウンド|TCP,22ポート|ローカルのパブリックIPアドレス|-|
|アウトバウンド|全プロトコル、全ポート|-|0.0.0.0/0|

デフォルトの制限を使っている部分もありますが、外部からのアクセスは制限しているようです。

# kubectlからEKSの接続方法について

今回はEC2にkubectlをインストールしてEKSに接続することがゴールになります。
接続にはkubeconfigファイルが必要であり、AWS CLIで作成しています。
```bash
$ aws eks update-kubeconfig --name <クラスタ名>
```

また、IAMとKubernetesのRBACの認証が必要になります。
以下のドキュメントのようにnamespace「kube-system」にあるConfigMap「aws-auth」を編集する必要があります。
https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/add-user-role.html

```bash
[ec2-user@ip-10-0-10-109 ~]$ kubectl get cm -n kube-system aws-auth
NAME       DATA   AGE
aws-auth   2      43m
```

今回は以下のeksctlコマンドで編集します。

```bash
eksctl create iamidentitymapping --cluster <クラスタ名> --region <リージョン名> --arn <aws_iam_role.kubectl_roleのARN> --username "admin" --group "system:masters" --no-duplicate-arns
```

各項目は以下のようにしました。

* group: `system:masters`
`RoleBinding`や`ClusterRoleBinding`で指定したグループの名前です。今回は勉強用のためコントロールプレーンと同等の`system:masters`を使いました。
* usernamme: `admin`
任意の名前です。
* arn: IAMロール`aws_iam_role.kubectl_role`のARN
マッピングするIAMロールのARNです。

今回はコンソールで使っているIAMユーザーでは権限が強すぎるため、別IAMロールを用意しています。ただ、aws-auth編集時にはまだマッピングされていないため、IAM Identity CenterユーザーのSSOで発行された以下のトークンを使用しています。

```bash
export AWS_ACCESS_KEY_ID="..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_SESSION_TOKEN="..."
```

上記のexportコマンドはコンソール上から確認できます。（Command line or programmatic accessより）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1474054/f372a401-9c9e-5344-26aa-67b724fcde81.png)

※実際のコマンドは以下のshellファイルになります。
https://github.com/kohei39san/mystudy-handson/blob/main/scripts/connect-eks-for-remote-ec2.sh

# EKS構築の実践

## 前提

* IAM Identity Centerユーザーが作成済みであること。
最近、セキュリティ強化のためにIAMユーザーからIAM Identity CenterユーザーのSSOに切り替えました。IAM Identity Centerユーザーで認証を行う前提で進めていきます。
* WSLでTerraformを実行しています。

## AWS CLIのアップデート

Terraformの実行前に、AWS CLI v1→v2にバージョンアップしました。
AWS CLI v1ではEICEやSSOを使えないためです。

以下のようなコマンドでアップデートしました。
```bash
sudo yum remove awscli -y
sudo rm -rf /usr/local/aws-cli/
cd /tmp
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install --bin-dir /usr/local/bin --install-dir /usr/local/aws-cli
```

## Terraform実行

Terraformの前にAWS CLIを実行するためのSSOを行うため、以下のconfig,shellファイルを作成します。

```conf:~/.aws/config
[profile sso-profile]
sso_start_url = #sso_start_url
sso_region = #sso_region
sso_account_id = #sso_account_id
sso_role_name = #sso_role_name
region = #region
output = json
```

```bash:aws-cli-source.sh
export AWS_PROFILE=sso-profile
aws sso logout
aws sso login
# たまに期限切れのトークンが残っていることがあったのでlogoutして消しています。
```

SSOします。

```bash
$ source aws-cli-source.sh
# 有効期限付きのトークンが発行されます。
```

では、Terraformを実行します。
```bash
$ cd mystudy-handson/tf-manifests/eks/
$ terraform init
$ terraform apply
```

私の環境ではapplyが終わるまで15分弱かかりました。。。
クラスタとノードプール作成が合計で10分以上かかって長いようでした。（まあクラスタ作るんだしそのくらいはかかるよなーといった感想です）

それではEICEでSSH接続します。キーを気にしなくてよいので少し楽ですね。

```bash
aws ec2-instance-connect ssh --instance-id <接続するEC2のID> --connection-type eice
```

ノードの様子を見てみます。

```bash
[ec2-user@ip-10-0-10-109 ~]$ k get node
NAME                                            STATUS   ROLES    AGE   VERSION
ip-10-0-0-101.ap-northeast-1.compute.internal   Ready    <none>   26m   v1.27.7-eks-e71965b
ip-10-0-1-77.ap-northeast-1.compute.internal    Ready    <none>   26m   v1.27.7-eks-e71965b
[ec2-user@ip-10-0-10-109 ~]$ k describe node ip-10-0-0-101.ap-northeast-1.compute.internal
...
Capacity:
  cpu:                1
  ephemeral-storage:  20959212Ki
  hugepages-2Mi:      0
  memory:             975592Ki
  pods:               4
Allocatable:
  cpu:                940m
  ephemeral-storage:  18242267924
  hugepages-2Mi:      0
  memory:             567016Ki
  pods:               4
...
```

ノードのインスタンスタイプはt2.microで作っているため、1台当たり1CPU,1GBで4PODしか立てられません（うち3台はすでに使われています）。。。

t2.microではノード1台につき、PODがあと1台しか立てられないということですね。
立ててみます。
```bash
[ec2-user@ip-10-0-10-109 ~]$ k run nginx --image nginx
pod/nginx created
[ec2-user@ip-10-0-10-109 ~]$ k get po
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          16s
[ec2-user@ip-10-0-10-109 ~]$ k exec nginx -- curl localhost -sI
HTTP/1.1 200 OK
Server: nginx/1.25.3
Date: Mon, 20 Nov 2023 13:06:31 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 24 Oct 2023 13:46:47 GMT
Connection: keep-alive
ETag: "6537cac7-267"
Accept-Ranges: bytes
```

できました！

# 苦労した点

## ConfigMap「aws-auth」が作成されるまで時間がかかる

クラスタが作成完了しても、1,2分ConfigMap「aws-auth」が作成されませんでした。何が進行中なのかわからず、仕方ないのでIAMプリンシパル追加前に以下のsleepを設けることにしました。

```bash
while [ `echo -n $(eksctl get iamidentitymapping --cluster "${CLUSTER}" --region ${REGION} -o json | jq -r '.[0].username')` = "null" ];do
  echo "waiting aws-auth mapping ..."
  sleep 10
done
```

## 全てのアベイラビリティーゾーン（AZ）がEKSに使えるわけではない

私の勉強不足ですが、AZにも種類があるんですね。。。

```bash
$ aws ec2 describe-availability-zones --region ap-northeast-1 | jq -r '.AvailabilityZones[].ZoneType' | sort | uniq -c
      3 availability-zone
      2 wavelength-zone
```

AWS Wavelengthはモバイルネットワークに使われるものということでした。

https://aws.amazon.com/jp/wavelength/

TerraformではフィルタリングしてEKSで使えるもののみ使うようにしています。
```tf
data "aws_availability_zones" "available" {
  filter {
    name   = "zone-type"
    values = ["availability-zone"]
  }
}
```

# EICEのキー登録は60秒で期限切れになってしまう

以下のドキュメントにあるようにEICEで登録したキーは60秒で期限切れになります。
そのため、長い処理は実行が厳しくなっています。
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/ec2-instance-connect-methods.html#ec2-instance-connect-connecting-aws-cli

ただ、試したところ強制的に切断されるわけではないので、1つのSSHコマンドで完結するようにしました。
```bash
ssh -i ${PRIVATE_KEY} ${SSH_USER}@${INSTANCE_IP} /bin/bash -e << 'EOF'
command 1...
command 2...
command 3...
EOF
# 'EOF'とすることで変数展開を防いでいます。結果、SSH接続先で展開されるようになります。
```

# おわりに

今回はEKSを作りました。作っただけなので、これから使っていきたいです。
最近はコンテナセキュリティ周りを学んでいるので、EKSではどうなっているか調べるのも面白そうです。
これからも勉強します。