如何编译Docker源码

如何编译Docker源码,第1张

本文根据docker官方给出的docker代码编译环境搭建指南做更深入的分析。官方给出的指导比较简单,但是由于国内的网络问题经常会编译失败,了解了编译步骤后,也可以结合自身遇到的网络问题进行“规避”。

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

前提

机器上已经安装了docker,因为编译环境是个docker容器,所以要事先有docker(daemon),后面会创建个编译环境容器,在容器里面编译代码。本文中使用物理机,物理机上运行着docker (daemon)。

机器(物理机)上安装了git 。 后续使用git下载docker源码

机器(物理机)上安装了make。

下载ubuntu 1404的docker镜像

下载docker源码

git clone

会把代码下载到当前目录下,后面会把代码拷贝到容器中。

编译前分析

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

Makefile

在下载的docker源码中可以看到它的Makefile,Makefile中比较关键的几个参数:

DOCKER_MOUNT := $(if $(BIND_DIR),-v "$(CURDIR)/$(BIND_DIR):/go/src/githubcom/docker/docker/$(BIND_DIR)") DOCKER_MOUNT 表示创建容器时的mount参数。因为编译环境是一个容器,在后续的步骤中启动容器时使用DOCKER_MOUNT参数,会将物理机上的目录mount给容器容器,容器中该目录是编译生成docker二进制文件的目录。

DOCKER_FLAGS := docker run --rm -i --privileged $(DOCKER_ENVS) $(DOCKER_MOUNT) 这是后面创建docker容器时的命令行的一部分,其中包含了前面的DOCKER_MOUNT参数。

DOCKER_IMAGE := docker-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH)) 这是docker image参数,镜像的名字是docker-dev,以当前git中docker版本作为tag名。这个镜像是在make build一步做出来的。

DOCKER_RUN_DOCKER := $(DOCKER_FLAGS) "$(DOCKER_IMAGE)" 创建docker容器的命令行,组合了前面的DOCKER_FLAGS 和 DOCKER_IMAGE 。 从命令行中可以看出,启动容器使用的参数有 --rm -i --privileged,使用了一些环境变量,还有使用了-v参数把物理机上目录mount给容器,在容器中编译好二进制文件后放到该目录中,在物理机上就能获得docker二进制文件。启动的的docker 容器镜像名字是docker-dev。下文会介绍docker-dev镜像是怎么来的。

由于官方给出的“构建编译环境”的方法是执行 make build,下面在Makefile中看到build分支是这样的:

make build时会调用 docker build -t "$(DOCKER_IMAGE)" 去制作一个叫做DOCKER_IMAGE的镜像。

进行源码编译的方式是执行 make binary来编译代码,在Makefile中make binary的分支如下:

make binary除了进行 make build以外,会执行$(DOCKER_RUN_DOCKER),即上文提到的docker run命令行。由于执行过了build,会build出来docker-dev镜像,所以在docker run时直接使用前面build出来的镜像。docker run时的命令行参数是hack/makesh binary。make binary的过程实际上是创建一个容器,在容器中执行hack/makesh binary脚本。接下来会详细介绍make build和make binary所做的内容。

make build

根据官方的指导,先执行make build来搭建编译环境。上面分析了,make build实际上是制作了一个镜像,这个镜像里会包含编译代码所需的环境。下面来介绍下这个镜像。

Dockerfile

在和Makefile相同的目录下(源码的根目录),有Dockerfile。执行make build 相当于调用docker build,使用的就是该Dockerfile。Dockerfile中的几个主要步骤(有些步骤这里略过):

FROM ubuntu:1404 使用ubuntu 1404作为基础镜像;在宿主机上,要事先下载好ubuntu 1404镜像。

安装一些编译需要的软件;

用git下载lvm2源码,并编译安装;

下载并安装GO 151;

安装GO相关的tools 可以做code coverage test 、 go lint等代码检查

安装registry和notary server;

安装docker-py 后面跑集成测试用的

将物理机的contrib/download-frozen-imagesh 脚本拷贝到镜像中/go/src/githubcom/docker/docker/contrib/

