Serf/Consulで管理を自動化! ~実践的な手法を紹介~

第3回Dockerコンテナ群をSerfで管理する方法・詳細オプション

これまではSerfの基本的な使い方や、Serfで構成されたイベントハンドラの動きを見てきました。今回はSerfに関する連載のまとめです。複数の仮想サーバ上にも応用可能なように、Dockerコンテナでの活用方法をご紹介します。また、より詳細な設定方法についての理解も進めていきます。

Dockerコンテナ群をSerfで管理する方法

DockerはLinuxコンテナを簡単に起動・管理するためのツールです。Dockerの魅力は、単純にコンテナを操作できる点だけではありません。公開レポジトリであるDockerHubを使ったコンテナの共有や、Dockerfileと言う設定ファイルを使った構成管理の自動化などの利点があります。

コンテナ管理問題を解決するSerf

コンテナを多く立ちあげた時に発生するのは、沢山のコンテナを効率的に操作するのかという課題です。例えばコンテナ内のプロセス稼働状況や、設定ファイルの確認を行いたい場合を考えてみます。Dockerではsshdを稼働させなくとも、ホストOS側からdocker execコマンドを実行することにより、任意のコマンドを実行することができます。

docker execは、小回りの利く便利なコマンドです。しかし、操作対象のコンテナが1つや2つではなく、3つ・4つ・5つ……と数が増えると都度docker execを実行するのは手間になりがちです。また、操作対象が増えると、コンテナに対して適切な管理が必要になります(このコンテナの役割は何か? 何のアプリケーションを動かすのか? 誰が管理しているのか?⁠⁠。

この問題を解決するために、テキストファイルやExcelなど、何らかの管理表を作成するのは1つの方法と言えます。とは言え、コンテナを簡単に立ち上げ、消せるのがDockerの利点です。これではコンテナ管理が面倒になり、本末転倒です。

そこで登場するのが、Serfを使ってコンテナ群を管理する方法です。Serfエージェントがクラスタを構成できるのは物理・仮想サーバ間だけでなく、コンテナとも通信することが可能です。Serfを使うことによって、次のような利点があります。

メンバー管理の自動化

Serfがコンテナのホスト名とIPアドレスを自動的に管理するため、コンテナの起動・停止時に人手で管理する必要がありません。

タグ機能でコンテナの役割を明確化

Serfはエージェントにタグを動的に付与でき、コンテナの役割や管理担当者の情報を動的に付与できます。

イベントハンドラでコンテナ内の一斉操作

コンテナにログインすることなく任意のコマンドを一斉に実行できます。開発環境やテスト環境を問わず、有用な機能です。

いずれも手動でも行えますが、Serfであれば正確かつ迅速に行うことができます。しかも、これまでの連載で見て来たように、Serfそのものを使うために覚えることは少ないため、低い学習コストで高い効率性を得られるようになります。

図1は1つのホスト上で複数のコンテナを管理するイメージです。この図ではホストOS側・コンテナ群どちらもSerfでクラスタを構成しています。もちろん、ホスト側が不要であれば、コンテナ群だけでクラスタを構成することもできます。IPアドレスはコンテナ間の通信で使うためのものです。ホストOS側は172.17.42.1で固定されているため、Serfエージェント起動時のクラスタへのジョイン先をこの172.17.42.1にしておくと便利です。

図1 SerfでLinuxコンテナを一括管理
図1 SerfでLinuxコンテナを一括管理

自動的にSerfクラスタを構成するDockerfile

Dockerでコンテナを起動・停止するたびに、ホスト側のSerfエージェントに自動追加・削除する方法を見ていきましょう。Serfのセットアップを自動的に行うコンテナイメージを作成しておき、そのイメージを使ってコンテナを起動し、自動的にSerfのクラスタに参加するものです。また、コンテナ内にログインしなくても操作できるように、イベントハンドラでbashを実行させてみます。

前提として、ホスト側では予めSerfエージェントがserf agent等のコマンドで起動しているものとします。この時、Docker上のコンテナと通信時に使用する172.17.42.1をバインドしている必要があります(通常は起動したままで自動的に認識しますが、serf agent -bind=172.17.42.1として明示することもできます⁠⁠。

自動的にSerfを起動するためにはDockerfileを用います。Dockerには、コンテナ内部のOS設定やミドルウェア・アプリケーションを自動的に設定するための仕組みがあります。これはChefやPuppet・Ansibleのような構成管理ツールの機能を実現します。これを使う場合は、Dockerfileという名称のテキスト形式のファイルを作成し、中にどのような環境を構築するか定義します。

作業用のディレクトリを作成し、そこに移動したあと、以下のDockerfileを作成します。内容はcentosのコンテナイメージの使用を明示し、wgetunzipパッケージの取得を行っています。それからserfのアーカイブを取得・展開し、バイナリをコピーの後、自動的にserfを起動するものです。

FROM centos

RUN yum -y install wget unzip
RUN wget -O 0.6.4_linux_amd64.zip https://dl.bintray.com/mitchellh/serf/0.6.4_linux_amd64.zip
RUN unzip 0.6.4_linux_amd64.zip
RUN cp ./serf /usr/bin/serf

ENTRYPOINT ["serf"]
CMD ["agent","-join=172.17.42.1","-event-handler=query:ssh=/bin/sh"]

※ 実際のDockerfileは、この他にも様々な環境のセットアップやデーモンの稼働に関する記述を行います。Serfに関する箇所以外は、環境に合わせて書き換えてお使いいただくこともできます。

次に、このDockerfileを元にdocker buildコマンドを使い、イメージのビルドを行います。次の例ではserf-centosというタグを付けていますが、<ユーザ名>/serf-centosなど、任意のタグ名を使うことができます。

# docker build -t serf-centos .
Sending build context to Docker daemon  3.31 MB
Sending build context to Docker daemon
Step 0 : FROM centos
 ---> 9dbcac75201e
Step 1 : RUN yum -y install wget unzip
 ---> Using cache
 ---> 7d35b2710d1d
Step 2 : RUN wget -O 0.6.4_linux_amd64.zip https://dl.bintray.com/mitchellh/serf/0.6.4_linux_amd64.zip
 ---> Using cache
 ---> 6f943b9c59cd
(省略)
Successfully built 5aa497793ab2

画面上にSuccessfullyと表示されれば、正常にserf-centosという名称のコンテナイメージが作成できました。次は、このコンテナイメージを使って起動します。docker runコマンドを使い、コンテナをデーモンとして立ち上げてみます。

# docker run -d serf-centos
175dbd9034122edff3af72804c1a8cc8e968ebed5d53c25259e375f82b4b40f4

コンテナが正常に稼働しているかどうかは、docker psコマンドで確認することができます。ここでは自動的にSerfクラスタを形成しているか確認するためにserf membersコマンドを実行してみます。正常であれば、次のようにホスト側と起動したコンテナのホスト名・IPアドレスの情報を確認することができます。

# serf members
dev2.pocketstudio.net  172.17.42.1:7946  alive
3f42611e8fe7           172.17.0.10:7946  alive

同時に複数台の起動も試してみましょう。先ほど同様docker run -d serf-centosを数回実行した後、serf membersコマンドを実行します。次のように、やはり自動的に追加されていることが確認できます。

# serf members
dev2.pocketstudio.net  172.17.42.1:7946  alive
3f42611e8fe7           172.17.0.10:7946  alive
0f8a23cdc921           172.17.0.11:7946  alive
4bbedca26a70           172.17.0.14:7946  alive
175dbd903412           172.17.0.15:7946  alive

コンテナ内をserf queryを使って同時操作

Serfのイベントハンドラsshを使い、コンテナにログインすることなく、コンテナ内で一斉にコマンドを実行してみます。引き続きホストOS上で作業を行います。ここで実行するのは、前回の連載で使用したserf queryコマンドを使用します。任意のコマンドを実行するにはserf eventまたはserf queryを使う方法がありますが、ここでは結果を実行した画面に表示させたいためquery形式を定義していました。結果を知る必要がなければevent形式でも構いません。

例として、コマンドserf ssh uptimeを実行してみます。正常に処理されると、次のように各コンテナ内部でuptimeが実行され、その結果が表示されます。それぞれのコンテナ内では、イベントsshが発生すると、/bin/bashと引数としてのコマンドを実行した結果が表示されます。

# serf query ssh uptime
Query 'ssh' dispatched
Ack from 'dev2.pocketstudio.net'
Ack from '0f8a23cdc921'
Response from '0f8a23cdc921':  09:11:04 up  2:23,  0 users,  load average: 0.17, 0.06, 0.02
Ack from '4bbedca26a70'
Response from '4bbedca26a70':  09:11:04 up  2:23,  0 users,  load average: 0.17, 0.06, 0.02
Total Acks: 3
Total Responses: 2

ほかにもpsコマンドでプロセスの状況を知ることもできます。

# serf query ssh "ps ax"
Query 'ssh' dispatched
Ack from 'dev2.pocketstudio.net'
Ack from '4bbedca26a70'
Response from '4bbedca26a70':   PID TTY      STAT   TIME COMMAND
    1 ?        Ssl    0:01 serf agent -join=172.17.42.1 -event-handler=query:sh=/bin/sh
   19 ?        S      0:00 /bin/sh
   20 ?        R      0:00 ps ax
Ack from '175dbd903412'
Response from '175dbd903412':   PID TTY      STAT   TIME COMMAND
    1 ?        Ssl    0:00 serf agent -join=172.17.42.1 -event-handler=query:sh=/bin/sh
   16 ?        S      0:00 /bin/sh
   17 ?        R      0:00 ps ax
Total Acks: 5

これはあくまで一例ですので、catyumコマンドだけでなく、様々なコマンドを実行することができます。また、イベントハンドラの例として/bin/bashを指定しました。しかしroot権限で何でも実行できてしまうため、インターネット上の共用環境など、セキュリティ上で好ましくない場合、特定のコマンドのみ実行可能なイベントハンドラを作っておくことを強く推奨します。

メンバー管理とタグ

Serfはエージェントのメンバー毎にタグを付ける機能があります。コンテナを複数起動している場合、そのコンテナの役割や目的を記録するために、タグの機能を使えます。タグの情報はserf membersコマンドで参照できるだけではなく、イベントハンドラ内でも変数として利用できます。そのため、タグ機能を応用すると、イベントハンドラ内で特定のタグを持つコンテナやサーバのみコマンドを実行させる使い方もあります。

タグを指定するにはserf tags -set <key>=<value>の形式でコマンドを実行します。コマンドの実行は、ホストOS、コンテナ内どちらでも構いません。以下はrole=adminというタグを設定し、serf membersコマンドで見た結果です。

$ serf tags -set role=admin
Successfully updated agent tags

$ serf members
dev2.pocketstudio.net  172.17.42.1:7946  alive  role=admin
3f42611e8fe7           172.17.0.10:7946  alive
0f8a23cdc921           172.17.0.11:7946  alive

タグの削除はserf tag -delete <key>の形式で指定します。また、エージェント起動時にタグを指定することも可能です。

Serfの詳細設定

Serfエージェントの様々な設定は、serfコマンドの引数で指定する方法と、外部の設定ファイルを使って定義する方法があります。ここでは、より細かな設定方法と合わせて、使い方を見ていきます。

外部の設定ファイルを使う方法

Serfを同じ設定で起動・停止したい場合や、複数のSerf動作環境で同じ設定を適用したい場合は、外部ファイルの利用が手軽です。ファイルは次のようなJSON形式で定義します。

{
  "node_name": "dev01",         # ノード名称を「dev01
  "tags": {
    "role": "develop"           # タグ「role=develop
  },
  "interface": "eth1",          # インターフェースを「eth1
  "discover": "serf-cluster",   # オート・ディスカバリで「serf-cluster」を検索
  "event_handlers": [
    "ssh=/bin/bash"             # イベントハンドラ「ssh=/bin/bash」を定義
  ]
}

このJSON形式のファイルを作成した後、Serf起動時に-config-file=オプションを指定します。

$ serf agent -confg-file=/etc/serf.json

JSON形式の場合は、Serfの設定変更と反映が簡単になります。エージェント起動後でも設定変更したい場合は、対象のJSONファイルを修正後、kill -1 <serfのPID>を実行します。

Serfの便利オプション

Serfには様々なオプションが用意されています。ここでは、その中でも主に使う主要なものを紹介していきます。なお説明は「JSONファイルでの指定名 ( -コマンドラインのオプション )」の形式です。

node_name(-node)

Serfクラスタ内の自分のノード名称を指定するもので、serf membersコマンド実行時に表示されます。省略時は自分自身のホスト名が利用されます。なお、Serfクラスタ内でノード名称の重複はできないため、設定ファイルをコピーして流用する時は注意が必要です。

  • 設定ファイル:"node_name": "dev01"
  • コマンドライン:serf agent -node=dev01
bind(-bind)

Serfエージェントが内部の通信で利用するIPアドレスを指定します。通常は指定する必要はありませんが、サーバが複数のインターフェースを持っている場合、どのIPアドレスを使うか明示するために使います。

  • 設定ファイル:"bind": "192.168.39.3"
  • コマンドライン:serf agent -bind=192.168.39.3
interface(-iface)

Serfが利用する標準インターフェース名を指定します。複数のインターフェースを持っている環境で多用します。

  • 設定ファイル:"interface": "eth1"
  • コマンドライン:serf agent -iface=eth1
badvertise(-advertise)

Serfクラスタ内の自分のIPアドレスを指定します。bindの指定に似ていますが、こちらはインターフェースが複数のIPアドレスを持っている場合や、NATの環境で明示したい時に使います。

  • 設定ファイル:"advertise": "192.168.39.1"
  • コマンドライン:serf agent -advertise=192.168.39.1
discover(-discover)

マルチキャストDNS(mDNS)がネットワーク内で利用可能な場合にオプションを指定すると、serf joinで参加するクラスタを明示しなくても自動的にクラスタを形成します。この引数として指定したクラスタ名と一致する場合に自動参加します。

  • 設定ファイル:"discover": "development"
  • コマンドライン:serf agent -discover=development
encrypt_key(-encrypt)

Serfで暗号化したネットワーク通信を行う場合に使う秘密鍵を指定します。予めserf keygenコマンドで生成した文字列を引数として使います。暗号化してクラスタを形成すると、ここで指定した鍵が一致しないとクラスタに参加できません。

  • 設定ファイル:"encrypt_key": "<文字列>"
  • コマンドライン:serf agent -encrypt=<文字列>
log_level(-log-level)

Serfエージェント起動後に標準出力されるログの粒度を指定します。種類には情報が多い順にtrace、debug、info、warn、errを選べます。デフォルトはinfoです。

  • 設定ファイル: "log_level": "debug"
  • コマンドライン: serf agent -log-level=debug
start_join(-join)

Serfエージェント起動時、どのSerfエージェントと通信を開始してクラスタを形成するか明示します。

  • 設定ファイル:"start_join": "192.168.39.1"
  • コマンドライン:serf agent -join=192.168.39.1

その他にも設定用のオプションがあります。詳細はSerfのConfigurationページが参考になります。

まとめ

Dockerを使ったコンテナ環境に対しても、Serfを使えばコンテナにログインすることなくコマンドの実行が可能です。また複数のコンテナに対しても同時に実行できます。これはコンテナだけでなく、複数のサーバ環境にも応用できます。皆さんの環境なり現場で作業の効率化・正確さが必要な場面があれば、Serfのことを思い出して使っていただければと思っています。Serfそのものは単なるツールにしかすぎませんが、イベントハンドラを工夫することで、十徳ナイフのように有用なものへと活用できるでしょう。

参考情報

おすすめ記事

記事・ニュース一覧