【ハンズオン】Amazon EKSをTerraformで構築してみる

AWS

2022.10.25

Topics

はじめに

はじめまして、マイグレーションチームのwakaです。
SAAやSAPの取得を目指す際に問題としてEKSの話題が少し出るかと思いますが、概要はざっくり知っているものの実際に手を動かして触ったことが無いサービスだったので、EKSクラスター上にWebサーバを構築し、ALB経由で表示するまでの流れを実行してみたいと思います。

実行環境

  • M1 mac
  • aws-cli/2.7.21
  • Terraform v1.3.1
  • kubectl v1.23.7-eks-4721010
  • helm v3.8.2

今回実現したい構成

ECRに登録したイメージを元にpodsを作成し、ALB経由で表示するというシンプルな構成を目指します。

Terraformやkubectlで使用するファイルは折りたたんでおりますので、必要に応じてクリックで表示してください。

作成手順

  1. 構築に使用するコマンド類の準備
  2. Terraformを用いてAWS上に動作環境の構築
  3. 作成したECRにdocker imageの登録
  4. ALBを使用するための準備
  5. kubectlを用いてEKS上に環境を構築

1. 構築に使用するコマンド類の準備

1-1. aws cliの準備

各OSに応じたcliのインストール方法は公式ドキュメント参照
~/.aws/credentialsにaws cliやTerraformで使用するためのアクセスキーを設定します。

[default]
aws_access_key_id=************************************
aws_secret_access_key=************************************************************************

1-2. kubectlの準備

既にkubectlが入っているか確認します。
以下のコマンドでバージョンが表示されない場合は公式ドキュメントを参照にv1.23のインストール作業を実行してください。

kubectl version | grep Client | cut -d : -f 5

1-3. helmの準備

既にhelm入っているか確認します。
以下のコマンドでバージョンが表示されない場合は公式ドキュメントを参照にインストール作業を実行してください。

helm version

1-4. dcokerの準備

以下のコマンドでバージョンなどが表示されない場合は公式ドキュメントを参照にインストール作業を実行してください。
Cloud9などを利用している場合はこちらが参考になるかと思います。

docker info

2. Terraformを用いてAWS上に動作環境の構築

2-1. tfファイルの準備

.
├── ecr.tf
├── eks.tf
├── iam.tf
├── providers.tf
└── vpc.tf

provider.tfのアカウントIDはリージョン、バケット名などは適宜書き換える必要がございます。

IAMロール用のポリシーをダウンロードします。

curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.3/docs/install/iam_policy.json
ecr.tf
resource "aws_ecr_repository" "web_repository" {
  name                 = "${local.name_prefix}-web-repository"
  image_tag_mutability = "MUTABLE"

  force_delete = true

  image_scanning_configuration {
    scan_on_push = true
  }
}
eks.tf
module "eks" {
  source          = "terraform-aws-modules/eks/aws"
  cluster_version = "1.23"
  cluster_name    = "${local.name_prefix}-eks"
  vpc_id          = module.vpc.vpc_id
  subnet_ids      = module.vpc.private_subnets
  enable_irsa     = true
  eks_managed_node_groups = {
    main = {
      desired_size   = 1
      instance_types = ["t3.medium"]
    }
  }
}

resource "aws_security_group_rule" "allow_ingress_port" {
  security_group_id        = module.eks.node_security_group_id
  type                     = "ingress"
  from_port                = 9443
  to_port                  = 9443
  protocol                 = "tcp"
  source_security_group_id = module.eks.cluster_security_group_id
}
iam.tf
resource "aws_iam_role" "eks_load_balancer_controller_role" {
  name               = "AmazonEKSLoadBalancerControllerRole"
  assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json
  managed_policy_arns = [aws_iam_policy.eks_iam_policy.arn]
}

resource "aws_iam_policy" "eks_iam_policy" {
  name   = "AWSLoadBalancerControllerIAMPolicy"
  policy = file("./iam_policy.json")
}

