読者です 読者をやめる 読者になる 読者になる

tail my trail

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

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

Docker ECS AWS Blox

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 化されているので、さくっと試せます。 ローカルでも動くので、とりあえずカジュアルにお試しを。

AWS CLI のprofileを簡単に切り替える (SwitchRole編)

AWS AWS CLI IAM

これまた意外と知らない人がちらほらいたので、書き留めておく。

tl;dr

$HOME/.aws/config で Switch Role な 設定をいれるだけ。

背景

Switch Role という機能を使うと、IAM User から特定のRole に切り替えることができる。

f:id:uorat:20170207160055p:plain

Management Console使ってると別アカウントの切り替えのためにログインし直すのは面倒だし、 アプリケーションのために AccessToken を発行しまくるのも管理上不便でリスクも増える(Token 漏洩すると悲惨)ので、自分はもっぱら IAM User は極力作成せず IAM Role に寄せるようにしている。

また、ログイン用の IAM User は ReadOnly にしておいて、操作が必要なときだけ パワフルな権限を有する Role に切り替えるなどしておくなどしておくと保険にもなる。

見た目もわかりやすいし。

f:id:uorat:20170207160253p:plain

で、今回のお題は、このSwitch Role を AWS CLI でどう扱うか。

やりかた

結構前に、AWS CLI の profile 切替方法を書いたが、

uorat.hatenablog.com

この応用でいける。

$HOME/.aws/config に、以下のように Switch 元の Profile と Switch 先のIAM Role の ARN を書いてあげれば良い。

[profile uorat-account-parent]
region = ap-northeast-1

[profile uorat-account-child-a]
role_arn = arn:aws:iam::123456789012:role/switchRoleReadOnly
source_profile = uorat-account-parent
region = ap-northeast-1

あとは、いつもどおり、aws コマンドを実行する時に –profile を指定すれば良いだけ。 source_profile で指定した IAM User で Switch Role され、Switch 先の Role での作業が可能となる。シンプル。

$ aws ec2 describe-vpcs --profile=uorat-account-parent
{
    "Vpcs": [
        {
            "VpcId": "vpc-xxxxxxxx",
            "InstanceTenancy": "default",
            "State": "available",
            "DhcpOptionsId": "dopt-xxxxxxxx",
            "CidrBlock": "172.31.0.0/16",
            "IsDefault": true
        }
    ]
}

$ aws ec2 describe-vpcs --profile=uorat-account-child-a
{
    "Vpcs": [
        {
            "VpcId": "vpc-yyyyyyyy",
            "InstanceTenancy": "default",
            "Tags": [
                {
                    "Value": "uorat-child-a",
                    "Key": "Name"
                }
            ],
            "State": "available",
            "DhcpOptionsId": "dopt-yyyyyyyy",
            "CidrBlock": "172.17.0.0/16",
            "IsDefault": false
        },
        {
            "VpcId": "vpc-zzzzzzzz",
            "InstanceTenancy": "default",
            "State": "available",
            "DhcpOptionsId": "dopt-yyyyyyyy",
            "CidrBlock": "172.31.0.0/16",
            "IsDefault": true
        }
    ]
}

この方法は公式ドキュメントにも書いてあるが、意外と知らない人が多かったので書き留めておきました。

参考: IAM ロールの切り替え(AWS Command Line Interface) - AWS Identity and Access Management

Docker for Mac 使っている場合の入力補完

Docker Docker for Mac Mac OS X

f:id:uorat:20170131164247p:plain

tl:dr

Docker.app 内に bash_completion がバンドルされているので、それ使ってね。

背景

たいてい、以下のようなコマンドで completion 設定すると思うが、

curl -L https://raw.githubusercontent.com/docker/docker/master/contrib/completion/bash/docker > /etc/bash_completion.d/docker
curl -L https://raw.githubusercontent.com/docker/compose/$(docker-compose version --short)/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
files=(docker-machine docker-machine-wrapper docker-machine-prompt)
for f in "${files[@]}"; do
  curl -L https://raw.githubusercontent.com/docker/machine/v$(docker-machine --version | tr -ds ',' ' ' | awk 'NR==1{print $(3)}')/contrib/completion/bash/$f.bash > `brew --prefix`/etc/bash_completion.d/$f
done

Docker for Mac の場合どうなんだろと気になって軽く調べた。

方法

またしてもググラビリティが低かったらしく、なかなかピンポイントな情報に出会えなかった。 結論、Docker for Mac の公式ドキュメントに書いてあるとおり、Docker.app 内にバンドルされているので、それを使おう。

Installing bash completion: Get started with Docker for Mac - Docker

