confd を試す

# confd とは

[confd](https://github.com/kelseyhightower/confd) は Go 言語で実装されたシンプルな設定管理ツールです。

confd を実行すると、etcd や Consul といったデータストアの値を取得し、テンプレートに反映して設定ファイルを生成します。設定ファイルの生成後に任意のコマンドも実行できるため、例えば nginx の設定ファイルを生成して、nginx に反映するといった流れも実現できます。

confd をデーモンとして起動しておくと、設定を定期的に更新したり、リアルタイムに反映したりできて非常に便利です。ここからは confd を使う手順などをまとめていきます。

# セットアップ

今回は Ubuntu 14.04 をインストールしたホストを用意し、そこに環境を構築していきます。

まず、confd をインストールします。

```
$ curl -L https://github.com/kelseyhightower/confd/releases/download/v0.11.0/confd-0.11.0-linux-amd64 -o confd
$ sudo mv confd /usr/local/bin
```

confd をサービスとして管理するためのファイルを作成しておきます。

```
$ sudo vim /etc/init/confd.conf
```

```filename:/etc/init/confd.conf
description "confd"

start on (net-device-up
          and local-filesystems
          and runlevel [2345])
stop on runlevel [016]

respawn
respawn limit 10 5

script
  if [ -f "/etc/default/confd" ]; then
    . /etc/default/confd
  fi

  chdir /var/confd
  exec /usr/local/bin/confd >> /var/log/confd.log 2>&1
end script
```

次に、confd のバックエンドとなる etcd をインストールします。

```
$ curl -L https://github.com/coreos/etcd/releases/download/v2.2.5/etcd-v2.2.5-linux-amd64.tar.gz -o etcd-v2.2.5-linux-amd64.tar.gz
$ tar xzvf etcd-v2.2.5-linux-amd64.tar.gz
$ sudo cp etcd-v2.2.5-linux-amd64/etcd /usr/local/bin
$ sudo cp etcd-v2.2.5-linux-amd64/etcdctl /usr/local/bin
$ rm -rf etcd-v2.2.5-linux-amd64 etcd-v2.2.5-linux-amd64.tar.gz
```

etcd をサービスとして管理するためのファイルを作成しておきます。

```
$ sudo vim /etc/init/etcd.conf
```

```filename:/etc/init/etcd.conf
description "etcd 2.0 distributed key-value store"

start on (net-device-up
          and local-filesystems
          and runlevel [2345])
stop on runlevel [016]

respawn
respawn limit 10 5

script
  if [ -f "/etc/default/etcd" ]; then
    . /etc/default/etcd
  fi

  chdir /var/etcd
  exec /usr/local/bin/etcd >> /var/log/etcd.log 2>&1
end script
```

必要なディレクトリを作成後に、confd と etcd を起動します。

```
$ sudo mkdir /var/confd /var/etcd
$ sudo service confd start
$ sudo service etcd start
```

# テンプレートからファイルを生成する

ここからは、[クイックスタートガイド](https://github.com/kelseyhightower/confd/blob/master/docs/quick-start-guide.md)を参考にテンプレートからファイルを生成する流れを確認します。今回は etcd に格納したキーの値をテンプレートに反映し、設定ファイルを生成します。

まずは、設定ファイルやテンプレートを配置するディレクトリを作成します。

```
$ sudo mkdir -p /usr/local/etc/confd/{conf.d,templates}
```

次に、confd の設定ファイルを作成します。設定ファイルは [TOML](https://github.com/toml-lang/toml) 形式で記述します。

```
$ sudo vim /usr/local/etc/confd/conf.d/test.toml
```

```filename:/usr/local/etc/confd/conf.d/test.toml
[template]
src = "test.conf.tmpl"
dest = "/tmp/test.conf"
keys = [
  "/myapp/database/url",
  "/myapp/database/user",
]
```

設定ファイルの内容をもとに、テンプレートを記述します。

```
$ sudo vim /usr/local/etc/confd/templates/test.conf.tmpl
```

```filename:/usr/local/etc/confd/templates/test.conf.tmpl
[myconfig]
database_url = {{getv "/myapp/database/url"}}
database_user = {{getv "/myapp/database/user"}}
```

テンプレートを作成したら、テンプレート変数の値を etcd に設定します。

```
$ etcdctl set /myapp/database/url example.com
$ etcdctl set /myapp/database/user summerwind
```

設定ファイル、テンプレート、テンプレート変数の準備ができたら、confd を実行して、ファイルを生成します。

```
$ sudo /usr/local/bin/confd -onetime -backend etcd -node http://127.0.0.1:4001 -confdir /usr/local/etc/confd/
2016-03-09T05:36:07Z dev /usr/local/bin/confd[2929]: INFO Backend set to etcd
2016-03-09T05:36:07Z dev /usr/local/bin/confd[2929]: INFO Starting confd
2016-03-09T05:36:07Z dev /usr/local/bin/confd[2929]: INFO Backend nodes set to http://127.0.0.1:4001
2016-03-09T05:36:07Z dev /usr/local/bin/confd[2929]: INFO Target config /tmp/test.conf out of sync
2016-03-09T05:36:07Z dev /usr/local/bin/confd[2929]: INFO Target config /tmp/test.conf has been updated
```

生成されたファイル `/tmp/test.conf` を確認すると、正しく変数が埋め込まれていることが分かります。

```
$ cat /tmp/test.conf
[myconfig]
database_url = example.com
database_user = summerwind
```

# 継続的にファイルを自動更新する

confd をデーモンとして起動すると、etcd の値を定期的に取得して、ファイルを自動更新することができます。更新頻度は `-interval` オプションで指定できます。以下の例では30秒ごとに定期更新しています。

```
$ sudo /usr/local/bin/confd -backend etcd -node http://127.0.0.1:4001 -confdir /usr/local/etc/confd/ -interval 30
```

この時点でのファイルの内容は以下のようになっています。

```
$ cat /tmp/test.conf
[myconfig]
database_url = example.com
database_user = summerwind
```

confd を起動後に、etcd のキーの値を変更します。

```
$ etcdctl set /myapp/database/url test.example.com
```

confd による更新がおこなわれると、ファイルの内容が以下のように変化します。

```
$ cat /tmp/test.conf
[myconfig]
database_url = test.example.com
database_user = summerwind
```

# Watch を使用してリアルタイムに更新する

etcd や Consul といったバックエンドには、Watch 機能が実装されているため、値の変更と同時に設定ファイルを更新することが可能です。Watch 機能を使用するには、`-watch` オプションを指定して confd を起動します。

```
$ sudo /usr/local/bin/confd -backend etcd -node http://127.0.0.1:4001 -confdir /usr/local/etc/confd/ -watch
```

# ファイル更新時に任意のコマンドを実行する

confd にはファイルの更新時に、更新されたファイルを検証するためのコマンドと更新されたファイルを反映するコマンドの2つを実行可能です。これらのコマンドは設定ファイルの `check_cmd` と `reload_cmd` に指定します。

設定ファイルを以下のように変更し、ファイル更新時に `/tmp/test.conf.checked` と `/tmp/test.conf.reloaded` が作成されるようにします。

```
$ sudo vim test.toml
```

```
[template]
src = "test.conf.tmpl"
dest = "/tmp/test.conf"
keys = [
  "/myapp/database/url",
  "/myapp/database/user",
]
check_cmd = "touch /tmp/test.conf.checked"
reload_cmd = "touch /tmp/test.conf.reloaded"
```

confd を起動後に etcd のキーを変更して、ファイルを更新します。

```
$ sudo /usr/local/bin/confd -backend etcd -node http://127.0.0.1:4001 -confdir /usr/local/etc/confd/ -watch
$ etcdctl set /myapp/database/url test.example.com
```

ファイルが更新されたのでコマンドが実行され、`/tmp/test.conf.checked` と `/tmp/test.conf.reloaded` が作成されていることが分かります。

```
$ ls -al /tmp
total 12
drwxrwxrwt  2 root root 4096 Mar  9 06:18 .
drwxr-xr-x 22 root root 4096 Mar  8 09:22 ..
-rw-r--r--  1 root root   67 Mar  9 06:18 test.conf
-rw-r--r--  1 root root    0 Mar  9 06:18 test.conf.checked
-rw-r--r--  1 root root    0 Mar  9 06:18 test.conf.reloaded
```

なお、コマンドは `check_cmd`、`reload_cmd` の順に実行され、コマンドの実行に失敗した場合は、後続のコマンドが実行されず、ファイルもロールバックされるようです。使い方次第では、任意のコマンドを実行するようにもできるので、十分な注意が必要です。

# セキュリティの考慮

confd は設定変更時に任意のコマンドを実行できる機能があるため、使い方によってはセキュリティ上問題になるかもしれません。Ubutntu では、標準でインストールされている AppArmor を使用すれば confd の権限を制御できます。AppArmor の設定方法については、[ubuntuで AppArmor を使ってセキュアな環境を構築する](http://ubulog.blogspot.jp/2009/06/ubuntu-apparmor.html) が詳しいです。

AppArmor による制限を有効にするには、最初に設定ファイルを作成します。この設定ファイルには、confd がどのファイルにアクセスできるかなどの情報を記述します。

```
$ sudo vim /etc/apparmor.d/usr.local.bin.confd
```

```filename:/etc/apparmor.d/usr.local.bin.confd
# Last Modified: Fri Mar 11 01:53:58 2016
#include <tunables/global>

/usr/local/bin/confd {
  #include <abstractions/nameservice>
  #include <abstractions/base>

  capability net_admin,

  network inet stream,
  network inet6 stream,

  /usr/local/bin/confd mr,
  /usr/local/etc/confd/conf.d/ r,
  /usr/local/etc/confd/conf.d/test.toml r,
  /usr/local/etc/confd/templates/ r,
  /usr/local/etc/confd/templates/test.conf.tmpl r,

  /etc/test.conf rw,
  /etc/.test.conf* rw,

  /usr/bin/basename ix,
  /usr/bin/env ix,

  /proc/sys/kernel/hostname r,
  /proc/sys/net/core/somaxconn r,
}
```

設定ファイルができたら、設定を有効化します。

```
$ sudo aa-enforce confd
```

設定を無効化する場合は以下のコマンドを実行します。

```
$ sudo aa-disable confd
```

AppArmor によりファイルの読み込みやコマンドの実行が制限されているかどうかは、`/var/log/syslog` で参照できます。以下のようなログが出力されていれば、制限が有効になっていることになります。

```
Mar 11 02:32:08 dev kernel: [234656.156133] type=1400 audit(1457663528.431:2358): apparmor="DENIED" operation="open" profile="/usr/local/bin/confd" name="/usr/sbin/service" pid=8515 comm="service" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
Mar 11 02:32:41 dev kernel: [234688.878779] type=1400 audit(1457663561.151:2360): apparmor="DENIED" operation="open" profile="/usr/local/bin/confd" name="/usr/sbin/service" pid=8527 comm="service" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
```