data "aws_iam_policy_document" "assume_role_policy" {
  statement {
    sid     = "EKSClusterAssumeRole"
    actions = ["sts:AssumeRoleWithWebIdentity"]

    principals {
      type        = "Federated"
      identifiers = ["${module.eks.oidc_provider_arn}"]
    }

    condition {
      test     = "StringEquals"
      variable = "${module.eks.oidc_provider}:sub"
      values = [
        "system:serviceaccount:kube-system:aws-load-balancer-controller"
      ]
    }

    condition {
      test     = "StringEquals"
      variable = "${module.eks.oidc_provider}:aud"
      values = [
        "sts.amazonaws.com"
      ]
    }
  }
}
providers.tf
locals {
  name_prefix = "hoge"
  region      = "ap-northeast-1"
  aws_profile = "default"
}

provider "aws" {
  region  = local.region
  profile = local.aws_profile
}

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

vpc.tf
module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

  name = "${local.name_prefix}-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["${local.region}a", "${local.region}c"]
  public_subnets  = ["10.0.0.0/24", "10.0.1.0/24"]
  private_subnets = ["10.0.100.0/24", "10.0.101.0/24"]

  enable_nat_gateway = true

  public_subnet_tags = {
    "kubernetes.io/role/elb" = "1"
  }

  private_subnet_tags = {
    "kubernetes.io/role/internal-elb" = "1"
  }
}

2-2. TerraformでEKSの環境を構築する

terraform init
terraform plan
terraform apply

applyしてからおよそ10分~15分ほどで作成が完了するかと思います。

Terraformのapply後にノードグループが設定されたEKSやECRのリポジトリなどが作成出来ていることを確認できるかと思います。

EKS

ECR

3. 作成したECRにdocker imageの登録

3-1. フォルダ構成

.
├── Dockerfile
└── src
    ├── index.html
    └── nginx.conf
Dockerfile
FROM alpine:3.6

RUN apk update && \
    apk add --no-cache nginx

ADD src /app
ADD ./src/nginx.conf /etc/nginx/conf.d/default.conf

RUN mkdir -p /run/nginx

CMD ["nginx", "-g", "daemon off;"]
nginx.conf
server {
        listen 80 default_server;
        listen [::]:80 default_server;

        root /app;

        location / {
        }
}
index.html
<html>
  <body>
      HOGE!!
  </body>
</html>

3-2. ローカル環境でサンプルのコンテナの準備

ローカル環境でイメージのビルドを行います。

docker build ./ -t sample_nginx
docker run -d -p 8080:80 -it sample_nginx

私のようにM1などのarm系のCPUを使用している場合はDocker Buildxを使用してビルドを実行するとよいかと思います。

ローカルのPCでdocker run実行後にブラウザにhttp://localhost:8080/と入力するとindex.htmlに記載した内容が確認出来るかと思います。

3-3. ECRへコンテナイメージの登録

account_id、region、repository_nameはterraform applyした内容に応じて適宜変更が必要となります。

ECRへのログイン

aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin {account_id}.dkr.ecr.ap-northeast-1.amazonaws.com

イメージタグとリポジトリの紐付け

docker tag sample_nginx:latest {account_id}.dkr.ecr.ap-northeast-1.amazonaws.com/{repository_name}:latest

ECRにnginxのイメージのpush

docker push {account_id}.dkr.ecr.ap-northeast-1.amazonaws.com/{repository_name}:latest

3-4. ECRへイメージが登録されているかの確認

Terraformで作成したECRのリポジトリにイメージが登録されていることが確認できるかと思います。

4. ALBを使用するための準備

EKSへのaws-load-balancer-controllerのインストールの公式ドキュメントはこちら

4-1. kubectlの向き先をEKSに変更

aws eks update-kubeconfig --region ap-northeast-1 --name {cluster_name}

kubectl config current-context

current-contextの実行結果がTerraformで作成したEKSのarnとなっていれば向き先の変更作業は完了です。

4-2. k8s用のサービスアカウントの登録

cat >aws-load-balancer-controller-service-account.yaml <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/name: aws-load-balancer-controller
  name: aws-load-balancer-controller
  namespace: kube-system
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::{account_id}:role/AmazonEKSLoadBalancerControllerRole
EOF
kubectl apply -f aws-load-balancer-controller-service-account.yaml

4-3. aws-load-balancer-controllerのインストール

eks-chartsリポジトリを追加します。

helm repo add eks https://aws.github.io/eks-charts
helm repo update

helmを用いたaws-load-balancer-controllerのインストール

helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
  -n kube-system \
  --set clusterName={cluster_name} \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller 

