Swarm フィルタ

フィルタとは Docker Swarm スケジューラに対して、ノードを使ってコンテナの作成・実行をするか伝えます。

設定可能なフィルタ

フィルタはノード・フィルタ(node filter)とコンテナ設定フィルタ(container configuration filter)の2種類に分けられます。ノード・フィルタは Docker ホストの特徴、あるいは Docker デーモンの設定によって処理します。コンテナ設定フィルタはコンテナの特徴、あるいはホスト上で利用可能なイメージによって処理します。

各フィルタには名前があります。ノード・フィルタは、以下の2つです。

  • constraint (ノードの制限)
  • health (ノードが正常かどうか)
  • containerslots (ノードでの最大実行コンテナ数)

コンテナ設定フィルタは以下の通りです。

  • affinity (親密さ)
  • dependency (依存関係)
  • port (ポート)

swarm manage コマンドで Swarm マネージャの起動する時、全てのフィルタが指定可能です。もしも Swarm に対して利用可能なフィルタを制限したい場合は、 --filter フラグと名前のサブセットを指定します。

$ swarm manage --filter=health --filter=dependency

注釈

コンテナ設定フィルタに一致するのは全てのコンテナが対象です。フィルタが適用されるのは停止しているコンテナも含みます。コンテナによって使用されているノードを解放するには、ノード上からコンテナを削除する必要があります。

ノード・フィルタ

コンテナ作成時とイメージ構築時に、 constrainthealth フィルタを使い、コンテナをスケジューリングするノード群を選択できます。もし、Swarmクラスタ内のノードが``containerslots``キーと数値をラベルに持っている場合、Swarmは指定された数以上のコンテナを起動しません。

constraint (制限)フィルタを使う

ノード制限(constraint;コンストレイント=制限・制約の意味)は Docker のデフォルトのタグやカスタム・ラベルを参照します。デフォルトのタグとは docker info の情報を元にします。しばし Docker ホストの設定状態に関連付けられます。現在以下の項目をデフォルト・タグとして利用できます。

  • node ノードを参照するための ID もしくは名前
  • storagedriver
  • executiondriver
  • kernelversion
  • operatingsystem

カスタム・ノード・ラベルは docker daemon 起動時に追加できます。実行例:

$ docker daemon --label com.example.environment="production" --label
com.example.storage="ssd"

そして、クラスタ上でコンテナの起動時に、これらのデフォルト・タグかカスタム・ラベルを使って制限(constraint)を指定可能です。Swarm スケジューラはクラスタ上に条件が一致するノードを探し、そこでコンテナを起動します。この手法は、いくつもの実践的な機能になります。

  • ホスト・プロパティを指定した選択( storage=ssd のように、特定のハードウェアにコンテナをスケジュールするため)
  • ノードの基盤に、物理的な場所をタグ付けする( region=us-east のように、指定した場所でコンテナを強制的に実行)
  • 論理的なクラスタの分割( environment=production のように、プロパティの違いによりクラスタを複数のサブクラスタに分割)

ノード制限の例

ノードに対してカスタム・ラベルを指定するには、 docker 起動時に --label オプションのリストを指定します。例として、 node-1storage=ssd ラベルを付けて起動します。

$ docker -d --label storage=ssd
$ swarm join --advertise=192.168.0.42:2375 token://XXXXXXXXXXXXXXXXXX

node-2storage=disk としても起動できます。

$ docker -d --label storage=disk
$ swarm join --advertise=192.168.0.43:2375 token://XXXXXXXXXXXXXXXXXX

ノードがクラスタに登録されたら、Swarm マネージャは個々のタグを取得します。マネージャは新しいコンテナをスケジューリングする時に、ここで取得したタグの情報を使って処理します。

先ほどのサンプルを例に進めましょう。クラスタには node-1node-2 があります。このクラスタ上に MySQL サーバ・コンテナを実行できます。コンテナの実行時、 constraint (制限) を使い、データベースが良い I/O 性能を得られるようにできます。そのためには、フラッシュ・ドライブを持つノードをフィルタします。

$ docker tcp://<manager_ip:manager_port>  run -d -P -e constraint:storage==ssd --name db mysql
f8b693db9cd6

