Docker学习笔记之Docker的Build 原理

网友投稿 268 2022-10-20

Docker学习笔记之Docker的Build 原理

0x00 概述

使用 Docker 时,最常用的命令无非是 ​​docker container​​​ 和 ​​docker image​​​ 相关的子命令,当然最初没有管理类命令(或者说分组)的时候,最常使用的命令也无非是 ​​docker run​​​ ​​docker commit​​​ ​​docker build​​​ 和 ​​docker images​​ 这些。

今天来聊一下和 Docker 中核心概念 ​​image​​​ 相关的重要命令, ​​docker build​​​ 或者说 ​​docker image build​​​ 为了简便起见,下文的命令全部使用 ​​docker build​​ 。

0x01 Docker Image

先简单介绍下 Docker Image, 通常情况下我们将其称之为镜像,镜像是由多个层组成的文件,这些层用于在容器内执行代码(命令)等。每个镜像基本上都是根据应用程序完整的可执行版本进行构建的,并且需要注意的是,它会依赖于主机的系统内核。当用户在运行镜像时,这将会创建一个或者多个容器实例。

0x02 Dockerd

Dockerd 是 Docker 的服务端,默认情况下提供 Unix Domain Socket 连接,当然也可以监听某个端口,用于对外提供服务。 所以有时候,我们也可以使用服务器上的 Docker daemon 来提供服务,以加快构建速度及解决一些网络问题之类的。

好的,基础概念了解了, 那我们开始进入正题。

0x03 使用 Dockerfile

我们知道构建镜像的方法有多种,本文中我们只介绍使用 Dockerfile 通过 ​​docker build​​ 的方式构建镜像。

为了简便,我们以一个简单的 Dockerfile 开始。构建一个容器内使用的 kubectl 工具 (当然选择它的原因在于 kubectl 足够大,并不考虑可用性,这个稍后解释)

FROM scratchLABEL maintainer='Jintao Zhang 'ADD kubectl /kubectlENTRYPOINT [ "/kubectl" ]

Dockerfile 足够简单,只是将 kubectl 的二进制文件拷贝进去,并将 Entrypoint 设置为 kubectl 。

0x04 Dockerd in Docker

我个人一般为了避免环境的污染,大多数的事情都在容器内完成。包括 dockerd 我也启在容器内。其中的原理不再介绍,可以参考我之前的文章或分享。使用起来很简单:

docker run --privileged -d -P docker:stable-dind

注意这里使用了 ​​-P​​ 所以本地会随机映射一个端口,当然你也可以直接指定映射到容器内的 2375 端口。

(Tao) ➜ build git:(master) docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESb56f6483614d docker:stable-dind "dockerd-entrypoint.…" 9 hours ago Up 9 hours 0.0.0.0:32769->2375/tcp trusting_babbage

0x05 构建

我们直接使用启动在容器内的 dockerd 进行构建,通过上面的 ​​docker ps​​ 命令可以看到是映射到了本地的 32769 端口。所以我们使用以下命令进行构建:

(Tao) ➜ kubectl git:(master) docker -H 0.0.0.0:32769 images REPOSITORY TAG IMAGE ID CREATED SIZE (Tao) ➜ kubectl git:(master) docker -H 0.0.0.0:32769 build -t local/kubectl . Sending build context to Docker daemon 55.09MBStep 1/4 : FROM scratch --->Step 2/4 : LABEL maintainer='Jintao Zhang ' ---> Running in ebcf44071bf0Removing intermediate container ebcf44071bf0 ---> eb4ea1725ff2Step 3/4 : ADD kubectl /kubectl ---> 1aad06c4dbb4Step 4/4 : ENTRYPOINT [ "/kubectl" ] ---> Running in 2fc78fe974e3Removing intermediate container 2fc78fe974e3 ---> 457802d4bf3eSuccessfully built 457802d4bf3eSuccessfully tagged local/kubectl:latest(Tao) ➜ kubectl git:(master) docker -H 0.0.0.0:32769 images REPOSITORY TAG IMAGE ID CREATED SIZElocal/kubectl latest 457802d4bf3e 3 seconds ago 55.1MB

0x06 深入原理-Dockerd 服务

在本文一开始,我已经提过 Dockerd 是 Docker 的后端服务,通过上面的

docker -H 0.0.0.0:32769 images

这条命令可以看到我们通过 ​​-H​​ 指定了本地 32679 端口的 dockerd 服务,这其实是个 HTTP 服务,我们来验证下。

(Tao) ➜ kubectl git:(master) curl -i 0.0.0.0:32769/_pingHTTP/1.1 200 OKApi-Version: 1.37Docker-Experimental: falseOstype: linuxServer: Docker/18.03.1-ce (linux)Date: Tue, 04 Sep 2018 17:20:51 GMTContent-Length: 2Content-Type: text/plain; charset=utf-8OK%

可以看到几条关键的信息 Api-Version: 1.37 这个表明了当前使用的 API 版本,本文的内容也是以 1.37 为例进行介绍,这是当前的稳定版本。我们也可以通过 ​​docker version​​ 进行查看。

(Tao) ➜ kubectl git:(master) docker -H 0.0.0.0:32769 version Client: Version: 18.06.0-ce API version: 1.37 (downgraded from 1.38) Go version: go1.10.3 Git commit: 0ffa825 Built: Wed Jul 18 19:11:45 2018 OS/Arch: linux/amd64 Experimental: falseServer: Engine: Version: 18.03.1-ce API version: 1.37 (minimum version 1.12) Go version: go1.9.5 Git commit: 9ee9f40 Built: Thu Apr 26 07:23:03 2018 OS/Arch: linux/amd64 Experimental: false

可以看到我本地在用的 docker cli 版本较高,当连接到低版本的 dockerd 时,API 版本降级至与 dockerd 版本保持一致。

