LoginSignup
0
0

【Terraform】AWS ECR・ECSを使用して仕組みを理解する

Last updated at Posted at 2024-05-16

今回の意図

Terraformとコンテナに対する知識が曖昧なためハンズオンを実施してより知識を深める。

参考資料

https://catalog.us-east-1.prod.workshops.aws/workshops/7ffc4ed9-d4b3-44dc-bade-676162b427cd/ja-JP
※この構成をTerraformを使用して作成、操作する

今回の構成図

image.png
※cloud9→Terraformを使用して作成

基本設定

今回は東京リージョン(ap-northeast-1)で実施

terraformでDockerを扱うために(環境準備)

AWS CLI のインストールと設定
Terraform のインストール
Docker のインストール

AWS CLIの設定(設定済みであれば不要)

①インストール
https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html
②下記コマンドで基本設定(※リージョンとフォーマットは任意)

$ aws configure
AWS Access Key ID [None]:
AWS Secret Access Key [None]:
Default region name [None]: ap-northeast-1
Default output format [None]: json

TerraformでECRリポジトリを作成

Terraformを使用してECRリポジトリを作成
→ Terraform 構成ファイル (main.tf) を作成し以下を記述して保存

provider "aws" {
  region = "ap-northeast-1" 
}

resource "aws_ecr_repository" "my_repository" {
  name = "my-ecr-repo"
}

ファイルの保存後に下記を実行してTerraformを実行

terraform init
terraform apply

下記で作成を確認
image.png

Dockerイメージをビルド

Dockerfile を作成して Docker イメージをビルド
→内容は参考資料を参照して下記のように

FROM ubuntu:18.04

# Install dependencies
RUN apt-get update && \
 apt-get -y install apache2

# Install apache and write hello world message
RUN echo 'Hello World!' > /var/www/html/index.html

# Configure apache
RUN echo '. /etc/apache2/envvars' > /root/run_apache.sh && \
 echo 'mkdir -p /var/run/apache2' >> /root/run_apache.sh && \
 echo 'mkdir -p /var/lock/apache2' >> /root/run_apache.sh && \ 
 echo '/usr/sbin/apache2 -D FOREGROUND' >> /root/run_apache.sh && \ 
 chmod 755 /root/run_apache.sh

EXPOSE 80

CMD /root/run_apache.sh

この記述内容は以下の画像参照
image.png
黄部分 : コンテナイメージを新たに作成する際に、基とするベースイメージを指定。FROM ubuntu:18.04 は、Docker Hub で公開されている Ubuntu イメージをベースイメージとしている。

紫部分 : Web サーバーの動作に必要な設定。Apache HTTP Server のインストール、index.html ファイルの作成、起動のためのスクリプトファイルの作成を行っている。

緑部分 : EXPOSE の指定で、コンテナ実行時にどのポートを公開したいのか指定する。80 を指定しており、HTTP 公開をする点を表現している。

青部分 : コンテナを実行時に動かすシェルスクリプトを指定する。

ファイルをビルド

"Dockerfile"のあるディレクトリに移動して下記コマンドを実施

docker build -t my-apache-image .

これでイメージのビルドに成功出来ていれば次はECRにプッシュする

DockerイメージをECRにプッシュ

#ECRにログイン
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin <AWS_ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com

#イメージをタグ付け(正しいリポジトリにプッシュするために必要)
docker tag my-apache-image:latest <AWS_ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com/my-ecr-repo:latest

#タグ付けしたイメージをプッシュ
docker push <AWS_ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com/my-ecr-repo:latest

プッシュ後にECRを開くと作成出来ていることを確認
image.png

VPCの作成

こちらもあえてTerraformで作成してみる
※セキュリティグループの設定で自分のIPアドレスを指定しようと考えているため下記コマンドで検索しておく

curl http://checkip.amazonaws.com

