SSH 接続可能な Docker コンテナを載せた VM に繋がらなくなったときの対処

(2020/09/25)
本記事は、下記記事からトラブルシュート部分を抜き出し、追記修正したものです。
[最小構成の CentOS 8 に SSH 接続可能な Docker コンテナを作ってみる](https://qiita.com/Shiratsuki_Kurom/items/ac0222b3e3c3dc1ca04d)

# 概要

SSH 接続できる Docker コンテナでしばらく遊んでいると、コンテナを載せている VM に SSH 接続ができなくなるという問題が発生したため、その調査と解決策についてまとめる。


# 環境
 - Windows10 Home (1909)　（ホスト）
 - VirtualBox 6.1.4
 - CentOS 8.1.1911　（ゲスト）
 - Docker 19.03.8
 - Docker Compose 1.25.4


# 問題と対処

本記事では、どこからのコマンド実行かを明示するため、コマンド行の先頭に装置情報を付加する。
先頭が`[centos@dockertest ~]$` となっているものはゲスト VM 上、`test~:$` となっているものはコンテナ上でのコマンド実行を表すものとする。

## 問題発生

立てたコンテナに対して SSH アクセスするスクリプトを組んだりしてしばらく遊んでいると、あるとき突然ホストからゲスト VM への SSH 接続ができなくなってしまった。
具体的には、 TeraTerm でアクセスしようとすると、画面が真っ黒のまま応答が無い。
また、他のゲスト VM からコンテナを載せたゲスト VM へ`ssh`でアクセスしてみると、下記のメッセージが出て接続が弾かれてしまう。

```
[centos@othervm ~]$ ssh -p 2222 centos@192.168.11.4
shell request failed on channel 0
```


## 調査

上記メッセージ（`shell request failed on channel 0`）をもとにググってみると、下記のような記事がヒット。

参考：[ssh ログインができない「shell request failed on channel 0」の対応方法](http://psychedelicnekopunch.com/archives/242)

環境等異なるところはあるものの、プロセス周りが怪しいと当たりをつけて、動いているプロセスを調べてみる。

```
[centos@dockertest ~]$ ps aux | grep ssh
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
（～中略～）
22        7762  0.0  0.0      0     0 ?        Z    02:27   0:00 [sshd] <defunct>
22        7764  0.0  0.0      0     0 ?        Z    02:27   0:00 [sshd] <defunct>
22        7766  0.0  0.0      0     0 ?        Z    02:27   0:00 [sshd] <defunct>
22        7768  0.0  0.0      0     0 ?        Z    02:27   0:00 [sshd] <defunct>
22        7770  0.0  0.0      0     0 ?        Z    02:27   0:00 [sshd] <defunct>
22        7772  0.0  0.0      0     0 ?        Z    02:27   0:00 [sshd] <defunct>
22        7774  0.0  0.0      0     0 ?        Z    02:27   0:00 [sshd] <defunct>
22        7776  0.0  0.0      0     0 ?        Z    02:27   0:00 [sshd] <defunct>
22        7778  0.0  0.0      0     0 ?        Z    02:27   0:00 [sshd] <defunct>
22        7780  0.0  0.0      0     0 ?        Z    02:27   0:00 [sshd] <defunct>
22        7782  0.0  0.0      0     0 ?        Z    02:27   0:00 [sshd] <defunct>
22        7784  0.0  0.0      0     0 ?        Z    02:27   0:00 [sshd] <defunct>
22        7786  0.0  0.0      0     0 ?        Z    02:27   0:00 [sshd] <defunct>
22        7788  0.0  0.0      0     0 ?        Z    02:27   0:00 [sshd] <defunct>
（～中略～）
```

お、おおう……。

「`Z`」や「`defunct`」という表記から、どうも大量の`ssh`プロセスがゾンビ化して、使用可能なプロセスIDを食いつぶしているらしいということがわかる。
ゲスト VM やコンテナを再起動すると一旦は再接続できるようになるものの、SSH 接続する度にゾンビプロセスが増えていくため、恒久対処にはならず、時限爆弾を抱えることになる。

```
[centos@dockertest ~]$ ps aux | grep Z | wc -l
2
（この間にホストから TeraTerm でコンテナに SSH 接続 ⇒ 切断を実行）
[centos@dockertest ~]$ ps aux | grep Z | wc -l
3
```
※「2」は`ps`コマンド出力のヘッダと実行コマンド自身（`grep Z`）がヒットしているだけなので、結果が「3」以上であればゾンビプロセスが発生していることがわかる。


## 原因

調べてみると、一般的に Linux では`init`というプロセスがすべてのプロセスの祖先になっていて、これがうまい具合にプロセス管理をしてくれ、通常はゾンビプロセスが発生しないようになっているらしい。
ところが、今回作成したDocker コンテナには`init`プロセスが存在しないので、プロセス管理の恩恵を受けられない。その結果、一度接続した`ssh`プロセスが、その役目を終えたあとに行き場を失ってゾンビ化し、それが積み重なっていっていずれ新たな`ssh`プロセスが起動できなくなる、ということのようだ。

ちなみに、`init`はプロセスIDとして1番（PID 1）が割り当てられるようだが、コンテナの PID 1 には`Dockerfile`で`CMD`命令に記述している`tail`が割り当てられている。

```
test:~$ ps
PID   USER     TIME  COMMAND
    1 root      0:00 tail -f /dev/null
  ...
```

なお、ゾンビプロセスに関する詳しい原理については下記記事が参考になった。

参考：[Unix プロセスと Docker の罠](https://kechako.dev/posts/2015/05/27/210459/)


## 解決策

方法はいろいろありそうだが、調査過程で`init`について学んだので、これを活かす方向で解決してみたい。
Docker Compose が version 3.7 以上であれば、`docker-compose.yml`に`init: true`を追加すれば、コンテナで`init`が動くようになるとのこと。

参考：[Dockerの--initフラグについて](https://christina04.hatenablog.com/entry/docker-init)


``` docker-compose.yml(修正後)
version: '3.8'

services:
  test:
    build: .
    container_name: test
    hostname: test
    ports:
      - "2222:22"
    tty: true
    init: true # この行を追加
```

実際に上記の通りファイルを書き換えて、再度コンテナを立ち上げてみる。

```
[centos@dockertest ~]$ docker-compose down
[centos@dockertest ~]$ docker-compose build
[centos@dockertest ~]$ docker-compose up -d
```

コンテナが立ち上がったら、再度ホストから TeraTerm でコンテナに SSH 接続し、プロセスを確認してみる。

```
test:~$ ps
PID   USER     TIME  COMMAND
    1 root      0:00 /sbin/docker-init -- /bin/sh -c /etc/init.d/sshd start && tail -f /dev/null
  ...
```

おお、`COMMAND`部分がちゃんと変わっている。
念の為、SSH 接続してゾンビプロセスが増えないことも確かめる。

```
[centos@dockertest ~]$ ps aux | grep Z | wc -l
2
（この間にホストから TeraTerm でコンテナに SSH 接続 ⇒ 切断を実行）
[centos@dockertest ~]$ ps aux | grep Z | wc -l
2
```

よきかな。


# まとめ

- SSH できる Docker コンテナを作るときは`init`を動かすようにしよう
- Docker コンテナを動かすときはゾンビプロセスの増殖に気をつけよう

プロセス管理絡みの話はこれまで特に気にしていなかったところだったので、いろいろ勉強になった。
そもそも Docker の思想として SSH 接続できるコンテナが是とされているのか、という問題はあるが、その辺りは追々勉強していきたい。