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を使うとコンテナ管理が捗りますよ。