設定としては
名前タグ:h4b-ecs
IPv4 CIDR:10.0.0.0/16
AZ:2
パブリックサブネット:2
VPCエンドポイント:S3ゲートウェイ
セキュリティグループ:default
インバウンドルール:HTTP ソース:マイIP
インバウンドルール:HTTP ソース:自分のIP/32
下記を“main.tf"に記述

#AWSを使用することを明示
provider "aws" {
  region = "ap-northeast-1" 
}

#VPCの作成
resource "aws_vpc" "h4b_vpc" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "h4b-ecs"
  }
}

#インターネットゲートウェイの作成
resource "aws_internet_gateway" "h4b_igw" {
  vpc_id = aws_vpc.h4b_vpc.id
  tags = {
    Name = "h4b-ecs-igw"
  }
}

#パブリックサブネットの作成
resource "aws_subnet" "h4b_public_subnet_1" {
  vpc_id            = aws_vpc.h4b_vpc.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "ap-northeast-1a"
  map_public_ip_on_launch = true
  tags = {
    Name = "h4b-public-subnet-1"
  }
}

resource "aws_subnet" "h4b_public_subnet_2" {
  vpc_id            = aws_vpc.h4b_vpc.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "ap-northeast-1c"
  map_public_ip_on_launch = true
  tags = {
    Name = "h4b-public-subnet-2"
  }
}

#ルートテーブルとルートの作成
resource "aws_route_table" "h4b_public_rt" {
  vpc_id = aws_vpc.h4b_vpc.id
  tags = {
    Name = "h4b-public-rt"
  }
}

resource "aws_route" "h4b_public_route" {
  route_table_id         = aws_route_table.h4b_public_rt.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.h4b_igw.id
}

resource "aws_route_table_association" "h4b_public_subnet_1_association" {
  subnet_id      = aws_subnet.h4b_public_subnet_1.id
  route_table_id = aws_route_table.h4b_public_rt.id
}

resource "aws_route_table_association" "h4b_public_subnet_2_association" {
  subnet_id      = aws_subnet.h4b_public_subnet_2.id
  route_table_id = aws_route_table.h4b_public_rt.id
}

#S3ゲートウェイエンドポイントの作成
resource "aws_vpc_endpoint" "s3_gateway" {
  vpc_id       = aws_vpc.h4b_vpc.id
  service_name = "com.amazonaws.ap-northeast-1.s3"
  vpc_endpoint_type = "Gateway"

  route_table_ids = [aws_route_table.h4b_public_rt.id]

  tags = {
    Name = "h4b-s3-gateway"
  }
}

#セキュリティグループの作成
resource "aws_security_group" "h4b_sg" {
  vpc_id = aws_vpc.h4b_vpc.id
  name   = "default"
  tags = {
    Name = "h4b-sg"
  }

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["<YOUR_IP>/32"]  #curl http://checkip.amazonaws.comで検索した自分のIP 
  }

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["126.89.51.98/32"]
  }

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

そしてTerraformを実行

terraform apply

しかし,,,

