tail my trail

作るのも使うのも、結局は、人なのだ

Amazon ECSのためのコンテナスケジューラー Blox をDocker for Mac で動かしてみる

f:id:uorat:20170221203052p:plain

tl;dr

  • 遅ればせながら、昨年の re:Invent 2016 で発表された Amazon ECS のためのOSS Container Scheduler Blox を試してみた。
  • AWS 上で動かす事もできるし、手軽に Blox をローカルで動かすこともできる
  • 必要なリソースは CloudFormation Template や Docker compose が用意されているのでさくっと試せるよ you try it

Blox の概要と経緯

公式かつ丁寧な紹介があるので、詳しくはそちらを参照してください。

Blox – Amazon EC2 Container Serviceのための新しいオープンソーススケジューラ | Amazon Web Services ブログ

Blox はできることをシンプルに表わすと、以下の4点です。

  • ECS Cluster のイベントを 検知
  • イベント情報をもとに ECS クラスタの状態を追跡
  • REST APIクラスタの状態を取得
  • スケジューラーによりイベントトリガーで処理を実行

昨年の AWS のApplication Load Balancer の登場 を機に、Web API サーバのようなコンテナを単にロードバランサー配下に並べてしまえば事足りる用途であれば、 ECS の Service Task が Dynamic Port Mapping まで面倒見てくれるのでずいぶん楽になりました。 また、ECS の Service Task は主要どころのコンテナ配備方法を提供してくれています。(Spread, BinPack, One Task Per Host)

ただ、独自のスケジューリングや、他アプリケーションやコンポーネントへの連携など見越すと、所謂コンテナスケジューラー的なものが欲しくなります。

  • 起動/破棄するコンテナインスタンスで 1回だけ実行するタスク
    • Data Volume 内のファイルの退避とか
  • コンテナそれぞれの Endpoint (コンテナインスタンスのIP + Port Mapping) を把握しておきたいケース
    • ELB を介することが難しい持続接続型のプロトコルで待ち受けるもの (例えば RTMP とか)
    • HAProxy や LVS などでコンテナにバランシングしたい時

こうしたケースは ECS API (ListContainerInstances や DescribeContainerInstances) を定期的に呼び出して、ECS コンテナインスタンスやその内部に配置されたコンテナの状態を追っかける必要がありました。

そんな中、 2016/11/25 に Amazon ECSイベントストリームで、クラスタの状態を監視 することができるようになりました。CloudWatch Events と連携できるようになり、ECS Eventをトリガーに独自の処理を発火することができるようになりました。 さらにその直後、 2016/12/2 re:Invent 2016 の Keynote Day 2 で ECS イベントストリームを活用した Blox が発表され、この恩恵を手軽に受けることができるようになりました。

Blox - Open Source Tools for Amazon ECS

つまり、上に挙げたユースケースを実現するにあたり必要だったスケジューラーの実装が Blox を使えば楽になるということです。

Blox の仕組み

Blox の構成要素と役割は以下の3つです。

  • cluster-state-service (abbr. css)
    1. ECSイベントの監視および Blox DB (etcd) への保存
    2. Blox API
  • etcd
    1. ECSイベントを格納するDB (Key-Value Store)
  • scheduler
    1. ECSイベントをトリガーに処理を行うスケジューラー

それぞれかいつまんで紹介します。

cluster-state-service (abbr. css)

CloudWatch Events をもとにクラスタインスタンスやコンテナの情報を追跡し、Blox DB (etcd) に保存します。 また、API も提供しており、css を通じて etcd に保存されたECSクラスタの内部ステータスを取得することが出来ます。

以下、README.md より引用。

The cluster-state-service consumes events from a stream of all changes to containers and instances across your Amazon ECS clusters, persists the events in a local data store, and provides APIs (e.g., search, filter, list, etc.) that enable you to query the state of your cluster so you can respond to changes in real-time. The cluster-state-service utilizes etcd as the data store to track your Amazon ECS cluster state locally, and it also manages any drift in state by periodically reconciling state with Amazon ECS.

css 自身は SQS Queue を監視する仕組みとなっています。 以下のようなEvent Pattern を SQS Queue に流す CloudWatch Events Rule を用意しておくことで、css が ECS のイベントを拾うことができるようになります。

{
    "detail-type": [
        "ECS Task State Change",
        "ECS Container Instance State Change"
    ],
    "source": [
        "aws.ecs"
    ]
}

etcd

Blox DB には Docker 使いなら馴染みのある人が多い、Golang 製の分散KVS etcd が採用されています。css が取得したECSイベントはここに格納されます。

coreos/etcd: Distributed reliable key-value store for the most critical data of a distributed system

scheduler (daemon-scheduler)

css/etcd に流れてきたECSイベントをトリガーに、独自の処理を行うスケジューラーです。

