LoginSignup
5
5

More than 5 years have passed since last update.

Docker のクロスコンパイルの処理を追う

Last updated at Posted at 2015-01-11

Git (docker/docker) から clone をしてはみたけれど、何だかよく分からない。とりあえず「Docker のソースの構成についてのメモ - Qiita」で、トップディレクトリの分類はしてみた。

そして、docker/ が CLI プログラムのメインと見うけられる。そもそも Go 言語のビルドシステムや作法が分からないので、いろいろと分からない。

まあ、やってみるか。

Docker を Docker 上でビルド

Docker のビルドは、Docker 上で行う。この点は簡単で助かる。依存地獄知らず。

ビルド用の Dockerfile が用意されているので、とりあえずそれを build してみる。右記を参考にした。 ∥ Etsukata blog: Docker のビルド方法に見る Golang の利点

docker run のオプションについては、Dockerfile 内に注記がある。これ、なんで --privileged が要るのかな?

$ git clone https://github.com/docker/docker.git
$ cd docker/
$ docker build -t docker .
$ docker run -it --rm -v $PWD:/go/src/github.com/docker/docker --privileged docker bash
# pwd
/go/src/github.com/docker/docker
# hack/make.sh

なんと、この Docker 環境はクロスコンパイルをするよ、すごいね。いや、Go 言語がすごいのか。

# ls -l /usr/local/go/bin/
total 12576
drwxr-xr-x 2 root root    4096 Jan 11 09:56 darwin_386
drwxr-xr-x 2 root root    4096 Jan 11 09:56 darwin_amd64
drwxr-xr-x 2 root root    4096 Jan 11 09:56 freebsd_386
drwxr-xr-x 2 root root    4096 Jan 11 09:56 freebsd_amd64
drwxr-xr-x 2 root root    4096 Jan 11 09:56 freebsd_arm
-rwxr-xr-x 1 root root 9350136 Jan 11 09:56 go
-rwxr-xr-x 1 root root 3496688 Jan 11 09:56 gofmt
drwxr-xr-x 2 root root    4096 Jan 11 09:55 linux_386
drwxr-xr-x 2 root root    4096 Jan 11 09:55 linux_arm
# hack/make.sh cross

すると、以下のように。ちなみに環境としては、Mac 上の Boot2docker で Docker している。コンテナとしては、Dockerfile に FROM ubuntu:14.04 とあるので、Ubuntu 14.04 TLS ですね。

# ls -d bundles/1.3.2-dev/cross/*/* | cat
bundles/1.3.2-dev/cross/darwin/386
bundles/1.3.2-dev/cross/darwin/amd64
bundles/1.3.2-dev/cross/freebsd/386
bundles/1.3.2-dev/cross/freebsd/amd64
bundles/1.3.2-dev/cross/freebsd/arm
bundles/1.3.2-dev/cross/linux/386
bundles/1.3.2-dev/cross/linux/arm
# bundles/1.3.2-dev/cross/darwin/386/docker version
Client version: 1.3.2-dev
Client API version: 1.16
Go version (client): go1.3.3
Git commit (client): 353ff40
OS/Arch (client): darwin/386
FATA[0000] Get http:///var/run/docker.sock/v1.16/version: dial unix /var/run/docker.sock: no such file or directory. Are you trying to connect to a TLS-enabled daemon without TLS?
# bundles/1.3.2-dev/cross/darwin/amd64/docker version
Client version: 1.3.2-dev
Client API version: 1.16
Go version (client): go1.3.3
Git commit (client): 353ff40
OS/Arch (client): darwin/amd64
FATA[0000] Get http:///var/run/docker.sock/v1.16/version: dial unix /var/run/docker.sock: no such file or directory. Are you trying to connect to a TLS-enabled daemon without TLS?
# bundles/1.3.2-dev/cross/darwin/amd64/docker version
bash: bundles/1.3.2-dev/cross/darwin/amd64/docker: cannot execute binary file: Exec format error
# bundles/1.3.2-dev/cross/freebsd/arm/docker version
bash: bundles/1.3.2-dev/cross/freebsd/arm/docker: cannot execute binary file: Exec format error

当然のことながら、Docker ホスト(というか、Boot2docker ホスト)である Mac(vboxsf で、VirtualBox の仮想マシンとフォルダを共有している)では、普通に Darwin のバイナリが実行できる。

$ bundles/1.3.2-dev/cross/darwin/amd64/docker version
Client version: 1.3.2-dev
Client API version: 1.16
Go version (client): go1.3.3
Git commit (client): 353ff40
OS/Arch (client): darwin/amd64
FATA[0000] Get http:///var/run/docker.sock/v1.16/version: dial unix /var/run/docker.sock: no such file or directory. Are you trying to connect to a TLS-enabled daemon without TLS?

クロスコンパイルと、daemon ビルドタグの扱い

hack/make.sh が、引数に指定されたバンドル(=ターゲット?)をターゲットとしてビルドする。無指定であれば、全部やるっぽい。

後に cross を見るが、これは binary を、ターゲットの OS/arch 指定で呼んでいる。無指定ならば linux/amd64 になるってことなんだろう。

hack/make.sh
...

