なになれ

IT系のことを記録していきます

Amazon ECS 入門ハンズオンの環境をTerraformで作成する

本記事では、以下のAmazon ECS 入門ハンズオンの環境をTerraformで作成する具体的な手順を紹介します。

catalog.us-east-1.prod.workshops.aws

AWSでコンテナを用いたシステムを構築する際にはECSが有力な選択肢であるため、有用なハンズオンになっていると思います。 なお、環境構築をする際のオプション的な要素であるため、作業環境となるAWS Cloud9については環境構築の対象外としています。

全体のソースコードは以下にあります

github.com

手順

コンテナイメージの作成

最初に以下の内容の通りにECSで動かす対象となるコンテナのイメージを作成します。
コンテナイメージの作成

ECRにアップロード

コンテナイメージをアップロードする先になるECRのリポジトリを作成します。
リポジトリを作成するためのTerraformのコードは以下です。

ecr.tf

resource "aws_ecr_repository" "main" {
  name         = "h4b-ecs-helloworld"
  force_delete = true
}

output "repository_url" {
  value = aws_ecr_repository.main.repository_url
}

Terraformで環境を作成する際には、初回のみterraform initを実行し、環境を更新するたびにterraform applyを実行します。

$ terraform init
$ terraform apply

Outputs:

repository_url = "xxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/h4b-ecs-helloworld"

リポジトリのURLが出力されますので、このURLに合わせてコンテナイメージのタグ名を変更して、ECRのリポジトリにアップロードできるようにします。

$ docker build -t xxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/h4b-ecs-helloworld:0.0.1 .

コンテナイメージをリポジトリにアップロード(push)するために、docker login が必要です。成功した場合、Login Succeededという出力があります。

$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin xxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com

Login Succeeded

コンテナイメージをECRにアップロードします。

$ docker push xxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/h4b-ecs-helloworld:0.0.1

VPCの作成

ECS クラスターを作成するために、新しくVPC を作成します。Terraformのコードは以下です。
VPCとパブリックサブネットを2つ作成します。また、デフォルトのセキュリティグループの設定を変更し、HTTPのプロトコルでのアクセスを許可するようにします。

vpc.tf

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name = "h4b-ecs"
  }
}

resource "aws_subnet" "public1" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.0.0/20"
  availability_zone = "ap-northeast-1a"
  tags = {
    Name = "h4b-ecs-public1-ap-northeast-1a"
  }
}

resource "aws_subnet" "public2" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.16.0/20"
  availability_zone = "ap-northeast-1c"
  tags = {
    Name = "h4b-ecs-public2-ap-northeast-1c"
  }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "h4b-ecs"
  }
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }
  tags = {
    Name = "h4b-ecs-rtb-public"
  }
}

resource "aws_route_table_association" "public1" {
  subnet_id      = aws_subnet.public1.id
  route_table_id = aws_route_table.public.id
}

resource "aws_route_table_association" "public2" {
  subnet_id      = aws_subnet.public2.id
  route_table_id = aws_route_table.public.id
}

