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

Hubotの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を使った方が良さそう
リポジトリを作成

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

一応実行したコマンドを書いておきます(aws-cli使う前提です)
以下を実行するとDockerのログインコマンドが出力されるので、コピペして実行して、ログインする
aws ecr get-login --no-include-email --region ap-northeast-1
ログイン後、以下のコマンドでイメージを作成し、プッシュする
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 ロール


新しいタスク定義の作成をクリック
コンテナの起動タイプの選択
FargateかEC2のどちらにホスティングするか決める

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

設定名 | 設定値 |
---|---|
タスク定義名 | 適当な名前 |
タスクロール | なし |
今回はタスクから他のAWSサービスにリクエストはしないので、タスクロールはなし
■ awsvpcネットワークモードについて
今までは、セキュリティグループなどの情報をもつENI(Elastic Network Interface)をEC2(ホスト)に設定してタスクに共有していたが、awsvpcでは直接タスクにENIを割り当てることが可能になる
Fargateの場合、EC2をプロビジョニングしないため、awsvpc固定
タスクの実行IAMロール

こちらのロールはタスクを実行するためのロールを設定する
Fargateでは、ログをCloudWatchに公開する必要があるので、ロールの設定は必須とのこと
今回は「新しいロールの作成」を選択し、自動的に作成してもらう
これを選ぶと、タスク定義の作成時に、ecsTaskExecutionRoleというロールが自動的に作られる
IAMで中身を見ると、CloudWatch LogsのWrite権限が付与されていることが分かる
タスクサイズ

こちらはタスクのメモリとCPUを指定する
Fargateでは、必須とのこと
今回はメモリやCPUを使うタスクは無さそうなので、最小構成のメモリとCPUにした
コンテナの追加
ここからはコンテナ定義を作成する
コンテナの追加をクリック

スタンダード

設定名 | 設定値 |
---|---|
コンテナ名 | 適当な名前 |
イメージ | ECRのリポジトリのURI |
プライベートレジストリの認証 | なし |
メモリ制限(MB) | 設定なし |
ポートマッピング | 9090 |
■ メモリに関して
タスクに設定したメモリより小さい値を設定する必要がある
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/AWS_Fargate.html
ただし、AWSの開発ガイドを見てみると、コンテナのメモリはオプションで、
「ほとんどのユースケースでは、タスクレベルでこれらのリソースを指定するだけで十分です。」
って書いてあるので、今回は設定しなかった
■ ポートに関して
Fargateを使うとawsvpcネットワークモードに固定となる
bridgeネットワークモードが使えないため、ホストと異なるポートマッピングはできない
よって、EC2を選んだ場合と違い、Fargateの場合はコンテナのポートのみを指定すれば良い
HEALTHCHECK

タスク内のコンテナのヘルスチェックを定義できる
ヘルスチェックのコマンドの設定やヘルスチェックの実行間隔、リトライなどを定義する
今回は設定なし
(チャットボットを呼んで反応無ければ、死んでるって判断できそうだったので)
環境

コンテナに渡すコマンドや環境変数などを設定する
このコンテナに割り当てるCPUユニット数はタスクに設定したCPUの値以下にする必要がある
ただし、Fargateでは、省略可能とのこと
ChatWorkアダプターが参照するトークンやルームIDなどを環境変数として、ここで設定する
(DockerfileのENV命令と同様)
ネットワーク設定

コンテナレベルでのネットワーク設定
今回は設定なし
ストレージとログ

ログとか設定
Fargateでは、awslogsのみをサポートしている
今回は設定なし
リソースの制限

コンテナが利用するリソースを制限する
今回は設定なし
DOCKERラベル

docker run
のlabelオプションにマッピングする
環境変数みたいなものだが、ラベルはコンテナ内で実行中のプロセスから参照不可
今回は設定なし
クラスターの作成
クラスターは、先程作成したタスクや後述で作成しているサービスをグルーピングできる

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

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

クラスターもFargateにした

クラスター名に適当な名前を設定して、作成
サービスを作成
サービスでは、以下のような項目を設定する
- 使用するタスク定義
- サービスを実行するクラスター
- サービスに配置するタスク数
- ネットワークの設定(VPCとセキュリティグループ)
- ELB
- ELBのヘルスチェック
- Auto Scaling

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

作成をクリック
サービスの設定

設定名 | 設定値 |
---|---|
起動タイプ | 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 (オプション)

今回は設定なし
サービス及びタスクが正常稼働しているか確認
Hubotのコード管理
Hubotのコード(CoffeeScript)管理をGitHubなりで管理しないと辛いので、GitHubへのpushをフックして、ECSへデプロイする仕組みを作っておいた方が良い
今回は使い慣れているCircleCIで作成した
使ったことないけど、AWS CodePipelineでも良さそうです
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でもいいかなと思います