DEFAULT_BUNDLES=(
    validate-dco
    validate-gofmt

    binary

    test-unit
    test-integration
    test-integration-cli
    test-docker-py

    dynbinary
    dyntest-unit
    dyntest-integration

    cover
    cross
    tgz
    ubuntu
)

...

bundle() {
    bundlescript=$1
    bundle=$(basename $bundlescript)
    echo "---> Making bundle: $bundle (in bundles/$VERSION/$bundle)"
    mkdir -p bundles/$VERSION/$bundle
    source "$bundlescript" "$(pwd)/bundles/$VERSION/$bundle"
}

main() {
    # We want this to fail if the bundles already exist and cannot be removed.
    # This is to avoid mixing bundles from different versions of the code.
    mkdir -p bundles
    if [ -e "bundles/$VERSION" ]; then
        echo "bundles/$VERSION already exists. Removing."
        rm -fr bundles/$VERSION && mkdir bundles/$VERSION || exit 1
        echo
    fi
    SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
    if [ $# -lt 1 ]; then
        bundles=(${DEFAULT_BUNDLES[@]})
    else
        bundles=($@)
    fi
    for bundle in ${bundles[@]}; do
        bundle $SCRIPTDIR/make/$bundle
        echo
    done
}

main "$@"

以下では、cross バンドルに注目する。

$SCRIPTDIR を見ると、hack/make/ の下から、バンドルごとにバンドル名と同名のスクリプトを source する。よって、環境変数(export していないけれど)は、ここ(上)で指定したまま、下位のスクリプトに受け渡される。

下記が cross bundle のスクリプト。デーモンをサポートしているのは linux/amd64 のみであることが分かる。下記に、「remove the "daemon" build tag from platforms that aren't supported」とあるとおり。

hack/make/cross
...
daemonSupporting=(
    [linux/amd64]=1
)
...
for platform in $DOCKER_CROSSPLATFORMS; do
    (
        mkdir -p "$DEST/$platform" # bundles/VERSION/cross/GOOS/GOARCH/docker-VERSION
        export GOOS=${platform%/*}
        export GOARCH=${platform##*/}
        if [ -z "${daemonSupporting[$platform]}" ]; then
            export LDFLAGS_STATIC_DOCKER="" # we just need a simple client for these platforms
            export BUILDFLAGS=( "${ORIG_BUILDFLAGS[@]/ daemon/}" ) # remove the "daemon" build tag from platforms that aren't supported
        fi
        source "$(dirname "$BASH_SOURCE")/binary" "$DEST/$platform"
    )
done

これがどう効いてくれかと言うと、たとえばプラットフォームによって、クライアント・Engine 両対応のバイナリを出力するか、あるいはクライアント専用のバイナリを出力するかの条件ビルドをする場合など。

Docker で言うと、main.mainDaemon() の定義が、docker/client.go と docker/daemon.go の二箇所にある。client.go は、クライアントオンリーバイナリの場合に結合されるソースだ。

docker/client.go
// +build !daemon

...

func mainDaemon() {
  log.Fatal("This is a client-only binary - running the Docker daemon is not supported.")
}
...

Mac で実行してみる。

$ docker -d
2015/01/11 23:54:30 This is a client-only binary - running the Docker daemon is not supported.

詳しくは、「build - The Go Programming Language 」あたりを参照。

ビルド制約はビルドタグとしても知られており、 以下に示す文字列で始まる行コメントです。

// +build

この後にそのファイルがパッケージに含められる条件を列挙します。

ビルドできる OS/プラットフォームのリストである $DOCKER_CROSSPLATFORMS は、下記のように Dockerfile でコンテナイメージを作る際に指定している。

Dockerfile
...
# Compile Go for cross compilation
ENV DOCKER_CROSSPLATFORMS \
    linux/386 linux/arm \
    darwin/amd64 darwin/386 \
    freebsd/amd64 freebsd/386 freebsd/arm \
    windows/amd64 windows/386
...

では、hack/make.sh では、daemon フラグはどう扱われているか。

project/make.sh
if [ -z "$DOCKER_CLIENTONLY" ]; then
    DOCKER_BUILDTAGS+=" daemon"
fi
...
ORIG_BUILDFLAGS=( -a -tags "netgo static_build $DOCKER_BUILDTAGS" )
BUILDFLAGS=( $BUILDFLAGS "${ORIG_BUILDFLAGS[@]}" )

上記のように、フラグ(「ビルドタグ」)を -tags "aaa bbb ..." と設定する。

project/make.sh → project/make/cross → project/make/binary と source されていって、最終的には、下記でビルドされる。

project/make/binary
...
go build \
    -o "$DEST/$BINARY_FULLNAME" \
    "${BUILDFLAGS[@]}" \
    -ldflags "
        $LDFLAGS
        $LDFLAGS_STATIC_DOCKER
    " \
    ./docker
...

なるほどね。

ところで、差分ビルドってできるの?

部分ビルドのしかたは分からないが(たぶん go biuld が差分ビルドをしてくれている)、コンテナ内の「docker/」で go build すれば、CLI ツールをコンパイルして出力してくれる。

-
# cd docker/
# vi flags.go

で、

-
# go build
5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5