docker的编译环境实际上是创建一个docker容器,在容器中对代码进行编译。 如果想快速的查看编译环境搭建指导,而不关注环境搭建的机制和细节,可以直接跳到最后一章“总结”。

 

前提

 

      机器上已经安装了docker,因为编译环境是个docker容器,所以要事先有docker(daemon),后面会创建个编译环境容器,在容器里面编译代码。

下载docker源码

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

 

 

编译前分析

 

     官方给的编译方法是make build 和 make binary等。下面先分析Makefile,看懂Makefile后,编译环境的准备流程就比较清楚了。

Makefile 

  • DOCKER_MOUNT := $(if $(BIND_DIR),-v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/docker/docker/$(BIND_DIR)")    DOCKER_MOUNT 表示创建容器时的mount参数。因为编译环境是一个容器,会将物理机上的目录mount给容器容器,容器中该目录是编译生成docker二进制文件的目录。
  • DOCKER_FLAGS := docker run --rm -i --privileged $(DOCKER_CONTAINER_NAME) $(DOCKER_ENVS) $(DOCKER_MOUNT) $(DOCKER_PORT_FORWARD)   创建docker容器时的命令行的一部分,包含了前面的DOCKER_MOUNT参数。 
  • DOCKER_IMAGE := docker-dev$(if $(GIT_BRANCH_CLEAN),:$(GIT_BRANCH_CLEAN))    镜像的名字是docker-dev
  • DOCKER_RUN_DOCKER := $(DOCKER_FLAGS) "$(DOCKER_IMAGE)"    创建docker容器的命令行,组合了前面的DOCKER_FLAGS 和 DOCKER_IMAGE 。 从命令行中可以看出,启动容器的参数有 --rm -i --privileged,使用了环境变量,还有使用了 -v 参数把物理机 mount 给容器,在容器中编译好二进制文件后放到该目录中,在物理机上就能获得二进制文件。启动的的docker 容器镜像名字是 docker-dev。

make build

     make build来搭建编译环境,实际上是制作了一个镜像,这个镜像里会包含编译代码所需的环境。下面来介绍下这个镜像。 

Dockerfile 

执行make build 相当于调用docker build,使用的就是该Dockerfile。Dockerfile中的几个主要步骤: 

  • 使用 debian 作为基础镜像
  • 安装一些编译需要的软件
RUN apt-get update && apt-get install -y \
   apparmor \
   apt-utils \
   aufs-tools \
   automake \
   bash-completion \
   binutils-mingw-w64 \
   bsdmainutils \
   btrfs-tools \
   build-essential \
   cmake \
   createrepo \
   curl \
   dpkg-sig \
   gcc-mingw-w64 \
   git \
   iptables \
   jq \
   less \
   libapparmor-dev \
   libcap-dev \
   libnl-3-dev \
   libprotobuf-c0-dev \
   libprotobuf-dev \
   libsystemd-journal-dev \
   libtool \
   mercurial \
   net-tools \
   pkg-config \
   protobuf-compiler \
   protobuf-c-compiler \
   python-dev \
   python-mock \
   python-pip \
   python-websocket \
   tar \
   vim \
   vim-common \
   xfsprogs \
   zip \
   --no-install-recommends \
   && pip install awscli==1.10.15

 

  • 创建 lvm2 目录,下载并解压安装
ENV LVM2_VERSION 2.02.103
RUN mkdir -p /usr/local/lvm2 \
   && curl -fsSL "https://mirrors.kernel.org/sourceware/lvm2/LVM2.${LVM2_VERSION}.tgz" \
      | tar -xzC /usr/local/lvm2 --strip-components=1
# See https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags

# Compile and install lvm2
RUN cd /usr/local/lvm2 \
   && ./configure \
      --build="$(gcc -print-multiarch)" \
      --enable-static_link \
   && make device-mapper \
   && make install_device-mapper

 

  • 下载并安装 Go 1.8.3 版本
ENV GO_VERSION 1.8.3
RUN curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" \
   | tar -xzC /usr/local

ENV PATH /go/bin:/usr/local/go/bin:$PATH
ENV GOPATH /go

 

  • 安装 GO 相关的 tools ,比如 go lint 等代码检查 
# Dependency for golint
ENV GO_TOOLS_COMMIT 823804e1ae08dbb14eb807afc7db9993bc9e3cc3
RUN git clone https://github.com/golang/tools.git /go/src/golang.org/x/tools \
   && (cd /go/src/golang.org/x/tools && git checkout -q $GO_TOOLS_COMMIT)

# Grab Go's lint tool
ENV GO_LINT_COMMIT 32a87160691b3c96046c0c678fe57c5bef761456
RUN git clone https://github.com/golang/lint.git /go/src/github.com/golang/lint \
   && (cd /go/src/github.com/golang/lint && git checkout -q $GO_LINT_COMMIT) \
   && go install -v github.com/golang/lint/golint

 

  • 安装 两个版本的 registry
ENV REGISTRY_COMMIT_SCHEMA1 ec87e9b6971d831f0eff752ddb54fb64693e51cd
ENV REGISTRY_COMMIT 47a064d4195a9b56133891bbb13620c3ac83a827
RUN set -x \
   && export GOPATH="$(mktemp -d)" \
   && git clone https://github.com/docker/distribution.git "$GOPATH/src/github.com/docker/distribution" \
   && (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT") \
   && GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
      go build -o /usr/local/bin/registry-v2 github.com/docker/distribution/cmd/registry \
   && (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT_SCHEMA1") \
   && GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
      go build -o /usr/local/bin/registry-v2-schema1 github.com/docker/distribution/cmd/registry \
   && rm -rf "$GOPATH"

 

  • 安装 notary server; 
ENV NOTARY_VERSION v0.5.0
RUN set -x \
   && export GOPATH="$(mktemp -d)" \
   && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
   && (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION") \
   && GOPATH="$GOPATH/src/github.com/docker/notary/vendor:$GOPATH" \
      go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
   && GOPATH="$GOPATH/src/github.com/docker/notary/vendor:$GOPATH" \
      go build -o /usr/local/bin/notary github.com/docker/notary/cmd/notary \
   && rm -rf "$GOPATH"

 

  • 安装docker-py 后面跑集成测试用的 
# Get the "docker-py" source so we can run their integration tests
ENV DOCKER_PY_COMMIT a962578e515185cf06506050b2200c0b81aa84ef
# To run integration tests docker-pycreds is required.
# Before running the integration tests conftest.py is
# loaded which results in loads auth.py that
# imports the docker-pycreds module.
RUN git clone https://github.com/docker/docker-py.git /docker-py \
   && cd /docker-py \
   && git checkout -q $DOCKER_PY_COMMIT \
   && pip install docker-pycreds==0.2.1 \
   && pip install -r test-requirements.txt

 

  • 挂盘 /var/lib/docker,设置工作目录为 /go/src/github.com/docker/docker
VOLUME /var/lib/docker
WORKDIR /go/src/github.com/docker/docker
ENV DOCKER_BUILDTAGS apparmor seccomp selinux

 

  • 脚本拷贝到镜像中 /go/src/github.com/docker/docker/contrib/ ,并运行下载镜像的tar文件。这里 docker build 只是创建临时容器,运行contrib/download-frozen-image.sh 在docker build 所创建的临时的容器中下载docker镜像,有docker-in-docker容器嵌套的意思
# Get useful and necessary Hub images so we can "docker load" locally instead of pulling
COPY contrib/download-frozen-image-v2.sh /go/src/github.com/docker/docker/contrib/
RUN ./contrib/download-frozen-image-v2.sh /docker-frozen-images \
   buildpack-deps:jessie@sha256:85b379ec16065e4fe4127eb1c5fb1bcc03c559bd36dbb2e22ff496de55925fa6 \
   busybox:latest@sha256:32f093055929dbc23dec4d03e09dfe971f5973a9ca5cf059cbfb644c206aa83f \
   debian:jessie@sha256:72f784399fd2719b4cb4e16ef8e369a39dc67f53d978cd3e2e7bf4e502c7b793 \
   hello-world:latest@sha256:c5515758d4c5e1e838e9cd307f6c6a0d620b5e07e6f927b07d05f6d12a1ac8d7

 

  • ENTRYPOINT ["hack/dind"]  镜像运行源码目录中的 hack/dind 脚本。 
  • COPY . /go/src/github.com/docker/docker  把物理机上的docker源码文件拷贝至镜像

download-frozen-image.sh脚本 
      作用是下载镜像的tar包,供后续docker load。在Dockerfile中的调用方式如下: 

      download-frozen-image.sh 传入参数,其中/docker-frozen-images作为base dir,下载存放路径。镜像包含了镜像名、镜像tag、镜像id。 
       download-frozen-image.sh 脚本中会从 https://registry-1.docker.io 下载

hack/dind脚本 

  • 对脚本的描述是 Dind: a wrapper script which allows docker to be run inside a docker container. 

make binary

      执行make binary 就可以编译出docker二进制文件。编译出来的二进制文件在源码目录下的 bundles文件中。

     Makefile中的binary       

 

binary: build ## build the linux binaries
   $(DOCKER_RUN_DOCKER) hack/make.sh binary

 

     

先执行build,制作docker-dev编译环境镜像。 再执行DOCKER_RUN_DOCKER,创建容器,DOCKER_RUN_DOCKER 就是执行 docker run,使用docker-dev 镜像启动容器,并且会 mount -v 将容器生成二进制文件的路径与宿主机共享。启动的容器运行的命令行是 hack/make.sh binary 。       

    docker run完整的形式如下: 

docker run --rm -i --privileged -e BUILDFLAGS -e DOCKER_CLIENTONLY -e DOCKER_DEBUG -e DOCKER_EXECDRIVER -e DOCKER_EXPERIMENTAL -e DOCKER_REMAP_ROOT -e DOCKER_GRAPHDRIVER -e DOCKER_STORAGE_OPTS -e DOCKER_USERLANDPROXY -e TESTDIRS -e TESTFLAGS -e TIMEOUT -v "/home/xx/src/docker/docker/bundles:/go/src/github.com/docker/docker/bundles" -t "docker-dev:master" hack/make.sh binary

hack/make.sh脚本 

     创建的容器启动命令是hack/make.sh binary,参数为binary。 

总结

编译步骤总结: 

  • make build ,在物理机上创建出一个 docker-dev 的镜像。 
  • make binary , 使用docker-dev镜像启动一个容器,在容器中编译docker代码。默认二进制文件在 bundles 目录下 
  •  

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_CROSSPLATFORMS -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 "/home/lin/project/src/github.com/docker/docker/bundles:/go/src/github.com/docker/docker/bundles" -v /home/lin/project/src/github.com/docker/docker/.git:/go/src/github.com/docker/docker/.git -v "dockerdev-go-pkg-cache-gopath:/go/pkg" -v "dockerdev-go-pkg-cache-goroot-linux_amd64:/usr/local/go/pkg/linux_amd64" -v "dockerdev-go-pkg-cache-goroot-linux_amd64_netgo:/usr/local/go/pkg/linux_amd64_netgo"  -t "docker-dev:moby" hack/make.sh binary