Terraform を利用した Amazon EKS の構築

2024-07-17
デベロッパーのためのクラウド活用方法

Author : 邵 正

近年、インフラストラクチャのコード化が注目されており、多くの企業で導入が進んでいます。インフラストラクチャのコード化により、繰り返しの手作業を減らし、効率的な開発が可能になります。

本記事では、Amazon Elastic Kubernetes Service (EKS) クラスターの構築を例にとり、Terraform を活用してインフラストラクチャを自動化する方法を解説します。

このクラウドレシピ (ハンズオン記事) を無料でお試しいただけます »

毎月提供されるクラウドレシピのアップデート情報とともに、クレジットコードを受け取ることができます。 


AWS for Games

AWS for Games はより早い開発、よりスマートな運営、そしてより楽しいゲームへの成長という Build、Run、Grow の 3 つの柱に沿ってサポートします。今回は Build の柱 クラウドゲーム開発 のお話になります。


Terraform の概要

Terraform は、インフラストラクチャを安全かつ効率的に構築、変更、バージョン管理するためのツールです。主な特徴は、まずインフラストラクチャをコード化できる点です。柔軟性の高い言語である HashiCorp Configuration Language(HCL) を使用してインフラを記述することで、アーキテクチャをバージョン管理し、再利用や共有が容易になります。次に、Terraform には「実行プラン」機能があり、これはリソースを反映する前に Terraform が何を行うかを示すプランを生成することが可能です。これにより、予期しない変更を防ぐことができます。

さらに、Terraform はリソースのグラフを構築し、依存関係のないリソースの作成や変更を並行して行います。インフラを効率的に構築でき、依存関係の把握も容易です。

Terraform は、aws provider を利用して AWS のリソースを構築するだけでなく、terraform-aws-modules のような高度なモジュールを利用することで、さらに簡単に構築できます。


EKS クラスター構築の実践例

EKS クラスターを構築する際には、多くのリソースが関与します。aws_eks_cluster を直接利用して構築する方法もできますが、今回は Terraform モジュール terraform-aws-eks を使用することで、簡単に本番環境で使用可能なクラスターを作成する手順を紹介します。

1. Terraform CLI をインストールします。検証環境として、Amazon EC2 上の Ubuntu 22.04 を使用しました。

$ sudo apt-get update && sudo apt-get install -y gnupg software-properties-common

# HashiCorp repository の GPG key を import
$ wget -O- https://apt.releases.hashicorp.com/gpg | \
gpg --dearmor | \
sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null

# GPG キーの確認
$ gpg --no-default-keyring \
--keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg \
--fingerprint

$ echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/hashicorp.list

# terraform package の install
$ sudo apt update
$ sudo apt-get -y install terraform

# tab key の自動補完機能を install
$ terraform -install-autocomplete
$ complete -C /usr/bin/terraform terraform

# terraform version の確認
$ terraform -version
Terraform v1.9.2
on linux_amd64

今回の最終構成は以下のようになります。

$ tree .
.
├── backend.tf
├── main.tf
├── outputs.tf
├── providers.tf
├── terraform.tfstate
├── terraform.tfvars
├── variables.tf
└── versions.tf

3. Providers の設定を行います。

$ cat << EOF > providers.tf
provider "random" {}

# https://registry.terraform.io/providers/hashicorp/aws/latest/docs#aws-configuration-reference
# 環境変数でも設定は可能です。ここでは Terraform に直接設定を入れてます。
provider "aws" {
  region = var.region
}
EOF

4. Backend の設定を行います。

設定を簡易化するため、Terraform の state ファイルをローカルに保存していますが、実際運用するときは、Amazon S3 のような安全な場所に入れておきましょう、一旦ローカルに保存したあとで、terraform init -migrate-state を使えば、自動的に S3 に移動することができるので便利です。

$ cat << EOF > backend.tf
terraform {
  backend "local" {
    path = "terraform.tfstate"
  }
}
EOF

5. versions.tf では、terraform バージョンやプロバイダーのバージョンを指定するためのファイルです。このファイルは、terraform の構成の一貫性を保ち、互換性のないバージョンによる問題を防ぐ役割を果たします。