$ docker tcp://<manager_ip:manager_port>  ps
CONTAINER ID        IMAGE               COMMAND             CREATED                  STATUS              PORTS                           NAMES
f8b693db9cd6        mysql:latest        "mysqld"            Less than a second ago   running             192.168.0.42:49178->3306/tcp    node-1/db

この例では、マネージャは全てのノードの中から storage-ssd 制限に一致するノードを探し、そこに対してリソース管理を適用します。ここではホストがフラッシュ上で動いている node-1 のみが選ばれました。

クラスタのフロントエンドとして Nginx の実行をお考えでしょうか。この例では、フロントエンドはディスクのログを記録するだけですので、フラッシュ・ドライブを使いたくないでしょう。

$ docker tcp://<manager_ip:manager_port> run -d -P -e constraint:storage==disk --name frontend nginx
963841b138d8

$ docker tcp://<manager_ip:manager_port> ps
CONTAINER ID        IMAGE               COMMAND             CREATED                  STATUS              PORTS                           NAMES
963841b138d8        nginx:latest        "nginx"             Less than a second ago   running             192.168.0.43:49177->80/tcp      node-2/frontend
f8b693db9cd6        mysql:latest        "mysqld"            Up About a minute        running             192.168.0.42:49178->3306/tcp    node-1/db

スケジューラは storage=disk ラベルを付けて起動済みの node-2 で起動します。

最後に、 docker build の構築時の引数としてもノード制限を利用できます。今度もフラッシュ・ドライブを避けてみましょう。

$ mkdir sinatra
$ cd sinatra
$ echo "FROM ubuntu:14.04" > Dockerfile
$ echo "MAINTAINER Kate Smith <ksmith@example.com>" >> Dockerfile
$ echo "RUN apt-get update && apt-get install -y ruby ruby-dev" >> Dockerfile
$ echo "RUN gem install sinatra" >> Dockerfile
$ docker build --build-arg=constraint:storage==disk -t ouruser/sinatra:v2 .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:14.04
 ---> a5a467fddcb8
Step 2 : MAINTAINER Kate Smith <ksmith@example.com>
 ---> Running in 49e97019dcb8
 ---> de8670dcf80e
Removing intermediate container 49e97019dcb8
Step 3 : RUN apt-get update && apt-get install -y ruby ruby-dev
 ---> Running in 26c9fbc55aeb
 ---> 30681ef95fff
Removing intermediate container 26c9fbc55aeb
Step 4 : RUN gem install sinatra
 ---> Running in 68671d4a17b0
 ---> cd70495a1514
Removing intermediate container 68671d4a17b0
Successfully built cd70495a1514

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
dockerswarm/swarm   manager             8c2c56438951        2 days ago          795.7 MB
ouruser/sinatra     v2                  cd70495a1514        35 seconds ago      318.7 MB
ubuntu              14.04               a5a467fddcb8        11 days ago         187.9 MB

health フィルタを使う

ノード health フィルタは障害の発生したノードにコンテナをスケジュールするのを防ぎます。対象のノードはダウンしているか、クラスタ・ストアとの通信ができないことが考えられます。

containerslots フィルタを使う

Dockerノードに``containerslots``ラベルを与えることができます。

$ docker daemon --label containerslots=3

Swarmはノードで3つのコンテナまで実行しますが、全てのノードが「満載(コンテナ数が最大)」であれば適切なノードが無い事を示すエラーがスローされます。もし、値が整数にキャストできないか、存在しなければコンテナ数に制限は存在しません。

コンテナ・フィルタ

コンテナの作成時、3種類のコンテナ・フィルタを使えます。

  • affinity
  • dependency
  • port

アフィニティ(親密さ)フィルタを使う

アフィニティ(親密さ)フィルタを使えば、コンテナ間を「集めて」作成できます。例えばコンテナを実行する時に、次の3つの親密さを元にして Swarm に対してスケジュールできます。

  • コンテナ名か ID
  • イメージのあるホスト
  • コンテナに適用したカスタム・ラベル

これらのアフィニティ(親密さ)とは、コンテナを同じネットワーク・ノード上で実行することです。それぞれどのノード上で実行しているかどうか、知る必要がありません。

名前アフィニティの例