If you are using bash completion, such as homebrew bash-completion on Mac bash completion scripts for the following commands may be found inside Docker.app, in the Contents/Resources/etc/ directory:

  • docker
  • docker-machine
  • docker-compose

homebrew で bash_completion 入れていれば、こんな感じで bash_completion.d/ ディレクトリに symlink 貼ってあげれば良い。

ln -s /Applications/Docker.app/Contents/Resources/etc/docker.bash-completion /usr/local/etc/bash_completion.d/docker
ln -s /Applications/Docker.app/Contents/Resources/etc/docker-machine.bash-completion /usr/local/etc/bash_completion.d/docker-machine
ln -s /Applications/Docker.app/Contents/Resources/etc/docker-compose.bash-completion /usr/local/etc/bash_completion.d/docker-compose

実行例

コマンド補完の例

$ docker [TAB]
attach     create     export     import     login      pause      push       run        stack      system     version
build      deploy     help       info       logout     plugin     rename     save       start      tag        volume
commit     diff       history    inspect    logs       port       restart    search     stats      top        wait
container  events     image      kill       network    ps         rm         secret     stop       unpause
cp         exec       images     load       node       pull       rmi        service    swarm      update

パラメーター補完の例

# 以下のコンテナがある状態で

$ docker ps -a
CONTAINER ID        IMAGE                           COMMAND                  CREATED              STATUS              PORTS                NAMES
dbd529912c30        uorat/ecs-demo-php-simple-app   "/usr/sbin/apache2..."   About a minute ago   Up About a minute   0.0.0.0:80->80/tcp   hopeful_kalam

# "docker stop" まで打って TAB 押すと
$ docker stop [TAB]
↓
$ docker stop hopeful_kalam

# もう一つコンテナ立ち上げる
$ docker run -d -p81:80 [TAB]
alpine                                          uorat/ecs-demo-php-simple-app         ubuntu
alpine:latest                                   uorat/ecs-demo-php-simple-app:latest  ubuntu:12.04

$ docker run -d -p81:80 uorat/ecs-demo-php-simple-app
01a8b01975c07667574042bced6ba14d9056f87b43053b46e1abd6a29800fa6f

# 確認
$ docker ps
CONTAINER ID        IMAGE                           COMMAND                  CREATED             STATUS              PORTS                NAMES
01a8b01975c0        uorat/ecs-demo-php-simple-app   "/usr/sbin/apache2..."   2 minutes ago       Up 2 minutes        0.0.0.0:81->80/tcp   elastic_chandrasekhar
dbd529912c30        uorat/ecs-demo-php-simple-app   "/usr/sbin/apache2..."   5 minutes ago       Up 5 minutes        0.0.0.0:80->80/tcp   hopeful_kalam

# "docker stop" まで打って TAB 押すと
$ docker stop [TAB]
elastic_chandrasekhar  hopeful_kalam

# 止める
$ docker stop [TAB]
elastic_chandrasekhar  hopeful_kalam
$ docker stop hopeful_kalam
hopeful_kalam

# 止まってるコンテナを再度立ち上げる
$ docker start [TAB]
↓
$ docker start hopeful_kalam ← ちゃんと止まってるコンテナだけフィルタリングされる

ちゃんちゃん。

Docker for Mac v1.13 でディスクイメージの自動圧縮に対応されたのであげておくべし

Docker Docker for Mac Mac OS X

f:id:uorat:20170131164247p:plain

tl;dr

「Docker for Mac 使ってる場合のimage群の保存場所と掃除方法」というタイトルでブログに書き殴っておこうと思ったら、実は Docker for Mac v1.13 から自動圧縮に対応していたというオチ。

Docker for Mac 使いは 今すぐ v 1.13 に上げよう。

背景

Docker および Amazon ECS 素振り中の身。 数年前に 軽く触った程度であまり詳しくないけど所用でコンテナを本番運用することになりそうなので、浦島太郎状態から抜け出すべくキャッチアップしつつ、ついでに開発機のMac OS X のDocker環境を Docker Toolbox から Docker for Mac に移行していた。

ゴミが散らかっていたのでコンテナやイメージを掃除していたら、なかなか開放されず困ったちゃんだったので色々調べていた。

Where are Docker images?

通常Linux をDocker ホストに使ってる場合は、 /var/lib/docker にイメージが保存される。 けど、Docker for Mac 入れたMac OS Xに /var/lib/docker が見当たらなかったので、気になってファイル漁ってみた。 そもそも Docker コマンドってどこにあるんだろう。

$ which docker
/usr/local/bin/docker
$ ll /usr/local/bin/docker
lrwxr-xr-x  1 uorat  Users  66  1 30 17:01 /usr/local/bin/docker -> /Users/uorat/Library/Group Containers/group.com.docker/bin/docker

