TerraformでEKSを構築する
公式モジュールを利用してネットワークおよびEKSクラスタを構築する。
ネットワークには VPCモジュール、EKSクラスタにはEKSモジュールを利用する。
基本的な設定はEKSモジュールのドキュメントに従い以下の通り。
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "2.78.0"
name = "hackday"
cidr = "10.1.0.0/16"
azs = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
public_subnets = ["10.1.0.0/24", "10.1.1.0/24", "10.1.2.0/24"]
}
data "aws_eks_cluster" "cluster" {
name = module.my-cluster.cluster_id
}
data "aws_eks_cluster_auth" "cluster" {
name = module.my-cluster.cluster_id
}
provider "kubernetes" {
host = data.aws_eks_cluster.cluster.endpoint
cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data)
token = data.aws_eks_cluster_auth.cluster.token
load_config_file = false
version = "~> 1.9"
}
module "my-cluster" {
source = "terraform-aws-modules/eks/aws"
cluster_name = "hackday-cluster"
cluster_version = "1.17"
subnets = module.vpc.public_subnets
vpc_id = module.vpc.vpc_id
worker_groups = [
{
instance_type = "m4.large"
asg_max_size = 5
}
]
}
これでEKSクラスタが構築できる。
eksctlやkubectlでクラスタの参照が可能。
2021-05-29 12:05:15 [ℹ] eksctl version 0.51.0
2021-05-29 12:05:15 [ℹ] using region ap-northeast-1
NAME REGION EKSCTL CREATED
hackday-cluster ap-northeast-1 False
$ aws eks update-kubeconfig --name hackday-cluster
Added new context arn:aws:eks:ap-northeast-1:xxxxxxxxxxxx:cluster/hackday-cluster to /home/thaim/.kube/config
$ kubectl get node
NAME STATUS ROLES AGE VERSION
ip-10-1-2-194.ap-northeast-1.compute.internal Ready <none> 5m22s v1.17.12-eks-7684af
サンプルとなるアプリをデプロイしてやる。
$ cat nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
selector:
matchLabels:
app: frontend
replicas: 2
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: nginx:latest
imagePullPolicy: Always
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
type: LoadBalancer
selector:
app: frontend
ports:
- protocol: TCP
port: 80
targetPort: 80
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
frontend LoadBalancer 172.20.127.205 ac03342f0e9c54c66a13ee43fcc5911f-927961018.ap-northeast-1.elb.amazonaws.com 80:30653/TCP 18s
kubernetes ClusterIP 172.20.0.1 <none>
あとはType: LoadBalancerの EXTERNAL-IPに記載されたドメインにアクセスすれば
見慣れたnginxの画面が確認できる。
(アクセスできるようになるまで1-2分掛かる)
EKSが利用するネットワークについて。
当初VPCモジュールを利用せずにネットワークを構築してEKSクラスタを構築しようとしたところ、
ワーカーノードが上手く動作してくれなかった。
クラスタは生成するがノードが検出できない。
$ eksctl get cluster
2021-05-29 13:26:28 [ℹ] eksctl version 0.51.0
2021-05-29 13:26:28 [ℹ] using region ap-northeast-1
NAME REGION EKSCTL CREATED
hackday-cluster ap-northeast-1 False
$ kubectl cluster-info
Kubernetes master is running at https://293189AABD2C71C06A5E4B7C758F82B1.gr7.ap-northeast-1.eks.amazonaws.com
CoreDNS is running at https://293189AABD2C71C06A5E4B7C758F82B1.gr7.ap-northeast-1.eks.amazonaws.com/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 172.20.0.1 <none> 443/TCP 5m58s
$ kubectl get node
No resources found in default namespace.
原因は FAQ: Why are nodes not being registered? にある通り、
ワーカーノードがEKSクラスタのエンドポイントに接続できなかったから。
パブリックサブネットにあるノードなら public_ip = true
にしろ、
プライベートサブネットにあるノードなら NATゲートウェイを有効にしろ、とある。
VPCモジュール経由でパブリックサブネットを構築する場合、
デフォルトでパブリックIPを付与してくれる。
このため、EKSモジュールの説明にある通り public_ip = true
にする必要はない。
もし VPCモジュールで map_public_ip_on_launch = false
にした場合はやはりワーカーノードが動作しなくなる。
この場合は EKSモジュールで public_ip = true
にすればワーカーノードが動作するようになる。
module "vpc" {
...
map_public_ip_on_launch = false
}
module "my-cluster" {
...
worker_groups = [
{
instance_type = "m4.large"
asg_max_size = 5
public_ip = true
}
]
}
パブリックサブネットの利用とプライベートサブネットの利用について。
AWSブログ: Amazon EKS ワーカーノードの謎を解くクラスターネットワーク では、
EKSクラスタのVPCサブネットについて以下のような説明がある。
- パブリックサブネットのみを使用します。ノードとイングレスリソース (ロードバランサーなど) は、すべて同じパブリックサブネットでインスタンスを作成します。
- パブリックサブネットとプライベートサブネットを使用します。ノードはプライベートサブネットで、イングレスリソース (ロードバランサーなど) はパブリックサブネットでインスタンスを作成します。
- プライベートサブネットのみを使用します。ノードはプライベートサブネットでインスタンスを作成します。この設定は、パブリックインターネットからの通信を受信する必要がないワークロードにのみ使用されるため、パブリックイングレスリソースはありません。
EKSドキュメント: Creating a VPC for your Amazon EKS clusterにも同様の内容の記載がある。
しかし、実際にパブリックサブネット・プライベートサブネット混合のEKSクラスタを構築してみても
ワーカーノードはパブリックサブネットを利用していた。
terraformの設定が悪いのかと、eksctlでクラスタを作成してみたが
こちらでもワーカーノードにパブリックサブネットを利用していた。
このため、パブリックサブネットとプライベートサブネットの双方を利用するEKSクラスタにおいて
ワーカーノードがプライベートサブネットを利用するには別の取組みが必要と判断。
(または不具合orアップデート等により機能しなくなった)。
ここではこの検証は保留する。
EKSクラスタがサブネットリソースに付与するタグ情報について。
EKSクラスタを構築すると、サブネットグループのタグにを付与する。
これはEKSドキュメント Subnet tagging - Cluster VPC considerations にも記載がある。
terraform apply
でVPCおよびEKSクラスタを構築直後にもう1度 terraform plan
を実行すると
以下のような差分が発生する(全サブネットで同じdiffが発生するのでprivate[1/2] および public[1/2]は省略)
# module.vpc.aws_subnet.private[0] will be updated in-place
~ resource "aws_subnet" "private" {
id = "subnet-014534b4a841af1c0"
~ tags = {
- "kubernetes.io/cluster/hackday-cluster" = "shared" -> null
# (1 unchanged element hidden)
}
~ tags_all = {
- "kubernetes.io/cluster/hackday-cluster" = "shared" -> null
# (1 unchanged element hidden)
}
# (9 unchanged attributes hidden)
}
# module.vpc.aws_subnet.public[0] will be updated in-place
~ resource "aws_subnet" "public" {
id = "subnet-0e094fbdfcfc7146a"
~ tags = {
- "kubernetes.io/cluster/hackday-cluster" = "shared" -> null
# (1 unchanged element hidden)
}
~ tags_all = {
- "kubernetes.io/cluster/hackday-cluster" = "shared" -> null
# (1 unchanged element hidden)
}
# (9 unchanged attributes hidden)
}
どうやらEKSはこのタグ情報を利用してリソースのデプロイ先を決定するらしい。
このため、このタグを削除すると type:LoadBalancerのデプロイに失敗する らしい
これを解決する方法は3通りで、
- ignore_changes に追加する
- 最初からタグを埋め込む
- EKS 1.19にアップデートする
ignore_changesの追加について、VPCモジュールを使わずにネットワークを構築している場合は容易だが、
VPCモジュールを利用している場合、サブネットのタグにignore_changesを追加することはできない。
このためこの方法は不適切。
よくある解決策としては、VPC構築時にEKSタグを埋め込む方法。
EKSモジュールのサンプルにもこの方法が採用されている。
module "vpc" {
...
public_subnet_tags = {
"kubernetes.io/cluster/${local.cluster_name}" = "shared"
"kubernetes.io/role/elb" = "1"
}
private_subnet_tags = {
"kubernetes.io/cluster/${local.cluster_name}" = "shared"
"kubernetes.io/role/internal-elb" = "1"
}
}
もっと簡単なのがEKSクラスタのバージョンを1.19以上にアップデートする方法。
1.19よりVPCサブネットにタグを追加する挙動が廃止された。
このため、単に1.19を利用すればよい。
EKSモジュールではREADMEに記載は1.17のままだが、1.19も問題なく動作する。
プライベートサブネット用に構築するNAT Gatewayについて。
VPCモジュールはデフォルトではプライベートサブネット毎にNAT Gatewayを構築する。
これは可用性を考慮すると重要だが、実際には過剰なので構築するNAT Gatewayを制御したい。
実際eksctlで構築するクラスタもデフォルトでは1つのNAT Gatewayしか構築しない。
(eksctlの挙動はどこかにドキュメント化されていたり制御方法が記載されている?)
eksctlと同様に1つのNAT Gatewayしか構築しない場合は
single_nat_gateway = true
を有効にすればよい。
これにより1つのNAT Gatewayを全プライベートサブネットで共有してくれる。
module "vpc" {
...
single_nat_gateway = true
}
可用性も考慮したい場合は、AZ毎にNAT Gatewayを構築する
one_nat_gateway_per_az = true
を有効にする方法もある。
ただし、1つのAZに複数のプライベートサブネットを構築する必要性はあまりないので、
これはデフォルトの挙動であるプライベートサブネット毎にNAT Gatewayを構築するのと
(少なくともEKSクラスタを構築する上では)ほぼ同じ挙動になる。
module "vpc" {
...
one_nat_gateway_per_az = true
}
可用性も考慮して2つのNAT Gatewayを構築したい、みたいなケースはVPCモジュールでは対応できない。
自前で全ネットワークリソースを作成する必要がある。
Warningへの対応。
上記実装でのplanはすべて以下のような警告が出力される。
Warning: Version constraints inside provider configuration blocks are deprecated
原因はkubernetes providerの宣言内部にてバージョンを指定していること。
terraform 0.14より、このような指定方法は非推奨となった。
(0.13から変更になって警告が出るようになったのが0.14から?)
provider "kubernetes" {
...
version = "~> 1.9"
}
今後は required_providersにて指定する必要がある。
terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 1.9"
}
}
}
ちなみに、ここで利用しているkubernetesプロバイダはhashicorp namespace管理のものなのでsourceの指定は省略できる。
ただし、これはterraform 0.13より古いバージョンとの後方互換性のためのものなので、
明示的に記載することを推奨している。
ちなみにsourceアドレスでホスト名が公式terraformレジストリの場合は省略でき、
ホスト名の省略はデフォルトなので推奨されていとのこと。
以上から、ソースアドレスにはホスト名は省略してネームスペースとプロバイダ名のみ明示的に記載するのが推奨の書き方となる。