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


概要

logspout というログ収集専用コンテナを使って CoreOS 上で起動する Docker コンテナのログを集約し、リモートログサーバへルーティングするという話。
systemd で CoreOS 上の Docker コンテナのログを集約・ルーティングする の "sidekick" の問題点をふまえ、 起動したいコンテナがログ収集プロセスについて気にしなくてよいアプローチを試す

Docker コンテナのログ収集の選択肢

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

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

参考: logspoutでDockerコンテナのログの集約・ルーティング - SOTA

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

logspout を使ったログの集約

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

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

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
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 を起動

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 を使って、ログストリームを出力すると色分けされて出力してくれる:)

コンテナ名で絞り込み

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

コンテナ名指定

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

コンテナ ID 指定

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

logspout へ集約したログのルーティング

現状は UDP syslog 経由でのルーティングしか対応していない

Logentries へルーティングする例

Logentries で token を作成する

新しくログを追加

Syslog を選択

syslogd を選択

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

logspout を起動する

logspout を起動する。

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 へ全てのログを送るルーティングを作成する

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"}}'
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 に送信され、

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

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

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"}}'
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 を起動する

#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 で整形したのちルーティングする方法 も試している

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

どちらにせよ journalctl の json log はリモートサーバへ送っておく

Docker コンテナ以外のログもあるし、後からハンドリングしやすいということで全てのコンテナは systemd 経由で起動しておき、journalctl の json ログを全て送っておく

REF