Docker
Docker2Day 11

Docker Engineをソースコードからビルドする

More than 1 year has passed since last update.


はじめに

この記事は Docker2 Advent Calendar 2016 の11日目の記事です。

ちょっとした気まぐれでDocker Engineをソースコードからビルドしてみようかと思いたち、試しにやってみたのでやり方を共有します。

ビルド手順自体は開発者向けに公式ドキュメントの Open Source at Docker というページに丁寧に説明されているので、この辺を参考にしつつ進めていきますが、公式の説明はgitの説明とかそのレベルの説明いる?ってぐらいちょっと丁寧すぎて逆に冗長なので、ここではDocker固有の要点だけ抑えつつ、気になったポイントを補足しつつ説明していきます。

以下の手順は執筆時点の最新のmasterブランチ(1.14.0-dev@001cbc4)で試してますが、最新の情報については、適宜公式ドキュメントを確認してください。

ちなみに手元の環境はMac OSX 10.11.6(EI Capitan)です。


必要なソフトウェアのインストール

以下のソフトウェアが必要です。


  • git

  • make

  • docker

gitとmakeはこれを読んでる人はたぶん何かしら入ってるでしょう。

またDockerの開発自体にDockerが使われているので、ソースコードからビルドするのにもdockerコマンドが必要です。Macでdocker動かすのにDockerToolBoxなども必要ですが、これもまぁ、Dockerを使ってれば既に入ってると思うので説明略。ちなみに手元のDockerには1.12.3を使いました。もうすぐ出そうな1.13.0がまだ出ないですが、とりあえずstableな最新版を使っておくとよいでしょう。


ソースコードのビルド

ソースコードをgit cloneしてきます。

$ git clone https://github.com/docker/docker

$ cd docker


不要なコンテナやイメージを削除

Dockerの開発環境自体にDockerを使うので、事前に不要なコンテナやイメージを削除して、できるだけクリーンな環境で作業することが推奨されています。必須ではないけど、とりあえず不要なものは消しとくのがGoodプラクティスだそうです。

以下のコマンドですべてのコンテナとどこからも参照されていないイメージを全消しできます。

[docker@master]$ docker rm $(docker ps -a -q)

[docker@master]$ docker rmi -f $(docker images -q -a -f dangling=true)

と、まぁそんなこと言われても、普段のDocker利用してると消せないコンテナも色々あったり、dinghyなどのツールを使ってNFSマウントしたりして余計なレイヤが挟まってたりするので、私の場合は普段使ってるdinghyは止めて、 docker-machine create で適当な作業用のVMを新規に作りました。この辺は各自よしなに読み替えていただいたらよいかと思いますが、普段利用の環境を壊さないように別にVMを立てるのがまぁ無難じゃなかろうかと思います。

普段使ってるdinghyを止めておく。

[docker@master]$ dinghy stop

Stopping the FsEvents daemon
Stopping the dinghy VM...
Stopping "dinghy"...
Machine "dinghy" was stopped.
Stopping NFS daemon, this will require sudo

docker-machineで作業用に新しくVMを立てる

[docker@master]$ docker-machine create --driver virtualbox dev                                        Running pre-create checks...

Creating machine...
(dev) Copying /Users/m-morita/.docker/machine/cache/boot2docker.iso to /Users/m-morita/.docker/machine/machines/dev/boot2docker.iso...
(dev) Creating VirtualBox VM...
(dev) Creating SSH key...
(dev) Starting the VM...
(dev) Check network to re-create if needed...
(dev) Waiting for an IP...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with boot2docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env dev

環境変数の読み直しして新しく立てたVMにdockerコマンドの向き先を変える。

[docker@master]$ docker-machine env dev

export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.102:2376"
export DOCKER_CERT_PATH="/Users/m-morita/.docker/machine/machines/dev"
export DOCKER_MACHINE_NAME="dev"
# Run this command to configure your shell:
# eval $(docker-machine env dev)

[docker@master]$ eval $(docker-machine env dev)

[docker@master]$ docker version
Client:
Version: 1.12.3
API version: 1.24
Go version: go1.7.3
Git commit: 6b644ec
Built: Mon Nov 14 15:13:08 UTC 2016
OS/Arch: darwin/amd64

Server:
Version: 1.12.3
API version: 1.24
Go version: go1.6.3
Git commit: 6b644ec
Built: Wed Oct 26 23:26:11 2016
OS/Arch: linux/amd64


