Fleetの使い方,Unitファイルの書き方

CoreOSに入門した | SOTA

CoreOSではすべてのアプリケーションをDockerで動かす.このとき,コンテナによるサービスをCoreOSクラスタのどのマシンで起動するかをいちいち人手で決めるわけにはいけない.クラスタ内のリソースの状態や動いているサービスに基づき,適切なマシンでコンテナを動かすスケジューリングの仕組みが必要になる.

このスケジューリングとコンテナの管理にCoreOSはfleetを用いる. fleetを使うとCoreOSクラスタが1つのinit systemで動いているかのようにそれを扱うことができるようになる.開発者はどのマシンでどのDockerコンテナが動いているかを気にする必要がなくなる.

例えば,5つのコンテナを動かす必要があれば,fleetはクラスタのどこかでその5つのコンテナが動いてることを保証する.もしコンテナが動いているマシンに障害があっても,fleetはそのコンテナを別のマシンにスケジューリングしなおす(フェイルオーバー).

スケジューリングは柔軟で,マシンのRegionやRoleによって振り分けることもできるし,同じサービスを同じマシンでは動かさないようにするといった設定もできる.例えば,複数のDBコンテナを別々のマシンに分散させるといったこともできる.

DigitalOceanの“Getting Started with CoreOS”シリーズの

において,fleetを操作するためのfleettclコマンドの使い方と,その設定ファイルであるUnitファイルの書き方を良い感じに解説していたので,それらを参考にfleetの使い方をまとめておく.

まずfleetの技術的概要をまとめる,次にfleetctlコマンドによるサービスの管理方法を書く.最後にUnitファイルの書き方について説明する.

fleetの技術的概要

fleetはクラスタレベルのsystemdと捉えることができる(単一マシンのinit systemがsystemdで,クラスタのinit systemがfleet).

https://coreos.com/assets/images/media/fleet-schedule-diagram.png

https://coreos.com/assets/images/media/fleet-schedule-diagram.png

fleetはengineagentという大きく2つのコンポーネントから構成される.engineはジョブスケジューリングとクラスタサイズの変更を管理する.agentはマシンの代わりにジョブを引き受ける.Unitがクラスタに割り当てられると,agentはUnitファイルを読み込み,それを開始する.そして,systemdの状態をfleetに通知する.

バックエンドではetcdクラスタが動いており,engineagentの協調に使われる.

fleetctlによるサービスの管理

fleetの設定ファイルは,systemdのunitファイルにfleet特有の設定(e.g., クラスタ内での分散方法など)を加えたものを利用する. このファイルの詳細は後述するとして,ここでは以下のようなHello Worldを出力しつづけるhello.serviceを利用する.

[Unit]
Description=My Service
After=docker.service

