Elasticsearch podでfile descriptor数制限が発生した時に、やったことまとめ

EKS上でElasticsearchを稼働させていた時のこと。
Elasticsearch内でインデックスが多くなりすぎ、`java.io.IOException: Too many open files`が発生しました。

File descriptor数が上限に達してしまっていることが原因だろう、と原因については割と早く当たりがつきました。
そこで`nofile`を変更しようとなったのですが、ではどうやって`nofile`を変更するか、というところで時間がかかってしまったので、うまくいった方法とうまくいかなかったことをまとめます。

# 環境（EKS）
```
Kubernetes バージョン: 1.12
プラットフォームのバージョン: 7
```

# うまくいった方法
## `docker.service`が読み込む環境変数ファイルを編集する
参考: [ulimit of nofile in Amazon ECS-optimized AMI](https://medium.com/@pahud/ulimit-of-nofile-in-amazon-ecs-optimized-ami-6790aedee582)
方針として、dockerコンテナの`nofile`を変更するために、Workerノードで実行されるdocker daemonの`default-ulimit`設定を変更することにします。
Workerノードの作成時に、以下の設定を追記してdaemonを再起動します。`xxxxx`には、`nofile`のsoft limit/hard limitとして適切な値を記載します。

```shell:/etc/sysconfig/docker
OPTIONS="--default-ulimit nofile=xxxxx:xxxxx"
```
`/etc/sysconfig/docker`は、`docker.service`が起動する時にサービスファイル内で読み込まれる環境変数ファイルです。
`systemd`によってdaemonが起動する時に、上記のオプション付きで起動され、コンテナを起動する際のデフォルトの`nofile`を変更することができます。

実際に設定を投入する際には、CloudFormationテンプレートにて、`UserData`にノード作成時の処理を記載すれば良いです。
設定投入後、サービスを再起動します。

```yaml
Resources:
  NodeLaunchConfig:
    Properties:
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          echo 'OPTIONS="--default-ulimit nofile=xxxxx:xxxxx"' >> /etc/sysconfig/docker
          systemctl restart docker.service

```

# うまくいかなかった方法
以下、うまくいかなかった方法を列挙します。
なお、なぜうまくいかなかったのか、と言うところまでは調査しきれていないです（そのため、単なる記載誤りの可能性もあります）。


## `/etc/sysconfig/docker`ではなく、`ExecStart`に直接オプションを追記する
参考: [Can I set ulimit for containers in k8s?](https://www.edureka.co/community/20382/can-i-set-ulimit-for-containers-in-k8s)
同じく`default-ulimit`を変更するために、`docker.service`のサービスファイルを、以下のように編集して再起動してみました。

```/usr/lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd --default-ulimit nofile=xxxxx:xxxxx
```
こちらの方法では、`nofile`の値が反映されませんでした。


## `/etc/docker/daemon.json`に、`default-ulimit`設定を追記する
参考: [How do I set ulimit for containers in Kubernetes?](https://stackoverflow.com/questions/33649192/how-do-i-set-ulimit-for-containers-in-kubernetes)
こちらも`default-ulimit`を変更する方針。
docker daemonの設定ファイルに、`default-ulimit`設定を記載してみましたが、こちらも正しく反映されませんでした。

```json:/etc/docker/daemon.json
{
...
  "default-ulimits": {
    "nofile": {
      "Name": "nofile",
      "Soft": xxxxx,
      "Hard": xxxxx
    }
  },
...
}
```

## Kubernetesのマニフェスト中で、`InitContainer`を使って設定する
参考: [GKE でカーネルパラメータの設定](https://qiita.com/sonots/items/60721e34c0e31367efa2)
参考: [How to set `ulimit` using kubernetes?](https://www.reddit.com/r/kubernetes/comments/anx4pd/how_to_set_ulimit_using_kubernetes/)
時系列的には、一番最初に試した方法です。
`InitContainer`でカーネルパラメータが設定できるなら、`nofile`も設定できないかな、と思って試したものです。
`nofile`の変更はプロセス単位で反映されます。そのため、この方法で`nofile`を変更した場合、変更は`InitContainer`として実行したコンテナにのみ反映され、Pod内で実際にワークロードを実行したいコンテナには引き継がれません（カーネルはホストと共有しているため、カーネルパラメータの変更は有効です）。

```yaml
...
  spec:
    initContainers:
    - image: alpine:3.6
      command: ["sh", "-c", "ulimit", "-n", "xxxxx"]
      name: init-container
      securityContext:
        privileged: true
...
```

# まとめ
上記の通り、docker daemonに対して`nofile`を設定するのが良さそうです。
が、何で`/etc/sysconfig/docker`に書く方法だけうまくいくんだろう・・・

現状、コンテナごとに`nofile`を変更する手立てはなく、Workerノードで稼働しているdocker daemonのデフォルト設定を変更することが必要です。
そのため、複数のアプリケーションで`nofile`に上限を設けることが必要な場合は、最も大きな上限に合わせなければなりません。
（Kubernetesでも、コンテナごとに変更する機能はサポートしていないようです。[こちら](https://github.com/kubernetes/kubernetes/issues/3595#issuecomment-142462460)に言及があります。）
また、`nofile`以外のリソース制限についても、同様の方法で変更することができそうです。