「コンテナには OS はない」ということについて


Posted at: 2022-02-11

Docker
Docker logo

TL;DR

  • コンテナには OS はない
    • Ubuntu や CentOS のコンテナは存在するが, カーネルを含まないディストリビューションのようなものであり OS ではない
  • コンテナは Linux カーネルの機能で実現しているので, ホスト OS に Linux カーネルが必須
    • Windows や Mac でも Docker が使えるのは Docker Desktop が Linux VM のようなものを作ってその上で動かしているため

コンテナと OS の関係

コンテナ技術の話になるとよく「コンテナに OS はない」という話を目にします。これまできちんと理解できていませんでしたが, 調べて納得したので記録しておきます。

Ubuntu や CeontOS などのコンテナイメージは何者か

Dokcer 公式のレジストリである Docker Hub には, UbuntuCentOS のイメージが存在します。 これを見て, 「コンテナにも OS があるのでは」と思ってしまいがち1 ですが, これは 「ディストリビューション」「カーネル」「OS」に関する誤解 に基づきます。

まず, 重要な点として, Ubuntu や CentOS はディストリビューションです。ディストリビューションは大抵, Linux カーネル + 基本ソフトウェア群 を指します。 シェルや, 普段よく使うコマンド群 (例えば echogrep) も「基本ソフトウェア群」の範疇です。 ただしややこしいのですが, ディストリビューションは 「広義の OS」 と呼ばれることもあります。

一方カーネルは, ディストリビューションに比べ, よりコアの部分を指し, 例えばハードウェアとソフトウェアの仲介を行います。 「狭義の OS」 と呼ばれることもあります。 上述の通りシェルもカーネルに含まれないので, カーネルだけでは通常は何もできません2。 ちなみに Linux カーネルは GitHub 上 で開発されています3

ここからが本題ですが, では Docker Hub にある Ubuntu や CentOS のコンテナは何なのでしょうか。

コンテナの仕組み

そもそもですが, コンテナは Linux kernel の namespaces, cgroups などの機能によって実現されています4

  • namespaces: プロセス, ネットワーク, ユーザーなどのグローバルなリソースを独立させる5
  • cgroups: CPU, メモリなどのリソース割り当てをプロセスごとに制限する

このため, 前提として, 通常は Linux kernel 上でないと Docker をはじめとするコンテナは作れません

そしてコンテナは, これらの機能によって, ホスト OS の一部を論理的に切り離している に過ぎないと言えます。 このように, あくまでコンテナは「ホスト OS の一部」なので, Linux 以外のコンテナは存在しません。 Docker Hub などのコンテナレジストリを見ても Windows や macOS のコンテナが存在しないのはこのためです。

実際のコンテナの様子を確認する

では実際に操作して確認してみます。 Docker を動かすホスト OS は Ubuntu 20.04, コンテナは Docker Hub にある CentOS イメージを使います。

まずコンテナのディストリビューションを確認します。

[[email protected] /]# cat /etc/os-release
NAME="CentOS Linux"
VERSION="8"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="8"
PLATFORM_ID="platform:el8"
PRETTY_NAME="CentOS Linux 8"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:8"
HOME_URL="https://centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"
CENTOS_MANTISBT_PROJECT="CentOS-8"
CENTOS_MANTISBT_PROJECT_VERSION="8"
[[email protected] /]#

ホスト OS は Ubuntu であるにもかかわらず, 確かにコンテナのディストリビューションは CentOS になっていました。 次に, Ubuntu, CentOS それぞれが 標準で実行できるはずのパッケージ管理コマンド を実行してみます。

[[email protected] /]# dpkg -l
bash: dpkg: command not found
[[email protected] /]# apt-get
bash: apt-get: command not found
[[email protected] /]#

[[email protected] /]# rpm -l
[[email protected] /]# yum history
Failed to set locale, defaulting to C.UTF-8
ID     | Command line                                                                                                                                                    | Date and time    | Action(s)      | Altered
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
[[email protected] /]#

コンテナ上では, Ubuntu なら通常は実行できるはずの dpkg, apt-get が実行できず, CentOS が通常実行できる rpm, yum が実行できました。

ここでもう一度, 今度は uname -a でコンテナの OS の情報を見てみます。

