Docker Engineとは何か
はじめに
Dockerは非常に便利ではありますが、その構造や仕組みまでちゃんと理解している人は少ないかと思われます。基本的にはイメージをビルドしたり、コンテナを起動したりするだけなのでコマンド実行による結果が理解出来ていれば、通常はその仕組みまで理解する必要はありません。ただ、最低限の仕組みでもそれを理解することで、突然エラーが発生した際に「何故このエラーが起きているのか」「どこでこのエラーが起きているのか」が分かるようになり、適切な対処を行うことが出来ます。
何故仕組みを知らなければならないのか
$ docker ps
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
ある日このようなエラーが突然発生したとします。既に仕組みを知っている人やエラーメッセージから冷静に想像付く方であれば良いですが、分からない場合時間内にやらなければならない仕事があるのにこのようなよく分からないエラーがいきなり発生したらどう思うでしょうか。「はやく直して目的の仕事をしたいからエラー内容で検索して一番上あたりに出た検索結果でそれっぽい解決方法があったら取り敢えずそれでなおせれば良い」と思うのではないでしょうか。自分自身、急いでいて仕組みを知らない場合は大体そのような対処になります。でも解決したところで毎回スッキリせず「結局あれはなんだったのだろう」という気持ち悪さが残ります。十中八九、再発したら同じように解決策を調べることになります。そうなると、そろそろ「仕組みは知らずに道具を使えるだけ」という状態も問題な気がしてきますね。
Docker Engineの構成要素は三つ
Docker Engineは三つの構成要素でできています。
- Docker CLI
- REST API
- Dockerデーモン
(出典:Docker-docs-ja、Docker 概要「Docker Engine とは何ですか?」)
docker run
等のDocker CLIを実行すると、UNIXドメインソケット/var/run/docker.sock
を介してREST APIが叩かれ、Dockerデーモンが実際にコンテナを立てたり、イメージビルド等の処理を行います。従って、前述で示したエラーは「Dockerデーモンが止まっている」、または「適切に接続が出来ていない」ことにより発生したことが問題であると分かります。仕組みがわかればこのように見るべき場所が分かります(とは言ったもののこの例の場合、そもそもエラーメッセージを読めば問題がそのまま記述されてますので仕組みを深く知らずともトラブルシュートに慣れている皆さんであれば対処出来るとは思います)
(出典:GitHub、docker/docker.github.io「Docker architecture」)
Docker Engine APIを実際にcurlで叩いてみる
Docker CLIがAPI経由でDockerデーモンとやりとりしているという話をしましたが、それがドメインソケットを経由しているため説明だけではいまいち腑に落ちない方がいらっしゃるかと思います(実際に見るまで腑に落ちなかったと同僚が実際に言ってました)。Docker Engine APIがドメインソケットを経由しているだけで「普通のAPIである」ということを実際に目で見ていただくためにcurlでそのAPIを叩いてみようと思います。curlでは確か7.4以上から--unix-socket <domain_socket_path>
指定でドメインソケットに対しリクエストすることが出来るので、それでDocker Engine APIを叩きDockerデーモンにメッセージを送り、コンテナ情報を取得するというところまでやります。もしも「自分でAPIをみながら叩いてみたい」ということであれば、対応しているdockerバージョンを確認し、docker docs, Develop with Docker Engine APIから対応しているバージョンのリンク先を参照ください。
$ docker version
Client: Docker Engine - Community
Version: 19.03.5
API version: 1.40
...
Server: Docker Engine - Community
Engine:
Version: 19.03.5
API version: 1.40 (minimum version 1.12)
...
(出典:docker docs, Develop with Docker Engine API「API version matrix」)
では、まず存在しないURLを叩いてみます。
$ curl --unix-socket /var/run/docker.sock http://v1.40/path/not/exists
{"message":"page not found"}
今度は、コンテナ情報リストを取得するAPI/containers/json
を叩いてみます。
$ curl --unix-socket /var/run/docker.sock http://v1.40/containers/json
[]
空のレスポンスがDockerデーモンから返ってきました。これは実際に起動しているコンテナがないためです。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
今度はNginxコンテナを立ててからAPIを叩いてみます。
~ $ docker run --rm -d --name nginx nginx
2b9896d63b8140871fc875a5b2c2f27ad1c2de8ca7a4cbcada49c124a3d39532
~ $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2b9896d63b81 nginx "nginx -g 'daemon of…" 5 seconds ago Up 4 seconds 80/tcp nginx
# そのままAPIを叩くとレスポンスが見辛くなるため末尾にjqを入れてます
$ curl --unix-socket /var/run/docker.sock http://v1.40/containers/json | jq
[
{
"Id": "2b9896d63b8140871fc875a5b2c2f27ad1c2de8ca7a4cbcada49c124a3d39532",
"Names": [
"/nginx"
],
"Image": "nginx",
"Command": "nginx -g 'daemon off;'",
"Created": 1620082854,
"Ports": [
{
"PrivatePort": 80,
"Type": "tcp"
}
...
"Mounts": []
}
]
コンテナの情報が返ってきました。このように見ると「Dockerは本当にAPIでやりとりしているんだな」ということがわかりますね。
補足
後は補足程度に説明をしておくと、DockerはLinuxの機能を利用して開発されているため本来であればLinux上でしか動作しません。従って、macOSやWindowsでは大抵VirtualBox等の仮想化ソフトウェアを使い、ゲストOSとしてLinuxを入れてDockerをインストールしなければ使用することができません(自分の場合、単純に考えることが億劫ということもあり脳死で重いDocker Desktop for Macを入れてdockerを使用していましたが、Elixirでコンパイルする時があまりにも重かったのでそろそろ別の手段を考えようかな)。もしもこの辺りをもっと深く知りたいのであればdocker-machineあたりの仕組みを調べると理解が深まるかとは思いますが、docker-machineに関する記事も書いていこうかなと考えておりますので、楽しみにしてください。
最後に
Dockerがどういう構造になっており、どのように動作しているかが理解出来たかと思います。仕組みは知ってしまえば意外と簡単で、次回問題が発生しても大体の問題箇所が分かり、すぐに対応することが出来ます。本番環境で異常が起きたとしても「ああ、仕組みはこのようになっているので実際には本番に影響はないですよ」と説明することが出来たり、仕組みの知らない他の開発者とは一つ差をつけることが出来ます。Dockerだけに限った話ではありませんが、一つ一つの技術を使うだけではなく「仕組みを理解する」というのは大切なことなのです。
Discussion