Amazon ECSを使った簡単なコンテナアプリの構築

## はじめに
仕事でChatBotを使ってDigdagコマンドを叩きたいっていう案件があって、あまりAWS触ったことなかったので、以下のような構成のものを作りました
案件を満たすだけであれば、ECSとかDockerとか必要なくて、Lambdaで出来そうだったんですけど、勉強のために使いました
今回の記事ではDigdag関連は全て省いてます

<img width="553" alt=" 2018-10-04 20.23.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/9cea0f4a-b4c7-25da-53ea-0f33f5547fa7.png">

## HubotのDockerfileを用意
```dockerfile
FROM node
LABEL maintainer="kurosame"

RUN npm install -g yo generator-hubot && \
    useradd bot && \
    mkdir /home/bot && \
    chown bot:bot /home/bot

USER bot
WORKDIR /home/bot
RUN yo hubot --defaults && \
    npm install hubot-chatwork && \
    rm -rf hubot-scripts.json

COPY --chown=bot:bot scripts /home/bot/scripts

CMD bin/hubot -a chatwork
```

## Amazon ECRにプッシュする
Amazon ECRとはDockerのコンテナイメージを保存できるレジストリ
つまり、Docker Hub的なサービス
AWSを使っている場合は、他のAWSサービスとの統合が簡単になるので、Docker HubよりECRを使った方が良さそう

### リポジトリを作成
<img width="406" alt=" 2018-09-06 15.57.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/540cffc7-b90a-5db6-5179-76e8cea4dd5a.png">

リポジトリ名を決めて作成

### Dockerイメージをプッシュ
作成したリポジトリを選択すると、以下のように「プッシュコマンドの表示」というボタンが出てるので、これをクリック
後は表示されたコマンドを順番に実行するとECRにプッシュされる

<img width="447" alt=" 2018-09-06 17.24.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/b1f56531-7491-d3d6-ffff-dcb6ed2a6f53.png">

#### 一応実行したコマンドを書いておきます（aws-cli使う前提です）
以下を実行するとDockerのログインコマンドが出力されるので、コピペして実行して、ログインする

```sh
aws ecr get-login --no-include-email --region ap-northeast-1
```

ログイン後、以下のコマンドでイメージを作成し、プッシュする

```sh
docker build -t [イメージ名] .
docker tag [イメージ名]:latest **********.dkr.ecr.ap-northeast-1.amazonaws.com/[イメージ名]:latest
docker push **********.dkr.ecr.ap-northeast-1.amazonaws.com/[イメージ名]:latest
```

## タスク定義を作成
タスク定義とは、以下から引用すると
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_definitions.html

Amazon ECSでDockerコンテナを実行するには、タスク定義が必要で、以下のような項目を設定する

- タスクのコンテナで使用する Docker イメージ
- 各コンテナで使用する CPU とメモリの量
- 使用する起動タイプ。この起動タイプにより、タスクをホストするインフラストラクチャが決定される
- タスクのコンテナをリンクするかどうか
- タスクのコンテナで使用する Docker ネットワーキングモード
- (オプション) ホストコンテナインスタンスにマッピングするコンテナのポート
- コンテナが終了または失敗した場合にタスクを実行し続けるかどうか
- コンテナの開始時に実行するコマンド
- (オプション) コンテナの開始時に渡す環境変数
- タスクのコンテナで使用するデータボリューム
- (オプション) タスクでアクセス権限の取得に使用する IAM ロール

<img width="572" alt=" 2018-09-21 15.22.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/adbded0a-7b1f-33a3-8960-0b5ebc1af138.png">
この図でいう、Container definitionとTask definitionの部分を作る

---

<img width="946" alt=" 2018-09-07 10.39.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/4b80258a-b6cc-db25-c7a4-5abe7c61572e.png">

新しいタスク定義の作成をクリック

### コンテナの起動タイプの選択
FargateかEC2のどちらにホスティングするか決める

<img width="903" alt=" 2018-09-07 10.53.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/64daef4d-f083-9a9c-8695-01b2f843550d.png">

AWS Fargateは、AWS re:Invent 2017のKeynoteで発表され、2018年7月3日に東京リージョンに対応された新しいサービス
従来はコンテナの実行環境として、EC2を選択してプロビジョニングする必要があったが、Fargateを使うとそれが不要
今までは、EC2とコンテナの管理を考える必要があったが、Fargateを使うとコンテナだけ考えれば良くて、スケールもFargateがやってくれる的な理解をした

せっかくなので、今回はFargateを使ってみます

Fargateを使わずに、従来のようにEC2インスタンス上でコンテナを動かしたい場合は、EC2を選択する

### タスクとコンテナの定義の設定
<img width="654" alt=" 2018-09-07 19.06.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/ea73a3b4-bd44-229d-a0aa-2a72fee37aec.png">

| 設定名 | 設定値 |
|:--|:--|
| タスク定義名 | 適当な名前 |
| タスクロール | なし |

今回はタスクから他のAWSサービスにリクエストはしないので、タスクロールはなし

