Edited at

Vagrant+Docker Machine Generic Driverを使ってDockerホスト・Swarmクラスタを構築する

More than 3 years have passed since last update.

Docker Machineを使用すると簡単に様々な環境にDockerホスト・Swarmクラスタを作成し、ローカルのDockerクライアントから使用できるよう設定することができます。

Docker Machine単体でも使えますがVagrantと組み合わせて使用することでDocker Machineが未対応の環境にホストやSwarmクラスタを構築することができます。


TL;DR

VagrantとDocker Machineを組み合わせて使うと以下のようなメリットがあります。


  • Docker Machineが未対応の環境にDocker環境を構築できる


  • vagrant upコマンドで複数ノードのDockerホスト・Swarmクラスタを構築・削除できる

  • VagrantのSynced Folderでリモートのホストとファイル・ディレクトリを同期してくれる


環境

動作確認は以下の環境で行いました。


  • Mac OSX 10.10.1

  • Vagrant 1.7.4

  • docker-machine 0.4.1

  • vagrant-triggers 0.5.1

  • vagrant-cloudstack 1.2.0


準備

Vagrant, Docker, Docker Machineをインストールしておきます。

Mac, WindowsであればDocker Toolboxを使うと関連ツールをまとめてインストールしてくれるので便利です。

仮想マシン作成・削除のタイミングでDocker Machineのコマンドを実行するためvagrant-triggersをインストールしておきます。

$ vagrant plugin install vagrant-triggers


VirtualBoxにDockerホストを構築

まずVirtualBoxにDockerホストを構築してみます。

Vagrantfileは次のようになります。


Vagrantfile

Vagrant.configure(2) do |config|

# vagrant-triggersをProvisionerとして使用し、
# 作成した仮想マシンに対してdocker-machine createを実行
config.vm.provision "trigger" do |trigger|
trigger.fire do
# docker-machine statusのexitstatusが0の場合はdocker-machine create実行済み
`docker-machine status #{@machine.name}`
if $?.exitstatus != 0
# 使用するIPとポートを取得
# VirtualBoxの場合には一度仮想マシンにログインしプライベートネットワークのIPを取得
# その他のProviderでは@machine.ssh_infoからIP、ポートを取得できる
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

# docker-machine createを実行
# SWARM_DISCOVERY_TOKENが設定されている場合Swarmクラスタ用のオプションを付ける
run "docker-machine 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" unless ENV["SWARM_DISCOVERY_TOKEN"].nil?} \
#{"--swarm-master" if !ENV["SWARM_DISCOVERY_TOKEN"].nil? && @machine.name.to_s.include?("master")} \
#{"--swarm-discovery token://#{ENV["SWARM_DISCOVERY_TOKEN"]}" unless ENV["SWARM_DISCOVERY_TOKEN"].nil?} \
#{@machine.name}"
end
end
end

# 仮想マシン削除時にはdocker-machine rmを実行しdocker-machineからも削除する
config.trigger.before :destroy do
# docker-machine statusのリターンコードが0でない場合は
# docker-machineに登録されていないか異常が発生しているため削除をスキップ
`docker-machine status #{@machine.name}`
if $?.exitstatus == 0
run "docker-machine rm #{@machine.name}"
end
end

# VirtualBoxを使用する際の設定
config.vm.provider :virtualbox do |virtualbox, override|
# Boxは現状Ubuntu 14.04がうまくいきやすい
override.vm.box = "boxcutter/ubuntu1404"
# VirtualBoxの場合はDockerクライアントからの接続にプライベートネットワークを使用
override.vm.network "private_network", type: "dhcp"
end

# 作成する仮想マシンを定義
config.vm.define "docker01" do |d|
end
end


vagrant up で仮想マシンが作成され、Docker Machineに登録されます。

$ vagrant up

登録された仮想マシンはdocker-machine ls で確認できます。

$ docker-machine ls

NAME ACTIVE DRIVER STATE URL SWARM
docker01 generic Running tcp://172.28.128.19:2376

docker-machine env の出力をeval するとDockerクライアントから使用できるようになります。

$ eval "$(docker-machine env docker01)"

$ docker run --rm hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
535020c3e8ad: Pull complete
af340544ed62: Pull complete
Digest: sha256:a68868bfe696c00866942e8f5ca39e3e31b79c1e50feaee4ce5e28df2f051d5c
Status: Downloaded newer image for hello-world:latest

Hello from Docker.
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker Hub account:
https://hub.docker.com

For more examples and ideas, visit:
https://docs.docker.com/userguide/

vagrant destroy で仮想マシンを削除すると、docker-machineからも削除されます。

