実習で学ぶDocker入門 | 6. Docker Engineの構造を体験しよう
今回はDocker Engineの構造について、クライアントサーバーモデルの観点から説明する。Docker Engine APIを呼び出したり、リモート接続したりしながら、Docker Engineの構造を体験的に理解していこう。
Docker Engine概要
Docker EngineはDockerの中核ソフトウェアである。これまで何度も登場したdocker runやdocker buildなどのコマンドと、それらのコマンドによって実行されるイメージビルド、コンテナ実行などのさまざまな作業をまとめてDocker Engineと呼ぶ。
単にDockerと言う場合、このDocker Engineを指すこともあれば、Docker Hubのような周辺サービスやツールを含むDocker Platformを指すこともある。Docker Platformのような広い意味でのDockerと区別するときに、Docker Engineという言葉が使われる。
公式ドキュメントによると、Docker Engineは主に次の3つの要素で構成されている。
- Docker CLI
- Docker Engine API
- Dockerデーモン
Docker CLIは、docker runやdocker buildなどのDockerコマンドを実行するコマンドラインツールである。Docker CLIは入力されたDockerコマンドに応じてDocker Engine APIを呼び出す。DockerデーモンはLinuxデーモンプロセスとしてDocker Engine APIが呼び出されるのを待ち、呼び出されたDocker Engine APIに応じてイメージのビルドやコンテナの起動などを実行する。
このようにDocker Engineはクライアントサーバーモデルのアプリケーションと言える。クライアントであるDocker CLIが、Docker Engine APIを通じてサーバーであるDockerデーモンに処理を要求し、応答を受け取る。

