時代は Kubernetes ですが、Docker Swarm mode を再調査していみます。
Swarm mode については 1.12 での登場時に調査した(Docker 1.12 の衝撃 [slideshare])際にまだちょっと使うには早いなということで見送ってそれ以降ほとんど調査していませんでした。
サーバーの準備
DigitalOcean で Docker インストール済みの Ubuntu を3台用意します。
for i in $(seq 3); do doctl compute droplet create swarm${i} \ --image docker-16-04 \ --region sgp1 \ --size s-2vcpu-4gb \ --ssh-keys 16797382 \ --enable-private-networking \ --enable-monitoring \ --user-data-file userdata.sh done
root@swarm1:~# ufw status verbose Status: active Logging: on (low) Default: deny (incoming), allow (outgoing), allow (routed) New profiles: skip To Action From -- ------ ---- 22 LIMIT IN Anywhere 2375/tcp ALLOW IN Anywhere 2376/tcp ALLOW IN Anywhere 22 (v6) LIMIT IN Anywhere (v6) 2375/tcp (v6) ALLOW IN Anywhere (v6) 2376/tcp (v6) ALLOW IN Anywhere (v6)
クラスタ管理用の通信で 2377/tcp を、ノード間通信で 7946/tcp, 7946/udp を、オーバーレイ・ネットワークで 4789/udp が使われるため ufw で firewall 設定を行います。
今回使ったイメージでは 2375/tcp, 2376/tcp が全開になっている(でも TCP の docker socket は使わないから開いてても大丈夫かな)のでこれを閉じて、上記のポートを eth1 でだけ開けます(DigitalOcean の eth1 は Private Network ですが、自分のアカウントに閉じられたネットワークではないため注意が必要)。
ufw delete allow 2375/tcp ufw delete allow 2376/tcp ufw allow in on eth1 from any port 2375 proto tcp ufw allow in on eth1 from any port 2376 proto tcp ufw allow in on eth1 from any port 2377 proto tcp ufw allow in on eth1 from any port 7946 proto tcp ufw allow in on eth1 from any port 7946 proto udp ufw allow in on eth1 from any port 4789 proto udp
overlay network 作成時に --opt encrypte
をつけて通信の暗号化を有効にした場合は追加で ESP プロトコルも通るようにする必要があります。
ufw allow from any proto esp
(doctl compute ssh NAME
で便利に ssh できるはずなんだけど Windows では winpty が必要でこれが ANSI エスケープシーケンスの扱いが良くなくて使いものにならないのが残念だ)
Swarm クラスタの作成
まず、docker swarm init
コマンドで初期化します
eth1 だけを listen するように次のようにしました
docker swarm init \ --advertise-addr $(ip a s eth1 | grep 'inet ' | awk '{print $2}' | sed 's/\/.*//') \ --listen-addr $(ip a s eth1 | grep 'inet ' | awk '{print $2}' | sed 's/\/.*//')
root@swarm1:~# docker swarm init \ > --advertise-addr $(ip a s eth1 | grep 'inet ' | awk '{print $2}' | sed 's/\/.*//') \ > --listen-addr $(ip a s eth1 | grep 'inet ' | awk '{print $2}' | sed 's/\/.*//') Swarm initialized: current node (asqtfpeef1ur58dijnnykuwuk) is now a manager. To add a worker to this swarm, run the following command: docker swarm join --token SWMTKN-1-49x7ir3ihge066dretzivcu2gmdbwtys7v6h1yvybx784e5g5v-734ruykh451rsguxdjfhonuxn 10.130.27.207:2377 To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions. root@swarm1:~#
swarm init のオプション一覧は次のようになっています
Usage: docker swarm init [OPTIONS] Initialize a swarm Options: --advertise-addr string Advertised address (format:[:port]) --autolock Enable manager autolocking (requiring an unlock key to start a stopped manager) --availability string Availability of the node ("active"|"pause"|"drain") (default "active") --cert-expiry duration Validity period for node certificates (ns|us|ms|s|m|h) (default 2160h0m0s) --data-path-addr string Address or interface to use for data path traffic (format: ) --dispatcher-heartbeat duration Dispatcher heartbeat period (ns|us|ms|s|m|h) (default 5s) --external-ca external-ca Specifications of one or more certificate signing endpoints --force-new-cluster Force create a new cluster from current state --listen-addr node-addr Listen address (format: [:port]) (default 0.0.0.0:2377) --max-snapshots uint Number of additional Raft snapshots to retain --snapshot-interval uint Number of log entries between Raft snapshots (default 10000) --task-history-limit int Task history retention limit (default 5)
--cert-expiry
のデフォルト 2160h0m0s は90日
worker node の join
docker swarm init
で出力されたコマンドを使って worker node として swarm クラスタに参加させられます。init の時と同じように --advertise-addr
, --listen-addr
root@swarm2:~# docker swarm join \ > --token SWMTKN-1-4h3mm7ekejoqq9vg4axaba8rdu3cisgck84feon7mqmh3kmf2a-3rus2x8h6p3rvti4r27by5wbt \ > --advertise-addr $(ip a s eth1 | grep 'inet ' | awk '{print $2}' | sed 's/\/.*//') \ > --listen-addr $(ip a s eth1 | grep 'inet ' | awk '{print $2}' | sed 's/\/.*//') \ > 10.130.71.24:2377 This node joined a swarm as a worker. root@swarm2:~#
root@swarm1:~# docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS bj8muio9ov1wkkxg7ts1lqozc * swarm1 Ready Active Leader vywjboq8e8pk1buntrulod9fu swarm2 Ready Active r55jic8bg1giuljuv9901lic1 swarm3 Ready Active root@swarm1:~#
root@swarm1:~# docker info | grep -A 5 ^Swarm Swarm: active NodeID: bj8muio9ov1wkkxg7ts1lqozc Is Manager: true ClusterID: udczyrb1xk8pvmehncli8y5o7 Managers: 1 Nodes: 3 root@swarm1:~#
manager ノードを増やすには docker node promote NODE-ANME
とします
root@swarm1:~# docker node promote swarm2 swarm3 Node swarm2 promoted to a manager in the swarm. Node swarm3 promoted to a manager in the swarm. root@swarm1:~#
root@swarm1:~# docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS bj8muio9ov1wkkxg7ts1lqozc * swarm1 Ready Active Leader vywjboq8e8pk1buntrulod9fu swarm2 Ready Active Reachable r55jic8bg1giuljuv9901lic1 swarm3 Ready Active Reachable root@swarm1:~#
MANAGER STATUS が Reachable に変わりました
root@swarm1:~# docker info | grep -A 5 ^Swarm Swarm: active NodeID: bj8muio9ov1wkkxg7ts1lqozc Is Manager: true ClusterID: udczyrb1xk8pvmehncli8y5o7 Managers: 3 Nodes: 3 root@swarm1:~#
Managers が 3 に増えました
node を増やすための token は忘れても docker swarm join-token {worker|manager}
コマンドで確認することができます
Service の作成
Usage: docker service create [OPTIONS] IMAGE [COMMAND] [ARG...] Create a new service Options: --config config Specify configurations to expose to the service --constraint list Placement constraints --container-label list Container labels --credential-spec credential-spec Credential spec for managed service account (Windows only) -d, --detach Exit immediately instead of waiting for the service to converge --dns list Set custom DNS servers --dns-option list Set DNS options --dns-search list Set custom DNS search domains --endpoint-mode string Endpoint mode (vip or dnsrr) (default "vip") --entrypoint command Overwrite the default ENTRYPOINT of the image -e, --env list Set environment variables --env-file list Read in a file of environment variables --generic-resource list User defined resources --group list Set one or more supplementary user groups for the container --health-cmd string Command to run to check health --health-interval duration Time between running the check (ms|s|m|h) --health-retries int Consecutive failures needed to report unhealthy --health-start-period duration Start period for the container to initialize before counting retries towards unstable (ms|s|m|h) --health-timeout duration Maximum time to allow one check to run (ms|s|m|h) --host list Set one or more custom host-to-IP mappings (host:ip) --hostname string Container hostname --isolation string Service container isolation mode -l, --label list Service labels --limit-cpu decimal Limit CPUs --limit-memory bytes Limit Memory --log-driver string Logging driver for service --log-opt list Logging driver options --mode string Service mode (replicated or global) (default "replicated") --mount mount Attach a filesystem mount to the service --name string Service name --network network Network attachments --no-healthcheck Disable any container-specified HEALTHCHECK --no-resolve-image Do not query the registry to resolve image digest and supported platforms --placement-pref pref Add a placement preference -p, --publish port Publish a port as a node port -q, --quiet Suppress progress output --read-only Mount the container's root filesystem as read only --replicas uint Number of tasks --reserve-cpu decimal Reserve CPUs --reserve-memory bytes Reserve Memory --restart-condition string Restart when condition is met ("none"|"on-failure"|"any") (default "any") --restart-delay duration Delay between restart attempts (ns|us|ms|s|m|h) (default 5s) --restart-max-attempts uint Maximum number of restarts before giving up --restart-window duration Window used to evaluate the restart policy (ns|us|ms|s|m|h) --rollback-delay duration Delay between task rollbacks (ns|us|ms|s|m|h) (default 0s) --rollback-failure-action string Action on rollback failure ("pause"|"continue") (default "pause") --rollback-max-failure-ratio float Failure rate to tolerate during a rollback (default 0) --rollback-monitor duration Duration after each task rollback to monitor for failure (ns|us|ms|s|m|h) (default 5s) --rollback-order string Rollback order ("start-first"|"stop-first") (default "stop-first") --rollback-parallelism uint Maximum number of tasks rolled back simultaneously (0 to roll back all at once) (default 1) --secret secret Specify secrets to expose to the service --stop-grace-period duration Time to wait before force killing a container (ns|us|ms|s|m|h) (default 10s) --stop-signal string Signal to stop the container -t, --tty Allocate a pseudo-TTY --update-delay duration Delay between updates (ns|us|ms|s|m|h) (default 0s) --update-failure-action string Action on update failure ("pause"|"continue"|"rollback") (default "pause") --update-max-failure-ratio float Failure rate to tolerate during an update (default 0) --update-monitor duration Duration after each task update to monitor for failure (ns|us|ms|s|m|h) (default 5s) --update-order string Update order ("start-first"|"stop-first") (default "stop-first") --update-parallelism uint Maximum number of tasks updated simultaneously (0 to update all at once) (default 1) -u, --user string Username or UID (format:[: ]) --with-registry-auth Send registry authentication details to swarm agents -w, --workdir string Working directory inside the container
alpine イメージで ping を実行する helloworld というサービスを作ります。実行するコンテナ数は1個。
root@swarm1:~# docker service create --replicas 1 --name helloworld alpine ping docker.com nti2sn57ise7179iewt15bvkz overall progress: 1 out of 1 tasks 1/1: running verify: Service converged root@swarm1:~#
サービスの確認
root@swarm1:~# docker service ls ID NAME MODE REPLICAS IMAGE PORTS nti2sn57ise7 helloworld replicated 1/1 alpine:latest root@swarm1:~#
サービスのコンテナ(タスクと呼ぶらしい)を確認
root@swarm1:~# docker service ps helloworld ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS u2vgsm3uuu2i helloworld.1 alpine:latest swarm1 Running Running about a minute ago root@swarm1:~#
docker ps
ではその node で実行されているコンテナだけが確認できます
root@swarm1:~# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 02ad80ae8ec8 alpine:latest "ping docker.com" 2 minutes ago Up 2 minutes helloworld.1.u2vgsm3uuu2i1f4f70y3f3yhe root@swarm1:~#
service inspect は --pretty
をつけると見やすい!!
root@swarm1:~# docker service inspect --pretty helloworld ID: nti2sn57ise7179iewt15bvkz Name: helloworld Service Mode: Replicated Replicas: 1 Placement: UpdateConfig: Parallelism: 1 On failure: pause Monitoring Period: 5s Max failure ratio: 0 Update order: stop-first RollbackConfig: Parallelism: 1 On failure: pause Monitoring Period: 5s Max failure ratio: 0 Rollback order: stop-first ContainerSpec: Image: alpine:latest@sha256:7b848083f93822dd21b0a2f14a110bd99f6efb4b838d499df6d04a49d0debf8b Args: ping docker.com Resources: Endpoint Mode: vip root@swarm1:~#
Scale の変更
サービスで実行するコンテナ数を増減させられます
root@swarm1:~# docker service scale helloworld=5 helloworld scaled to 5 overall progress: 5 out of 5 tasks 1/5: running 2/5: running 3/5: running 4/5: running 5/5: running verify: Service converged root@swarm1:~#
5つのコンテナに増えました
root@swarm1:~# docker service ps helloworld ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS u2vgsm3uuu2i helloworld.1 alpine:latest swarm1 Running Running 8 minutes ago udthnceo672p helloworld.2 alpine:latest swarm3 Running Running about a minute ago to5h2akq19tl helloworld.3 alpine:latest swarm1 Running Running 59 seconds ago jtnr25puybdb helloworld.4 alpine:latest swarm2 Running Running about a minute ago y8561et8m5e1 helloworld.5 alpine:latest swarm3 Running Running 59 seconds ago root@swarm1:~#
docker ps
ではその node で実行されているコンテナだけが確認できます
root@swarm1:~# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7f828e94f492 alpine:latest "ping docker.com" About a minute ago Up About a minute helloworld.3.to5h2akq19tliyshxwe7enzyc 02ad80ae8ec8 alpine:latest "ping docker.com" 9 minutes ago Up 9 minutes helloworld.1.u2vgsm3uuu2i1f4f70y3f3yhe root@swarm1:~#
Service の削除
root@swarm1:~# docker service rm helloworld helloworld root@swarm1:~#
service はすぐに削除して見えなくなるが container は cleanup されるまで数秒残るため docker ps ではしばらく見えます
Rolling update
Rolling update の確認用に 3 コンテナの redis サービスを作成します。--update-delay
で更新と更新の間の待ち時間を指定します。
root@swarm1:~# docker service create \ > --replicas 3 \ > --name redis \ > --update-delay 10s \ > redis:3.0.6 d97voxnq0jt74a7hlb1749ytt overall progress: 3 out of 3 tasks 1/3: running 2/3: running 3/3: running verify: Service converged root@swarm1:~#
root@swarm1:~# docker service ls ID NAME MODE REPLICAS IMAGE PORTS d97voxnq0jt7 redis replicated 3/3 redis:3.0.6 root@swarm1:~# docker service ps redis ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS po89kjashmcm redis.1 redis:3.0.6 swarm2 Running Running 29 seconds ago dg8cu0lz71t7 redis.2 redis:3.0.6 swarm3 Running Running 29 seconds ago sbs7yrrr3ct2 redis.3 redis:3.0.6 swarm1 Running Running 26 seconds ago root@swarm1:~#
デフォルトでは1コンテナずつ更新するが --update-parallelism
でいくつ同時に更新するかを指定可能。
root@swarm1:~# docker service inspect --pretty redis ID: d97voxnq0jt74a7hlb1749ytt Name: redis Service Mode: Replicated Replicas: 3 Placement: UpdateConfig: Parallelism: 1 Delay: 10s On failure: pause Monitoring Period: 5s Max failure ratio: 0 Update order: stop-first RollbackConfig: Parallelism: 1 On failure: pause Monitoring Period: 5s Max failure ratio: 0 Rollback order: stop-first ContainerSpec: Image: redis:3.0.6@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842 Resources: Endpoint Mode: vip root@swarm1:~#
On failure が pause なので更新に失敗すると更新を中断(pause)します。
Update order, Rollback order が stop-first なのでまず、現在のコンテナを停止して新しいものを起動させます。
中断してしまった更新は docker service update CONTAINER-ID
で再開できます。上記の redis サービスでは docker service update redis
とします。
root@swarm1:~# docker service update --image redis:3.0.7 redis redis overall progress: 3 out of 3 tasks 1/3: running 2/3: running 3/3: running verify: Service converged root@swarm1:~#
root@swarm1:~# docker service ps redis ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS tak46phep0nr redis.1 redis:3.0.7 swarm2 Running Running 45 seconds ago po89kjashmcm \_ redis.1 redis:3.0.6 swarm2 Shutdown Shutdown 52 seconds ago i510owz0oag8 redis.2 redis:3.0.7 swarm3 Running Running about a minute ago dg8cu0lz71t7 \_ redis.2 redis:3.0.6 swarm3 Shutdown Shutdown about a minute ago trnsqcuxgrt9 redis.3 redis:3.0.7 swarm1 Running Running 24 seconds ago sbs7yrrr3ct2 \_ redis.3 redis:3.0.6 swarm1 Shutdown Shutdown 33 seconds ago root@swarm1:~#
Drain node
node をクラスタから外す際には docker node update
で --availability drain
とします。サービスで実行中のコンテナ(タスク)は他の node に移動してくれます。
Usage: docker node update [OPTIONS] NODE Update a node Options: --availability string Availability of the node ("active"|"pause"|"drain") --label-add list Add or update a node label (key=value) --label-rm list Remove a node label if exists --role string Role of the node ("worker"|"manager")
root@swarm1:~# docker node update --availability drain swarm2 swarm2 root@swarm1:~# docker node inspect --pretty swarm2 | head ID: 7h2d9guflr988w7r6eanuml1l Hostname: swarm2 Joined at: 2018-03-12 14:11:26.07479248 +0000 utc Status: State: Ready Availability: Drain Address: 10.130.39.87 Platform: Operating System: linux Architecture: x86_64 root@swarm1:~#
再度 docker node update
で --availability active
にすれば再度タスクが割り当てられるようになります。が、既に他の node で実行中のコンテナが移動されるわけではなく、scale
コマンドや service update
による更新、他の node を drain
にしたり他の node 障害が発生した場合にタスクが移ってきます。
Routing mesh
サービス作成時に --publish
(-p
) オプションを指定すると、全 node の published port へアクセスするとコンテナにルーティングしてくれるようになります。
$ docker service create \ --name my-web \ --publish published=8080,target=80 \ --replicas 2 \ nginx
--publish published=8080,target=80
は -p 8080:80
でも ok
上記のコマンドではどの node の port 8080 にアクセスしても my-web コンテナの port 80 に転送してくれます。複数のコンテナに順に振り分けてくれます(ロードバランス)。アクセスした node 上で当該コンテナが起動してるかどうかに関係なく他の node であっても転送されます。
udp の場合は --publish published=53,target=53,protocol=udp
(-p 53:53/udp
) のように指定します。
--publish
(-p
) は複数指定可能なので複数ポート使うことができます。
mesh routing が不要な場合は --publish published=53,target=53,protocol=udp,mode=host
のように mode=host
を追加します。ただし、この場合はその node でポートがかぶらないように調整が必要となるため注意が必要です。このため通常は --mode global
で各 node でひとつずつ実行するタイプのサービスで使用することになると思われます。
次回
次はもっと実践的なサービスを構築してみようかと思います。