[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill hello
ExecStartPre=-/usr/bin/docker rm hello
ExecStartPre=/usr/bin/docker pull busybox
ExecStart=/usr/bin/docker run --name hello busybox /bin/sh -c "while true; do echo Hello World; sleep 1; done"
ExecStop=/usr/bin/docker stop hello

fleetによるサービス管理はfleetctlコマンドを使って行う.サービスの起動は以下の流れで行われる.

  1. Unitファイルを読み込む
  2. クラスタ内の特定のマシンにスケジューリングする
  3. サービスを起動する

サービスの登録

まず,submitコマンドを使ってサービスを登録する.これは単にfleetがファイルをメモリ内に読み込むだけ.

$ fleetctl submit hello.service

fleetが読み込んだunitファイルは以下で一覧できる.

$ fleetctl list-unit-files
UNIT            HASH    DSTATE          STATE           TARGET
hello.service   3f8de4b inactive        inactive        -

DSTATEとは望まれる状態であり,STATEは実際の状態を示す(DSTATESTATEが一致しているとき各種コマンドがちゃんと動いたと考えて良い).TARGETはサービスを実行するべきマシンを示す.今はまだスケジューリングをしていないので,-となる.

読み込んだファイルの内容を確認することもできる.

$ fleetctl cat hello
[Unit]
Description=My First Service
After=docker.service
...

submitコマンドを一度実行し,Unitファイルを変更して再びsubmitしてもアップロードは実行されない.ファイルを更新するには,一度アップロードしたものを削除する必要がある.

サービスのスケジューリング

次に,サービスをスケジューリングする.スケジューリングとは,fleetエンジンがクラスタ内でサービスを実行するのに最も適したマシンを選択することである.これはUnitファイルの[X-Fleet]セクションの既述と,クラスタ内のマシンの現在のリソース状態に基づき決定される.サービスがスケジューリングされると,Unitファイルはそのマシンに渡され,ローカルのsystemdインスタンスに読み込まれる.

スケジューリングは,loadコマンドで行う.

$ fleetctl load hello.service
Unit hello.service loaded on 0d8b5e37.../10.132.181.182

読み込んだUnitファイルを確認する.

fleetctl list-unit-files
UNIT            HASH    DSTATE  STATE   TARGET
hello.service   3f8de4b loaded  loaded  0d8b5e37.../10.132.181.18

TAGETセクションにサービスを実行するマシンが追加されているのが確認できる.STATEはスケジューリングされたことを示している.

list-unitsコマンドを使うと,実行中,もしくはスケジューリングされたサービスとその状態を確認することができる.

$ fleetctl list-units
UNIT            MACHINE                         ACTIVE          SUB
hello.service   0d8b5e37.../10.132.181.182      inactive        dead

これはsystemdから得られる情報であり,ACTIVEはサービスの状態を示し,SUBはより低レベルな状態を示す.

サービスを起動する

次にサービスを起動してみる.起動はstartコマンドで行う.

fleetctl start hello
Unit hello.service launched on 0d8b5e37.../10.132.181.182

Unitファイルの状態を確認する.

$ fleetctl list-unit-files
UNIT            HASH    DSTATE          STATE           TARGET
hello.service   3f8de4b launched        launched        0d8b5e37.../10.132.181.182

次に,systemdの状態を確認する.

$ fleetctl list-units
UNIT            MACHINE                         ACTIVE  SUB
hello.service   0d8b5e37.../10.132.181.182      active  running

ちゃんとサービスが起動したことが確認できる.

サービスの詳細の状態を得る

list-unitコマンドで,現在スケジュールされているUnitを一覧でき,list-unit-filesでfleetが知っている*全て*のUnitの状態を一覧できる.

一覧ではなく,各Unitの詳細をみることもできる.例えば,statusコマンドを使うと,そのUnitが動いているマシンのsystemctl statusの結果を取得することができる.

$ fleetctl status hello
● hello.service - My First Service
   Loaded: loaded (/run/fleet/units/hello.service; linked-runtime)
      Active: active (running) since Sun 2014-11-16 07:02:10 UTC; 8min ago
        Process: 11863 ExecStartPre=/usr/bin/docker pull busybox (code=exited, status=0/SUCCESS)
        Process: 11853 ExecStartPre=/usr/bin/docker rm hello (code=exited, status=0/SUCCESS)
        Process: 11844 ExecStartPre=/usr/bin/docker kill hello (code=exited, status=0/SUCCESS)
        Main PID: 11873 (docker)
           CGroup: /system.slice/hello.service
              └─11873 /usr/bin/docker run --name hello busybox /bin/sh -c while true; do echo Hello World; sleep 1; done

Nov 16 07:10:59 core2 docker[11873]: Hello World
Nov 16 07:11:00 core2 docker[11873]: Hello World
Nov 16 07:11:01 core2 docker[11873]: Hello World
Nov 16 07:11:02 core2 docker[11873]: Hello World

journalコマンドを使うと,各サービスのjournalのエントリをみることもできる.

$ fleetctl journal hello
-- Logs begin at Sat 2014-11-15 10:23:32 UTC, end at Sun 2014-11-16 07:12:54 UTC. --
Nov 16 07:12:44 core2 docker[11873]: Hello World
Nov 16 07:12:45 core2 docker[11873]: Hello World
Nov 16 07:12:46 core2 docker[11873]: Hello World
Nov 16 07:12:47 core2 docker[11873]: Hello World

tail -fみたいなこともできる.

$ fleetctl journal -f hello

スケジューリングされたマシンにログインしていろいろ調査することもできる.このときスケジューリングされたマシンのIPを知る必要はなく,サービス名でログインできる.

$ fleetctl ssh hello

サービスの停止

まず,stopコマンドでサービスを停止できる.

$ fleetctl stop hello
Unit hello.service loaded on 0d8b5e37.../10.132.181.182
 $ fleetctl list-unit-files
 UNIT            HASH    DSTATE  STATE   TARGET
 hello.service   3f8de4b loaded  loaded  0d8b5e37.../10.132.181.182

これにより,サービスはloaded状態に戻ったことが確認できる.これはサービスは止まったが,スケジューリングされたマシンのsystemdには読み込まれた状態である.

これを削除するには,unloadコマンドを使う.

$ fleetctl unload hello
Unit hello.service inactive
$ fleetctl list-unit-files
UNIT            HASH    DSTATE          STATE           TARGET
hello.service   3f8de4b inactive        inactive        -

inactive状態になり,ターゲットのマシンも消えていることが確認できる.これは,スケジューリングを解除し,fleetにUnitファイルが読み込まれただけの状態である.

fleetが読み込んだUnitファイルを削除するには,destroyコマンドを使う.

$ fleetctl destroy hello
Destroyed hello.service
$ fleetctl list-unit-files
UNIT    HASH    DSTATE  STATE   TARGE

完全に消えた.

Unitファイル

fleetによるスケジューリングはUnitファイルにより行う.UnitファイルはsystemdのUnitファイルにfleet特有の[X-Fleet]セクションを加えたものを使う.systemdについて以下の記事が詳しい.

Unitファイルの名前

Unitファイル名には命名規則がある.string.suffixもしくはstring@instance.suffixとする.

  • stringは必須.Unitの名前を指定する. [a-zA-Z0-9:_.@-]+の正規表現に一致する必要がある
  • instanceは必須ではない.1つのUnitファイルから複数のUnitインスタンスを作成するときに利用する(例えば,hello@.serviceファイルからhello@1.servicehello@2.serviceを作る).この値はUnitファイル内から%iとして参照することもできる. [a-zA-Z0-9:_.@-]+の正規表現に一致する必要がある
  • suffixは必須.Unitの属性を指定する.service, socket, device, mount, automount, timer, pathのいずれか.

セクション

一般的なfleetの設定ファイルは以下のようになる(serviceの場合).

[Unit]
generic_unit_directive_1
generic_unit_directive_2

[Service]
service_specific_directive_1
service_specific_directive_2
service_specific_directive_3

[X-Fleet]
fleet_specific_directive

[Unit]セクションには,Unitの依存関係/順序関係など,Unitのタイプに依存しない設定を既述する(systemdと同様).

[Service]セクションには,service固有の設定を書く.例えば,起動・停止コマンドや,サービス起動前に実行するべきコマンド,環境変数ファイルの場所などを既述する(systemdと同様).

[X-Fleet]セクションには,fleet特有の設定を書く.どのようにクラスタにスケジューリングを行うかを定義する.具体的には以下のような設定ができる.

  • MachineID - 特定のマシンにスケジューリングしたときに利用する.マシンのIDを指定する(fleetctl list-machines -l ).
  • MachineOf - 特定のUnitが実行されているマシンと同様のマシンにスケジューリングしたときに利用する.Unit名を指定する.
  • MachineMetadata - マシンのメタデータに基づきスケジューリングしたいときに利用する.例えばRegionやRole, diskTypeなど.これらはcloud-configfleet.metadataの項目で指定できる.
  • Conflicts - 特定のUnitが実行されているマシンを避けたいときに利用する.MachineOfの逆.
  • Global - すべてのマシンにスケジューリングしたいときに利用する.

Apacheサービス

具体例としてApacheサービスを起動するためのUnitファイルを作る.ファイル名はapache@.serviceとする.

[Unit]
Description=Apache web server service
After=etcd.service
After=docker.service
Requires=apache-discovery@%i.service

[Service]
TimeoutStartSec=0
KillMode=none
EnvironmentFile=/etc/environment
ExecStartPre=-/usr/bin/docker kill apache%i
ExecStartPre=-/usr/bin/docker rm apache%i
ExecStartPre=/usr/bin/docker pull user_name/apache
ExecStart=/usr/bin/docker run --name apache%i -p $COREOS_PUBLIC_IPV4:%i:80 user_name/apache /usr/sbin/apache2ctl -D FOREGROUND
ExecStop=/usr/bin/docker stop apache%i

[X-Fleet]
X-Conflicts=apache@*.service

まず[Unit]セクションには,サービス名とこのサービスを起動するための依存を既述する.Afterにはこのサービスが起動する前に起動されているべきサービスを,Requireにはこのサービスが必要とするサービスを既述する.

Requireに書いたサービスが失敗したらこのサービスも起動に失敗する.失敗してもサービスを継続したい場合はWantを使う.

次に[Service]セクションには,具体的なサービスの起動・停止方法を書く.TimeoutStartSecを0にしてタイムアウトを無効にしている(デフォルトは90秒).これはDockerイメージのpullに時間がかかる可能性があるため.

EnvironmentFileを指定するとその中に既述された環境変数が有効になる./etc/environmentにはCOREOS_PUBLIC_IPV4といった値が定義されている.

ExecStartPreにはサービス起動前に実行するべきコマンドを定義する.=-とした場合は,失敗してもサービスの起動を続行する(一度目の起動はdocker killdocker rmは必ず失敗する).そしてExecStartExecStopで起動,停止コマンドを指定する.

最後に[X-Fleet]には,fleetのスケジューリングに関わる設定を既述する.X-Conflictsを指定すると,そのUnitが実行されているマシンではスケジューリングされなくなる.この場合,このapacheサービスは同じマシンにはスケジューリングされない.

さらにこのUnitファイルはテンプレートになっており,%iには動的に値が代入される.スケジューリングする際に具体的な値を入れる.例えば,以下のようにすると,起動時には80という値が代入される.

$ fleetctl submit apache@.service
$ fleetctl load apache@80.service

テンプレートの扱い

fleetもsystemdもシンボリックリングを扱うことができる.そのため上述したapache@.serviceのスケジューリングは以下のように既述できる.

$ ln -s apache@.service apache@8888.service
$ fleetctl start apache@8888.service

これは扱うテンプレートとそのインスタンスが増えたときに管理が楽になる.例えば,templeatesinstancestaticというディレクトリを作り,動的な変更のないUnitファイルはstatic以下に,テンプレートとして使うUnitファイルはtemplates以下に配置し,instancesにシンボリックリンクを作る.

$ mkdir templates instances static
$ mv apache@.service templates/.
$ mv hello.service static/.
$ cd instances
$ ln -s ../templates/apache@.service apache@5555.service
$ ln -s ../templates/apache@.service apache@6666.service
$ ln -s ../templates/apache@.service apache@7777.service

こうしておくと,instanceは以下のように一気に起動できる.管理と運用が楽になる.

$ fleetctl start instances/*