$HOME/Library/Group Containers にあるらしい。 $HOME/Library/Containers というディレクトリもあったので、ここを掘り下げて見てみると

$ ll -h ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/
total 1998512
drwxr-xr-x  13 uorat  Users   442B  1 30 17:01 .
drwxr-xr-x  22 uorat  Users   748B  1 30 17:01 ..
-rw-r--r--   1 uorat  Users   976M  1 31 14:04 Docker.qcow2
-rw-r--r--   1 uorat  Users    64K  1 30 17:01 console-ring
-rw-r--r--   1 uorat  Users    64K  1 30 16:45 console-ring.0
-rw-r--r--   1 uorat  Users     3B  1 30 17:01 hypervisor.pid
-rw-r--r--   1 uorat  Users     0B  1 30 16:45 lock
drwxr-xr-x   2 uorat  Users    68B  1 30 16:45 log
-rw-r--r--   1 uorat  Users    17B  1 30 17:01 mac.0
-rw-r--r--   1 uorat  Users    36B  1 30 16:45 nic1.uuid
-rw-r--r--   1 uorat  Users     3B  1 30 17:01 pid
-rw-r--r--   1 uorat  Users    94B  1 30 17:01 syslog
lrwxr-xr-x   1 uorat  Users    12B  1 30 17:01 tty -> /dev/ttys000

更新日やファイルサイズ見る限りなんかそれっぽいのがある。 qcow2 という拡張子見る限り、QEMUのイメージファイルらしい。内部的にQEMU使ってるのか。

フォーラム漁ってみる

Dockerのフォーラム見てみると、似たようなスレッドがあってまさにこのファイルがdefault指定されていると言及されていた。

Where are images stored on Mac OS X? - Docker for Mac - Docker Forums

It depends on the driver being used, but the default is: $HOME/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.qcow2

Try it

ごりごり docker rmi で不要なイメージ消してみる。 元々1GBくらいに育ってたのですっきり。

$ docker images prune
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
alpine              latest              70c557e50ed6        11 months ago       4.8 MB

では、実体のファイル見てみると、

$ ll -h Docker.qcow2
-rw-r--r--  1 uorat  Users   976M  1 31 14:45 Docker.qcow2

減らないうぇーい。

Docker for Mac の仕組み

Mac OS X の xhyve 使って Linux 仮想マシン立ち上げている

QEMUファイルがあることから推察できるとおり、Docker for Mac も所詮は仮想マシン上にContainer立ち上げる仕組みのよう。 昔使ってた Docker Toolbox はVirtualBoxバックグラウンドで使って実現していたわけだけども、Docker for Mac は 内部的には xhyve という Mac OS X の Hypervisor 使って Dockerホスト用のLinux を立ち上げているらしい。

引用: The Docker Platform

DOCKER FOR MAC An integrated, easy-to-deploy environment for building, assembling, and shipping applications from a Mac, Docker for Mac is a native Mac application architected from scratch, with a native user interface and auto-update capability, deeply integrated with OS X native virtualization, Hypervisor Framework, networking and file system, making it faster and more reliable than previous ways of getting Docker on a Mac.

引用: Docker for Mac and Windows Beta: the simplest way to use Docker on your laptop - Docker Blog

Faster and more reliable: no more VirtualBox! The Docker engine is running in an Alpine Linux distribution on top of an xhyve Virtual Machine on Mac OS X or on a Hyper-V VM on Windows, and that VM is managed by the Docker application. You don’t need docker-machine to run Docker for Mac and Windows.

なので、screen tty するとLinuxに入れる。

$ cd ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/
$ screen tty

...

 * Running system containerd ... [ ok ]
 * Running system containers ... binfmt rng-tools [ ok ]
 * Configuring host settings from database ... [ ok ]
 * Setting up proxy port service ... [ ok ]
 * Starting Docker ... [ ok ]
 * Starting chronyd ... [ ok ]
 * Checking system state ...
✓ Drive found: sda
✓ Drive mounted: /dev/sda1 on /var type ext4 (rw,relatime,data=ordered)
✓ Network connected:           inet addr:192.168.65.2  Bcast:192.168.65.7  Mask:255.255.255.248
✓ Process transfused running
✓ Process dockerd running: dockerd --pidfile=/run/docker.pid -H unix:///var/run/docker.sock --swarm-default-advertise-addr=eth0 --userland-proxy-path /usr/bin/slirp-proxy --debug --experimental --storage-driver aufs
✓ Process containerd running: docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --shim docker-containerd-shim --runtime docker-runc --debug
✓ Docker daemon working: 1.13.0
✓ Diagnostics server running: /usr/bin/diagnostics-server -vsock
✓ System containerd server running: /usr/bin/containerd
✓ System containerd working
 * Starting Hyper-V daemon: hv_kvp_daemon ... [ ok ]
 * Starting Hyper-V daemon: hv_vss_daemon ... [ ok ]
 * Adjusting oom killer settings ... [ ok ]

