DockerのTTYって何?

3 min read読了の目安(約3100字

概要

docker-composeでよくコンテナ永続させるためにttyをtrueに設定してコンテナが正常終了しないようにしている記事があるが、ttyってそもそもなんなの、なんでコンテナが正常終了しないの?というところを忘備録として残しておきます。

コンテナが正常終了する例としてGoコンテナをみてみる

version: "3.8"
	services:
		go-app:
		    build:
			context: .
			dockerfile: ./docker/go/Dockerfile
		    volumes:
			- ./src/sample-go-fluentd:/go/src
		    ports:
			- 10881:8080
		    working_dir: /go/src

		    # ここをfalseに
		    tty: false
$ docker-compose up
# ...省略
go-app_1 exited with code 0

こんな感じログが吐かれてコンテナが停止してしまいます。
コンテナを起動させ続けるためのプロセスが存在しないため、コンテナが正常終了してしまいます。

まずはTTY

まずはttyってそもそもなんなの?というところからスタート。
wiki曰く、

ttyとは、標準入出力となっている端末デバイス(制御端末、controlling terminal)の名前を表示するUnix系のコマンドである。元来ttyとはteletypewriter(テレタイプライター)のことを指す。

どうやらttyは標準入出力先のデバイスとのことです。
そして、その標準入出力先のデバイスを確認する方法が、ttyコマンドで、このコマンドはUnix系のコマンドであるため、ターミナルでttyとそのまま叩けます。

以下に例としてttyのコマンドと標準入出力先デバイスの具体例を記述します。

AさんとBさんの2名が同じPC(PCというかサーバーの方がわかりやすい)にログインしていたとして、ttyコマンドを実行すると

# Aさんが実行
$ tty
/dev/pts/0
# Bさんが実行
$ tty
/dev/pts/1

こんな感じで現在使用している端末を取得できます。

ptsって何?

ptsはsshやtelnetで接続している仮想端末(ターミナル)で、sshやtelnetで接続時標準入出力先デバイスとすることでターミナル上に文字の表示をしたりできます。

これで両者の仮想端末/dev/pts/0/dev/pts/1が確認できました。これが入出力先デバイスなので、以下を実行すると、

# Aさんが実行
$ echo "Hello!" > /dev/pts/1

Bさんの画面に

Hello!

と出力されます。
少し驚きました、相手に対してしれっと標準出力できちゃうんだなーと。

検証も兼ねて実際に試してみました。
ttyのgif.gif
相手の端末へ標準出力が普通に流れていますね。このGif画像では同じターミナルですがそれぞれ別のプロセスで実行されています。

もう少し裏側が知りたくなったので、標準出力先を追ってみると、

# Aさん
/dev/stdout -> /proc/8/fd/1 -> /dev/pts/0
# Bさん 
/dev/stdout -> /proc/10/fd/1 -> /dev/pts/1

標準出力先である/dev/stdoutは、現在の仮想端末プロセスである/proc/self/fd/1のシンボリックリンクになっていて、/proc/self/fd/1は仮想端末のシンボリックリンクになっていました。

/proc/self/fdとは?

/proc/selfは現在実行中のプロセスへのリンクを保持するディレクトリです。
self部分は実際には$PIDが割り当てられています。$PIDとはプロセスのIDです。

# Aさん 
/proc/self -> /proc/8
# Bさん 
/proc/self -> /proc/10

のようになっており、プロセス毎にselfの部分は異なります。

/proc/self/fd直下の「0」「1」「2」などは、ファイルディスクリプター番号と同じで、以下の表通りです。

ファイルディスクリプター番号 出力先
0 標準入力
1 標準出力
2 標準エラー出力
n 任意の入出力先

少し逸れますが、これ標準出力を相手にむけて流していますが別のものにもできます。
Nginxのコンテナでアクセスログやエラーログを標準出力に流しててそれをDocker側がログキャッチしてたりするのでなかなか標準出力周りって面白いですね。

ではDockerにおけるTTYとは?

Dockerにおけるttyは、仮想端末を配置するコマンド、になります。
上で確認したようにttyは標準入出力先のデバイスであり、
具体的にはsshやTelnetで繋いでいる仮想端末で、それはディレクトリの/dev/ptsという場所で管理されていました。
そういった意味で、docker-compose.ymltty:trueと設定をすると/dev/ptsに端末が追加されるということだと考えられます。

TTYって設定する必要あるの?

tty:true設定をすることで、コンテナが正常終了するのを防ぐことができます。
例えば最初にGoコンテナを起動したら正常終了しましたが、あれはGoコンテナにフォアグラウンドで実行するプロセスが無いため、そのままDockerがコンテナを終了させています。
そこでttyです、これを設定することでコンテナ側に仮想端末を置いて実行しているプロセスを用意してあげることによってコンテナを持続させることができます。
正常終了してしまうと、コンテナの中に入ってサーバーを立てたりすることができないので、なんらかのフォアグラウンドで実行するプロセスが必要になります。
その役割がDockerのTTYということです。

例として、Nginxはデフォルトでバックグラウンドで動かすので、Nginxコンテナもフォアグラウンドで実行されているプロセスがないため止まりそうなものですが、Nginxコンテナは正常終了しません。
それは以下の記述がNginxイメージに入っているからです。

CMD ["nginx" "-g" "daemon off;"]

フォアグラウンドで起動するように設定しているため、Nginxのコンテナは止まらないということになります。

なのでTTYを設定する必要のあるコンテナは、

  • コンテナを継続させたい
  • Dockerの起動プロセスでフォアグラウンドで実行しているプログラムがない

場合に限るかなと。
以上です、参考になれば幸いです。