运行contrib/download-frozen-imagesh 制作镜像 实际上这一步只是下载了3个镜像的tar文件。注意:docker build相当于创建一个临时的容器(在临时的容器中执行Dockerfile中的每一步,最后在保存成镜像),“运行contrib/download-frozen-imagesh 制作镜像”这个动作出现在Dockerfile中,相当于在docker build所创建的临时的容器中下载docker镜像,有docker-in-docker容器嵌套的概念。下一小节会对download-frozen-imagesh脚本做详细分析。

ENTRYPOINT ["hack/dind"] 做出来的镜像,使用它启动的容器可以自动运行源码目录中的hack/dind脚本。 dind这个脚本是a wrapper script which allows docker to be run inside a docker container 。后面的小节会对hack/dind脚本做详细的分析。

COPY /go/src/githubcom/docker/docker 把物理机上的docker源码文件打入到镜像中

download-frozen-imagesh脚本

上一小节里提到,在Dockerfile中,有一步会调用contrib/download-frozen-imagesh ,它主要作用是下载3个镜像的tar包,供后续docker load。在Dockerfile中的调用方式如下:

download-frozen-imagesh脚本中会依次解析参数,其中/docker-frozen-images作为base dir,后面下载的东西全放到这里。之后的3个参数是镜像,里面包含了镜像名(例如busybox)、镜像tag(例如latest)、镜像id(例如d7057cb020844f245031d27b76cb18af05db1cc3a96a29fa7777af75f5ac91a3),后面会在循环中依次下载这3个镜像的tar文件。

download-frozen-imagesh脚本中会通过curl从registry上获取如下信息:

token:获取token,后面curl获取的其他信息时都需要使用token。例如本例中 token='signature=9088f0552b1b147364e07bdd48857dd77c0d94ee,repository="library/busybox",access=read'

ancestryJson:把镜像相关联的历史层次的id也都获取到,因为每一层的tar都需要下载。本例中 ancestryJson='["d7057cb020844f245031d27b76cb18af05db1cc3a96a29fa7777af75f5ac91a3", "cfa753dfea5e68a24366dfba16e6edf573daa447abf65bc11619c1a98a3aff54"]'

这里可以看到这个镜像只有2层,两层的id这里都列了出来。 每个镜像包含的层数不同,例如。第三个镜像jess/unshare共有10层。

VERSION、json、tar: 每一层镜像id的目录下,都下载这3个文件,其中VERSION文件内容目前都是“10”,json文件是该层镜像的json文件,tar文件是该层镜像的真正内容,以tar保存。

下载好的各层镜像目录结构如下:

$ls

$tree

hack/dind脚本

在Dockerfile中,ENTRYPOINT ["hack/dind"] ,表示在镜像启动后,运行该脚本,下面分析一下这个脚本的功能。

脚本在代码根目录下的hack目录中,作者对脚本的描述是 DinD: a wrapper script which allows docker to be run inside a docker container

就是可以在docker容器中创建docker容器。它就做了一个事,那就是在容器中创建好cgroup目录,并把各个cgroup子系统mount上来。

为了方便理解,我们可以先看看物理机。在宿主机上如果创建docker容器,需要宿主机上必须事先mount cgroup子系统,因为cgroup是docker容器的一个依赖。同理docker-in-docker也要求外层的docker容器中有cgroup子系统,dind脚本在容器启动后,先去/proc/1/cgroup中获取cgroup子系统,然后依次使用mount命令,将cgroup mount上来,例如mount -n -t cgroup -o "cpuset" cgroup "/cgroup/cpuset"

最终在运行make build后,会制作出一个叫docker-dev的镜像。

make binary

执行make binary 就可以编译出docker二进制文件。编译出来的二进制文件在源码目录下的bundles/1100-dev/binary/docker-1100-dev ,其中还包含md5和sha256文件。

Makefile中的binary

Makefile中关于make binary流程是

先执行build,即上一节介绍的,制作docker-dev编译环境镜像。