Blox としてサポートしているスケジューラーは今は daemon-scheduler のみで、冒頭に挙げたコンテナインスタンスそれぞれに “1つずつ” 稼働するコンテナを稼働させるケースにマッチするスケジューラーです。

以下、README.md より引用。

The daemon-scheduler allows you to run exactly one task per host across all nodes in a cluster. It monitors the cluster state and launches tasks as new nodes join the cluster, and it is ideal for running monitoring agents, log collectors, etc. The daemon-scheduler can be used a reference for how to use the cluster-state-service to build custom scheduling logic.

daemon-scheduler の機能自体も便利ですが、daemon-scheduler の実装がカスタムスケジューラーのリファレンスとしても使えるということです。

Architecture

本家 から図を拝借します。

f:id:uorat:20170221203112p:plain

Blox 自体は Golang 製の3つのプロセスが動けばよいだけなので、極端な話 SQS Queue に通信できる環境であれば ローカルでもAWS上に立てたEC2上でもどこでも動きます。 さらに、Blox 自体の Docker compose や Docker Image が公開されているので、Blox の Docker コンテナをローカルで動かすことも出来ますし、Blox 自身を ECS で動かすことも出来ます。 さすが本家が開発しているOSSなだけあって、必要な CloudFormation テンプレートもバンドルされています。

blox/deploy at blox/blox | Github

Local の Docker for Mac 環境に Blox を 動かしてみる

前置きが大変長くなりましたが、今回の本題。

blox/deploy/README.md に紹介されている Local Installation を試してみます。

手順はざっくり以下の2点です。CloudFormation Template と Docker Compose が用意されているのでとても簡単。

  1. BloxGithub Repository を clone
  2. Blox が使用する AWS Resources を作成 (by CloudFormation)
    • SQS Queue
    • SQS Queue にイベントを流すための CloudWatch Events Rule
  3. Blox Components をローカルで動かす (by Docker Compose)
    • cluster-state-service (abbr. css)
    • etcd
    • daemon-scheduler

1. BloxGithub Repository を clone

uorat-mbp:~ uorat$ cd app/docker/
uorat-mbp:docker uorat$ git clone https://github.com/blox/blox.git
Cloning into 'blox'...
remote: Counting objects: 4984, done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 4984 (delta 0), reused 0 (delta 0), pack-reused 4979
Receiving objects: 100% (4984/4984), 3.94 MiB | 734.00 KiB/s, done.
Resolving deltas: 100% (2488/2488), done.
Checking connectivity... done.
uorat-mbp:~ uorat$ cd blox/

2. Blox が使用する AWS Resources を作成 (by CloudFormation)

CloudFormation Template が用意されているので、これを使うだけです。 ${BLOX_HOME}/deploy/docker/conf/cloudformation_template.json にあります。 region や profile を指定して実行すればOK

uorat-mbp:blox uorat$ aws --region ap-northeast-1 --profile uorat cloudformation create-stack --stack-name BloxLocal --template-body file://./deploy/docker/conf/cloudformation_template.json
{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/BloxLocal/abcdefgh-abcd-abcd-abcd-abcdefghijkl"
}

StackStatus が 完了するのを待つ。

uorat-mbp:blox uorat$ aws --region ap-northeast-1 --profile uorat cloudformation describe-stacks --stack-name BloxLocal | jq ".Stacks[] | .StackId, .Description, .StackName, .StackStatus"
"arn:aws:cloudformation:ap-northeast-1:123456789012:stack/BloxLocal/abcdefgh-abcd-abcd-abcd-abcdefghijkl"
"Template to deploy Blox framework locally"
"BloxLocal"
"CREATE_COMPLETE"

3. Blox Components をローカルで動かす (by Docker Compose)

docker-compose.yml が用意されているので、これを使えば良いです。

composeファイル内に、 AWS Region と Profile を指定する必要があるので、自分の環境にあわせて変更しておくこと。 docker-compose.yml を見ると分かるとおり、cssdaemon-scheduler も Docker ホストの $HOME/.aws を read-only でマウントしています。 AWS_PROFILE で指定したプロファイル名に対応する credential を使用して SQS や ECS と対話する仕掛けになっています。

README.md にも書いてあります。

  • Update the AWS_REGION value with the region of your ECS and SQS resources.
  • Update the AWS_PROFILE value with your profile name in ~/.aws/credentials. You can skip this step if you are using the default profile.
uorat-mbp:blox uorat$ cd deploy/docker/conf/
uorat-mbp:blox uorat$ vim docker-compose.yml

uorat-mbp:conf uorat$ git diff
diff --git a/deploy/docker/conf/docker-compose.yml b/deploy/docker/conf/docker-compose.yml
index b626ac2..7a16857 100644
--- a/deploy/docker/conf/docker-compose.yml
+++ b/deploy/docker/conf/docker-compose.yml
@@ -5,8 +5,8 @@ services:
     ports:
       - "2000:2000"
     environment:
