logspout で CoreOS 上の Docker コンテナのログを集約・ルーティングする


## 概要
logspout というログ収集専用コンテナを使って CoreOS 上で起動する Docker コンテナのログを集約し、リモートログサーバへルーティングするという話。
[systemd で CoreOS 上の Docker コンテナのログを集約・ルーティングする](http://qiita.com/spesnova/items/64a3720e48a19914cf20) の "sidekick" の問題点をふまえ、 起動したいコンテナがログ収集プロセスについて気にしなくてよいアプローチを試す

## Docker コンテナのログ収集の選択肢
Docker コンテナのログ収集では選択肢が 3 種類ある

* **コンテナ内部で収集する**: コンテナ内でログ収集のプロセスをメインプロセスと同時に走らせログを収集・ルーティングする
* **コンテナ外部で収集する**: `-v` を使ってログ収集対象コンテナのログディレクトリをホストに共有し、ホスト側のログ収集のエージェントを使ってログを収集・ルーティングする
* **収集用コンテナを立てる**: `--volumes-from` を使ってログ収集専用コンテナとログ収集対象コンテナでログディレクトリを共有し、ログ収集専用コンテナがログを収集・ルーティングする

参考: [logspoutでDockerコンテナのログの集約・ルーティング - SOTA](http://deeeet.com/writing/2014/05/14/logspout/) 

[systemd で CoreOS 上の Docker コンテナのログを集約・ルーティングする](http://qiita.com/spesnova/items/64a3720e48a19914cf20) は、 **コンテナ外部で収集する** にあたる。 コンテナ外部で収集すること自体が悪かった訳ではなく、 systemd の unit のバインドを使った "coprosess" "sidekick" と呼ばれる方法だと、コンテナを起動する際にログ収集プロセスについても一緒に考える必要が出て来てこれは正直面倒だよね、という話。そこでコンテナを起動する際ログ収集プロセスについて考えなくてよい方法として [progrium/logspout](https://github.com/progrium/logspout) というものがある。 logspout を使えば、上記の問題が解決するだけでなく [Datadog で CoreOS をモニタリングする](http://qiita.com/spesnova/items/882e0a61ddbe6c356394) と同じく、出来るだけ全てコンテナだけでやる "docker way" が実現できる。（これは **収集用コンテナを立てる** にあたる）


## logspout を使ったログの集約

logspout は Docker コンテナのログ収集専用のサービス。 Docker イメージが用意されており、ホストの `/var/run/docker.sock` をマウントすることで、そのホストで起動する全ての Docker コンテナのログを収集・ルーティングしてくれる。ログ収集対象コンテナに何の設定がいらないことが一番のメリット。

例えば以下のような hello world を出力するコンテナを 3 つ起動しておく

```bash
core@core-01 ~ $ docker run --name hello.1 busybox /bin/sh -c "while true; do echo hello world; sleep 1; done"
hello world
hello world
hello world
```

```bash
core@core-01 ~ $ docker run -d --name hello.1 busybox /bin/sh -c "while true; do echo hello world; sleep 1; done"
1d6d6b3a37d663730761726a9e8ca4245ca6beefac9c3b800680db8fcb076af3
core@core-01 ~ $ docker run -d --name hello.2 busybox /bin/sh -c "while true; do echo hello world; sleep 1; done"
8a031e9eacacfa9369fafdb001ea05880cdc742a153fc8191a168def1cd1f1ef
core@core-01 ~ $ docker run -d --name hello.3 busybox /bin/sh -c "while true; do echo hello world; sleep 1; done"
6c47bf268ed8999fb4a6c9108fe2a2f203266cea7f3704d8c12ed55c706ab713
```

logsput を起動

```bash
core@core-01 /etc/systemd/system $ docker run -d --name logspout -p 8000:8000 -v=/var/run/docker.sock:/tmp/docker.sock progrium/logspout
d4e86b5f001657124a725e4b7ee5bacc61809f082fbeb5c47c437171403c63a3
```

logspout の HTTP API を使って、ログストリームを出力すると色分けされて出力してくれる:)

![1__core_core-01____ssh_.png](https://qiita-image-store.s3.amazonaws.com/0/20833/2fbde119-22fc-e745-33c8-57ed6a2528e4.png "1__core_core-01____ssh_.png")

### コンテナ名で絞り込み

```bash
core@core-01 ~ $ curl localhost:8000/logsfilter:hello
         hello.2|hello world
         hello.1|hello world
         hello.3|hello world
         hello.2|hello world
```

### コンテナ名指定

```bash
core@core-01 ~ $ curl localhost:8000/logs/name:hello.1
hello world
hello world
hello world
```

### コンテナ ID 指定

```bash
core@core-01 ~ $ curl localhost:8000/logs/id:6c47bf268ed8
hello world
hello world
```

## logspout へ集約したログのルーティング
現状は UDP syslog 経由でのルーティングしか対応していない

### Logentries へルーティングする例
#### Logentries で token を作成する
新しくログを追加

![hosts_CoreOS___Logentries.png](https://qiita-image-store.s3.amazonaws.com/0/20833/9ee6e289-259d-5055-a563-069ef8017efd.png "hosts_CoreOS___Logentries.png")

Syslog を選択
![hosts_CoreOS___Logentries.png](https://qiita-image-store.s3.amazonaws.com/0/20833/82fe3010-0183-a7dd-b358-c5caab6cf6b2.png "hosts_CoreOS___Logentries.png")

syslogd を選択

![hosts_CoreOS___Logentries.png](https://qiita-image-store.s3.amazonaws.com/0/20833/8bb51508-d74c-9737-a17f-c05105e4b23c.png "hosts_CoreOS___Logentries.png")


token が表示されるのでこの token を使って logenteries へ送る。

#### logspout を起動する
logspout を起動する。

```bash
core@core-01 ~ $ docker run -d --name logspout -h $HOSTNAME -p 8000:8000 -v=/var/run/docker.sock:/tmp/docker.sock progrium/logspout
```

Syslog のホスト名部分は logspout のコンテナのホスト名が利用されるので、 `-h $HOSTNAME` を付けておくとよい。

```
# -h $HOSTNAME 無しのログの例
<14>2014-09-11T15:34:30Z 8ce4ab44556 hello.2[1]: hello world

# -h $HOSTNAME 付きのログの例
<14>2014-09-11T15:34:30Z core-01 hello.2[1]: hello world
```

#### ルーティングを作成する

logentries へ全てのログを送るルーティングを作成する

```bash
core@core-01 ~ $ curl localhost:8000/routes -X POST \
  -d '{"target": {"type": "rfc5424", "addr": "api.logentries.com:10000", "structured_data": "e15dca62-3629-44f3-9057-ca586dcad7a3"}}'
```

```bash
core@core-01 ~ $ curl localhost:8000/routes
[
  {
    "id": "2b38d0c5c069",
    "target": {
      "type": "rfc5424",
      "addr": "api.logentries.com:10000",
      "structured_data": "e15dca62-3629-44f3-9057-ca586dcad7a3"
    }
  }
]
```

これで全てのコンテナのログが Logentries に送信され、

![hosts_CoreOS_Docker_Containers___Logentries.png](https://qiita-image-store.s3.amazonaws.com/0/20833/1d552442-fa6a-51bd-0720-3e29ff8807ba.png "hosts_CoreOS_Docker_Containers___Logentries.png")


Logentries 上でリアルタイムにフィルタリングできる

![hosts_CoreOS_Docker_Containers___Logentries.png](https://qiita-image-store.s3.amazonaws.com/0/20833/986693e7-d0cc-93ba-146f-4c44f6d0118d.png "hosts_CoreOS_Docker_Containers___Logentries.png")

#### 特定の名前がついたコンテナのログだけルーティングする

```bash
core@core-01 ~ $ curl localhost:8000/routes -X POST -d '{"source": {"filter": "hello"}, "target": {"type": "rfc5424", "addr": "api.logentries.com:10000", "structured_data": "bec96897-1804-44d4-a8ac-89bd380a038b"}}'
```

```bash
core@core-01 ~ $ curl localhost:8000/routes
[
  {
    "id": "8e549a91270f",
    "source": {
      "filter": "hello"
    },
    "target": {
      "type": "rfc5424",
      "addr": "api.logentries.com:10000",
      "structured_data": "bec96897-1804-44d4-a8ac-89bd380a038b"
    }
  }
]
```

## Cloud-Config から logspout を起動する

```yaml
#cloud-config
# 一部抜粋

coreos:
  units:
    - name: logspout.service
      command: start
      enable: true
      content: |
        [Unit]
        Description=Logspout
        After=docker.service
        Requires=docker.service

        [Service]
        TimeoutStartSec=0
        ExecStartPre=-/usr/bin/docker kill logspout
        ExecStartPre=-/usr/bin/docker rm logspout
        ExecStartPre=/usr/bin/docker pull progrium/logspout:latest
        ExecStart=/usr/bin/docker run --name logspout -h %H -p 8000:8000 -v=/var/run/docker.sock:/tmp/docker.sock progrium/logspout:latest rfc5424://api.logentries.com:10000?structuredData=xxxxxxxxxxxxxxxxxxxxxxxxxx    
        ExecStop=/usr/bin/docker stop progrium/logspout:latest

        [Install]
        WantedBy=multi-user.target
```


## メモと感想
logspout 便利...

### コンテナ個別にルーティングしない理由
logsput はフィルタリングしたログをどこどこへルーティングといったことをいくつも登録することができるようになっている。が、それをし始めると「あるコンテナを立てた際に、 logspout に routes を追加しないと」というように "coprocess" "sidekick" っぽくなる気がしてるので、コンテナのログは全て logspout に集約し、コンテナ名のタグ付きで全てリモートサーバに送り、リモートサーバ側でフィルタリングして見る、というのがシンプルな気がしてる。

### token ベースで logentries へ送る必要がある
logentries は固有ポートへの Plain TCP, UDP 経由で送ると、送信元 IP で判別するので、CoreOS マシンを捨てられなくなる。token ベースの方を追記する

https://github.com/progrium/logspout/issues/12
https://github.com/progrium/logspout/issues/13

> I didn't catch that part. Yes, that will require some modification to
support custom structured data. Maybe make a new issue for that feature
request.

~~まだ syslog header に token 入れるのは出来ないもよう。~~　CoreOS マシンを disposable にしておくために、[journalctl の json output を jq で整形したのちルーティングする方法](http://qiita.com/spesnova/items/64a3720e48a19914cf20) も試している

追記: https://github.com/progrium/logspout/pull/20 にて token 追加できるようになる！
2015/01/13 追記: マージされた！

### どちらにせよ journalctl の json log はリモートサーバへ送っておく
Docker コンテナ以外のログもあるし、後からハンドリングしやすいということで全てのコンテナは systemd 経由で起動しておき、[journalctl の json ログを全て送っておく](http://qiita.com/spesnova/items/631ffb5c13e1d0da258b)

## REF

* [logspoutでDockerコンテナのログの集約・ルーティング](http://deeeet.com/writing/2014/05/14/logspout/) - Docker のログ管理方法について非常に勉強になりました
* [progrium/logspout](https://github.com/progrium/logspout)