$ vagrant destroy

$ docker-machine ls


VirtualBoxにSwarmクラスタを構築

上記のVagrantfileを使ってSwarmクラスタを構築することも可能です。

今回はmaster1台、agent2台のクラスタを作成してみます。

次のようにVagrantfileの仮想マシンの定義を変更します。(名前に「master」を含む仮想マシンをmasterとして使用するようになっています。)


Vagrantfile

Vagrant.configure(2) do |config|

# vagrant-triggersをProvisionerとして使用し、
# 作成した仮想マシンに対してdocker-machine createを実行
config.vm.provision "trigger" do |trigger|
trigger.fire do
# docker-machine statusのexitstatusが0の場合はdocker-machine create実行済み
`docker-machine status #{@machine.name}`
if $?.exitstatus != 0
# 使用するIPとポートを取得
# VirtualBoxの場合には一度仮想マシンにログインしプライベートネットワークのIPを取得
# その他のProviderでは@machine.ssh_infoからIP、ポートを取得できる
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

# docker-machine createを実行
# SWARM_DISCOVERY_TOKENが設定されている場合Swarmクラスタ用のオプションを付ける
run "docker-machine 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" unless ENV["SWARM_DISCOVERY_TOKEN"].nil?} \
#{"--swarm-master" if !ENV["SWARM_DISCOVERY_TOKEN"].nil? && @machine.name.to_s.include?("master")} \
#{"--swarm-discovery token://#{ENV["SWARM_DISCOVERY_TOKEN"]}" unless ENV["SWARM_DISCOVERY_TOKEN"].nil?} \
#{@machine.name}"
end
end
end

# 仮想マシン削除時にはdocker-machine rmを実行しdocker-machineからも削除する
config.trigger.before :destroy do
# docker-machine statusのリターンコードが0でない場合は
# docker-machineに登録されていないか異常が発生しているため削除をスキップ
`docker-machine status #{@machine.name}`
if $?.exitstatus == 0
run "docker-machine rm #{@machine.name}"
end
end

# VirtualBoxを使用する際の設定
config.vm.provider :virtualbox do |virtualbox, override|
# Boxは現状Ubuntu 14.04がうまくいきやすい
override.vm.box = "boxcutter/ubuntu1404"
# VirtualBoxの場合はDockerクライアントからの接続にプライベートネットワークを使用
override.vm.network "private_network", type: "dhcp"
end

# 作成する仮想マシンを定義
config.vm.define "master01" do |d|
end

config.vm.define "agent01" do |d|
end

config.vm.define "agent02" do |d|
end
end


仮想マシンを作成する前にSwarmクラスタを構築するためのトークンを取得しておきます。

