Docker SwarmによるDockerクラスタ環境の構築(Dockerの最新機能を使ってみよう:第4回)
新たなサーバー環境構築ツールとして普及が始まっているDockerは、その開発も積極的に行われている。そこで本連載記事では、4回に渡って最近Dockerに実装された新機能について紹介していく。今回は、Dockerが開発するDockerクラスタ管理・運用ツール「Docker Swarm」について紹介する。
目次
Dockerを使ったクラスタ環境とは
Dockerでは1つのアプリケーションを1つのコンテナに入れ、複数のコンテナを組み合わせてサービスを構築する、という手法が推奨されている。このような構成によって各アプリケーションを分離することで、セキュリティの強化や開発・運用の効率化が期待できる。また、コンテナはデプロイが容易であるため、同じコンテナを複数のマシン上で同時に実行させる負荷分散構成を取りやすいというメリットもある。
Dockerを利用したクラスタ環境についてはDockerが登場した当初から議論されており、こういった環境を構築するためのソフトウェアは複数公開されている。代表的なものとしては以前紹介したKubernetesというツールがあるが、2015年11月にリリースされたDocker 1.9では、似たような機能を提供する「Docker Swarm」という機能が「production-ready」(製品として利用可能)になった。今回はこのDocker Swarmを使ったクラスタ環境構築について紹介していく。
Docker Swarmのアーキテクチャ
Dockerではdockerコマンドでコンテナの起動や停止、そのほか各種管理操作を行うようになっている。この際、dockerコマンドはバックグランドで動作しているDocker Daemon(dockerサービス)をAPI経由で操作して各種操作を実行するようになっている(図1)。
Dockerのデフォルト設定では、UNIXドメインソケット(通常は/var/run/docker.sock)経由でdockerコマンドとDocker Daemonのやり取りを行う仕組みになっているが、ネットワーク(TCP)経由でやり取りを行うことも可能だ。Docker Swarmではこれを利用し、「Swarm Manager」と呼ばれるソフトウェアが各ノードで動作するDocker Daemonを一括操作することで、クラスタ上でのコンテナ管理を実現している(図2)。
また、Swarm ManagerはDocker Daemonと同じAPIを実装している。そのため、Swarm ManagerではローカルのDockerを利用するのとまったく同じdockerコマンドを使ってコンテナの起動や停止といった管理操作を実行できる。つまり、Dockerの知識があれば特に新たな知識を必要とせず、そのままクラスタの管理が行えるのだ。
Docker Swarmでできること・できないこと
Docker Swarmではユーザーからのコンテナ作成要求に対し、Dockerクラスタを構成するノードから最適なノードを自動で選択してそこで要求されたコンテナを実行させることができる。また、各ノードで実行されているコンテナの状況を取得したり、コンテナの停止や再起動を行うことも可能だ。さらにノードが何らかのトラブルで停止した場合などに、停止したコンテナを別のノードで実行させる機能もある。
いっぽう、コンテナ間のネットワーク的な接続やルーティングについてはDockerが備えている以上の機能は提供されない。また、コンテナイメージの管理についても、Docker Swarmが特別な機能を提供するわけではない。そのため各ノードで実行させるコンテナのイメージはDocker Hub、もしくは自前で用意したコンテナイメージリポジトリに配備し、かつそれぞれのノードからそこにアクセスできるよう設定しておく必要がある。
Docker Swarmのインストールと設定
Docker Swarmを利用するためには、コンテナを動かすためのサーバーに加えて、Swarm Managerを実行させるサーバーが必要となる。また、ノードの管理情報などを格納するKey-Valueストアも必要だ。Key-Valueストアへのアクセスは「libkv」というライブラリを使って実現されており、現時点ではConsulおよびEtcd、Apache ZooKeeperが利用可能だ。
以下ではKey-ValueストアとしてEtcdを利用し、次の3つのホストを使った構成でDocker Swarmを利用したDockerクラスタを構築する例を紹介していく。
- ホスト1(fedora01):docker managerおよびEtcdを実行させるサーバー
- ホスト2(fedora02):コンテナを実行させるサーバー1
- ホスト3(fedora03):コンテナを実行させるサーバー2
なお、各ホストのOSにはFedora 23を使用している。
Docker Swarmのインストール
Docker SwarmはDockerと同様、GitHubでソースコードが公開されている(GitHubのdocker/swarmプロジェクト)。また、Dockerの公式リポジトリであるDockerHubではDocker Swarmのバイナリが含まれる公式コンテナイメージが公開されており(DockerHubのswarmリポジトリ)、DockerおよびDockerHubの利用が可能な環境であれば次のようにすることで容易にコンテナ内でDocker Swarmを実行できる。
# docker run -d swarm <swarmコマンドに与えるオプション>
もちろん、ソースコードからDocker Swarmwをビルドして利用することも可能だ。ちなみにDocker SwarmはGo言語で実装されているため、その場合はGo言語の処理系およびライブラリ一式(記事執筆時点での最新版であるDocker Swarm 1.2.3をビルドするにはGo 1.5系以降)が必要となる。Fedora 23では標準でバージョン1.5.4のGo処理系およびライブラリが提供されており、そちらをそのまま利用できる。具体的なビルド手順は以下の通りだ。
まず、ビルド用のディレクトリ(ここでは~/swarm_buildディレクトリ)を作成し、「GOPATH」環境変数にそのディレクトリのフルパス名を格納しておく。
$ mkdir ~/swarm_build $ cd ~/swarm_build $ export GOPATH=$(pwd)
続いて、ビルド用ディレクトリ内のsrc/github.com/docker/swarmディレクトリにDocker Swarmのリポジトリをクローンし、ビルドしたいバージョンに対応するタグをチェックアウトする。今回はバージョン1.2.2(v1.2.2)というタグをチェックアウトした。
$ git clone https://github.com/docker/swarm src/github.com/docker/swarm $ cd src/github.com/docker/swarm $ git checkout -b v1.2.2 v1.2.2
また、Docker SwarmではGo 1.5の実験的機能である「vendor」ディレクトリ内への依存ライブラリ配置機能を利用している(この機能はGo 1.6で正式版機能として利用可能になっている)。この機能を利用するため、「GO15VENDOREXPERIMENT」環境変数を1に設定しておく。
$ export GO15VENDOREXPERIMENT=1
以上の設定が完了したら、「go install」コマンドを実行することでビルドを行える。
$ go install .
ビルドが完了すると、ビルド用ディレクトリ(今回は~/swarm_buildディレクトリ)直下に作成されたbinディレクトリ内に「swarm」というバイナリが格納される。これを/usr/local/binディレクトリなどのパスが通っているディレクトリにコピーすればインストール完了だ。
# cp ~/swarm_build/bin/swarm /usr/local/bin
与えられる引数などは、「--help」オプション付きでswarmコマンドを実行することで確認できる。
$ swarm --help Usage: swarm [OPTIONS] COMMAND [arg...] A Docker-native clustering system Version: 1.2.2 (HEAD) Options: --debug debug mode [$DEBUG] --log-level, -l "info" Log level (options: debug, info, warn, error, fatal, panic) --experimental enable experimental features --help, -h show help --version, -v print the version Commands: create, c Create a cluster list, l List nodes in a cluster manage, m Manage a docker cluster join, j Join a docker cluster help Shows a list of commands or help for one command Run 'swarm COMMAND --help' for more information on a command.
Docker Swarmはコンテナを実行する各ノードにインストールしておく必要があるため、今回はこのようにしてビルドしたswarmファイルをそのまま各ノードの/usr/local/binディレクトリ以下にコピーした。Goで作成されたバイナリは依存するライブラリなどがすべて静的にリンクされるため、この単一ファイルをコピーするだけで同じアーキテクチャのほかのマシン上でも動作する。
Etcdの設定
Etcdの設定については、各ホストからEtcdにアクセスできるようにしておくのみで、特にDocker Swarmに向けて固有のものはない。これについては「Dockerのマルチホストネットワークで複数ホスト間を繋ぐ仮想ネットワークを作る」記事内の「複数のDockerホストにまたがるネットワーク(マルチホストネットワーク)を利用するための設定」で紹介しているので、そちらを参照してほしい。
Swarm Managerの起動
Docker SwarmのインストールおよびEtcdの設定が完了したら、「swarm manage」コマンドを実行してSwarm Managerを起動する。
$ swarm manage -H tcp://<ホストのIPアドレス>:3375 etcd://<Etcdを稼働させているホストのIPアドレス>:2379
今回は3375番ポートを使用してSwarm ManagerがDockerクライアント(dockerコマンド)からの接続を受け付けるよう設定している。そのため、外部から3375番ポートへの接続を許可するようファイアウォールの設定を変更しておく。たとえばfirewalldを使用している場合、以下のように実行すれば良い。
# firewall-cmd --add-port 3375/tcp
なお、今回は検証目的なのでシェルから直接Swarm Managerを起動させているが、運用環境では適切な起動スクリプトを用意し、サービスとして起動させるようにすることをおすすめする。
Swarm Managerを起動させたら、次のように「docker info」コマンドを「-H」オプション付きで実行してSwarm Managerに接続できるかを確認しておこう。この時点ではコンテナを実行させるためのノードを登録していないため、「Nodes」は0になっているはずだ。
$ docker -H tcp://<Swarm Managerを実行させているホストのIPアドレス>:3375 info Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 0 Server Version: swarm/1.2.2 Role: primary Strategy: spread Filters: health, port, containerslots, dependency, affinity, constraint Nodes: 0 Plugins: Volume: Network: Kernel Version: 4.4.6-300.fc23.x86_64 Operating System: linux Architecture: amd64 Number of Docker Hooks: 0 CPUs: 0 Total Memory: 0 B Name: fedora01 Registries:
コンテナを実行するノードでの設定
コンテナを実行するノードでは、以下の設定が必要となる。
- ホスト外からAPI経由で操作できるようDocker Daemonの起動オプションを追加
- ファイアウォールの設定
- Docker Swarmの起動
まず、ホスト外からDocker Daemonを操作できるようにするため、Docker Daemonの起動オプションに「-H tcp://<ノードのIPアドレス>:2375」を追加する。Red Hat Enterprise LinuxやCentOSなどその互換OS、Fedoraでは/etc/sysconfig/dockerファイル内に、Ubuntuでは/etc/default/dockerファイル内にこの設定を記述する。
Red Hat Enterprise LinuxやCentOS、Fedoraの場合、/etc/sysconfig/dockerファイル内の「OPTIONS=」行に以下のようにオプションを追加すれば良い。
OPTIONS='-H <ノードのIPアドレス>:2375 --selinux-enabled --log-driver=journald'
また、Ubuntuでは/etc/default/dockerファイル内でコメントアウトされている「DOCKER_OPTS」にこのオプションを記述する。
DOCKER_OPTS="-H <ノードのIPアドレス>:2375 "
なおこれらの設定を行った場合、各ホスト内でdockerコマンドを実行する際に「-H <ノードのIPアドレス>:2375」オプションを使用して明示的に接続先を指定する必要がある点に注意したい。
また、ホスト外から2375番ポートへの接続を許可するようファイアウォールの設定を変更しておく。たとえばfirewalldを使用している場合、以下のように実行すれば良い。
# firewall-cmd --add-port 2375/tcp
今回の設定ではDocker Daemonへのアクセスに対するセキュリティ保護を行っていない点には注意してほしい。この場合、外部からDocker Daemonに自由にアクセスできてしまうことになる。そのため、第三者からのアクセスが制限されたローカルネットワーク内のIPアドレスをDocker Daemonが使用するIPアドレスとして割り当てることを推奨する。
以上の設定が完了したらDocker Daemonを再起動し、問題がなければ続けて「swarm join」コマンドを実行してノードをクラスタに追加する。
$ swarm join --advertise=<ノードのIPアドレス>:2375 etcd://<Etcdを稼動させているホストのIPアドレス>:2379
この状態で「docker info」コマンドを実行すると、ノードが追加されていることを確認できる。
$ docker -H tcp://<Swarm Managerを稼動させているホストのIPアドレス>:3375 info Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 5 Server Version: swarm/1.2.2 Role: primary Strategy: spread Filters: health, port, containerslots, dependency, affinity, constraint Nodes: 1 fedora02: <ノードのIPアドレス>:2375 └ ID: HLMS:WYP5:EKY4:BNHZ:VHZE:NF6Z:SCJR:F3VO:IIFC:TR4Z:64JX:Y2BN └ Status: Healthy └ Containers: 0 └ Reserved CPUs: 0 / 1 └ Reserved Memory: 0 B / 1.018 GiB └ Labels: executiondriver=native-0.2, kernelversion=4.4.6-300.fc23.x86_64, operatingsystem=Fedora 23 (Twenty Three), storagedriver=devicemapper └ Error: (none) └ UpdatedAt: 2016-05-29T11:13:24Z └ ServerVersion: 1.10.3 Plugins: Volume: Network: Kernel Version: 4.4.6-300.fc23.x86_64 Operating System: linux Architecture: amd64 Number of Docker Hooks: 0 CPUs: 1 Total Memory: 1.018 GiB Name: fedora01 Registries:
ここでノードが追加されないと言ったトラブルが発生した場合は、Docker Managerを実行しているホストからそのノードのDocker Daemonにアクセスできるかどうか、以下のようにして確認してみよう。
$ docker -H tcp://<コンテナを実行させるノードのIPアドレス>:2375 version Client: Version: 1.10.3 API version: 1.22 Package version: docker-1.10.3-4.gitf8a9a2a.fc24.x86_64 Go version: go1.6 Git commit: f8a9a2a/1.10.3 Built: OS/Arch: linux/amd64 Server: Version: 1.10.3 API version: 1.22 Package version: docker-1.10.3-4.gitf8a9a2a.fc24.x86_64 Go version: go1.6 Git commit: f8a9a2a/1.10.3 Built: OS/Arch: linux/amd64
コンテナの起動
Docker Swarmを使って構成したクラスタ上でコンテナを起動するには、「docker run」コマンドを「-H tcp://<Swarm Managerを起動させているホストのIPアドレス>:3375」オプション付きで実行すれば良い。この際、ローカルホスト上で「docker run」コマンドを実行する場合と同様のオプションが指定できる。たとえば次の例は、「httpd」コンテナを実行するものだ。
$ docker -H tcp://<Swarm Managerを起動させているホストのIPアドレス>:3375 run -d --name httpd01 httpd
この状態で稼動しているコンテナを確認する「docker ps」コマンドを実行してみると、コンテナ名の前にそのコンテナを稼動させているホストの名前が付与されていることが分かる。下記の例では、「httpd01」コンテナは「fedora02」というホスト上で稼動している。
$ docker -H tcp://<Swarm Managerを起動させているホストのIPアドレス>:3375 ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4a458cf98650 httpd "httpd-foreground" 6 seconds ago Up 6 seconds 80/tcp fedora02/httpd01
なお、-Hオプションの代わりに「DOCKER_HOST」環境変数でSwarm Managerの稼動しているホストおよびポートを指定することも可能だ。
$ export DOCKER_HOST=<Swarm Managerを起動させているホストのIPアドレス>:3375
この環境変数を設定することで、-HオプションなしでDockerクラスタに対する操作を実行できるようになる。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4a458cf98650 httpd "httpd-foreground" 2 minutes ago Up 2 minutes 80/tcp fedora02/httpd01
Docker Swarmにおけるコンテナのスケジューリング機構
Docker Swarmのデフォルト設定では、コンテナがどのノードで起動されるかが自動的に決定される。この際の決定戦略としては「spread」および「binpack」、「random」の3つが用意されており、デフォルト設定ではspread戦略が使われる。
spread戦略は、各ノードができるだけ同じ数のコンテナを実行するようコンテナ実行先ノードを決定するもので、新たにコンテナを実行する際は最も稼動しているコンテナが少ないノードが選択される。また、binpack戦略では1つのノードにできるだけ多くのコンテナを稼動させるように実行先ノードが選択される。random戦略では完全にランダムで実行先ノードが選択される。
これらのうちどの戦略が使われているかは、docker infoコマンドで確認できる。
$ docker -H tcp://<Swarm Managerを起動させているホストのIPアドレス>:3375 info Nontainers: 2 Running: 2 Paused: 0 Stopped: 0 Images: 10 Server Version: swarm/1.2.2 Role: primary Strategy: spread Filters: health, port, containerslots, dependency, affinity, constraint : :
たとえばデフォルトのspread戦略の場合、2つのコンテナを実行すると、それぞれは別のコンテナで稼動する。
↓1つめのコンテナを起動 $ docker -H tcp://<Swarm Managerを起動させているホストのIPアドレス>:3375 run -d --name httpd01 httpd ↓2つめのコンテナを起動 $ docker -H tcp://<Swarm Managerを起動させているホストのIPアドレス>:3375 run -d --name httpd02 httpd ↓「fedora02」と「fedora03」の2つのホストでそれぞれコンテナが稼動する $ docker -H tcp://<Swarm Managerを起動させているホストのIPアドレス>:3375 ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 08ea1f436980 httpd "httpd-foreground" 30 minutes ago Up 30 minutes 80/tcp fedora03/httpd02 4a458cf98650 httpd "httpd-foreground" 34 minutes ago Up 34 minutes 80/tcp fedora02/httpd01
なお、Docker Swarmでは指定された条件を満たすようにコンテナを稼動させるノードを決定させる「フィルタ」という機能も用意されている。詳しくはDocker Swarmのドキュメントを参照して欲しいが、条件としてはホスト名やホストに指定したラベル、OS、特定のDocker設定などを指定できる。また、「指定したコンテナが稼動しているノード」や「指定したポートが利用できるノード」といった指定も可能だ。
たとえば「docker run」コマンドの引数として「-e constraint:node==<ホスト名>」というオプションを追加することで、指定したホスト名を持つノード上でコンテナを実行できる。たとえば次の例は、「fedora03」というノード上でhttpdコンテナを実行するものだ。
$ docker -H tcp://<Swarm Managerを起動させているホストのIPアドレス>:3375 run -e constraint:node==fedora03 -d --name httpd04 httpd
コンテナのリスケジューリング
Docker Swarmでは、なんらかの原因でコンテナが停止した際にコンテナを再起動するリスケジューリング機能が用意されている。この機能はデフォルト設定では無効になっているため、利用するにはコンテナの起動時に明示的にリスケジューリングを行うよう指定する必要がある。
リスケジューリングを有効にするには、コンテナに与える「reschedule」環境変数に「on-node-failure」という値をセットするか、もしくは「com.docker.swarm.reschedule-policy」ラベルに「on-node-failure」を指定れば良い。具体的には、「docker run」コマンド時に前者の場合は「-e reschedule:on-node-failure」オプションを、後者の場合は「-l 'com.docker.swarm.reschedule-policy=["on-node-failure"]'」というオプションを追加すれば良い。
たとえば以下の例では、reschedule環境変数を設定する方法でリスケジューリングを有効にしてhttpdコンテナを起動している。
$ docker -H tcp://<Swarm Managerを起動させているホストのIPアドレス>:3375 run -e reschedule:on-node-failure -d --name httpd01 httpd
続いて「docker ps」コマンドで確認すると、この例ではfedora02というノード上でこのコンテナが起動していることが分かる。
$ docker -H tcp://<Swarm Managerを起動させているホストのIPアドレス>:3375 ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 69d5a01f0647 httpd "httpd-foreground" About a minute ago Up About a minute 80/tcp fedora02/httpd01
この状態でfedora02ノードを停止させてみると、Swarm Managerで次のようなログが表示され、ノードが利用できなくなったことを認識すると同時に、コンテナを別のノードで再起動させていることが分かる。
INFO[5462] Removed Engine fedora02 INFO[5463] Rescheduled container 69d5a01f0647990750d426d141a0fa540ecaba47181b0f4f4571de7c08cf960a from fedora02 to fedora03 as 7f7d42b986cff4225de9f9f5f2f3a17fee8a6d3ed2a6ff171f3bdaf4cffdd551 INFO[5463] Container 69d5a01f0647990750d426d141a0fa540ecaba47181b0f4f4571de7c08cf960a was running, starting container 7f7d42b986cff4225de9f9f5f2f3a17fee8a6d3ed2a6ff171f3bdaf4cffdd551
この状態で「docker ps」コマンドでコンテナの稼働状況を確認すると、httpd01コンテナがfedora03ノードで再起動されていることが分かる。
$ docker -H tcp://<Swarm Managerを起動させているホストのIPアドレス>:3375 ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7f7d42b986cf httpd "httpd-foreground" About a minute ago Up About a minute 80/tcp fedora03/httpd01
Docker Swarmを利用した場合のクラスタ構成とネットワーク構成
Docker SwarmでDockerクラスタを構築してサービスを運用する場合、どのようなネットワーク構成を取るかを検討しなければならない。Docker Swarmでは特別なネットワークルーティング機能などは用意されないため、単純にコンテナを起動するだけでは、異なるノード上にあるコンテナ間の通信すら行えない。そのためDockerが提供するオーバーレイネットワーク機能や、異なるホスト上にあるコンテナ間の通信を可能にするようなネットワークプラグイン等を利用して独自に設定を行う必要がある。
もっとも簡単なのは、以前の記事で紹介したDockerのマルチホストネットワーク機能を利用するものだ。Docker 1.9以降ではデフォルトでこの機能が利用できるため、簡単に異なるノード上のコンテナ同士を通信させることができる。
また、Docker SwarmによるDockerクラスタを構築する際のもう1つの課題として、クラスタ外からのアクセスを受け付けるコンテナをどう配置するか、というものがある。「docker run」コマンドにはコンテナを実行するホストの指定したポートへのアクセスをコンテナ内に転送する設定を行う「-p」オプションがあり、これを利用してコンテナ内で稼動しているサービスを外部に公開することが一般的だ。しかしDocker Swarmでコンテナを起動する場合、デフォルト設定では状況によってコンテナを実行するホストが変わってしまうため、外部に公開するIPアドレスを一意に決定できないという問題が発生する。
これを解決する方法の1つに、クラスタ外からのアクセスを受け付けるコンテナ(フロントエンドコンテナ)を稼動させるノードを前述のフィルタ機能を利用して固定し、そのノードに対してロードバランサでアクセスを振り分ける、というものがある(図3)。
この構成ではフロントエンドコンテナを実行するノードを固定し、かつノードの特定ポート(たとえばHTTPであれば80番)をフロントエンドコンテナに転送するよう設定することで、ロードバランサから振り分けられたコネクションをフロントエンドコンテナが受け付けられるように設定できる。この場合、フロントエンドコンテナ1つにつき1つのノードが必要となるというデメリットがあるが、ノードの増減を制御しやすいクラウド環境内などであればこちらの構成が容易だ。
また、コンテナ内でロードバランサを実行するという構成も考えられる(図4)。こちらの場合、フロントエンドコンテナではなくロードバランサコンテナを実行するノードを固定し、そこに外部のロードバランサやファイアウォールからのアクセスを振り分けるという形になる。
Dockerのマルチホストネットワーク機能では、内部的に立ち上げられたDNSサーバーを利用することで、コンテナ名だけでネットワークアクセスを行うことが可能になる。これを利用して適切に設定を行っておけば、フロントエンドコンテナがどのノードで稼動してるかに関わらずロードバランサコンテナからフロントエンドコンテナへのアクセスが可能になる。
この構成ではロードバランサが冗長になりやすいというデメリットがあるが、フロントエンドコンテナを柔軟に配置できるようになる。使用するインフラ環境や目的に応じて、どのような構成を取るのかを検討すると良いだろう。
必要十分の機能を提供するDocker Swarm
Dockerを利用したクラスタ構築ツールとしてはKubernetesが先行していたが、Docker Swarmも正式リリース版となり、機能的には必要十分なものを提供している。Docker Swarmは特別なクライアント等を利用することなしにDockerクラスタを構築でき、ネットワークなどもDockerの標準機能をそのまま利用するため習得しやすいというメリットがある。
いっぽうで、Docker SwarmではKubernetesが持つ、依存関係のあるコンテナをまとめて起動・管理するといった機能は提供されていない。この機能は「Docker Compose」と呼ばれる別のツールで提供されており、これを利用することが推奨されている。今回はDocker Composeについては触れていないが、興味のある方はドキュメント等を参照してほしい。