CoreOS でシステムメトリクスを収集し、グラフ化する

# CoreOS でシステムメトリクスを収集し、グラフ化する

どうも、僕です。
`CoreOS` Beta まできましたね。GCEにもイメージが乗るようなので興味をもった方多いかも知れません。
今回は `CoreOS` の運用のお話をしたいと思います。
まず `CoreOS` について知りたい方は [CoreOS 入門](http://qiita.com/mopemope/items/fa9424b094aae3eac580)を読んでおくとよいでしょう。
(少し古いですが）

今回はクラスタ上の `CoreOS` のシステム情報をかき集めてグラフ表示するというお話です。
以下のとこまでもっていくのがゴールです。
![Screenshot from 2014-05-26 11:20:13.png](https://qiita-image-store.s3.amazonaws.com/0/2526/a1d627ab-ada9-aa87-faf6-7178fec27c62.png)

追記
期間限定でGCE上にデモを配置しました。
http://108.59.85.122/#/dashboard/elasticsearch/CoreOS

## CoreOS ならではの運用上の問題


`CoreOS` ですが、パッケージマネージャーを持ちません。
そのため、アプリケーションはほぼ `Docker` 上で動作させることになります。
ですが、`Docker` 上で動作させるとホスト OS の情報にアクセスできません。
そのため `CoreOS` 自体のシステム情報の監視を行うためにはなんらかしらの工夫が必要になります。
ホストの色々な情報がある `/proc` や `/sys/fs/cgroup` を `Docker` から参照できればうまくいきそうです。

また `CoreOS` はクラスタサポート込の設計がなされています。
なので複数台でクラスタを組んで運用することが多いかと思います。
そうなると各ホストの状態を一箇所にまとめて監視したくなります。
この辺もカバーしたいところです。

## Docker を使う場合の限界

では早速確認してみましょう。
`Docker` は別の名前空間で動くのですがマウントでなんとかごまかせるかも知れません。。
システム情報を取得したいのでホストの`/proc`をマウントしてみましょう。

```shell
$ sudo docker run -t -i --privileged=true -v /proc:/proc ubuntu /bin/bash
2014/05/xx xx:xx:xx set apparmor profile unconfined: permission denied
```

特権モードでもできませんでした。。。
やはり無理がありますね。
つまり、`Docker` 上から `ps` コマンドの類からはホスト OS の情報にアクセスすることは不可能ということです。
ではこれではどうでしょう？

```shell
$ sudo docker run -t -i --privileged=true -v /proc:/host_proc ubuntu /bin/bash
root@7a589eceb66a:/# 
```

うまくいったように見えますね。

```shell
root@7a589eceb66a:/# cat /host_proc/diskstats
.....
```

省略していますが、ホストと同じ情報が表示されているはずです。
問題なさそうです。
では `/proc/mounts` はどうでしょう？

```shell
root@7a589eceb66a:/# cat /host_proc/mounts
....
```

ホストと見比べてみましょう。
やはりここは無理のようです。。。

あと欲しいのは `/sys/fs/cgroup` 配下でしょうか？
ここには cgroup で管理されているプロセスの情報があります。
試してみましょう。

```shell
$ sudo docker run -t -i --privileged=true -v /sys:/sys ubuntu /bin/bash
root@9234d03c8638:/#
root@9234d03c8638:/# cat /sys/fs/cgroup/memory/docker/9234d03c863892f03237ac1a6d0f4a8d1022ba2cab6ff0eb3edc34def4959fb0/memory.stat
cache 675840
rss 610304
rss_huge 0
mapped_file 0
writeback 0
swap 0
pgpgin 978
pgpgout 664
pgfault 3296
pgmajfault 2
inactive_anon 28672
active_anon 618496
inactive_file 413696
active_file 192512
unevictable 0
hierarchical_memory_limit 18446744073709551615
hierarchical_memsw_limit 18446744073709551615
total_cache 675840
total_rss 610304
total_rss_huge 0
total_mapped_file 0
total_writeback 0
total_swap 0
total_pgpgin 978
total_pgpgout 664
total_pgfault 3296
total_pgmajfault 2
total_inactive_anon 28672
total_active_anon 618496
total_inactive_file 413696
total_active_file 192512
total_unevictable 0

```

今起動している自身のコンテナのメモリ情報にもアクセスできました。

`/proc/mounts` はやはり上書き不可能でしたが、その他は頑張ればいけそうな気がします。
頑張れば。。。

`Docker` を使う場合の問題は以下でしょうか。

- `/proc` がそのまま使えないので `/host_proc/` などを使わなければいけない（それでも全ての情報にアクセスできるとは限らない）
- システム情報を収集するツールが `/proc` 部のパスを変更しても動作する必要がある

もちろんシステム情報を収集するツールにパッチを当てて収集ツールを動かすといった方法もあるでしょう。
これはツールのアップデートに合わせて再度パッチをあて改修していくといった作業が必要になるため現実的で
はありませんね。

参照：現実的ではない例

http://blogs.nuxeo.com/development/2014/03/coreos-monitoring-diamond-graphite/

これが `CoreOS` + `Docker` できゃあきゃあ言ってる連中のリアル（現実）です。
なんだかダサいですね。もっとスタイリッシュにできないもんでしょうか？

## 2 種類のコンテナを使って問題を乗り越える

さて、実際問題この問題を乗り越えるにはどうしたらよいでしょう？
coreos-dev を読んでる人ならすぐにわかりますね。

### CoreOS に付属する toolbox 

`CoreOS` にはパッケージマネージャーが存在しません。
ですが、これではデバッグがあまりにも大変です。
そのため、`CoreOS` 標準でデバッグに使うための `toolbox`というツールが付属してきます。
`toolbox` を使うと `fedora20` の yum が使えたり `fedora20` の多くの機能を使うことが出来ます。

例：

```
core@coreos-local ~ $ toolbox 
unknown
Spawning container core-fedora on /var/lib/toolbox/core-fedora. Press ^] three times within 1s to abort execution.
/etc/localtime is not a symlink, not updating container timezone.
[root@coreos-local ~]#
[root@coreos-local ~]# yum install -y emacs
...

```

`toolbox` の中身はどうなっているのでしょう？？

```bash:toolbox
#!/bin/bash

TOOLBOX_DOCKER_IMAGE=fedora
TOOLBOX_USER=root

toolboxrc="${HOME}"/.toolboxrc

if [ -f "${toolboxrc}" ]; then
    source "${toolboxrc}"
fi

machinename=$(echo "${USER}-${TOOLBOX_DOCKER_IMAGE}" | sed -r 's/[^a-zA-Z0-9_.-]/_/g')
machinepath="/var/lib/toolbox/${machinename}"

if [ ! -d ${machinepath} ] || systemctl is-failed ${machinename} ; then
    sudo mkdir -p "${machinepath}"
    sudo chown ${USER}: "${machinepath}"

    docker pull "${TOOLBOX_DOCKER_IMAGE}"
    docker run --name=${machinename} "${TOOLBOX_DOCKER_IMAGE}" /bin/true
    docker export ${machinename} | sudo tar -x -C "${machinepath}" -f -
    docker rm ${machinename}
    sudo touch "${machinepath}"/etc/os-release
fi

sudo systemd-nspawn -D "${machinepath}" --share-system --bind=/:/media/root --bind=/usr:/media/root/usr --user="${TOOLBOX_USER}" "$@"

```

なにやら怪しいコマンドを実行していますね。
`systemd-nspawn` がキモのようです。

### systemd-nspawn

`systemd-nspawn` は `systemd` の機能の一部にある軽量コンテナ実装のひとつです。
詳しくは Arch の Wiki などを見てもらえばわかりますが、`Docker` と同じように別の名前空間で動作したり、ネットワークも切り離したりすることができます。
コンテナも `machinectl` で管理することが出来ます。
（`CoreOS` 上での machinectl からのログインは一部のサービスが足りないのでエラーになります）

`Docker` と異なる点は コンテナ内のディレクトリ、ライブラリなどを自分で用意する必要があるという点です。
`Docker` でいうイメージの部分は自前管理です。

例:

```shell
$ yum -y --releasever=19 --nogpg --installroot=/srv/mycontainer --disablerepo='*' --enablerepo=fedora install systemd passwd yum fedora-release vim-minimal
$ systemd-nspawn -bD /srv/mycontainer
```

使用方法はシンプルですね。
コンテナになるディレクトリにbaseになるパッケージを入れてしまえばディレクトリも作成されるので簡単に使うことができます。

上記のように `systemd-nspawn` はコンテナのディレクトリを自前で用意しなければなりません。
この部分に `Docker` Imageを使ってるのが`toolbox` です。
`Docker` Image を export した tar 解凍すれば `systemd-nspawn` でそのまま使用できます。
`toolbox` では `fedora20` のイメージが指定されています。

また `systemd-nspawn` には `--share-system` フラグというものがあります。
これは名前空間をホストと共有して動作するモードです。
そうです、これを使えばホスト OS の情報に簡単にアクセスできます。


### nspawn-container

`toolbox` を元に任意のコンテナを実行できればホスト OS のシステム情報を集めることが出来そうです。
なので以下のようなスクリプトを書きました。

```bash:nspawn-container
#!/bin/bash

TOOLBOX_DOCKER_IMAGE=$1
TOOLBOX_USER=root

machinename=$(echo "${USER}-${TOOLBOX_DOCKER_IMAGE}" | sed -r 's/[^a-zA-Z0-9_.-]/_/g')
machinepath="/var/lib/toolbox/${machinename}"

if [ ! -d ${machinepath} ] || systemctl is-failed ${machinename} ; then
        sudo mkdir -p "${machinepath}"
        sudo chown ${USER}: "${machinepath}"

        docker pull "${TOOLBOX_DOCKER_IMAGE}"
        docker run --entrypoint="/bin/bash" --name=${machinename} "${TOOLBOX_DOCKER_IMAGE}" /bin/true
        docker export ${machinename} | sudo tar -x -C "${machinepath}" -f -
        docker rm ${machinename}
        sudo touch "${machinepath}"/etc/os-release
fi

sudo systemd-nspawn -D "${machinepath}" --share-system --bind=/:/media/root --bind=/usr:/media/root/usr --bind-ro=/sys/fs/cgroup:/sys/fs/cgroup --bind-ro=/sys/fs/cgroup/memory:/sys/fs/cgroup/memory --bind-ro=/sys/fs/cgroup/cpuacct:/sys/fs/cgroup/cpuacct  --bind=/run:/run --user="${TOOLBOX_USER}" "$2" "${@:3:$#}"

```

修正箇所はイメージを引数で受け取れるようにしたのと起動プログラム、そして引数を渡せるようにした点です。
今回はさらにシステム情報を集めるために各種パスをbindしています。
`/sys/fs/cgroup` などは明示的に指定しないとうまくbindできなかったので指定します。あとは `/run` をbindしてるのでdocker.sockにもアクセスできます。


## システム情報を集め、グラフ化する

では早速システム情報を集めてホストの情報を監視してみましょう。
今回はシステム情報を集めるのに[Diamond](https://github.com/BrightcoveOS/Diamond)を使用します。
`Diamond` でシステム情報を集め、`Graphite(Grafana)` でグラフに表示するのが目標です。

まず、システム情報を集める先に`Graphite` + `Carbon` を起動します。
セットアップがめんどいので[mopemope/graphite-docker](https://index.docker.io/u/mopemope/graphite-docker/)を使うといいでしょう。
`Graphite` + `Grafana` の他に `Elasticsearch` + `StatsD` 込みのイメージとなっています。

起動方法は以下です。
Port 80 には素の `Graphite`、81 には `Grafana` が立ち上がります。
下記の例では8080, 8081にそれぞれ `Graphite` / `Grafan` aを割り当てています。
Port 2003はcarbonのポートです。ここに向けて `Diamond` でデータを送信します。

```shell
$ sudo docker run -d -p 8081:81 -p 8080:80 -p 20030:2003 -p 2004 -p 7002 -p 8125:8125/udp -p 8126:8126 mopemope/graphite 
```

集める先なので `CoreOS` でなくても任意のOS上の `Docker` で起動して問題ありません。
私も `CoreOS` クラスタ外にGraphiteをセットアップしています。

では `CoreOS` に `Diamond` を仕込みます。
`fleet` から起動するserviceファイルは以下です。

```bash:diamond.1.service
[Unit]
Description=Diamond Service
After=docker.service
Requires=docker.service


[Service]
User=core
ExecStartPre=/usr/bin/curl -so /home/core/nspawn-container https://gist.githubusercontent.com/mopemope/79476588ed3650bc08d4/raw/8fd78056ebcbca86df51c5092c7b441e288ea2eb/nspawn-container
ExecStartPre=/usr/bin/chmod +x /home/core/nspawn-container
ExecStart=/bin/sh -c '/home/core/nspawn-container mopemope/diamond-docker /diamond/run_diamond 192.168.2.28 20030'
Restart=always

[X-Fleet]
X-Conflicts=diamond.*.service

```

mopemope/diamond-docker は私が作成した `Diamond` 起動用の Image です。
Trusted Builds になっているのでソースも見れます。
Collectorは12個動いていますが、とりあえず設定も公開されているので適当に見て下さい。
システム情報の取得は60秒間隔です。

起動するプログラム `/diamond/run_diamond` にはホスト、ポートを指定します。
今回は `Graphite` が192.168.2.28に動作しているものとします。

`fleet` から起動し、以下のようになっていればOKです。
しばらくすると `Graphite` 側にデータが送られてきます。

例：3台のクラスタ

```shell:fleetctl
diamond.1.service               launched        loaded  active  running Diamond Service                         790495b4.../192.168.2.52
diamond.2.service               launched        loaded  active  running Diamond Service                         d5a76cb1.../192.168.2.51
diamond.3.service               launched        loaded  active  running Diamond Service                         872761f0.../192.168.2.50

```

例：各ホストのDockerコンテナのメモリ使用量
![Screenshot from 2014-05-26 11:47:48.png](https://qiita-image-store.s3.amazonaws.com/0/2526/8ded845e-ef92-9b55-0ed1-23735c83c6e6.png)

## まとめ
`CoreOS`の運用監視についてはまだあまり確立されてない状態です。
上記の方法が一番スマートな方法だと思います。
ホスト OS の運用監視は `Docker` では無理があるので他の方法を検討するのがよいでしょう。
`CoreOS` の場合は`systemd`などの総合的な知識が必要になると思います。
次回はログ監視 + Alert、cabotによるしきい値チェックによる監視、Alert、etcd, fleetのチューニングなどお話でもしようかと思います。
（書き起こしめんどい。。。）