[[email protected] /]# uname -a
Linux fdda8cec6fe2 5.4.0-97-generic #110-Ubuntu SMP Thu Jan 13 18:28:08 UTC 2022 aarch64 aarch64 aarch64 GNU/Linux
[[email protected] /]#

よく見ると, /etc/os-release には CentOS と書いてあったのに, uname -a の結果は Ubuntu になっており, ホスト OS 上で実行した結果とほぼ同じになっています。 ただし, ホスト名のみコンテナ名になっており, コンテナ上で hostname を実行した結果と同じになっています。

[[email protected] /]# hostname
fdda8cec6fe2
[[email protected] /]#

結論

上の実験から, 以下のことがわかりました。

  1. Ubuntu のホスト OS 上で, ディストリビューションの異なる CentOS のコンテナが動かせた
  2. CentOS コンテナ上では CentOS に含まれるコマンド群が実行でき, Ubuntu に含まれるコマンド群は実行できなかった6
  3. uname -a の結果は, コンテナ上での実行でもホスト OS とほぼ同じ7

以上から, 結論ありきであれば, 「コンテナに OS はない」ということに納得できるのではないでしょうか。 コンテナレジストリなどにあるディストリビューション別のコンテナは, 確かに固有の OS (カーネル) があるわけではなく, ディストリビューションからカーネル部分を除いた, ファイルシステムやソフトウェア, コマンド群のパッケージ と言えそうです (カーネルがないので広義の OS ですらない)。

少なくとも自分はこの辺りを確認できたので, 「コンテナに OS はない」の意味に納得できました。

おまけ 1: Docker Desktop の仕組み

上で「Linux kernel 上でないと Docker をはじめとするコンテナは作れません」と書きましたが, では Linux カーネルではない Windows や Mac でなぜ Docker が使えるのでしょうか。

Windows や Mac では, Dokcer 使用時には Docker Desktop を使えます8

Docker Desktop がどのようにコンテナを Windows, Mac などの非 Linux カーネル上で実現しているかというと, 実は軽量の Linux VM を構築して, その上で Docker を動かしています。 この仕組みは例えば, Docker 公式ブログの記事, The Magic Behind the Scenes of Docker Desktop で次のように説明されています。

At the heart of Docker Desktop we have a lightweight LinuxKit VM that Docker manages for you.

おまけ 2: コンテナ上で「OS」を停止しようとするとどうなるか

本筋に関係ない実験 1。

少し怖いですが実験として, shutdownpoweroff --halt を実行して, コンテナの「OS の」電源を切れるか試してみます。

[[email protected] /]# poweroff
System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down
Failed to talk to init daemon.
[[email protected] /]# shutdown --halt
System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down
[[email protected] /]#

エラーになりました。 エラーメッセージに「systemd が init system (PID 1) として実行されていない」と出ているので, プロセスを確認します。

[[email protected] /]# ps auxf
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0   3916  2908 pts/0    Ss   12:00   0:00 /bin/bash
root          48  0.0  0.0   8604  3024 pts/0    R+   12:17   0:00 ps auxf
[[email protected] /]#

docker run した時に指定した /bin/bash が PID 1 になっています。 通常は init プロセスが存在して, それを頂点とするプロセスツリーができているはずなのですが, コンテナはこのようになります。 ホスト OS の方では, 確かに init プロセス (systemd) が PID 1 になっていることを確認できます。

