Docker Engine - CE のソースビルド

### はじめに

Docker Engine - Community Edition をソースコードからビルドします。最新のソースビルドはいろいろ状況が変化しています。他所においてもソースビルドの情報は提供されていますが、日本語情報で最新のビルド手順を示しているものを、まだ見かけたことがありません。少々古い情報であると、ビルド手順が異なります[^1]。そこで本文では 2020年6月執筆時点、最新の Docker-ce-19.03.11 ソースビルドを示します。

[^1]: そもそも Docker のプロジェクト体制が変わり、Docker Engine は [Moby プロジェクト](https://mobyproject.org) が担うようになっています。したがって git ソースを [https://github.com/docker/docker] から入手している情報は古いものです。しかも Docker デーモンと Docker クライアントのビルドが、ある時点で分離されたため、デーモン、クライアントを一括してビルドするためには、本文の手順が必要となります。

なお本文に示す手順に従って作業を進めようとするなら、十分に注意しておいてください。多くの方が利用している Linux ディストリビューションにおいてはパッケージマネージャーが存在し、Docker モジュールをパッケージ導入しているかと思います。本文の作業はパッケージ導入された Docker を無効にして、自ビルドした Docker で置き換えようとしています。また筆者は Linux From Scratch を利用しているため、パッケージマネージャーのことは考慮しておらず、インストール先も躊躇せずに `/usr/bin` としています。パッケージマネージャーが管理する状態を十分に理解し、環境を壊すリスクを十分に理解した上で、本文の作業を行ってください。たとえばインストール先を `/usr/local/bin` にするといった策を講じてください。

#### 前提

筆者は当Qiitaの投稿 「[Linux From Scratch に Docker Engine を導入](https://qiita.com/matsuand/items/cd6e32146b52a331d514)」に示しているように、[Linux From Scratch](http://www.linuxfromscratch.org) に基づいて構築した Linux OS を利用しており、その OS 上でのソースビルドを行います。ただこのソースビルド方法は、Linux OS 全般に通用すると思います。というのも Docker ソースからのビルドでは、ソースビルドを行うシステム（＝ホストシステム）上にすでにインストールされている Docker（正確には Docker デーモン）を利用して Docker コンテナーを作り出し、そのコンテナー内にてソースビルドを行うからです。Docker 風に言えば、コンテナー内にカプセル化された環境において、必要なビルドツールを動的に、つまりその時点で導入してビルドを行うため、Linux ホストシステムは何でもよく、ホストシステムにビルドツールが整っていなくてもビルドできてしまうというものです。

全般的に以下を前提とします。

* ビルドを行うためには Docker 自体がすでにインストールされていることが必要です。当然これに合わせて Docker の動作環境が構築済であり、実際に Docker が動作していなければなりません。
* 本文では Linux OS 上において動作させる Linux 用の Docker 実行モジュール類をビルドすることとしています。
* ビルドを行う Linux OS はたぶん何でもよいと思います。
* それよりもマシン自体に十分なスペックがあるとよいでしょう。ビルドを行ってみるとわかりますが、ビルド時には複数のコンテナーがビルドされ並列的に処理されます。`/var/lib/docker`（Docker 関連の種々ファイルが収容されるディレクトリ）が、ビルドだけで 10 GB 以上膨れあがりましたので、それなりに空き容量は確保しておく必要があります。

### 手順

以下、本手順においては root ユーザーでの操作としています。ご注意ください。

#### 1. ソースのダウンロード

ソース入手は２通りあります。一つは、tarball にまとめられているソースコードを利用する方法です。そしてもう一つは、随所でよく説明されているように `git` リポジトリから最新ソースを入手する方法です。どちらを選ぶかは目的や好みによって決めたらよいと思いますが、筆者としては tarball による方法をおすすめします。

* tarball のソースコードはバージョンごとに公式サイトから提供されています。当然のことながら、これは `git` リポジトリにおいて管理されている Docker ソースコードの、特定時点のものをバージョンづけして tarball にまとめているものです。通常はこれを用いることをおすすめします。
* `git ` による方法は、本当に最新のソース（時々刻々と更新される git HEAD の最新）を入手したい場合、あるいはソースコードの修正履歴を確認したり追ったりする必要がある場合に利用します。ただし問題は「本当に」、たとえば本日のつい5分ほど前に更新されたばかりの出来立てのソースを、是が非でも必要かどうかです。たぶん開発者でもない限り、そういう最新ソースは不要であると思います。むしろ git HEAD のソースが、常に完全にエラーなくビルドできる保証はないので要注意です。長い時間をかけてソースビルドしても、最後にビルドエラーになったり実行時エラーで動作しなかったりといったことがないとも限りません。ですから明確な目的がない限り `git` によるソース入手はおすすめできません。

##### 1.1. tarball 入手

tarball ソースは [Docker 公式 Github サイトのリリースページ](https://github.com/docker/docker-ce/releases) から提供されています。長い長いリリース説明文章の途中に tarball のダウンロードリンクがあって分かりにくいのですが、よく探してみると [Source code
(tar.gz)](https://github.com/docker/docker-ce/archive/v19.03.11.tar.gz) というものがあります。本文では執筆時点最新の `v19.03.11.tar.gz` を用います。

そこで `wget` を使ってダウンロードします。ダウンロードディレクトリは、これからソースビルドを行うディレクトリとして `/mnt/lfs/sources` というものを例にします。

ここで個人的な趣向の話になりますが、tarball 名が `v19.03.11.tar.gz` というものなので、見ただけでは何のソース tarball かわかりません。後々管理していくことを考慮して、以下のように `wget` のオプション `--output-document` を使って、ファイル名を変更してダウンロードすることにします。

```shell
# cd /mnt/lfs/sources
# wget -N https://github.com/docker/docker-ce/archive/v19.03.11.tar.gz \
    --output-document docker-ce-19.03.11.tar.gz
```

tarball の中身を念のため確認しておくと、`docker-ce-19.03.11` というディレクトリ配下に各種ソースコードがまとめられていることがわかります。

```shell
# tar tf docker-ce-19.03.11.tar.gz | less
docker-ce-19.03.11/
docker-ce-19.03.11/.github/
docker-ce-19.03.11/.github/PULL_REQUEST_TEMPLATE
docker-ce-19.03.11/.gitignore
docker-ce-19.03.11/CHANGELOG.md
docker-ce-19.03.11/CONTRIBUTING.md
docker-ce-19.03.11/Makefile
docker-ce-19.03.11/README.md
docker-ce-19.03.11/VERSION
docker-ce-19.03.11/components.conf
docker-ce-19.03.11/components/
docker-ce-19.03.11/components/cli/
docker-ce-19.03.11/components/cli/.dockerignore
(以下略)
```

tarball を伸張（解凍）し、ソースのトップディレクトリ `docker-ce-19.03.11` に移動します。

```shell
# tar xf docker-ce-19.03.11.tar.gz
# cd docker-ce-19.03.11
```

この tarball 入手の方法を選んだ場合は、次に示す `git` の説明を読み飛ばして「2. ソースビルド」の項に進んでください。

##### 1.2. `git` によるソース入手

Docker 公式の `git` リポジトリをクローンして、最新のソースコードを入手します。新たに `docker-ce` ディレクトリが生成されるので、そこに移動します。

```shell
# cd /mnt/lfs/sources
# git clone https://github.com/docker/docker-ce.git
# cd docker-ce
```

#### 2. ソースビルド

##### 2.1. ソースビルドの方針決定

ソースビルドを進める前に、まずビルド方針を定めます。

###### 2.1.1. ビルドターゲットの決定

ソースディレクトリに入って `make` と入力すると、これだけではビルド処理は開始されず、`make` のビルドターゲット一覧が表示されます。`make` では、所定のビルドターゲットを引数に与えて実行するようになっています。つまりどの `make` ビルドターゲットを選ぶかということが必要になります。いくつかのターゲットがあるのですが、Docker の実行モジュールを新規にビルドするには、以下の３つがあります。

* `make` の３つのターゲット：  `static`、`deb`、`rpm`

明確なビルド目的を持っていない限り、結論から言うと `static` を選べばよいと思います。本文ではこれを選ぶことにします。

`deb` と `rpm` は、その名からわかるように、それぞれ deb パッケージ、rpm パッケージをビルドするものと思われます。筆者は実際には試していないため、本分では説明しません。

###### 2.1.2. 最小ビルドのための方策

`make` のビルドターゲット `static` を選ぶと、ビルドされる実行モジュールが多すぎる難点があります。具体的には、Linux、Windows、Mac、ARM という対応アーキテクチャー向け実行モジュールをクロスコンパイルによって全部作り出してしまいます。これらすべてを作り出そうとする開発者の方なら別ですが、そうでない方にとって全部は必要のないものです。ただでさえ Docker ソースビルドに処理時間がかかるのに、全てをビルドするとなると相当な覚悟が必要となります。

筆者の意識は Linux OS 上で動作する Docker デーモンおよびクライントモジュールを、ソースビルドによって再構成することであり、Windows や Mac の実行モジュールは必要としていません。そこで Linux 実行モジュールのみをビルドするようにします。

##### 2.2. ビルド前の確認

Docker ソースビルドを行うためには、Docker デーモンが起動していることが必要です。起動していない場合は以下のようにして起動します。

```shell
# systemctl start docker
```

##### 2.3. ビルドの実行

tarball を伸張（解凍）して出来上がったディレクトリ `docker-ce-19.03.11` がソーストップディレクトリです。（あるいは `git` リポジトリから入手した場合は、`docker-ce` がソーストップディレクトリです。）

* `git` リポジトリから入手した場合のソースディレクトリは `docker-ce` ですが、いちいちお断りしながら説明するのは大変なので、ソーストップディレクトリは `docker-ce-19.03.11` であるとして説明していくことにします。

まずはソーストップディレクトリに移動します。

```shell
# cd /mnt/lfs/sources/docker-ce-19.03.11
```

次に `make static` を実行します。ただしそのまま実行すると、前述したように対応アーキテクチャー向け実行モジュールをすべて作り出してしまいます。Linux 向け実行モジュールだけをビルドするには、以下のように入力します。

```shell
# make static DOCKER_BUILD_PKGS=static-linux
```

簡単に上のコマンドの意味を説明しておきます。

* 上で指定した `DOCKER_BUILD_PKGS` は ``<ソーストップディレクトリ>/components/packaging` 内の `Makefile` に記述されています。このファイルに以下のような記述が存在します。

```Makefile
.PHONY: static
static: DOCKER_BUILD_PKGS:=static-linux cross-mac cross-win cross-arm
```

* ただ単に `make static` を実行すると、`DOCKER_BUILD_PKGS:=static-linux cross-mac cross-win cross-arm` という変数定義のまま処理が進みます。それがデフォルトの処理になります。これを `DOCKER_BUILD_PKGS=static-linux` と上書き定義して `make static` を実行しているのが、本文のやり方です。

* この変数定義内に見られる文字は容易に想像がつきます。`cross-mac` はクロスコンパイルによる Mac 用実行バイナリ、`cross-win` は Windows 用実行バイナリ、`cross-arm` は ARM 用実行バイナリを意味します。デフォルトではすべてをビルドすることが、ここに定義されているわけです。本文ではこの変数を `DOCKER_BUILD_PKGS=static-linux` とすることで、Linux 用実行バイナリのみをビルドすることにしましたが、この変数値を変更することで、ビルド対象を細かく設定することも可能ということです。

##### 2.4. ビルド処理時間の検証

ちなみに `make static` のみの実行による全モジュールビルドと、`DOCKER_BUILD_PKGS=static-linux` をつけた Linux モジュールビルドのそれぞれについて、time コマンドを使ってビルド時間を測ってみました。ビルド環境は VMware 上仮想イメージ、Linux From Scratch、メモリ2GB、スワップ1GB の同じ環境です。圧倒的な差異をご覧ください。

|処理方法                 |処理コマンド                                |処理時間     |
|:------------------------|:-------------------------------------------|------------:|
|全モジュールビルド       |`make static`                               |4時間06分49秒|
|Linuxモジュールのみビルド|`make static DOCKER_BUILD_PKGS=static-linux`|     59分04秒|

全モジュールビルドでは `static-linux`、`cross-mac`、`cross-win`、`cross-arm` という４種類のアーキテクチャー向けモジュールをビルドしており、だいたい１つのアーキテクチャービルドに１時間要している、という結果になりました。

#### 3. インストール（実行モジュールの保存）

##### 3.1. 実行モジュールの確認

`make static` によりビルドされる実行モジュールは、`<ソーストップディレクトリ>/components/packaging/static/build/linux` に保存されます。保存されているのは、１つには実行モジュールを集めた tarball であり、もう１つはさらにサブディレクトリ `docker`（および `docker-rootless-extras`） 内に個々の実行モジュールが置かれています。その様子が以下です。tarball ２つとサブディレクトリ `docker`、`docker-rootless-extras` が見えます。

```shell
# cd /mnt/lfs/sources/docker-ce-19.03.11
# cd components/packaging/static/build/linux
# ls
docker               docker-rootless-extras
docker-19.03.11.tgz  docker-rootless-extras-19.03.11.tgz
```

上に続けてサブディレクトリ `docker`、`docker-rootless-extras` を見ると、実行モジュールがそのまま置かれています。

```shell
# ls docker
containerd       ctr     docker-init   dockerd
containerd-shim  docker  docker-proxy  runc

# ls docker-rootless-extras
dockerd-rootless.sh  rootlesskit  rootlesskit-docker-proxy  vpnkit
```

この次に行うインストール作業方法として、tarball をどこかにコピー保存し、場合によっては他マシンにコピーしてインストールするという方法が考えられます。単純には、サブディレクトリ `docker`（および `docker-rootless-extras`）内にある実行モジュールを直接ホストシステムにコピーしてしまう方法もあります。必要に応じて適当な方法を選びます。本文では後者の方法で進めます。

##### 3.2. Docker サービスの停止

Docker サービスを再インストールするので、まずは Docker サービスを停止します。

```shell
# systemctl stop docker
```

これを行っておかないと、いざ Docker デーモンの実行モジュール `dockerd` をコピーしてインストールしようとしても、`systemd` がそれまでの古い `dockerd` を掴んでしまっているため、新たな実行モジュールを上書きコピーできません。

##### 3.3. それまでの実行モジュールのバックアップ（または削除）

必要に応じて、それまで用いていた Docker モジュールのバックアップをとっておきます。どこかのディレクトリに移動させて保存するか、`.orig` などのサフィックスをつけて保存しておくなどです。バックアップ手順は本文では特に示しません。お好みの方法により実施してください。

あるいは十分に状況把握ができていて、かつ可能であるなら、それまでの Docker 実行モジュールをすべて削除する方法もあります。利用している Linux ディストリビューションが提供しているパッケージマネージャーを使って Docker 関連パッケージを導入しているのであれば、それを削除する操作を行うのでもよいでしょう。ただし、何度でも書きますが十分に状況を理解できている場合に限ります。

##### 3.4. 実行モジュールのインストール

上で示したようにソースディレクトリ配下に、ビルドされた新たな実行モジュールが置かれているので、それを直接 `/usr/bin` にコピーする方法です。単にソースディレクトリ内から `/usr/bin` へのコピーを行うだけです。

実行モジュールの単体は `<ソーストップディレクトリ>/components/packaging/static/build/linux/docker` に置かれていると説明しました。
そこで前と同じくソーストップディレクトリが `/mnt/lfs/sources/docker-ce-19.03.11` であるとして、以下を実行します。

```shell
# cd /mnt/lfs/sources/docker-ce-19.03.11
# cd components/packaging/static/build/linux/docker
# cp * /usr/bin
```

rootless の実行モジュールもインストールするのであれば、同様のことを `docker-rootless-extras` ディレクトリに対しても行います。

```shell
# cd /mnt/lfs/sources/docker-ce-19.03.11
# cd components/packaging/static/build/linux/docker-rootless-extras
# cp * /usr/bin
```

`/usr/bin` にインストールした `docker`、`dockerd` などの実行モジュールに対して、`--version` オプションをつけて実行してみます。間違いなく新しいバージョンがインストールされていることを確認してください。

```shell
# which -a docker
/usr/bin/docker

# docker --version
Docker version 19.03.11, build
```

```shell
# which -a dockerd
/usr/bin/dockerd

# dockerd --version
Docker version 19.03.11, build unsupported
```

##### 3.5. Docker サービスの再起動

停止していた Docker サービスを起動します。

* もしパッケージマネージャーを通じて、完全に Docker 関連パッケージを削除してしまっているなら、Docker デーモンを起動するための Docker サービス用ファイル、`docker.service`、`docker.socket` も消してしまっているかもしれません。
* その場合は、拙稿「[Linux From Scratch に Docker Engine を導入](https://qiita.com/matsuand/items/cd6e32146b52a331d514)」において、`docker.service`、`docker.socket` の入手および設定方法を示していますから参照してください。

`docker.service`、`docker.socket` が正しく用意できていれば、ここからは新たな Docker デーモン実行モジュール `dockerd` が動き出すことになります。実際に動かしてみます。

```shell
# systemctl start docker.service
```

##### 3.6. 動作確認もろもろ

この後は `docker run hello-world` や、Docker 公式ドキュメントの [チュートリアル](https://docs.docker.com/get-started/) などを試してみて、Docker デーモンや Docker クライアントが動作することを確認します。

本文は以上です。