キーワードで検索

今日を知り、明日を変えるシステム運用メディア

TerraformでECSにコンテナをデプロイする方法を解説!ECRへイメージをプッシュする方法もあわせて解説

Amazon ECS(Elastic Container Service)は、コンテナ化されたアプリケーションを簡単に実行・管理できる便利なサービスです。Terraformと組み合わせることで、インフラをコード化し、繰り返し可能かつ管理しやすい形でデプロイが可能です。

本記事は、「Hello OpsToday ECS」という言葉を表示させるWebページを作成して、それをECS上で起動する手順の解説記事となります。Terraformの基本的な知識を持つエンジニアが、より効率的にAWS環境を運用する方法を知りたい方に最適な内容です!

terraformでECRとECSを作成し、コンテナを起動する手順

実際にterraformでECRとECSを作成し、コンテナをAWS上で起動させる方法を解説します。

コンテナの概要やECSとは何か、ECRとは何か、についてはこちらの記事で解説していますので概要を知りたいという方は、まずはこちらをご覧ください。

1. HTMLファイルの作成

まずは、OpsToday-ECSフォルダ内にindex.htmlファイルを作成し、次の内容を記述してください。

<!DOCTYPE html>
<html lang=”en”>
<head>
    <meta charset=”UTF-8″>
    <meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
    <title>Hello OpsToday ECS</title>
</head>
<body>
    <h1>Hello OpsToday ECS</h1>
</body>
</html>

2. Docker イメージをビルド

HTMLファイルと同一のフォルダ内にDockerfileという名前のファイルを作成し、次の内容を Dockerfile に保存してください。

FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html

Dockerfile作成が完了したら次のコマンドで、まずはローカル環境にDocker イメージをビルドします。

(base) apple@MacBook OpsToday-ECS % docker build -t hello-opstoday-ecs .

ERROR: Cannot connect to the Docker daemon at unix:///Users/apple/.docker/run/docker.sock. Is the docker daemon running?

上記エラーは、Dockerのアプリが起動していない場合に発生します。

Docekrアプリケーションを起動し、コマンドを再実行してください。

3. tfstateファイルを格納するためのS3バケットを作成する

terraformの基本的な使い方については以前の記事で解説しています。使い方がわからない場合や環境設定がまだの場合は、こちらの記事を参照してください。

001-00004のリンク

今回は、S3に「opstoday-terraform-tstate-0111」 という名前のバケットを作成してそのバケットの中にtfstateファイルを格納します。

今回は、「opstoday-terraform-tstate-0111」の名前でバケットを作成しましたが、バケット名は全世界で一意である必要があります。他のバケット名と被らないように環境に合わせて命名してください。

4. tfstateファイルの作成

VScodeで「tfstate.tf」という名前のファイルを作成し、下記のコードを記述します。

# tfstate管理バケット
terraform {
required_version = “= 1.9.6” #terraformのバージョン
backend “s3” {
  bucket = “<ステップ6で作成したバケット名>” #AWSコンソール上のバケット名を入力
  key    = “opstoday-terrafom-tfstate-ecs” #オブジェクト名を入力 参照/新規作成
  region = “ap-northeast-1”
}
}

# プロバイダー指定
provider “aws” {
region = “ap-northeast-1”
}

コードの入力が完了したら、VScodeのターミナル上で下記コマンドを実行します。

terraform init

下記の画像のようにTerraform has been successfully initialized!と表示されていれば、tfstateファイルの作成が完了しています。

5. Terraformコードの作成

OpsToday-ECSフォルダ内に以下の4つのファイルを作成します。

  • ecr.tf
  • ecs.tf
  • iam.tf
  • network.tf

4つのファイル内にそれぞれコードを記述していきます。

ecr.tf

resource “aws_ecr_repository” “main” {
name         = “hello-world-opstoday”
force_delete = true
}

output “repository_url” {
value = aws_ecr_repository.main.repository_url
}

ecs.tf

resource “aws_ecs_cluster” “main” {
name = “OpsToday-ecs-cluster”
}