Welcome to Moby

                        ##         .
                  ## ## ##        ==

/ # hostname
moby

/ # cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.5.0
PRETTY_NAME="Alpine Linux v3.5"
HOME_URL="http://alpinelinux.org"
BUG_REPORT_URL="http://bugs.alpinelinux.org"

/ # (Ctl-a + D)

Mac OS XQEMUディスクイメージをリサイズする方法 (従来)

ググってみたら、まさに同じようなことを言及している記事を発見。 ここでは qemu で仮想ディスクをリサイズする方法が紹介されている。KVM触ってた頃を思い出す。

Docker for macでDocker.qcow2というファイルが肥大化する件 | GENDOSU@NET

Github Issue でも同じ案内を見つけた。DockerホストとなるLinuxサーバにログインして /var/tmpfile を0埋めした後で、Mac OS X上の Docker.qcow2 ファイルを qemu-img コマンドで convert しろというもの。

Docker.qcow2 never shrinks - disk space usage leak in docker for mac · Issue #371 · docker/for-mac

試してみたらいつの間にか勝手に圧縮されていた

結論、ブログタイトル & この見出しのような挙動をした。 一応途中までやったこと書き記しておく。

従来の圧縮方法の下準備

まずは brewqemu 入れた。

$ brew install qemu

リサイズする前に、念のため Linux仮想マシン上に無駄なファイルがないか確認。 上の記事に良いこと書いてあるので、ここにも転機させて頂く。

変換をかける前に、考慮する部分があり。通常ディスク上でファイルを削除した場合にはインデックス上で削除したという情報を管理して、実体は消えていない状態ですが、通常の場合だとそこに上書きすることになるので、容量があるという見え方になりますが、仮想ディスクはそのインデックス上では削除されているけれど、実体がある場合も、ディスク容量として消費していることになります。この削除されているけれど実体がある部分を綺麗にしてから変換をすることで、効率よくファイルを小さくする事が出来ます。

で、Docker for Mac の Preferances を変更する。これを機に自動起動 (Start Docker when you log in) を無効にしておいた。使わないのに無駄にデーモン立ち上がってるの嫌いだから使うときだけ起動するようにする。

で、Docker for Mac 再起動。

圧縮しようとしたら…

ここでddコマンドによる0埋めや、qemu-imgコマンドでリサイズかける前に、今一度 ディスクイメージのファイルサイズを測ってみたところ

$ ll -h Docker.qcow2
-rw-r--r--  1 uorat  Users   113M  1 31 15:30 Docker.qcow2

あら、減っとる。。

Docker for Mac v1.13 で qcow2 ファイルの圧縮に対応していた

2017年1月31日時点のstable版 v1.13 で対応したらしい。 上記の Github Issues を読み進めていくと、 compaction に対応する旨が言及されており、無事 v1.13 に組み込まれリリースされていた。

経緯は以下あたりから読み進めてみてほしい。

Docker.qcow2 never shrinks - disk space usage leak in docker for mac · Issue #371 · docker/for-mac

ドキュメントを探してみると、確かに記載あった。ググラビリティが低かったらしく、最初にこれを見つけられなかったのが無念。

How do I reduce the size of Docker.qcow2?: Frequently asked questions (FAQ) - Docker

以下引用する。

In Docker 1.13 there is preliminary support for “TRIM” to non-destructively free space on the host. First free space within the Docker.qcow2 by removing unneeded containers and images with the following commands:

  • docker ps -a: list all containers
  • docker image ls: list all images
  • docker system prune: (new in 1.13): deletes all stopped containers, all volumes not used by at least one container and all images without at least one referring container.

Note the Docker.qcow2 will not shrink in size immediately. In 1.13 a background cron job runs fstrim every 15 minutes. If the space needs to be reclaimed sooner, run this command: docker run –rm -it –privileged –pid=host walkerlee/nsenter -t 1 -m -u -i -n fstrim /var

厳密には即時圧縮というわけではなく、15分毎に走るcron jobで実現しているらしい。 ただ、以前の手法と比べると大きな改善だし、これで十分だと思う。

結論

Docker for Mac v1.13 に上げると幸せになれます。

WebSocket対応した噂のALB (Application Load Balancer) を試してみた

AWS ELB ALB Socket.IO WebSocket Node.js

TL;DR

  • 2016年8月にAWSのLoad Balancerが WebSocketに対応した
  • スムーズすぎて心配になるくらい簡単に導入できる
  • 「うまい、はやい、やすい」ので、導入しない理由はないと思う