$ cat << EOF > versions.tf
terraform {
  # required Terraform version
  required_version = ">= 1.9.2"

  required_providers {
    random = {
      source = "hashicorp/random"
      version = ">= 3.6.1"
    }
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.48.0"
    }
  }
}
EOF

6. Amazon VPC の構築を行います。

terraform-aws-vpc を利用することで、Amazon VPC や関連する Subnet、Nat Gateway、Route Table などのリソースをまとめて作成できます。

$ cat << EOF > main.tf

# 共通で使う変数の設定
locals {
  azs = slice(data.aws_availability_zones.available.names, 0, 3)

  tags = {
    project    = var.name_prefix
    managed_by = "Terraform"
  }
}

# data を利用して現在のリージョンから使える aws availability zones を取得します。
# この手法を使えば様々なリソースを Dynamic に生成できます。
# 他によく使われている例としてたとえば、Amazon Linux や Ubuntu の AMI の取得が挙がられます。
data "aws_availability_zones" "available" {}

# VPC 関連のリソースを構築
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name                  = var.name_prefix
  cidr                  = var.vpc_cidr
  secondary_cidr_blocks = var.secondary_cidr_blocks

  azs = local.azs

  private_subnets = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 2, k)]
  public_subnets  = [for k, v in local.azs : cidrsubnet(var.secondary_cidr_blocks.0, 2, k)]
  intra_subnets   = [for k, v in local.azs : cidrsubnet(var.secondary_cidr_blocks.1, 2, k)]

  map_public_ip_on_launch = true
  enable_nat_gateway      = true
  single_nat_gateway      = true
  create_egress_only_igw  = true
  
  public_subnet_tags = {
    "subnet-type"            = "public"
    
    # 外向けの ELB はこちらの Subnet を利用
    "kubernetes.io/role/elb" = 1
  }

  private_subnet_tags = {
    "subnet-type"                     = "private"
    
    # 内向けの ELB はこちらの Subnet を利用
    "kubernetes.io/role/internal-elb" = 1
  }

  tags = local.tags
}

7. EKS 本体の構築を行います。

こちらの例では Managed Group の EKS を構築する例となります。トラブルシューティングがやすいように SSH を設定したり、addon を入れる方法も示しています。

$ cat << EOF >> main.tf

# Managed Node に ssh 用の鍵を生成
module "key_pair" {
  source  = "terraform-aws-modules/key-pair/aws"
  version = "~> 2.0"

  key_name_prefix    = var.name_prefix
  create_private_key = true

  tags = local.tags
}

# VPC の CIDR から ssh を許可する firewall ルールを定義
resource "aws_security_group" "remote_access" {
  name_prefix = "${var.name_prefix}-remote-access"
  description = "Allow remote SSH access"
  vpc_id      = module.vpc.vpc_id

  ingress {
    description = "SSH access"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [var.vpc_cidr]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = merge(local.tags, { Name = "${var.name_prefix}-remote" })
}

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 20.0"

  cluster_name                   = var.name_prefix
  cluster_version                = var.eks_version
  cluster_endpoint_public_access = true
  cluster_ip_family              = "ipv4"
  vpc_id                         = module.vpc.vpc_id
  subnet_ids                     = concat(module.vpc.private_subnets, module.vpc.public_subnets)
  control_plane_subnet_ids       = module.vpc.intra_subnets
  create_kms_key                 = false
  cluster_encryption_config      = {}
  enable_irsa                    = true
  
  # EKS addon をこちらで install
  # 利用可能な addon 一覧はこのコマンドから確認することができる
  # $ aws eks describe-addon-versions  \
  # --query 'sort_by(addons &owner)[].{publisher: publisher, owner: owner, addonName: addonName, type: type}' \
  # --output table
  cluster_addons = {
    coredns = {
      most_recent = true
    }
    kube-proxy = {
      most_recent = true
    }
    eks-pod-identity-agent = {
      most_recent = true
    }
    aws-ebs-csi-driver = {
      most_recent = true
    }
    vpc-cni = {
      most_recent    = true
      before_compute = true
      configuration_values = jsonencode({
        env = {
          // must enable this if pod is running in private subnet and access going out via nat gateway
          AWS_VPC_K8S_CNI_EXTERNALSNAT = "true"

          # Reference docs https://docs.aws.amazon.com/eks/latest/userguide/cni-increase-ip-addresses.html
          ENABLE_PREFIX_DELEGATION = "true"
          WARM_PREFIX_TARGET       = "1"
        }
      })
    }
  }

  # x86_64 の manged node group を利用する例となりますが、
  # その以外に、AL2_ARM_64 や Fargate なども利用が可能
  eks_managed_node_group_defaults = {
    ami_type = "AL2_x86_64"
    # 利用可能なインスタンスタイプはこちらのページから確認することができます https://console.aws.amazon.com/ec2/home?#InstanceTypes:v=3;instanceFamily=t3,t3a;defaultVCPus=%3C%5C=2
    # vpc-cniが有効になっているため、各インスタンスタイプで利用可能なIPアドレスの数を確認する必要があります
    instance_types = ["t3a.medium"]
    subnet_ids     = module.vpc.private_subnets
  }

  eks_managed_node_groups = {
    default_node_group = {
      use_custom_launch_template = false
      disk_size = 20
      remote_access = {
        ec2_ssh_key               = module.key_pair.key_pair_name
        source_security_group_ids = [aws_security_group.remote_access.id]
      }
    }
  }

  # クラスターアクセスエントリ
  # 現在の caller identity を管理者として追加
  enable_cluster_creator_admin_permissions = true

  # 他に EKS にアクセスできる IAM Role/User はこちらで付与することも可能
  # access_entries = {
  #   default = {
  #     kubernetes_groups = []
  #     principal_arn     = "FILL_THIS_PRINCIPAL_ARN"

  #     policy_associations = {
  #       default = {
  #         policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSAdminPolicy"
  #         access_scope = {
  #           type = "cluster"
  #         }
  #       }
  #     }
  #   },
  # }

  tags = local.tags
}
EOF