[email protected]:~$ ps auxf
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
[...]
root           1  0.0  0.1 101664 10416 ?        Ss   16:09   0:01 /sbin/init splash
root         400  0.0  0.4  83780 38560 ?        S<s  16:09   0:00 /lib/systemd/systemd-journald
root         429  0.0  0.0  18448  4704 ?        Ss   16:09   0:00 /lib/systemd/systemd-udevd
root         530  0.0  0.2 279988 16352 ?        SLsl 16:09   0:09 /sbin/multipathd -d -s
systemd+     584  0.0  0.0  90324  6284 ?        Ssl  16:09   0:00 /lib/systemd/systemd-timesyncd
systemd+     628  0.0  0.0  26284  6824 ?        Ss   16:09   0:00 /lib/systemd/systemd-networkd
systemd+     630  0.0  0.1  23892 10940 ?        Ss   16:09   0:00 /lib/systemd/systemd-resolved
root         669  0.0  0.1 240816  8456 ?        Ssl  16:09   0:01 /usr/lib/accountsservice/accounts-daemon
root         674  0.0  0.0   8336  2580 ?        Ss   16:09   0:00 /usr/sbin/cron -f
message+     675  0.0  0.0   8268  4308 ?        Ss   16:09   0:00 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
root         681  0.0  0.0  80908  2932 ?        Ssl  16:09   0:01 /usr/sbin/irqbalance --foreground
root         684  0.0  0.2  35188 18652 ?        Ss   16:09   0:00 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers
syslog       685  0.0  0.0 220936  4044 ?        Ssl  16:09   0:00 /usr/sbin/rsyslogd -n -iNONE
root         687  0.0  0.4 1093412 36980 ?       Ssl  16:09   0:04 /usr/lib/snapd/snapd
root         692  0.0  0.0  16448  6536 ?        Ss   16:09   0:00 /lib/systemd/systemd-logind
root         693  0.0  0.0  15904  5788 ?        Ss   16:09   0:00 /lib/systemd/systemd-machined
root         698  0.0  0.1 393692 12368 ?        Ssl  16:09   0:00 /usr/lib/udisks2/udisksd
daemon       703  0.0  0.0   3592  1844 ?        Ss   16:09   0:00 /usr/sbin/atd -f
root         704  0.6  0.4 1407488 38180 ?       Ssl  16:09   2:00 /usr/bin/containerd
root         721  0.0  0.0   6836  1756 ?        Ss+  16:09   0:00 /sbin/agetty -o -p -- \u --keep-baud 115200,38400,9600 ttyAMA0 vt220
root         725  0.0  0.0   5312  1484 tty1     Ss+  16:09   0:00 /sbin/agetty -o -p -- \u --noclear tty1 linux
root         728  0.0  0.0  12212  6404 ?        Ss   16:09   0:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
root        1245  0.0  0.0  15336  7648 ?        Ss   16:09   0:00  \_ sshd: kangetsu [priv]
kangetsu    1366  0.0  0.0  15352  4212 ?        S    16:09   0:01  |   \_ sshd: [email protected]/0
kangetsu    1367  0.0  0.0   9928  4880 pts/0    Ss   16:09   0:00  |       \_ -bash
kangetsu   10428  0.1  0.5 1418496 45464 pts/0   Sl+  21:00   0:01  |           \_ docker run -it centos /bin/bash
[...]
[email protected]:~$ pstree
systemd─┬─accounts-daemon───2*[{accounts-daemon}]
        ├─2*[agetty]
        ├─atd
        ├─containerd───9*[{containerd}]
        ├─containerd-shim─┬─bash
        │                 └─10*[{containerd-shim}]
[...]
[email protected]:~$

[email protected]:~$ ls -la /sbin/init
lrwxrwxrwx 1 root root 20 Jan 10 13:56 /sbin/init -> /lib/systemd/systemd
[email protected]:~$

おまけ 3: コンテナ上で保存した一時データの行方

本筋に関係ない実験 2。

コンテナ上で保存したデータがホスト上のどこに保存されているのか見ます。

まずコンテナ上で, 1GB のファイルを作成します。

[[email protected] /]# df -h /
Filesystem      Size  Used Avail Use% Mounted on
overlay          39G   18G   21G  46% /
[[email protected] /]# dd if=/dev/zero of=/data.dat bs=1G count=1
1+0 records in
1+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 0.556696 s, 1.9 GB/s
[[email protected] /]# df -h /
Filesystem      Size  Used Avail Use% Mounted on
overlay          39G   19G   20G  49% /
[[email protected] /]#

この時, 当然ホストにも 1GB のファイルが増えていることになります。 このようにしてコンテナ上で作られたファイルがどこにあるかというと, /var/lib/docker/overlay2/ 以下にありました (後述する docker image inspect であたりをつけた後, 愚直にファイル作成前後で du して探しました)。

[email protected]:~$ sudo ls -l /var/lib/docker/overlay2/337886ef932e709fecd0e4b11e0977bbf5da390c022be2deebd1dc8f7042e1d4/diff
total 1048592
-rw-r--r-- 1 root root 1073741824 Feb 11 22:26 data.dat
drwxrwxrwt 2 root root       4096 Feb 11 21:08 tmp
drwxr-xr-x 5 root root       4096 Sep 15 23:25 var
[email protected]:~$

