Getting Started with Amazon EKSが長いので自動化してみた

# はじめに
EKSがGAになったので、早速 [Getting Started with Amazon EKS](https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html) をやってみました。

EKSの構築が完了してアプリケーションをデプロイできるようになるまでには、IAMロールを作成して、VPCを作成して、Masterを起動して、Workerを起動して、ConfigMapを作成して...と、たくさんのステップが必要で非常に面倒です。

今後は勉強用に気軽にEKSを起動できるよう、AWS CLIを使ったシェルスクリプトでEKSの構築を自動化しました。

# 前提

## 事前準備
以下の3つは [Getting Started with Amazon EKS](https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html) に従ってインストール済みとします。

- kubectl
- AWS CLI
- heptio-authenticator-aws

## 環境
動作確認した環境は以下の通りです。

- OS
    - macOS High Sierra バージョン 10.13.3
- Bash
    - GNU bash version 3.2.57(1)-release (x86_64-apple-darwin17)
- kubectl
    - v1.10.2
- AWS CLI
    - 1.15.35[^1]
[^1]: 2018/06/09の時点ではHomebrewでEKSに対応しているバージョンをインストールできなかったので、pipでインストールしました
- heptio-authenticator-aws
    - 1.10.3

# 自動化した内容
以下、ソースコードの抜粋です。

## IAMロールの作成
AWS CLIで作成するようにしました。

```bash
aws iam create-role \
    --role-name $EKS_ROLE_NAME \
    --assume-role-policy-document file://$POLICY_FILE

aws iam attach-role-policy \
    --role-name $EKS_ROLE_NAME \
    --policy-arn arn:aws:iam::aws:policy/AmazonEKSClusterPolicy

aws iam attach-role-policy \
    --role-name $EKS_ROLE_NAME \
    --policy-arn arn:aws:iam::aws:policy/AmazonEKSServicePolicy
```

## SSHキーペアの作成
AWS CLIで作成するようにしました。

```bash
aws ec2 create-key-pair \
    --key-name $EKS_KEY_NAME \
    --query 'KeyMaterial' \
    --output text \
    > $EKS_KEY_FILE

chmod 600 $EKS_KEY_FILE
```

## VPCの作成
[Getting Started with Amazon EKS](https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html) に記載されているCloudFormationテンプレートをAWS CLIから呼び出すようにしました。

```bash
aws cloudformation create-stack \
    --stack-name $EKS_VPC_STACK_NAME \
    --template-body https://amazon-eks.s3-us-west-2.amazonaws.com/1.10.3/2018-06-05/amazon-eks-vpc-sample.yaml \
    --parameters \
        ParameterKey=VpcBlock,ParameterValue=$VPC_BLOCK \
        ParameterKey=Subnet01Block,ParameterValue=$SUBNET_01_BLOCK \
        ParameterKey=Subnet02Block,ParameterValue=$SUBNET_02_BLOCK \
        ParameterKey=Subnet03Block,ParameterValue=$SUBNET_03_BLOCK
```

## Masterの作成
こちらも [Getting Started with Amazon EKS](https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html) に記載されているCloudFormationテンプレートをAWS CLIから呼び出すようにしました。
パラメータはVPC構築時のOutputsを使うようにしました。

```bash
ROLE_ARN=$(aws iam get-role \
    --role-name $EKS_ROLE_NAME \
    --query 'Role.Arn' \
    | sed -E 's/.(.*)./\1/')

SUBNET_IDS=$(aws cloudformation describe-stacks \
    --stack-name $EKS_VPC_STACK_NAME \
    --query 'Stacks[0].Outputs[?OutputKey==`SubnetIds`].[OutputValue][0][0]' \
    | sed -E 's/.(.*)./\1/')

SECURITY_GROUP_IDS=$(aws cloudformation describe-stacks \
    --stack-name $EKS_VPC_STACK_NAME \
    --query 'Stacks[0].Outputs[?OutputKey==`SecurityGroups`].[OutputValue][0][0]' \
    | sed -E 's/.(.*)./\1/')

aws eks create-cluster \
    --name $EKS_CLUSTER_NAME \
    --role-arn $ROLE_ARN \
    --resources-vpc-config subnetIds=$SUBNET_IDS,securityGroupIds=$SECURITY_GROUP_IDS
```

## Workerの作成
こちらも [Getting Started with Amazon EKS](https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html) に記載されているCloudFormationテンプレートをAWS CLIから呼び出すようにしました。
パラメータはVPC構築時とMaster構築時のOutputsを使うようにしました。

