Amazon ECS対応のスケジューラBloxを試してみた #reinvent

ども、大瀧です。
先週開催されたAWSの年次イベントre:Inventで発表された、OSSのコンテナスケジューラであるBloxを試してみた様子をレポートします。

Bloxとは

Blox公式ページの説明を引用します。

Blox is a collection of open source projects for container management and orchestration on Amazon ECS

"Amazon ECS向けのコンテナ管理&オーケストレーションツール群"という訳語が適当でしょうか。Amazon ECSには元々、クラスタ管理や簡易なコンテナスケジューラが組み込まれていますが、コンテナ配置についてはおおまかな設定ができる"Service"と、詳細に指定する"Task"の2種類で、その中間の位置づけとなるようないわゆるコンテナスケジューラはユーザーで用意してね、というスタンスでした。Bloxはこの領域をカバーするOSSとして公開され、現在はAWSが主体となって開発が進められています。

Bloxのデプロイ方法

現時点で用意されている、Bloxの実行方法は以下の2通りです。

  • ローカルのDocker環境
  • AWS環境

今回は比較的手軽に試すことができる、ローカルのDocker環境で試してみました。ローカルのDocker環境で実行する場合のアーキテクチャを以下に示します。

blox-ataglance02

ローカルとは言いつつ、AWSアカウントといくつかの最低限のサービスが構成として必要です。詳細は手順で後述します。Docker側は3つのコンテナが動作し、Bloxとしては以下2つのコンポーネントになります。

  • Blox - Daemon Scheduler : 現在Bloxで実装されている唯一のコンテナスケジューラ
  • Blox - Cluster State Service(以下CSS) : スケジューラがECSクラスタの状態を把握するために利用するバックエンドサービス
  • etcd : Daemon SchedulerとCSSがデータストアとして利用するKVS

デプロイ手順

動作確認環境

  • OS : macOS Sierra バージョン 10.12.1
  • Docker : Docker for Mac バージョン 1.12.3
  • AWSリージョン : 東京

1. ECSクラスタの作成

Bloxの管理対象となるECSクラスタを作成します。クラスタの制約は特にないので、既存のクラスタがあればそれを利用しても問題ありません。試しに新規で作成するのであれば、ECSのウィザードで作成するのが良いでしょう。今回はウィザードからクラスタ名default、t2.micro 1台で作成しました。

blox02

2. CloudFormationの実行