$ SWARM_DISCOVERY_TOKEN=$(curl -s -X POST https://discovery.hub.docker.com/v1/clusters)

この値を使って次のようにvagrant upコマンドを実行するとSwarmクラスタが構築されます。

$ SWARM_DISCOVERY_TOKEN=$SWARM_DISCOVERY_TOKEN vagrant up

$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM
agent01 generic Running tcp://172.28.128.21:2376 master01
agent02 generic Running tcp://172.28.128.22:2376 master01
master01 * generic Running tcp://172.28.128.20:2376 master01 (master)

docker-machine env --swarm を使って環境変数を取得・設定します。

$ eval "$(docker-machine env --swarm master01)"

うまくいっていればdocker info コマンドで3ノードあることが確認できます。

$ docker info

Containers: 4
Images: 3
Role: primary
Strategy: spread
Filters: affinity, health, constraint, port, dependency
Nodes: 3
agent01: 172.28.128.24:2376
└ Containers: 1
└ Reserved CPUs: 0 / 1
└ Reserved Memory: 0 B / 514.2 MiB
└ Labels: executiondriver=native-0.2, kernelversion=3.16.0-30-generic, operatingsystem=Ubuntu 14.04.2 LTS, provider=generic, storagedriver=aufs
agent02: 172.28.128.25:2376
└ Containers: 1
└ Reserved CPUs: 0 / 1
└ Reserved Memory: 0 B / 514.2 MiB
└ Labels: executiondriver=native-0.2, kernelversion=3.16.0-30-generic, operatingsystem=Ubuntu 14.04.2 LTS, provider=generic, storagedriver=aufs
master01: 172.28.128.23:2376
└ Containers: 2
└ Reserved CPUs: 0 / 1
└ Reserved Memory: 0 B / 514.2 MiB
└ Labels: executiondriver=native-0.2, kernelversion=3.16.0-30-generic, operatingsystem=Ubuntu 14.04.2 LTS, provider=generic, storagedriver=aufs
CPUs: 3
Total Memory: 1.507 GiB
Name: a4fbe0415d5f

docker ps -aでswarm用のコンテナが起動していることを確認できます。

$ docker ps -a

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9d3924175431 swarm:latest "/swarm join --advert" 5 minutes ago Up 5 minutes 2375/tcp master01/swarm-agent
a4fbe0415d5f swarm:latest "/swarm manage --tlsv" 5 minutes ago Up 5 minutes 2375/tcp, 172.28.128.23:3376->3376/tcp master01/swarm-agent-master
056fbce9741c swarm:latest "/swarm join --advert" 16 minutes ago Up 16 minutes 2375/tcp agent02/swarm-agent
3da76b011711 swarm:latest "/swarm join --advert" 18 minutes ago Up 18 minutes 2375/tcp agent01/swarm-agent

Swarmクラスタでもdocker run コマンドでコンテナを起動することができます。

$ docker run --rm hello-world

Hello from Docker.
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker Hub account:
https://hub.docker.com

For more examples and ideas, visit:
https://docs.docker.com/userguide/


CloudStack/IDCFクラウドの場合

Vagrantを仮想マシン作成に利用することでDocker Machineが未対応の環境にもDocker環境を構築することができます。

例としてIDCFクラウドのCloudStack上にSwarmクラスタを構築してみます。

2015/12/04 追記: CloudStackドライバを作成しました。Synced Folderが不要であればCloudStackドライバを使用したほうが手軽です。(Docker Machine CloudStack Driverを使ってDockerホスト・Swarmクラスタを構築する - Qiita)

CloudStackを使うためのプラグインをインストールします。

$ vagrant plugin install vagrant-cloudstack

Docker Machine Generic DriverはDocker用のポートとして2376を固定で使用するためホストごとに異なるパブリックIPが必要になります。今回は追加IPの料金を節約するため3台の仮想マシンをtesla, henry, pascalゾーンに作成しソースIPを使用します。

Vagrantfileは以下のようになります。環境変数はお使いの環境に合わせて適切に設定してください。


Vagrantfile

Vagrant.configure(2) do |config|

# vagrant-triggersをProvisionerとして使用し、
# 作成した仮想マシンに対してdocker-machine createを実行
config.vm.provision "trigger" do |trigger|
trigger.fire do
# docker-machine statusのexitstatusが0の場合はdocker-machine create実行済み
`docker-machine status #{@machine.name}`
if $?.exitstatus != 0
# 使用するIPとポートを取得
# VirtualBoxの場合には一度仮想マシンにログインしプライベートネットワークのIPを取得
# その他のProviderでは@machine.ssh_infoからIP、ポートを取得できる
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

# docker-machine createを実行
# SWARM_DISCOVERY_TOKENが設定されている場合Swarmクラスタ用のオプションを付ける
run "docker-machine 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" unless ENV["SWARM_DISCOVERY_TOKEN"].nil?} \
#{"--swarm-master" if !ENV["SWARM_DISCOVERY_TOKEN"].nil? && @machine.name.to_s.include?("master")} \
#{"--swarm-discovery token://#{ENV["SWARM_DISCOVERY_TOKEN"]}" unless ENV["SWARM_DISCOVERY_TOKEN"].nil?} \
#{@machine.name}"
end
end
end

# 仮想マシン削除時にはdocker-machine rmを実行しdocker-machineからも削除する
config.trigger.before :destroy do
# docker-machine statusのリターンコードが0でない場合は
# docker-machineに登録されていないか異常が発生しているため削除をスキップ
`docker-machine status #{@machine.name}`
if $?.exitstatus == 0
run "docker-machine rm #{@machine.name}"
end
end

# VirtualBoxを使用する際の設定
config.vm.provider :virtualbox do |virtualbox, override|
# Boxは現状Ubuntu 14.04がうまくいきやすい
override.vm.box = "boxcutter/ubuntu1404"
# VirtualBoxの場合はDockerクライアントからの接続にプライベートネットワークを使用
override.vm.network "private_network", type: "dhcp"
end

# IDCFクラウドを使用する際の設定
config.vm.provider :cloudstack do |cloudstack, override|
# Ubuntu 14.04のテンプレートを使用
override.vm.box = "Ubuntu Server 14.04 LTS 64-bit"

cloudstack.host = "compute.jp-east.idcfcloud.com"
cloudstack.path = "/client/api"
cloudstack.port = "443"
cloudstack.scheme = "https"

cloudstack.api_key = "#{ENV['CLOUDSTACK_API_KEY']}"
cloudstack.secret_key = "#{ENV['CLOUDSTACK_SECRET_KEY']}"

