Posted at

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


はじめに

仕事で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 ロール



この図でいう、Container definitionとTask definitionの部分を作る


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


コンテナの起動タイプの選択

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でも良さそうです


.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でもいいかなと思います