はじめに
普段使っているdockerコマンドの裏では、dockerd・containerd・runcが連携して動いている。今回は公式のインストールスクリプトやパッケージマネージャを使わず、静的バイナリからdockerdだけを取り出してインストールし、docker CLIを使わずcurlでAPIを直接叩いてコンテナを動かす。
Dockerのアーキテクチャ
Dockerはクライアント/サーバー型のアーキテクチャになっている。CLIのdockerコマンドはクライアントの一つで、実体はdockerdが公開するREST APIを叩いているだけである。
アーキテクチャ図に登場するRegistryはイメージを保管・配布するサーバーで、Docker Hubなどがこれにあたる。docker pullやdocker pushでは、dockerdがクライアントに代わってRegistryとの通信を行っている。DockerfileのFROM命令も指定したイメージが手元になければRegistryからpullする。今回はRegistryを使わずに完結させたいので、FROM scratchを使い、ベースイメージなしでコンテナを組み立てる。
dockerコマンドやDockerレジストリのパッケージがなくても、dockerdとAPIで通信できるHTTPクライアント(ここではcurl)があればコンテナは動く。
以下で確認する。
手順
1. dockerdをインストールする
パッケージマネージャや公式インストールスクリプトは使わず、静的バイナリから必要なバイナリのみを取り出す。テスト用途向けの方法で、アップデートも手動管理になるため通常は推奨されていない。環境はDebian安定版を使用。
バイナリを取得し、必要なもの(dockerd, containerd, containerd-shim-runc-v2, runc)を配置する。
$ mkdir -p ~/Work && cd ~/Work
$ curl -LO https://download.docker.com/linux/static/stable/x86_64/docker-XX.X.X.tgz # バージョンは最新のものに
$ tar -xvf docker-XX.X.X.tgz
$ sudo cp docker/{dockerd,containerd,containerd-shim-runc-v2,runc} /usr/bin/
ネットワーク周りは最小構成にするため、設定ファイルで無効化する。
$ sudo mkdir -p /etc/docker
$ sudo tee /etc/docker/daemon.json <<'EOF'
{
"userland-proxy": false,
"iptables": false
}
EOF
$ sudo dockerd
2. Dockerfileを用意する
動作確認用に、1秒ごとに現在時刻を出力するプログラムを用意する。
// hello.c
#include <stdio.h>
#include <unistd.h>
#include <time.h>
int main(void)
{
setbuf(stdout, NULL);
while (1)
{
time_t now = time(NULL);
struct tm *t = localtime(&now);
printf("[%02d:%02d:%02d] hello\n", t->tm_hour, t->tm_min, t->tm_sec);
sleep(1);
}
return (0);
}
FROM scratchで使うため、静的リンクでコンパイルする。
$ cc -static hello.c -o hello
# Dockerfile
FROM scratch
COPY hello /hello
CMD ["/hello"]
ビルドコンテキストとしてtarにまとめる。
$ tar -cvf hello_docker.tar hello Dockerfile
3. curlで動かす
dockerコマンドは使わず、Unixソケット越しのHTTPリクエストのみで進める。
イメージのビルドは/buildエンドポイントに先ほどのtarをそのまま送る。
$ sudo curl --unix-socket /var/run/docker.sock \
-X POST "http://localhost/v1.45/build?t=hello" \
-H "Content-Type: application/x-tar" \
--data-binary @hello_docker.tar
イメージができたらコンテナを作成し、起動する。
# コンテナ作成
$ sudo curl --unix-socket /var/run/docker.sock \
-X POST -H "Content-Type: application/json" \
-d '{"Image": "hello"}' \
"http://localhost/v1.45/containers/create?name=hello_container"
# => {"Id":"xxxxxxxx...","Warnings":null}
# 起動
$ sudo curl --unix-socket /var/run/docker.sock \
-X POST "http://localhost/v1.45/containers/hello_container/start"
ログを見ると、docker runした場合と同様に動作していることが確認できる。
# ログを出力
$ sudo curl --unix-socket /var/run/docker.sock -X GET "http://localhost/v1.43/containers/hello-test/logs?stdout=true&stderr=true" --output -
エンドポイントの詳細はDocker Engine APIのリファレンスにまとまっている。
まとめ
dockerd本体のみをインストールし、docker CLIなしでAPIを直接叩くことで、CLIが内部で行っている「イメージのビルド」「コンテナの作成」「起動」がそれぞれ独立したHTTPリクエストであることを確認した。dockerコマンドはこのAPIに対するラッパーであり、実体はシンプルなREST APIである。