開発用のDockerコンテナの起動

以下のコマンドでDocker開発用のDockerコンテナを起動します。

※この例ではmasterブランチで直接作業しちゃってますが、後述するようにビルドされてできるイメージのタグにブランチ名も入るので、あとでコードいじるつもりなら適当な作業ブランチを切ってからの方がよかった気がします。

[docker@master]$ make BIND_DIR=. shell

PCのスペックやネット環境にもよりますが、私の環境だと初回は20分弱かかりました。コーヒーでも飲んで待ちましょう。

ビルドが終わると以下のようにDocker開発用のコンテナが起動してシェルでログインした状態になります。

root@68cb65a60ca0:/go/src/github.com/docker/docker#

このコンテナはなんでしょうか?よく見るとさきほどのビルドの標準出力の最後に、dockerコマンドが見えます。

実際には1行ですが、わかりにくいので、改行するとこんなかんじ。

docker run --rm -i --privileged 

-e BUILD_APT_MIRROR -e BUILDFLAGS -e KEEPBUNDLE -e DOCKER_BUILD_ARGS -e DOCKER_BUILD_GOGC
-e DOCKER_BUILD_PKGS -e DOCKER_DEBUG -e DOCKER_EXPERIMENTAL -e DOCKER_GITCOMMIT
-e DOCKER_GRAPHDRIVER -e DOCKER_INCREMENTAL_BINARY -e DOCKER_PORT -e DOCKER_REMAP_ROOT
-e DOCKER_STORAGE_OPTS -e DOCKER_USERLANDPROXY -e TESTDIRS -e TESTFLAGS -e TIMEOUT
-e HTTP_PROXY -e HTTPS_PROXY -e NO_PROXY -e http_proxy -e https_proxy -e no_proxy
-v "/Users/m-morita/src/github.com/docker/docker/.:/go/src/github.com/docker/docker/."
-v "dockerdev-go-pkg-cache-gopath:/go/pkg"
-v "dockerdev-go-pkg-cache-goroot-linux_amd64_netgo:/usr/local/go/pkg/linux_amd64_netgo"
-t "docker-dev:master" bash

これはどうやらdindを使った Docker in Docker環境のようです。

この状態でホスト側で docker ps して見るとこんなかんじ。

[docker@master]$ docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
68cb65a60ca0 docker-dev:master "hack/dind bash" 2 minutes ago Up 2 minutes jolly_montalcini

また、カレントディレクトリはホスト側のgitレポジトリがマウントされて共有された状態になってます。

root@68cb65a60ca0:/go/src/github.com/docker/docker# ls

AUTHORS Dockerfile.windows builder dockerversion oci runconfig
CHANGELOG.md LICENSE bundles docs opts utils
CONTRIBUTING.md MAINTAINERS cli experimental pkg vendor
Dockerfile Makefile cliconfig hack plugin vendor.conf
Dockerfile.aarch64 NOTICE client image poule.yml volume
Dockerfile.armhf README.md cmd integration-cli profiles
Dockerfile.ppc64le ROADMAP.md container layer project
Dockerfile.s390x VENDORING.md contrib libcontainerd reference
Dockerfile.simple VERSION daemon man registry
Dockerfile.solaris api distribution migrate restartmanager


ソースコードのビルド

開発用のコンテナ内でソースコードをビルドしてバイナリを生成します。

root@68cb65a60ca0:/go/src/github.com/docker/docker# hack/make.sh binary

---> Making bundle: binary (in bundles/1.14.0-dev/binary)
Building: bundles/1.14.0-dev/binary-client/docker-1.14.0-dev
Created binary: bundles/1.14.0-dev/binary-client/docker-1.14.0-dev
Building: bundles/1.14.0-dev/binary-daemon/dockerd-1.14.0-dev
Created binary: bundles/1.14.0-dev/binary-daemon/dockerd-1.14.0-dev
Copying nested executables into bundles/1.14.0-dev/binary-daemon

こちらは数分で終わりました。ログに生成されたバイナリのファイルパスが出てますが、シムリンクなども準備してくれてるので、以下のコマンドでまとめて /usr/bin/ 配下にコピーします。

root@68cb65a60ca0:/go/src/github.com/docker/docker# cp bundles/1.14.0-dev/binary-client/docker* /usr/bin/

root@68cb65a60ca0:/go/src/github.com/docker/docker# cp bundles/1.14.0-dev/binary-daemon/docker* /usr/bin/


