Docker Compose for Amazon ECSを試してみた

# 1.はじめに

2020年11月にリリースされたDocker Compose for Amazon ECS　を試してみました。
Amazon ECSは dockerのコンテナの技術が活かされていると言いながら、今までは自分のやりたいことはやりづらい環境でした。
dockerを初回から触っていたものからすると、1コンテナインスタンスで利用するケースはほとんどなく、大抵は組み合わせます。
複数のサービスコンテナをまとめて稼働させて一つのシステムを作成する際に、Docker Composeは極めて手軽で強力です。
既に多くの docker-compose.ymlを資産として持ち合わせているものにとっては、これが使えないと魅力が半減してしまうのが実情ではないでしょうか。

私には以下の願望がありました。
願望①：Local環境で構築した環境をそのまま、（できれば）何の手も加えずに、AWSに乗せたい。
願望②：Dockerイメージのビルドもできるなら自動化してほしい。

## 1.1.目的

上記の願望が果たして実現できるのか？本当にアナウンス通りなのか？を検証するのが目的になります。
そして落とし穴があるとすれば何か？など。

## 1.2.対象

以下の特徴の人がこの記事の対象者となります。

+ Dockerは使っているけどAmazon ECSとの違いがわからない
+ AWSは使ってるけど、Amazon ECSははじめて
+ Amazon ECSを「サーバーレスで」使ってみたい
+ 環境構築は、AWS CloudFormation でサクッと作成したい
+ できるだけ自動化したい
+ Dockerのイメージのbuildも任せてしまいたい。レジストリに配置するのも面倒

# 2.検証環境

事前に今回検証したマシンの情報を記載します。
今回はWindows 10で作業しましたが、Docker Desktopであれば、Macでも基本的に手順は変わりません。

## 2.1.Local環境

Localはある意味Docker Compose CLIとAWS CLIさえ使えれば問題ありません。

### 2.1.1. Docker

```
$ docker version
Client:
 Cloud integration: 1.0.17
 Version:           20.10.7
 API version:       1.41
 Go version:        go1.16.4
 Git commit:        f0df350
 Built:             Wed Jun  2 12:00:56 2021
 OS/Arch:           windows/amd64
 Context:           myecscontext
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.7
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       b0f5bc3
  Built:            Wed Jun  2 11:54:58 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.6
  GitCommit:        d71fcd7d8303cbf684402823e425e9dd2e99285d
 runc:
  Version:          1.0.0-rc95
  GitCommit:        b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

```

### 2.1.2. Docker Compose CLI

Docker Compose version 1.0.17

Use Docker Compose V2 をチェックしました。
![docker-desktop-compose.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25389/3e6579bb-07cf-0c0c-6080-aecaf2185464.png)


### 2.1.3. docker context

Localの環境にコンテナを建てたい場合は、「default」
Amazon ECS環境にコンテナを建てたい場合は、「myecscontext」を利用します。（※はじめにこのcontextを作成する必要があります）

```
$ docker context ls

Amazon ECS 用の context を設定済み＆有効化ずみです。

NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT                  KUBERNETES ENDPOINT   ORCHESTRATOR
default             moby                Current DOCKER_HOST based configuration   npipe:////./pipe/docker_engine                         swarm
myecscontext *      ecs

```

### 2.1.4. AWS CLI

aws-cli/2.2.13 Python/3.8.8 Windows/10 exe/AMD64 prompt/off

defaultプロファイル設定済み

## 2.2.AWS環境

+ IAMユーザ作成済み

# 3.今回チャレンジしてみる課題の参考リンク
https://aws.amazon.com/jp/blogs/news/automated-software-delivery-using-docker-compose-and-amazon-ecs/

## 3.1.サンプルDemoアプリ
https://github.com/aws-containers/demo-app-for-docker-compose

# 4.トラブルと解決策、改良点

上記のサンプルおよび手順でうまくいかなかった箇所があります。
RedisでvolumeにEFSを使うようなのですが、CloudFoundationから実施されるCodePipelineでDockerComposeのファイルからapplication用のCloudFoundationファイルをconvert生成するタイミングで、ExtractBuildのRoleにアクセス権限がないため落ちてしまう現象がありました。
このようなトラブルは、AWSでDocker使う場合の問題であり、LocalでDocker環境仕様の場合は生じません。

StackOverFlowにて質問させていただき、ブログ著者より回答を得ました。
https://stackoverflow.com/questions/68176406/try-automated-software-delivery-using-docker-compose-and-amazon-ecs-but-fail/68186440#68176406

解決策として、ExtractBuildRoleにAmazonElasticFileSystemFullAccessを追加する事で障害は解決したため、そのソースを forkし以下に上げる予定です。（すみません、まだあげてません＞＜）

