Terraformとeksctlで構築するAmazon EKS

この記事は[Amazon EKS Advent Calendar 2019](https://qiita.com/advent-calendar/2019/amazon-eks)の17日目の記事になります。

## はじめに

こんにちは、[OPENREC.tv](https://www.openrec.tv/)でSREに所属している[@toro_ponz](https://twitter.com/toro_ponz)です。白米が好きです。
先日の[リニューアル](https://openrecnext.amebaownd.com/posts/7432080)で、OPENRECではAmazon EKSによるマイクロサービスの運用が始まりました。
本記事では、EKSを運用するにあたってOPENRECで採っているEKSクラスターの構成管理について触れられればと思います。

## Amazon EKSとは

AWSが提供するKubernetesのマネージドのサービスです。2019/12/17現在、Kubernetes1.14までをサポートしています。つい先日[マネージドなKubernetesワーカーノードがサポートされる旨](https://aws.amazon.com/jp/about-aws/whats-new/2019/11/amazon-eks-adds-support-for-provisioning-and-managing-kubernetes-worker-nodes/)がアナウンスされましたが、私たちのプロダクトではまだ採用していません。

さて、コントロールプレーンがフルマネージドであることで、Kubernetesクラスターの管理はかなり容易になっているのは言うまでもありません。しかしその構築物は少なくなく、いくつかの構築方法が挙げられます。LTSがなく数か月に一度バージョンが更新されるKubernetesでは、ワーカーノードだけでなくコントロールプレーンの更新容易性も重要になることは必至です。

## AWSマネジメントコンソールを使った構築方法

これまでマネジメントコンソールから作成できるリソースはコントロールプレーンのみでしたが、先述したマネージドノードグループのサポートが発表され、ノードグループの作成もWeb上から簡単に行えるようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262177/47d048af-f04c-5abf-0172-e856a8a8514b.png)
「Add node group」からノードグループ作成画面へ行き、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262177/f05fd6a8-63a0-2aca-db7b-9dd675887bee.png)
ロールやサブネット、ノードのスケーリングの設定値を入力して、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262177/17b681fd-751d-a442-cbed-5f15e870dbfe.png)
ノードグループを作成することができます。

オートスケーリンググループや起動テンプレートを自分で作成しなくても良くなり、EKSクラスターの構築はある程度楽になりました。
しかし依然として指定するリソースや構成物は多いため、可能であればInfrastructure as Code(以下IaC)として構成管理しておきたいところです。

## eksctlを使ったEKS構築

現状最も**簡単に**EKSクラスターを構築できるであろう手法が、[eksctl](https://eksctl.io/)を使った方法です。eksctlはWeaveworks社が開発しているEKSの操作を容易にする公式のCLIツールで、`eksctl create cluster`コマンドではVPCからコントロールプレーン、ノードグループまでをCloudFormationで構築した後、kubectlで接続情報の構成を行ってくれます。

```shell
eksctl create cluster --name test-cluster
```

試しに上のコマンドを実行すると、以下の2つのCloudFormationスタックが作成され、VPCからノードグループまでが作成されます。コマンド一つでEKSを利用できる状態になるため、マネジメントコンソールでEKSを構築するよりも気軽に行えます。CloudFormationのリソース欄を見ると、その構築物の多さがうかがえます。

| control plane |　nodegroup |
---|---
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262177/611e88f8-de54-a657-d9db-e1d7185282c2.png)　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262177/b47f2843-acbc-b5f7-6f8c-99ae2728ba49.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262177/4bb0bc62-91b5-058e-8d96-9772b658b24e.png) |

作成された1つ目のスタックを見ると、VPCやルートテーブル、そしてEKSのコントロールプレーンが含まれていることが分かります。VPC等とコントロールプレーンといったリソースが同一のスタックで管理されていることはあまり好ましくないように感じます。eksctlはコマンドライン引数で外部で作成したリソースを指定することもできるため、コントロールプレーンとそれ以外のものは管理を分けた方が運用しやすいでしょう。

```shell
eksctl create cluster \
  --name xxxx
  --vpc-private-subnets=subnet-xxxxxxxx,subnet-xxxxxxxx,subnet-xxxxxxxx \
  --vpc-public-subnets=subnet-xxxxxxxx,subnet-xxxxxxxx,subnet-xxxxxxxx
```

しかし、CLIでの実行となるため、宣言的にクラスターの構成管理を行うことができません。宣言的なTerraformでの構成管理にシフトしていっている私たちとしては、指示的に構築するAWSリソースは最小限に抑えたいものです。

そこで私たちは、EKSクラスターの構築にTerraformとeksctlを組み合わせて利用しています。

## OPENRECでのEKS構築

先述したとおり、Terraformでの構成管理は宣言的です。EKS環境を構築するにあたって、VPCやルートテーブル、サブネットなどは基本的に変更しないこともあり、Terraformで管理します。一方で、コントロールプレーンや、ワーカーノード(ノードグループ)はeksctlで作成します。

