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


# はじめに
この記事は [Docker2 Advent Calendar 2016](http://qiita.com/advent-calendar/2016/docker2) の11日目の記事です。

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

ビルド手順自体は開発者向けに公式ドキュメントの [Open Source at Docker](https://docs.docker.com/opensource/code/)  というページに丁寧に説明されているので、この辺を参考にしつつ進めていきますが、公式の説明は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](https://github.com/codekitchen/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コマンドが見えます。
実際には１行ですが、わかりにくいので、改行するとこんなかんじ。

```
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](https://hub.docker.com/_/docker/)を使った 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](https://docs.docker.com/opensource/project/test-and-docs/) あたりを参照。

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

まったく余談ですが、そろそろリリースされそうなDocker1.13についての話も先日書いたので興味があればこちらも合わせてどうぞ。
[Docker 1.13の気になる変更点と新機能](http://qiita.com/minamijoyo/items/fa42104019240936e2cf)
Docker1.13は元々2016/12/8リリース予定だったようだけど、これを書いてる12/10時点ではまだRC3でまだ正式版はリリースされていないっぽい。