Docker 镜像,已经是我们这些 IT 工程师工作中,不可或缺的一部分,可以说是我们工作的基础,但是 docker 镜像本质上,到底是什么东西?我们生成一个 docker 镜像到底做了什么操作呢?01
Dockerfile生成 docker 镜像如想要生成一个我们自己的 docker 镜像,可以先编写自己的 dockerfile 文件,然后基于此文件使用 docker build 生成镜像,那这个过程中到底发生了什么呢?
发送 Build context在执行 docker build 命令时,会在末尾加上一个 “.”,这个点就是 docker 的构建上下文,在 linux 下 “.”即代表当前目录;docker 构建镜像需要使用到构建上下文里的文件,所以需要将 build context 下的文件遍历发送给 docker 守护进程,这样我们就可以在构建开始的日志信息中,看到如下信息:
Sending build context to Docker daemon xxx.xx MB 这条信息是在告知需要发送给 docker daemon 的文件有多少 MB 大小校验 dockerfile 命令并执行。
Docker daemon 在执行 dockerfile 的命令之前都会先预校验一下命令是不是符合语法,不符合的将返回一个错误;命令都没问题后 docker daemon 会逐条执行命令,执行的过程如下:在原来的镜像上启动一个容器,在容器内执行命令,执行完写操作,然后 docker daemon 执行一次 commit,提交一个新的镜像,这里就产生了新的一层镜像,紧接着继续执行下面的命令,直至执行完成生成镜像。
02Docker镜像的结构按照 OCI (Open Container Initiative) 规范中的容器镜像标准,Docker 镜像其实本质是文件目录,包括 index 索引文件 (可选) 、配置文件、清单文件、一组文件系统层。
配置文件里包含环境变量、挂载卷、暴露的端口等;清单文件中列出了构成镜像的层的信息我们可以通过 docker manifest inspect 命令来查看镜像的配置文件、清单文件里的信息下面我们就来详细看一看:
(1) index 索引文件以下是查看一个 openjdk 镜像元数据信息的示例:docker manifest inspect openjdk:8-alpine
以上 JSON 文件就是一个镜像的 index 索引文件,这个文件的作用是标记不同的平台该使用哪个镜像 manifest 文件,其中的 digest 字段就是 openjdk 镜像的 manifest 文件 ID,我们可以根据这个文件 ID (文件指纹) 来查看manifest 文件信息。
(2) 清单文件以下是查看清单文件的示例:docker manifest inspect openjdk@sha256:44b3cea369c947527e266275cee85c71a81f20fc5076f6ebb5a13f19015dce71
从上图可以看出 manifest 文件中由配置文件和很多的层信息组成,每个层信息包含类型、文件ID (文件指纹) 、大小信息拉取镜像时先获得 manifest 文件,再根据此文件中的元数据信息,去获取相应的配置文件、层文件,要注意的是获取层文件时,会对比文件指纹。
但如果本地已存在层文件,就不需要再拉取了,直接使用本地缓存,推送镜像时也是如此,如果在 docker registry 中已存在了层数据,不需再次推送,只会推送改变了的层(3) 配置文件配置文件的位置在以下
目录:/var/lib/docker/image/overlay2/imagedb/content/sha256 而 manifest 文件中的 config.digest 字段就是配置文件的名称,查看文件的内容:
配置文件中配置了容器运行时,需要的环境变量、入口命令、挂载卷等信息至此,我们已经了解了docker 镜像的分层结构,那分层结构有哪些好处呢?主要好处在于资源可共享、可复用,当多个镜像间存在相同的层时可直接复用,无需再拉取,。
极大地节约了存储、网络带宽资源03Dockerfile最佳实践通过以上内容,我们可以知道,一个 docker 镜像产生和dockerfile 里的命令密不可分,因此 dockerfile 不同的写法会影响到镜像的大小、构建的速度等。
Docker 官方有 dockerfile 编写的最佳实践 (网址如下)https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
它建议从以下几点优化:清晰当前的构建上下文,只包含需要的资源,排除其余多余的资源,我们可以使用 docker build -f xx/Dockerfile xx/context 来指定 Dockerfile 和构建上下文。
使用.dockerignore文件排除不需要的文件,只保留需要的,保证构建上下文尽可能的小使用多阶段构建,由于镜像是在构建过程的最后阶段构建生成的,因此我们可以将初始化构建镜像所需要的环境、依赖项等放在开始阶段,最后阶段只需要获取构建产物、添加入口命令,这样就得到了一个单层的镜像。
尽量减少镜像的层数,其中 RUN、COPY、ADD 命令会创建新的层,多个 RUN 命令时,可以检查是否可以合并成一条命令以上是一些常用的优化配置,更多详细的配置可查询 docker 官网上的 dockerfile 最佳实践。
04如何提高构建 docker 镜像的速度到此,我们已梳理了一个 docker 镜像产生的过程、一个docker镜像的结构、以及如何编写一个较好的 dockerfile那如何提高 docker 镜像构建的速度呢,我们可以从以下几点出发:。
1. 构建上下文构建上下文是我们构建镜像的基础,最好只包含 docker build 构建过程需要的资源,这里可以参考 dockerfile 的最佳实践做出优化2. Base image在构建镜像过程中,我们自己的程序其实是很小的,但程序运行的环境占了很大的空间,所以我们可以选择比较小巧的底包,这样可以大大减小镜像的大小,从而缩短镜像构建过程中拉取和推送的时间;我们也可以选择更小巧的 Linux 发行版,比如 Alpine,或者 Google 的 Distroless 镜像。
Alpine 是一个小巧、安全、简单、功能完备的 linux 发行版,大小只有几 M,非常适合用于制作镜像,现在很多官方镜像都已经有 Alpine 的版本了,使用 Alpine 版本的 base image 可以极大减小构建出来的镜像大小。
Distroless 是 Google 的一个镜像构建文件,专门在安全漏洞方面做了优化,只包含应用程序及其运行时所需的依赖,不包含软件包管理器、shell 和其他 GNU 二进制文件这些几乎用不到的功能,大大降低了被攻击的风险,并减少了漏洞,所以 Distroless 较 Alpine 更加的安全,不过 gcr.io 对国内用户稍微不是很友好,镜像拉取不了。
3. 其他优秀的镜像分层技术镜像的结构如果是满足 OCI 的标准规范的话,就可以在 OCI 的运行时中运行;换句话说只要我们能构建出满足 OCI 标准的镜像文件目录,就是一个标准的 docker 镜像;现在也有了很多优秀的镜像分层技术,他们满足 OCI 标准,并且解决了 docker 的一些缺点;合理的分层,可以使构建过程使用上大量的缓存,无需重复拉取,从而加快镜像的构建,下面我们看看一些比较流行的镜像分层技术:
PodmanPodman 提供与 Docker 非常相似的功能可以说podman就是为了替代 docker 的,podman 解决 docker 的一些痛点,比如 docker daemon 是一个守护进程、并需要 root 权限,但 podman 它不需要在系统上运行任何守护进程,并且还可以在没有 root 权限的情况下运行。
Podman 可以管理和运行任何符合 OCI 规范的容器和容器镜像;podman 的命令和 docker 的命令,基本上是相同的,只需要将 docker 换为 podman,即可兼容 docker 的基本常用命令,podman 也可以根据用户提供的 dockerfile 文件构建镜像,不过一般不推荐使用 podman build 构建镜像,因为 podman 构建速度超慢,并且默认情况下使用 vfs 存储驱动程序会耗尽大量磁盘空间,一般使用 podman 的构建工具 Buildah。
BuildahBuildah 是一个专注于构建 OCI 容器镜像的工具,Buildah 构建速度非常快并使用覆盖存储驱动程序,可以节约大量的空间Buildah 也支持使用 dockerfile 构建镜像:。
Buildah 使用 dockerfile 构建时是在构建的最后一步进行的 commit,这样构建的镜像就只有一层,无法使用到缓存,也就是要做一些重复的拉取工作;如果使用 buildah 的原生命令构建镜像的话,分层会变得更加的灵活,我们可以自定义缓存点,在我们认为需要缓存的地方加上 commit 命令就能提交一层新的镜像。
Buildah 的原生命令就是一个 bash 脚本,下面展示了 buildah 构建的一个简单脚本:
当不使用 Dockerfile 而是使用 Buildah 命令构建镜像时,我们可以使用 commit 命令来随时决定提交缓存在上例中,所有的变更是一起提交的;但其实可以增加中间提交,这样就能自由标记 缓存点 (cache point):例如,我们可以在安装完一些构建需要的工具后就提交一次,这样下一次构建可以直接使用这个缓存。
Buildah mount 命令可以将容器的根目录挂载到主机的一个挂载点上;这使得我们可以使用主机上的工具进行构建和安装软件,不用将这些构建工具打包到容器镜像本身中Google 的 jib 构建工具Jib 是 Google 的一个 java 构建镜像的工具,将原来一层docekr 镜像拆分得更细,在原来我们 COPY springboot 项目的 jar 文件构建成一个 java 的镜像,但是 springboot 的 jar 压缩文件中,有很多的文件在我们每次编译时,基本上是不会改变的,比如说第三方的依赖 jar、以及资源文件夹、配置文件等,改变代码后编译出的文件中只有 class 文件是不一样的,所以 jib 将 springboot 项目分为三层,分别为第三方依赖 lib、资源文件 resources、字节码 class 文件,体现在 dockerfile 文件里就如下:
前两层在大多数情况下,都是可以复用的,仅仅需要构建最后一层即可,越是大型的项目越能体现出 jib 的优势。下面我们看一下它们的优缺点:
镜像分层技术现在已经是很普遍的存在,除了上述提到的外,还有谷歌的 Kaniko、Buildkit、Source-To-Image (S2I)、Bazel 等,它们都有各自的一些特性,使用于一些特定的场景,我们可以视情况选择使用。
项目中的实践 – jib目前「DaoCloud 道客」有些项目中已使用 jib 作为 JAVA项目的镜像构建工具,明显能感觉到对项目开展效率的提升:1. 更方便地构建、调试Jib 让我们可以在仅有 JAVA 的环境下完成镜像构建,可以在本地构建镜像到远程仓库,而不触发 CICD,这样便于更快速地进行线上程序联调。
2. 配置更简洁不需要写 dockerfile,更不需要考虑dockerfile的最佳实践,只需要简单的几行配置即可3. 构建速度更快即使进行 CICD 构建,jib 对镜像的特殊分层,也会让构建过程中使用到更多的缓存,受低带宽的影响也会更小。
总结:得益于 OCI 规范的存在,只要构建出的镜像遵守 OCI 规范,就可以交给遵守 OCI 规范的容器运行时去运行,这样就使得容器技术的发展,更加多元化,我们也不必再拘泥于一款工具使用,可以按需选择那些能提高我们工作效率的工具。
#daocloud#举报/反馈
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别
丞旭猿论坛
暂无评论内容