Slackのbotをdockerに入れてEC2 Container Service経由でこんにちは

最近AWSのEC2 Container Service使ったとか、ハマったとかの記事をチラチラと見かけるようになって、気になっていたのですが、昨今のdocker事情の勉強も兼ねて、ちょっと触ってみることにしました。

## この記事でやること
+ Slackbotをdocker上で動かす
  + botのidとかはdocker上には入れずに外部から渡す
  + Mac上（ローカル）でも、AWS上（クラウド）でも動くように設定する
+ 上記dockerをEC2 Container Service（Amazon ECS）を使って動かす
  + ログはCloudWatch上で見れるようにする
+ 上記dockerのイメージはEC2 Container Registryに入れる

## 環境 / 前提
+ MacBookAir + macOS Sierra(10.12.5)
+ AWSのアカウントは持ってる（AWSの操作は、グローバル以外全て東京リージョン(ap-northeast-1))
+ Slackのアカウントは持ってる
+ 各サービスの利用に関しては利用料等が発生する可能性がありますが、記事では特に言及していません
+ 無保証です

## 今回のあらすじ
今回、STEPが多いので、先にやることを並べておきます。

+ 準備編
  1. Slackのbotを設定して、AccessTokenを取得する
  2. docker　for　Macのインストール
  3. AWS上でIAMグループ/ユーザ/ロールの作成
  4. AWS上でSlackのbotのAccess Tokenの格納と暗号化キー設定
  5. AWS上でCloudWatchグループの設定
  6. AWS CLIのインストール/設定
+ Dockerイメージ作成編
  1. slackbotが入ったdockerイメージを作る
  2. dockerを動かして、Slackbotが動作するか試してみる
+ EC2 Container Service動作編
  1. slackbotが入ったdockerイメージをEC2 Container Registryに登録する
  2. EC2 Container Service（Amazon ECS）の新しいタスク定義を作成する
  3. EC2 Container Service（Amazon ECS）のクラスターを作成する
  4. 3のクラスターができたら、新しいタスクを実行する
  5. ４のタスクが動いたら、Slackbotが動作していることを確認する

## 先に名前をつける
今回、多数の名前をつけるところがあるのですが、同じ名前にするとだんだんわからなくなってくるし、違う名前にするとどれがどれかわからなくなるので、先に整理しておきます。

| No  | 定義名      |       名前 |    備考    |
|:----|:-----------------|:------------------|:------------------|
|1|SlackBot AppName | docker_bot| SlackbotのAppName|
|2|IAMグループ名 |iam_slack_group | Mac上のAWS CLIで使うIAMグループ権限|
|3|IAMユーザID  |iam_slack | Mac上のAWS CLIで使うIAMユーザ|
|4|IAMロールID|iam_slack_taskrole|Amazon EC2 Container Service Task Roleとして設定するIAMロール|
|5|Amazon ECSタスク名|task_docker_bot|Amazon ECSでタスクを実行する際のタスク名|
|6|IAM暗号化キーエイリアス|slack_seckey|Slack Bot IDをパラメータストアに登録する際の暗号化キー|
|7|EC2パラメータストアSlack Bot ID Name|slack.API_TOKEN|SlackbotのAccessTokenをAWS上に設定する|
|8|EC2 Container Registry リポジトリ名|slack_image|slack用のdockerイメージを格納するリポジトリ|
|9|Amazon ECSコンテナ名|container_slack|Amazon ECSでタスクを実行する際に指定するコンテナ名|
|10|CloudWatchのロググループ名|slack_log|Amazon ECSでタスクを実行した時にCloudWatchに出力されるロググループ|
|11|Amazon ECSクラスター名|cluster-slack|Amazon ECSでクラスターを構成する際に必要なクラスター名(ハイフンが使えない)|

## 準備編

### 1. Slackのbotを設定して、AccessTokenを取得する

SlackのbotのAccessTokenとは、外部サーバからSlackに接続する際に使用する「Bot User OAuth Access Token」のことで、これを取得する必要があります。

Access Tokenの取得は最初出て来ないので慣れないとわかりづらいです。ピンポイントでbotのAccess Tokenについて解説している記事が見当たらなかったので、本記事で画面キャプチャ付きで解説します。