Dockerの起動

以下のコマンドでdockerデーモンをデバッグモード (-D) でバックグラウンドで起動します。

root@68cb65a60ca0:/go/src/github.com/docker/docker# docker daemon -D&

[1] 3141
root@68cb65a60ca0:/go/src/github.com/docker/docker# Command "daemon" is deprecated, and will be removed in Docker 1.16. Please run `dockerd` directly.
DEBU[0000] docker group found. gid: 999
DEBU[0000] Listener created for HTTP on unix (/var/run/docker.sock)
INFO[0000] libcontainerd: new containerd process, pid: 3148
DEBU[0000] containerd: read past events count=0
DEBU[0000] containerd: supervisor running cpus=1 memory=995 runtime=docker-runc runtimeArgs=[] stateDir=/var/run/docker/libcontainerd/containerd

上記の手順通りやると dockerd を直接使えって警告が見えるので、 たぶん dockerd -D & のが正しそう。

ちゃんとビルドしたものが起動できているか、docker versionを見てみます。

root@68cb65a60ca0:/go/src/github.com/docker/docker# docker version

DEBU[0254] Calling GET /_ping
DEBU[0254] Calling GET /v1.26/version
Client:
Version: 1.14.0-dev
API version: 1.26
Go version: go1.7.4
Git commit: 001cbc4
Built: Sat Dec 10 07:52:45 2016
OS/Arch: linux/amd64

Server:
Version: 1.14.0-dev
API version: 1.26 (minimum version 1.12)
Go version: go1.7.4
Git commit: 001cbc4
Built: Sat Dec 10 07:52:45 2016
OS/Arch: linux/amd64
Experimental: false

たしかにビルドしたバイナリに埋まってるGit commitとローカルのmasterのHEADが一致してるのでよさそうです。

[docker@master]$ git log --oneline | head -1

001cbc451 Merge pull request #29210 from vdemeester/integration-cli-extract-daemon

docker runしてみます。

root@68cb65a60ca0:/go/src/github.com/docker/docker# docker run hello-world

DEBU[0577] Calling GET /_ping
DEBU[0577] Calling POST /v1.26/containers/create
DEBU[0577] form data: {"AttachStderr":true,"AttachStdin":false,"AttachStdout":true,"Cmd":null,"Domainname":"","Entrypoint":null,"Env":[],"HostConfig":{"AutoRemove":false,"Binds":null,"BlkioDeviceReadBps":null,"BlkioDeviceReadIOps":null,"BlkioDeviceWriteBps":null,"BlkioDeviceWriteIOps":null,"BlkioWeight":0,"BlkioWeightDevice":null,"CapAdd":null,"CapDrop":null,"Cgroup":"","CgroupParent":"","ConsoleSize":[0,0],"ContainerIDFile":"","CpuCount":0,"CpuPercent":0,"CpuPeriod":0,"CpuQuota":0,"CpuRealtimePeriod":0,"CpuRealtimeRuntime":0,"CpuShares":0,"CpusetCpus":"","CpusetMems":"","Devices":[],"DiskQuota":0,"Dns":[],"DnsOptions":[],"DnsSearch":[],"ExtraHosts":null,"GroupAdd":null,"IOMaximumBandwidth":0,"IOMaximumIOps":0,"IpcMode":"","Isolation":"","KernelMemory":0,"Links":null,"LogConfig":{"Config":{},"Type":""},"Memory":0,"MemoryReservation":0,"MemorySwap":0,"MemorySwappiness":-1,"NanoCpus":0,"NetworkMode":"default","OomKillDisable":false,"OomScoreAdj":0,"PidMode":"","PidsLimit":0,"PortBindings":{},"Privileged":false,"PublishAllPorts":false,"ReadonlyRootfs":false,"RestartPolicy":{"MaximumRetryCount":0,"Name":"no"},"SecurityOpt":null,"ShmSize":0,"UTSMode":"","Ulimits":null,"UsernsMode":"","VolumeDriver":"","VolumesFrom":null},"Hostname":"","Image":"hello-world","Labels":{},"NetworkingConfig":{"EndpointsConfig":{}},"OnBuild":null,"OpenStdin":false,"StdinOnce":false,"Tty":false,"User":"","Volumes":{},"WorkingDir":""}
ERRO[0577] Handler for POST /v1.26/containers/create returned error: No such image: hello-world:latest
Unable to find image 'hello-world:latest' locally
DEBU[0577] Calling GET /v1.26/info
DEBU[0577] Calling POST /v1.26/images/create?fromImage=hello-world&tag=latest
DEBU[0577] Trying to pull hello-world from https://registry-1.docker.io v2
DEBU[0580] Pulling ref from V2 registry: hello-world:latest
latest: Pulling from library/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.
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.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker Hub account:
https://hub.docker.com

