今更ながらDocker Swarmに入門することとなりました。普段K8sに慣れていたのでK8sと同じようにラップトップ側でクラスタの操作をしたいと思いました。調べたらDockerのAPIを外部に公開することで可能なようです。ただ、おもむろに公開するのはセキュリティ的にもよろしくないのでTLSを設定して安全にしたいと思います。
基本的に公式ドキュメントのここを参考にして進めていきます。
環境説明
AWS上のEC2にDockerをインストールしています。ホストは全部で5台ありSwarmクラスタを組んでいます。5台中3台がmanagerで残り2台がworkerです。各ホストのスペックは以下の通りです。各ホストはパブリックサブネットに配置しています。
- インスタンスタイプ: t2.medium
- OS: t2.medium
- docker: docker-ce 20.10.7
クライントは私のラップトップです。各ホストには私のラップトップからのインバウンドを許可するセキュリティグループルールを設定してあります。
今回はmanager3台に設定を追加して外部から操作可能にします。
手順
証明書準備
以降の手順はmanagerの内どれか1台で実施します。rootで作業する想定です。
TLSで通信したいためCAの認証局を作ります。
$ openssl genrsa -aes256 -out ca-key.pem 4096
# pass phraseは適当な4文字以上の文字列を入れる。
$ openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem
# CNはDockerホストのパブリックDNS名などクライアントから名前解決可能なホスト名にする。
サーバーの秘密鍵とCSRを作成します。
$ openssl genrsa -out server-key.pem 4096
$ openssl req -subj "/CN=<Dockerホスト名①>" -sha256 -new -key server-key.pem -out server.csr
# CNはmanagerの内どれか一台代表でOK。後ほどsubjectAltNameで他のmanagerも設定します。
サーバー用の拡張設定をファイルに保存します。ここで全managerのパブリックDNS名やパブリックIPアドレスの設定を追加しておきます。
$ echo subjectAltName = DNS:<Dockerホスト①>,DNS:<Dockerホスト②>,DNS:<Dockerホスト③>,IP:<Dockerホスト①>,IP:<Dockerホスト②>,IP:<Dockerホスト③> >> extfile.cnf
$ echo extendedKeyUsage = serverAuth >> extfile.cnf
サーバー証明書を作成します。
$ openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem \
-CAcreateserial -out server-cert.pem -extfile extfile.cnf
クライアントの秘密鍵とCSRを作成します。
$ openssl genrsa -out key.pem 4096
$ openssl req -subj '/CN=client' -new -key key.pem -out client.csr
クライアント用の拡張設定をファイルに保存します。
$ echo extendedKeyUsage = clientAuth > extfile-client.cnf
クライアント証明書を作成します。
$ openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem \
-CAcreateserial -out cert.pem -extfile extfile-client.cnf
パーミッションを整えておきます。
$ chmod -v 0400 ca-key.pem key.pem server-key.pem
$ chmod -v 0444 ca.pem server-cert.pem cert.pem
作成したca.pem
、server-cert.pem
、server-key.pem
を他のmanagerにもコピーします。(各種ファイルをcatして中身をコピペでもいい。)
dockerd設定
以降の手順はすべてのmanagerで実施します。rootで作業する想定です。
ここから参考手順にないオリジナル手順。dockerdをTLS有効にして起動するためdaemon.jsonを設定します。設定はここを参考にします。
まず各種ファイルをディレクトリに配置します。
$ mkdir /var/docker
$ mv ca.pem /var/docker
$ mv server-cert.pem /var/docker
$ mv server-key.pem /var/docker
daemon.jsonを作成します。(すでにある場合は編集にします。)
$ cat <<EOF > /etc/docker/daemon.json
{
"tls": true,
"tlscacert": "/var/docker/ca.pem",
"tlscert": "/var/docker/server-cert.pem",
"tlskey": "/var/docker/server-key.pem",
"hosts": ["tcp://0.0.0.0:2376","unix:///var/run/docker.sock"]
}
EOF
また、上記のようにdaemon.jsonでhostsを指定する場合は/etc/systemd/system/docker.service.d/docker.confを作成しておく必要があります。ネタ元
$ mkdir /etc/systemd/system/docker.service.d/
$ cat <<EOF > /etc/systemd/system/docker.service.d/docker.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd
EOF
上記ファイルを作成したらデーモンリロードしてdockerを起動しなおします。
$ systemctl daemon-reload
$ systemctl restart docker
クライアント設定
以降の手順はクライアントで実施します。
ca.pem
、cert.pem
、key.pem
をクライントにコピーします。やり方はお好きな方法でどうぞ。(各種ファイルをcatして中身をクライアントにコピペでもいい。)
資材をクライアントに持って行ったら以下コマンドでリモートのDockerホストに接続できるか確認します。クライアント→Dockerホストでport:2376のインバウンドが許可されていることに注意してください。
$ docker --tlsverify \
--tlscacert=ca.pem \
--tlscert=cert.pem \
--tlskey=key.pem \
-H=<Dockerホスト①IP>:2376 node ls
上記接続に問題なければ以下コマンドでコンテキストを作成しておきます。名前やhostを変えて他のホストのコンテキストも作っておきます。また、ホストの指定はパブリックDNS名でもよいです。
$ docker context create manager0 --description "remote manager0" --docker "host=tcp://<Dockerホスト①IP>:2376,ca=<ca.pemフルパス>,cert=<cert.pemフルパス>,key=<key.pemフルパス>"
$ docker context create manager1 --description "remote manager1" --docker "host=tcp://<Dockerホスト②IP>:2376,ca=<ca.pemフルパス>,cert=<cert.pemフルパス>,key=<key.pemフルパス>"
$ docker context create manager2 --description "remote manager2" --docker "host=tcp://<Dockerホスト③IP>:2376,ca=<ca.pemフルパス>,cert=<cert.pemフルパス>,key=<key.pemフルパス>"
使用するコンテキストを切り替えます。
$ docker context use manager0
オプションを指定しなくてもリモートのDockerホストにつながることを確認します。
$ docker node ls
動作確認
簡単なcomposeファイルを作成してスタックがデプロイできることを確認します。
$ cat <<EOF > test.yaml
version: "3.9"
services:
test:
image: nginx
deploy:
replicas: 1
EOF
スタックをデプロイします。
$ docker stack deploy -c test.yaml test
スタックがデプロイできたことを確認します。
$ docker stack ls
$ docker stack services test
$ docker stack ps test
別managerのコンテキストに切り替えても見えることを確認します。
$ docker context use manager1
$ docker node ls
$ docker stack ps test
あとがき
マネージャの前段にLB立ててやる方がカッコいいだろうとなと思いつつ、とりあえず目的は達成できたのでこれで我慢する。