8. variables.tf および terraform.tfvars のファイルでは、今回利用していた変数の定義をこのように設定しています。

$ cat << EOF > variables.tf
variable "eks_version" {
  type = string
  description = "The EKS version to use"
}

variable "vpc_cidr" {
  type = string
  description = "The CIDR block for the VPC"
}

variable "secondary_cidr_blocks" {
  type = list(string)
  description = "The secondary CIDR blocks for the VPC"
}

variable "name_prefix" {
  type = string
  description = "The prefix to use for all resources"
}

variable "region" {
  type = string
  description = "The AWS region to deploy to"
}
EOF

$ cat << EOF > terraform.tfvars
eks_version           = "1.29"
name_prefix           = "terraform-eks-example"
vpc_cidr              = "10.0.0.0/16"
secondary_cidr_blocks = ["10.1.0.0/16", "10.2.0.0/16"]
region                = "ap-northeast-1"
EOF

9. 構築してみましょう。

初めに init コマンドで初期化を行い、必要な module や provider をダウンロードします。次に plan コマンドで変更内容の確認をし、最後に apply コマンドで実際に環境に変更を反映します。

$ terraform init
$ terraform plan -out ./plan
$ terraform apply ./plan

まとめ

本記事では、Terraform を使ってインフラストラクチャを自動化する方法について詳しく解説しました。

Terraform を活用することで、インフラストラクチャの自動化が簡単に行えます。例えば、AWS のリソースをコードで管理したり、再利用可能なモジュールを使って設定を効率化することが可能です。

また、Terraform を使用する際のトラブルシューティングも簡単です。環境変数に TF_LOG=DEBUG を設定することで、Terraform の実行時に行われる API リクエストの詳細な内容を確認することができます。これにより、問題の特定や解決が迅速に行えます。

この記事を参考に、あなたの環境で Terraform を実際に試してみて、インフラストラクチャの管理を効率化しましょう !


builders.flash メールメンバーへ登録することで
AWS のベストプラクティスを毎月無料でお試しいただけます

筆者プロフィール

邵 正
アマゾン ウェブ サービス ジャパン合同会社
ソリューションアーキテクト

Linux カーネル、コンテナ、データベースにフォーカスしています。お客様と協力して様々なクラウドインフラストラクチャのチャレンジを一緒に解決することで、よりヘルシーな設計・運用ができるように、サポートしています。

AWS を無料でお試しいただけます

AWS 無料利用枠の詳細はこちら ≫
5 ステップでアカウント作成できます
無料サインアップ ≫
ご不明な点がおありですか?
日本担当チームへ相談する