Amazon ECS で実行するコンテナをローカルでテストする

# 概要

* Amazon ECS 上で実行するコンテナをローカルでテストする際ってどうしてます？
* すでにご存知の方はバシバシ使われているかもですが、改めて焼き直しということで本ブログを書いていきます。
* 紹介するツールは、こちらの [amazon-ecs-local-container-endpoints](https://github.com/awslabs/amazon-ecs-local-container-endpoints) と [ecs-cli](https://github.com/aws/amazon-ecs-cli/tree/mainline#running-tasks-locally) です。

https://github.com/awslabs/amazon-ecs-local-container-endpoints

https://github.com/aws/amazon-ecs-cli/tree/mainline#running-tasks-locally

## ちなみに ECS ってなにもの？

* [Amazon ECS](https://aws.amazon.com/jp/ecs/)とは、AWSのフルマネージドなコンテナオーケストレータサービスです。
* また、コンテナが実行される場所(データプレーン)としては、fargate、EC2、Anywhere などがあります。

## amazon-ecs-local-container-endpoints

* まず初めに [amazon-ecs-local-container-endpoints](https://github.com/awslabs/amazon-ecs-local-container-endpoints) についてです。
* 一言で表すと、**ECS 上で実行するコンテナをデプロイする前に手元のローカル環境上でテストするためのツールです。**
* ちなみに、ここで述べているテストというのは複数のテストを指しています。下記がツールを使用したときに**検証できる内容**になります。
  * **AWS の各サービスとコンテナ内のアプリがやりとりしている場合、その権限等が足りているのかを検証**
  * **ECS におけるタスク定義の検証**
  * **データプレーンが EC2 の場合、使用可能なネットワーキングモードが選べ、 awsvpc を選択したときのエミュレートによる検証**
  * **個別のタスクとして実行される複数のアプリケーションをローカル環境で検証**

### 何が嬉しいの？

* このツールの嬉しいところは、コンテナ実行のクレデンシャル情報やタスクメタデータのエンドポイントへのアクセスを検証するだけのために ECS に対しデプロイする必要がなくなることですね。

### このツールを使わないとどうすることになるのか。

* 例えば、ECS 上で実行されているコンテナが IAM ロールを使用して S3 などのサービスにアクセスする場合に、ローカルで実行するには、ローカルではクレデンシャルを環境変数などで起動するアプリケーションに渡して実行したりする必要があります。(もしくは、ローカル起動時には S3 にアクセスする機能を別の代替措置になるよう実装しておくとか)
* そんな時にこのツールを使うと、ECS で実行されるコンテナが使用するタスク用の IAM ロールを使用しているかのようにローカル環境のコンテナを動かすことができるようになります。
* それ以外にも美味しい機能がたくさんあるので、検証しながら残りを説明していきます。

### 検証
#### AWS の他のサービスを使用するためにクレデンシャル情報が必要なコンテナのテスト

* まず初めに検証することは、AWS のクレデンシャル情報を必要とするコンテナをローカルでテストする場合を想定します。
* 本ツールを使わないとなると、例えば環境変数にクレデンシャル情報を食わせ実行するなどする必要がありますが、本ツールを使用することでローカルで起動するコンテナに安全に認証情報を渡すことができます。
* 下記に、サンプルの docker-compose.yml と docker-compose.override.yml を用意しました。

##### docker-compose.yml

* ざっくりみていただければわかると思いますが、今回ローカルで起動するコンテナイメージには MySQL と WorldPress を使用しました。

```docker-compose.yml
version: '3'
services:
   db:
     image: mysql:5.7
     platform: linux/amd64
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     platform: linux/amd64
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
volumes:
    db_data:
```

##### docker-compose.override.yml

* 上記のような docker-compose ファイルがあったときに、docker-compose.yml と同時に読まれ上書きしてくれる docker-compose.override.yml を用意し、下記の例のように Amazon ECS Local Container Endpoints を使用するための設定を記述します。
* 何をここではしているのかというと、この例では、ローカルのコンテナエンドポイントが IPアドレス(169.254.170.2)をリッスンできるように Docker のブリッジネットワークを作成し、環境変数 $AWS_CONTAINER_CREDENTIALS_RELATIVE_URI に指定した 169.254.170.2/creds にHTTPリクエストを行い資格情報を取得する仕組みになっています。
* ECS 上でコンテナが実運用されている場合、ECSエージェントはこのエンドポイントを経由してコンテナにクレデンシャルを送ります。
* じゃあ、元のクレデンシャル情報は？？？というと、この場合、デフォルトのAWS CLI Profileから取得した一時的なクレデンシャルを送信します。これは、AWS CLI用の資格情報と設定を含む.awsフォルダをマウントすることで実現しているのが分かるのではないでしょうか。

```docker-compose.override.yml
version: "3"
networks:
    credentials_network:
        driver: bridge
        ipam:
            config:
                - subnet: "169.254.170.0/24"
                  gateway: 169.254.170.1
services:
    ecs-local-endpoints:
        image: amazon/amazon-ecs-local-container-endpoints
        volumes:
          - /var/run:/var/run
          - $HOME/.aws/:/home/.aws/
        environment:
          HOME: "/home"
          AWS_PROFILE: "default"
        networks:
            credentials_network:
                ipv4_address: "169.254.170.2"
                
    wordpress:
        depends_on:
            - ecs-local-endpoints
        networks:
            credentials_network:
                ipv4_address: "169.254.170.3"
        environment:
          AWS_DEFAULT_REGION: "ap-northeast-1"
          AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: "/creds"
```

実際に、docker-compose up をしてみると下記のようなコンテナたちが起動していることが確認できます。

![スクリーンショット 2022-11-07 21.13.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563635/65f780bd-f31e-cd04-c052-9fc2d5af93a5.png)


#### タスク用の IAM ロールの認証情報を使用したテスト

* ローカルでのテストする用の docker-compose ファイルで タスク用の IAM ロールを使用するには、AWS_CONTAINER_CREDENTIALS_RELATIVE_URIの値を変更するだけです。

```
AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: "/role/<name of your role>"
```

* 例えば、**ecs_task_role** という名前のロールであれば、環境変数を **/role/ecs_task_role** に設定する必要があります。
* これだけで、ecs-local-endpoints コンテナは、タスクロールを引き受けることで得られた認証情報を利用して、タスク用の IAM ロールに設定された権限が、アプリケーションを実行するのに十分であるかをテストすることができます。

#### タスクメタデータエンドポイントを使用するコンテナのテスト

* タスクメタデータエンドポイントを利用すると、ECS上で動作するコンテナ自身に関する情報を取得できるようにできます。
* つまり、コンテナに関するリソース情報を取れるということですね。
* 下記のように設定します。

```
ECS_CONTAINER_METADATA_URI: "http://169.254.170.2/v3"
```

## ecs-cli

* [ecs-cli](https://github.com/aws/amazon-ecs-cli/tree/mainline#running-tasks-locally) とは、```Docker Compose ファイル形式を使用して、Amazon ECS および AWS Fargate でアプリケーションを実行するためのCLI```と公式ドキュメントでは記載されています。

引用: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html

* では、ecs-cli を使用するとどのようにローカルでのテストができるようになるのかを検証していきます。

#### タスク定義から(ローカルにあるタスク定義)

* ecs-cli を使用するとローカル環境用でテストしたい ECS のタスク定義があった時に、タスク定義からDocker Composeファイルを生成し、検証することができるのです。
* やってみましょう。
* まずは、タスク定義を用意します。ここでも image に wordpress と mysql を使用しています。

```test.json
{
  "containerDefinitions": [
    {
      "name": "wordpress",
      "image": "wordpress",
      "cpu": 10,
      "memory": 500,
      "essential": true,
      "links": [
        "mysql"
      ],
      "portMappings": [
        {
          "containerPort": 80,
          "hostPort": 8080
        }
      ]
    },
    {
      "name": "mysql",
      "image": "mysql",
      "cpu": 10,
      "memory": 500,
      "essential": true,
      "environment": [
        {
          "name": "MYSQL_ROOT_PASSWORD",
          "value": "password"
        }
      ]
    }
  ],
  "family": "test"
}
```

* 次に、下記コマンドで、タスク定義から、docker-composeファイルを生成します。

```
$ ecs-cli local create --task-def-file test.json --output docker-compose.test.yml                                                        1 err  23:19:47 
INFO[0000] Reading task definition from /Users/koitabashi/Desktop/qiita/20221108/test.json
 
INFO[0000] Successfully wrote docker-compose.test.yml   
INFO[0000] Successfully wrote docker-compose.test.override.yml 

```

* 下記が生成された docker-compose ファイル群になります。


```docker-compose.yml
version: "3.4"
services:
  mysql:
    logging:
      driver: json-file
  wordpress:
    logging:
      driver: json-file
```

```docker-compose.test.yml
version: "3.4"
services:
  mysql:
    environment:
      AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: /creds
      ECS_CONTAINER_METADATA_URI: http://169.254.170.2/v3
      MYSQL_ROOT_PASSWORD: password
    image: mysql
    labels:
      ecs-local.task-definition-input.type: local
      ecs-local.task-definition-input.value: /Users/koitabashi/Desktop/qiita/20221108/test.json
    networks:
      ecs-local-network: null
  wordpress:
    environment:
      AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: /creds
      ECS_CONTAINER_METADATA_URI: http://169.254.170.2/v3
    image: wordpress
    labels:
      ecs-local.task-definition-input.type: local
      ecs-local.task-definition-input.value: /Users/koitabashi/Desktop/qiita/20221108/test.json
    links:
    - mysql
    networks:
      ecs-local-network: null
    ports:
    - target: 80
      published: 8080
networks:
  ecs-local-network:
    external: true

```

* 次に、生成された docker-compose.test.yml を使用して、ローカル環境でコンテナを起動します。

```
$ ecs-cli local up --task-def-compose docker-compose.test.yml                                                                               ok  23:27:22 
INFO[0000] Creating network: ecs-local-network...       
INFO[0000] Created network ecs-local-network with ID 6574c1e44a0e065b53f633edd08d489534aa30aa3740eb78c29ae3f24a36e130 
INFO[0000] Created the amazon-ecs-local-container-endpoints container with ID ec6fb6c6638b9504abe1883ba4dc7b0a01807b9ee4faa4c1b0c88c3dda8cc21f 
INFO[0000] Started container with ID ec6fb6c6638b9504abe1883ba4dc7b0a01807b9ee4faa4c1b0c88c3dda8cc21f 
INFO[0000] Using docker-compose.test.yml, docker-compose.test.override.yml files to start containers 
 ⠿ Container 20221108-mysql-1      Started
 ⠿ Container 20221108-wordpress-1  Started
```

* さてプロセスを確認してみましょう。
* いい感じですね。

```
$ docker ps                                                                                                                             ok  4s  23:28:34 
CONTAINER ID   IMAGE                                         COMMAND                  CREATED          STATUS          PORTS                  NAMES
10c90bf94b9a   wordpress                                     "docker-entrypoint.s…"   52 seconds ago   Up 51 seconds   0.0.0.0:8080->80/tcp   20221108-wordpress-1
16d49da1dfc9   mysql                                         "docker-entrypoint.s…"   54 seconds ago   Up 52 seconds   3306/tcp, 33060/tcp    20221108-mysql-1
ec6fb6c6638b   amazon/amazon-ecs-local-container-endpoints   "/local-container-en…"   2 minutes ago    Up 2 minutes    80/tcp                 amazon-ecs-local-container-endpoints
```

**この検証より、ECSのタスク定義だけを利用して、ローカル環境でdocker-composeなどを０ベースで書かずにコンテナを起動することができました。**

#### タスク定義から(AWS上にあるタスク定義)

* 実は上記で検証したタスク定義から Docker-compose を利用してコンテナを起動するやり方にはもう一つ方法があります。
* それは、AWS上に既にあるタスク定義を指定して、ローカル環境で起動することができるというものです。
* やってみましょう。

* 利用するタスク定義はこちらです。
![スクリーンショット 2022-11-07 23.43.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563635/4986b920-be75-74fc-66cd-b3dfe15c05eb.png)

* 次に、ecs-cli を実行する際にタスク定義の ARN が必要になるので、aws cli を利用して取得します。

```
aws ecs describe-task-definition --task-definition test --query "taskDefinition.taskDefinitionArn" --output text
```

* 次に取得した ARN を使用して ecs-cli を実行します。

```
ecs-cli local up --task-def-remote arn:aws:ecs:ap-northeast-1:123456789012:task-definition/test:2

INFO[0000] Reading task definition from test:2
         
INFO[0000] Task Definition network mode is ignored when running containers locally. Tasks will be run in the ecs-local-network.  networkMode=awsvpc
INFO[0000] Successfully wrote docker-compose.ecs-local.yml 
INFO[0000] Successfully wrote docker-compose.ecs-local.override.yml 
INFO[0000] Creating network: ecs-local-network...       
INFO[0000] Created network ecs-local-network with ID xxxxxxxxxxxxxxxxxxxxxxxx 
INFO[0000] Created the amazon-ecs-local-container-endpoints container with ID xxxxxxxxxxxxxxxxxxx 
INFO[0000] Started container with ID xxxxxxxxxxxxxxxxxxxxxxxx 
INFO[0000] Using docker-compose.ecs-local.yml, docker-compose.ecs-local.override.yml files to start containers 
[+] Running 2/2
 ⠿ Container test-wordpress-1  Started
 ⠿ Container test-mysql-1      Started
```

* さてプロセスを確認してみましょう。
* こちらもいい感じですね。

```
docker ps                                                                                                                            ok  23:47:30 
CONTAINER ID   IMAGE                                         COMMAND                  CREATED         STATUS         PORTS                 NAMES
872dd7038a30   wordpress                                     "docker-entrypoint.s…"   2 minutes ago   Up 2 minutes   80/tcp                test-wordpress-1
9294fa575bf9   mysql                                         "docker-entrypoint.s…"   2 minutes ago   Up 2 minutes   3306/tcp, 33060/tcp   test-mysql-1
04544a72d52e   amazon/amazon-ecs-local-container-endpoints   "/local-container-en…"   2 minutes ago   Up 2 minutes   80/tcp                amazon-ecs-local-container-endpoints
```

**検証より、AWS 上にあるタスク定義を利用して、ローカル環境上で簡単にコンテナを起動することができました。**

# まとめ

* まとめる元気も無くなってきたので、めっちゃ便利じゃない？？で締めさせてください。。
* 最後まで読んでいただき、ありがとうございました！！


# 参考文献

https://aws.amazon.com/jp/about-aws/whats-new/2019/07/amazon-ecs-now-offers-improved-capabilities-for-local-testing/

https://aws.amazon.com/jp/blogs/compute/a-guide-to-locally-testing-containers-with-amazon-ecs-local-endpoints-and-docker-compose/

https://github.com/awslabs/amazon-ecs-local-container-endpoints

https://aws.amazon.com/jp/about-aws/whats-new/2019/07/amazon-ecs-now-offers-improved-capabilities-for-local-testing/

https://dev.classmethod.jp/articles/ecs-local/