AWS ElasticBeanstalkでDockerコンテナログが肥大化したら

ElasticBeanstalkのSingle Container DockerでNode.jsのアプリを動かしています。
ある日アプリが動かなくなっていて、その原因を調べたところ、
`/var/lib/docker/containers/<Container ID>/<Container ID>-json.log`というファイルが肥大化して、EC2の容量がいっぱいになっていることがわかりました。
この記事ではそのファイルが無限大に大きくならないようにするために試したことを紹介しようと思います。

## 現在唯一の解決策

問題ファイルの肥大化を防止し、かつ、デメリットが少ないものは今現在以下の1つの方法しかわかっていません。
もしより良い方法をご存知の方がいらっしゃったら、教えてください・・！

### 解決策: Docker Daemonの設定ファイルを使用する

#### 方法
* 以下のファイルを.ebextensionsに配置する

```yaml
files:
  "/etc/docker/daemon.json":
    content: |
      {
        "log-driver": "json-file",
        "log-opts": {
          "max-size": "20m",
          "max-file": "3"
        }
      }

services:
  sysvinit:
    docker:
      enabled: "true"
      ensureRunning: "true"
      files:
        - "/etc/docker/daemon.json"
```

以上の設定をするとログファイル容量が20MBに達したらログローテート&ログファイルは最新から3つだけ(xxx.log, xxx.log.1, xxx.log.2)を維持します
設定できる値の詳細は[公式ドキュメント](https://docs.docker.com/config/containers/logging/json-file/#options)を参照してください

#### デメリット
* ログローテート後からなのか最初からなのか、`/var/log/docker-events.log`へのログ出力がおかしくなる(以下しか出力されなくなる)


```sh
unexpected EOF
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

```

## 過去に試した回避策

上の解決策に至る前に、色んな方法を試しました。
どれもデメリットが発生したり、問題の根本的解決にはなっていないので回避策としました。

### 回避策1: 定期的にアプリのデプロイを行う
* `<Container ID>-json.log`が大きくなりすぎる前に、(過去のバージョンでも新しいバージョンでも)アプリをデプロイする
    * デプロイをするとコンテナが切り替わるので、`/var/lib/docker/containers/<Container ID>`以下がごっそり消える

#### デメリット
* デプロイの自動実行などを設定していない限り、手間になってしまう
* デプロイ手段によってはアプリが止まる時間が発生する

#### こんな場合にオススメ
* ログ出力量が予測可能かつ定期的にデプロイすることが負担にならない(または既にそのような運用になっている)
* 他の回避策にあるような設定ファイルを書きたくない

### 回避策2: `<Container ID>-json.log`をログローテートする
* .ebextensionsを使って設定ファイルを配置して`<Container ID>-json.log`をログローテートする

#### デメリット
* ログローテート直後から`/var/log/eb-docker/containers/eb-current-app/<Container ID>-stdouterr.log`に新しいログが追記されなくなる
  * AWSサポートに聞いてみたところ、`<Container ID>-stdouterr.log`が`<Container ID>-json.log`に依存していることがわかった。
     * `<Container ID>-json.log` = Docker コンテナが出力する標準のログファイル
     * `<Container ID>-stdouterr.log` = Beanstalk 環境内で実行されている `docker logs` コマンドが出力しているログファイル
     * `docker logs` コマンドはコンテナのログ出力(=`<Container ID>-json.log`)にアクセス

#### こんな場合にオススメ
* `<Container ID>-stdouterr.log`を使わない (そんな人はそもそもアプリログをどこにも出力させないかも・・？)
* アプリが止まるのだけは避けたい
* ログ出力量が予測不能or大量で頻繁に対策が必要

#### 方法
* 以下のファイルを.ebextensionsに配置する

```yaml
files:
  "/etc/logrotate.elasticbeanstalk.hourly/logrotate.elasticbeanstalk.containers.conf":
    mode: "00644"
    owner: "root"
    group: "root"
    content: |
      /var/lib/docker/containers/*/*.log {
      size 1k
      rotate 5
      missingok
      compress
      notifempty
      copytruncate
      dateext
      dateformat %s
      olddir /var/lib/docker/containers/rotated
      }
  "/etc/cron.hourly/cron.logrotate.elasticbeanstalk.containers.conf":
    mode: "000755"
    owner: "root"
    group: "root"
    content: |
      #!/bin/sh
      test -x /usr/sbin/logrotate || exit 0
      /usr/sbin/logrotate /etc/logrotate.elasticbeanstalk.hourly/logrotate.elasticbeanstalk.containers.conf
```

### 回避策3: `<Container ID>-json.log`と`<Container ID>-stdouterr.log`をログローテート & `docker restart` する
* .ebextensionsを使って設定ファイルを配置して`<Container ID>-json.log`をログローテートする
* ログローテート後の操作として、`docker restart`を埋め込む

#### デメリット
* `docker restart` が走っている間、アプリが止まってしまう
    * 私の環境では10秒前後止まってしいます。
* ログローテート後、`/var/log/docker-events.log`へのログ出力がおかしくなる(以下しか出力されなくなる)

```sh
unexpected EOF
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

```

#### こんな場合にオススメ
* `<Container ID>-stdouterr.log`は使いたいが、`docker-events.log`は不要
* アプリが多少止まっても構わない(クライアント側に策があるor使用のない時間帯がある)

#### 方法
* 以下のファイルを.ebextensionsに配置する(ログローテートの頻度をhoulyからdailyに変更しているので少し複雑です)

```yaml
files:
  "/tmp/custom-logrotate.conf":
    mode: "00644"
    owner: "root"
    group: "root"
    content: |
      /var/log/eb-docker/containers/eb-current-app/* /var/lib/docker/containers/*/*.log {
      size 1k
      rotate 5
      missingok
      compress
      notifempty
      copytruncate
      dateext
      dateformat %s
      olddir /var/log/eb-docker/containers/eb-current-app/rotated
      sharedscripts
      postrotate
      /sbin/service docker restart > /dev/null 2>&1 || true
      endscript
      }
  "/etc/cron.d/cron-my-custom":
    mode: "000644"
    owner: root
    group: root
    content: |
        SHELL=/bin/bash
        PATH=/sbin:/bin:/usr/sbin:/usr/bin
        MAILTO=root
        HOME=/
        05 04 * * * root run-parts /etc/cron.my.custom/

container_commands:
  00-create-rotate-daily-dir:
    command: mkdir -p /etc/logrotate.elasticbeanstalk.daily
  01-copy-rotate-conf:
    command: cp -f /tmp/custom-logrotate.conf /etc/logrotate.elasticbeanstalk.daily/logrotate.elasticbeanstalk.applogs.conf
  02-remove-existing-applogs-rotate-conf:
    command: rm -f /etc/logrotate.elasticbeanstalk.hourly/logrotate.elasticbeanstalk.applogs.conf
  03-create-custom-cron-dir:
    command: mkdir -p /etc/cron.my.custom
  04-move-applogs-cron-conf:
    command: mv /etc/cron.hourly/*applogs.conf /etc/cron.my.custom/
    ignoreErrors: true
  05-modify-applogs-cron-conf:
    command: sed -i s/hourly/daily/g /etc/cron.my.custom/*applogs.conf
```