locals {
container_name = “apache-helloworld”
}

resource “aws_ecs_task_definition” “main” {
family                   = “OpsToday-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 = jsonencode([
{
  “name”: local.container_name,
  “image”: “${aws_ecr_repository.main.repository_url}:latest”,
  “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/OpsToday-ecs-task-definition”,
      “awslogs-region”: “ap-northeast-1”,
      “awslogs-stream-prefix”: “ecs”
    }
  }
}
])
}

resource “aws_ecs_service” “main” {
name            = “OpsToday-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
}
}

iam.tf

resource “aws_iam_role” “ecs_task_execution_role” {
name = “OpsToday-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   = “OpsToday-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
}

network.tf

resource “aws_vpc” “main” {
cidr_block           = “10.0.0.0/16”
enable_dns_support   = true
enable_dns_hostnames = true
tags = {
  Name = “OpsToday-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 = “OpsToday-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 = “OpsToday-ecs-public2-ap-northeast-1c”
}
}

resource “aws_internet_gateway” “main” {
vpc_id = aws_vpc.main.id
tags = {
  Name = “OpsToday-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 = “OpsToday-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
}
}

resource “aws_lb” “main” {
name               = “OpsToday-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        = “OpsToday-ecs-targetgroup”
port        = 80
protocol    = “HTTP”
vpc_id      = aws_vpc.main.id
target_type = “ip”
}

4ファイル全てにコードをかけたら下記コマンドで文法に誤りがないかチェックを行います。

terraform plan

エラーが出ることなく、下記のように18個のリソースを作成するという出力となっていれば問題ありません。

Plan: 18 to add, 0 to change, 0 to destroy.

6. AWSリソースを作成する

terraform apply

上記コマンドを実行して実際にAWS上にリソースをデプロイします。

下記のように、リソース作成を行って良いかの確認が入るため、問題なければ「yes」を入力して実行します。

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only ‘yes’ will be accepted to approve.

  Enter a value: 

デプロイには数分かかりますが、
Apply complete! Resources: 18 added, 0 changed, 0 destroyed.
の文字が出力されていればリソース作成完了です。

7. イメージを ECR にプッシュ

リソース作成補は、正しくリソースが作成されているか確認をしましょう。

作成したリポジトリのリポジトリ名をクリックし、右上の「プッシュコマンドを表示」をクリックします。

プッシュコマンドが記載されたポップアップが表示されるため、ここに記載されている手順通りコマンドを入力し、ECR上にDockerファイルをプッシュします。

プッシュが完了したら、ページを更新しイメージが作成されているか確認してください。

最後にECSのタスクを開き、その中に記載されているIPアドレスをWebで検索し、ページが開かれるか確認します。

「Hello world OpsToday」と表示されていれば成功です。

今回利用したリソースは、下記コマンドで削除できます。

terraform destroy

場合によっては高額な料金が発生するため、不要なリソースは削除しましょう。

まとめ

本記事では、Terraformを使用してAWSのECSにコンテナをデプロイする手順を解説しました。シンプルな「Hello OpsToday ECS」アプリを例に、インフラのコード化からECS上でのサービス実行までを実践しました。

Terraformを活用することで、複雑なAWSリソースの構築や管理が効率的になり、再現性やスケーラビリティの向上が期待できます。今回の手順を基に、さらに高度な構成や自動化の実現に挑戦してみてください。

ECSの運用を最適化し、クラウド環境を効果的に活用する第一歩を踏み出しましょう!

現在クラウドエンジニアとして勤務。AWS(SAP、DOP)とAzure(AZ-305)の資格を保有しており、ネットワークやセキュリティに関する業務を主に行っています。

最新情報をお届けします!

最新のITトレンドやセキュリティ対策の情報を、メルマガでいち早く受け取りませんか?ぜひご登録ください

メルマガ登録

最新情報をお届けします!

最新のITトレンドやセキュリティ対策の情報を、メルマガでいち早く受け取りませんか?ぜひご登録ください

メルマガ登録