再执行DOCKER_RUN_DOCKER,创建容器,DOCKER_RUN_DOCKER就是执行docker run,使用docker-dev镜像启动容器,并且会mount -v 将容器生成二进制文件的路径与宿主机共享。DOCKER_RUN_DOCKER在“编译前分析”一章中有介绍。启动的容器运行的命令行是 hack/makesh 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/mubai/src/docker/docker/bundles:/go/src/githubcom/docker/docker/bundles" -t "docker-dev:master" hack/makesh binary

hack/makesh脚本

上一节提到的make binary中创建的容器启动命令是hack/makesh binary,运行容器中的(docker源码目录下的)hack/makesh脚本,参数为binary。

makesh中根据传入的参数组装后续编译用的flags(BUILDFLAGS),最后根据传入的参数依次调用 hack/make/目录下对应的脚本。例如我们的操作中传入的参数只有一个binary。那么在makesh的最后,会调用hack/make/binary脚本。

hack/make/binary脚本中,就是直接调用go build进行编译了,其中会使用BUILDFLAGS LDFLAGS LDFLAGS_STATIC_DOCKER等编译选项。

如果最终生成的docker二进制文件不在bundles/1100-dev/binary/目录下,那么可能是编译参数BINDDIR设置的不正确,可以在执行make binary时增加BINDDIR参数,例如

make BINDDIR= binary , 将BINDDIR设置为当前目录。

总结

编译步骤总结:

1、编译前在物理机上安装好make、git,并下载好docker代码。下载好ubuntu:1404镜像

2、执行make build 。这步执行完会在物理机上创建出一个docker-dev的镜像。

3、执行make binary 。 这步会使用docker-dev镜像启动一个容器,在容器中编译docker代码。编译完成后在物理机上直接可以看到二进制文件。默认二进制文件在 bundles/1100-dev/binary/目录下

4、docker代码里有很多test,可以使用此套编译环境执行test,例如 make test 。 更多参数可以看Makefile

搭建环境心得:

1、在make build时,使用Dockerfile创建制作镜像,这个镜像有40多层,其中一层失败就会导致整个build过程失败。由于Dockerfile中很多步骤是要连到国外的网站去下载东西,很容易失败。好在docker build有cache机制,如果前面的层成功了,下次重新build时会使用cache跳过,节省了很多时间。所以如果make build中途失败(一般是由于国内连国外的网络原因),只要重新执行make build就会在上次失败的地方继续,多试几次可以成功。

2、如果其他人已经build出了docker-dev镜像,可以把它下载到自己的环境上。这样在自己make build时,会跳过那些已经在本地存在的层,可以节省时间。

3、每一次编译会自动删除掉前面已经生成的二进制文件,所以不用担心二进制文件不是最新的问题。

1 安装 Docker

在开始前,我们首先得确保在Linux主机中已经安装了Docker。这里,我运行的是CentOS 7 主机,我们将运行yum管理器和下面的命令来安装Docker。

yum install docker

systemctl restart dockerservice

2 创建 Dockerfile

现在,Docker守护进程已经在运行中了,我们现在准备创建自己的Firefox Docker容器。我们要创建一个Dockerfile,在其中我们要输入需要的配置来创建一个可以工作的Firefox容器。为了运行 Docker 镜像我们需要使用最新版本的CentOS。要创建 Docker 镜像,我们需要用文本编辑器创建一个名为Dockerfile的文件。

nano Dockerfile

接着,在Dockerfile中添加下面的行并保存。

!/bin/bashFROM centos:7RUN yum install -y firefox 用你自己的 uid /gid 替换下面的0RUN export uid=0 gid=0RUN mkdir -p /home/developerRUN echo ;developer:x:${uid}:${gid}:Developer,,,:/home/developer:/bin/bash; ;; /etc/passwdRUN echo ;developer:x:${uid}:; ;; /etc/groupRUN echo ;developer ALL=(ALL) NOPASSWD: ALL; ;; /etc/sudoersRUN chmod 0440 /etc/sudoersRUN chown ${uid}:${gid} -R /home/developerUSER developerENV HOME /home/developerCMD /usr/bin/firefox

