Link and Motivation Developers' Blog

リンクアンドモチベーションの開発者ブログです

Docker / Docker Desktop / Rancher Desktop って何が違うの?


こんにちは。リンクアンドモチベーション SRE グループの川津と申します!

弊社では、開発や本番環境等、至る所で Docker (コンテナ仮想化) を利用しています。 普段から使っているが、仕組みは分からない!という方も多いのではないかと思い記事にしました!


背景

近年の開発では、各自のローカル PC 上での開発として docker (docker-compose) を使う事が多くなりました。

例えば、最近の Web Application の殆どは以下の3つを使って動きます。

昔は開発者 wikiREADME.md に上記の構築方法が書かれていて、開発者みんなが頑張って自前でローカルマシン上に構築をしていました。

もしくは VMWare イメージを配布する〜、Vagrant 等が試みられていた。

最近では、もうこれ一発で 開発者の環境差分の影響もなく (※ M1 Mac.. 😭) 簡単に構築できますね。素晴らしい時代です。

docker-compose up -d

とは言え、ブラックボックス化され初期設計した人以外は 「手順通りにやったら動いたけど、仕組みが全然わからない!」 になっている事が多い様に思えます。

目的

と言うことで、今回の記事では以下の3つを説明します。

  1. そもそも Docker って何?
  2. Docker Desktop for Mac / Windows って、素の Docker とどう違うの?
  3. 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.2172.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 そのもの です。

例えば、nginx:1.21-alpine Image の元となった Dockerfile は以下の様になっています。

  • ベース Image に Alpine Linux を用いている。コンテナ中のファイル・ディレクトリは、ほぼここから来てる
  • Image のビルド過程で nginx をインストールしている
  • この Image からコンテナを起動すると nginx -g daemon off; が実行され最初のプロセス (PID 1) になる
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;"]

https://github.com/nginxinc/docker-nginx/blob/d039609e3a537df4e15a454fdb5a004d519e9a11/mainline/alpine/Dockerfile

この様に、コンテナ中で ls 等をすると隔離されたコンテナ特有のファイルシステム (ファイルやディレクトリ) が見えます。

これらコンテナのファイル・ディレクトリ実体はホストマシン上の下記ディレクトリにあります。

/var/lib/docker/overlay2/

Docker

Linux コンテナ技術と Docker

実は、これまで話してきた事の殆どは名前空間を分離する Linux 標準の機能を使って実現されています。 DockerLinux のコンテナ技術を API 化し、万人に使いやすくしたソフトウェアです。

Docker クライアントと dockerd

Amazon Linux 2 等で yum install -y docker して入る Docker のコマンド・サービスは次の2種類に分かれています。

# サービス 説明
1 Docker クライアント 皆さん馴染み深い docker コマンドは、dockerdHTTP 通信で指示を出す為の 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

前述の通り、DockerLinux のコンテナ技術を使って実現されています。 その為、Mac / Windows 上では dockerd サービスは動きません。

そこで Docker Desktopdockerd サービスを動かす基盤として 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 Desktop ではなく、後述する Rancher Desktop のケースです🙏

その為、ホストマシン上での 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年 7月 時点でオススメなのが、Rancher Desktop です。

Rancher Desktop は、ほぼ Docker Desktop と同じ機能を有しており、Docker の操作はホストマシン上で完結できます。

CPU Emulation 対応 (M1 Mac arm64 問題)

Rancher DesktopDocker Desktop と同様に、CPU Emulation に対応しており arm64 アーキテクチャマシン (※ つまり M1 Mac の事) 上で、x86_64 (Intel CPU) の Docker Image を実行できます!

開発環境を docker-compose で構成しているケースは多いと思いますが、Docker Hub から Image を取得する際は、自動的に利用しているホストマシンの CPU アーキテクチャに合わせた Docker Image が docker pull されます。

みなさんが今まで使っていた Intel Mac (x64) では linux/amd64 が自動選択されています。

その為、 mysql の様な OS/ARCH が linux/amd64 しか無い Docker Image を使っている場合、M1 Mac では対応するイメージが無い為、動作しなくなります。

linux/arm64 ※ があるイメージは、M1 Mac (arm64) でも普通に動きます。 ※ amd, arm で文字似ているので注意

しかし 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