resource "aws_default_security_group" "default" {
  vpc_id = aws_vpc.main.id

  ingress {
    protocol  = -1
    self      = true
    from_port = 0
    to_port   = 0
  }

  ingress {
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    from_port   = 80
    to_port     = 80
  }

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

ECSクラスターの作成

ECSクラスターは、コンテナを動かすための論理的なグループです。ECSを操作するために、まずは ECSクラスターを作成していきます。Terraformのコードは以下です。

ecs.tf

resource "aws_ecs_cluster" "main" {
  name = "h4b-ecs-cluster"
}

タスク定義の作成

タスク定義とは、アプリケーションを動かすために、どのようにコンテナを動かすか、コンテナの動作を定義したものです。Terraformのコードは以下です。

ecs.tf

locals {
  container_name = "apache-helloworld"
}

resource "aws_ecs_task_definition" "main" {
  family                   = "h4b-ecs-task-definition"
  cpu                      = 512
  memory                   = 1024
  requires_compatibilities = ["FARGATE"]
  execution_role_arn       = aws_iam_role.ecs_task_execution_role.arn
  network_mode             = "awsvpc"
  container_definitions    = <<-EOS
  [
    {
        "name": "${local.container_name}",
        "image": "${aws_ecr_repository.main.repository_url}:0.0.1",
        "cpu": 0,
        "portMappings": [
            {
                "name": "${local.container_name}",
                "containerPort": 80,
                "hostPort": 80,
                "protocol": "tcp",
                "appProtocol": "http"
            }
        ],
        "essential": true,
        "environment": [],
        "environmentFiles": [],
        "mountPoints": [],
        "volumesFrom": [],
        "logConfiguration": {
            "logDriver": "awslogs",
            "options": {
                "awslogs-create-group": "true",
                "awslogs-group": "/ecs/h4b-ecs-task-definition",
                "awslogs-region": "ap-northeast-1",
                "awslogs-stream-prefix": "ecs"
            }
        }
    }
  ]
  EOS
  runtime_platform {
    cpu_architecture        = "X86_64"
    operating_system_family = "LINUX"
  }
}

タスク定義ではタスク実行ロールというIAMロールを設定する必要があります。以下のTerraformのコードでそのIAMロールを作成します。
このIAMロールにより、ECRリポジトリからコンテナイメージを取得する、CloudWatch Logsにコンテナログを送信するといった許可を与えます。

iam.tf

resource "aws_iam_role" "ecs_task_execution_role" {
  name = "h4b-ecs-task-execution-role"

  assume_role_policy = <<-EOS
  {
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "Service": "ecs-tasks.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
  }
  EOS
}

resource "aws_iam_policy" "ecs_task_execution_role_policy" {
  name   = "h4b-ecs-task-execution-role-policy"
  policy = <<-EOS
  {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecr:GetAuthorizationToken",
                "ecr:BatchCheckLayerAvailability",
                "ecr:GetDownloadUrlForLayer",
                "ecr:BatchGetImage",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "logs:CreateLogGroup"
            ],
            "Resource": "*"
        }
    ]
  }
  EOS
}

resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" {
  role       = aws_iam_role.ecs_task_execution_role.name
  policy_arn = aws_iam_policy.ecs_task_execution_role_policy.arn
}

サービスの作成

サービスを使用すると、ECSクラスターで、指定した数のコンテナ群(タスク)を維持できます。一般的には、長期間動かすアプリケーションに適しています。
Terraformのコードは以下です。

ecs.tf

resource "aws_ecs_service" "main" {
  name            = "h4b-ecs-service"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.main.arn
  launch_type     = "FARGATE"
  desired_count   = 2
  network_configuration {
    subnets = [
      aws_subnet.public1.id,
      aws_subnet.public2.id
    ]
    security_groups = [
      aws_default_security_group.default.id
    ]
    assign_public_ip = true
  }
  load_balancer {
    target_group_arn = aws_lb_target_group.main.arn
    container_name   = local.container_name
    container_port   = 80
  }
}

サービスを作成する際にロードバランサーも必要になります。 ロードバランサーにアクセスが来ると、ロードバランサーを経由してECSサービスで起動するタスクに接続される形になります。

lb.tf

resource "aws_lb" "main" {
  name               = "h4b-ecs-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_default_security_group.default.id]
  subnets            = [aws_subnet.public1.id, aws_subnet.public2.id]
}

resource "aws_lb_listener" "main" {
  load_balancer_arn = aws_lb.main.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.main.arn
  }
}

resource "aws_lb_target_group" "main" {
  name        = "h4b-ecs-targetgroup"
  port        = 80
  protocol    = "HTTP"
  vpc_id      = aws_vpc.main.id
  target_type = "ip"
}

これで環境作成は完了です。
サービス→ ネットワーキング→ロードバランサーDNS名にアクセスするとHello World! と表示されてアクセスできるはずです。

まとめ

この記事では、Amazon ECS 入門ハンズオンの環境をTerraformで作成する手順を紹介しました。
Terraformを使用することで、以下のメリットがあります。

  • インフラのコード化: 環境構築の手順をコードで管理することで、再現性と柔軟性が向上します。
  • バージョン管理: インフラの変更履歴をGitなどのバージョン管理システムで追跡できます。
  • 環境の再利用: 同じ環境を複数のプロジェクトやチームで再利用できます。
  • 簡易化されたデプロイ: Terraformを使うことで、インフラのデプロイが簡単になり、ミスを減らすことができます。

これらのメリットを活かし、ECSを利用したシステム構築を効率的に行うことができます。

宣伝

以下のUdemy講座を公開しています。AWSやTerraformの入門から実践的な内容までを学べるものになっています。ぜひ受講ください。

www.udemy.com