[メモ] ECS cluster on Fargate をぽちぽち初期化してみる

# 経緯

業務で分散ロードテストフレームワークを作るために ECS on Fargate を使用する。
最終的にはECSクラスターをTerraformでデプロイする予定。その前段階としてAWS console上でぽちぽちいじりながら作ってみる。

# やりたいこと

最終的に以下をできるようにする。
- AWS ECS task をローカルの aws cli で実行する。この際に、コンテナ内実行時に参照可能な環境変数を指定する。
- AWS ECS task では、ECR上のプライベートレポジトリ内のDocker imageを使用してコンテナを立ち上げる。
- AWS ECS task 内で、S3へファイルをアップロードする。


# ステップ

## 1. ECS cluster の初期化

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/46acb27c-8779-101d-4c21-7c12ca477aaa.png)

## 2. Task definition の作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/0a11beff-0ad6-734e-cc81-cc7a7c57a9d2.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/9fc626db-c79e-40e2-ed2e-10d4684797e8.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/b4956d8b-8b02-1e76-2902-0036c628c595.png)

以下のように作成された。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/3eaab132-c5a0-c4f5-40b0-a6631f1e36ee.png)



## 3. Task を実行してみる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/edcbc099-1b15-ae81-79ac-ab879d99e1e6.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/fdb8c9b8-7479-ca61-5bfe-60e0def59ec0.png)

以下のように実行された。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/c8a1e467-6158-e43f-3706-7c51e5e44642.png)


## 4. カスタマイズ(1)

> 自前の Docker image をECR内に用意し、ECSがそれをpullしてきて実行させたい。


ECRのレポジトリの作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/2bd383e8-cbee-c5ff-0df6-e0e839849c8a.png)

ローカルでDockerfileを作成する。

```Dockerfile
FROM ubuntu:latest
CMD echo "Hello from Daiki in custom docker file"
```

これをビルドしてプッシュする。

```sh
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 111111111111.dkr.ecr.us-east-1.amazonaws.com

docker build -t daiki-test .

docker tag daiki-test:latest 111111111111.dkr.ecr.us-east-1.amazonaws.com/daiki-test:latest

docker push 111111111111.dkr.ecr.us-east-1.amazonaws.com/daiki-test:latest
```

ECR上にイメージが存在するのを確認できた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/03542357-1b3f-97de-a298-89cfb1e1d517.png)


これを使って、ECS task definition の新しいrevisionを作成する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/b06c9059-28eb-23fe-ee61-af690189a87a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/be52e5d8-3309-0d1e-6580-2be64aeb3496.png)

`daiki-loadtesting-test-task:2`が完成した。

自分のMacは、`arm64`なので、以下のようにECSクラスターの定義を更新した。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/f4ac430e-b7e8-927d-7408-907ebfb250bc.png)


このECSタスクを実行する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/3fb8d937-1f36-06e7-668a-938349ee2bd6.png)

実行完了。以下がログ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/13408e9c-4e47-9ce7-84c7-eb99f58734b9.png)

## AWS CLIから実行する

以下をローカルで実行する。subnetは、default subnetを指定した。
(AWS console上でタスクを実行した場合はこれが使用されていた。)


```sh
aws ecs run-task \
    --cluster daiki-load-testing-test \
    --task-definition daiki-loadtesting-test-task:3 \
    --launch-type FARGATE \
    --network-configuration "awsvpcConfiguration={subnets=[subnet-6331723a],assignPublicIp=ENABLED}"
```

(:pencil: `assignPublicIp=ENABLED`は ECRからベースイメージをfetchしてくる場合に必要そう？)

## 5. カスタマイズ(2)

> タスク実行時できるパラメータを、環境変数としてrun task時に外部から注入したい。

まずは、Dockerfileを以下のように更新し、push。

```Dockerfile
FROM ubuntu:latest
CMD echo "Hello from Daiki in custom docker file. I got injected_var=${injected_var}"
```

```
docker build -t daiki-test .
docker tag daiki-test:latest 1111111111111.dkr.ecr.us-east-1.amazonaws.com/daiki-test:latest
docker push 111111111111.dkr.ecr.us-east-1.amazonaws.com/daiki-test:latest 
```

Task definition内から、`containerDefinitions.name`を調べる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/c6508281-415c-5dbe-7713-622ce715bec2.png)


これと、環境変数を指定して、以下をローカル上で実行する。

```sh
aws ecs run-task \
    --cluster daiki-load-testing-test \
    --task-definition daiki-loadtesting-test-task:3 \
    --launch-type FARGATE \
    --network-configuration "awsvpcConfiguration={subnets=[subnet-6331723a],assignPublicIp=ENABLED}" \
    --overrides '{
        "containerOverrides": [
            {
                "name": "daiki-test-image-in-ecr",
                "environment": [
                    {
                        "name": "injected_var",
                        "value": "injected_from_aws_cli_ecs_run_task"
                    }
                ]
            }
        ]
    }'
```

ECSの実行ログから、環境変数が注入されているのを確認できた。

## 6. カスタマイズ(3)

> タスク内でログをファイルに書き出し、それをS3へアップロードしたい。


S3に bucket `daiki-test-ecs` を作成する。