ALB Release!

巷では長らく噂になっていた、新しいAWSのLoad Balancer。 先月2016年8月11日についにリリースされた。待望のL7対応だ。

個人的に魅力の一つだったのはWebSocket対応。

従来のELBではWebSocket通信を終端できなかったため、 WebSocket通信の負荷分散/バランシングを行ったりSSL/TLS化する場合、 自前でバランシングする仕組みを実装したり、Nginxを前段に配置するなどちょっとした工夫が必要だった。 AWSのLoad BalancerがWebSocketサポートしてくれるのであれば、この設計構築および運用をアウトソース出来ることになる。ハッピーな未来しか見えない。

ちょうど時間ができたので、遅ればせながら試してみた。

新しいLoad Balancer, ALBとは

至るところで紹介されているので、ここでは簡潔に。

Application Load Balancer、通称ALBは、Elastic Load Balancerのラインナップの一つという位置付けで発表された。 以前のELBは "Classic Load Balancer" という扱いになるらしい。 Management ConsoleのLoad Balancerを開くと、以下のように2つから選択できるようになっている。

f:id:uorat:20160924224519p:plain

主だったUpdateは以下。 魅力盛り沢山!

  • Content-Based Routing (Path Based Routing):
    • URLパスに基いて、リクエストを異なるバックエンドサーバに振り分けすることが出来る
  • Support for Container-Based Applications:
    • ECS (EC2 Container Service) - ELB 連携対応、ポートマッピングやヘルスチェック定義の運用性が大幅改善
  • Better Metrics:
    • ポートやURLパス (ターゲットグループ) 毎のメトリクス、トラフィック(GB)、アクティブコネクション数、コネクションレートなどメトリクスを拡充
  • Support for Additional Protocols & Workloads:
    • WebSocketとHTTP/2 をサポート

引用: Amazon Web Services ブログ (日本語)

引用: AWS Blog (English)

WebSocketとLoadBalancer

これまでのWebSocketアプリケーションの構成

おさらい。

詳しくは以前の記事に記載しているが、従来のELBではWebSocket通信をサポートしていなかったため、 WebSocket通信の負荷分散/バランシングを行うためには、自前でバランシングする仕組みを用意する必要があった。

SSL/TLS化する場合も同様。ELBで終端することが出来ないためアプリケーション側で実装したり、Nginxを前段に配置するなどちょっとした工夫が必要だった。

これからのWebSocketアプリケーションの構成

通常のWeb Applicationと同じ構成が取れる。つまりこういう事だ。

f:id:uorat:20160924224649p:plain

Try it!

百聞は一見にしかず。試してみる。

なお、ALBの登場に伴い、AWSのLoad Balancerの構成要素が変わったので注意。一瞬面食らうが、思想がわかれば大した差ではない。

Make Target Group

今までのバックエンドサーバの設定が Load Balancer から独立し Target Group という要素に分離された。 L7対応ということでContent-based Routingが可能になったので、Load Balancerそのものと、後段のバックエンドサーバの定義の関係性が疎になったイメージ。

ということで、まずバックエンドサーバとTarget Groupを用意する。 バックエンドサーバは、以前使ったNode.js / Socket.IOなサンプルアプリケーションを再利用し、EC2上で稼働させる。

server

package.json

How to run

$ vim app.js
$ vim package.json
$ vim /usr/share/nginx/html/sample.html
$ npm install
$ node app.js

それからTarget Groupを作成する。

基本的に今までのELBのバックエンドサーバの設定箇所と同じ。 ここで設定/作成するTarget Groupを、後ほどALBに振り分けルールとセットで登録することとなる。

  • バックエンドのNode.jsサーバは :3000 で listen しているので、ProtocolはHTTP、ポートは3000を選択する
  • Health Check Pathは 今回は /socket.io/socket.io.js を指定する

f:id:uorat:20160924231855p:plain

まず、これでTarget Groupを作成。

その後、Target GroupにInstanceを登録する。今回はテスト用に2台のEC2 instanceを用意したので、これをTarget Groupとして設定する。

ここまで特にWebSocket特有の設定項目はない。

一点注意点として、Socket.IOベースのアプリケーションを使用している場合は、Target Groupの Attribute を編集してStickness を有効化すること。 つまり、Sticky Sessionを効かせる。

f:id:uorat:20160924225023p:plain

理由は後述する。

Launch ALB

次に、Load Balancerを作成する。

まず、Application Load Balancer を選択する。

f:id:uorat:20160924224519p:plain

ALBの設定画面はいつも通り

  • 名前とScheme (internet-facing か internal ) を決め、ALBのListenerとAZの設定を行う
  • Listner Protocol は Target Groupと同じくHTTP / HTTPS のいずれかを選択すれば良い