新しいコンテナを、既存のコンテナ名や ID を元にしてスケジューリングできます。例えば、 frontend という名前のノードで nginx を実行します。

$ docker tcp://<manager_ip:manager_port>  run -d -p 80:80 --name frontend nginx
87c4376856a8

$ docker tcp://<manager_ip:manager_port> ps
CONTAINER ID        IMAGE               COMMAND             CREATED                  STATUS              PORTS                           NAMES
87c4376856a8        nginx:latest        "nginx"             Less than a second ago   running             192.168.0.42:80->80/tcp         node-1/frontend

それから、 -e affinity:container==frontend フラグを使い、2つめのコンテナを frontend の隣にスケジュールします。

$ docker tcp://<manager_ip:manager_port> run -d --name logger -e affinity:container==frontend logger
87c4376856a8

$ docker tcp://<manager_ip:manager_port> ps
CONTAINER ID        IMAGE               COMMAND             CREATED                  STATUS              PORTS                           NAMES
87c4376856a8        nginx:latest        "nginx"             Less than a second ago   running             192.168.0.42:80->80/tcp         node-1/frontend
 963841b138d8        logger:latest       "logger"            Less than a second ago   running                                             node-1/logger

コンテナ名のアフィニティ指定によって、 logger コンテナは frontend コンテナと同じ node-1 コンテナで実行されることになります。 frontend という名前だけでなく、次のように ID を使った指定もできます

docker run -d --name logger -e affinity:container==87c4376856a8

イメージ・アフィニティの例

コンテナを起動する時、特定のイメージをダウンロード済みのノードのみにスケジュールすることができます。例えば、2つのホストに redis イメージをダウンロードし、3つめのホストに mysql イメージをダウンロードしたい場合があるでしょう。

$ docker -H node-1:2375 pull redis
$ docker -H node-2:2375 pull mysql
$ docker -H node-3:2375 pull redis

node-1node-3 のみが redis イメージを持っています。 -e affinity:image==redis フィルタを使い、これらのノード上でスケジュールします。

$ docker tcp://<manager_ip:manager_port> run -d --name redis1 -e affinity:image==redis redis
$ docker tcp://<manager_ip:manager_port> run -d --name redis2 -e affinity:image==redis redis
$ docker tcp://<manager_ip:manager_port> run -d --name redis3 -e affinity:image==redis redis
$ docker tcp://<manager_ip:manager_port> run -d --name redis4 -e affinity:image==redis redis
$ docker tcp://<manager_ip:manager_port> run -d --name redis5 -e affinity:image==redis redis
$ docker tcp://<manager_ip:manager_port> run -d --name redis6 -e affinity:image==redis redis
$ docker tcp://<manager_ip:manager_port> run -d --name redis7 -e affinity:image==redis redis
$ docker tcp://<manager_ip:manager_port> run -d --name redis8 -e affinity:image==redis redis

$ docker tcp://<manager_ip:manager_port> ps
CONTAINER ID        IMAGE               COMMAND             CREATED                  STATUS              PORTS                           NAMES
87c4376856a8        redis:latest        "redis"             Less than a second ago   running                                             node-1/redis1
1212386856a8        redis:latest        "redis"             Less than a second ago   running                                             node-1/redis2
87c4376639a8        redis:latest        "redis"             Less than a second ago   running                                             node-3/redis3
1234376856a8        redis:latest        "redis"             Less than a second ago   running                                             node-1/redis4
86c2136253a8        redis:latest        "redis"             Less than a second ago   running                                             node-3/redis5
87c3236856a8        redis:latest        "redis"             Less than a second ago   running                                             node-3/redis6
87c4376856a8        redis:latest        "redis"             Less than a second ago   running                                             node-3/redis7
963841b138d8        redis:latest        "redis"             Less than a second ago   running                                             node-1/redis8

ここで見えるように、コンテナがスケジュールされるのは redis イメージを持っているノードのみです。イメージ名に加えて、特定のイメージ ID も指定できます。

$ docker images
REPOSITORY                         TAG                       IMAGE ID            CREATED             VIRTUAL SIZE
redis                              latest                    06a1f75304ba        2 days ago          111.1 MB

$ docker tcp://<manager_ip:manager_port> run -d --name redis1 -e affinity:image==06a1f75304ba redis

