今更の記事ですが、ざっくり説明用に書きました。
背景
近年の開発では、各自のローカル PC 上での開発として docker (docker-compose) を使う事が多くなりました。
例えば、最近の Web Application の殆どは以下の3つを使って動きます。
昔は開発者 wiki や README.md
に上記の構築方法が書かれていて、開発者みんなが頑張って自前でローカルマシン上に構築をしていました。
もしくは VMWare イメージを配布する〜、Vagrant 等が試みられていた。
最近では、もうこれ一発で 開発者の環境差分の影響もなく (※ M1 Mac.. 😭) 簡単に構築できますね。素晴らしい時代です。
docker-compose up -d
とは言え、ブラックボックス化され初期設計した人以外は 「手順通りにやったら動いたけど、仕組みが全然わからない!」 になっている事が多い様に思えます。
目的
と言うことで、本書では以下の3つを説明します。
- そもそも Docker って何?
- Docker Desktop for Mac / Windows って、素の Docker とどう違うの?
- Docker Desktop 有料になっちゃった。Rancher Desktop がいいらしいけど、ナニソレ?
コンテナ仮想化
そもそもコンテナって? (VM とは違うよ)
良くVM (仮想マシン) と混同されますが、そもそも大きく違うのは次の点です。
- VM (仮想マシン) は OS を仮想化する
- コンテナ技術は プロセス を仮想化する
例えば VM (仮想マシン) 以下の様に図示されます。
AWS とかは Hypervisor 型で、 Xen を使ってます。
一方コンテナ技術はこうです。
実は Linux OS 上で直接プロセスを起動しているだけで、普通のプロセスと大きく変わりはありません。
つまり、コンテナ仮想化とは Linux 上で 「プロセスを動かす空間を分離する」 事ですが、それが具体的にどういう事か見ていきましょう。
プロセス空間分離
AWS 上に立てた Amazon Linux 2 インスタンス上で実際に確認してみます。
以下のコマンドで nginx コンテナを起動しましょう。
docker run -d --name nginx -p 8080:80 nginx:1.21-alpine
ブラウザで http://localhost:8080 で、Docker Image から起動した nginx にアクセスできます。
以降の説明では、実際にコンテナの中に入って実例で説明をします。以下のコマンドで入れます。
docker exec -it nginx sh
PID 名前空間
「PID 名前空間」とは ps aux
した時に見えるプロセスの一覧の事です。
実際にホスト OS 上で ps aux
してみます。
実行されている全プロセスが見れますが、 実はコンテナとして起動したプロセスも見えています。
[ec2-user@ip-172-30-1-40 ~]$ ps auxf
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.2 123716 5608 ? Ss 06:46 0:03 /usr/lib/systemd/systemd --switched-root --system --deserialize 21
root 2 0.0 0.0 0 0 ? S 06:46 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? I< 06:46 0:00 [rcu_gp]
...
root 3757 0.0 4.1 1446720 82940 ? Ssl 06:48 0:05 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
root 2531 0.0 0.4 711720 9136 ? Sl 09:21 0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id ff6516b31969bfa33b
root 2554 0.0 0.2 6280 4516 ? Ss 09:21 0:00 \_ nginx: master process nginx -g daemon off;
101 2622 0.0 0.0 6736 1776 ? S 09:21 0:00 | \_ nginx: worker process
root 2742 0.0 0.0 1692 1180 pts/0 Ss+ 09:26 0:00 \_ sh
...
PID
2554
,2622
は、コンテナとして起動したnginx
プロセス達.
一方、コンテナ内部で ps aux
をすると、3つのプロセスしか見えません。
ホスト OS やその他コンテナとは PID 空間が隔離されており、 外部プロセスに影響を与えない 訳です。
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 nginx: master process nginx -g daemon off;
32 nginx 0:00 nginx: worker process
39 root 0:00 sh
コンテナ中で稼働するプロセスは基本的に一つで、それは Dockerfile で ENTRYPOINT, CMD に指定したコマンド です。
これは必ず PID 1
になります。
上記のその他プロセス (32
, 39
) は次の理由で生成されました。
PID | Process name | プロセス起動の経緯 |
---|---|---|
1 |
nginx |
Dockerfile で ENTRYPOINT, CMD に指定したコマンド |
32 |
nginx |
nginx (PID 1 ) が起動時に生成した子プロセスです |
39 |
sh |
コンテナに入る際のおまじない docker exec -it nginx sh は、この通りコンテナ内に sh プロセスを起動する為の物でした |
コンテナ仮想化は VM (OS 仮想化) とは異なり、例えば以下の様な Linux OS 起動時の処理は一切行われません。
- 起動スクリプトの実行 (e.g.
/etc/init.d
)- サービスの起動 (e.g.
crond
,sshd
)
ネットワーク名前空間
「ネットワーク名前空間」とは ifconfig
した時に見えるネットワーク構成 (NIC) の事です。
実際にホスト OS 上で ifconfig
してみます。
このマシンに接続されているネットワーク・インターフェース (LAN コネクタや Wi-fi の事) が一覧表示されます。
NIC 名 | アドレス (例) | 説明 |
---|---|---|
docker0 | 172.17.0.1 |
このホストマシン上に Docker コンテナが仮想ネットワークがあります。その入口 (Bridge) です。 コンテナの IP アドレス範囲は 172.17.0.2 〜 172.17.0.255 になります。 |
eth0 | 172.30.1.40 |
インターネット LAN 接続の出入り口です。 AWS VPC 上で IP アドレス 172.30.1.40 が割り振られています。 |
lo | 127.0.0.1 |
ループバックアドレスと言い、所謂 localhost (127.0.0.1 ) アドレスです。 |
veth | -- | ※ 省略 |
[ec2-user@ip-172-30-1-40 ~]$ ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
...
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 9001
inet 172.30.1.40 netmask 255.255.255.0 broadcast 172.30.1.255
...
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
...
veth3f835b9: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
...
veth902e697: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
...
一方、コンテナ内部で ifconfig
をすると、2つのネットワーク・インターフェースしか見えません。
このコンテナはホストマシン上の Docker 仮想ネットワークに所属し、IP アドレス 172.17.0.2
でアクセスが可能です。
NIC 名 | アドレス (例) | 説明 |
---|---|---|
eth0 | 172.17.0.2 |
インターネット LAN 接続の出入り口です。ホストマシンの Docker 仮想ネットワーク上で IP アドレス 172.17.0.2 が割り振られています。 |
lo | 127.0.0.1 |
ループバックアドレスと言い、所謂 localhost (127.0.0.1 ) アドレスです。 |
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:03
inet addr:172.17.0.2 Bcast:172.17.255.255 Mask:255.255.0.0
...
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
...
過去に書いた記事 からネットワーク図を転載します。
マウント名前空間 (Docker Image の事)
「マウント名前空間」とは ls
した時に見えるファイルシステム (ファイルやディレクトリ) の事です。
実際にホスト OS 上で ls /
してみます。
このマシンのルートディレクトリ /
にあるファイル・ディレクトリが一覧されました。
[ec2-user@ip-172-30-1-40 ~]$ ls /
bin boot dev etc home lib lib64 local media mnt opt proc root run sbin srv sys tmp usr var
一方、コンテナ内部で ls /
をすると、全く別のファイル・ディレクトリが一覧されています。
/ # ls /
bin etc mnt run tmp
dev home opt sbin usr
docker-entrypoint.d lib proc srv var
docker-entrypoint.sh media root sys
このディレクトリ構造は皆さんご存知の Docker Image そのもの です。
- docker hub → nginx:1.21-alpine
例えば、nginx:1.21-alpine Image の元となった Dockerfile は以下の様になっています。
- ベース Image に Alpine Linux を用いている。コンテナ中のファイル・ディレクトリは、ほぼここから来てる
- Image のビルド過程で
nginx
をインストールしている - この Image からコンテナを起動すると
nginx -g daemon off;
が実行され最初のプロセス (PID1
) になる
FROM alpine:3.15
RUN set -x \
... \
&& apk add -X "https://nginx.org/packages/mainline/alpine/v$(egrep -o '^[0-9]+\.[0-9]+' /etc/alpine-release)/main" --no-cache $nginxPackages \
...
CMD ["nginx", "-g", "daemon off;"]
この様に、コンテナ中で ls
等をすると隔離されたコンテナ特有のファイルシステム (ファイルやディレクトリ) が見えます。
これらコンテナのファイル・ディレクトリ実体はホストマシン上の下記ディレクトリにあります。
/var/lib/docker/overlay2/
Docker
Linux コンテナ技術と Docker
実は、これまで話してきた事の殆どは名前空間を分離する Linux 標準の機能を使って実現されています。
Docker は Linux のコンテナ技術を API 化し、万人に使いやすくしたソフトウェアです。
Docker クライアントと dockerd
Amazon Linux 2 等で yum install -y docker
して入る Docker のコマンド・サービスは次の2種類に分かれています。
# | サービス | 説明 |
---|---|---|
1 | Docker クライアント | 皆さん馴染み深い docker コマンドは、dockerd に HTTP 通信で指示を出す為の CLI です。 |
2 | dockerd (moby) | Docker サービスの本体で、コンテナの管理等をしている Engine API (HTTP) を持ったデーモンプロセスです。 |
前述の様に docker run -d nginx:1.21-alpine
をすると、dockerd
配下のプロセスとして nginx
は起動します。
[ec2-user@ip-172-30-1-40 ~]$ ps auxf
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
...
root 3566 0.0 2.1 1367168 43612 ? Ssl 06:47 0:04 /usr/bin/containerd
root 3757 0.0 4.1 1446720 82940 ? Ssl 06:48 0:05 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
root 2531 0.0 0.4 711720 9136 ? Sl 09:21 0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id ff6516b31969bfa33b
root 2554 0.0 0.2 6280 4516 ? Ss 09:21 0:00 \_ nginx: master process nginx -g daemon off;
101 2622 0.0 0.0 6736 1776 ? S 09:21 0:00 | \_ nginx: worker process
root 2742 0.0 0.0 1692 1180 pts/0 Ss+ 09:26 0:00 \_ sh
Docker クライアント (docker
コマンド) は dockerd
に対し HTTP 通信による API 呼び出しをしています。
その際に Docker クライアントは、TCP による通信ではなく /var/run/docker.sock ソケットファイルによる UNIX ドメインソケット 通信を行います。
例えば Docker サービスがまだ起動してない時に docker
コマンドを実行すると良く次のエラーに見舞われますが、これは UNIX ドメインソケット 接続が確立できない為に発生しています。
$ docker ps
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
Docker Desktop for Mac / Windows
前述の通り、Docker は Linux のコンテナ技術を使って実現されています。
その為、Mac / Windows 上では dockerd
サービスは動きません。
そこで Docker Desktop は dockerd
サービスを動かす基盤として Linux VM (仮想マシン) を導入しています。
しかし、当然ながら Linux VM (仮想マシン) を用いた場合は、 VM 内に入らなければ docker
コマンドや、立ち上がったコンテナ (プロセス) にアクセスできません。
そこで Docker Desktop では VM に入らなくても大丈夫な様に、下図に示す仕組みが導入されています。
/var/run/docker.sock がマウントされている
Docker Desktop では、ホストマシン上の Docker クライアント (docker
コマンド) が、Linux VM 中の dockerd
と通信出来るように、ホストマシン上の /var/run/docker.sock
が用意されています。
$ ls -alht /var/run/docker.sock
lrwxr-xr-x 1 root daemon 38B Jun 9 15:13 /var/run/docker.sock -> /Users/{user-name}/.rd/docker.sock
その為、ホストマシン上での docker ps
の様なコマンドは、Linux VM 中の dockerd
へ伝達されます。
コンテナのローカルポートが Port-Forward されている
前述の様に、コンテナを起動する際に -p 8080:80
option を指定する事で、ローカルポート :8080
をコンテナが :80
で LISTEN できます。
docker run -d --name nginx -p 8080:80 nginx:1.21-alpine
ただし、この場合は Linux VM (仮想マシン) のローカルポート :8080
を LISTEN している 事になるので、ホストマシンのブラウザで次の URL にアクセスしても、通常は HTTP 接続が確立できない筈です。
↑ 本来こうなる筈だが...?
しかし実際には、Docker Desktop ではホストマシンから直接アクセスできます。
Linux VM (仮想マシン) 側のコンテナのローカルポートが、ホストマシン側に Port-Forwarding されている為です。
Rancher Desktop
残念ながら Docker Desktop は 2022年 2月 に有料化されました。
引っ越し先として 2022年 6月 時点でオススメなのが、Rancher Desktop です。
Rancher Desktop は、ほぼ Docker Desktop と同じ感覚で使え、Docker の操作はホストマシン上で完結できます。
CPU Emulation 対応 (M1 Mac arm64 問題)
Rancher Desktop は Docker Desktop と同様に、CPU Emulation に対応しており arm64
アーキテクチャマシン (※ つまり M1 Mac の事) 上で、x86_64
(Intel CPU) の Docker Image を実行できます!
開発環境を docker-compose で構成しているケースは多いと思いますが、Docker Hub から Image を取得する際は、自動的に利用しているホストマシンの CPU アーキテクチャに合わせた Docker Image が docker pull
されます。
その為 mysql の様な OS/ARCH が linux/amd64
しか無い Docker Image を使っている場合、M1 Mac では対応するイメージが無い為、動作しなくなります。
しかし Rancher Desktop では CPU Emulation に対応している為、次の様に OS/ARCH を linux/amd64
に固定して docker pull
させる様にする事で、これまで通り動作させる事ができます。
version: '3.1'
services:
db:
image: mysql
+ platform: 'linux/amd64'
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
MYSQL_ROOT_PASSWORD: example
とは言え、性能面は残念ながら劣化しますが...