f:id:uorat:20160924231919p:plain

HTTPSを選んだ場合は証明書を選択し、続いてSecurityGroupを選択/作成する。

ここまでは従来のELBと同じ。

次にRouting先のTarget Groupを設定する。

先程作成したTarget Groupをルーティング先として指定する。 これがデフォルトのルーティング先となる。

f:id:uorat:20160924225429p:plain

前述したとおり、URLパスに基づいて複数のTarget Groupを登録することも出来る。 複数のサブシステムやマイクロサービスな作りをしているものを一つのLB、ドメインでまとめたり、アプリケーションコードを変えずにALBだけでパスルーティングを返ることが出来たり、 運用が幸せになる可能性を感じる。

設定内容が問題ないことを確認後、ALBを立ち上げる。

これだけ。 WebSocket特有の設定項目は必要ない。

Run!

このLoad Balancer経由でサンプルアプリケーションを稼働させる。

以下の sample.html を手元のPCで作成し、ブラウザで開くとサンプルアプリケーションが立ち上がる。

ブラウザのDeveloper ConsoleなどでHTTP Upgrade (101) やWebSocketの通信状況を確認して、無事接続確立していればOK。

f:id:uorat:20160924225800p:plain

心配になるくらい、驚くほどあっさり動いてしまう。

Socket,IO 利用時の注意点

前述したが、Socket.IOを利用しているWebSocketアプリケーションの場合、Sticknessを有効化しておく必要がある。 これはSocket.IOのハンドシェイク処理の仕様で、接続確立までに行われる幾つかのHTTPリクエストが同一サーバに固定される必要がある。 公式ドキュメントにも言及されている。

Socket.IO — Using multiple nodes

Sticknessが無効のままだと以下のように400エラーや502エラーが発生し、接続確立に失敗する。

f:id:uorat:20160924225829p:plain

この時のResponse Dataを見てみると、前リクエストでSession ID が見つからない旨出力される。

{"code":1,"message":"Session ID unknown"}

シンプルにWebSocketを話すアプリケーションは特にSticknessの有効化は不要だと思うが、 環境やブラウザによってはWebSocketを話せないケースもあるため、両方に対応可能なSocket.IOを利用するケースは多いと思うので、参考までに。

所感

何度も言うが、心配になるくらい、驚くほどあっさり動いてしまう。 簡単なチャットアプリケーションであれば、ALB前段にかませばそれで終了だ。

WebSocket接続用のEndpointの管理が不要となり、DNS Recordのメンテは初回以降不要。 SSL/TLS通信の終端先としても使えるのでReverseProxyを置く必要もなくなる。 Target GroupにAutoScaling Groupの登録もできるようなので、普通のWebアプリケーションサーバと同じ感覚でScalingできるようになる。

WebSocketアプリケーションの作りや規模によってはシャーディングっぽい作りが必要になることもあると思うが、 その際はTarget GroupとPath Rule追加して、同じALBにぶら下げれば良い。

ALBを活用すればランニングコスト、構築や運用にかかる労力ともに減るので、メリットしか感じられない。

がしがし活用していこう。

Nginxを用いたWebSocketサーバのReverseProxy構成及びSSL/TLS接続

Nginx WebSocket Node.js Socket.IO

TL;DR


今更ながら、随分前の作業メモが貯まっていたのと、もう一つの記事のつなぎとするために吐き出しておく。

WebSocket 通信のSSL/TLS通信をさせたりロードバランシングさせたい場合など、WebSocketサーバの前段にReverseProxyを置きたい時は Nginx (v1.3.13 以降) を使おうという話。

なお、AWSのELB (ALB: Application Load Balancer) を使っても実現できるようになったので、AWSで運用しているサービスの場合はALB使ったほうがお手軽省コストメリット大きいと思う。

はじめに


以前Node.js 自身でSSL/TLS通信する方法を記したが、 シングルコアで動作するNode.jsプロセスに、暗号化のオーバーヘッドまで食わせるのは非常にもったいない。 pm2などのNode.js製のクラスタリングツールでマルチプロセス化はできるとはいえ、Node.jsサーバのコンピューティングリソースは本来の責務であるアプリケーションロジックに集中させたい。

Nginx v1.3.13以降 のWebSocket Support

WebSocket接続のハンドシェイク時、HTTP/1.1 101 (Switching Procotols) というステータスコードとHop-by-Hopヘッダを使用してWebSocket通信への切り替えを行う。

2013年02月にリリースされたNginx v1.3.13 で、これらの仕様に対応された。

WebSocket proxying - http://nginx.org/

