Docker Swarm + Compose でマルチホスト環境でoverlay networkを利用したコンテナクラスタを構築する
モチベーション
Docker Composeで複数コンテナを管理するでは、複数コンテナからなるシステムをDocker Composeで一括管理可能にした。また、Docker Swarmで複数Dockerホストからクラスタを作るでは、複数のDockerホストからなるコンテナクラスタをDocker Swarmによって実現した。しかし、記事のようにディスカバリサービスとしてDockerHubを利用するのは本番環境では考えられない。また、コンテナ間通信では非推奨のlinkを用いていた。本記事では、これらをより本番での運用に近づけるために、ディスカバリサービスにConsulを、コンテナ間通信にoverlay networkを用いてマルチホストコンテナクラスタを構築する。
クラスタ全体構成
コンテナクラスタの構築手順
Consulを使ったDocker Swarmクラスタの構築手順をさらっとおさらいする。Consulを起動するDockerホストにて以下を実行する。
➤ docker run --name consul -d \ -p "8500:8500" \ -h "consul" \ --restart=always \ progrium/consul -server -bootstrap
Swarmクラスタを構築する。まずは、Swarm Masterから。
➤ docker-machine create -d virtualbox \ --swarm --swarm-master \ --swarm-discovery="consul://$(docker-machine ip consul):8500" \ --engine-opt="cluster-store=consul://$(docker-machine ip consul):8500" \ --engine-opt="cluster-advertise=eth1:2376" swarm-master
つづいて、Swarm Nodeを構築する。
➤ docker-machine create -d virtualbox \ --swarm --swarm-discovery="consul://$(docker-machine ip consul):8500" \ --engine-opt="cluster-store=consul://$(docker-machine ip consul):8500" \ --engine-opt="cluster-advertise=eth1:2376" swarm-node-01 ➤ docker-machine create -d virtualbox \ --swarm --swarm-discovery="consul://$(docker-machine ip consul):8500" \ --engine-opt="cluster-store=consul://$(docker-machine ip consul):8500" \ --engine-opt="cluster-advertise=eth1:2376" swarm-node-02
ここまでの作業をVagrantで自動構築するには以下のファイルを使う。(正確には利用するドライバを変更しているので多少異なる)
Vagrant.configure(2) do |config|
config.vm.define "consul" do |consul|
consul.vm.provider :virtualbox do |virtualbox, override|
virtualbox.name = "consul"
override.vm.boot_timeout = 7200
override.vm.box = "takanabe/ubuntu1504"
override.vm.network "private_network", type: "dhcp"
end
consul.vm.provision "trigger" do |trigger|
trigger.fire do
puts "======= IN TRIGGER SECTION !!======="
puts "Creating Consul ..."
`docker-machine status #{@machine.name}`
if $?.exitstatus != 0
if @machine.provider_name == :virtualbox
ip = `vagrant ssh #{@machine.name} -c "ip addr show dev eth1 | grep -w inet"`
.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/)[0]
port = 22
else
ip = @machine.ssh_info[:host]
port = @machine.ssh_info[:port]
end
run "docker-machine -D create -d generic \
--generic-ip-address #{ip} \
--generic-ssh-key #{@machine.ssh_info[:private_key_path][0]} \
--generic-ssh-port #{port} \
--generic-ssh-user #{@machine.ssh_info[:username]} \
#{@machine.name}"
consul_config = `docker-machine config #{@machine.name}`
run "docker #{consul_config} run --name consul -d \
--publish 8500:8500 \
--hostname consul \
--restart=always \
progrium/consul -server -bootstrap"
end
end
end
consul.trigger.before :destroy do
`docker-machine status #{@machine.name}`
if $?.exitstatus == 0
run "docker-machine rm #{@machine.name}"
end
end
end #consul end
config.vm.define "swarm-master" do |master|
master.vm.provider :virtualbox do |virtualbox, override|
virtualbox.name = "swarm-master"
override.vm.boot_timeout = 7200
override.vm.box = "takanabe/ubuntu1504"
override.vm.network "private_network", type: "dhcp"
end
master.vm.hostname = "swarm-master"
master.vm.provision "trigger" do |trigger|
trigger.fire do
puts "======= IN TRIGGER SECTION !!======="
puts "Creating Swarm Master ..."
`docker-machine status #{@machine.name}`
if $?.exitstatus != 0
if @machine.provider_name == :virtualbox
ip = `vagrant ssh #{@machine.name} -c "ip addr show dev eth1 | grep -w inet"`
.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/)[0]
port = 22
else
ip = @machine.ssh_info[:host]
port = @machine.ssh_info[:port]
end
consul_server_address = `docker-machine ip consul`.chomp
run "docker-machine -D create -d generic \
--generic-ip-address #{ip} \
--generic-ssh-key #{@machine.ssh_info[:private_key_path][0]} \
--generic-ssh-port #{port} \
--generic-ssh-user #{@machine.ssh_info[:username]} \
--swarm \
--swarm-master \
--swarm-discovery=consul://#{consul_server_address}:8500 \
--engine-opt=cluster-store=consul://#{consul_server_address}:8500 \
--engine-opt=cluster-advertise=eth1:2376 \
#{@machine.name}"
end
end
end
master.trigger.before :destroy do
`docker-machine status #{@machine.name}`
if $?.exitstatus == 0
run "docker-machine rm #{@machine.name}"
end
end
end #master end
config.vm.define "swarm-node01" do |node01|
node01.vm.provider :virtualbox do |virtualbox, override|
virtualbox.name = "swarm-node01"
override.vm.boot_timeout = 7200
override.vm.box = "takanabe/ubuntu1504"
override.vm.network "private_network", type: "dhcp"
end
node01.vm.hostname = "swarm-node01"
node01.vm.provision "trigger" do |trigger|
trigger.fire do
puts "======= IN TRIGGER SECTION !!======="
puts "Creating Swarm node01..."
`docker-machine status #{@machine.name}`
if $?.exitstatus != 0
if @machine.provider_name == :virtualbox
ip = `vagrant ssh #{@machine.name} -c "ip addr show dev eth1 | grep -w inet"`
.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/)[0]
port = 22
else
ip = @machine.ssh_info[:host]
port = @machine.ssh_info[:port]
end
consul_server_address = `docker-machine ip consul`.chomp
run "docker-machine -D create -d generic \
--generic-ip-address #{ip} \
--generic-ssh-key #{@machine.ssh_info[:private_key_path][0]} \
--generic-ssh-port #{port} \
--generic-ssh-user #{@machine.ssh_info[:username]} \
--swarm \
--swarm-discovery=consul://#{consul_server_address}:8500 \
--engine-opt=cluster-store=consul://#{consul_server_address}:8500 \
--engine-opt=cluster-advertise=eth1:2376 \
#{@machine.name}"
end
end
end
node01.trigger.before :destroy do
`docker-machine status #{@machine.name}`
if $?.exitstatus == 0
run "docker-machine rm #{@machine.name}"
end
end
end #node01 end
config.vm.define "swarm-node02" do |node02|
node02.vm.provider :virtualbox do |virtualbox, override|
virtualbox.name = "swarm-node02"
override.vm.boot_timeout = 7200
override.vm.box = "takanabe/ubuntu1504"
override.vm.network "private_network", type: "dhcp"
end
node02.vm.hostname = "swarm-node02"
node02.vm.provision "trigger" do |trigger|
trigger.fire do
puts "======= IN TRIGGER SECTION !!======="
puts "Creating Swarm node02..."
`docker-machine status #{@machine.name}`
if $?.exitstatus != 0
if @machine.provider_name == :virtualbox
ip = `vagrant ssh #{@machine.name} -c "ip addr show dev eth1 | grep -w inet"`
.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/)[0]
port = 22
else
ip = @machine.ssh_info[:host]
port = @machine.ssh_info[:port]
end
consul_server_address = `docker-machine ip consul`.chomp
run "docker-machine -D create -d generic \
--generic-ip-address #{ip} \
--generic-ssh-key #{@machine.ssh_info[:private_key_path][0]} \
--generic-ssh-port #{port} \
--generic-ssh-user #{@machine.ssh_info[:username]} \
--swarm \
--swarm-discovery=consul://#{consul_server_address}:8500 \
--engine-opt=cluster-store=consul://#{consul_server_address}:8500 \
--engine-opt=cluster-advertise=eth1:2376 \
#{@machine.name}"
end
end
end
node02.trigger.before :destroy do
`docker-machine status #{@machine.name}`
if $?.exitstatus == 0
run "docker-machine rm #{@machine.name}"
end
end
end #node02 end
end
以降はこのVagrantfileでクラスタを構築したことを前提で動作確認をする。
クラスタの状態を確認する
Dockerホストを確認する。
➤ docker-machine ls NAME ACTIVE DRIVER STATE URL SWARM ERRORS consul - generic Running tcp://172.28.128.3:2376 swarm-master - generic Running tcp://172.28.128.4:2376 swarm-master (master) swarm-node01 - generic Running tcp://172.28.128.5:2376 swarm-master swarm-node02 - generic Running tcp://172.28.128.6:2376 swarm-master
クラスタの情報を確認するにはswarm-masterの環境変数を使う。
➤ eval "$(docker-machine env --swarm swarm-master)" ➤ docker info Containers: 4 Images: 3 Role: primary Strategy: spread Filters: health, port, dependency, affinity, constraint Nodes: 3 swarm-master: 172.28.128.4:2376 └ Status: Healthy └ Containers: 2 └ Reserved CPUs: 0 / 1 └ Reserved Memory: 0 B / 513.5 MiB └ Labels: executiondriver=native-0.2, kernelversion=3.19.0-37-generic, operatingsystem=Ubuntu 15.04, provider=generic, storagedriver=aufs swarm-node01: 172.28.128.5:2376 └ Status: Healthy └ Containers: 1 └ Reserved CPUs: 0 / 1 └ Reserved Memory: 0 B / 513.5 MiB └ Labels: executiondriver=native-0.2, kernelversion=3.19.0-37-generic, operatingsystem=Ubuntu 15.04, provider=generic, storagedriver=aufs swarm-node02: 172.28.128.6:2376 └ Status: Healthy └ Containers: 1 └ Reserved CPUs: 0 / 1 └ Reserved Memory: 0 B / 513.5 MiB └ Labels: executiondriver=native-0.2, kernelversion=3.19.0-37-generic, operatingsystem=Ubuntu 15.04, provider=generic, storagedriver=aufs CPUs: 3 Total Memory: 1.505 GiB Name: 4bb9ee961926
consulが管理しているSwarmノードの情報を知らべられる。
➤ docker run swarm list consul://$(docker-machine ip consul):8500 172.28.128.4:2376 172.28.128.5:2376 172.28.128.6:2376
期待している環境になっていることが確認できた。
overlay nw + composeでシステムを構築する
RedmineとPostGresSQLのコンテナをComposeを用いてSwarm nodeに分散配置して、マルチホスト環境でコンテナを管理する。利用するcomposeファイルは以下のとおり。
➤ cat docker-compose.yml
db:
image: postgres:9.5
environment:
- POSTGRES_PASSWORD=secret
- POSTGRES_USER=redmine
- constraint:node==swarm-node02
redmine:
image: redmine:2.6.8
volumes:
- /srv/docker/redmine/redmine:/home/redmine/data
ports:
- "8080:3000"
environment:
- constraint:node==swarm-node01
前回と異なり、RedmineとPostGresSQL間の通信にlinkオプションをつけていないのに注目して欲しい。これはコンテナ間通信はVXLANによる overlayネットワークに任せるためだ。
このoverlayネットワークを利用するにはdocker-compose
を実行する際に--x-networking
、--x-network-driver overlay
オプションを付ける。
➤ docker-compose --x-networking --x-network-driver overlay up -d Creating network "composeandswarm" with driver "overlay" Pulling redmine (redmine:2.6.8)... swarm-node02: Pulling redmine:2.6.8... : downloaded swarm-master: Pulling redmine:2.6.8... : downloaded swarm-node01: Pulling redmine:2.6.8... : downloaded Creating composeandswarm_redmine_1 Pulling db (postgres:9.5)... swarm-node02: Pulling postgres:9.5... : downloaded swarm-master: Pulling postgres:9.5... : downloaded swarm-node01: Pulling postgres:9.5... : downloaded Creating composeandswarm_db_1
composeで管理しているコンテナを確認する。
➤ docker-compose ps Name Command State Ports --------------------------------------------------------------------------------------------- composeandswarm_db_1 /docker-entrypoint.sh Up 5432/tcp postgres composeandswarm_redmi /docker-entrypoint.sh Up 172.28.128.5:8080->30 ne_1
Redmine、PostGreSQLコンテナが指定したnode01,node02でそれぞれ起動していることを確認する。
➤ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f9995dc07765 postgres:9.5 "/docker-entrypoint.s" About an hour ago Up About an hour 5432/tcp swarm-node02/composeandswarm_db_1 94ec7d822ac7 redmine:2.6.8 "/docker-entrypoint.s" About an hour ago Up About an hour 172.28.128.5:8080->3000/tcp swarm-node01/composeandswarm_redmine_1
ブラウザを開き、172.28.128.5:8080にアクセスするとRedmineにアクセスできる。
クラスタの状態を確認する
ここまででコンテナクラスタとコンテナのマルチホスト配置が完了した。ここで、クラスタの状態をネットワークの観点から確認する。
クラスタが所持する全ネットワークは以下の通り。composeandswarm
という名のoverlayネットワークはcomposeで作られたもの。
➤ docker network ls NETWORK ID NAME DRIVER d60b430ccffa swarm-master/host host 7b313d0cf852 swarm-node01/none null 0416227693dd swarm-node01/docker_gwbridge bridge bd0d120a4a26 swarm-node01/bridge bridge 26772f8d0e77 swarm-node02/host host a775f144fcb7 swarm-node02/docker_gwbridge bridge db2a4af0fcb8 composeandswarm overlay 77e34c859d90 swarm-master/none null 20ab037e4b49 swarm-node02/bridge bridge edd9de3b93b5 swarm-node02/none null 5f5b2c79da0d swarm-master/bridge bridge 5ce93a14dfbe swarm-node01/host host
swarm-node01のネットワーク調査
swarm-node01のネットワーク情報を確認する。
➤ eval "$(docker-machine env swarm-node01)" ➤ docker network ls NETWORK ID NAME DRIVER db2a4af0fcb8 composeandswarm overlay bd0d120a4a26 bridge bridge 7b313d0cf852 none null 5ce93a14dfbe host host 0416227693dd docker_gwbridge bridge ➤ docker exec composeandswarm_redmine_1 ip addr 1: lo:mtu 65536 qdisc noqueue state UNKNOWN group default link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 11: eth0: mtu 1450 qdisc noqueue state UP group default link/ether 02:42:0a:00:00:02 brd ff:ff:ff:ff:ff:ff inet 10.0.0.2/24 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::42:aff:fe00:2/64 scope link valid_lft forever preferred_lft forever 14: eth1: mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff inet 172.18.0.2/16 scope global eth1 valid_lft forever preferred_lft forever inet6 fe80::42:acff:fe12:2/64 scope link valid_lft forever preferred_lft forever
これをみるに、eth0でipが10.0.0.2のインタフェースが割り当てられていることが確認できる。ネットワークレンジを指定しない場合、overlayネットワークはデフォルトで10.0.0.0/24のアドレス帯になるので、これがoverlayネットワークで利用されるインターフェースのようだ。
さらにRedmineコンテナから別ホストのPostGreSQLコンテナに向けたPingを打ってみる。
➤ docker exec -it composeandswarm_redmine_1 bash root@94ec7d822ac7:/usr/src/redmine# ping 10.0.0.3 PING 10.0.0.3 (10.0.0.3): 56 data bytes 64 bytes from 10.0.0.3: icmp_seq=0 ttl=64 time=0.855 ms 64 bytes from 10.0.0.3: icmp_seq=1 ttl=64 time=0.656 ms 64 bytes from 10.0.0.3: icmp_seq=2 ttl=64 time=0.639 ms
無事通信できた。さらに、/etc/hosts
を確認するとPostgreSQLのホスト名とip情報が登録されている。
root@94ec7d822ac7:/usr/src/redmine# cat /etc/hosts 10.0.0.2 94ec7d822ac7 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters 10.0.0.3 composeandswarm_db_1 10.0.0.3 composeandswarm_db_1.composeandswarm
無論、ホスト名でも名前解決が出来るためPingが通る。
root@94ec7d822ac7:/usr/src/redmine# ping composeandswarm_db_1 PING composeandswarm_db_1 (10.0.0.3): 56 data bytes 64 bytes from 10.0.0.3: icmp_seq=0 ttl=64 time=0.474 ms 64 bytes from 10.0.0.3: icmp_seq=1 ttl=64 time=0.630 ms 64 bytes from 10.0.0.3: icmp_seq=2 ttl=64 time=0.787 ms### node02
swarm-node02のネットワーク調査
同様にswarm-node02のネットワーク情報も確認する。
➤ eval "$(docker-machine env swarm-node02)" ➤ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f9995dc07765 postgres:9.5 "/docker-entrypoint.s" 20 minutes ago Up 20 minutes 5432/tcp composeandswarm_db_1 1c68829a3cb0 swarm:latest "/swarm join --advert" 41 minutes ago Up 41 minutes 2375/tcp swarm-agent ➤ docker network ls NETWORK ID NAME DRIVER db2a4af0fcb8 composeandswarm overlay 26772f8d0e77 host host a775f144fcb7 docker_gwbridge bridge 20ab037e4b49 bridge bridge edd9de3b93b5 none null
この時、overlayネットワークのIDがswarm-node01のoverlayネットワークのIDと同一であることから同じネットワークであることがわかる。 インターフェースを確認するとeth0に10.0.0.3が振られている。
➤ docker exec composeandswarm_db_1 ip addr 1: lo:mtu 65536 qdisc noqueue state UNKNOWN group default link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 9: eth0: mtu 1450 qdisc noqueue state UP group default link/ether 02:42:0a:00:00:03 brd ff:ff:ff:ff:ff:ff inet 10.0.0.3/24 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::42:aff:fe00:3/64 scope link valid_lft forever preferred_lft forever 12: eth1: mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff inet 172.18.0.2/16 scope global eth1 valid_lft forever preferred_lft forever inet6 fe80::42:acff:fe12:2/64 scope link valid_lft forever preferred_lft forever
Redmineへの通信とホスト情報の確認をする。
➤ docker exec -it composeandswarm_db_1 bash root@f9995dc07765:/# ping 10.0.0.2 PING 10.0.0.2 (10.0.0.2): 56 data bytes 64 bytes from 10.0.0.2: icmp_seq=0 ttl=64 time=2.140 ms 64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=1.365 ms 64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=1.340 ms root@f9995dc07765:/# cat /etc/hosts 10.0.0.3 f9995dc07765 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters 10.0.0.2 composeandswarm_redmine_1 10.0.0.2 composeandswarm_redmine_1.composeandswarm
overlayネットワークでコンテナ間通信をする問はホスト名やFQDNが自動で登録されるので、ipは気にしないで運用することになりそうだ。
今回はここまで。
参考
consul + swarm
- Docker Swarm discovery
- DockerコンテナをConsulで管理する方法 - Qiita
- Docker DNS & Service Discovery with Consul and Registrator
Docker Swarm + Docker Compose
- Orchestrating Docker with Machine, Swarm and Compose | Docker Blog
- Seamless Docker Multihost Overlay Networking on DigitalOcean With Machine, Swarm, and Compose ft. RethinkDB
- Dockerのoverlayネットワークでコンテナを分散実行 - Qiita
- Docker Swarm Cluster using Consul
Network関連情報
consul in production
- How to Configure Consul in a Production Environment on Ubuntu 14.04
- docker run –restart=alwaysがどれくらいrestartしてくれるか調べた