■ awsvpcネットワークモードについて
今までは、セキュリティグループなどの情報をもつENI（Elastic Network Interface）をEC2（ホスト）に設定してタスクに共有していたが、awsvpcでは直接タスクにENIを割り当てることが可能になる
Fargateの場合、EC2をプロビジョニングしないため、awsvpc固定

#### タスクの実行IAMロール
<img width="646" alt=" 2018-09-07 19.34.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/f43075b8-94e8-a802-b513-49924c989eb7.png">

こちらのロールはタスクを実行するためのロールを設定する
Fargateでは、ログをCloudWatchに公開する必要があるので、ロールの設定は必須とのこと

今回は「新しいロールの作成」を選択し、自動的に作成してもらう
これを選ぶと、タスク定義の作成時に、ecsTaskExecutionRoleというロールが自動的に作られる
IAMで中身を見ると、CloudWatch LogsのWrite権限が付与されていることが分かる

#### タスクサイズ
<img width="655" alt=" 2018-09-10 14.55.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/3297905b-901a-0225-2dfc-0d87d63d1796.png">

こちらはタスクのメモリとCPUを指定する
Fargateでは、必須とのこと

今回はメモリやCPUを使うタスクは無さそうなので、最小構成のメモリとCPUにした

#### コンテナの追加
ここからはコンテナ定義を作成する

コンテナの追加をクリック

<img width="604" alt=" 2018-09-13 12.59.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/e8468029-81ed-bec9-f3b9-913ba57341fc.png">

##### スタンダード
<img width="850" alt=" 2018-10-01 18.25.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/055b270f-67da-b550-1a74-7aec73b4d056.png">

| 設定名 | 設定値 |
|:--|:--|
| コンテナ名 | 適当な名前 |
| イメージ | ECRのリポジトリのURI |
| プライベートレジストリの認証 | なし |
| メモリ制限（MB） | 設定なし |
| ポートマッピング | 9090 |

■ メモリに関して
タスクに設定したメモリより小さい値を設定する必要がある

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/AWS_Fargate.html
ただし、AWSの開発ガイドを見てみると、コンテナのメモリはオプションで、
「ほとんどのユースケースでは、タスクレベルでこれらのリソースを指定するだけで十分です。」
って書いてあるので、今回は設定しなかった

■ ポートに関して
Fargateを使うとawsvpcネットワークモードに固定となる
bridgeネットワークモードが使えないため、ホストと異なるポートマッピングはできない
よって、EC2を選んだ場合と違い、Fargateの場合はコンテナのポートのみを指定すれば良い

##### HEALTHCHECK
<img width="837" alt=" 2018-09-20 13.10.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/c03101af-98ab-9bb0-5771-d18ff4ae129d.png">

タスク内のコンテナのヘルスチェックを定義できる
ヘルスチェックのコマンドの設定やヘルスチェックの実行間隔、リトライなどを定義する

今回は設定なし
（チャットボットを呼んで反応無ければ、死んでるって判断できそうだったので）

##### 環境
<img width="829" alt=" 2018-10-01 20.14.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/3b26d36d-a1b4-a04e-3352-9b678b5f8239.png">

コンテナに渡すコマンドや環境変数などを設定する

このコンテナに割り当てるCPUユニット数はタスクに設定したCPUの値以下にする必要がある
ただし、Fargateでは、省略可能とのこと

ChatWorkアダプターが参照するトークンやルームIDなどを環境変数として、ここで設定する
（DockerfileのENV命令と同様）

##### ネットワーク設定
<img width="816" alt=" 2018-09-20 13.44.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/f3a7d027-4c1e-52c1-710f-cb1373365076.png">

コンテナレベルでのネットワーク設定

今回は設定なし

##### ストレージとログ
<img width="841" alt=" 2018-09-20 13.53.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/33496c5f-1d2c-a42b-e202-0bede39ed05b.png">

ログとか設定
Fargateでは、awslogsのみをサポートしている

今回は設定なし

##### リソースの制限
<img width="817" alt=" 2018-09-20 14.00.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/9cc52de3-bac2-82b3-8758-fd4e91a1ac04.png">

コンテナが利用するリソースを制限する

今回は設定なし

##### DOCKERラベル
<img width="814" alt=" 2018-09-20 14.06.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/f80dfffa-e467-b4a4-35b8-bbc439bda0e9.png">

`docker run`のlabelオプションにマッピングする
環境変数みたいなものだが、ラベルはコンテナ内で実行中のプロセスから参照不可

今回は設定なし

## クラスターの作成
クラスターは、先程作成したタスクや後述で作成しているサービスをグルーピングできる

<img width="560" alt=" 2018-09-21 15.22.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/83fbf199-b084-cf18-f636-58ac9a9f69b1.png">

この図でいう、Clusterの部分を作る

---

<img width="508" alt=" 2018-09-21 17.30.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/82cdc326-f300-8fe6-fef1-6a97cd30cc6d.png">

クラスターの作成をクリック

---

