Docker Swarm概要
Docker Engineを単独に使う場合はどちらかというと開発の段階で使う場面が多いと思います。その場合、自らそれぞれのコンテナを管理しないといけないので、管理が大変だと感じることが多いのではないでしょうか?
そこで今回紹介するDocker EngineのSwarm Modeを利用すると、クラスタリングツールがコンテナの管理を行ってくれるのでコンテナ管理がとても楽になります。 具体的に次のようなことがSwarm Modeを利用で可能になります。
- サービスのスケーラビリティの向上や単一障害点の排除。 例えばふたつのコンテナで構成されているサービスにアクセス数が多くなった場合に、簡単にコンテナを増やすことが可能。
- あるコンテナが稼働しているサーバーが落ちた場合に、自動で別のサーバーにコンテナを再作成。
- Docker Engineがインストールされる複数のサーバーでDocker Swarmクラスタを作成。 複数のアプリケーションそれぞれ違うポート番号使えば、共同にDocker Swarmクラスタに動かすことができます。
- Docker Swarmクラスタを使うことによってリソースの節約や、サービスのスケーリング、アプリケーションのローリングアップデートを比較的簡単に実現。
今回は、Goで作成するサンプルアプリケーションをIDCFクラウド上に作成したDocker Swarmクラスタにて動かしてみます。
- Docker Swarm概要
- IDCFクラウド上に作成する仮想マシンのスペック
- Dockerのインストール
- Docker Swarmクラスタを構築
- GoアプリをDocker Swarmにデプロイ
- アプリのイメージをビルド