注意:在第四行的配置中,用你自己的用户和组id来替换0。 我们可以用下面的命令在shell或者终端中得到uid和gid。

id $USER

3 构造Docker容器

下面我们就要根据上面的Dockerfile构建一个容器。它会安装firefox浏览器和它需要的包。它接着会设置用户权限并让它可以工作。这里镜像名是firefox,你可以根据你的需要命名。

docker build --rm -t firefox

4 运行Docker容器

现在,如果一切顺利,我们现在可以在运行在CentOS 7镜像中的Docker容器里面运行我们的GUI程序也就是Firefox浏览器了。

docker run -ti --rm -e DISPLAY=$DISPLAY -v /tmp/X11-unix:/tmp/X11-unix firefox

总结

在Docker容器中运行GUI程序是一次很棒的体验,它对你的主机文件系统没有任何的伤害。它完全依赖你的Docker容器。本教程中,我尝试了CentOS 7 Docker中的Firefox。我们可以用这个技术尝试更多的GUI程序。

新版本的docker镜像存储其实是很绕的,各种ID和目录定义较多,不是很直观,本文较详细的分析一下镜像本地存储和在registry存储的格式。测试用的docker版本是20109,存储引擎overlay2。

为了方便分析镜像层级结构,我们基于ubuntu加两个文件,使用 docker build -t myubuntu 创建一个新镜像myubuntu。

镜像元数据存储在 /var/lib/docker/image/overlay2 :

imagedb目录存储的是镜像元数据。

镜像ID是从Image Json文件得到的,即 sha256sum(ImageJson)。

Layer DiffID是没有压缩的对应层的tar文件的sha256sum值。当然,打包和截包文件的适合得保证是可以可重复操作的,不然会导致Layer的DiffID出错(可以使用tar-split保存tar headers)。比如上面例子中ubuntu镜像只有一层,diff_ids列表只有 (history里面的CMD不占磁盘空间) "sha256:350f36b271dee3d47478fbcd72b98fed5bbcc369632f2d115c3cb62d784edaec"。每一层的tar文件我们可以通过 docker save ubuntu -o ubuntutar 得到,解压ubuntutar后可以得到对应的层级的打包后的layertar文件,如下可以验证DiffID的值计算原理。

ChainID用于标识镜像层级栈,它的值由DiffID计算得来。ChainID对应的layer目录是 /var/lib/docker/image/overlay2/layerdb/sha256 ,这下面的目录就是ChainID,其中内容存储了镜像的层级栈关系。比如这一层的parent是什么,以及对应的镜像数据存储目录的cache_id。本身layerdb只是存储layer的元数据信息,并不存储实际镜像数据。

即最底层的ChainID跟DiffID一样,而其他层的ChainID则是通过计算从最底层到这层的Digest得到。

查看Image Json文件可以看到myubuntu相比ubuntu的diff_ids加了两层,imagedb目录下面也多了两个镜像元数据信息文件,对应新增加的两层镜像。

除了之前的350f对应ubuntu,其中7c7e是onetxt那层,而a58f则是twotxt那层。