当然,你可能会问,如果是 dockerd 版本高会如何呢?其实我日常中的开发环境就是这样,大多数 API 都没什么影响, 不过这并不是今天的重点。

root@bdcdac73ee20:/# docker versionClient: Version: 17.06.0-ce API version: 1.30 Go version: go1.8.3 Git commit: 02c1d87 Built: Fri Jun 23 21:15:15 2017 OS/Arch: linux/amd64Server: Version: dev API version: 1.39 (minimum version 1.12) Go version: go1.10.3 Git commit: e8cc5a0b3 Built: Tue Sep 4 10:00:36 2018 OS/Arch: linux/amd64 Experimental: false

0x07 build context

回到我们上面的构建过程中。我们可以看到日志内容的第一行:

...Sending build context to Docker daemon 55.09MB

从这条日志,我们可以得到两个信息:

构建的过程是将 build context 发送给 dockerd , 实际的构建压力在 dockerd 上发送了 55.09 MB

第一条结论,我们在上一小节已经讨论过了,我们来重点看下第二条结论。

(Tao) ➜ kubectl git:(master) ls -al 总用量 53808drwxrwxr-x. 2 tao tao 4096 9月 5 01:00 .drwxrwxr-x. 3 tao tao 4096 9月 5 00:57 ..-rw-rw-r--. 1 tao tao 109 9月 5 01:00 Dockerfile-rwxrwxr-x. 1 tao tao 55084063 9月 5 00:53 kubectl(Tao) ➜ kubectl git:(master) du -sh .53M .(Tao) ➜ kubectl git:(master) du -sh kubectl Dockerfile 53M kubectl4.0K Dockerfile

按照我们 Dockerfile 的内容,我们需要将 kubectl 的二进制包放入镜像内,所以 build context 虽然比二进制文件多出来 2M 左右的大小你也不会很意外。

但我这里做了另一个例子,不多赘述,代码可以在我的 [GitHub]() 中找到。这里贴出来结果:

(Tao) ➜ text git:(master) ls -al 总用量 16 drwxrwxr-x. 2 tao tao 4096 9月 5 01:45 . drwxrwxr-x. 4 tao tao 4096 9月 5 01:44 .. -rw-rw-r--. 1 tao tao 77 9月 5 01:45 Dockerfile -rw-rw-r--. 1 tao tao 61 9月 5 01:45 file (Tao) ➜ text git:(master) du -b Dockerfile file77 Dockerfile61 file (Tao) ➜ text git:(master) docker -H 0.0.0.0:32769 build --no-cache=true -t local/file . Sending build context to Docker daemon 3.072kB...

相信你看到这个结果已经明白我想表达的意思,我们继续探索下这个过程。

0x08 /build 请求

前面我们已经说过,这就是个普通的 HTTP 请求,所以我们当然可以直接抓包来看看到底发生了什么?

很简单,通过 dockerd 的地址,使用 ​​POST​​​ 方法,访问 ​​/build​​​ 接口, 当然实际情况是会增加前缀,即我在上面提到的版本号,在目前的环境中使用的是 ​​/v1.37/build​​ 这个接口。

而这个请求携带了一些很有用的参数,和头信息。这里我来简单说下:

Header

build 请求的头部,主要有以下两个

​​Content-Type​​​ 默认值为​​application/x-tar​​,表明自己是一个归档。​​X-Registry-Config​​ 这个头部信息中包含着 registry 的地址及认证信息,并且以 base64 进行编码。对 docker 熟悉的朋友或者看过我之前文章的朋友应该知道, Docker cli 在 login 成功后,会将认证信息保存至本地,密码做 base64 保存。而 build 的时候则会将此信息再次 base64 进行编码。通过这里也可以看出来,在使用远端 Dockerd 的时候, 应该尽量配置 TLS 以防止中间人攻击,造成密码泄漏等情况。

Parameters

请求参数中,列几个比较有意义的:

​​t​​​ 这其实就是我们​​docker build -t​​​ 时候指定的参数,并且,我们可以同时指定多个​​-t​​ 同时构建多个不同名称的镜像。​​memory​​​​cpusetcpus​​ 这些主要用于资源限制​​buildargs​​​ 如果想要了解这个参数,可以回忆下 Dockerfile 中的​​ARG​​ 指令的用法

当然,我们想要探索的过程其实重点就在于请求头部了, 整个请求的输入流,必须是一个 ​​tar​​​ 压缩包,并且支持 ​​identity​​​ (不压缩), ​​gzip​​​, ​​bzip2​​​, ​​xz​​ 等压缩算法。

0x09 实现

我们来看下基本的实现:

func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) { query, err := cli.imageBuildOptionsToQuery(options) if err != nil { return types.ImageBuildResponse{}, err } headers := buf, err := json.Marshal(options.AuthConfigs) if err != nil { return types.ImageBuildResponse{}, err } headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf)) headers.Set("Content-Type", "application/x-tar") serverResp, err := cli.postRaw(ctx, "/build", query, buildContext, headers) if err != nil { return types.ImageBuildResponse{}, err } osType := getDockerOS(serverResp.header.Get("Server")) return types.ImageBuildResponse{ Body: serverResp.body, OSType: osType, }, nil}

0x0A 总结

这篇主要内容集中在 docker build 的过程及其原理上,为什么首先要写这篇,主要是因为镜像和我们息息相关, 并且也是我们使用的第一步。而很多情况下,推进业务容器化也都要面临着性能优化及其他规范之类的。

​​参考​​

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Spring Boot在开发过程中常用IDEA插件
下一篇:Docker学习笔记之搭建 Java Web 项目运行环境
相关文章

 发表评论

暂时没有评论,来抢沙发吧~