ローカルのDockerfileを以下のように更新する。
(TODO: aws-cli を含むベースイメージがないか調べる)

```Dockerfile
FROM ubuntu:latest
RUN apt-get update && apt-get install -y less vim curl unzip sudo
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"
RUN unzip awscliv2.zip
RUN sudo ./aws/install
CMD echo "Hello from Daiki in custom docker file. I got injected_var=${injected_var}" >> "/log.txt" && \
    aws s3 cp /log.txt s3://daiki-test-ecs/
```

ビルドしてECRにpushする。

```sh
docker build -t daiki-test . && \
    docker tag daiki-test:latest 111111111111.dkr.ecr.us-east-1.amazonaws.com/daiki-test:latest && \
    docker push 111111111111.dkr.ecr.us-east-1.amazonaws.com/daiki-test:latest
```

`ecsTaskExecutionRole`に、`AmazonEC2ContainerRegistryReadOnly`ポリシーを付与してやる。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/c4dd0117-86c6-a538-40ea-dc4b921d205e.png)


<details>
<summary>TFで自前で作成する場合</summary>

CW logにログをダンプする権限も忘れずに。

```tf
resource "aws_iam_role" "ecs_task_execution_role" {
  name = local.ecs_task_execution_role_name
  assume_role_policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Effect" : "Allow",
        "Principal" : {
          "Service" : "ecs-tasks.amazonaws.com"
        },
        "Action" : "sts:AssumeRole"
      },
    ]
  })

  inline_policy {
    name = "DumpLogsToCloudWatchLogs"
    policy = jsonencode({
      "Version" : "2012-10-17",
      "Statement" : [
        {
          Effect   = "Allow"
          Action   = ["logs:CreateLogGroup"]
          Resource = "arn:aws:logs:${local.ecs_task_log_region}:${var.account_id}:*"
        },
        {
          Action = [
            "logs:CreateLogStream",
            "logs:PutLogEvents"
          ]
          Effect   = "Allow"
          Resource = "arn:aws:logs:${local.ecs_task_log_region}:${var.region}:log-group:/ecs/benchmarking-personalization-api:*"
        },
      ]
    })
  }

  tags = var.tags
}

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

resource "aws_iam_role_policy_attachment" "aws_managed_ecr_read_only" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
  role       = aws_iam_role.ecs_task_execution_role.name
}
```
</details>


ECSのタスクを実行する。


```sh
aws ecs run-task \
    --cluster daiki-load-testing-test \
    --task-definition daiki-loadtesting-test-task:3 \
    --launch-type FARGATE \
    --network-configuration "awsvpcConfiguration={subnets=[subnet-6331723a],assignPublicIp=ENABLED}" \
    --overrides '{
        "containerOverrides": [
            {
                "name": "daiki-test-image-in-ecr",
                "environment": [
                    {
                        "name": "injected_var",
                        "value": "injected_from_aws_cli_ecs_run_task"
                    }
                ]
            }
        ]
    }'
```

結果は`Unable to locate credentials`とのこと。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/205d7196-0e26-e424-4e09-75067f0f3ac5.png)



必要な権限をECS task definitionに付与する。

IAM policyの作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/e917df2b-01cd-c8f1-92c4-05600931466b.png)


IAM roleの作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/56607870-8126-8f98-96c7-f66656f50975.png)

作成したIAM roleをECS taskに紐付ける。
新しく、`daiki-loadtesting-test-task:4`を発行した。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/2e65a708-db61-be63-d813-ae31226a7c5a.png)


```sh
aws ecs run-task \
    --cluster daiki-load-testing-test \
    --task-definition daiki-loadtesting-test-task:4 \
    --launch-type FARGATE \
    --network-configuration "awsvpcConfiguration={subnets=[subnet-6331723a],assignPublicIp=ENABLED}" \
    --overrides '{
        "containerOverrides": [
            {
                "name": "daiki-test-image-in-ecr",
                "environment": [
                    {
                        "name": "injected_var",
                        "value": "injected_from_aws_cli_ecs_run_task"
                    }
                ]
            }
        ]
    }'
```


タスク内でS3へのアップロードが完了していた。
S3内にファイルが存在することも確認した。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/f632ef0f-5f10-d55f-27f6-f0670fdb0d44.png)



# (雑な追記) ECSのRunTaskに必要となる権限

例えばLambdaから実行する場合は以下の権限が必要。
`iam:PassRole`を忘れずに。


```tf
resource "aws_iam_policy" "ecs-task-runner-lambda" {
  name        = "ecs-task-runner-lambda"
  path        = "/"
  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Action = [
          "ecs:RunTask",
        ],
        Resource = "arn:aws:ecs:us-east-1:111111111111:task-definition/my-ecs-task:*",
      },
      {
        Effect = "Allow",
        Action = [
          "iam:PassRole",
        ],
        Resource = [
          "arn:aws:iam::111111111111:role/my-ecs-task-role",
          "arn:aws:iam::111111111111:role/my-ecs-task-execution-role",
        ]
      },
    ]
  })
}
```







# Ref.

https://aws.amazon.com/jp/solutions/implementations/distributed-load-testing-on-aws/