-      AWS_REGION: "<region>"
-      AWS_PROFILE: "default"
+      AWS_REGION: "ap-northeast-1"
+      AWS_PROFILE: "uorat"
     command: [
       "--bind", "0.0.0.0:2000",
       "--css-endpoint", "css:3000",
@@ -25,8 +25,8 @@ services:
     ports:
       - "3000:3000"
     environment:
-      AWS_REGION: "<region>"
-      AWS_PROFILE: "default"
+      AWS_REGION: "ap-northeast-1"
+      AWS_PROFILE: "uorat"
     command: [
       "--bind", "0.0.0.0:3000",
       "--etcd-endpoint", "etcd:2379",

あとは docker-compose コマンドでコンテナを立ち上げるだけ。

uorat-mbp:conf uorat$ docker-compose up -d
Creating network "conf_default" with the default driver
Pulling etcd (quay.io/coreos/etcd:v3.0.15)...
v3.0.15: Pulling from coreos/etcd
3690ec4760f9: Pull complete
53b6b297c402: Pull complete
0ee7413b6e7d: Pull complete
0b6d568d289d: Pull complete
f79877e4a632: Pull complete
Digest: sha256:aed90a29fbe7ad0e6629b2ea5a290f5b6efb9b719cec97c756df13f1db3760bf
Status: Downloaded newer image for quay.io/coreos/etcd:v3.0.15
Pulling css (bloxoss/cluster-state-service:0.2.0)...
0.2.0: Pulling from bloxoss/cluster-state-service
2551f207d95c: Pull complete
559c70e440db: Pull complete
b778135d24c5: Pull complete
67ed40d0e0b3: Pull complete
Digest: sha256:63aebbcf800a64d51acb20d5d7ae2c261fb272fd6aa020c533d4721c22279114
Status: Downloaded newer image for bloxoss/cluster-state-service:0.2.0
Pulling scheduler (bloxoss/daemon-scheduler:0.2.0)...
0.2.0: Pulling from bloxoss/daemon-scheduler
a4d560910c77: Pull complete
bcfbf1954f9f: Pull complete
e199cf3f7828: Pull complete
1bb380f5b15e: Pull complete
Digest: sha256:4e3d0314ceb8ab62bc296c6f351d87ee89c21616f28f3a9bd425f7edc8281636
Status: Downloaded newer image for bloxoss/daemon-scheduler:0.2.0
Creating conf_etcd_1
Creating conf_css_1
Creating conf_scheduler_1

docker ps コマンドで確認。 css は 3000/tcpdaemon-scheduler は 2000/tcp で待ち受けます。

uorat-mbp:conf uorat$ docker-compose ps
      Name                    Command               State                Ports
--------------------------------------------------------------------------------------------
conf_css_1         /cluster-state-service --b ...   Up      0.0.0.0:3000->3000/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

なお、今回は既に ECS Cluster を稼働させているアカウントを使っていたので特にハマることなく3つのコンテナが稼働してくれましたが、 @smiyaguchi さんによると 先にECS で何かしらのクラスタが起動していないと css_1 の起動に失敗するそうです。

Bloxを使ってみました - smiyaguchi’s blog

また、ECSやSQSへのアクセス権限が足りなかったり、profile の指定が間違っていても同様に以下のようなエラーが出て css_1 がコケます。

css_1        | 2017-02-21T08:55:56Z [INFO] Reconciler loading tasks and instances
css_1        | 2017-02-21T09:00:58Z [CRITICAL] Error starting event stream handler: NoCredentialProviders: no valid providers in chain. Deprecated.
css_1        |  For verbose messaging see aws.Config.CredentialsChainVerboseErrors
css_1        | Failed to list ECS clusters.
css_1        | github.com/blox/blox/cluster-state-service/handler/reconcile/loader.clientWrapper.listClusters
css_1        |  /go/src/github.com/blox/blox/cluster-state-service/handler/reconcile/loader/ecs_wrapper.go:75
css_1        | github.com/blox/blox/cluster-state-service/handler/reconcile/loader.clientWrapper.ListAllClusters
css_1        |  /go/src/github.com/blox/blox/cluster-state-service/handler/reconcile/loader/ecs_wrapper.go:54

複数AWSアカウントを運用していると、Switch Role で切り替えられるように .aws を書いたりすると思いますが、

uorat.hatenablog.com

そのノリで profile name を指定しても 該当する credential が取得できずに css が落ちてしまうので気をつけてください。 (※しばらくハマった残念な人です。)

うまく動くとこんな感じで動きます。

uorat-mbp:conf uorat$ docker-compose logs css
Attaching to conf_css_1
css_1        | 2017-02-21T09:29:15Z [INFO] Reconciler loading tasks and instances
css_1        | 2017-02-21T09:29:15Z [INFO] Bootstrapping completed
css_1        | 2017-02-21T09:29:15Z [INFO] Starting to poll for events from SQS
css_1        | 2017-02-21T09:29:47Z [INFO] SQS attribute[ApproximateNumberOfMessages] = 0
css_1        | 2017-02-21T09:29:47Z [INFO] SQS attribute[ApproximateNumberOfMessagesDelayed] = 0
css_1        | 2017-02-21T09:29:47Z [INFO] SQS attribute[ApproximateNumberOfMessagesNotVisible] = 0
css_1        | 2017-02-21T09:30:17Z [INFO] SQS attribute[ApproximateNumberOfMessages] = 0
css_1        | 2017-02-21T09:30:17Z [INFO] SQS attribute[ApproximateNumberOfMessagesDelayed] = 0
css_1        | 2017-02-21T09:30:17Z [INFO] SQS attribute[ApproximateNumberOfMessagesNotVisible] = 0
css_1        | 2017-02-21T09:30:47Z [INFO] SQS attribute[ApproximateNumberOfMessagesDelayed] = 0
css_1        | 2017-02-21T09:30:47Z [INFO] SQS attribute[ApproximateNumberOfMessagesNotVisible] = 0
css_1        | 2017-02-21T09:30:47Z [INFO] SQS attribute[ApproximateNumberOfMessages] = 0

uorat-mbp:conf uorat$ docker-compose logs scheduler
Attaching to conf_scheduler_1
scheduler_1  | 2017-02-21T09:29:16Z [INFO] Started dispatcher

css 経由でECS Cluster の情報を参照してみる

css で提供されている REST API を使って ECS の情報を参照してみます。 APIcss の README.md に書いてあるとおり swagger.json にて一覧が確認できます。

After you launch the cluster-state-service, you can interact with and use the REST API by using the endpoint at port 3000. Identify the cluster-state-service container IP address and connect to port 3000. For more information about the API definitions, see the swagger specification.

例えば コンテナインスタンスの一覧を取得する場合は “http://localhost:3000/v1/instances” です。 以下 swagger.json より引用。

"/instances": {
  "get": {
    "description": "Lists all instances, after applying filters if any",
    "operationId": "ListInstances",
    "parameters": [
      {
        "name": "status",
        "in": "query",
        "description": "Status to filter instances by",
        "type": "string"
      },
      {
        "name": "cluster",
        "in": "query",
        "description": "Cluster name or ARN to filter instances by",
        "type": "string"
      }
    ],
    "responses": {
      "200": {
        "description": "List instances - success",
        "schema": {
          "$ref": "#/definitions/ContainerInstances"
        }
      },
      "400": {
        "description": "List instances - bad input",
        "schema": {
          "type": "string"
        }
      },
      "500": {
        "description": "List instances - unexpected error",
        "schema": {
          "type": "string"
        }
      }
    }
  }
},

試しに問い合わせしてみると、ECS API ライクに ECS Instance の情報が json で返ります。以下、jq で整形した例。

uorat-mbp:~ uorat$ curl http://localhost:3000/v1/instances 2>/dev/null | jq '.items[].entity | [.EC2InstanceID, .clusterARN, .containerInstanceARN, .status, .versionInfo.dockerVersion]'
[
  "i-xxxxxxxxxxxxxxxxx",
  "arn:aws:ecs:ap-northeast-1:123456789012:cluster/ecs-demo-php-simple-app-cluster",
  "arn:aws:ecs:ap-northeast-1:123456789012:container-instance/abcdefgh-abcd-abcd-abcd-hogehogehoge",
  "ACTIVE",
  "DockerVersion: 1.12.6"
]
[
  "i-yyyyyyyyyyyyyyyyy",
  "arn:aws:ecs:ap-northeast-1:123456789012:cluster/openrec-station-ecs-demo-php-simple-app-cluster",
  "arn:aws:ecs:ap-northeast-1:123456789012:container-instance/abcdefgh-abcd-abcd-abcd-fugafugafuga",
  "ACTIVE",
  "DockerVersion: 1.12.6"
]

同じように task もとれました。

daemon-scheduler を試す

長くなったので、次回にします。。

まとめ

BloxAmazon ECS のためのOSS Container Scheduler です。 今まで ECS では手の届かなかった(ユーザー任せになっていた)独自のスケジューリングが組み込みやすくなり、他アプリケーションやコンポーネント間の連携まで含めた所謂オーケストレーションが可能となるでしょう。

肝心の scheduler は今回は記載しきれなかったのでまたの機会にしますが、Blox の稼働に必要なものは全て Dockerize & Template 化されているので、さくっと試せます。 ローカルでも動くので、とりあえずカジュアルにお試しを。