我们可以看下ChainID目录下的内容,可以看到除了ubuntu的基础层,其他层都有一个文件parent,值就是父层的diff_id,diff是diff_id值,cache-id则是镜像实际存储目录,位于 /var/lib/docker/overlay2/{cache-id} ,size是这一层实际增加文件的大小,tar-splitjsongz是打包这层镜像的配置(参考 https://githubcom/vbatts/tar-split )。

CacheID是一个uuid值,每次都不一样。它对应的目录 /var/lib/docker/overlay2/${CacheID} ,该目录存储了镜像每层文件和下一层的链接等。验证一下:

其中diff目录下面便是这一层的文件。link是对应的diff目录的短链接,lower则是下一层diff目录的短链接。

在layerdb目录的size文件可以看到每一层的镜像大小,但是加起来跟 docker images 显示的大小会有点差距,比如myubuntu镜像每一层计算加起来是 4 + 65593591 + 4 = 65593599 / 1024 / 1024 = 6255MiB ,实际显示是 656MB,这是因为 docker images 里面计算是按 65593599/1000/1000=6559 计算的。

另外,因为镜像每层记录的是相对前一层的文件变化,即便删除了文件和软件包,新镜像大小也不会变小。除非使用 docker export 重新导出一个新镜像。

这个目录存储的是layer diffid和digest的关系,其中digest是镜像仓库里面的目录ID。

其中 v2metadata-by-diff存储了layer diffid对应在镜像仓库的信息,包括digest,sourcerepository等。

diffid-by-digest则是反过来的,文件名是仓库里面的layer digest,内容是layer diffid。因为我们新加的镜像myubuntu并没有push到仓库,所以这个目录下面没有信息。

我们创建一个本地的registry,然后对myubuntu另外打个tag, docker tag myubuntu 127001:5000/myubuntu ,则respositoriesjson会多一条新的记录。此时,distribution目录还没有变化。

当我们执行 docker push 127001:5000/myubuntu ,则会发现distribution的两个目录分别多了两条记录,对应的是myubuntu的 7c7e 和 a58f 两个diffid。此时repositoryjson里面 127001:5000/myubuntu 会多一条带digest的记录,这就是镜像仓库里面对应的digest。其中push时显示的 digest:sha256:bb3c 对应的是registry的manifest文件的digest,下一节分析。

registry中存储的镜像文件是经过gzip压缩,比如myubuntu本地大小是656MB,推到registry压缩后大小约27MB,压缩比还是不错的。registry存储目录如下,主要分为blobs和repositories两个目录。

repositories的一级子目录是镜像名,这里是myubuntu。下面对应三个子目录 _manifests,_layers, _uploads。

blobs存储的内容除了各镜像layer的压缩后的文件,还包括Manifest Json和Image Json文件(未压缩)。

选一个layer的压缩文件data解压看一下内容:

另外需要注意的是, docker pull 时前面显示的值是对应层在registry的压缩文件的digest值,并不是layer diffid,size也是registry存储的压缩文件大小。

1Windows操作系统中安装Docker,如果是Win10以下的系统安装Docker T

2接下来Windows系统需要开启Hyper-V,打开控制面板,找到程序和功能选项,

3接下来进行Docker Desktop的安装,打开安装包进行安装,安装完选择添加

4接下来是启动Docker Desktop,在自动重启的过程中可以看到右下角的图像

5Docker Desktop启动成功后,我们可以使用cmd打开命令行工具

        回顾上期,明哥打包好了一个前端镜像

        第一期内容回顾: https://wwwjianshucom/p/d7718adee07e

     1找到上回我们打包好的镜像,启动!

        docker    run    -it   -p3000:80    my_vue:20     /bin/bash

        -p:端口映射:主机(宿主)端口:容器端口

        我们访问服务器的3000端口实际上是访问容器的80端口

        还有印象的小伙伴应该记得test2是我们上次clone的vue项目

        执行npm run build 得到dist文件夹

        找到nginx配置文件中的server(虚拟主机)    ps:居然还include文件。有点难找。。

        将nginx虚拟主机网页根目录指向list

            listen:虚拟主机的服务端口(默认80)

            root:用于指定虚拟主机的网页根目录(改为指向dist)

            try_files: 按顺序检查文件是否存在,返回第一个找到的文件

    4 重启容器中的nginx(nginx -t排错)

        成功访问到明哥的vue项目!

        docker  run  -it  -p宿主机端口:容器端口   -v宿主机文件夹:容器文件夹    my_vue:20  /bin/bash

        我们-v绑定后,在本机中修改文件,容器中文件也会对应修改。

        完结撒花!

欢迎分享,转载请注明来源:浪漫分享网

原文地址:https://hunlipic.com/meirong/11513948.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-12-02
下一篇2023-12-02

发表评论

登录后才能评论

评论列表(0条)

    保存