│ Error: creating Security Group (default): InvalidParameterValue: Cannot use reserved security group name: default
│       status code: 400, request id: 0839fffa-fa43-4923-9425-9c9fa88167ae
│
│   with aws_security_group.h4b_sg,
│   on main.tf line 81, in resource "aws_security_group" "h4b_sg":
│   81: resource "aws_security_group" "h4b_sg" {

原因はセキュリティグループの名前「default」はAWSで予約されている名前のため使用できないとのことで「h4b-sg」に変更し再度実行。

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

無事成功。
image.png

ECSの作成

こちらもTerraformにて
上記で作成したVPCとサブネットを使用するように明記
さらにタスク定義とALB、ターゲットグループも作成
同様に"main.tf"に追記

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

# IAMロールの作成
resource "aws_iam_role" "ecs_task_execution_role" {
  name = "ecs_task_execution_role"

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

resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" {
  role       = aws_iam_role.ecs_task_execution_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

# ECSタスク定義の作成
resource "aws_ecs_task_definition" "h4b_task" {
  family                   = "apache-helloworld"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"
  memory                   = "512"

  container_definitions = jsonencode([
    {
      name      = "apache-helloworld"
      image     = "223303915231.dkr.ecr.ap-northeast-1.amazonaws.com/my-ecr-repo"
      essential = true
      portMappings = [
        {
          containerPort = 80
          hostPort      = 80
          protocol      = "tcp"
        }
      ]
    }
  ])

  execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
}

# ALBの作成
resource "aws_lb" "h4b_alb" {
  name               = "h4b-ecs-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.h4b_sg.id]
  subnets            = [aws_subnet.h4b_public_subnet_1.id, aws_subnet.h4b_public_subnet_2.id]

  enable_deletion_protection = false
}

# ALBターゲットグループの作成
resource "aws_lb_target_group" "h4b_target_group" {
  name     = "h4b-ecs-target-group"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.h4b_vpc.id
  target_type = "ip"

  health_check {
    path                = "/"
    protocol            = "HTTP"
    interval            = 30
    timeout             = 5
    healthy_threshold   = 2
    unhealthy_threshold = 2
  }
}

# ALBリスナーの作成
resource "aws_lb_listener" "h4b_listener" {
  load_balancer_arn = aws_lb.h4b_alb.arn
  port              = 80
  protocol          = "HTTP"

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

# ECSサービスの作成
resource "aws_ecs_service" "h4b_service" {
  name            = "h4b-ecs-service"
  cluster         = aws_ecs_cluster.h4b_ecs_cluster.id
  task_definition = aws_ecs_task_definition.h4b_task.arn
  desired_count   = 2
  launch_type     = "FARGATE"
  network_configuration {
    subnets          = [aws_subnet.h4b_public_subnet_1.id, aws_subnet.h4b_public_subnet_2.id]
    security_groups  = [aws_security_group.h4b_sg.id]
    assign_public_ip = true
  }
  load_balancer {
    target_group_arn = aws_lb_target_group.h4b_target_group.arn
    container_name   = "apache-helloworld"
    container_port   = 80
  }
  depends_on = [aws_lb_listener.h4b_listener]
}

image.png
image.png
image.png

作成を確認
さらに接続テストを実施(DNS名より)
image.png
image.png
Hello World!を確認

ECSの運用負担軽減を体験

目的

①コンテナの自動復旧の様子を体験する
②コンテナの数をスケールアウト/インする操作を体験する

自動復旧

image.png

VSCODEにて下記コマンドで1秒に1回アクセスした結果を表示実施しておく

url=http://<your alb dns name>/
while true; do TZ=JST-9 date; curl $url; sleep 1s; done

2024年 5月 16日 木曜日 13:46:49 JST
Hello World!
2024年 5月 16日 木曜日 13:46:50 JST
Hello World!
2024年 5月 16日 木曜日 13:46:51 JST
Hello World!
2024年 5月 16日 木曜日 13:46:53 JST

選択したものを停止
image.png

image.png
新しいタスクが出来上がる(約1分ほどで実行中に)
image.png

image.png
コンテナ上の障害を検知したときに自動的に復旧する様子を確認でき、運用負荷が軽減できることを体験

スケールアウト

また上記タスクのスケールアウトも容易である
下記で選択し更新を押下
image.png

ここを現状2のものを3に変更する
image.png

すると
image.png

タスクが2→3に変更されていることを確認

躓いた点

IAMロール(セキュリティ)の見直し

ロールでは以前このハンズオンを実施していた際にすでにECSのためのロールを付与していたためTerraformで付与するように指定した際にエラーが発生してしまった。
→個人利用のためおろそかにしがちだが、セキュリティ面を考慮しIAMユーザーへのロールの付与はせずグループへの付与で使用する。admin権限はなるべく使用せず、サービスごとに付与し、使用しないものを削除するという点を再認識。。少し面倒ではあるが勉強のため。。)

Terraform、コンテナの知識を取得するために上記を実施したわけであるが、結果的に権限、セキュリティというのは常に付きまとうものだと再認識しさらに理解を深める必要があると感じた。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0