このようなコンテナ上に作成されたファイルは, コンテナを停止しても消えませんが,

[email protected]:~$ docker container start fdda8cec6fe2
fdda8cec6fe2
[email protected]:~$ docker exec -it fdda8cec6fe2 /bin/bash
[[email protected] /]# ls /data.dat
/data.dat
[[email protected] /]#
[email protected]:~$ sudo ls -l /var/lib/docker/overlay2/337886ef932e709fecd0e4b11e0977bbf5da390c022be2deebd1dc8f7042e1d4/diff/data.dat
-rw-r--r-- 1 root root 1073741824 Feb 11 22:41 /var/lib/docker/overlay2/337886ef932e709fecd0e4b11e0977bbf5da390c022be2deebd1dc8f7042e1d4/diff/data.dat
[email protected]:~$

コンテナのインスタンス削除時に一緒に消えます。

[email protected]:~$ docker container rm fdda8cec6fe2
fdda8cec6fe2
[email protected]:~$
[email protected]:~$ sudo ls -l /var/lib/docker/overlay2/337886ef932e709fecd0e4b11e0977bbf5da390c022be2deebd1dc8f7042e1d4/diff/data.dat
ls: cannot access '/var/lib/docker/overlay2/337886ef932e709fecd0e4b11e0977bbf5da390c022be2deebd1dc8f7042e1d4/diff/data.dat': No such file or directory
[email protected]:~$ sudo ls -l /var/lib/docker/overlay2/337886ef932e709fecd0e4b11e0977bbf5da390c022be2deebd1dc8f7042e1d4/
ls: cannot access '/var/lib/docker/overlay2/337886ef932e709fecd0e4b11e0977bbf5da390c022be2deebd1dc8f7042e1d4/': No such file or directory
[email protected]:~$

ちなみに docker image inspect.GraphDriver.Data という key があり, value が /var/lib/docker/overlay2 以下のパスになっているのですが, 上で作った data.dat が保存されたのはそことは違うディレクトリでした。

[email protected]:~$ docker image inspect centos | jq '.[].GraphDriver'
{
  "Data": {
    "MergedDir": "/var/lib/docker/overlay2/4c506c18e194df7f7396d93a287027d81e0ceac12856df9b7e5c25175fd169f5/merged",
    "UpperDir": "/var/lib/docker/overlay2/4c506c18e194df7f7396d93a287027d81e0ceac12856df9b7e5c25175fd169f5/diff",
    "WorkDir": "/var/lib/docker/overlay2/4c506c18e194df7f7396d93a287027d81e0ceac12856df9b7e5c25175fd169f5/work"
  },
  "Name": "overlay2"
}
[email protected]:~$

この辺りは理解できていないので, また後で調べてみようと思います。


  1. 私はそう思ってました
  2. 頑張ればもちろんいろいろできるけどユーザーが直接使うようなものではない
  3. 本来 Linux はこの Linux カーネルのみを指す, と言われる
  4. 一緒に語られることもありますが, 呼び出したプロセスにとってのルートディレクトリ (/) を変更する chroot はコンテナ技術とは別物です
  5. プログラミングにおける「名前空間」をイメージするとわかりやすい
  6. コマンドや関連ファイルがあるかないかの違いなので, 厳密には Ubuntu などの Debian 系で rpmyum を実行することもできるはず。逆も同様。デフォルトで入っていないというだけ
  7. 本当は実装をちゃんと見ないとこれが証拠になるかは断言できないが
  8. Docker Desktop を使わなくても, Windows でも例えば WSL (Windows Subsystem for Linux) などを使えば Docker を使える

About This Blog

Blog: 学習メモや読書記録など

Zenn: 体系的にまとめたもの

Qiita: 過去記事

はてなブログ: 過去記事


ご意見・ご質問などは Twitter にお送りください

Author

大学院修士課程で認知/教育心理学を学びました

1社目: 保守コンサルタント

2社目: QAエンジニア

Links

Tags

Docker (1)GatsbyJS (2)GitHub (1)Markdown (2)React (2)VS Code (1)