続いてCSSがECSクラスタのイベントを取得する仕組みとして、最近ECSのイベントがソースとしてサポートされたCloudWatch Eventsとそのイベントの保存先としてのSQSを、CloudFormationで作成します。CloudFormationテンプレートはGitHubのBloxリポジトリ(https://github.com/blox/blox/)にあるので、git cloneしCloudFormation実行時にテンプレートファイルを指定します。

$ git clone https://github.com/blox/blox.git
$ cd blox
$ aws cloudformation  create-stack --stack-name BloxLocal --template-body file://./deploy/docker/conf/cloudformation_template.json

しばらく待つと、CloudFormationスタック(今回はスタック名BloxLocal)の作成が完了します。CloudFormationの管理画面から作成されたリソース一覧が確認できます。

blox01

AWS側の準備はこれでOKです。

3. Docker Composeの実行

Dockerの3つのコンテナは、Docker for MacにバンドルされるDocker Composeでまとめて管理します。Docker Composeの構成ファイルであるdocker-compose.ymldeploy/docker/conf/ディレクトリにあり一部設定しなければならない項目があるので、あらかじめ確認しておきましょう。

$ ls
CHANGELOG.md           Godeps/                README.md              daemon-scheduler/      licenses/
CONTRIBUTING.md        LICENSE                cluster-state-service/ deploy/                vendor/
$ cd deploy/docker/conf/
$ ls
cloudformation_policy.json    cloudformation_template.json  docker-compose.yml
$

docker-compose.ymlでは、各コンテナがAWSにアクセスするための設定が2箇所に2つずつ(AWSリージョン名: AWS_REGIONとIAMユーザー名": AWS_PROFILE)あり、実行する環境にあわせて変更する必要があります。今回は東京リージョンと、あらかじめ作成したIAMユーザー名bloxを設定しました。その他はそのままでOKです。

version: '2'
services:
  scheduler:
    image: "bloxoss/daemon-scheduler:0.1.0"
    ports:
      - "2000:2000"
    environment:
      AWS_REGION: "ap-northeast-1"
      AWS_PROFILE: "blox"
    command: [
      "--bind", "0.0.0.0:2000",
      "--css-endpoint", "css:3000",
      "--etcd-endpoint", "etcd:2379"
    ]
    links:
      - "css:css"
      - "etcd:etcd"
    volumes:
      - "~/.aws:/.aws:ro"
    depends_on:
      - "css"
      - "etcd"
  css:
    image: "bloxoss/cluster-state-service:0.1.0"
    ports:
      - "3000:3000"
    environment:
      AWS_REGION: "ap-northeast-1"
      AWS_PROFILE: "blox"
    command: [
      "--bind", "0.0.0.0:3000",
      "--etcd-endpoint", "etcd:2379",
      "--queue", "blox_queue"
    ]
    links:
      - "etcd:etcd"
    volumes:
      - "~/.aws:/.aws:ro"
    depends_on:
      - "etcd"
  etcd:
    image: "quay.io/coreos/etcd:v3.0.13"
    ports:
      - "2379:2379"
    command: [
      "/usr/local/bin/etcd",
      "--data-dir", "/var/lib/etcd/data",
      "--wal-dir", "/var/lib/etcd/wal",
      "--listen-client-urls", "http://0.0.0.0:2379",
      "--advertise-client-urls", "http://0.0.0.0:2379",
      "--listen-peer-urls", "http://0.0.0.0:2380"
    ]
    volumes:
      - "~/blox-state:/var/lib/etcd"

schedulerとcssのvolumesを見るとわかりますが、コンテナからはMac側のAWSクレデンシャルファイルをマウントして上記のプロファイル名に対応するAPIキーを取得する形になります。AWSのAPI認証でエラーになる場合はこのあたりを見直しましょう。

では、Docker Composerを実行しコンテナを起動します。

$ docker-compose up -d
Starting conf_etcd_1
Starting conf_css_1
Starting conf_scheduler_1
$ docker-compose ps
      Name                    Command               State                Ports
--------------------------------------------------------------------------------------------
conf_css_1         /cluster-state-service --b ...   Up      0.0.0.0:3000->3000/tcp, 80/tcp
conf_etcd_1        /usr/local/bin/etcd --data ...   Up      0.0.0.0:2379->2379/tcp, 2380/tcp
conf_scheduler_1   /daemon-scheduler --bind 0 ...   Up      0.0.0.0:2000->2000/tcp
$

起動しました!コンテナが終了してしまう場合は、docker logs <コンテナ名>などでトラブルシュートしましょう。私の環境ではcssが起動しないトラブルに遭ったので、以下の記事にまとめておきました。参考になれば幸いです。

これで準備OKです。

動作確認

それでは、CSSとDaemon Schedulerそれぞれの動作を確認してみます。CSSはECSクラスタのイベントをトラックしてクラスタの状態情報を保持し、localhostのポート番号3000でREST APIをサービスします。API一覧はGitHubのswagger.jsonで確認できます。試しにECSクラスタのインスタンス一覧をコールしてみましょう。

$ curl localhost:3000/v1/instances
{"items":[{"EC2InstanceID":"i-18d80d86","agentConnected":true,"attributes":null,"clusterARN":"arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:cluster/default","containerInstanceARN":"arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:container-instance/42c9130c-2eb8-4b53-970d-b73ca0bf2f4c","registeredResources":[{"name":"CPU","type":"INTEGER","value":null},{"name":"MEMORY","type":"INTEGER","value":null},{"name":"PORTS","type":"STRINGSET","value":null},{"name":"PORTS_UDP","type":"STRINGSET","value":null}],"remainingResources":[{"name":"CPU","type":"INTEGER","value":null},{"name":"MEMORY","type":"INTEGER","value":null},{"name":"PORTS","type":"STRINGSET","value":null},{"name":"PORTS_UDP","type":"STRINGSET","value":null}],"status":"ACTIVE","versionInfo":{"agentHash":"efe53c6","agentVersion":"1.13.1","dockerVersion":"DockerVersion: 1.11.2"}}]}
$

取得できる情報自体はECSのAPIと同等ですので、CSSはあくまでBloxスケジューラからECSを参照するためのAPIラッパーと考えるのが良さそうです。

続いて、Daemon Schedulerです。Daemon Schedulerは見本のスケジューラとして提示されるもので、独自のスケジューラを実装するためのひな形として利用できそうです。また、今後新たなスケジューラを追加する予定とのことです(GitHubのREADMEの記述を引用します)。

The scheduler can be used as a reference for how to use the cluster-state-service to build custom scheduling logic, and we plan to add additional scheduling capabilities for different use cases.

Daemon Schedulerは1インスタンス毎に1つのタスクを実行するシンプルなスケジューラです。ECSクラスタのインスタンス追加・削除に追随するようになっています。スケジューラ自体の設定は、localhostのポート番号2000でListenするREST APIを利用します。API一覧はGitHubのswagger.jsonで参照できます。まずはGET /v1/pingで200レスポンスが返ってくるか、動作を確認しましょう。

$ curl -v localhost:2000/v1/ping
*   Trying ::1...
* Connected to localhost (::1) port 2000 (#0)
> GET /v1/ping HTTP/1.1
> Host: localhost:2000
> User-Agent: curl/7.49.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=UTF-8
< Date: Wed, 07 Dec 2016 00:11:19 GMT
< Content-Length: 0
<
* Connection #0 to host localhost left intact

Daemon Schuedulerの動作を簡単に確認できるデモスクリプトがリポジトリのdeploy/demo-cli/にあるので、今回はこちらを利用します。

$ cd ../../demo-cli/
suzaku:demo-cli ryuta$ ls
README.md                       blox-list-environments.py*      css-list-tasks.py*              task-definition.json
blox-create-deployment.py*      common.py                       increment-cluster-instances.py*
blox-create-environment.py*     common.pyc                      list-task-definitions.py*
blox-list-deployments.py*       css-list-instances.py*          register-task-definition.py*
$

Daemon Schedulerでは、タスクと実行するクラスタの組み合わせEnvironmentとして定義します。今回はディレクトリにあるサンプルファイルtask-definition.jsonからタスク定義を作成し、Environment作成時に指定してみます。

$ aws ecs register-task-definition --cli-input-json file://task-definition.json
{
    "taskDefinition": {
        "status": "ACTIVE",
        "family": "sleep300",
        "volumes": [],
        "taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task-definition/sleep300:2",
        "containerDefinitions": [
           :(中略)
        ],
        "revision": 2
    }
}
$ ./blox-create-environment.py \
  --environment TestEnvironment \
  --cluster default \
  --task-definition arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task-definition/sleep300:2
== Blox Demo CLI - Create Blox Environment ==

HTTP Response Code: 200
{
  "deploymentToken": "e3cf6414-47fb-4bbc-be15-ca834c7db6a0",
  "health": "healthy",
  "name": "TestEnvironment",
  "instanceGroup": {
    "cluster": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:cluster/default"
  }
}
$

オプションで指定したEnvironment名(TestEnvironment)とレスポンスに含まれるデプロイメントトークンをデプロイ時に指定するので、覚えておきましょう。

では、スケジュール設定をデプロイ(有効化)します。

$ ./blox-create-deployment.py \
  --environment TestEnvironment \
  --deployment-token e3cf6414-47fb-4bbc-be15-ca834c7db6a0
== Blox Demo CLI - Create Blox Deployment ==

HTTP Response Code: 200
{
  "status": "pending",
  "environmentName": "TestEnvironment",
  "id": "9071d07b-a7ba-4930-b45c-cbf23f9ec4cf",
  "failedInstances": [],
  "taskDefinition": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task-definition/sleep300:2"
}

これで、Daemon SchedulerはEnvironmentの定義に従い、インスタンス毎のタスク実行を管理するようになりました。ECSの実行タスク一覧を確認すると、定義したsleep300タスクが実行状態であることが確認できました!

blox03

では、クラスタにインスタンスを追加してタスクの様子を見てみましょう。ウィザードで作成するECSクラスタのインスタンスはAuto Scalingで構成されるので、EC2の管理画面からAuto ScalingグループEC2ContainerService-default-EcsInstanceAsg-<ランダム文字列>のインスタンス数を1→2に変更します。

blox04

しばらく待つとクラスタに追加したインスタンスが起動し、その追加イベントをDaemon Schedulerが捕捉、タスクが実行されるはずです。ECSタスク一覧を更新すると。。。

blox05

タスクが増えていますね。きちんとスケジューラが動作していることがわかります。

ちなみにこのsleep300タスクは名前の通り300秒(5分)sleepコマンドを実行して終了するタスクなので、デプロイから5分経つとタスクは一旦終了します。一方Daemon Schedulerはインスタンス毎にタスク実行を維持する機能があるため、終了を検知すると新たなタスクを実行するようにもなっています。停止タスク一覧でその様子が確認できます。

blox06

まとめ

Bloxの基本的な機能と動作を一通り解説してみました。現時点では特別なにかができると言ったものはないですが、ユーザーが独自にスケジューラを開発して組み込める点、今後コミュニティドリブンで新たなスケジューラが追加される点に期待していいのではないでしょうか。

また、当然スケジューラが停止するとタスク管理も維持できませんので、高可用性を備えたAWS環境での構成も試してみたいところですね。

参考URL