Since version 1.3.13, nginx implements special mode of operation that allows setting up a tunnel between a client and proxied server if the proxied server returned a response with the code 101 (Switching Protocols), and the client asked for a protocol switch via the "Upgrade" header in a request.

これをReverseProxyとして挟む。

Try it!


ということで試してみる。

Install Nginx

まずはインストール。

NginxのRepositoryを登録し、Nginxをインストールする。 Nginx 1.3.13以降であれば良いので、Latest versionをインストールする。

Ansible Playbookのサンプルを載せておく。

Nginx Configuration

設定方法は公式ドキュメントにあるとおり。

WebSocketのハンドシェイク時に使われる Upgrade ヘッダと Connection ヘッダ は Hop-by-Hop ヘッダ である。 通常のEnd-to-End headerと異なり基本的に一度の転送に対して有効なヘッダなため、ReverseProxyを間に挟んでいる場合これらのheaderはバックエンドサーバまで届かない。

以下、Hop-by-Hop ヘッダに関するRFCのリンク。

RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1

以下、Nginxのリンク。

WebSocket proxying - http://nginx.org/

As noted above, hop-by-hop headers including "Upgrade" and "Connection" are not passed from a client to proxied server, therefore in order for the proxied server to know about the client's intention to switch a protocol to WebSocket, these headers have to be passed explicitly:

Nginxも例外ではないため、WebSocket通信をプロキシする場合は明示的にこれらのヘッダをバックエンドに渡すようにする必要がある。

この設定を入れて、Nginxを起動する。

$ sudo systemctl start nginx.service

なお、後段のNode.jsサーバは、以前使ったサンプルアプリケーションを再利用する。

server

package.json

client

How to run

$ vim app.js
$ vim package.json
$ vim /usr/share/nginx/html/sample.html
$ npm install
$ node app.js

動作確認

ブラウザで動作確認してみる。複数のタブで sample.html を開いてそれぞれで投稿したコメントが同期できればOK。

f:id:uorat:20160919201007p:plain

画像左部のDeveloper Toolsでも分かる通り、101 (Switching Protocols) が通り、WebSocket接続が確立できていることが分かる。

Request / Response Headerは以下。Hop-by-Hopヘッダも期待通りセットされている。

Request URL

ws://your.domain.or.ip/socket.io/?EIO=3&transport=websocket&sid=3_K8sGX3W6bYyXAAAAAe

Request Headers

Accept-Encoding           : gzip, deflate, sdch
Accept-Language           : ja,en-US;q=0.8,en;q=0.6
Connection                : Upgrade
Cookie                    : io=lsIxmwKy6QY8s9ocAAAQ
Host                      : (your domain or IP)
Origin                    : http://your.domain.or.ip
Sec-WebSocket-Extensions  : permessage-deflate; client_max_window_bits
Sec-WebSocket-Key         : aZWrWEnJQsBvp38sAtBP1A==
Sec-WebSocket-Version     : 13
Upgrade                   : websocket

Response Headers

Connection                : upgrade
Date                      : Mon, 19 Sep 2016 07:30:43 GMT
Sec-WebSocket-Accept      : 9wz2i4UrQXQqilnyAtByxzwbwb8=
Sec-WebSocket-Extensions  : permessage-deflate
Server                    : nginx/1.10.1
Upgrade                   : websocket

WebSocket通信を暗号化する


SSL/TLS Termination

あとは、このNginx Reverse Proxy - cient間の通信を暗号化すれば良い。 普通に、サーバ証明書 + 中間CA証明書 と 秘密鍵を配置して ssl on; すれば良いだけ。

Nginx を再起動して、https で sample.html を開き直せばws通信もSSL/TLS化される。

Request URL

wss://your.domain.or.ip/socket.io/?EIO=3&transport=websocket&sid=uLFmikk0IVrjBy8bAAAb

非常に簡単。

HTTP/HTTPSとWebSocket (ws/wss) の両接続を単一のNginxで終端する

上述の Nginx の設定ファイルを見て分かる通り、locationを分けることで単一のNginx かつ同じ 443 portで、通常のHTTP/HTTPSとWebSocket (ws/wss) の両通信を扱うことが出来る。 今回のように両要件を共存で動かす場合や、一つのNginx Reverse Proxy クラスタ、同一ドメインでサービス提供する時などに活用できると思う。

※ステートレスな通信であるHTTP/HTTPSと違い、WebSocketは接続を持続するステートフルな通信となるので、リソースには要注意。

まとめ

Reverse Proxyを挟んでおくと色々と小回りが効き運用上も楽になることが多いので、前記事のように対応するよりもこちらの方がベターだと思う。 餅は餅屋。

ちなみに、2016年8月にAWSのElastic Load Balancerが WebSocketに対応した。ALB (Application Load Balancer) というらしい。 ELBのラインナップに含まれる形で、以前のELBは "Classic" という扱いとなるらしい。