For more examples and ideas, visit:
https://docs.docker.com/engine/userguide/

(略)

DEBU[0581] libcontainerd: received containerd event: &types.Event{Type:"exit", Id:"47de86018c461e146470ab3720a3b10cfca83d786c51fd105daac0c6dae8db54", Status:0x0, Pid:"init", Timestamp:(*timestamp.Timestamp)(0xc42128c790)}
DEBU[0581] attach: stdout: end
DEBU[0581] attach: stderr: end
DEBU[0581] Revoking external connectivity on endpoint modest_banach (f89725b1dc19c6a5eb7cec02855e9e2ef45283ccf07e705493e51377178f966e)
root@68cb65a60ca0:/go/src/github.com/docker/docker# DEBU[0581] Client context cancelled, stop sending events
DEBU[0581] Releasing addresses for endpoint modest_banach's interface on network bridge
DEBU[0581] ReleaseAddress(LocalDefault/172.18.0.0/16, 172.18.0.2)

ちゃんと動いたっぽい。

root@68cb65a60ca0:/go/src/github.com/docker/docker# docker ps -a

DEBU[0677] Calling GET /_ping
DEBU[0677] Calling GET /v1.26/containers/json?all=1
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
47de86018c46 hello-world "/hello" About a minute ago Exited (0) About a minute ago modest_banach

root@68cb65a60ca0:/go/src/github.com/docker/docker# docker images

DEBU[0900] Calling GET /_ping
DEBU[0900] Calling GET /v1.26/images/json
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest c54a2cc56cbb 5 months ago 1.85 kB

ちなみにホスト側で見てみると、Docker in Dockerなのでhello-worldのコンテナやイメージは、特にホスト側の環境からは見えないです。

[docker@master]$ docker ps -a

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
68cb65a60ca0 docker-dev:master "hack/dind bash" 49 minutes ago Up 49 minutes jolly_montalcini

[docker@master]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-dev master df2b34329e3b 49 minutes ago 2.437 GB
debian jessie 73e72bf822ca 4 weeks ago 123 MB


コードをいじってみる

試しにちょっとコードをいじってビルドし直してみましょう。

さきほどの出力のdocker開発用のイメージ名にdocker-dev:master ってブランチが入っちゃてて、コードいじるならドキュメントに忠実に適当な作業ブランチ切ってからやったほうが良かったかなぁと若干後悔しつつ、まぁ実験なのでコミットせずに直接ファイルだけ編集しちゃおう。

公式ドキュメントの例と同じく、適当なエディタで docker attach コマンドのヘルプメッセージの出力をいじります。

[docker@master]$ vi cli/command/container/attach.go

[docker@master]$ git diff
diff --git a/cli/command/container/attach.go b/cli/command/container/attach.go
index 31bb10934..b63f551f4 100644
--- a/cli/command/container/attach.go
+++ b/cli/command/container/attach.go
@@ -38,7 +38,7 @@ func NewAttachCommand(dockerCli *command.DockerCli) *cobra.Command {
}

flags := cmd.Flags()
- flags.BoolVar(&opts.noStdin, "no-stdin", false, "Do not attach STDIN")
+ flags.BoolVar(&opts.noStdin, "no-stdin", false, "Do not attach STDIN (standard in)")
flags.BoolVar(&opts.proxy, "sig-proxy", true, "Proxy all received signals to the process")
flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
return cmd

開発用のコンテナでバイナリをビルドしなおします。コミットせずにビルドすると親切に警告される。

root@68cb65a60ca0:/go/src/github.com/docker/docker# hack/make.sh binary

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# GITCOMMIT = 001cbc4-unsupported
# The version you are building is listed as unsupported because
# there are some files in the git repository that are in an uncommitted state.
# Commit these changes, or add to .gitignore to remove the -unsupported from the version.
# Here is the current list:
M cli/command/container/attach.go
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bundles/1.14.0-dev already exists. Removing.