https://github.com/hidetarou2013/demo-app-for-docker-compose

-----

# 5.事前準備

+ AWSのアカウントを取得している事
+ 適切な権限を有するIAMユーザが存在する事
+ クライアント環境にDocker Desktop等をインストール
+ クライアント環境にDocker Compose CLIをインストール
+ クライアント環境にAWS CLIをインストール
+ 正しく defaultプロファイル が設定されている事
+ ※　Amazon ECRにログインできている事

※）今回は特別に必要というわけではありません。

## 5.1. Local環境

インストール作業につては割愛

ところどころでAWSリソースのID等を環境変数に設定する必要があります。
LocalのターミナルからAWSのアカウントのcredential情報を元に接続して使うため、
AWS上の構築環境上のリソース情報をLocalでも使用します。メモしておく必要があります。

## 5.2. AWS環境

IAMユーザ作成作業割愛

オーケストレーションなので結構広範囲な事を手掛けていますので、以下を都度メモしておきましょう。

+ VPC_ID
+ ECS_CLUSTER
+ LOADBALANCER_ARN
+ BUCKET_NAME
+ ECR_ID
+ 自分のアカウントにおけるECRのリポジトリのPrefix

そのほかにも、

+ EFS
+ SUBNET_ID 

だったりも意識しておきたいです。
最後に後片付けを行う際に、CloudFormationのスタックを削除する事で、基本的にはそこで作成されたリソースは消えるのですが、
手動でS3 Bucketにアップロードしたりすると、自動では消えてくれません。
S3,ECR,EFSなどは、手動で削除する必要が出てくるかもしれませんので、そこら辺を気に留めておきましょう。



# 6. DockerCompose

## 6.1. docker-compose.ymlの準備

```yaml
version: "3.0"

x-aws-vpc: ${AWS_VPC}
x-aws-cluster: ${AWS_ECS_CLUSTER}
x-aws-loadbalancer: ${AWS_ELB}

services:
  frontend:
    image: ${IMAGE_URI:-frontend}:${IMAGE_TAG:-latest}
    build: ./frontend
    environment:
      REDIS_URL: "backend"
    networks: 
      - demoapp
    ports:
      - 80:80

  backend:
    image: public.ecr.aws/bitnami/redis:6.2
    environment:
      ALLOW_EMPTY_PASSWORD: "yes"
    volumes:
      - redisdata:/data
    networks:
      - demoapp

volumes:
  redisdata:

networks:
  demoapp:

```

オリジナルには、「version: "3.0"」がありませんでしたが、念のため指定します。3.0 以上を指定しておきます。
サービスポートですが、redisのサービスポートは設定不要です。コンテナ間連携として、demoappというネットワークを構築するため不要です。
DBのポートなどもむやみに解放しないように。（frontendはpublic subnetに置き外部から疎通させる必要があります。）

## 6.2. まずはLocalで動かしてみる

docker context は local環境用の default で行います。

```sh
$ docker context use default
$ cd demo-app-for-docker-compose/application
$ docker-compose up -d
```

docker-compose　とハイフンありです。

この時、/frontend/Dockerfile を buildしてイメージ作成、Localに保存まで行っています。
そのイメージは、docker imagesで確認可能です。

正常に稼働確認出来たら、docker-compose down しておいてください。


## 6.3. AWS ECSで動かすには…

もしAmazon ECSで動かしたいのであれば
本来は、そのイメージ（上記のLocalでbuildしたもの）を Amazon ECRのレジストリにUploadする必要があります。
しかし、今回は、その build作業も CodePipeline用の2本目のCloudFormation内のBuildステップで行うため、Uploadの必要はありません。

ちなみに、ECS用のcomposeのコマンドの基本的な手順は、
①まず、docker context を ECS用に切り替える
②そのうえで、「docker compose up」というハイフンなしのコマンドを実行する⇒注意

事になります。
そうした手順を紹介しているサイトは多いですが、ここではあくまでもComposeのコマンド発行は、Amazon Fargate 上で行うため、
CloudFormationにその指示を記載する形になります。

今回のサンプルは、

Docker Compose for Amazon ECS の手順ではなく
Docker Compose for Amazon ECS　+ CloudFormation + CodePipeline　の手順になります。

```sh
$ docker context use myecscontext
```

# 7. 1本目のCloudFormationの実施

インフラストラクチャの下地作りとなります。本家のブログから借用します。
![image-2021-04-06T102543.756.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25389/f8a5f907-c4e4-f479-18c1-709b6eb499cc.png)

