【AWS】IaCで環境構築･コンテナの機能を理解する

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

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

## 今回の構成図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/e2d332f8-0d9c-1ac3-557b-6d2af6139fc1.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
②下記コマンドで基本設定(※リージョンとフォーマットは任意)
```bash
$ 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) を作成し以下を記述して保存
```hcl
provider "aws" {
  region = "ap-northeast-1" 
}

resource "aws_ecr_repository" "my_repository" {
  name = "my-ecr-repo"
}
```
ファイルの保存後に下記を実行してTerraformを実行
```bash
terraform init
terraform apply
```

下記で作成を確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/49a2c13d-c8e0-8132-e611-4005a65e08bc.png)

## Dockerイメージをビルド
Dockerfile を作成して Docker イメージをビルド
→内容は参考資料を参照して下記のように
```dockerfile
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/23593666-ec93-f902-143f-6e3ace13776f.png)
黄部分 : コンテナイメージを新たに作成する際に、基とするベースイメージを指定。FROM ubuntu:18.04 は、Docker Hub で公開されている Ubuntu イメージをベースイメージとしている。

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

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

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

### ファイルをビルド
"Dockerfile"のあるディレクトリに移動して下記コマンドを実施
```bash
docker build -t my-apache-image .
```
これでイメージのビルドに成功出来ていれば次はECRにプッシュする

## DockerイメージをECRにプッシュ
```bash
#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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/0a4fe34c-8265-1cdd-e6c1-09d8e113083f.png)

# VPCの作成
こちらもあえてTerraformで作成してみる
※セキュリティグループの設定で自分のIPアドレスを指定しようと考えているため下記コマンドで検索しておく
```bash
curl http://checkip.amazonaws.com
```
設定としては
名前タグ：h4b-ecs
IPv4 CIDR:10.0.0.0/16
AZ:2
パブリックサブネット:２
VPCエンドポイント：S3ゲートウェイ
セキュリティグループ:default
インバウンドルール:HTTP　ソース:マイIP
インバウンドルール:HTTP　ソース:自分のIP/32
下記を“main.tf"に記述
```HCL
#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を実行
```bash
terraform apply
```
しかし,,,
```bash
│ 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/3d812102-9d21-c70f-b7fa-f095253b5ce8.png)


## ECSの作成
こちらもTerraformにて
上記で作成したVPCとサブネットを使用するように明記
さらにタスク定義とALB、ターゲットグループも作成
同様に"main.tf"に追記
```hcl
# 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/06fd2f35-06a9-d034-474c-385dd25c9730.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/234bde21-8e55-058c-01b8-75d461f5a954.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/8a6df6bf-a5c7-333d-51f4-8abd47e6d90d.png)

作成を確認
さらに接続テストを実施(DNS名より)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/1738ab80-3ea0-dc08-bdcb-afa8645496e3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/f0e4e2fc-500c-5161-9cc9-32320757ae09.png)
Hello World！を確認


## ECSの運用負担軽減を体験
### 目的
①コンテナの自動復旧の様子を体験する
②コンテナの数をスケールアウト/インする操作を体験する

### 自動復旧
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/2d366d58-f634-b122-e716-384127ae92bb.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/c96e5d71-8874-5de7-3eae-5e581b77c949.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/6b15107e-71af-e73a-5539-52548e4d93bc.png)
新しいタスクが出来上がる(約1分ほどで実行中に）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/c0a1eff4-2b4f-1b8b-58e1-4bb3499d1803.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/6c9c2e07-2a81-9ddb-13fa-23baf574ce5e.png)
コンテナ上の障害を検知したときに自動的に復旧する様子を確認でき、運用負荷が軽減できることを体験

### スケールアウト
また上記タスクのスケールアウトも容易である
下記で選択し更新を押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/6b01a61d-1102-b835-0b22-9495bdd39b4c.png)

ここを現状2のものを3に変更する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/8f23630f-4da0-2c1b-7888-ed7665b762c2.png)

すると
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3557022/387a6df3-cba8-f060-81c7-31fb7e01b95b.png)

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

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


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