ラベル・アフィニティの例

ラベル・アフィニティによって、コンテナのラベルで引き寄せてたセットアップが可能です。例えば、 nginx コンテナに com.example.type=frontend ラベルを付けて起動します。

$ docker tcp://<manager_ip:manager_port> run -d -p 80:80 --label com.example.type=frontend nginx
87c4376856a8

$ docker tcp://<manager_ip:manager_port> ps  --filter "label=com.example.type=frontend"
CONTAINER ID        IMAGE               COMMAND             CREATED                  STATUS              PORTS                           NAMES
87c4376856a8        nginx:latest        "nginx"             Less than a second ago   running             192.168.0.42:80->80/tcp         node-1/trusting_yonath

それから、 -e affinity:com.example.type==frontend を使って、 com.example.type==frontend ラベルを持つコンテナの隣にスケジュールします。

$ docker tcp://<manager_ip:manager_port> run -d -e affinity:com.example.type==frontend logger
87c4376856a8

$ docker tcp://<manager_ip:manager_port> ps
CONTAINER ID        IMAGE               COMMAND             CREATED                  STATUS              PORTS                           NAMES
87c4376856a8        nginx:latest        "nginx"             Less than a second ago   running             192.168.0.42:80->80/tcp         node-1/trusting_yonath
963841b138d8        logger:latest       "logger"            Less than a second ago   running                                             node-1/happy_hawking

logger コンテナは、最終的に node-1 に置かれます。これはアフィニティに com.example.type==frontend ラベルを指定しているからです。

dependency フィルタを使う

コンテナの依存関係(dependency)フィルタは、既にスケジューリング済みのコンテナと同じ場所でスケジューリングするという依存関係をもたらします。現時点では、以下の依存関係を宣言できます。

  • --volumes-from=dependency (共有ボリューム)
  • --link=dependency:alias (リンク機能)
  • --net=container:dependency (共有ネットワーク)

Swarm は依存関係のあるコンテナを同じノード上に置こうとします。もしそれができない場合(依存関係のあるコンテナが存在しない場合や、ノードが十分なリソースを持っていない場合)、コンテナの作成を拒否します。

必要であれば、複数の依存関係を組み合わせることもできます。例えば、 --volumes-from=A --net=container:B は、コンテナ AB を同じノード上に置こうとします。しかし、これらのコンテナが別々のノードで動いているなら、Swarm はコンテナのスケジューリングを行いません。

port フィルタを使う

port フィルタが有効であれば、コンテナのポート利用がユニークになるよう設定します。Docker Swarm は対象のポートが利用可能であり、他のコンテナのプロセスにポートが専有されていないノードを選びます。ホスト側にポート番号を割り当てたい場合や、ホスト・ネットワーキング機能を使っている場合は、対象ポートの明示が必要になるかもしれません。

ブリッジ・モードでの例

デフォルトでは、コンテナは Docker のブリッジ・ネットワーク上で動作します。ブリッジ・ネットワーク上で port フィルタを使うには、コンテナを次のように実行します。

$ docker tcp://<manager_ip:manager_port> run -d -p 80:80 nginx
87c4376856a8

$ docker tcp://<manager_ip:manager_port> ps
CONTAINER ID    IMAGE               COMMAND         PORTS                       NAMES
87c4376856a8    nginx:latest        "nginx"         192.168.0.42:80->80/tcp     node-1/prickly_engelbart

Docker Swarm はポート 80 が利用可能であり他のコンテナ・プロセスに専有されていないノードを探します。この例では node-1 にあたります。ポート 80 を使用する他のコンテナを起動しようとしても、Swarm は他のノードを選択します。理由は node-1 では既にポート 80 が使われているからです。

$ docker tcp://<manager_ip:manager_port> run -d -p 80:80 nginx
963841b138d8

$ docker tcp://<manager_ip:manager_port> ps
CONTAINER ID        IMAGE          COMMAND        PORTS                           NAMES
963841b138d8        nginx:latest   "nginx"        192.168.0.43:80->80/tcp         node-2/dreamy_turing
87c4376856a8        nginx:latest   "nginx"        192.168.0.42:80->80/tcp         node-1/prickly_engelbart