また別に書くが、スムーズすぎて心配になるくらい簡単に導入できるので、AWSで運用している方はALBを使ったほうがメリット大きいと思う。 お手軽で省コスト。

nginx実践入門 (WEB+DB PRESS plus)

nginx実践入門 (WEB+DB PRESS plus)

OS boot/shutdown時に自動でZabbixのホストのステータスを変更する

Zabbix AWS

tl;dr

OS 起動・停止時にZabbixのホストのステータスの有効・無効を切り替えるサンプルプログラムを書いた。

uorat/zabbix-host-controller - Github

前置き

クラウドを使うことのメリットの一つに、リードタイムなくサーバの稼働数を柔軟に制御できる点がある。 例えばAWSの場合、AutoScalingを使ってシステム負荷などに応じてサーバを動的に生成・削除できる。 AutoScalingが要件にあわなければ、あらかじめ作成しておいたEC2に対してAPIでサーバの起動/停止を制御できる。

必要なリソースを必要なタイミングで調達し稼働させることでコストメリットを受けられるし、システムキャパシティを拡げ可用性を上げることができる。

ただ、運用まで見据えると、考慮しておくべきことが幾つか出てくる。 その一つが監視で、動的なサーバの増減、up/downに対して監視をどのように追従させるかがポイントとなる。

増えたものは監視したいし、スケールインしたことにより正常に止まった・消えたものは監視を止めて良い。 検証環境も、営業時間内は監視しておきたいし営業時間外は止めるから監視したくない。 ただし、スケールインしていないにもかかわらず疎通不可になっただとか、プロセス障害だとか、そうした異常なステータスは検知したい。

今回のお題

サーバの稼働数をアプリケーション側で制御するようなシステムがある。 この監視 (Zabbix) の有効・無効を自動で切り替えてみる。 イメージはこちら。

f:id:uorat:20160615114836p:plain

アプローチ

ZabbixはAPIが提供されている。

17. API - Zabbix Documentation 2.4

host.get や host.create, host.update などホストの情報を参照したり、作成・更新できるので、これを活用すれば良い。

※Zabbixの 「メンテナンス」機能は、計画的な起動・停止でないと使いづらいことと、設定後対象のホストが実際にメンテナンスになるまで数十秒程度のタイムラグが発生することから、要件にマッチしないので今回は扱わない。

監視の有効・無効のタイミングは、以下の決めとする。

  • OS boot時: 監視を有効化
  • OS shutdown時: 監視を無効化

kernel panicを起こしたり、プロセス障害が発生したり、突如疎通不可になった場合は検知するが、shutdownが走った時は正常停止とみなし監視を無効化する。 つまり、runlevelに応じて監視を制御できるようにする。

実装

Zabbi APIは仕様が非常にシンプルなので、サードパーティのラッパーは使用せずにそのまま直で実装する。 Zabbix API は以下のような仕組みとなる。

  1. user.login で auth ID
  2. auth ID を使って後続のAPIを実行

軽く書いてみた。ソースは以下。 (CentOS6 / AmazonLinux 動作確認済み)

uorat/zabbix-host-controller - Github

スクリプトは単純明快なので中身の細かい説明は割愛するが、 zabbix_module.py の中で loginやhostidの取得、ステータスの更新を行う関数を定義してあり、 zabbix_enable.py と zabbix_disable.py でこれらの関数を呼び出している。 bin/zabbix_config.py 内にZabbixの接続情報を定義できるようにしてあるので、環境にあわせて変更する。

あとは、起動スクリプトでこれらのスクリプトを実行するようにして、chkconfigでサービス登録すれば良い。 起動スクリプトは example/zabbix-host-controller.init を使えば基本的にそのまま動くはず。 これで、サーバが立ち上がるとZabbixのホストステータスが有効になり、シャットダウンすれば自動で無効化される。

手動実行するとこんな形。

$ /etc/init.d/zabbix-host-controller start
   => host: your-host01, hostid: 11407, status: 1
   => status update : hostid: 11407, result: True
   => host: your-host01, hostid: 11407, status: 0
zabbix-host-control start: [OK]
$ /etc/init.d/zabbix-host-controller stop
   => host: your-host01, hostid: 11407, status: 0
   => status update : hostid: 11407, result: True
   => host: your-host01, hostid: 11407, status: 1
zabbix-host-control stop: [OK]

まとめ

割とシンプルに実現できるので、お試しあれ。 上記のコードは、既にZabbixに登録されているホストの有効/無効しかできないので、 気が向いたら初期登録やテンプレートの設定、スクリーンへの追加なども対応させて、AutoScalingに追従できるようにするかも。