4-4. podsのapplyに進む前の確認事項

  • aws-load-balancer-controllerのREADYが2/2となっていること
    [waka]% kubectl get deployment -n kube-system aws-load-balancer-controller
    NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
    aws-load-balancer-controller   2/2     2            2           11s
    
  • aws-load-balancer-controllerのserviceaccountが登録されていること
    [waka]% kubectl get serviceaccount -n kube-system | grep "aws-load-balancer-controller"
    aws-load-balancer-controller         1         41s
    

5. kubectlを用いてEKS上に環境を構築

5-1. kunbctl用yamlの準備

.
├── alb.yaml
└── nginx.yaml
alb.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  namespace: sample-eks
  name: sample-eks-ingress
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
spec:
  ingressClassName: alb
  rules:
    - http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: sample-eks-service-target
              port:
                number: 80
nginx.yaml
---
apiVersion: v1
kind: Namespace
metadata:
  name: sample-eks
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: sample-eks
  name: eks-sample-linux-deployment
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: sample-eks-pods
  replicas: 2
  template:
    metadata:
      labels:
        app.kubernetes.io/name: sample-eks-pods
    spec:
      containers:
      - image: 701440566517.dkr.ecr.ap-northeast-1.amazonaws.com/{repository_name}:latest
        imagePullPolicy: Always
        name: sample-eks-pods
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  namespace: sample-eks
  name: sample-eks-service-target
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  type: NodePort
  selector:
    app.kubernetes.io/name: sample-eks-pods

nginx.yaml内の{account_id}と{repository_name}の内容を自身の環境の内容に変更が必要になります。

5-2. kubectlを用いて環境の構築

namespace,podsの作成

kubectl apply -f nginx.yaml

podsの動作確認

[waka]% kubectl get pods -n sample-eks -o wide
NAME                                           READY   STATUS    RESTARTS   AGE   IP            NODE                                              NOMINATED NODE   READINESS GATES
eks-sample-linux-deployment-7bf565f6bf-4sk85   1/1     Running   0          37s   10.0.101.31   ip-10-0-101-163.ap-northeast-1.compute.internal   <none>           <none>
eks-sample-linux-deployment-7bf565f6bf-wm5s5   1/1     Running   0          37s   10.0.101.94   ip-10-0-101-163.ap-northeast-1.compute.internal   <none>           <none>

STATUSがRunningになっていればpodsの起動に成功しています。

ALBの作成、登録

kubectl apply -f alb.yaml
[waka]% kubectl describe ingress sample-eks-ingress -n sample-eks
Name:             sample-eks-ingress
Labels:           <none>
Namespace:        sample-eks
Address:          k8s-sampleek-sampleek-2b376492de-661143548.ap-northeast-1.elb.amazonaws.com
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
  Host        Path  Backends
  ----        ----  --------
  *           
              /   sample-eks-service-target:80 (10.0.101.31:80,10.0.101.94:80)
Annotations:  alb.ingress.kubernetes.io/scheme: internet-facing
              alb.ingress.kubernetes.io/security-groups: sg-0b5e9fd22db184041
              alb.ingress.kubernetes.io/target-type: ip
Events:
  Type    Reason                  Age   From     Message
  ----    ------                  ----  ----     -------
  Normal  SuccessfullyReconciled  104s  ingress  Successfully reconciled

describe ingressで取得したAddressに表示されているELBのアドレスにアクセスすると、作成したhtmlの内容が表示されるかと思います。

5-3. 動作確認出来たらお掃除

kubectl delete -f alb.yaml

terraform destroy

最後に

TerraformでEKSクラスターを作成し、kubectlを用いてEKS上にpodsを作成しALBと紐付けて表示確認を行う、という手順で動作確認を行いました。

今回はkubectlでALBを作成してpodsを紐付けましたが、TargetGroupBindingというTerraformで作成したALBにpodsを紐付けるといった処理も可能です。
また、EKSではFargateを用いての構築も可能なようですので、また後日そちらの構成で紹介が出来たらと思います。

テックブログ新着情報のほか、AWSやGoogle Cloudに関するお役立ち情報を配信中!

waka

2022年に中途入社した人です。好きなAWSサービスはLambdaです。

Recommends

こちらもおすすめ

Special Topics

注目記事はこちら