TSL認証なSwarmクラスタはdocker-machineで構築すると、勝手に設定してくれて非常に楽だが、ホストのネットワークを事前に弄りたかったり、Terraformなどの他オーケストレーションツールを組み合わせたいときに、ちょっと融通がきかない。
なので、TSL認証を用いたDocker Swarmクラスタを自力で構築できるように、手順をまとめておきたい。また、docker-machineの代替としてTerraformを使い、自動化できるようにしたい。
作業の流れ
概ね以下の様な流れでSwarmクラスタの構築を行う。基本的には、docker-machineで行われていることを模倣している。docker-machine自体ではサービスディスカバリの準備は行わないので、そこら辺の手順も残しておく。
[付録] SwarmクラスタをTerraformで構築するサンプル
今回の記事で行う作業をTerraformで自動化したものを、以下のリポジトリに置いておくので、ご参考までに。terraform.tfのcount
の値を弄ることで、指定数のノードを自動で作成するので、大量にノードが必要な場合に便利かも。
Build docker swarm cluster over TLS using Terraform https://github.com/namikingsoft/sample-terraform-docker-swarm-over-tls
事前準備
必要なソフトウェアのインストール
作業で使うPC(またはホスト)に以下のDocker関連のソフトウェアをインストールしておく。
- docker (Engine)
- OpenSSL (Linux系やOSXなら、デフォルトで入っているはず)
DigitalOceanの登録とアクセストークンの取得
この記事の例では、ホストにDigitalOceanを使うが、AWSとかでも可能と思います。
https://www.digitalocean.com/
登録後、管理画面からdocker-machineとの連携に必要なアクセストークンを発行できる。
ノード用のホストを用意する
今回の例では、DigitalOceanでノードを2台用意して、Swarmクラスタの連携を確認した。
Host | OS | Mem | IP (eth0) | IP (eth1) |
---|---|---|---|---|
swarm-node0 | ubuntu-15-10-x64 | 512MB | x.x.x.1 | y.y.y.1 |
swarm-node1 | ubuntu-15-10-x64 | 512MB | x.x.x.2 | y.y.y.2 |
備考
- swarm-node0はマスターノードとして使う
- ホスト名(hostname)は別になんでもよい
- プライベートネットワークを有効にしておく
- eth0はグローバルネットワークに繋がるインタフェース
- eth1はプライベートネットワークに繋がるインタフェース
各ノードでConsulを動かす
Swarmクラスタのサービスディスカバリー(分散KVS)であるConsulを各ノードにインストールする。使わないでもSwarmクラスタは構築できるが、マルチホスト間でオーバーレイ・ネットワークを作れるようになったりと色々利点が多いので。(etcdやZookeeperでも構築可能)
swarm-node0にてConsulをサーバーモードで動かす
SSHでログインして作業を行う。
インストール
# 必要なパッケージのインストール
apt-get install -y curl zip
# Consulインストール
cd /tmp
curl -LO https://releases.hashicorp.com/consul/0.6.1/consul_0.6.1_linux_amd64.zip
unzip consul_0.6.1_linux_amd64.zip
mv consul /usr/local/bin
# ConsulのWebUIを設置(任意)
curl -LO https://releases.hashicorp.com/consul/0.6.1/consul_0.6.1_web_ui.zip
unzip consul_0.6.1_web_ui.zip -d consul-webui
サービス登録
# service
cat << EOS > /lib/systemd/system/consul.service
[Unit]
Description=consul agent
After=network-online.target
[Service]
ExecStart=/usr/local/bin/consul agent -config-dir=/etc/consul.d
Type=simple
Restart=always
[Install]
WantedBy=multi-user.target
EOS
設定
# 自分自身のプライベートIPを取得
export MY_PRIVATE_IP=$(
ip addr show eth1 \
| grep -o -e '[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+' \
| head -n1
)
# 設定ファイルに書き出す
cat << EOS > /etc/consul.d/config.json
{
"server": true,
"bootstrap": true,
"bind_addr": "$MY_PRIVATE_IP",
"node_name": "consul0",
"datacenter": "dc0",
"ui_dir": "/var/local/consul/webui",
"data_dir": "/var/local/consul/data",
"log_level": "INFO",
"enable_syslog": true
}
EOS
1台構成のサーバーモードをWebUI付き(任意)で起動する。プライベートネットワークであるeth1のIPにバインドする。
自動起動設定&起動
systemctl enable consul
systemctl start consul
swarm-node1にてConsulをクライアントモードで動かす
SSHでログインして作業を行う。設定以外は同じなので割愛。
設定
# 自分自身のプライベートIPを取得
export MY_PRIVATE_IP=$(
ip addr show eth1 \
| grep -o -e '[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+' \
| head -n1
)
# 設定ファイルに書き出す
cat << eos > /etc/consul.d/config.json
{
"server": false,
"start_join": ["y.y.y.1"],
"bind_addr": "$MY_PRIVATE_IP",
"datacenter": "dc0",
"ui_dir": "/var/local/consul/webui",
"data_dir": "/var/local/consul/data",
"log_level": "INFO",
"enable_syslog": true
}
EOS
swarm-node0のプライベートIPにジョインする。
設定が終わったら、自動起動設定と起動を行っておく。
メンバー確認
各ノードのConsulが連携できているかを確認する。
$ consul members
Node Address Status Type Build Protocol DC
consul0 y.y.y.1:8301 alive server 0.6.1 2 dc1
consul1 y.y.y.2:8301 alive client 0.6.1 2 dc1
WebUIで確認する場合、ローカルPCと各ノードの間に8500ポートのSSHトンネルを開ければ、ブラウザで閲覧できる。
ssh root@x.x.x.1 -L8500:localhost:8500
open http://localhost:8500
各ノードにDockerをインストール
各ノードのSSHにて、以下のコマンドを実行する。
wget -qO- https://get.docker.com/ | sh
デーモン起動時の引数設定などは後ほど行う。
TLS認証用の鍵を生成する
クライアント側(ローカルPC)で生成する。後ほど各ノードに必要なファイルを転送する。
CAの証明書を生成
openssl genrsa -out ca-key.pem 4096
openssl req -subj "/CN=ca" -new -x509 -days 365 -key ca-key.pem -out ca.pem
CommonName(CN)は任意。
クライアントの秘密鍵と証明書を生成
# extfile
echo "extendedKeyUsage = clientAuth" >> extfile-client.cnf
# client cert
openssl genrsa -out key.pem 4096
openssl req -subj '/CN=client' -new -key key.pem -out client.csr
openssl x509 -req -days 365 -sha256 -in client.csr -out cert.pem \
-CA ca.pem -CAkey ca-key.pem -CAcreateserial -extfile extfile-client.cnf
CommonName(CN)は任意。
swarm-node0(master)の秘密鍵と証明書を生成
# extfile
echo "subjectAltName = IP:x.x.x.1" > extfile.cnf
echo "extendedKeyUsage = clientAuth,serverAuth" >> extfile.cnf
# server cert
openssl genrsa -out node0-key.pem 4096
openssl req -subj "/CN=node0" -new -key node0-key.pem -out node0.csr
openssl x509 -req -days 365 -sha256 -in node0.csr -out node0-cert.pem \
-CA ca.pem -CAkey ca-key.pem -CAcreateserial -extfile extfile.cnf
Swarm Managerを動かすノードなので、clientAuthも設定しておく。subjectAltNameにグローバルIPを設定するので、CommonName(CN)は割と何でもよいが、ドメイン名があれば、それを設定するとよい。
swarm-node1の秘密鍵と証明書を生成
# extfile
echo "subjectAltName = IP:x.x.x.2" > extfile.cnf
# server cert
openssl genrsa -out node1-key.pem 4096
openssl req -subj "/CN=node1" -new -key node1-key.pem -out node1.csr
openssl x509 -req -days 365 -sha256 -in node1.csr -out node1-cert.pem \
-CA ca.pem -CAkey ca-key.pem -CAcreateserial -extfile extfile.cnf
CommonName(CN)は任意。
TLS認証鍵を各ノードへアップロード
SFTPやSCPなどを使って、各ノードの/etc/docker
1あたりにアップロードする。
- swarm-node0
- ca.pem
- node0-cert.pem
- node0-key.pem
- swarm-node1
- ca.pem
- node1-cert.pem
- node1-key.pem
各ノードのDockerの設定を変更する
各ノードにSSHでログインして、設定を行う。
Dockerデーモン起動時の引数設定
swarm-node0
vi /lib/systemd/system/docker.service
# 変更前
ExecStart=/usr/bin/docker daemon -H fd://
# 変更後
ExecStart=/usr/bin/docker daemon \
--tlsverify \
--tlscacert=/etc/docker/ca.pem \
--tlscert=/etc/docker/node0-cert.pem \
--tlskey=/etc/docker/node0-key.pem \
-H=0.0.0.0:2376 \
--cluster-store=consul://localhost:8500 \
--cluster-advertise=eth0:2376 \
-H fd://
swarm-node1
vi /lib/systemd/system/docker.service
# 変更前
ExecStart=/usr/bin/docker daemon -H fd://
# 変更後
ExecStart=/usr/bin/docker daemon \
--tlsverify \
--tlscacert=/etc/docker/ca.pem \
--tlscert=/etc/docker/node1-cert.pem \
--tlskey=/etc/docker/node1-key.pem \
-H=0.0.0.0:2376 \
--cluster-store=consul://localhost:8500 \
--cluster-advertise=eth0:2376 \
-H fd://
備考
- 変更後は見やすさのため複数行で書いているが、
\
を消して1行ぶっ続けで記述する。 - この設定はUbuntu15.10の場合なので、他のOSの場合は
/etc/default/docker
のDOCKER_OPTS
に引数を付け加えたりなど、やり方が違ってくると思うので、各々調節する。 - cluster-storeとcluster-advertiseの指定は、オーバーレイ・ネットワーク機能のためなので、使わない場合は特に指定しなくても、Swarmクラスタの動作は可能。
Docker再起動
service docker restart
TLS接続確認
ローカルPCから、TLS(TCP)でホストのDockerを利用できるか確認してみる。
# swarm-node0
docker --tlsverify \
--tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem \
-H=x.x.x.1:2376 \
version
# swarm-node1
docker --tlsverify \
--tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem \
-H=x.x.x.2:2376 \
version
ClientとServerのDockerバージョンが表示されれば、正しく設定できている。
また、以下の様な環境変数を設定することで、いちいちTLS認証鍵やIP指定をしなくても、普通にdockerコマンドが扱えるようになる。
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://(dockerホストのIP):2376"
export DOCKER_CERT_PATH="/path/to/クライアント認証鍵があるディレクトリ"
docker version
DOCKER_CERT_PATH
については、~/.docker/
にクライアント認証鍵(ca.pem, cert.pem, key.pem)を設置すれば、省略可能。
各ノードでSwarmコンテナを動かす
各ノードにSSHでログインして、Swarmコンテナを起動させる。
swarm-node0
# Swarm Manager
docker run -d --name=swarm-agent-master \
-v /etc/docker:/etc/docker --net=host --restart=always \
swarm manage --tlsverify \
--tlscacert=/etc/docker/ca.pem \
--tlscert=/etc/docker/node0-cert.pem \
--tlskey=/etc/docker/node0-key.pem \
-H=tcp://0.0.0.0:3376 --strategy=spread \
--advertise=x.x.x.1:2376 consul://localhost:8500
# Swarm Agent
docker run -d --name=swarm-agent --net=host --restart=always \
swarm join --advertise=x.x.x.1:2376 consul://localhost:8500
swarm-node1
# Swarm Agent
docker run -d --name swarm-agent --net=host --restart=always \
swarm join --advertise=x.x.x.2:2376 consul://localhost:8500
備考
- ネットワークのホストと共有する必要があるので、
--net=host
を指定している。 - Swarm Managerで
/etc/docker
を共有Volume指定しているのは、TLS認証鍵の共有だけではなく、/etc/docker/key.json
の共有のため。DockerユニークIDの識別に必要とのこと。
動作確認
クライアント側(ローカルPC)から、Swarmクラスタへの接続を試みる。
Swarm MasterにTLS接続
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://x.x.x.1:3376"
export DOCKER_CERT_PATH="/path/to/クライアント認証鍵があるディレクトリ"
$ docker info
Containers: 3
Images: 2
Role: primary
Strategy: spread
Filters: health, port, dependency, affinity, constraint
Nodes: 2
swarm-node0: x.x.x.1:2376
└ Status: Healthy
└ Containers: 2
└ Reserved CPUs: 0 / 1
└ Reserved Memory: 0 B / 513.4 MiB
└ Labels: executiondriver=native-0.2, kernelversion=4.2.0-16-generic, operatingsystem=Ubuntu 15.10, storagedriver=aufs
swarm-node1: x.x.x.2:2376
└ Status: Healthy
└ Containers: 1
└ Reserved CPUs: 0 / 1
└ Reserved Memory: 0 B / 513.4 MiB
└ Labels: executiondriver=native-0.2, kernelversion=4.2.0-16-generic, operatingsystem=Ubuntu 15.10, storagedriver=aufs
CPUs: 2
Total Memory: 1.003 GiB
Name: swarm-node0
上のように、ノードが2つ接続されていることが確認できれば、Swarmクラスタの構築がうまく行えている。DOCKER_HOST
のポート指定を2376
ではなく、3376
にすることで、dockerコマンドでSwarm関連の操作を行うことができる。
Swarmクラスタにコンテナを配置してみる
$ docker run -d --name container1 nginx
$ docker run -d --name container2 nginx
$ docker ps --format "{{.Names}}"
swarm-node1/container1
swarm-node2/container2
Swarm Masterのストラテジーがspread
なので、各ノードにコンテナが分散配置される。
- TLS認証鍵の置き場所は任意。Dockerデーモン起動時の引数で指定する。 [return]