AWS
Docker
ECS
docker-compose
ECR

docker-composeで開発環境構築、一部コンテナをECS Fargate起動タイプで定期実行するまで

最近社内向けwebアプリの開発にdocker-composeを使ったんですが、一部のコンテナだけをAWS ECSにデプロイするような構成にしたので、その辺をざっくりメモしておきます。

今回、抽象化したsampleアプリを作成してgithubにあげておきました。

※sampleなので中身はスタブ

sampleアプリ


sampleアプリの構成

VueとGoを使ったWebアプリ。WebのUIにて登録されたデータをバッチで処理していく感じのアプリ。

- frontend: Vue.js

- backend: Go

- batch: Go


環境


docker-composeで開発環境を構築してみる

docker composeは複数のdockerコンテナをまとめて管理できるツールで、port forwardingやコンテナ間通信をサービス名で名前解決、volumeの設定などdocker-compose.yml1つで管理できるのがいいところです。


まずはdocker machine作成

docker machineは複数のDockerホストを構築・管理するためのツールで、docker machine上でコンテナを起動します。プロジェクトごとにmachineを分ける感じで使っています。

以下でsampleアプリ用のホスト環境を作成

$ docker-machine create -d virtualbox docker-compose-sample

docker-machine lsで確認するとdocker-compose-sampleという名前のホスト環境(machine)が作成されます。

$ eval $(docker-machine env docker-compose-sample)

docker-compose-sampleをアクティブにする。以後、docker-compose-sampleホスト環境上でコンテナを作れるようになります。

$ docker-machine ip docker-compose-sample

docker-compose-sampleのIPを取得。今回は192.168.99.101でした。

コンテナに対してこのIP + portでアクセスできます。


docker-compose.yml作成

必要なコンテナ群の作成方法をまとめた設定ファイル。docker-composeコマンドで特別ファイル名を指定しなければ、カレントディレクトリ配下のdocker-compose.ymlを参照するようになっています。重要なラベルは以下の通りです。

docker-compose.yml version3


version

docker-compose.ymlのバージョン。バージョンによって書き方が若干変わります。


services

コンテナとして起動される。sampleではbackend、batch、frontendの3つがあり、

これらをサービス名としてそれぞれコンテナになります。


build

コンテナをビルドするためのコンテキストを指定する。

sampleアプリのように直接ディレクトリを指定すると、その直下のDockerfileをもとにコンテナを作成する。


ports

ホスト:コンテナのように指定。

例えばsampleアプリでは、ブラウザでhttp://192.168.99.101:20002にアクセスすると、backendからレスポンスが返ってきます。


links

サービス名を指定すると、そのコンテナ内においてサービス名で他コンテナにアクセスできます。

sampleではfrontend -> backendへの参照のみ必要だと仮定し、frontendコンテナ内でhttp://backendでbackendコンテナのサーバーサイドにアクセスできるようになります。

本来であればmysqlなどのdbコンテナも入れて、backend,batch -> dbといったアクセスも必要だが、sampleなので割愛します。


volumes

コンテナ内でソースコードなどのデータを保持するだけだと、コンテナを削除するとデータが消えます。なので普通はvolumeを設定し、そのvolumeをコンテナのあるディレクトリにマウントすることで、データの永続化とホスト-コンテナ間でのデータの共有を実現します。

例えば、mac上にgithubからソースコードをcloneしてきて、そのディレクトリとコンテナの中のディレクトリにマウントすると、mac上でソースをいじればコンテナの中にも反映される -> コンテナの中で最新のソースコードをビルドできる、といった感じです。

sampleアプリでは、frontendだけFONTEND_DOCKER_SYNC_VOLUMEというvolume名でexternal=trueとなっているのは、docker-sync用のvolumeを使うという意味で、docker-sync.ymlに設定を書いています。


docker-sync

サンプルの場合、vuejsのホットリロードがめちゃくちゃ遅く、原因はjsのビルド等ではなくホストとコンテナの同期の遅さでした。(体感ではvueのソースを編集してからブラウザのホットリロードがかかるまで数分とか、耐えられないレベル。。)

この遅さを改善するツールがdocker-syncです。

docker-syncでは、docker-sync専用のvolumeを作ってホストのディレクトリを同期、docker-sync専用のvolumeからコンテナに対していくつかの転送方法で同期するという仕組みらしい。

転送方法とベンチマークについてはこちら

docker-syncを使ったところ同期がかなり速くなりましたw

(vueのソースを編集してからブラウザのホットリロードがかかるまで5~10秒程度)


docker-syncのインストール

@pocariさんの記事を参考にさせていただきました。


docker-syncしつつコンテナ起動

$ docker-sync start

docker volume lsで確認するとFONTEND_DOCKER_SYNC_VOLUMEというボリュームが作られています。

docker-compose.yml通りにコンテナを起動します。

$ docker-compose up -d

docker-compose psで確認するとbackend, batch frontendという3つのコンテナが立ち上がる


アプリを動かしてみる


backend

backendコンテナに入る

$ docker-compose exec backend bash

goのパッケージマネージャを使ってパッケージを入れる

$ dep init

godoでサーバーサイドのアプリを起動

$ godo server -w

http://192.168.99.101:20002/jobsにアクセスするとjsonが返ってくると思います.


frontend

frontendコンテナに入る

$ docker-compose exec frontend ash

npmでパッケージを入れる

$ npm install

webpack-dev-serverをたててアプリを起動