同じコマンドを繰り返しますと node-3 が選ばれます。これは node-1node-2 の両方でポート 80 が使用済みのためです。

$ docker tcp://<manager_ip:manager_port> run -d -p 80:80 nginx
963841b138d8

$ docker tcp://<manager_ip:manager_port> ps
CONTAINER ID   IMAGE               COMMAND        PORTS                           NAMES
f8b693db9cd6   nginx:latest        "nginx"        192.168.0.44:80->80/tcp         node-3/stoic_albattani
963841b138d8   nginx:latest        "nginx"        192.168.0.43:80->80/tcp         node-2/dreamy_turing
87c4376856a8   nginx:latest        "nginx"        192.168.0.42:80->80/tcp         node-1/prickly_engelbart

最終的に、Docker Swarm は他のコンテナがポート 80 を要求しても拒否するでしょう。クラスタ上の全てのノードでポートが使えないためです。

$ docker tcp://<manager_ip:manager_port> run -d -p 80:80 nginx
2014/10/29 00:33:20 Error response from daemon: no resources available to schedule container

各ノード中のポート 80 は、各コンテナによって専有されています。これはコンテナ作成時からのものであり、コンテナを削除するとポートは解放されます。コンテナが exited (終了)の状態であれば、まだポートを持っている状態です。もし node-1prickly_engelbart が停止したとしても、ポートの情報は削除されないため、 node-1 上でポート 80 を必要とする他のコンテナの起動を試みても失敗します。nginx インスタンスを起動するには、 prickly_engelbart コンテナを再起動するか、あるいは prickly_engelbart コンテナを削除後に別のコンテナを起動します。

ホスト・ネットワーキング機能とノード・ポート・フィルタを使う

コンテナ実行時に --net=host を指定したら、デフォルトの bridge モードとは違い、 host モードはどのポートも拘束しません。そのため、 host モードでは公開したいポート番号を明示する必要があります。このポート公開には DockerfileEXPOSE 命令を使うか、コマンドラインで --expose を指定します。Swarm は host モードで新しいコンテナを作成しようとする時にも、これらの情報を利用します。

例えば、以下のコマンドは3つのノードのクラスタで nginx を起動します。

$ docker tcp://<manager_ip:manager_port> run -d --expose=80 --net=host nginx
640297cb29a7
$ docker tcp://<manager_ip:manager_port> run -d --expose=80 --net=host nginx
7ecf562b1b3f
$ docker tcp://<manager_ip:manager_port> run -d --expose=80 --net=host nginx
09a92f582bc2

docker ps コマンドを実行してもポートをバインド(拘束)している情報が表示されないのは、全てのノードで host ネットワークを利用しているためです。

$ docker tcp://<manager_ip:manager_port> ps
CONTAINER ID        IMAGE               COMMAND                CREATED                  STATUS              PORTS               NAMES
640297cb29a7        nginx:1             "nginx -g 'daemon of   Less than a second ago   Up 30 seconds                           box3/furious_heisenberg
7ecf562b1b3f        nginx:1             "nginx -g 'daemon of   Less than a second ago   Up 28 seconds                           box2/ecstatic_meitner
09a92f582bc2        nginx:1             "nginx -g 'daemon of   46 seconds ago           Up 27 seconds                           box1/mad_goldstine

4つめのコンテナを起動しようとしても、Swarm は処理を拒否します。

$  docker tcp://<manager_ip:manager_port> run -d --expose=80 --net=host nginx
FATA[0000] Error response from daemon: unable to find a node with port 80/tcp available in the Host mode

しかしながら、例えばポート 81 のように、異なった値のポートをバインドするのであれば、コマンドを実行できます。

$  docker tcp://<manager_ip:manager_port> run -d -p 81:80 nginx:latest
832f42819adc
$  docker tcp://<manager_ip:manager_port> ps
CONTAINER ID        IMAGE               COMMAND                CREATED                  STATUS                  PORTS                                 NAMES
832f42819adc        nginx:1             "nginx -g 'daemon of   Less than a second ago   Up Less than a second   443/tcp, 192.168.136.136:81->80/tcp   box3/thirsty_hawking
640297cb29a7        nginx:1             "nginx -g 'daemon of   8 seconds ago            Up About a minute                                             box3/furious_heisenberg
7ecf562b1b3f        nginx:1             "nginx -g 'daemon of   13 seconds ago           Up About a minute                                             box2/ecstatic_meitner
09a92f582bc2        nginx:1             "nginx -g 'daemon of   About a minute ago       Up About a minute                                             box1/mad_goldstine