cloudstack.service_offering_name = "light.S1"

# Firewallを開放する際に許可するCIDR
# vagrant-cloudstack 1.2.0からopenfirewallで開放されるルールに自動で設定される
cloudstack.pf_trusted_networks = "#{ENV['CLOUDSTACK_PF_TRUSTED_NETWORKS'] || "0.0.0.0/0"}"

cloudstack.keypair = "#{ENV['CLOUDSTACK_SSH_KEYPAIR']}"
override.ssh.private_key_path = "#{ENV['VAGRANT_SSH_PRIVATE_KEY']}"

# requirettyが有効なテンプレートを使う際の設定
# http://qiita.com/atsaki/items/467ce1569fbc2cb11cb8
override.ssh.username = "root"
override.ssh.pty = true
config.vm.synced_folder ".", "/vagrant", disabled: true
end

# 作成する仮想マシンを定義
config.vm.define "master01" do |vm|
vm.vm.provider :cloudstack do |cloudstack|
cloudstack.zone_name = "#{ENV['MASTER01_ZONE']}"
# SSH用のポートフォワーディングを設定
# vagrant-cloudstack 1.2.0から自動でランダムなポートを使ってくれるようになった
cloudstack.pf_ip_address = "#{ENV['MASTER01_PUBLIC_IP_ADDRESS']}"
# Docker, Swarm用のポートのポートフォワーディングを設定
cloudstack.port_forwarding_rules = [2376, 3376].map { |port|
{
:ipaddress => "#{ENV['MASTER01_PUBLIC_IP_ADDRESS']}",
:protocol => "tcp",
:publicport => port,
:privateport => port,
:openfirewall => true
}
}
end
end

config.vm.define "agent01" do |vm|
vm.vm.provider :cloudstack do |cloudstack|
cloudstack.zone_name = "#{ENV['AGENT01_ZONE']}"
cloudstack.pf_ip_address = "#{ENV['AGENT01_PUBLIC_IP_ADDRESS']}"
cloudstack.port_forwarding_rules = [2376].map { |port|
{
:ipaddress => "#{ENV['AGENT01_PUBLIC_IP_ADDRESS']}",
:protocol => "tcp",
:publicport => port,
:privateport => port,
:openfirewall => true
}
}
end
end

config.vm.define "agent02" do |vm|
vm.vm.provider :cloudstack do |cloudstack|
cloudstack.zone_name = "#{ENV['AGENT02_ZONE']}"
cloudstack.pf_ip_address = "#{ENV['AGENT02_PUBLIC_IP_ADDRESS']}"
cloudstack.port_forwarding_rules = [2376].map { |port|
{
:ipaddress => "#{ENV['AGENT02_PUBLIC_IP_ADDRESS']}",
:protocol => "tcp",
:publicport => port,
:privateport => port,
:openfirewall => true
}
}
end
end
end


トークンを取得しなおしてvagrant upを実行するとIDCFクラウド上にSwarmクラスタが構築されます。

--no-parallel をつけないとdocker-machine createが全ての仮想マシンに対して実行されず正しくクラスタが構築できなかった。)

$ SWARM_DISCOVERY_TOKEN=$(curl -s -X POST https://discovery.hub.docker.com/v1/clusters)

$ SWARM_DISCOVERY_TOKEN=$SWARM_DISCOVERY_TOKEN vagrant up --provider=cloudstack --no-parallel


Synced Folderとボリュームマウント

Vagrantは作成した仮想マシンとSynced Folderでディレクトリ・ファイルを同期することができます。

デフォルトでローカルのカレントディレクトリをリモートの/vagrantと同期しているため、次のように-v オプションを使ってローカルのファイルをリモートの仮想マシン上で利用することができます。

$ docker run  --rm -v /vagrant:/data  ubuntu head /data/Vagrantfile

Vagrant.configure(2) do |config|
# vagrant-triggersをProvisionerとして使用し、
# 作成した仮想マシンに対してdocker-machine createを実行
config.vm.provision "trigger" do |trigger|
trigger.fire do
# docker-machine statusのexitstatusが0の場合はdocker-machine create実行済み
`docker-machine status #{@machine.name}`
if $?.exitstatus != 0
# 使用するIPとポートを取得
# VirtualBoxの場合には一度仮想マシンにログインしプライベートネットワークのIPを取得

rsyncを使用している場合、同期はローカルからリモートへの一方向です。Swarmクラスタのホスト間の同期は行われないので、何れかのホストで変更を加えるとホストごとに中身が異なる状態になります。