Fluentd on Docker on Elastic Beanstalk

@repeatedly さんが「[Docker 周りとのFluentdの記事が無い](https://twitter.com/repeatedly/status/537199909045157889)」と言っていたので、[AWS Elastic Beanstalk](http://aws.amazon.com/jp/elasticbeanstalk/) の Docker で Fluentd を動かす話について書きます。Fluentd というよりは Docker とか Beanstalk の話が厚めです。

![eb-docker-fluentd.png](https://qiita-image-store.s3.amazonaws.com/0/6870/e644c356-3fc9-718d-0507-fd9d058544d9.png)

# 要点だけ三行で

 - Elastic Beanstalk の Docker で Fluentd を動かすときは
 - 起動用のスクリプトを書いたり
 - Docker 起動スクリプトを調整したりしましょう

# くわしく

Beanstalk やら Docker やらについての簡単な説明は、今年4月の[【AWS発表】AWS Elastic Beanstalk for Docker](http://aws.typepad.com/aws_japan/2014/04/aws-elastic-beanstalk-for-docker.html) からの引用で済ませておきます。

> AWS Elastic Beanstalkを使えば、簡単に、AWSクラウド内でアプリケーションをデプロイおよび管理することができます。 アプリケーションをアップロードすると、Elastic Beanstalkはキャパシティ(Amazon EC2インスタンス)をデプロイ、監視、スケールし、ロードバランサーを使って、正常に稼働しているインスタンスにリクエストを分散してくれます。
> 
> Dockerは、さまざまな環境で実行することができる、軽量、ポータブルで、カスタマイズ可能なコンテナという形でアプリケーションのデプロイを自動化します。 コンテナは事前に構築されたDocker イメージや、Dockerfileとして知られるシンプルなレシピから作成することができます。

補足として、Beanstalk には [eb コマンド](http://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/command-reference-get-started.html)というものがあり、手元にこのコマンドをインストールして `eb push` で手元の git リポジトリをデプロイできるようになります。AWS を使ってウェブサービスを動かす数ある手法はたくさんありますが、その中でも簡単な方です。わぁ、Heroku みたい！

Docker で Fluentd を動かす話は、既にたくさんの事例が出ていますが、Beanstalk の上となると、少し状況が異なってきます。

まず一番大きい違いが、Beanstalk では Docker コンテナをひとつしか走らせられないことです。つまりリンクが使えないので、nginx と Fluentd でそれぞれコンテナを分けるといったことができず、ひとつのコンテナで nginx と Fluentd を走らせることになります。Docker らしくないですが、デプロイが楽になることを重視したければ我慢しましょう。

もう一つの違いが、これはやり始めてから気づくのですが、コンテナ側の ulimit の制御が難しいということです。ここがこの記事のキモになります。

[nginx 公式イメージ](https://registry.hub.docker.com/_/nginx/)をベースに Fluentd を組み込んでみます。

## 起動用のスクリプトを書く

```Dockerfile:Dockerfile
FROM nginx

RUN apt-get update
RUN apt-get -y install curl sudo

RUN curl -L http://toolbelt.treasuredata.com/sh/install-debian-wheezy-td-agent2.sh | sh

ADD run.sh /opt/run.sh

EXPOSE 80
CMD ["sh", "/opt/run.sh"]
```

```bash:run.sh
service td-agent start
nginx -g "daemon off;"
```

これで普通の環境なら動くと思います。動きはするんですが、ulimit -n がイマイチです。Request Logs するなり直接 ssh するなりして `/var/log/eb-docker/containers/eb-current-app/なんとか-stdouterr.log` を見れば分かるんですが、

```
/etc/init.d/td-agent: 76: ulimit: error setting limit (Operation not permitted)
```

が出ちゃってます。td-agent の 76 行目というのは、[Fluentd の init スクリプトが ulimit を変更するところ](https://github.com/treasure-data/td-agent/blob/311bea73e2572e7d6cb3c32864177937181cedf0/debian/td-agent.init#L76)です。

コンテナ側の ulimit についてはコンテナ側でどうすることもできないので、ホスト側をいじる必要があります。一般にはホスト側の ulimit を上げて docker を再起動という方法が取られているようですが、これを ebextensions に書くと何か微妙な結果になっちゃいます。

あ、ebextensions っていうのは、PaaS な感じの Beanstalk に対して細かい調整をするための仕組みです。yaml を書くことで任意のパッケージをインストールしたりコマンドを実行できたりします。docker を止めて ulimit 上げて docker 起動する ebextensions はこれ。

```yaml:.ebextensions/restartdocker.config 
commands:
  restartdocker:
    command: service docker stop && ulimit -n 65536 && service docker start
```

で、これを eb push してやると、なんかエラーが出ます。

![スクリーンショット 2014-12-10 午前2.03.06.png](https://qiita-image-store.s3.amazonaws.com/0/6870/33da7925-a225-62b4-2a77-1d363f83bcc2.png)

Health が Green なのに ERROR とか ERROR とか出ててキモチワルイですね。docker を再起動した後に、さっきまで動いてたコンテナを止めようとしてエラーとか、そんな状況になっていると思われます。

`/etc/security/limits.conf` を書き換える方法をとったとしても、結局は docker を再起動する必要があり、docker を再起動する限り上のエラーが出てしまいます。

そこで、今回はこれを回避するために Docker ホスト側の起動スクリプトを調整する方法を紹介します。

## Docker 起動スクリプトを調整する

コンテナの ulimit を上げるには、ホストで ulimit を上げて再起動する以外にも方法があります。コンテナを動かすとき、つまり `docker run` を実行するときに `--privileged` オプションを使うことで、ホスト側の設定に縛られること無くコンテナ側で ulimit を上げることができます。

Beanstalk の docker ホストがどこで `docker run` を叩いているのがどこかというと、`/opt/elasticbeanstalk/hooks/appdeploy/pre/04run.sh` です。

```bash:/opt/elasticbeanstalk/hooks/appdeploy/pre/04run.sh
# run the container

docker run -d \
                   "${EB_CONFIG_DOCKER_ENV_ARGS[@]}" \
                   "${EB_CONFIG_DOCKER_VOLUME_MOUNTS[@]}" \
                   "${EB_CONFIG_DOCKER_ENTRYPOINT_ARGS[@]}" \
                   $EB_CONFIG_DOCKER_PORT_MAPPING \
                   $EB_CONFIG_DOCKER_IMAGE_STAGING \
                   "${EB_CONFIG_DOCKER_COMMAND_ARGS[@]}" 2>&1 | tee /tmp/docker_run.log | tee $EB_CONFIG_DOCKER_STAGING_APP_FILE
```

これを ebextensions で無理やり書き換えちゃいましょう。

```yaml:.ebextensions/patchrunsh.config 
commands:
  patchrunsh:
    command: sed -i "s/docker run -d/docker run --privileged -d/" /opt/elasticbeanstalk/hooks/appdeploy/pre/04run.sh
```

これで、おかしなエラーを回避しつつ Beanstalk で Fluentd を動かすことができます。めでたしめでたし。

![スクリーンショット 2014-12-10 午前2.27.35.png](https://qiita-image-store.s3.amazonaws.com/0/6870/cd7e3391-a055-275c-55d1-0e1428a06498.png)

`--privileged` オプションは、ulimit 上げるだけにしては権限強すぎるなーなんてことを考えてたら、Docker 側でちょうど [Proposal: Allow setting ulimits for containers](https://github.com/docker/docker/pull/9437) という Pull Request が上がってました。これがリリースされたら、そのオプションを使ったほうがいいでしょうね。

ちなみにこの記事を書いている最中に、[Beanstalk が Docker 1.3.2 に対応したこと](https://forums.aws.amazon.com/ann.jspa?annID=2772)に気づきました。1.3 ということは docker exec が使えるわけで、だいぶ開発が捗りそうです。それでは。

## 参考リンク

 - ホスト側の ulimit を上げてコンテナの ulimit を上げる
   - [Dockerコンテナのファイルディスクリプタ設定 - Qiita](http://qiita.com/kazunori279/items/5aedb6ed548225545a3c)
   - [docker で nginx + elasticsearch + fluentd + kibana を試してみる - Qiita](http://qiita.com/gom/items/cf1abc3d9c398b7462e2)
   - [Failed to run ulimit -n 65535 - Google グループ](https://groups.google.com/forum/#!topic/docker-user/T45Kc9vD804)