![スクリーンショット 2017-07-23 20.19.23.png](https://qiita-image-store.s3.amazonaws.com/0/32778/549e0245-61ea-c96a-67ae-5ba1e8126243.png)

今回用に"bottest"というteamを作ってみました。WEBのslackの最初の画面で、左上の「bottest」と表示されている部分をクリックするとメニューが出てきます。このメニューの「Apps & integrations」をクリックしてください。

![スクリーンショット 2017-07-23 20.19.38.png](https://qiita-image-store.s3.amazonaws.com/0/32778/cff3c7c1-86a7-88a3-fa0e-790b160cc525.png)

別ページに飛びます。ここで右上の「bottest」のすぐ左の「Build」をクリックしてください。

![スクリーンショット 2017-07-23 20.19.46.png](https://qiita-image-store.s3.amazonaws.com/0/32778/88c9c7d3-df8e-68ad-6f60-4a74d997e5ae.png)

SlackAPIのページに飛びました。ここの右上の「Your Apps」をクリックしてください。

![スクリーンショット 2017-07-23 20.20.34.png](https://qiita-image-store.s3.amazonaws.com/0/32778/49134dca-6018-27f2-983d-d99ba554dab5.png)

何も登録がないと、ページ真ん中に「Create an App」というボタンが表示されます。何かすでにAppを登録済みの場合は、登録済みApp名と、「Create an App」ボタンが表示されていますので、「Create an App」ボタンをクリックしてください。

![スクリーンショット 2017-07-23 20.21.20.png](https://qiita-image-store.s3.amazonaws.com/0/32778/557db1a4-30f6-24c2-ef49-15ac92ee08e9.png)

さっそく「App Name」と、どのSlackTeamで使うのかを聞いてきます。今回は「AppName」は"docker_bot"とします。「Development Slack Team」は選択式ですので、"bottest"を選びます。そのあと、「Create App」をクリックしてください。

![スクリーンショット 2017-07-23 20.22.17.png](https://qiita-image-store.s3.amazonaws.com/0/32778/fe5386f5-085d-0e5b-d874-57cc5b37ee09.png)

ここから一気に選択肢が増えて、よくわからなくなるので、落ち着いて一つ一つ処理していきましょう。「Add features and functionality」というメニューをクリックすると、画面のような６つの選択肢が出てきます。この中の「Bots」という部分がクリックできるようになっているので、クリックします。

![スクリーンショット 2017-07-23 20.23.12.png](https://qiita-image-store.s3.amazonaws.com/0/32778/87a010f5-5f73-be8d-5713-e85c9d61fb13.png)

ここで、デフォルトのBotの名前と常時オンライン表示するかという選択肢がありますが、ここは両方デフォルトのままで、「Add Bot User」をクリックしてください。必要であれば後で変えられます。

![スクリーンショット 2017-07-23 20.24.25.png](https://qiita-image-store.s3.amazonaws.com/0/32778/41c18e05-1555-2abd-cfd7-0944e449ba3d.png)

次に、前の６つの選択肢の一番右下の「Permission」に進みたいのですが、戻るのが面倒なので、左のメニューから、「OAuth & Permissions」をクリックします。ずずーっと下にスクロールすると、「Select Permission Scopes」という選択ボックスが出てきます。

![スクリーンショット 2017-07-23 20.25.06.png](https://qiita-image-store.s3.amazonaws.com/0/32778/a27aa246-f707-ea9d-5f9e-3c6c944fdcf2.png)

ここで、「Send messages as docker_bot」を選んでください。

![スクリーンショット 2017-07-23 20.25.23.png](https://qiita-image-store.s3.amazonaws.com/0/32778/59c828bf-c103-5bf3-872b-75b33a7174ef.png)

その後、「Save Changes」をクリックすると、設定が保存されます。

左のメニューの一番上、「Basic Information」をクリックすると、「Create App」をクリックした直後の画面に戻ってきます。

![スクリーンショット 2017-07-23 20.25.35.png](https://qiita-image-store.s3.amazonaws.com/0/32778/066f3231-a686-0647-86a4-9425383df192.png)

さて、「Add features and functionality」のところに大きなチェックマークが付いていますね。これが処理完了したことのようです。

さて、ここで、もう一度左のメニューから、「OAuth & Permissions」をクリックします。

![スクリーンショット 2017-07-23 20.24.25.png](https://qiita-image-store.s3.amazonaws.com/0/32778/41c18e05-1555-2abd-cfd7-0944e449ba3d.png)

次に、「Install App to Team」をクリックします。

![スクリーンショット 2017-07-23 20.26.01.png](https://qiita-image-store.s3.amazonaws.com/0/32778/91024bd6-a871-8fc4-3928-b0e1b1b64833.png)

いきなり画面のテイストが変わってびっくりするのですが、ここで右下の「Authorize」をクリックすると、ようやくAccessTokenが発行されます。

されるのですが、パッと出て来ずに、いままで操作していた画面に追加で表示される形となっています。

![スクリーンショット 2017-07-23 20.27.42.png](https://qiita-image-store.s3.amazonaws.com/0/32778/3a3d3105-7eca-786f-251c-0225a7999a54.png)

左のメニューから、「Install App」をクリックすると、「OAuth Tokens for Your Team」という項目が追加されていて、そこにAccess Tokenが表示されています。長かったですが、これが欲しかったAccess Tokenになります。

ちなみに必要なのは、下の「Bot User OAuth Access Token」の方になります。このAccess Tokenのみでアクセスできてしまうので、厳重に管理しましょう。

なお、botとチャンネル内で会話をするためには、チャンネルにbotが参加している必要があります。

![スクリーンショット 2017-07-23 23.10.49.png](https://qiita-image-store.s3.amazonaws.com/0/32778/9a77df13-d19a-8888-10d2-e42bb707d4a0.png)

```:slackメッセージ
/invite @dockerbot
```
として呼ぶのが正しいようです。（上の画面では、「join @dockerbot」としたのですが、この場合でもシステムがなんとなくフォローしてくれました。）

### 2. docker　for　Macのインストール
準備編の１で力を使い果たしてしまったので、後は簡単に済ませましょう。

今回はMacを使っているので、docker for Macをインストールします。バイナリのダウンロードは、[Install Docker for Mac](https://docs.docker.com/docker-for-mac/install/#download-docker-for-mac)からお願いいたします。

選択肢が「Stable channel」、「Edge channel」の２つあるのですが、「Stable channel」からダウンロードして、インストールしましたが問題ありませんでした。

本記事でインストールしたdocker for Macのversionは、`Docker version 17.06.0-ce, build 02c1d87`になります。

### 3. AWS上でIAMグループ/ユーザ/ロールの作成
AWSコンソールから、IAMを選択して、あらかじめグループ、ユーザとロールのポリシーを設定しておきます。

● IAMグループ/ユーザーの作成
ここでのIAMグループ/ユーザーの作成は、MacからEC2 Container Registryや、EC2 Container Serviceや、パラメータストアとそれに紐づく鍵情報にアクセスするためです。

![スクリーンショット 2017-07-23 21.23.29.png](https://qiita-image-store.s3.amazonaws.com/0/32778/416f0ca4-4e72-c1fe-e45e-9daa52659057.png)

グループ名は「iam_slack_group」、ポリシーは、「AmazonEC2ContainerRegistryFullAccess」、「AmazonEC2ContainerServiceFullAccess」、
「AWSKeyManagementServicePowerUser」、「AmazonSSMReadOnlyAccess」の4つを指定します。

![スクリーンショット 2017-07-23 21.24.02.png](https://qiita-image-store.s3.amazonaws.com/0/32778/ba74aa4a-0541-9108-84ae-21b96bbdfff1.png)

上記グループにユーザを紐づけてください。ユーザー名は「iam_slack」、アクセスの種類は「プログラムによるアクセス」にチェックを入れてください。ユーザー作成が完了した際に取得できる、アクセスキーIDとシークレットアクセスキーは後で使うので保存しておいてください。

● IAMロールの作成
ここでのIAMロールの作成は、後でEC2 Container Serviceのタスクを実行する時に、Dockerで動くプログラムのポリシーとして付与されるものです。パラメータストアとそれに紐づく鍵情報にアクセスしたいので、それらのポリシーを付与しておきます。

![スクリーンショット 2017-07-23 21.26.34.png](https://qiita-image-store.s3.amazonaws.com/0/32778/7b4488d5-6f88-f1b1-6c08-8cfdca6d3ded.png)

AWSのサービスロールがいっぱいあるのですが、その中の「Amazon EC2 Container Service Task Role」を選択してください。

![スクリーンショット 2017-07-23 21.27.14.png](https://qiita-image-store.s3.amazonaws.com/0/32778/478a0caf-f1a9-af3a-91d6-1a1dbe0b5d6f.png)

ロール名は「iam_slack_taskrole」、ポリシーは、「AWSKeyManagementServicePowerUser」と、「AmazonSSMReadOnlyAccess」の２つを指定します。

### 4. AWS上でSlackのbotのAccessTokenの格納と暗号化キー設定
最初に頑張って取得したSlackBotのAccessTokenを、プログラムとかに埋め込むのは嫌なので、AWS側に登録しておきます。また、そのままの値を登録するのではなくて、暗号化キーによって暗号化された状態にしておくのがよさそうです。

● 暗号化キー設定
IAMの画面の左メニュー下に「暗号化キー」というメニューがあるので、クリックします。

![スクリーンショット 2017-07-23 21.55.09.png](https://qiita-image-store.s3.amazonaws.com/0/32778/7d39daba-51a8-f794-20ab-2b7e4168cbda.png)

ここの「キーの作成」をクリックすると、暗号化キーが設定できるのですが、その前にリージョンを確認してください。暗号化キーはリージョン毎に設定されるので、別のリージョンで設定していると、設定したはずなのに出て来ないということが起こり得るので注意です。

![スクリーンショット 2017-07-23 21.59.01.png](https://qiita-image-store.s3.amazonaws.com/0/32778/c254a472-ca4e-05f7-b03a-8387a62dfde4.png)

エイリアスを「slack_seckey」として、それ以外は空白もしくはデフォルトのまま次へ進めます。タグ名とキー管理アクセス許可のページはそのまま未入力で次へ進んでください。

![スクリーンショット 2017-07-23 22.00.11.png](https://qiita-image-store.s3.amazonaws.com/0/32778/84c0bc77-302d-7065-0ef8-9e1bfaeaae6f.png)

キー使用アクセス許可の定義は、さきほど設定したIAMユーザーと、ロールが選択肢に出てきますので、それぞれクリックして次のステップに進んでください。最後にポリシーがずらっと出てきますが、特に何もせず進んで問題ありません。

● パラメータストア設定
EC2サービスの左メニューの下部に、「パラメータストア」というメニューがあります。

![スクリーンショット 2017-07-23 22.51.35.png](https://qiita-image-store.s3.amazonaws.com/0/32778/7886ab57-c992-55d2-91df-7a471d0dbd9d.png)

今すぐ始めると、パラメータの設定画面になります。

![スクリーンショット 2017-07-23 23.02.32.png](https://qiita-image-store.s3.amazonaws.com/0/32778/150873cf-7eb2-1583-8c2b-5ed69404bbf8.png)

Nameに「slack.API_TOKEN」、Typeを「Secure String」にすると、設定済みの暗号化キーを選択できます。ここでは「alias/slack_seckey」を選択してください。最後の「Value」は、SlackBotのAccessTokenを入力します。暗号化されているのでマスクされた文字になります。「Create Parameter」をクリックすると登録されます。


### 5. AWS上でCloudWatchグループの設定
後でDockerからの出力をCloudWatchログに表示するようにするために、ロググループを作成します。

![スクリーンショット 2017-07-23 22.08.57.png](https://qiita-image-store.s3.amazonaws.com/0/32778/74968578-12ac-f673-48e1-9e7db9732027.png)

CloudWatchのサービス画面で、左のメニューから「ログ」をクリックしてください。「ロググループの作成」ボタンをクリックして、「slack_log」グループを作成してください。

### 6. AWS CLIのインストール/設定
MacにAWS CLIをインストールしてください。Macのターミナルからいくつかコマンドを実行するためです。

+ [AWS CLIのインストール](http://qiita.com/yuyj109/items/3163a84480da4c8f402c)

インストールが終わったら、`aws configure`を実行しますが、その際のアクセスキーIDとシークレットアクセスキーは、先ほど設定した「iam_slack」ユーザーのものを使用してください。

本記事のAWS CLIのversionは、`aws-cli/1.11.123 Python/2.7.10 Darwin/16.6.0 botocore/1.5.86`です。

## Dockerイメージ作成編

### 1. slackbotが入ったdockerイメージを作る

SlackBot用プログラムを作り、それをDockerに組み込んで、動作するようにします。

+ [PythonのslackbotライブラリでSlackボットを作る](http://qiita.com/sukesuke/items/1ac92251def87357fdf6)

また、dockerの肥大化を防ぐため、構成は、Alpine Linux+Python3とします。

+ [frolvlad/alpine-python3](https://hub.docker.com/r/frolvlad/alpine-python3/)
+ [Alpine Linux で Docker イメージを劇的に小さくする](http://qiita.com/asakaguchi/items/484ba262965ef3823f61)
+ [Alpine Linux で軽量な Docker イメージを作る](http://qiita.com/pottava/items/970d7b5cda565b995fe7)
+ [お前のDockerイメージはまだ重い💢💢💢](https://speakerdeck.com/stormcat24/oqian-falsedockerimezihamadazhong-i)


さらに、AWS上で確認する前にローカルで動作確認することを考えて、SlackのAccessTokenについては環境変数から取得するようにして、AWSからパラメータ取得する際に、環境変数をいい感じに扱ってくれる、[env-injector](https://github.com/okzk/env-injector)を導入しています。

+ [EC2 Systems Manager のパラメータストアを利用したアプリケーション環境設定の管理](http://dev.classmethod.jp/cloud/aws/parameter-store-application-config/)
+ [AWS上で動かすアプリケーションとクレデンシャル情報](http://okzk.hatenablog.com/entry/2017/06/01/201115)

Mac側の環境がどうであれ、dockerのコンテナ内に環境構築して動作確認できればよいので、環境差異を気にしなくていいというのは楽ですね。

DockerfileおよびPythonのプログラムはこちらになります。
Dockerfile内にFROMが２回出て来ますが、参考記事を参照してください。

+ [Docker multi stage buildで変わるDockerfileの常識](http://qiita.com/minamijoyo/items/711704e85b45ff5d6405)
+ [Dockerfile reference #FROM](https://docs.docker.com/engine/reference/builder/#from)

```:Dockerfile
FROM golang AS build-env
RUN CGO_ENABLED=0 go get github.com/okzk/env-injector

FROM alpine:3.6
RUN apk --no-cache update && \
    apk add --no-cache python3 && \
    apk add --no-cache ca-certificates && \
    python3 -m ensurepip && \
    rm -r /usr/lib/python*/ensurepip && \
    pip3 install --upgrade pip setuptools && \
    if [ ! -e /usr/bin/pip ]; then ln -s pip3 /usr/bin/pip ; fi && \
    rm -r /root/.cache && \
    rm -rf /var/cache/apk/* && \
    pip3 install slackbot && \
    mkdir -p /opt/slackbot/plugins
# for DEBUG
# RUN apk --no-cache add python curl groff less && \
#     pip --no-cache-dir install awscli && \
#     rm -rf /var/cache/apk/*
COPY --from=build-env /go/bin/env-injector /usr/local/bin/
ENTRYPOINT ["env-injector"]
COPY ./run.py /opt/slackbot/
COPY ./slackbot_settings.py /opt/slackbot/
COPY ./plugins/* /opt/slackbot/plugins/
ENV API_TOKEN=

CMD ["python3","/opt/slackbot/run.py"]
```

```run.py
# coding: utf-8

from slacker import Slacker
import slackbot_settings
from slackbot.bot import Bot
import logging
import os

def main():
    bot = Bot()
    bot.run()

if __name__ == "__main__":
    loglevel = os.getenv("LOG_LEVEL", "WARNING")
    num_level = getattr(logging, loglevel.upper(),None)
    if not isinstance(num_level, int):
        raise ValueError('Invalid log level: %s' % loglevel)
    logging.basicConfig(level=num_level,
                        format='%(asctime)s- %(name)s - %(levelname)s - %(message)s')
    slack = Slacker(slackbot_settings.API_TOKEN)
    slack.chat.post_message(
        'general',
        'こんにちわー',
        as_user=True
        )
    logger = logging.getLogger(__name__)
    logger.info('start slackbot')
    main()
```

```slackbot_settings.py
# coding: utf-8
import os

# botアカウントのトークンを指定
API_TOKEN = os.getenv("API_TOKEN", "")

# このbot宛のメッセージで、どの応答にも当てはまらない場合の応答文字列
DEFAULT_REPLY = "聞こえてますよー"

# プラグインスクリプトを置いてあるサブディレクトリ名のリスト
PLUGINS = ['plugins']
```

[github](https://github.com/daxanya1/docker_slackbot)に一式用意しましたので、以下の流れで一気にdockerイメージ作成までいけます。

```:コマンド入力
$ git clone https://github.com/daxanya1/docker_slackbot.git
$ cd docker_slackbot
$ docker build ./ -t alpine_python36
```
これでdockerイメージが作成されたか確認してみましょう。

```:コマンド入力
$ docker images |grep alpine
```

```:コマンド出力
alpine_python36 latest   (省略)   71.8MB
alpine 　　　　　　　　　　　　　　　　　　　3.6      (省略)   3.97MB 
```
正常に出力されていたら、dockerイメージは作成されています。
ベースのAlpineからはだいぶ大きくなりましたが、それでも71.8MBという小さいdockerイメージができています。（AmazonLinuxベースで、PythonとGolangを普通に入れると1GByte超えるdockerイメージになることがあります）


### 2. dockerを動かして、Slackbotが動作するか試してみる

やりたいことは３点です。

1. さきほど作成したdockerコンテナを、ローカルのMac上で起動させる
2. 環境変数API_TOKENにslackbotのAccessTokenを入れる
3. 環境変数LOG_LEVELにDEBUGを設定する（詳細なログを出力する)

このうち、２のAccessTokenを直接記述するのは差し障りがあるというのと、せっかくAWS上に登録しているのだから、そこから取得すればよいということで、AWS CLIコマンドを叩いて、その結果をAPI_TOKENに突っ込んでいます。

```:コマンド入力
$ docker run -it \
-e API_TOKEN=$(aws --region ${AWS_REGION} \
ssm get-parameters --name slack.API_TOKEN \
--with-decryption \
--query "Parameters[0].Value" \
--output text) \
-e LOG_LEVEL=DEBUG \
alpine_python36:latest
```

起動後に、自動的に、#generalチャンネルに、dockerbotから「こんにちわー」と挨拶があれば、正常に動作しています。

![スクリーンショット 2017-07-23 23.59.22.png](https://qiita-image-store.s3.amazonaws.com/0/32778/a1ac67b7-19c9-d5ee-a918-9174a6760921.png)

起動したdockerの終了のさせ方は、ctrl+cです。

## EC2 Container Service動作編
### 1. slackbotが入ったdockerイメージをEC2 Container Registryに登録する

さて、あと一息です。ここからは、AWSコンソールの、EC2 Container Service（以下ECS)内作業と、一部AWS CLIを使った作業になります。

最初の画面でいきなり選択を迫られますが、あまり気にせずに上のチェックを外して、下を残して次へ進んでください。

![スクリーンショット 2017-07-24 0.05.53.png](https://qiita-image-store.s3.amazonaws.com/0/32778/13d87686-4696-d658-fb83-2c71fe63fcf2.png)

ここでリポジトリを設定します。リポジトリ名を「slack_image」としてください。

![スクリーンショット 2017-07-24 0.07.00.png](https://qiita-image-store.s3.amazonaws.com/0/32778/da9a5d63-48d7-8ac6-f4b1-2d32d8eba1a8.png)

次の画面で、pushの仕方を教えてくれます。画面からはpushできず、AWS CLIを使ってpushしてねと書いてありますので、AWS CLIコマンドを実行するところから始めましょう。


```:コマンド入力
$ aws ecr get-login --no-include-email --region ap-northeast-1
$ (上記aws ecr get-loginの出力結果の超長い部分をコピペ「docker login -u AWS -p 〜 」)
$ docker tag alpine_python36:latest xxxxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/slack_image:latest
$ docker push xxxxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/slack_image:latest
```
※ xxxxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.comは実際の画面に表示されている、リポジトリドメイン名に置き換えてください。

`docker push`でさきほど作ったdockerイメージがAWSのEC2 Container Registryに格納されます。１個しか指定していないはずなのに、複数個Pushされているように見えますが、dockerイメージ作成の際に、ベースとなったAlpineLinuxのイメージや中間で作成されたイメージと共にpushされるからで、とくに問題ありません。

さて、イメージが無事pushされたか、AWSコンソール画面で確認しましょう。


![スクリーンショット 2017-07-27 20.11.45.png](https://qiita-image-store.s3.amazonaws.com/0/32778/34d260e6-e0b9-5452-f4d1-01cdd4625654.png)

「latest」というImageTagが追加されていますね。22.57MBと表示されていますが、Docker クライアントは V2 Docker レジストリにプッシュする前にイメージレイヤーを圧縮するので、ローカルで表示されているサイズ（非圧縮）に比べて、サイズが小さくなるようです。

+ [リポジトリ情報の表示](http://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/repository-info.html)

さて、毎回latestを上書き更新するというのも変なので、同じイメージでバージョン番号もつけておきたいです。もう一度同じイメージをpushし直す必要はなくて、AWS CLIを使ってpush済みのイメージに別のタグを振ることができます。

+ [AWS CLI を使用してイメージにもう一度タグを付ける]
(http://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/retag-aws-cli.html)

```:コマンド入力
$ aws ecr batch-get-image --repository-name slack_image --image-ids imageTag=latest --query images[].imageManifest --output text > amazonlinux.manifest
$ aws ecr put-image --repository-name slack_image --image-tag v1.0 --image-manifest file://amazonlinux.manifest
```
２番目のコマンドを実行すると、出力結果がどばっと出ますが、特に今回は使いません。

画面を見ると、v1.0が追加されているのがわかりますね。容量は同じですがダイジェストが違うので、同一イメージではなくて別のイメージとして扱われているようです。

![スクリーンショット 2017-07-27 20.12.17.png](https://qiita-image-store.s3.amazonaws.com/0/32778/c1ef3e44-546e-1f2d-ad43-97692a396da7.png)


### 2. EC2 Container Service（Amazon ECS）の新しいタスク定義を作成する

ECSの２番目のメニューとなっている、タスク定義をクリックして、新しいタスク定義を作りましょう。

このあたりから用語がよくわからなくなってくるのですが、一度動かしてみると理解が早いと思うので、特に定義を語ったりせずに先に進みます。

● タスク定義

タスク定義名を「task_docker_bot」、タスクロールを以前に作成したIAMロール「iam_slack_taskrole」を選択します。ネットワークモードは「bridge」のままで良いです。

![スクリーンショット 2017-07-24 5.55.26.png](https://qiita-image-store.s3.amazonaws.com/0/32778/41ee87e3-7f9c-6d93-6193-072a9d3ab046.png)

画面を少し下にスクロールすると、「コンテナの追加」ボタンがあるのでクリックします。

![スクリーンショット 2017-07-24 5.55.39.png](https://qiita-image-store.s3.amazonaws.com/0/32778/be130d31-9d11-d957-b2bc-88a2482d5478.png)


● コンテナの追加

コンテナ名を「container_slack」、イメージを、EC2 Container Registryにpushした、「xxxxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/slack_image:latest」と入力します。

※画面ではlatestの部分を入力し忘れていますが、コンテナ名は[Docker デーモンに直接渡される名前](http://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_definition_parameters.html#container_definitions)で、タグ名は[Docker Engine はデフォルトで :latest タグを使う](http://docs.docker.jp/engine/reference/commandline/pull.html)ため、指定がなくてもlatestが使用されるため問題ありませんでした。

![スクリーンショット 2017-07-24 5.59.31.png](https://qiita-image-store.s3.amazonaws.com/0/32778/2439d4f6-4153-0bcb-9fc1-67f75766c62b.png)



また、メモリの制限を指定します。ここでは「ハード制限」として「300」を指定していますが、今回１つのタスクしか起動しないので、厳密には意味がない数字になります（指定が必須なので適当に指定しています）。

下にスクロールしていくと、「環境変数」という記載があるので、キーを「ENV_INJECTOR_PREFIX」、値を「slack」とします。この設定で、dockerのENTRYPOINTに登録した「env-injector」が、パラメータストアの設定を取得しに行きます。

![スクリーンショット 2017-07-24 6.01.21.png](https://qiita-image-store.s3.amazonaws.com/0/32778/93a8833d-2396-67ff-28dd-c783c007bb4a.png)




コンテナ設定の最後に、ログドライバーの設定を行います。これでCloudWatchからログが出力されるようになります。「awslogs-group」をさきほど設定した、「slack_log」に、「awslogs-region」を「ap-northeast-1」と入力します。

![スクリーンショット 2017-07-24 6.02.04.png](https://qiita-image-store.s3.amazonaws.com/0/32778/66c40511-831a-3816-c5e1-b82b0bca50af.png)




コンテナの追加ページはこれで入力終了です。画面右下の「追加」をクリックして戻りまして、タスク定義のページ画面右下の「作成」をクリックするとタスク定義が作成されます。

![スクリーンショット 2017-07-24 6.03.01.png](https://qiita-image-store.s3.amazonaws.com/0/32778/695b3c65-7616-65c5-c63a-6e7a022e25eb.png)



### 3. EC2 Container Service（Amazon ECS）のクラスターを作成する

さて、最後の大物、ECSのクラスターを作成します。一度ボタンを押すと、勝手に実行中！完了！実行中！完了！と、自動で動き出すところに大物感を感じます。

ここではクラスター名を「cluster-slack」として、空のクラスター作成にはチェックをつけないで、プロビジョニングモデルは「オンデマンドインスタンス」を選択してください。

![スクリーンショット 2017-07-24 6.04.46.png](https://qiita-image-store.s3.amazonaws.com/0/32778/9aa154b2-a705-2b5b-e3b2-9156ecfaa757.png)

次に、大事なところですが、テストで使うだけであれば、「EC2インスタンスタイプ」は「t2.micro」で十分です。費用に影響するので一番小さいのにして置きましょう。

![スクリーンショット 2017-07-24 6.05.01.png](https://qiita-image-store.s3.amazonaws.com/0/32778/531b2e53-a4cc-11a6-ff4c-f32c30eb712e.png)


後は特に触らずに、画面下の「作成」をクリックすると、自動的に裏でごそごそ動き始めて、ステータスが変わるのがわかります。

![スクリーンショット 2017-07-24 6.05.53.png](https://qiita-image-store.s3.amazonaws.com/0/32778/2e66ca95-5468-4896-2d06-93230cb234a2.png)



全部緑になったら完了です。私が何度か実行した感じですと、毎回3〜５分程度で終わりました。

![スクリーンショット 2017-07-24 6.08.31.png](https://qiita-image-store.s3.amazonaws.com/0/32778/faf571d5-6b12-7842-f57e-397adb4e7e6a.png)



### 4. 3のクラスターができたら、新しいタスクを実行する

ようやくここまで来ました！　もう一息です。さきほど作ったクラスターに、その前に作ったタスクを紐づけて、dockerイメージを動かします。

設定されたクラスターを開くと、最初にサービスタブの画面が出るのですが、サービスは今回使いませんので無視します。

![スクリーンショット 2017-07-24 6.09.26.png](https://qiita-image-store.s3.amazonaws.com/0/32778/2e2349d3-c772-25a5-a091-6a5fc776c604.png)

タブを「タスク」に切り替えて「新しいタスクの実行」をクリックします。


![スクリーンショット 2017-07-24 6.09.36.png](https://qiita-image-store.s3.amazonaws.com/0/32778/1719d147-7ee2-abf2-f9fd-1a64b528dfd0.png)

タスクの実行画面が表示されて、どのタスクをどのクラスターで実行するかを定義できます。ここでは１個ずつしか定義していないので、そのまま選択された状態になっています。

![スクリーンショット 2017-07-24 6.13.39.png](https://qiita-image-store.s3.amazonaws.com/0/32778/a8f3dbfb-f434-4251-8fc5-5849aaac7162.png)

ここで、ログをCloudWatchに出したいので、ログレベルをINFOに設定したいとします。（今回docker上のプログラムはデフォルトがWARNINGで、WARNINGにすると正常時は出力するログがありません)

その場合は、タスク定義のコンテナの設定で指定した環境変数以外に、この画面で更新、追加が可能です。ここでは、環境変数のキー「LOG_LEVEL」、値「INFO」を追加します。

![スクリーンショット 2017-07-24 6.14.56.png](https://qiita-image-store.s3.amazonaws.com/0/32778/8d13c0c1-c9a7-add8-0ddb-8b4a54609cae.png)

最後に画面下の「実行」をクリックすると、タスクを実行します。

### 5. ４のタスクが動いたら、Slackbotが動作していることを確認する

タスクを実行すると、画面上でPENDINGとなり、RUNNING待ち状態となります。

![スクリーンショット 2017-07-24 6.10.07.png](https://qiita-image-store.s3.amazonaws.com/0/32778/39eccdcd-9c98-521a-a463-93c43e837c60.png)

タスクの部分がリンクになっているので、タスクの文字列をクリックすると、詳細画面に飛びます。RUNNINGになるまで待ちましょう。

![スクリーンショット 2017-07-24 6.11.43.png](https://qiita-image-store.s3.amazonaws.com/0/32778/2f59ed25-7fd5-73e5-8c58-cf5c9a3c80a1.png)

RUNNINGになると、slack上でもbotから挨拶があるので、起動していることがわかります。

![スクリーンショット 2017-07-24 6.11.19.png](https://qiita-image-store.s3.amazonaws.com/0/32778/2791b7c4-ee96-e387-bcf8-4c74ee6fbbaa.png)

CloudWatchログのslack_logにもログが出力されていることが確認できます。


![スクリーンショット 2017-07-24 6.15.51.png](https://qiita-image-store.s3.amazonaws.com/0/32778/8cc6c210-a8c0-ad80-db2b-be5b85f12ebd.png)


今回のdocker内のプログラムは、Slackのbotなのですが、Slack側で何かお話すると止まるとか高度な機能は入っていないので、ECSのクラスターのタスクの画面から直接停止させてください。

## 試行錯誤

何度か動かない〜ということがあり、何が悪いのかわからず、いろいろ調査していたのですが、面倒なことをする前にここを見ておけ、というコツがわかりましたので、共有しておきます。

### タスクでdockerイメージの指定が間違っている

URLを省略しすぎたり、文字列が間違っていると、登録自体はできてしまって、起動時にPENDING→STOPPEDと自動的に止まるのですが、そもそもなぜ止まったのかがわかりにくいという特徴があります。

![スクリーンショット 2017-07-27 20.33.24.png](https://qiita-image-store.s3.amazonaws.com/0/32778/a589b31b-d79f-6721-6f48-91923eae6808.png)

止まったタスクのIDをクリックすると、詳細ページが出てくるのですが、その中のコンテナをクリックすると、コンテナの詳細情報が表示されます。ここに状況の理由という項目があって、エラー内容が書いてあります。

> Invalid parameter at 'registryIds' failed to satisfy constraint

これはレジストリのIDがおかしいというエラーですが、それ以外にも、ファイルがpullできないというエラーのケースもあります。

> タスク停止理由:Essential container in task exited
> コンテナ詳細：状況の理由：CannotPullContainerError: API error (404): repository ***** not found: does not exist or no pull access

### awslogsログドライバーを指定しているのに、aws-logsに指定したグループがCloudWatch上に設定されていない

aws-logsのグループ名を適当につけて、CloudWatch側にそのグループ名がない場合にも、登録はできるのに、起動時にPENDING→STOPPEDで無慈悲に止まります。

![スクリーンショット 2017-07-27 20.39.38.png](https://qiita-image-store.s3.amazonaws.com/0/32778/4cde72f8-593a-8655-176a-18ad8df5ce34.png)

> The specified log group does not exist.

と、エラー理由が明確に書いてあるわけで、この詳細さえ見てれば、まずはケアレスミス的なものは潰せるのかなと。


## まとめ

サーバ建てて、sshで入って、プログラムコピーして、設定して、動作確認して、といういままでの進め方が、今回AWS上ではサーバに一切触れていない（EC2もS3も触ってない）のに目的を達成しているわけでして、なんか一気に未来に来た感がありますね。

IoTとも相性良さそうなので、もう少し突き詰めていきたいと思います。