Terraform個人メモ：ローカルで作成したGoアプリケーションを、Terraformテンプレートを使用してECRからECSへ自動デプロイ

## はじめに
この記事では、ローカルで作成したGoアプリケーションをTerraformを用いてインフラをコード化し、ECSがECRからDockerイメージを自動的にプルして本番環境にデプロイする手順を紹介します。

:::note
***この記事は、過去の技術検証内容を組み合わせた実践的な応用例です。***
:::

自分用の備忘録も兼ねて、手順や実施内容を詳しくまとめていきます。**少しでも参考になれば幸いです。**

## 補足：過去記事の組み合わせによる検証

:::note warn
**※注意点**
***本記事では、以下の記事で取り上げた技術検証を組み合わせて進めています。***
:::

**1. Go言語用のDockerfileをシェルスクリプトで一括作成する方法**👇

https://qiita.com/free-honda/items/da3768998fa4f9c6d07b

**2. Dockerfileをローカルで実行し、ブラウザから『Hello, World!』を表示する方法**👇

https://qiita.com/free-honda/items/e93cb49246ed89ce08f2

**3. DockerイメージをAWS CLIを使ってECRにプッシュする方法**👇

https://qiita.com/free-honda/items/a93afb0ca7fecf443460#ecr%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%81%AE%E4%BD%9C%E6%88%90

## Terraformテンプレートの準備
以下は、今回使用したTerraformテンプレートです。このテンプレートには、ECSクラスター、タスク定義、IAMロールなど、必要なリソース設定が含まれています。

***注意点として、デプロイするアプリケーションは「ポート1323」で動作するため、`containerPort` を正しく指定する必要があります。***

```main.tf
# AWSプロバイダーの設定
provider "aws" {
  region = "ap-northeast-1"
}

# VPCの作成
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "honda-vpc"
  }
}

# パブリックサブネットの作成
resource "aws_subnet" "public1" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.0.0/20"
  availability_zone = "ap-northeast-1a"

  tags = {
    Name = "honda-subnet-public1-ap-northeast-1a"
  }
}

# インターネットゲートウェイの作成
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "honda-igw"
  }
}

# パブリックルートテーブルの作成
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "honda-rtb-public"
  }
}

# パブリックルートの設定
resource "aws_route" "public_route" {
  route_table_id         = aws_route_table.public.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.main.id
}

# サブネットとルートテーブルの関連付け
resource "aws_route_table_association" "public_subnet_association" {
  subnet_id      = aws_subnet.public1.id
  route_table_id = aws_route_table.public.id
}

# セキュリティグループの作成（ECS用）
resource "aws_security_group" "ecs_sg" {
  vpc_id = aws_vpc.main.id

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

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

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

  tags = {
    Name = "ecs-sg"
  }
}

# ECSクラスターの作成
resource "aws_ecs_cluster" "main" {
  name = "my-ecs-cluster"
}

# ECSタスク実行ロールの作成
resource "aws_iam_role" "ecs_task_execution_role" {
  name = "ecs-task-execution-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Service = "ecs-tasks.amazonaws.com" }
      Action    = "sts:AssumeRole"
    }]
  })

  inline_policy {
    name = "ecs-task-execution-policy"
    policy = jsonencode({
      Version = "2012-10-17"
      Statement = [
        {
          Effect   = "Allow"
          Action   = [
            "ecr:GetAuthorizationToken",
            "ecr:GetDownloadUrlForLayer",
            "ecr:BatchGetImage",
            "logs:CreateLogStream",
            "logs:PutLogEvents",
            "s3:GetObject"
          ]
          Resource = "*"
        }
      ]
    })
  }
}

# ECSタスク定義の作成
resource "aws_ecs_task_definition" "main" {
  family                   = "my-app-task"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"
  memory                   = "512"
  execution_role_arn       = aws_iam_role.ecs_task_execution_role.arn

  container_definitions = jsonencode([
    {
      name       = "my-app-repo"
      image      = "xxx.dkr.ecr.ap-northeast-1.amazonaws.com/my-app-repo"
      portMappings = [
        {
          containerPort = 1323
          hostPort      = 1323
          protocol      = "tcp"
        }
      ]
    }
  ])
}

# ECSサービスの作成
resource "aws_ecs_service" "main" {
  name            = "my-ecs-service"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.main.arn
  desired_count   = 1
  launch_type     = "FARGATE"

  network_configuration {
    subnets         = [aws_subnet.public1.id]
    security_groups = [aws_security_group.ecs_sg.id]
    assign_public_ip = true
  }
}
```

今回は、[過去の記事](https://qiita.com/free-honda/items/c7a68a216eef2161c567)で作成したテンプレートを基に、コンテナとホストのポート設定、およびセキュリティグループ周りの設定を修正しました。

## 実際に動かしてみた
ここでは詳細な使い方は省略しますが、私はCloudShellを使ってTerraformを実行しています。

:::note info
***詳しく知りたい方は、過去の記事で紹介していますので、ぜひ参考にしてみてください。***
:::

https://qiita.com/free-honda/items/19b4e7db8b8f24d41277#%E7%B0%A1%E5%8D%98%E3%81%AA%E3%83%87%E3%83%A2s3%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90

## デプロイ後の動作確認

デプロイが完了したら、タスクが正常に実行されていることを確認します。ここでは、ECRのイメージコンテナからプルされていることも合わせて確認しました。

![スクリーンショット 2024-12-08 12.16.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/f055905e-145e-003d-d184-74cbb130bafe.png)

ECSページの「クラスター」→「タスク」タブから、実行中のタスクを選択し、タスクのパブリックIPを確認します。

```bash
http://52.195.176.91:1323/
```

ここで、タスクが使用しているパブリックIP（例：``52.195.176.91``）を確認したら、ブラウザでアクセスしてみます。

![Screenshot 2025-01-21 at 14.35.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/25293113-cb16-0510-4ae2-f879088c65ed.png)

:::note
***想定通り、Goアプリが正常に動作し、`Hello, World!` と表示されることを確認できました！***
:::

## まとめ

ここまでお読みいただき、ありがとうございました。今回の記事は、過去の複雑な技術検証を組み合わせた内容となりましたが、無事にまとめることができて本当によかったです。

:::note warn
**ただ、CloudFormationで同じことをしていた経験があったため、その知識を活用して比較的スムーズに進めることができました。**
:::

***この記事がどなたかの技術的な参考になれば幸いです！***