---> Making bundle: binary (in bundles/1.14.0-dev/binary)
Building: bundles/1.14.0-dev/binary-client/docker-1.14.0-dev
Created binary: bundles/1.14.0-dev/binary-client/docker-1.14.0-dev
Building: bundles/1.14.0-dev/binary-daemon/dockerd-1.14.0-dev
Created binary: bundles/1.14.0-dev/binary-daemon/dockerd-1.14.0-dev
Copying nested executables into bundles/1.14.0-dev/binary-daemon

今回はクライアント側しかいじってないけど、バイナリ配布する前に生きてるデーモンは一旦止めておく。

root@68cb65a60ca0:/go/src/github.com/docker/docker# ps -ef | grep docker

root 3141 1 0 08:08 ? 00:00:02 dockerd -D
root 3148 3141 0 08:08 ? 00:00:01 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --shim docker-containerd-shim --runtime docker-runc --debug
root 3962 1 0 08:40 ? 00:00:00 grep docker
root@68cb65a60ca0:/go/src/github.com/docker/docker# kill 3141
root@68cb65a60ca0:/go/src/github.com/docker/docker# INFO[1945] Processing signal 'terminated'
DEBU[1945] start clean shutdown of all containers with a 15 seconds timeout...
DEBU[1945] Cleaning up old mountid : start.
DEBU[1945] Cleaning up old mountid : done.
DEBU[1945] Clean shutdown succeeded
INFO[1945] stopping containerd after receiving terminated
DEBU[1945] Unix socket /run/docker/libnetwork/ab09a6af4d26c874a0d0bf83ab1d07c6817cbc8b5f770b68c2b674ef6b79cb72.sock doesn't exist. cannot accept client connections
DEBU[1945] libcontainerd: containerd health check returned error: rpc error: code = 9 desc = grpc: the client connection is closing

[1]+ Done docker daemon -D

バイナリを配布しなおし。

root@68cb65a60ca0:/go/src/github.com/docker/docker# cp bundles/1.14.0-dev/binary-client/docker* /usr/bi

n/
root@68cb65a60ca0:/go/src/github.com/docker/docker# cp bundles/1.14.0-dev/binary-daemon/docker* /usr/bin/

dockerdを起動する。

root@68cb65a60ca0:/go/src/github.com/docker/docker# dockerd -D &

[1] 3965

docker versionを確認。更新されてますね。

root@68cb65a60ca0:/go/src/github.com/docker/docker# docker version

DEBU[0075] Calling GET /_ping
DEBU[0075] Calling GET /v1.26/version
Client:
Version: 1.14.0-dev
API version: 1.26
Go version: go1.7.4
Git commit: 001cbc4-unsupported
Built: Sat Dec 10 08:37:49 2016
OS/Arch: linux/amd64

Server:
Version: 1.14.0-dev
API version: 1.26 (minimum version 1.12)
Go version: go1.7.4
Git commit: 001cbc4-unsupported
Built: Sat Dec 10 08:37:49 2016
OS/Arch: linux/amd64
Experimental: false

いじった docker attach のヘルプを見てみる。

root@68cb65a60ca0:/go/src/github.com/docker/docker# docker attach --help

DEBU[0136] Calling GET /_ping

Usage: docker attach [OPTIONS] CONTAINER

Attach to a running container

Options:
--detach-keys string Override the key sequence for detaching a container
--help Print usage
--no-stdin Do not attach STDIN (standard in)
--sig-proxy Proxy all received signals to the process (default true)

うん。よさげ。


テストを実行する

ついでにテストの流し方も調べておく。

make test でホスト側のローカルから全部フルセット流せるよう。

[docker@master]$ make test

特定のケースだけ流すには以下のように絞込をする。

[docker@master]$ TESTFLAGS='-test.run ^TestValidateIPAddress$' make test-unit

あとは、開発用のコンテナの中からも流せるみたいだけど、そのへんは公式ドキュメントの Run tests and test documentation あたりを参照。


おわりに

Docker Engineをソースコードからビルドする方法について書きました。だいたいやり方わかったので、これでコードいじって遊べそうですね。何か間違ったことを言っていたら教えてください。

まったく余談ですが、そろそろリリースされそうなDocker1.13についての話も先日書いたので興味があればこちらも合わせてどうぞ。

Docker 1.13の気になる変更点と新機能

Docker1.13は元々2016/12/8リリース予定だったようだけど、これを書いてる12/10時点ではまだRC3でまだ正式版はリリースされていないっぽい。