<img width="857" alt=" 2018-09-21 17.31.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/0e07abdd-e2d1-125c-51c3-e0ab37c76169.png">

クラスターもFargateにした

---

<img width="860" alt=" 2018-09-21 17.49.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/6decd1a9-d3aa-ceaa-8a22-33bdf49b34af.png">

クラスター名に適当な名前を設定して、作成

## サービスを作成
サービスでは、以下のような項目を設定する

- 使用するタスク定義
- サービスを実行するクラスター
- サービスに配置するタスク数
- ネットワークの設定（VPCとセキュリティグループ）
- ELB
- ELBのヘルスチェック
- Auto Scaling

<img width="564" alt=" 2018-09-21 15.22.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/489bd83b-33da-bb65-9169-d2bae0ce7660.png">

この図でいう、Serviceの部分を作る

---

<img width="648" alt=" 2018-10-01 13.07.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/74abdfef-6daa-6c1c-d66c-ba281fc16b52.png">

作成をクリック

### サービスの設定
<img width="881" alt=" 2018-10-01 18.17.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/8db9ca4b-ae0a-7128-d7c0-6af9aab4f327.png">

| 設定名 | 設定値 |
|:--|:--|
| 起動タイプ | FARGATE |
| タスク定義 | hubot:1 |
| プラットフォームのバージョン | LATEST |
| クラスター | hubot |
| サービス名 | 適当な名前 |
| サービスタイプ | REPLICA |
| タスクの数 | 1 |
| 最小ヘルス率 | 50 |
| 最大率 | 200 |

■ サービスタイプに関して
・REPLICA
クラスター全体で必要なタスクの実行数を指定する

・DAEMON
ホストの増減に合わせて、タスクの実行数を制御する
Fargateではサポートされていない

■ 最小ヘルス率と最大率に関して
タスク数に対して、最小ヘルス率は最低でもタスク数を維持する値（タスク数2に対して、最小ヘルス率が50%ならばタスク数は最低1つは維持）
最大率は起動するタスク数の最大値（タスク数2に対して、最大率が200%ならばタスク数は最大4つになる）

### ネットワーク構成
#### VPC とセキュリティグループ
この辺は今AWSを使っている環境に合わせて設定してください

#### Elastic Load Balancing（オプション）
今回は設定なし

#### サービスの検出 (オプション)
今回は設定なし

### Auto Scaling (オプション)
<img width="884" alt=" 2018-10-01 19.59.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/e8791cbc-a7bb-7018-35dd-fc5528f1ec40.png">

今回は設定なし

## サービス及びタスクが正常稼働しているか確認
サービス
<img width="756" alt=" 2018-10-02 9.56.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/13dd7064-c8ba-be49-1392-eb405d273c3b.png">

---

タスク
<img width="890" alt=" 2018-10-02 9.56.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/4e93629a-5419-7dcf-fe53-de14bd671137.png">

## Hubotのコード管理
Hubotのコード（CoffeeScript）管理をGitHubなりで管理しないと辛いので、GitHubへのpushをフックして、ECSへデプロイする仕組みを作っておいた方が良い
今回は使い慣れているCircleCIで作成した
使ったことないけど、AWS CodePipelineでも良さそうです

```yaml:.circleci/config.yml
references:
  commands:
    setup-docker: &setup-docker
      ...
      environment:
        AWS_ACCESS_KEY_ID: XXXXXXXXXX
        AWS_SECRET_ACCESS_KEY: XXXXXXXXXX
        AWS_DEFAULT_REGION: ap-northeast-1
        ECR_REGISTRY_NAME: XXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com

jobs:
  ...
  build-hubot:
  <<: *setup-docker
  steps:
    - checkout
    - setup_remote_docker
    - run:
        name: Login ECR
        command: $(aws ecr get-login --no-include-email)
    - run:
        name: Deploy
        command: |
          ROOT_DIR=$(pwd)
          docker build -t hubot ${ROOT_DIR}/docker/hubot/
          docker tag hubot:latest ${ECR_REGISTRY_NAME}/hubot:latest
          docker push ${ECR_REGISTRY_NAME}/hubot:latest
          ecs-deploy -c hubot -n hubot -t 300 -i ${ECR_REGISTRY_NAME}/hubot:latest

workflows:
  ...
  jobs:
    - build-hubot:
      filters:
        branches:
          only:
            - master
```

https://github.com/silinternational/ecs-deploy
このツールがすばらしくて、面倒なサービスやタスクの更新を引き受けてくれる
使うときは、AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY、AWS_DEFAULT_REGIONを環境変数に設定しておく必要がある

## さいごに
Amazon EKSでなく、ECSを使った理由の1つにEKSは現在（2018/10）、東京リージョンが無いっていうのがあったんですが、今回みたいなパフォーマンスをあまり気にしない案件であれば、海外リージョンのEKSで良かったかもしれません
将来的にもKubernetesベースの方が良さそうな気がします
ただ、現状はFargateやCodePipelineのサポートがあり、使いやすいので、ECSでもいいかなと思います