ECS CLIを使ってDocker Composeのファイルを使ってECSにデプロイする

[ECS CLI](https://github.com/aws/amazon-ecs-cli)というECS専用のCLIツールが先日、発表されました（AWS CLIのECS対応という話ではなく、ElasticBeanstalk用のebコマンドがあるようなイメージと似ていると思います）

このツールの最大の利点としてはDockerComposeで作成した`docker-compose.yml`がECSでも使えるよ!という所だと思います。

自分はDockerComposeを使った事がなかったので、初めにDockerComposeの公式チュートリアルをローカルで試しつつ、その後、ECS-CLIを使ってローカルで作成したものをAWSのECSにデプロイしてみたのでメモ。

# 結論

+ ECS-CLIを使う事をで簡単にDockerComposeで作った環境をAWSにデプロイできる
+ コマンドはdocker-composeに似ているので覚えやすい
+ ECS-CLIでbuildディレクティブは現在使えないので、利用するイメージは予めビルドしておくこと
+ ECS-CLIがGoで書かれており、バイナリなのでインストールが楽
+ ECSでt2.microなど少ないスペックで実施する場合、複数コンテナ起動時にメモリ不足で起動できない場合があるので、その場合、mem_limitディレクティブなどで調整が必要
+ EC2を意識しないコンテナサービスが出て欲しい。。。。

# 参考

+ [Using the Amazon ECS Command Line Interface](http://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ECS_CLI.html)
+ [aws/amazon-ecs-cli](https://github.com/aws/amazon-ecs-cli)
+ [ecs-cli チュートリアル](http://inokara.hateblo.jp/entry/2015/10/10/055245)
+ [ECS CLIでAmazon ECSを操作してみた #reinvent](http://dev.classmethod.jp/cloud/aws/using-ecs-cl/)

# 環境

+ MacOSX(10.10.5)
+ Docker 1.8.2
+ Docker Compose 1.4.0
+ ECS CLI v0.1.0

# ローカル環境(Mac)でDocker Composeを試す

下記に沿ってやります。

[Overview of Docker Compose](https://docs.docker.com/compose/)

dockerやdocker composeをローカルマシンにインストールしていない場合、説明に従ってインストールしてください。

## instalation and set-up


```bash
# version確認
$docker-compose --version
docker-compose version: 1.4.0
```

```bash
$mkdir composetest
$cd composetest
```

2つのファイルを作成します。Pythonのアプリケーション本体とアプリケーションの依存ライブラリの一覧

```lang:app.py
from flask import Flask
from redis import Redis

app = Flask(__name__)
redis = Redis(host='redis', port=6379)

@app.route('/')
def hello():
    redis.incr('hits')
    return 'Hello World! I have been seen %s times.' % redis.get('hits')

if __name__ == "__main__":
    app.run(host="0.0.0.0", debug=True)
```

```lang:requirements.txt
flask
redis
```

## Create a Docker image

Dockerfileを作ります。

```lang:Dockerfile
FROM python:2.7
ADD . /code
WORKDIR /code
RUN pip install -r requirements.txt
CMD python app.py
```

+ DockerHubのpythonというイメージの2.7というタグがついたイメージを利用
+ app.pyとrequirments.txtをDockerイメージの/codeというパス配下に配置
+ ディレクトリを /code に移動
+ `pip install -r requirements.txt`コマンドを実行し、依存ライブラリをインストール
+ `python app.py`というコマンドをコンテナ起動時に実行するように設定

## Define services

`docker-compose.yml`というファイルを作成し、複数のコンテナの定義及び起動時のオプションを設定します。

```lang:docker-compose.yml
web:
  build: .
  ports:
   - "5000:5000"
  volumes:
   - .:/code
  links:
   - redis
redis:
  image: redis
```

+ webとredisというコンテナを使います
+ redisコンテナはDockerHubからredisという名前のイメージを取得して利用します
+ webコンテナはカレントディレクトリのDockerfileをビルドし、そのイメージを利用します
+ Hostの5000ポートにアクセスした場合、コンテナの5000ポートにforwardするようにします
+ カレントディレクトリのファイル群をコンテナの`/code`にマウントします。これによって以降、app.pyを変えた場合にコンテナの再ビルドを行う必要がなくなります
+ redisというコンテナとリンク接続します。これによってwebコンテナからredisコンテナにアクセスできるようにします。リンク設定をするとwebコンテナからはredisというホスト名でredisコンテナにアクセスできるようになり、接続ができます(/etc/hostsにredisホストの情報を追記している)

## Build and run your app with Compose

いよいよdocker-composeを使ってみましょう！
イメージのダウンロードを行ってない場合、初回時に実行されるため、多少時間がかかりますが、ゆっくり待ちましょう!

```bash
$docker-compose up
....
redis_1 | 1:M 26 Oct 12:35:07.634 * The server is now ready to accept connections on port 6379
web_1   |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
web_1   |  * Restarting with stat
```

これでコンテナが起動していますが、フォワグラウンドで起動し、操作できないのでCtrl+Cで一度停止させます。

次は`-d`オプションを利用して、起動し、バックグラウンドでコンテナを起動します。

```bash
# webとredisコンテナをバックグラウンドで起動
$docker-compose up -d

# docker-composeによって起動しているコンテナ群を確認
$docker-compose ps
      Name             Command             State              Ports
-------------------------------------------------------------------------
composetest_redi   /entrypoint.sh     Up                 6379/tcp
s_1                redis-server
composetest_web_   /bin/sh -c         Up                 0.0.0.0:5000->50
1                  python app.py
```

無事バックグラウンドで指定した2つのコンテナが起動しているのを確認できました。

起動したwebコンテナのアクセスはboot2dockerを使っていない場合、`curl http://localhost:5000/`とすることでできますが、自分の場合、Macでboot2docker(DockerMachine)を使っているのでそのIPを使ってアクセスします。

```bash
# docker-machineのIPアドレスを確認
$docker-machine ls
NAME      ACTIVE   DRIVER       STATE     URL                         SWARM
default   *        virtualbox   Running   tcp://192.168.99.100:2376

# アクセス。アクセスごとにカウントが増える
$curl http://192.168.99.100:5000/
Hello World! I have been seen 2 times.%
```

バックグラウンドで起動したコンテナは以下で停止させます。

```bash
$docker-compose stop
```

# Amazon ECS CLI Tutorial

次にECS CLIを使ってみます。まずは先ほど作成したDockerComposeのファイルを使わず、公式でドキュメントのチュートリアルをそのまま実行してできる事を確認します。

以下、公式ドキュメントに沿ってやっています。

[Using the Amazon ECS Command Line Interface](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ECS_CLI.html)

## Installing the Amazon ECS CLI

MacとLinux用のGoで書かれたバイナリをあるようなのでそれを利用します。

```bash
# /usr/local/bin/ecs-cliにバイナリを配置
$sudo curl -o /usr/local/bin/ecs-cli https://s3.amazonaws.com/amazon-ecs-cli/ecs-cli-darwin-amd64-latest

# 実行権限を付与
$sudo chmod +x /usr/local/bin/ecs-cli

# 確認
$ecs-cli help
```

# Configuring the Amazon ECS CLI

AWSの認証や利用するリージョン、クラスタ名をCLIに教えてあげる必要があります。
設定した内容は`~.ecs/config`として保存されるようです

まず、AWS認証情報ですが以下のいずれかで設定が可能です。

+ 環境変数の`AWS_ACCESS_KEY_ID`と`AWS_SECRET_ACCESS_KE`を指定
+ AWS CLIの`aws configure`コマンドなどによって` ~/.aws/credentials`が存在し、認証情報が記載さている
+ `ecs-cli configure`コマンドで指定。 例:`ecs-cli configure --region us-west-2 --access-key $AWS_ACCESS_KEY_ID --secret-key $AWS_SECRET_ACCESS_KEY --cluster ecs-cli-demo`

自分の場合、既にAWS CLIを利用しており、`~.aws/credentials`があったのでリージョンとクラスタ名のみ指定します


```bash
# configureコマンドで設定
$ecs-cli configure --region ap-northeast-1 --cluster my-cluster

# 確認
$cat ~/.ecs/config
[ecs]
cluster = my-cluster
aws_profile =
region = ap-northeast-1
aws_access_key_id = xxxxx
aws_secret_access_key = xxxxxx
```

む、キー情報はこちらにも書かれるんですね。。。

## Amazon ECS CLI Tutorial

ではチュートリアルをやってみます。

### Step 1: Create your Cluster

AWS上でコンテナを起動させるためのAmazon EC2のクラスタを起動する必要が有ります。（正確にはECS AgentやDockerがインストールされたECS readyなEC2の起動)
上記クラスタを起動するために`ecs-cli up`コマンドを利用します。

```bash
$ecs-cli up --capability-iam --keypair HogeFugaKey
```

オプションでVPCやinstance type、解放するポートなど色々設定できますので詳細はhelpオプションで確認ください。

なお、起動したEC2を確認するとAMIはECS用の設定がされたAmazonLinuxのようでした。

## Step 2: Create a Compose File

Docker-composeのファイルを作成します。
t2.mirocのEC2なので利用するメモリを各コンテナとも256MBとしています。

```hello-world.yml
wordpress:
  image: wordpress
  mem_limit: 268435456
  ports:
    - "80:80"
  links:
    - mysql
mysql:
  image: mysql
  mem_limit: 268435456
  environment:
    MYSQL_ROOT_PASSWORD: password
```

なお現在、すべてのdocker-composeで指定できるディレクテイブが使えるわけではない点が注意点です。
利用できるのは以下のディレクティブで`build`ディレクティブが使えないのでimageは必ず一旦DockerHubなどに置く必要がある点が注意点です。

+ command
+ cpu_shares
+ entry point
+ environment
+ image
+ links
+ mem_limit (in bytes)
+ ports
+ volumes
+ volumes_from


### Step 3: Deploy the Compose File to a Cluster

さあ、` ecs-cli compose up`コマンドを利用して、作成したcomposeファイルのコンテナをクラスタにデプロイしてみます。

デフォルトでカレントディレクトリの`docker-compose.yml`ファイルを探しますが、`--file`オプションでファイル名を指定することが可能です。

```bash
# composeに記載されたコンテナの起動
$ecs-cli compose --file hello-world.yml up
```

### Step 4: View the Running Containers on a Cluster

psコマンドで起動しているコンテナを確認してみます。

```bash
# 起動しているコンテナの確認
$ecs-cli ps
Name                                            State                                                                               Ports                     TaskDefinition
d2d44fd7-5d14-4510-92ee-3a385d28ac61/wordpress  RUNNING                                                                             54.64.183.253:80->80/tcp  ecscompose-tmp:4
d2d44fd7-5d14-4510-92ee-3a385d28ac61/mysql      RUNNING                                                                                                       ecscompose-tmp:4
```

ブラウザでコンテナのIPアドレス(上記では54.64.183.253)にアクセスするとWordPressの画面が表示されました。

### Step 5: Scale the Tasks on a Cluster

スケールさせるぞ！と思ったらメモリを足りず失敗。。。

```bash
$ecs-cli compose --file hello-world.yml scale 2
INFO[0000] Couldn't run containers                       reason=RESOURCE:MEMORY
```

なので`ecs-cli scale`コマンドでクラスタとなっているEC2の数を1つから2に増やします。

```bash
$ecs-cli scale --capability-iam --size 2
INFO[0001] Waiting for your cluster resources to be updated
INFO[0001] Cloudformation stack status                   stackStatus=UPDATE_IN_PROGRESS
```

なるほど。CloudFormationのupdate stackをしているのか。

完了後、再度やってみます。

```bash
# 起動
$ecs-cli compose --file hello-world.yml scale 2

# 確認.コンテナが2つから4つになっている
$ecs-cli ps
```

同じようにIPアドレスにアクセスするとWordPressの画面が確認できました。

### Step 6: Create an ECS Service from a Compose File

今まで実行したコンテナはECSでいうタスクというバッチなどに適した実行だったので、次にserviceというWeb/APIに適したものとして実行します。

```bash
# 現在利用しているrun taskを停止
$ecs-cli compose --file hello-world.yml down

# 停止を確認
$ecs-cli ps

# serviceとしてコンテナを起動
$ecs-cli compose --file hello-world.yml service up

# serviceとしての起動を確認
$ecs-cli compose --file hello-world.yml service ps
```

### Step 7: Clean Up

最後に環境を削除します。
先ほど作成したserviceを削除し、クラスタインスタンスを削除します。

```bash
# serviceの削除
$ecs-cli compose --file hello-world.yml service rm

# クラスタの削除
$ecs-cli down --force
```

# DockerComposeチュートリアルで試したものを適用してみる

最初の項でローカル環境で試したdocker-composeのファイルをECSに適用してみましょう。ただし、ECS CLIで利用できるcomposeファイルではbuildディレクティブが現在使えないのでローカルでイメージのビルドとDockerhubへのpushを行っておくことが必要な点を注意しておきます。

## DockerfileのビルドとDockerHubへのpush

最初に作成したwebコンテナをDockerHubへpushします。
DockerHubへのアカウントがない場合、事前に取得しておいてください。

```bash
cd ~/composetest

# Dockerfileのビルド。-tオプションで{username}/compose-testというリポジトリを設定。usernameはDockerHubのユーザー名
$docker build -t toshihirock/compose-test .

# DockerHubへのログイン
$docker login

# DockerHubへイメージのpush
$docker push toshihirock/compose-test
```

## DockerComposeのファイル変更

少し変更します。

+ webコンテナのイメージをDockerHubにpushしたものに変更
+ フォワードするポートを変更
+ メモリを設定(t2.microインスタンスのため)

```lang:docker-compose.yml
web:
  image: toshihirock/compose-test
  ports:
   - "80:5000"
  mem_limit: 268435456
  links:
   - redis
redis:
  image: redis
  mem_limit: 268435456
```

また、上記設定でも今まで同様にローカル環境でdocker-composeコマンドで起動できる事を確認します。

## ECS-CLIによる起動

では、先ほどチュートリアルでやった際と同じ要領でやってみます。

```bash
# クラスタの起動
$ecs-cli up --capability-iam --keypair HogeFugaKey

# docker-compose.ymlを利用してserviceの起動
$ecs-cli compose service up

# 確認
$ ecs-cli compose service ps
Name                                        State    Ports                      TaskDefinition
e63f0788-2adb-49cb-a9f8-601758f8890a/redis  RUNNING                             ecscompose-composetest:3
e63f0788-2adb-49cb-a9f8-601758f8890a/web    RUNNING  52.68.36.117:80->5000/tcp  ecscompose-composetest:3 

$curl http://52.68.36.117/
Hello World! I have been seen 2 times.
```

出来ました！
ローカルで作成した環境を簡単にAWSに移行できるのは良いですね！

# 掃除

綺麗にします

```bash
# serviceの削除
$ecs-cli compose service rm

# クラスタの削除
$ecs-cli down --force
```

お疲れ様でした!