IDCFクラウド上に作成する仮想マシンのスペック
IDCFクラウド上で3台のCentOS 7.3 仮想マシンを作成します。詳細は下の表に記載しています。 ※IPは動的に割り当てられたIPをそのまま使っています。
| ホスト名 | マシンタイプ | IP | OS |
|---|---|---|---|
| swarm-node-01 | standard.S4 | 10.32.0.68 | CentOS 7.3 |
| swarm-node-02 | standard.S4 | 10.32.0.34 | CentOS 7.3 |
| swarm-node-03 | standard.S4 | 10.32.0.20 | CentOS 7.3 |
Dockerのインストール
Docker Swarmクラスタを構築するにあたって、3台の仮想マシンすべてにDocker Engineをインストールします。現時点で一番新しいDocker 17.03をインストールします。
sudo yum update -y
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # Docker Community EditionのRepositoryをインストール
sudo yum makecache fast # Repositoryのメタデータが使えるように
sudo yum info docker-ce
sudo yum install docker-ce -y
sudo groupadd docker
sudo gpasswd -a ${USER} docker # 現在のユーザーがdocker コマンドを使えるように
sudo systemctl enable docker
sudo systemctl start docker # Docker Engineを起動する
Docker Swarmクラスタを構築
まずswarm-node-01でDocker Swarmクラスタを初期化をします。初期化を行うとswarm-node-01がManagerになります。
[deploy@swarm-node-01 ~]$ docker swarm init --advertise-addr 10.32.0.68
Swarm initialized: current node (ynyqekkutkbj3td54e7tw9rpt) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join \
--token SWMTKN-1-0ng5lwnm042158d003x0a2g78lz9263rs0upid8t3qevse53ua-cv57yx0i5spgh3j5u65o3nbhi \
10.32.0.68:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
ynyqekkutkbj3td54e7tw9rpt * swarm-node-01 Ready Active Leader
残りの2台の仮想マシンをクラスタにjoinさせるため、上記コマンドが提示してくれたdocker swarm joinコマンドを入力します。これで2台の仮想マシンがWorkerとしてクラスタにJoinしました。
[deploy@swarm-node-02 ~]$ docker swarm join \ > --token SWMTKN-1-0ng5lwnm042158d003x0a2g78lz9263rs0upid8t3qevse53ua-cv57yx0i5spgh3j5u65o3nbhi \ > 10.32.0.68:2377 This node joined a swarm as a worker. [deploy@swarm-node-03 ~]$ docker swarm join \ > --token SWMTKN-1-0ng5lwnm042158d003x0a2g78lz9263rs0upid8t3qevse53ua-cv57yx0i5spgh3j5u65o3nbhi \ > 10.32.0.68:2377 This node joined a swarm as a worker.
クラスタ中のノードの状況はdocker nodeコマンドで調べることができます。
$ docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS 1lqadobvtsp0yrc3s9bp9lgh8 swarm-node-03 Ready Active vrk6bserls04hj9rrrn8zpv9t swarm-node-02 Ready Active ynyqekkutkbj3td54e7tw9rpt * swarm-node-01 Ready Active Leader
さらにノードごとの詳細はdocker node inspectコマンドで調べることができます。
$ docker node inspect swarm-node-02
例としてNginxサービスをDocker Swarmクラスタに作成してみます。コンテナ自体は80番ポートにEXPOSEされています。サービスを作る際に仮想マシンの8080番ポートとコンテナの80番ポートをひも付けて公開します。 これで、外部から仮想マシンの8080番ポートにアクセスするとNginxサービスにフォワードされるようになります。
Docker Swarmモードにはrouting mesh機能があり、すべてのノードがリクエストを受け付けすることができます。ポート番号に基づいて、適切にリクエストをサービスにフォワードしてくれます。
[deploy@swarm-node-01 ~]$ docker service create --name test-nginx --replicas 2 -p 8080:80 nginx:1.10 # コンテナのレプリケーションを2にする t929h1hugo7hk8es4oqqgqzo1 [deploy@swarm-node-01 ~]$ curl 10.32.0.68:8080 # クラスタのどのノードにアクセスしてもレスポンスが返ってくる [deploy@swarm-node-01 ~]$ curl 10.32.0.34:8080 [deploy@swarm-node-01 ~]$ curl 10.32.0.20:8080
test-nginxサービスの詳細を見てみると、2つのコンテナがswarm-node-01とswarm-node-02の2台の仮想マシン上で動いています。test-nginxサービス自身にVirtual IP(10.255.0.6)が割り当てられています。
[deploy@swarm-node-01 ~]$ docker service ps test-nginx # サービスは2つのレプリケーションで構成されている
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
x6cz28kimuzx test-nginx.1 nginx:1.10 swarm-node-02 Running Running 24 seconds ago
n47lcgfvmm6w test-nginx.2 nginx:1.10 swarm-node-01 Running Running 24 seconds ago
[deploy@swarm-node-01 ~]$ docker service inspect --format="{{json .Endpoint.VirtualIPs}}" test-nginx # Virtual IPを取得
[{"NetworkID":"mifrgvabwccmkxok88ouf1z3q","Addr":"10.255.0.6/16"}]
swarm-node-01とswarm-node-02で動いているコンテナはdocker inspectコマンドでIP アドレスが割り当てられていることが確認できます。
docker psコマンドでコンテナ名の取得をし、docker inspectコマンドでIPの確認をします。
[deploy@swarm-node-01 ~]$ docker ps # swarm-node-01に動いているコンテナの名前を取得
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7e7b916c51ec nginx@sha256:6202beb06ea61f44179e02ca965e8e13b961d12640101fca213efbfd145d7575 "nginx -g 'daemon ..." 10 minutes ago Up 10 minutes 80/tcp, 443/tcp test-nginx.2.n47lcgfvmm6w0gnl28khfc95k
bash:swarm-node-02
[deploy@swarm-node-02 ~]$ docker ps # swarm-node-02に動いているコンテナの名前を取得
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6ba7fc790572 nginx@sha256:6202beb06ea61f44179e02ca965e8e13b961d12640101fca213efbfd145d7575 "nginx -g 'daemon ..." 10 minutes ago Up 10 minutes 80/tcp, 443/tcp test-nginx.1.x6cz28kimuzxisopfcogaewhr
[deploy@swarm-node-01 ~]$ docker inspect test-nginx.2.n47lcgfvmm6w0gnl28khfc95k --format="{{json .NetworkSettings.Networks.ingress.IPAddress}}" # コンテナ test-nginx.2のIPを取得
"10.255.0.8"
[deploy@swarm-node-02 ~]$ docker inspect test-nginx.1.x6cz28kimuzxisopfcogaewhr --format="{{json .NetworkSettings.Networks.ingress.IPAddress}}" # コンテナ test-nginx.1のIPを取得
"10.255.0.7"
test-nginxサービスの2つのコンテナは別々の仮想マシンにありますが、Overlay Networkを通して、同じネットワークにあるように通信できています。各コンテナからサービスのVirtual IPにもアクセスできます。
[deploy@swarm-node-01 ~]$ docker exec -it test-nginx.2.n47lcgfvmm6w0gnl28khfc95k /bin/bash root@7e7b916c51ec:/# ping 10.255.0.7 # もうひとつの別のコンテナをPingすることができる ... 64 bytes from 10.255.0.7: icmp_seq=0 ttl=64 time=0.298 ms ... root@7e7b916c51ec:/# apt-get update root@7e7b916c51ec:/# apt-get install curl root@7e7b916c51ec:/# curl 10.255.0.6 # サービスのVirtual IPにもアクセスできます。 ... <p><em>Thank you for using nginx.</em></p> ...
Nginxのイメージ1.10から1.11へアップデートしたい場合はサービスをアップデートします。イメージタグの変更以外にも、いろいろなオプションを使うことができます。たとえば、-limit-memoryオプションを使うとコンテナの使用できるメモリが制限されます。コンテナが実際使用しているメモリの量はctopコマンドで簡単に確認できます。また、--update-delayオプションを使うことによって、コンテナは10秒間隔で仮想マシンごとにアップデートしていきます。
[deploy@swarm-node-01 ~]$ docker service update test-nginx --image nginx:1.11 --limit-memory 2G --update-delay 10s
Docker Swarmクラスタのノードは2種類に分けられています。ひとつはManager、もうひとつはWorkerです。Managerノードはクラスタのステータスやスケジュールリングなどを管理しています。今回3台の仮想マシンでクラスタを組んでいるため、すべてのノードをManagerにすることをおすすめします。すべてのノードをManagerにすることで、1台のManagerが落ちても、クラスタのスケジューリング管理に問題が出なくなります。。
それではswarm-node-02とswarm-node-03をManagerにしていきます。その後、1台の1仮想マシンのDocker Engineを停止して(systemctl stop docker)、そこに動いているコンテナは別の仮想マシンに作成されるかを見てみましょう。
[deploy@swarm-node-01 ~]$ docker node update --role manager swarm-node-02 [deploy@swarm-node-01 ~]$ docker node update --role manager swarm-node-03
GoアプリをDocker Swarmにデプロイ
今回はGoで作成したサンプルのHTTPSアプリケーションを、Docker Swarmにデプロイしてみます。
ローカルの開発環境でアプリケーションを作成したので、手順を紹介します。
まずは、HTTPS通信のアプリケーションを作るのでサーバーの秘密鍵server.keyと証明書server.crtを作成します。
$ openssl genrsa -out server.key 2048 $ openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
つぎに /tmp/play.goファイルを作成します。
package main import ( "log" "net/http" "os" ) func greet(w http.ResponseWriter, req *http.Request) { log.Println("Hey there!") // app.logに書き込む w.Write([]byte("Hey there!")) } func main() { file, err := os.OpenFile("app.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { log.Fatal("failed to open file :", err.Error()) } log.SetOutput(file) http.HandleFunc("/greet", greet) // greet関数が実行される err = http.ListenAndServeTLS(":7443", "server.crt", "server.key", nil) if err != nil { log.Fatal("ListenAndServe: ", err) } }
go build -o play && ./playコマンドを実行して、 ブラウザからhttps://localhost:8443/greetにアクセスするとHi there!というレスポンスが返ってきます。
Docker Swarmにデプロイする際にはLinuxでアプリを動かすことになるため、GoのCross compilation機能を利用してビルドします。
$ GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -a -ldflags '-s' -installsuffix cgo -o play play.go
アプリのイメージをビルド
この簡単なアプリケーションをDocker Swarmにデプロイしてみますが、バイナリファイルを実行するだけで数百MBのCentOSイメージを使う必要がありません。
できる限り不要なファイル削減したいので、アプリのイメージサイズを小さくして(10Mぐらい)、scratchイメージをベースに作成します。
scratchイメージ自体のサイズは0バイトで、Shellもありません。ですのでコンテナ立ち上げても、docker exec -it <container-id> /bin/bashで入れません。
HTTPS通信なので、Trust Storeルート証明書ca-certificates.crtが必要となるので Mozillaのデータをダウンロードします。
$ curl -o ca-certificates.crt https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt
ログ収集するときに時間が出力されるので、タイムゾーンの情報も必要です。zoneinfo.tar.gzは仮想マシンにログインして取得します。
$ tar cfz zoneinfo.tar.gz /usr/share/zoneinfo
/tmp/Dockerfileを作成します。内容は次の通りで、必要な情報は証明書、タイムゾーン情報、鍵、コンパイルされたバイナリファイルです。
FROM scratch ADD ca-certificates.crt /etc/ssl/certs/ ADD zoneinfo.tar.gz / ADD server.crt / ADD server.key / ADD play / WORKDIR / CMD ["/play"]
イメージをビルドした後にDocker HubにPushします。
$ docker build -t wzj8698/go-swarm:1.0 . $ docker login # PushするまえにDocker Hubにログインする必要がある Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one. Username: wzj8698 Password: Login Succeeded $ docker push wzj8698/go-swarm:1.0 The push refers to a repository [docker.io/wzj8698/go-swarm] 7de27f392ae7: Pushed f6dde78f2228: Pushed 3d99cecfd591: Pushed 1.0: digest: sha256:53ab1aa6295da87ee303041fc161e66649c9839789c712a3c26b1fa881ea2071 size: 948
それでは、ローカル環境から、仮想マシン環境へログインしてください。
これからデプロイに入ります。3台の仮想マシンからwzj8698/go-swarm:1.0をPullしておきます。docker service createコマンドでデプロイし、--mountオプションも一緒に使うため、3台の仮想マシンにあらかじめログファイルapp.logを作成しておく必要があります。--mountオプションを使うことで、仮想マシン側のファイルをコンテナ内のファイルと結びつけることができます。これで、仮想マシン側でもログを確認できるようになります。
[deploy@swarm-node-01 ~]$ docker pull wzj8698/go-swarm:1.0 [deploy@swarm-node-02 ~]$ docker pull wzj8698/go-swarm:1.0 [deploy@swarm-node-03 ~]$ docker pull wzj8698/go-swarm:1.0 [deploy@swarm-node-01 ~]$ cd && touch app.log [deploy@swarm-node-02 ~]$ cd && touch app.log [deploy@swarm-node-03 ~]$ cd && touch app.log [deploy@swarm-node-01 ~]$ docker service create --name go-swarm --replicas 2 -p 7443:7443 --mount type=bind,source=/home/deploy/app.log,destination=/app.log wzj8698/go-swarm:1.0
これで外から7443番ポートにアクセスできるようになっています。Docker Swarm 上でアプリが稼働している状態になりました。
今回は Go のアプリケーションをDocker Swarm 上にて動かしていますが、ぜひ自分で作成したアプリケーションで試してみてください。
いろいろなコンテナをたくさん管理する場合は、Docker Swarmを使うとコンテナ管理が捗りますよ。