前置き
- 「Dockerはkernelを共有する」。よく聞くフレーズだと思います。しかし具体的にカーネル共有するから何が起きるんでしょうか?
- コンテナはOSディストリビューションも分離できてるし、IPも分離できてるし、ファイルシステムも分離できてるし、で何を共有してるのか良くわかりません。
- せいぜい「Linuxしか動かない」ぐらいにも思えます
- 実際はそんなことはありません
- 実際に共有してるもので障害が起きたことがあるので、それを契機に調べてみました
記事が長いので先に結論
-
Linuxの持つ分離機能で分離されていないものがコンテナ同士やホストで共有されています
-
特にカーネルパラメータで上限設定されているものが共有されていると、コンテナ・ホスト間でリソースの奪い合いが起きます
- 例として、「オープンできるファイル数の上限」や「ファイル変更監視の上限数」です
- コンテナでそういったリソースを使い切ると、別コンテナやホスト側でコマンドが失敗する等の影響が出ます
-
システム全体の上限を超えてコンテナが消費するケースについては回避が難しいと思います
- あらかじめ上限を超えそうとわかっている場合はホストのカーネルパラメータを上げておくといいと思います
- どういうコンテナが動くかわかっている場合は、そのアプリのパラメーターチューニングでググって、ホストのパラメータを上げるといいと思います
-
ユーザーごとに上限が設定されるような項目については、 緩和策としてコンテナの実行ユーザーを変えることが有効そうです
調査方針:引き算で考えてみる
- 何を共有しているか?ではなく、カーネルにどういう項目があって、そのうち何が分離されているか調べる方針で行きます。
- 「カーネル全体」 - 「分離された項目」 = 「分離されていない(共有されている)項目」
- とはいえカーネル全体だと膨大になることが想像できるので、先に分離されているものから見た後に、どんなカーネルパラメータがあるかを調べて見て、その中で分離されていなさそうなものを取り上げていきます。
- 特に上限のあるものに着目してみます。コンテナ間で奪い合って競合が起きるからです
- 他の視点もあればコメントください
- あたりを付けたら実際に共有されているものを奪い合うなどしてみて、何が起きるか観察します
分離されているものを調べる
- Docker内部で利用されているLinuxカーネルの機能 (namespace/cgroups)が良いので本記事では割愛
カーネル全体にどういうものがあるのか調べる
見つけたコンテナ間で共有されてそうな項目
内容 |
---|
オープンできるファイルの最大数 |
変更監視するファイル数上限 |
パケット受信時にキューに繋ぐことができるパケットの最大数 |
- このうち、検証が簡単そうな「オープンできるファイルの最大数」、「変更監視するファイル数上限」でホントに競合するか検証してみます
競合をおこしてみる検証
- 環境情報
- VMPlayperの仮想マシンで構築
- Linux ubuntu 4.4.0-134-generic
- Docker version 18.05.0-ce, build f150324
オープンできるファイルの最大数で競合を起こしてみる
初期状態チェック
項目 | 確認コマンド | 結果 |
---|---|---|
現在オープン中のファイル数 | lsof | wc -l | 26000 |
最大オープン数 | cat /proc/sys/fs/file-max | 808880 |
やってみる
手順
-
競合しやすいように上限を減らします
echo 100000 > /proc/sys/fs/file-max
```
2. ターミナルを2つ開き、コンテナを2つ立てます
```bash:ターミナル1
docker run -it centos bash
bash:ターミナル2
docker run -it centos bash
```
3. 2つのコンテナ内でファイルを大量(50000個)に作ります
```bash
mkdir files
touch files/file{1..50000}
```
-
2つのコンテナ内にファイルをopenするためのスクリプト
open-files.py
を置きますopen-files.pyimport time numList = range(1, 50000) def open_files(n): return open('files/file' + str(n),'w') fileList = map(open_files, numList) time.sleep(60)
-
コンテナ1で
python open-files.py
を実行- この時点ではまだ上限は超えないのでエラーにはなりません
- hostで
lsof
するとオープンしているファイル数がコンテナ内でオープンした50000個分増えているのがわかります- システムが裏側でファイル操作してたりするので若干の誤差はあります
hostで実行
root@ubuntu:~# lsof | wc -l
76055
```
6. コンテナ2でpython open-files.py
を実行します
```bash
[root@d18f088f96c6 /]# python open-files.py
Traceback (most recent call last):
File "open-files.py", line 8, in
fileList = map(open_files, numList)
File "open-files.py", line 6, in open_files
return open('files/file' + str(n),'w')
IOError: [Errno 23] Too many open files in system: 'files/file42987'
```
* オープンできるファイル数上限を超えたためにエラー。コンテナ1のせいでコンテナ2がファイルオープンできません
分かったこと
- オープンできるファイル数には上限があります
- 上限はコンテナ・ホスト全体のオープン数の合計にかかります
- そのため、どれか1つのコンテナが大量にオープンすると他のコンテナやホストでファイルオープンできない事態が発生します
変更監視するファイル数上限 で競合を起こしてみる
- コンテナAで大量のファイル変更監視をすればこの項目を枯渇させることができ、コンテナBやホストでファイル変更監視できないという事態を起こせそうです
- この項目はシステム全体上限だけでなく、ユーザー単位でも上限があります
初期状態チェック
項目 | 確認できるファイル | 結果 |
---|---|---|
inotify インスタンスキューに入る最大イベント数 | /proc/sys/fs/inotify/max_queued_events | 16384 |
ユーザーごとのinotify インスタンスの数の上限 | /proc/sys/fs/inotify/max_user_instances | 128 |
ユーザーごとの作成可能な監視対象の数の上限 | /proc/sys/fs/inotify/max_user_watches | 524288 |
- inotifyというのは、ファイル変更監視に用いられる Linuxのシステムコールです
- 今回は「ユーザーごとの作成可能な監視対象の数の上限」を100に下げてみて競合するのを確認してみます
- dockerのデフォルトではrootユーザーがコンテナ内外で共通のため、rootでやれば競合するはずです
やってみる
- 最初はコンテナ内で
tail -f
することで挑戦しましたが、うまくいきませんでした- 原因が気にはなりますが、そこの調査は目的ではないので今回はパスします
- docker内でcreate-react-appでセットアップした後に
npm run start
で監視してみます- reactのアプリを作る際にデバッグ用サーバーが立ち、ソース変更するとすぐにサーバーに反映される、というコマンドです
- もうちょっと自然な検証がないか検討中。。
手順
-
nodejs dockerコンテナを起動
hostで実行
root@ubuntu:~# docker run -it --rm --name node node bash
root@1c7afe16ce2c:/#
```
-
create-react-appでプロジェクトセットアップ
コンテナで実行
root@1c7afe16ce2c:/# npm install -g create-react-app
bash:コンテナで実行
root@1c7afe16ce2c:/# create-react-app test
```
-
test/srcディレクトリにファイルを1000個生成
コンテナで実行
root@1c7afe16ce2c:/test# touch src/file{1..1000}.txt
```
-
ファイル変更監視の上限変更
hostで実行
root@ubuntu:~# echo 100 > /proc/sys/fs/inotify/max_user_watches
* ちなみに、コンテナの中で上限を確認しても確かに変更されています
```bash:コンテナで実行
root@1c7afe16ce2c:/test# cat /proc/sys/fs/inotify/max_user_watches
100
```
5. コンテナの中でファイル変更監視する前はhostで`tail -f`実行してもエラーを出さないことを確認
```bash:hostで実行
root@ubuntu:~# tail -f file_on_host
^C
```
6. コンテナの中でファイル変更監視を実行
```bash:コンテナで実行
root@1c7afe16ce2c:/test# npm run start
```
* この時点で上限100を超えたファイル変更監視をしてるはずですが、コンテナ内ではエラーは出ていません
* おそらくポーリング等で回避していて、エラーメッセージを出していないだけだと思われます
7. hostで`tail -f`を実行。エラーが出る
```bash:hostで実行
root@ubuntu:~# tail -f file_on_host
tail: inotify 資源を使い果たしました
tail: inotify を使用できません。ポーリングに戻ります
```
* tailの場合はポーリングで回避していますが、アプリによってはエラーで終了してしまいます
#### 分かったこと
* ファイル変更監視できる数には上限があります
* 上限はシステム全体にかかるものと、ユーザーごとにかかるものがあります
* 同じユーザーで動いているコンテナやホストでは、片方がファイル変更監視数を使い切るともう片方のコンテナまたはホストでファイル変更監視に失敗します
* アプリによっては、変更監視に失敗してもポーリング等で回避してくれます
# まとめ
* 結論は冒頭で述べた通りです
* 実際に検証して競合が起きることを確認できました
# おまけ - この記事を書くきっかけとなった障害について
* 2つ目に検証したファイル変更監視で起きました。
* Kubernetes環境です
* デバッグモードでwebサーバのコンテナを起動した結果、コンテナ内のwwwroot配下の大量ファイルに変更監視が走りました。
* その結果ホストでファイル変更監視ができない状態になりました。
* その後しばらく(数日)してから、なぜかkubeletが落ちてしまい、systemdで再起動しようとしました。(ここの真相は追えてないです)
* kubeletが設定ファイルに変更監視をかけられず、再起動しようとしても起動できない状態でした
* という障害でした