$ npm run dev

http://192.168.99.101:20003にアクセスするとbackendからデータをとってきたfrontendのviewが見れます。  

  

frontendはdocker-syncの対象にしているので、例えば/frontend/vue/src/app/src/components/JobList.vueなどのソースをhost側で修正すると、数秒たってからブラウザのリロードが走って修正が反映されます。(docker-sync + webpack-dev-serverのhot reload)


batch

batchコンテナに入る

$ docker-compose exec batch bash

goのパッケージマネージャを使ってパッケージを入れる

$ dep init

実行

$ go run main.go

Batch executed.の文字列が標準出力される

ここまでがdocker-composeを使った開発環境の構築になります。

続いて、batchコンテナをECSで動かす手順を見ていきましょう。


AWS ECS

AWS ECS(Elastic Container Service)は、DockerHubやAWS ECR(docker imageのAWSリポジトリ的なやつ)にpushしたdocker imageからコンテナ起動・管理するサービスです。ECSには2つの起動タイプがあり、Fargate起動タイプ・EC2起動タイプから選ぶことができます。EC2起動タイプはもともとあって、後発でFargate起動タイプが追加されたという経緯があり、EC2起動タイプではEC2インスタンスをベースにコンテナを起動するが、Fargate起動タイプではEC2インスタンスを必要としません。つまりFargate起動タイプではEC2の管理を必要とせずにサーバーレスでコンテナを実行できるというイメージです。

料金はEC2起動タイプではEC2やEBSなどのAWSリソースに対して、Fargate起動タイプはCPUおよびメモリに対して従量課金されます。

EC2起動タイプはEC2インスタンスにSSHで入っていろいろできますが、Fargate起動タイプではSSHでログインできません。また、Fargate起動タイプでは基本的に処理終了後にコンテナがkillされるます。

コンテナを立てるEC2インスタンスに対して色々やりたい場合はEC2起動タイプ、

input/outputがDBやS3など外部サービスに集約できるのであればFargate起動タイプを選ぶといいかも。。


タスク定義

コンテナやボリューム、およびメモリ、CPUなどを設定する。

タスク定義にしたがってタスク(コンテナ)が生成されて処理が行われる。


タスク

タスク定義を元に作られたコンテナ


クラスター

タスクまたはサービスのまとまり。


サービス

Webサーバー用コンテナなど、アプティブな状態で常駐してほしい場合に、コンテナを1くくりにして死活監視/必要タスク数まで復旧、およびポートマッピングなどを行ってくれるもの。


実際に動かしてみる

今回のsampleアプリではbatchコンテナのみをECSで動かしてみる。

batchはDB fetch -> 処理 -> DB updateのようにI/OがDBに完結する処理を想定しており、コンテナは実行後破棄でいいので、ECSのサービスではなくタスクのスケジュール実行を使うことにする。


ECRにbatchコンテナのimageをpushする

前もってAWSマネジメントコンソール上でECRにsampleアプリ用のリポジトリを作成しておきましょう。

batchコンテナの中でアプリをビルドする(コンテナ内のroot直下にバイナリを出力)

$ GOOS=linux GOARCH=amd64 go build -o /app

batchコンテナからexitする

aws cliでECRのログインセッションを取得

$ $(aws ecr get-login --no-include-email --region ap-northeast-1)

batchコンテナからイメージを作成

$ docker commit batch docker-compose-sample:batch

ECRにプッシュするためのタグ付け

$ docker tag docker-compose-sample:batch [リポジトリURI]:batch

ECRにイメージをプッシュ

$ docker push [リポジトリURI]:batch

これでECSがECR上のリポジトリからbatchコンテナイメージを取得できるようになりました。


ECSクラスターを作成する

「クラスターの作成」から 「ネットワーキングのみ AWS Fargate を使用」を選択し、「次のステップへ」

クラスター名を設定して作成。

create_cluster_1.png


タスク定義を作成する

「新しいタスク定義を作成」で「FARGATE」を選択し「次のステップへ」

タスク定義名、タスクメモリ、タスクvCPU等を設定し、「コンテナの追加」をクリック

イメージは先ほどECRにプッシュした際の[リポジトリURI]:batchを指定する。

「エントリポイント」にbatchアプリの実行ファイルパス「/app」を指定する。

entrypoint.png

コンテナに対する環境変数が必要であれば、この画面でセットしておく。

このタスクでは、batchコンテナはECRからイメージをpull、コンテナをたてて/appを実行、終わったらkillされる。


タスクスケジューリングにより定期実行する

先ほど作成したクラスターを選択し「タスクのスケジューリング」タブから「作成」をクリック

schedule_0.png

実行間隔や対象となるタスク定義を指定して作成。

schedule_1.png

sampleアプリだと、1分間隔でcloudwatchにBatch executed.の文字列が標準出力されたログが出ている。

cloudwatch.png


おわりに

docker-composeについて、ほぼほぼ言うことなくかなり快適です。ただ、webpack-dev-server + Vue.jsでhot reload開発する場合、docker-syncないと地獄です。。  

ECSについて、dockerで開発しておけばECSへのデプロイ・運用はめちゃくちゃ楽だなぁという印象です。今回の記事のきっかけとなった社内向けのwebアプリの中でECSに乗せたのはbatch部分だけでしたが、デプロイの楽さとデプロイフローの統一という意味では全部ECSにしてもよかったと思いました。

弊社「仏のCTO」はこちら