Docker swarmを試してみた¶
TD; LR¶
docker swarmは気軽に使えてとても良いが、実運用は自分でちゃんと試してからの方がよさげ。
まえがき¶
Dockerの1.12.0からDocker swarmがdockerと統合され、クラスタ環境でのDockerが簡単に使えるようになりました。 この記事は夏休みの自由研究として、このDocker swarm modeについて調べてみた記録です。
長いです。細かいです。前提をいろいろ飛ばしています。全部読むことはおすすめしません。
間違っている点は指摘していただけると助かります。
リポジトリ¶
- この文章に関するリポジトリ
- docker engine: docker engineのCLI的なやつ。
- swarmkit: この中にswarmの実際の中身が入っている
- swarm: 1.12までのswarm実装。今回は対象外
docker swarm mode¶
docker swarm modeは、複数のdocker hostを束ねて使えるようになるモードです。従来はdocker swarmとして別物でしたが、1.12からdocker engineに統合されました。
とかいう概要はばさっと飛ばします。以下は用語やコマンドのメモです。
登場人物¶
- manager node
管理ノード。3〜7がmanager nodeの最適個数
- worker node
task実行ノード
- node
一つのdocker engineが動いているサーバー
- service
複数のdocker engineが協調して、一つのportでサービスを提供する 一つのswarm clusterは複数のserviceを持てる
使いかた¶
swarm
docker swarm init
swarm clustrを初期化します。つまり、このコマンドを実行したdocker hostが一番最初のmanager nodeとなります。
docker swarm join
指定したmanager nodeが管理するswarm clusterにjoinします。 --token でswarm cluster tokenを指定します。manager tokenを指定すればmanagerとして、worker tokenを指定すればworkerとしてjoinします。あるいは --manager で明示的にmanagerとしてjoinさせることもできます。
docker swarm leave
clusterから抜けます
node
docker node ls
nodeの状態を見ます
docker node ps
taskの状態を見ます
docker node update
nodeをupdate
docker node demote / docker node promote
workerに降格(demote) / managerに昇格(promote)
service
docker service create
serviceを作成
docker service ls
serviceの状態を見ます
docker service ps
taskの状態を見ます
docker service update
rolling updateをします
network
docker network create
overray networkを作成
今回使うプロセス¶
今回実行するプロセスは以下の通り。コードは こちら 。
package main
import (
"fmt"
"net/http"
"strings"
"time"
)
var wait = time.Duration(1 * time.Second)
func handler(w http.ResponseWriter, r *http.Request) {
rec := time.Now()
time.Sleep(wait)
rep := time.Now()
s := []string{
rec.Format(time.RFC3339Nano),
rep.Format(time.RFC3339Nano),
}
fmt.Fprintf(w, strings.Join(s, ","))
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil) // fixed port num
}
単に1秒待ってからリクエスト時とリプライ時の時刻をCSVを8080ポートで返しているだけ。1秒をblockingして待つという最悪な処理なのです。
今回buildはCircleCIに任せてあって、tar.gzを作ってあるので、各nodeでは以下のようにimportすればいい。tagは適当。
$ docker import https://<circleci artifact URL>/docker-swarm-test.tar.gz docker-swarm-test:1
ヒント
golangはglibcなどが必要なく1バイナリで実行できるので、Dockerfileとか別に要らなくてtar.gzで十分。linuxでnetを使うとdynamic linkになる件は Go 1.4でstatic binaryを作成する や golangで書いたアプリケーションのstatic link化 をみてください。
$ docker service create --name web --replicas 3 --publish 8080:8080 docker-swarm-test:1 "/docker-swarm-test"
$ docker service ps web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR
18c1hxqoy3gkaavwun43hyczw web.1 docker-swarm-test:1 worker-2 Running Running 3 minutes ago
827sjn1t4nrj7r4c0eujix2it web.2 docker-swarm-test:1 manager-1 Running Running 3 minutes ago
2xqzzwf2bte3ibj2ekccrt6nv web.3 docker-swarm-test:1 worker-3 Running Running 3 minutes ago
この状態で、コンテナが動いている worker-2,3, manager-1に対して、curlでGETすると返してくれます。それ以外に、コンテナが動いていないworker-1に対してcurlで聞いても、きちんと答えてくれます。これは、中でリクエストが転送されているからです。
rolling update¶
swarm clusterで --publish を付けてserviceを提供すると、 一つのport
で複数のnodeに対してリクエストを割り振るload balancingをやってくれます。
従って、今までdockerでportが動的に変わったり、一つのnodeで同じport番号を使ってしまったりするのが問題になる場合がありましたが、その問題は生じません。
また、swarm cluster内でLoad balancingを行ってくれるので、rolling updateも容易です。
% sudo docker service update --image "docker-swarm-test:1" web
というわけで、試してみました。先ほどのプロセスは1秒待つので、下手に切るとリクエストを取りこぼすはずです。
ツールにはabを使います。今回は処理能力ではなく、リクエストを取りこぼさないかどうかのテストなので、abで十分と判断しました。
% ab -rid -c 10 -n 500 http://45.76.98.219:8080/
Concurrency Level: 10
Time taken for tests: 50.146 seconds
Complete requests: 500
Failed requests: 8
(Connect: 0, Receive: 4, Length: 0, Exceptions: 4)
ということで、取りこぼしてしまいました。残念ですね。 --update-delay は次のコンテナを起動するまでの時間なので関係がなさそうです。 --restart-delay も組み合わせてみましたがだめでした。nodeのステータスをdrainに手動で変更していけばうまく動くかもしれませんが、手間がかかるので試してません。
調べてみると、この辺りが関連してそうです。
次のpatch releaseで修正されるとのこと。ちょっとまだlibnetworkまで調査が足りてないのでこれで本当に直るのかは分かりませんが、今はまだ本番環境上で使うのは早そうです。
というより、nginxとか使え¶
というより、そもそもingress overlay networkは 内部で使う用途で、外部サービスに公開する用途ではない 、ということのようです。外部に公開する場合は、nginxなりを使って後述のDNS service discoveryに従って使うコンテナを決定しろ、とのことのようです。
この辺りはこれからもうちょっと調べないといけない感じです。
network¶
docker network create でnetworkを作成します。あとから docker service update --network-add でnetworkを追加しようとしたら
Error response from daemon: rpc error: code = 2 desc = changing network in service is not supported
と怒られたのでserviceを作り直します。
docker service create --replicas 3 --name web --network webnet ...
その後、shellとしてalpineを立ち上げます。
$ docker service create --name shell --network webnet alpine sleep 3000
$ sudo docker service ls
ID NAME REPLICAS IMAGE COMMAND
1f9jj2izi9gr web 3/3 docker-swarm-test:1 /docker-swarm-test
expgfyb6yadu my-busybox 1/1 busybox sleep 3000
execで中に入ってnslookupで同じnetworkに属している web serviceをDNSで探します。
$ docker exec -it shell.1.3x69i44r6elwtu02f1nukdm2v /bin/sh
/ # nslookup web
Name: web
Address 1: 10.0.0.2
/ # nslookup tasks.web
Name: tasks.web
Address 1: 10.0.0.5 web.3.8y9qbba8eknegorxxpqchve76.webnet
Address 2: 10.0.0.4 web.2.ccia90n3f4d2sr96m2mqa27v3.webnet
Address 3: 10.0.0.3 web.1.44s7lqtil2mk4g47ls5974iwp.webnet
web つまりservice名を聞けばVIPを、 tasks.web を聞けば各nodeを直接DNS RoundRobinで答えてくれます。
このように、同じnetworkに属しさえすれば、他のServiceを名前で引くことができるので、コンテナ間の連携がしやすいと思います。
プロトコル¶
raft¶
docker swarmでは、複数のmanager node間でのLeader選出には Raft consensus を使っています。raftの実装はetcdのraft libraryです。 docker node ls でどのManagerがLeaderかが分かります。
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
5g8it81ysdb3lu4d9jhghyay3 worker-3 Ready Active
6td07yz5uioon7jycd15wf0e8 * manager-1 Ready Active Leader
91t9bc366rrne19j37d0uto0x worker-1 Ready Active
b6em4f475u884jpoyrbeubm45 worker-2 Ready Active
raftですので、きちんとした耐故障性を持つためにはmanager nodeは最低限3台必要ですし、3台中2台が落ちたらLeader選出ができなくなります。この場合、docker swarmでは、新規のtaskを受け付けられない状態になります。
heat beat¶
swarm node間は heatbeat で死活監視をしています。heatbeatは通常 5秒間隔 ですが、docker swarm init時に --dispatcher-heartbeat duration で指定することも出来ます。死活監視の結果はgossipで分配されます。
疑問¶
serviceを消したらcontainerはどうなるの?¶
docker service rm でserviceを消すと、containerも全部消えます。消えるまでは時間がかかるので注意
worker node以上のtaskを振られたら?¶
nodeが3つしかない場合に、 docker swarm scale web=10とかしたらどうなるか?
答えは一つのnodeに複数のコンテナが立ち上がります。
podの概念¶
なさそう。 serviceを作成するときに --constraint で配置制約として affinity を使うとかなのかな。
あとがき¶
もはやコンテナ技術そのものはどうでも良くて、Kubernetesなどの複数Node管理が重要だと個人的には思っています。Docker swarm自体は今までもあったけれども、それをDocker Engineに統合することで、コンテナだけではなくその上の管理も事実上の標準を取っていくぞという意気込みが感じられます。しかも、kubernetesは使いはじめるまでが大変ですが、その手間がほぼないため、優位と感じます。
swarm clusterを組むのは簡単ですし、cluster自体はとても安定しているように思いました。もちろん、split brainなど凝ったテストをしていないのでなんともですが、raft関係はetcdのを使っていますので安定しているのではないかと思っています。
ただ、networkについてはまだ不安定なようで、serviceを作ったり消したりnetworkを作ったり消したりしていると、名前が引けなくなったりしました(詳細は追っていません)。
networkやgracefulなど、まだこなれていない点はありますが、今後Docker swarmは普及していくのではないかな、と思います。
Comments
comments powered by Disqus