フィルタ表現の書き方

ノード constraint やコンテナ affinity フィルタをノードに適用するには、コンテナがフィルタ表現を使うため環境変数の指定が必要です。例:

$ docker tcp://<manager_ip:manager_port> run -d --name redis1 -e affinity:image==~redis redis

表現は次のような記述方式です。

<フィルタ・タイプ>:<キー><演算子><値>

<フィルタ・タイプ>affinityconstraint のキーワードのどちらかです。使いたいフィルタのタイプによって異なります。

<キー> は英数字のパターンであり、先頭はアルファベットかアンダースコアです。 <キー> に相当するのは以下の条件です。

  • container キーワード
  • node キーワード
  • デフォルト・タグ(node 制限)
  • カスタム・メタデータ・ラベル(node あるいは containers)

<オペレータ> (演算子)は ==!= のどちらかです。デフォルトではフィルタ処理はハード・エンフォース(hard enforced:強制)です。指定した表現に一致しなければ、マネージャはコンテナをスケジュールしません。 ~ (チルダ)を使い 「ソフト」表現を作成できます。こちらはフィルタ条件に一致しなくても、スケジューラ自身のストラテジに従ってコンテナを実行します。

<値> は英数時、ドット、ハイフン、アンダースコアと、以下を組み合わせた文字列です。

  • 部分一致、例えば abc*
  • /regexp/ 形式の正規表現。Go 言語の正規表現構文をサポート。

現時点の Swarm は、以下のような命令をサポートしています。

  • constraint:node==node1 は、ノード node1 に一致。
  • constraint:node!=node1 は、node1 をのぞく全てのノードに一致。
  • constraint:region!=us* は、 us が付いているリージョン以外のノードに一致。
  • constraint:node==/node[12]/ は、 node1node2 に一致。
  • constraint:node==/node\d/ は、 node + 10進数の1文字に一致。
  • constraint:node!=/node-[01]/ は、 node-0node-1 以外の全てのノードに一致。
  • constraint:node!=/foo\[bar\]/ は、 foo[var] 以外の全てのノードに一致。
  • constraint:node==/(?i)node1/ は、大文字・小文字を区別しない node1 に一致。そのため、 NoDe1NODE1 も一致する。
  • affinity:image==~redis は、redis に一致する名前のイメージがあるノード上でコンテナを実行。
  • constraint:region==~us* は、*us に一致するリージョンのノードを探す。
  • affinity:container!=~redis* は、 redis* という名前を持つコンテナが動いていないノードで node5 コンテナをスケジュール。

警告

以下 v1.9 用のドキュメント、削除予定

Soft アフィニティ・制約の設定

デフォルトでは、アフィニティと制約は厳密(ハード)に強制されるものです。アフィニティや制約で指定した条件に対応するノードがなければ、コンテナはスケジュールされません。Soft affinities/constrains (ソフト設定)があれば、スケジュールが一致するルールを探そうとします。もし一致しなければ、スケジューラはフィルタを廃棄し、コンテナはスケジューラのストラテジに従ってスケジュールします

アフィニティと制約のソフト設定は ~ で指定します。例えば、次のように指定します。

$ docker run -d --name redis1 -e affinity:image==~redis redis

もし、クラスタにイメージ redis を持つノードが無ければ、スケジューラはアフィニティを破棄し、ストラテジに従ってスケジュールします。

$ docker run -d --name redis2 -e constraint:region==~us* redis

もし、 us リージョンに属すノードがクラスタに無ければ、スケジューラは制約を破棄し、ストラテジに従ってスケジュールします。

$ docker run -d --name redis5 -e affinity:container!=~redis* redis

アフィニティ・フィルタは新しい redis5 コンテナを、指定した redis* の名前を含むコンテナが無いノードにスケジュールします。もしクラスタの各々のノードが redis* コンテナを持っている場合、スケジューラはアフィニティのルールを破棄し、ストラテジに従ってスケジュールします。