Unixソケット通信とTLS通信
上の図のように、Docker CLIがDockerホスト上にあるか外部にあるかによって、Dockerデーモンとの通信方式は異なる。
Docker CLIがDockerホスト上にある場合は、Unixドメインソケット(以下、Unixソケット)を利用してDockerデーモンと通信する。Unixソケットは同じシステム上のプロセス同士が通信できる仕組みである。前回はdocker-machine sshコマンドでDockerホストへログインした後、Dockerホスト上のDocker CLIコマンドを実行していたため、Unixソケット通信を行っていた。
Docker CLIがDockerホストの外部にある場合は、TCPソケットを使用してDockerデーモンと通信する。TCPソケットの場合、HTTPをそのまま利用するのではなく、何らかのセキュリティ対策を行うことが推奨されている。その1つがTLSである。Docker EngineはTLSを使用してHTTPを暗号化されたHTTPS通信にすると同時に、クライアントとサーバーを信頼できるものに限定できる。
このようなTLS通信を行うには、証明書を作成するなどさまざまな作業が必要だが、Docker Machineを使用してDockerホストを作成した場合、その作業は自動的に行われる。ここでもDocker Machineの自動設定を利用してTLS通信を行う。なお、自動設定を使う理由はTLS通信を簡単に体験できるためであり、高い安全性が保証されるためではない。
事前準備
以上で説明した内容について、ここでは実際にDocker Engineを使ってさらに深く理解してみよう。概要は次のとおりである。
- 事前準備
- curlによるAPI呼び出し(Unixソケット通信)
- API呼び出しによる「Hello World」(Unixソケット通信)
- Docker CLIのインストール
- Docker CLIのリモート接続(TLS通信)
- 補足: プロキシと証明書の再作成
まず前回と同じく、事前準備としてWindows 10、VirtualBox、PowerShellを用意する。
また次のようにDocker Machineを使用してDockerホストを作成する。
PS > docker-machine create myhost
このcreateコマンドを実行すると、Dockerホスト内のDockerバージョンは最新になる。しかし筆者が利用したDockerバージョンはv17.09.0-ceであるため、Dockerバージョンが異なると次に紹介する内容を実行できない可能性がある。この場合、次のようにcreateコマンドの--virtualbox-boot2docker-urlオプションにバージョンを指定してDockerホストを作成する。
PS> $url = 'https://github.com/boot2docker/boot2docker/releases/download/v17.09.0-ce/boot2docker.iso'
PS> docker-machine create --virtualbox-boot2docker-url $url myhost
Dockerホストが作成されたらログインする。
PS> docker-machine ssh myhost
ログインしたら、Docker CLIを使って「Hello World」が動作することを確認する。
docker@myhost:~$ docker run hello-world
(中略)
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
ここまでは前回と同じである。
改めてログを読むと、Docker CLI(Docker client)とDockerデーモン(Docker daemon)の処理について順に説明されている。このログには書かれていないが、Docker CLIはDockerデーモンとやり取りするDocker Engine APIを呼び出している。Docker Engine APIはDocker CLIがなくても呼び出せる。そこで次にcurlを使用してDocker Engine APIを呼び出してみよう。
curlによるAPI呼び出し(Unixソケット通信)
curlは、HTTPなどさまざまな通信を行える広く知られたツールである。curlは最初からDockerホストにインストールされているが、念のためcurlのバージョンを確認しよう。
docker@myhost:~$ curl --version
curl 7.49.1 (x86_64-pc-linux-gnu) libcurl/7.49.1 OpenSSL/1.0.2h zlib/1.2.8
Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS Largefile NTLM NTLM_WB SSL libz TLS-SRP UnixSockets
curlのバージョンは7.40.0以上であればよい。curl 7.40.0からUnixソケットを使用できる。次のようにUnixソケットを使用してDocker Engine APIを呼び出す。
docker@myhost:~$ curl --unix-socket /var/run/docker.sock http:/version
{
"Version": "17.09.0-ce",
"ApiVersion": "1.32",
"MinAPIVersion": "1.12",
"GitCommit": "afdb6d4",
"GoVersion": "go1.8.3",
"Os": "linux",
"Arch": "amd64",
"KernelVersion": "4.4.89-boot2docker",
"BuildTime": "2017-09-26T22:45:38.000000000+00:00"
}
--unix-socketオプションに指定されている/var/run/docker.sockがDockerソケットのパスである。http:/versionはDockerのバージョン情報を取得するAPIである。バージョン情報はJSON形式で出力されている。"ApiVersion": "1.32"がAPIのバージョンである。バージョン1.32のドキュメントには、さまざまなAPIがサンプルとともに説明されている。その内容を参考にしながら、次は「Hello World」を出力してみよう。
API呼び出しによる「Hello World」(Unixソケット通信)
まずコンテナを作成する。
curl --unix-socket /var/run/docker.sock \
-H "Content-Type: application/json" \
-d '{"Image": "hello-world", "Tty": true}' \
-X POST http:/containers/create
{"Id":"469a115ce858fc7ae41639dc3ec7bf354e88015cc98b2540a4c8a7598e01445e","Warnings":null}
イメージ名は"hello-world"であり、-d '{"Image": "hello-world", ...のようにJSON形式で指定されている。-X POSTはHTTPのPOSTメソッドを使用するという意味である。http:/containers/createはコンテナを作成するAPIである。
出力結果の{"Id":"469a11...は作成されたコンテナのIDである。このIDを利用してコンテナを起動する。
docker@myhost:~$ curl --unix-socket /var/run/docker.sock \
-X POST http:/containers/469a11/start
http:/containers/469a11/startがコンテナを起動するAPIである。起動するコンテナは、このようにコンテナIDの先頭文字(469a11)で指定できる。
作成したhello-worldイメージのコンテナは、起動時にログを出力してすぐ停止するコンテナである。正常に停止したか確認しよう。
docker@myhost:~$ curl --unix-socket /var/run/docker.sock -X POST http:/containers/469a11/wait
{"StatusCode":0}
http:/containers/469a11/waitは、コンテナが停止するまで待った後、終了コードをStatusCodeとして返すAPIである。StatusCodeが0なので、コンテナの処理は成功した。
コンテナのログを確認する。
docker@myhost:~$ curl --unix-socket /var/run/docker.sock http:/containers/469a11/logs?stdout=1
Hello from Docker!
This message shows that your installation appears to be working correctly.
(以下省略)
docker runコマンドで「Hello World」を実行したときと同じログが出力されている。呼び出すAPIは少し異なるが、docker runコマンドもこのようにDocker Engine APIを通じてDockerデーモンとやり取りしている。
Docker CLIのインストール
次に、Docker CLIがリモート接続(TLS通信)を行うため、WindowsにDocker CLIをインストールする。インストールはDocker Machineではなく、バイナリファイルをダウンロードしてPath設定を行うだけでよい。
Docker CLIのWindows 64ビット版バイナリファイルはこちらからダウンロードできる。もしリンクが開けない場合は公式ドキュメントを参照してほしい。
Docker CLIのバージョンはDockerホストのDockerバージョンと合わせる。バージョンが異なると正常に動作しない可能性が高い。バージョンがv17.09.0-ceの場合、ファイル名はdocker-17.09.0-ce.zipである。
zipファイルをダウンロードした後、解凍し、中に入っているdocker.exeファイルを環境変数Pathに追加する。PowerShellでのPath設定方法については前回を参考にしてほしい。
Pathを設定した後、次のコマンドを実行する。
PS> docker version
Client:
Version: 17.09.0-ce
API version: 1.32
Go version: go1.8.3
Git commit: afdb6d4
Built: Tue Sep 26 22:40:09 2017
OS/Arch: windows/amd64
error during connect: Get https://%2F%2F.%2Fpipe%2Fdocker_engine/v1.32/version:
open //./pipe/docker_engine: The system cannot find the file specified.
In the default daemon configuration on Windows, the docker client must be run elevated to connect.
This error may also indicate that the docker daemon is not running.
出力内容の上部にClient:としてDocker CLIのバージョン情報が出力されていれば、インストールは完了である。
下部にはエラーメッセージが出力されている。次にリモート接続を設定すると、下部にはDockerホスト側の情報が出力されるようになる。
Docker CLIのリモート接続(TLS通信)
リモート接続するには、対象Dockerホストの情報を設定する必要がある。ここでは設定方法を2つ紹介する。
1. dockerオプションを設定する方法
PowerShellで次のDocker Machineコマンドを実行してみよう。
PS> docker-machine config myhost
--tlsverify
--tlscacert="C:\\Users\\Taro\\.docker\\machine\\machines\\myhost\\ca.pem"
--tlscert="C:\\Users\\Taro\\.docker\\machine\\machines\\myhost\\cert.pem"
--tlskey="C:\\Users\\Taro\\.docker\\machine\\machines\\myhost\\key.pem"
-H=tcp://192.168.99.100:2376
このconfigコマンドは、リモート接続のためのdockerオプションを出力するコマンドである。--tlsで始まるオプションには、Docker Machineが自動生成した証明書のパスなどが指定されている。-H=tcp://192.168.99.100:2376は、TLS通信を許可するmyhostのIPアドレスとポート番号である。
このオプションをコピーし、次のようにdocker versionコマンドへ追加して実行する。各行末のバッククォートは、PowerShellで長いコマンドを複数行に分けて書きたいときに使用する。
PS> docker --tlsverify `
--tlscacert="C:\\Users\\kimkc\\.docker\\machine\\machines\\myhost\\ca.pem" `
--tlscert="C:\\Users\\kimkc\\.docker\\machine\\machines\\myhost\\cert.pem" `
--tlskey="C:\\Users\\kimkc\\.docker\\machine\\machines\\myhost\\key.pem" `
-H=tcp://192.168.99.100:2376 version
Client:
Version: 17.09.0-ce
API version: 1.32
Go version: go1.8.3
Git commit: afdb6d4
Built: Tue Sep 26 22:40:09 2017
OS/Arch: windows/amd64
Server:
Version: 17.09.0-ce
API version: 1.32 (minimum version 1.12)
Go version: go1.8.3
Git commit: afdb6d4
Built: Tue Sep 26 22:45:38 2017
OS/Arch: linux/amd64
Experimental: false
リモート接続に成功し、Server:情報が出力された。
しかしdockerコマンドを実行するたびに、毎回同じ長いオプションを付けるのはできれば避けたい。そこで次のように環境変数を設定する。
2. 環境変数を設定する方法
リモート接続に必要な環境変数は、Docker MachineとPowerShellを使用して次のように1行で設定できる。
PS> docker-machine env myhost | Invoke-Expression
このコマンドを実行した後、同じPowerShellでdocker versionおよびdocker run hello-worldを実行してみよう。バージョン情報が出力され、「Hello World」を実行できるはずである。
では、このコマンドはいったい何をしているのだろうか。分割して見てみよう。
まず次のようにパイプライン記号|の左側のコマンドを実行する。
PS> docker-machine env myhost
$Env:DOCKER_TLS_VERIFY = "1"
$Env:DOCKER_HOST = "tcp://192.168.99.100:2376"
$Env:DOCKER_CERT_PATH = "C:\Users\kimkc\.docker\machine\machines\myhost"
$Env:DOCKER_MACHINE_NAME = "myhost"
$Env:COMPOSE_CONVERT_WINDOWS_PATHS = "true"
# Run this command to configure your shell:
# & "C:\Users\kimkc\Scripts\docker-machine.exe" env myhost | Invoke-Expression
$Env:文字列が数行出力されている。$Env:は前述のとおりPowerShellの環境変数である。変数名と設定値から分かるように、これらはTLS通信のための環境変数である。このようにDocker Machineのenvコマンドは、リモート接続したいDockerホストに関する環境変数を文字列として出力してくれる。
ただし、envコマンドは単に文字列を出力しているだけである。出力された文字列をPowerShellコマンドとして実行しなければ、環境変数は設定されない。
文字列を実行してくれるのがPowerShellのInvoke-Expressionコマンド(省略形iex)である。つまり、docker-machine env myhost | Invoke-Expressionは、Docker Machineのenvコマンドで出力された文字列をPowerShellのInvoke-Expressionコマンドで実行し、リモート接続に必要な環境変数を設定していたのである。
このようにDocker CLIは環境変数によって接続先を切り替えられる。今回作成したmyhost以外にも複数のDockerホストを作成して試してみるとよいだろう。
以上でDocker CLIがリモート接続(TLS通信)できるようになった。以下では、さらにリモート接続で困ったときの対処法を2つ紹介する。
補足1: プロキシ
envコマンドを使用して環境変数を設定しても、次のようにエラーが発生することがある。
PS> docker-machine env myhost | Invoke-Expression
PS> docker version
(中略)
error during connect: Get https://192.168.99.100:2376/v1.32/version: Service Unavailable
原因の1つとして、会社や学校のHTTPプロキシによりDockerホストのIPアドレス(192.168.99.100)へ到達できないことがある。この場合、次のように--no-proxyオプションを追加しよう。
PS> docker-machine env --no-proxy myhost
$Env:DOCKER_TLS_VERIFY = "1"
$Env:DOCKER_HOST = "tcp://192.168.99.100:2376"
$Env:DOCKER_CERT_PATH = "C:\Users\kimkc\.docker\machine\machines\myhost"
$Env:DOCKER_MACHINE_NAME = "myhost"
$Env:COMPOSE_CONVERT_WINDOWS_PATHS = "true"
$Env:NO_PROXY = "192.168.99.100"
# Run this command to configure your shell:
# & "C:\Users\kimkc\Scripts\docker-machine.exe" env --no-proxy myhost | Invoke-Expression
--no-proxyオプションを追加することで、下から3行目に$Env:NO_PROXY = "192.168.99.100"が追加出力された。$Env:NO_PROXYはプロキシを経由しないIPアドレスを設定する環境変数である。このように$Env:NO_PROXYにmyhostのIPアドレスを指定すると、Docker CLIはプロキシを経由せずにmyhostへリモート接続を試みる。
補足2: 証明書を再作成する
Docker Machineを継続して使用していると、まれに次のようなエラーメッセージが出力されることがある。
PS> docker-machine env myhost | Invoke-Expression
Error checking TLS connection: Error checking and/or regenerating the certs:
There was an error validating certificates for host "192.168.99.101:2376":
x509: certificate is valid for 192.168.99.100, not 192.168.99.101
You can attempt to regenerate them using 'docker-machine regenerate-certs [name]'.
Be advised that this will trigger a Docker daemon restart which might stop running containers.
原因の1つとして、証明書を生成したとき、つまりDockerホストを作成したときのIPアドレスと、今回Dockerホストを起動したときのIPアドレスが異なることが考えられる。Docker MachineはDockerホスト起動時にIPアドレスを動的に割り当てるため、Dockerホストを複数作成しているとこのようなエラーがよく発生する。
この場合、エラーメッセージに表示されているとおり、regenerate-certsコマンドで証明書を作り直してみよう。
PS> docker-machine regenerate-certs myhost
Regenerate TLS machine certs? Warning: this is irreversible. (y/n): y
Regenerating TLS certificates
Waiting for SSH to be available...
Detecting the provisioner...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
これで再びリモート接続できるようになるはずである。
結論: Docker Engineと「assemble」
今回はDocker Engineの構造について説明した。API呼び出しとリモート接続を通じて、クライアントサーバーモデルとも言えるDocker Engineの構造を体験できたのではないかと思う。
最後に、最近のDocker社の動向とDocker Engineの関係について簡単に紹介し、まとめたい。
Docker Engineという名称が明示的に使われたのは、2014年6月のDockerConが初めてと言ってよいだろう。このDockerConでは、libcontainerやlibswarmなど、現在開発されている構成要素の源流となる要素も発表されている。明らかに、それまで「Docker」と一括りに呼んでいたものを、この頃から少しずつ構成要素として明確に区分しようとしていたようである。
今回紹介したDocker Engineの3つの構成要素も、それぞれDockerの進化とともに構成要素として形を明確にしてきた。そのうちDockerデーモンは、さらにいくつかの構成要素に分かれている。たとえばcontainerdは、Dockerデーモンのコンテナに関する処理を構成要素として切り出したものである。現在containerdは、Cloud Native Computing Foundation(CNCF)という組織のプロジェクトとして開発が進められている。
このような構成要素化の流れは、2017年4月のDockerCon 2017で発表されたMobyプロジェクトによって新しい段階に入ったと言える。
Mobyは、いわば自分なりのDockerを作るためのフレームワークである。Mobyプロジェクトのホームページには次のように書かれている。
An open framework to assemble specialized container systems without reinventing the wheel.
この文で注目すべきなのはassembleという単語である。assembleは「集める」「組み立てる」という意味である。Mobyは、さまざまな構成要素を集めてDockerのようなコンテナシステムを組み立てるオープンフレームワークなのである。
Docker社は今後、さまざまなコンテナシステムをassembleする手段としてMobyを提供し、Mobyを活用して優れた形でassembleされたコンテナシステムの1つとしてDockerを提供していこうとしている。Dockerを中心とするコンテナ技術は変化が激しく、続々と新しい製品が登場するが、それがコンポーネントなのか、コンポーネントをassembleした成果なのかという観点で眺めると理解しやすいのではないかと思う。