```sh
$ cd ../infrastructure
$ aws cloudformation create-stack \
>     --stack-name compose-infrastructure \
>     --template-body file://cloudformation.yaml \
>     --capabilities CAPABILITY_IAM
{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:xxxxxx:stack/compose-infrastructure/ca687190-d922-11eb-8343-064e8d2dcf33"
}

```


## 7.1. 実施後の環境変数設定

ShellScript等を作成しておくと楽ですね。

checkShell01.sh

```sh
$ VPC_ID=$(aws cloudformation describe-stacks --stack-name compose-infrastructure --query "Stacks[0].Outputs[?OutputKey=='VpcId'].OutputValue" --output text)
$ echo $VPC_ID

$ ECS_CLUSTER=$(aws cloudformation describe-stacks --stack-name compose-infrastructure --query "Stacks[0].Outputs[?OutputKey=='ClusterName'].OutputValue" --output text)
$ echo $ECS_CLUSTER

$ LOADBALANCER_ARN=$(aws cloudformation describe-stacks --stack-name compose-infrastructure --query "Stacks[0].Outputs[?OutputKey=='LoadbalancerId'].OutputValue" --output text)
$ echo $LOADBALANCER_ARN
```

※　forkしたソースには、カスタマイズした　「/infrastructure/cloudformation.yaml」では、SubnetもOutputしておきました。
　後々欲しくなってくる情報かもしれないため。

# 8. 2本目のCloudFormationの実施

CodePipeline用のCloudFormationになります。

```sh
$ cd ../pipeline/
$ aws cloudformation create-stack \
>      --stack-name compose-pipeline \
>      --template-body file://cloudformation.yaml \
>      --capabilities CAPABILITY_IAM \
>      --parameters \
>      ParameterKey=ExistingAwsVpc,ParameterValue=$VPC_ID \
>      ParameterKey=ExistingEcsCluster,ParameterValue=$ECS_CLUSTER \
>      ParameterKey=ExistingLoadbalancer,ParameterValue=$LOADBALANCER_ARN
{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:xxxxxxx:stack/compose-pipeline/de5f91a0-d923-11eb-b9ea-0e71fe807c55"
}

```

## 8.1. S3 Bucket for Source の環境変数設定

```
$ BUCKET_NAME=$(aws cloudformation describe-stacks --stack-name compose-pipeline --query "Stacks[0].Outputs[?OutputKey=='S3BucketName'].OutputValue" --output text)

$ echo $BUCKET_NAME
```

## 8.2. Dockerイメージの格納先である ECRの確認

Fargate上でdocker buildされ、作成されたイメージ（今回は /frontend/Dockerfileのもの）が、自分のアカウントのECRにPUSHされます。
ビルドが成功したかどうかは、中身があるかないかで確認できます。

## 8.3. [Local]docker-compose.ymlなどをZip圧縮

S3に置くのはZipファイルです。Zipファイル名は、ブログ記載の通りにしておく必要があります。

## 8.4. ZipファイルをS3 Bucketに転送

Zip圧縮してからS3に配置します。
それを感知して、トリガーとなり、CodePipelineが続行されます。

```sh
$ aws s3 cp compose-bundle.zip s3://$BUCKET_NAME/compose-bundle.zip
```

本家のブログから借用します。
![image-2021-04-06T102401.314.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25389/46caeb72-34fe-4527-8155-b0d8f7b38ea4.png)


## 8.5. CodePipelineの開始

FargateはZipファイルをDownloadして解凍し、次のタスクの準備をします。

## 8.6. Codepipeline:Compose2CloudFormationの実行確認

docker compose convert して、cloudformationのstackファイルを作成します。
compose-application

エラーがない事
リソースが正常であることを確認

# 8.7. 一時停止しているCodePipelineを承認して続行させる

うまくいけば、これで完了です。
見事コンテナが立ち上がっているはずです。

# 9. LBのエンドポイントURIを取得して、ブラウザからアクセス

```sh
$ aws cloudformation describe-stacks --stack-name compose-infrastructure --query "Stacks[0].Outputs[?OutputKey=='LoadbalancerEndpoint'].OutputValue" --output text
```

![image-2021-04-06T102941.818.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25389/4f515a91-501b-b069-db85-eb8894f6f30d.png)

# 10. おわりに

いかがでしたでしょうか。
実際に手で動かしてみると、トラブルがなければ数十分で構築が完了します。
そして、実現できている事の豊富さに驚くばかりです。

今回のサンプルは非常によいサンプルでした。
やりたいことに一歩近づくことができました。

みなさんも、これでため込んでいる docker-compose.yml を使い、ECSにあげることが楽しくなってくると思います。
それでは、本日はこの辺で。