```bash
CLUSTER_CONTROL_PLANE_SECURITY_GROUP=$(aws cloudformation describe-stacks \
    --stack-name $EKS_VPC_STACK_NAME \
    --query 'Stacks[0].Outputs[?OutputKey==`SecurityGroups`].[OutputValue][0][0]' \
    | sed -E 's/.(.*)./\1/')

VPC_ID=$(aws cloudformation describe-stacks \
    --stack-name $EKS_VPC_STACK_NAME \
    --query 'Stacks[0].Outputs[?OutputKey==`VpcId`].[OutputValue][0][0]' \
    | sed -E 's/.(.*)./\1/')

SUBNETS=$(aws cloudformation describe-stacks \
    --stack-name $EKS_VPC_STACK_NAME \
    --query 'Stacks[0].Outputs[?OutputKey==`SubnetIds`].[OutputValue][0][0]')

aws cloudformation create-stack \
    --stack-name $EKS_WORKER_STACK_NAME \
    --template-body https://amazon-eks.s3-us-west-2.amazonaws.com/1.10.3/2018-06-05/amazon-eks-nodegroup.yaml \
    --parameters \
        ParameterKey=ClusterName,ParameterValue=$EKS_CLUSTER_NAME \
        ParameterKey=ClusterControlPlaneSecurityGroup,ParameterValue=$CLUSTER_CONTROL_PLANE_SECURITY_GROUP \
        ParameterKey=NodeGroupName,ParameterValue=$NODE_GROUP_NAME \
        ParameterKey=NodeAutoScalingGroupMinSize,ParameterValue=$NODE_AUTO_SCALING_GROUP_MIN_SIZE \
        ParameterKey=NodeAutoScalingGroupMaxSize,ParameterValue=$NODE_AUTO_SCALING_GROUP_MAX_SIZE \
        ParameterKey=NodeInstanceType,ParameterValue=$NODE_INSTANCE_TYPE \
        ParameterKey=NodeImageId,ParameterValue=$NODE_IMAGE_ID \
        ParameterKey=KeyName,ParameterValue=$EKS_KEY_NAME \
        ParameterKey=VpcId,ParameterValue=$VPC_ID \
        ParameterKey=Subnets,ParameterValue=$SUBNETS \
    --capabilities CAPABILITY_IAM
```

## kubectlの設定
Master構築時のOutputsから設定ファイルを作るようにしました。

```bash
ENDPOINT_URL=`aws eks describe-cluster \
    --name $EKS_CLUSTER_NAME \
    --query cluster.endpoint`

BASE64_ENCODED_CA_CERT=`aws eks describe-cluster \
    --name $EKS_CLUSTER_NAME \
    --query cluster.certificateAuthority.data`

mkdir -p ~/.kube

cat << EOT > $EKS_KUBE_CONFIG_FILE
apiVersion: v1
clusters:
- cluster:
    server: $ENDPOINT_URL
    certificate-authority-data: $BASE64_ENCODED_CA_CERT
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: aws
  name: aws
current-context: aws
kind: Config
preferences: {}
users:
- name: aws
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1alpha1
      command: heptio-authenticator-aws
      args:
        - "token"
        - "-i"
        - "$EKS_CLUSTER_NAME"
        # - "-r"
        # - "<role-arn>"
EOT
```

## ConfigMapの作成
Worker構築時のOutputsから設定ファイルを作るようにしました。

```bash
ROLE_ARN=$(aws cloudformation describe-stacks \
    --stack-name $EKS_WORKER_STACK_NAME \
    --query 'Stacks[0].Outputs[0].OutputValue' \
    | sed -E 's/.(.*)./\1/')

cat << EOT | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    - rolearn: $ROLE_ARN
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes
EOT
```

# まとめ
以上をまとめて、以下のコマンドでEKSの構築が完了するようになりました。

```bash
$ source set-env.sh
$ ./launch.sh
```

15分ほど待てば、Workerの設定まで完了します。

```bash
$ kubectl get node
NAME                                           STATUS    ROLES     AGE       VERSION
ip-192-168-111-33.us-west-2.compute.internal   Ready     <none>    7m        v1.10.3
ip-192-168-152-30.us-west-2.compute.internal   Ready     <none>    7m        v1.10.3
ip-192-168-250-1.us-west-2.compute.internal    Ready     <none>    7m        v1.10.3
```

ついでに廃棄も1コマンドにしました。

```bash
./cleanup.sh
```

ソースコードの全容は以下です。
https://github.com/os1ma/eks-sample/tree/master/getting-started-with-amazon-eks

## 課題
ひとまず動くように作った程度なので、課題がいくつもあります。

- 例外処理やロールバックがない
- Serviceのtype LoadBalancerを削除せずにEKSを削除してしまうと、ELBとSecurity Groupが残ってしまい、VPCの削除がうまくいかない
    - EKSを削除する前に関連するリソースを削除する処理を入れたい
- 命名にランダムな文字列などをつけていないので、名前が競合してしまう可能性がある
    - 特に、`~/.kube/config-$EKS_CLUSTER_NAME`は既存のファイルを上書きしないよう注意が必要