しかし、ここでeksctlコマンドにTerraformで作成したリソースを引数で渡す様なことはしません。eksctlはyamlファイルでのクラスター構築をサポートしているため、以下の様な形式でEKSを配置するVPCやサブネット、ノードグループを指定することができます。

```yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: xxxxxxxxxxxxxxxxx
  region: ap-northeast-1
  version: "1.14"
iam:
  withOIDC: true
cloudWatch:
  clusterLogging:
    enableTypes: ["api", "authenticator", "controllerManager", "scheduler"]
vpc:
  id: vpc-xxxxxxxxxxxxxxxxx
  cidr: xxxxxxxxxxxxxxxxx/xx
  subnets:
    public:
      ap-northeast-1a:
        id: subnet-xxxxxxxxxxxxxxxxx
        cidr: xxxxxxxxxxxxxxxxx/xx
      ap-northeast-1c:
        id: subnet-xxxxxxxxxxxxxxxxx
        cidr: xxxxxxxxxxxxxxxxx/xx
      ap-northeast-1d:
        id: subnet-xxxxxxxxxxxxxxxxx
        cidr: xxxxxxxxxxxxxxxxx/xx
    private:
      ap-northeast-1a:
        id: subnet-xxxxxxxxxxxxxxxxx
        cidr: xxxxxxxxxxxxxxxxx/xx
      ap-northeast-1c:
        id: subnet-xxxxxxxxxxxxxxxxx
        cidr: xxxxxxxxxxxxxxxxx/xx
      ap-northeast-1d:
        id: subnet-xxxxxxxxxxxxxxxxx
        cidr: xxxxxxxxxxxxxxxxx/xx
nodeGroups:
  - name: xxxxxxxxxxxxxxxxx
    minSize: 3
    maxSize: 10
    iam:
      instanceProfileARN: arn:aws:iam::0000000000:instance-profile/xxxxxxxxxxxxxxxxx
      instanceRoleARN: arn:aws:iam::0000000000:role/xxxxxxxxxxxxxxxxx
    instancesDistribution:
      instanceTypes: [m5.large]
      onDemandBaseCapacity: 0
      onDemandPercentageAboveBaseCapacity: 0
    privateNetworking: true
    preBootstrapCommands:
      - "yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm"
      - "systemctl enable amazon-ssm-agent"
      - "systemctl start amazon-ssm-agent"
    securityGroups:
      attachIDs: [sg-xxxxxxxxxxxxxxxxx]
    ami: ami-xxxxxxxxxxxxxxxxx
    amiFamily: AmazonLinux2
    tags:
      "k8s.io/cluster-autoscaler/enabled" : "1"
      "k8s.io/cluster-autoscaler/xxxxxxxxxxxxxxxxx" : "1"
```

私たちはこのyamlファイルの生成までをTerraformで行い、それをoutputとして吐き出してgitにステージングしています。Terraformのコードでいうと、`null_resource`でechoコマンドを実行して、yamlファイルに書き出している形です。

```hcl
resource "null_resource" "cluster" {
  provisioner "local-exec" {
    command = "echo '${data.template_file.cluster.rendered}' > outputs/${var.eks_cluster_name}.yaml"
  }
  triggers = {
    template = data.template_file.cluster.rendered
  }
}
```

Terraformによって吐き出されたyamlファイルを使い、eksctlを実行します。こうすることでeksctlから可能な限り指示的な要素を減らし、EKSクラスターのIaCを実現しています。

```shell
eksctl create cluster -f xxxxxxxxxx.yaml
```

ノードグループの変更などをする際も直接eksctlを打ったり、yamlファイルを書き換えたりすることはせず、Terraformでの出力から変更を適用します。Terraformのリポジトリ1つで完結するため、管理・運用が比較的わかりやすくなっています。

## おわりに

eksctl create clusterをyamlファイルで実行されている方は多いと思いますが、yamlファイル自体をTerraformから生成しているところはそう多くないかもしれません。サブネットやセキュリティグループの番号はPRレビューでも見落としがちなパラメータだと思いますので、そういったリスクを減らせるのはこの構成の良い利点だと思っています。
依然としてノードグループの移行などではノードグループ追加、drain、ノードグループ削除のフローは変わらないですが、出来るだけTerraformによるIaCに落とし込みたい私としてはこの構成管理はしっくりきています。ご参考になれば幸いです。

## 参考

* [Amazon EKS とは](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/what-is-eks.html)
* [eksctl - The official CLI for Amazon EKS](https://eksctl.io/)
* [GitHub - weaveworks/eksctl](https://github.com/weaveworks/eksctl)
* [「eksctl」コマンドを使ったAmazon EKS構築入門](https://dev.classmethod.jp/cloud/aws/getting-started-amazon-eks-with-eksctl/)
* [eksctl で VPC を作るのをやめて Terraform で作るようにしました](https://blog.hatappi.me/entry/2019/02/17/123111)