dockerfile最佳实践,dockerfile 教程
chanong
|1 之前写过一篇文章,分享一些关于如何优化Dickerfile构建镜像的笔记。如果你不太理解,朋友可以帮你纠正。每个人只有一项真正的责任。这是关于寻找你自己。并将其全心全意地记在心里,一生一世,永不止步。所有其他道路都是不完整的,人性的逃亡,懦弱地回归大众的理想,随波逐流,内心的恐惧—— Hermann Hesse 《德米安》
2 简介Docker 提供了两种常用的定制和构建新镜像的方法。
使用正在运行的容器构建新镜像使用Dockerfile 文件在基础镜像的基础上构建新镜像无论使用哪种方法,定制镜像的原则都是一样的镜像层次结构通过设计创建读/写层并更改配置。重新包装。
在这里,我将与朋友们分享如何使用Dockerfile 优化构建自定义镜像。所谓优化也可以理解为一种比较好的构建方法。这是有关如何构建第一个图像的快速指南。
通过正在运行的容器构建新映像通常涉及对正在运行的映像执行一些预构建的操作。例如,如果您的内网环境没有依赖库,则没有直接的方法来拉取所需的依赖项。通过检查网络环境中相应的依赖关系来创建依赖基础映像。
比如我想在内网使用Python镜像,但是内网环境没有pip源,所以创建镜像的唯一方法就是下载外部环境对应的包。
运行容器并构建新镜像[root@vms100.liruilongs.github.io]-[~]$docker run -d python python -m http.server 33333009033ff4c0155f81647b857c0bf8975ee750a13d7aa2584638af032aa 请输入fa758 b 创建容器打包那个下载相关依赖并生成镜像导出
[root@vms100.liruilongs.github.io]-[~]$docker commit bcdd82ca5b48 my-python:latestsha256:cb7c9965c541dfc794f78eb06ae1c4af0c77bb87c92e5e6e768c7770eb61a 5 bb [root@vms100.liruilongs.github.io]-[~]$docker保存my -python:latest -o ./my-python.tar 操作起来有点困难,所以使用Dockerfile 可能会更方便。
[root@vms100.liruilongs.github.io]-[~]$docker build - FROM python RUN python -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple RUN python -m pip install psycopg2 将构建上下文发送到EOFSDocker 守护进程2.048kBStep 1/3 : FROM python --- a5d7930b60ccStep 2/3 : RUN python -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple --- 在88140ad45551 上运行写入/root/.config/pip/pip.conf 删除中间容器88140ad45551 --- df41fddd2cd2 步骤3/3 : RUN python -m pip install psycopg2 --- 在1eddfbf7fa58 上运行搜索索引: https://pypi.tuna.tsinghua.edu.cn/simple 收集psycopg2 https://pypi.tuna.tsinghua.edu.cn/packages/89/d6/cd8c46417e0f7a16b4b0fc321f4ab676a5 9250d08fce5b64921897fb07cc/psycopg2-2 .9.5.tar .gz (384 kB)构建收集的轮子packages: psycopg2 构建psycopg2 的轮子(setup.py): 开始. 14/44f32ab3b3f40f2e9a1a9ab8281a40ff4a911a930121c928b1成功构建psycopg2Collected 包安装: psycopg2.删除中间容器1edd fbf7fa58 --- 8791cb1dc692S 构建成功8 791cb1dc692[root@vms100 .liruilongs.github.io] -[ ~] $ 我忘记添加标签了。在这里手动输入。可以在构建期间使用-t 命令指定。
[root@vms100.liruilongs.github.io]-[~]$docker 标签8791cb1dc692 my-python:latest[root@vms100.liruilongs.github.io]-[~]$docker 镜像my-pythonREPOSITORY TAGIMAGE IDCRETED SIZEmy-python 最新8791cb1dc692 8 分钟前927MBDockerfile 查看构建自定义映像的一般步骤
FROM:基础镜像RUN:创建镜像时可以运行多个命令。每个命令都有一层。 ADD:复制文件到镜像并自动解压(文件类型为: tar.gz或tar.bz2) COPY:复制文件到镜像。不要解压缩。 MAINTAINER:图像创建者信息EXPOSE:打开端口。 ENV:设置变量。 WORKDIR:定义容器的默认工作目录。 CMD:容器启动时执行的命令只能包含一个CMD.ENTRYPOINT:函数,例如: CMD指令被容器用来指定一个默认的可执行文件,使容器看起来是一个单独的可执行程序。
如果在docker run 命令中声明了参数,Docker 守护进程将忽略CMD 命令。与CMD 不同,ENTRYPOINT 启动的程序不会被docker run 命令行上指定的参数覆盖,这些命令行参数会作为参数传递给ENTRYPOINT 指定的程序。但是,docker run命令的--entrypoint可选参数可以覆盖ENTRYPOINT指令中指定的程序Dockfile。如果未使用CMD 指定启动命令,则继承上一个映像中的默认启动命令。启动CMD容器只有一个命令:以及根据Dockerfile生成新镜像的命令,build创建一个新镜像; -t 指定新镜像的名称和标签; 指定所在目录Dockerfile 文件驻留
docker build -t imagename:latest 容器和镜像之间的主要区别在于顶层可写层。所有添加新数据或修改现有数据的对容器的写入都存储在该可写层中。当删除容器时,可写层也会被删除。基础图像保持不变。
这里使用的是写时复制技术(COW),对于开发伙伴来说可以结合享元设计模式来理解,对于运维伙伴来说可以结合Openstack组件Glance来理解。原则。
通俗地说,就是修改时,将数据复制到容器层进行修改。添加时直接添加到容器层,移除时遮挡图像层。
Docker 通过阅读您提供的说明自动构建镜像。每个都遵循特定的格式和指令集在容器映像中创建一个层。层是堆叠在一起的,每一层与前一层相比都有增量变化。
这里我们以官方镜像redis:7为例,看看标准的Dockerfile是如何编写的。您可以看到该图像是由16 层构建的。
[root@vms107.liruilongs.github.io]-[/etc/systemd]$docker History -- human=true redis:7 镜像创建大小创建带注释19c51d4327cf 6周前/bin/sh -c #(nop ) CMD ['redis-server'] 0B 6 周前/bin/sh -c #(nop) EXPOSE 6379 0B 6 周前/bin/sh -c #(nop) ENTRYPOINT ['docker-entry. 0B 6 周前/bin /sh -c #(nop) COPY file:e873a0e3c13001b5… 661B 6 周前/bin/sh -c #(nop) WORKDIR /data 0B 6 周前/bin/sh -c #(nop) VOLUME [/data] 0B 6 周前/bin/sh -c mkdir /data chown redis:redis … 0B 6 周前/bin/sh -c set -eux; SavedAptMark='$(apt-m… 32MB 6 周前/bin/sh -c # ( nop ) ENV REDIS_DOWNLOAD_SHA=06… 0B 6 周前/bin/sh -c #(nop) ENV REDIS_DOWNLOAD_URL=ht… 0B 6 周前/bin/sh -c #(nop) ENV REDIS_VERSION=7.0.8 0B 7 周上一页/bin /sh -c set -eux; SavedAptMark='$(apt-ma… 4.13MB 7 周前/bin/sh -c #(nop) ENV GOSU_VERSION=1.14 0B 7 周前/bin/sh -c groupadd - r - g 999 redis 用户… 329kB 7周前/bin/sh -c #(nop) CMD ['bash'] 0B 7周前/bin/sh -c #(nop) ADD file:e2398d0bf516084b2… 80.5MB [ root @ vms107.liruilongs.github.io]-[/etc/systemd]$docker History -- human=true redis:7 | wc -l17 在镜像构建中包含两个Dockerfile 文件
基础形象建设
FROMScratchADD rootfs.tar.xz /CMD ['bash']reids 构建镜像
当您运行此映像来创建容器时,它实际上在其下方的层之上添加了一个新的可写层(容器层)。对正在运行的容器所做的所有更改(写入新文件、修改现有文件、删除文件等)都会写入此可写容器层。
如您所见,上面的Dockerfile 相当大,但我将总结一些创建它时需要记住的事项。
3Dockerfiles被编写并优化为使用小型基础镜像,在上面的Dockerfile中,基础镜像是FROM debian:bullseye-slim、FROMScratch。
Scratch 并不常用,因为它是一个空图像,但用于构建最基本的图像。 debian:bullseye-slim 是Debian 系统bullseye 版本的精简版镜像。您可以看到使用的图像非常小。较小的镜像可以让您更快地构建、推送和拉取镜像。它通常更安全,因为它仅包含运行应用程序所需的库和系统依赖项。尤其是在CI/CD 等管道中,大型基础镜像在每个链路上都会消耗时间,导致管道时间非常长。镜像之间的区别主要在于底层操作系统
图片选择类型官方图片:官方图片(标准图片)是通常由官方控制的图片,是正确的选择,但可能不是最好的选择。该镜像基于最新稳定的Debian操作系统版本,使用上述Dockerfile构建的镜像是redis:7的官方镜像。
Debian(bullseye/buster/stretch/jessie):各种Debian Linux 发行版镜像,jessie(8.0)、stretch(9.0) 是旧版本,buster(10.0)、bullseye(11.0) 是新版本
修身:修身版。通常,会安装运行特定工具所需的最少软件包。
alpine:专门为容器内部使用而构建的操作系统,基于Alpine Linux 项目。与Debian 相比,Alpine 要小得多,但有时区和兼容性问题需要考虑。
Scratch: 显式空图像,尤其是“从头开始”构建图像时。
选择最小基础镜像时,尽量避免安装不必要的软件包。
当你运行指令链时,可以清楚地看到上面Dockerfile中的RUN指令相当长。
这是因为每条指令都会创建一个可缓存单元并构建一个新的中间图像层。因此,您可以通过将所有命令链接在一起来避免过多的层。另外,避免将太多可缓存的RUN 命令链接在一起。这会创建一个大的缓存并最终导致缓存爆裂。
RUN set -eux; \ \savedAptMark='$(apt-mark showmanual)'; \ apt-get update; \ apt-get install -y --no-install-recommends \ ca-certificates \ wget \ \ dpkg-dev \ gcc \ libc6-dev \ libssl-dev \ make \ ; \ rm -rf /var/lib/apt/lists/*; \ \ wget -O redis.tar.gz '$REDIS_DOWNLOAD_URL'; \ ..修改指令放在最后Dockerfile 描述要多次构建,每层往往需要时间来构建,但是为了加快后续构建的速度当Docker 在构建一层时自动缓存每一层时,缓存如果对应层的指令和之前的指令没有改变就直接使用。当修改某个步骤的相应指令时,缓存不仅对于该特定步骤而且对于所有后续步骤都无效。
因此,始终将最常更改的指令放在最后。提高构建速度。
FROM python:3.9-slimWORKDIR /appCOPYrequirements.txt .RUN pip install -r /requirements.txtCOPY app.py . 优先使用数组而不是字符串语法最终的进程启动命令可以用两种不同的方式编写。
数组:ENTRYPOINT ['python','-m','http.server','33333'] 字符串:ENTRYPOINT 'python -m http.server 33333' 首选数组格式。这是因为使用字符串格式会导致Docker 使用bash 运行进程,而bash 无法正确处理信号。大多数shell 不处理子进程中的信号,因此如果使用shell 格式,CTRL-C(生成SIGTERM)可能无法停止子进程。
如果您有多个步骤在COPY 而不是ADD 上下文中使用不同的文件,请单独复制它们,而不是一次全部复制。这只会使每个步骤的构建缓存无效并强制该步骤重新运行,特别是在所需文件发生更改的情况下。
COPYrequirements.txt /tmp/RUN pip install --requirement /tmp/requirements.txtCOPY . /tmp/使用.dockerignore。 dockerignore 文件类似于git 项目中的.gitignore。不同之处在于.dockerignore 适用于构建docker 镜像。它位于docker 构建上下文的根目录中,用于忽略不需要包含在映像中的文件。
.dockerignore文件的写法与.gitignore类似,支持正则和通配符,具体规则如下:
每行一个条目;以# 开头的行被注释掉;空行被忽略;构建上下文路径是所有文件的根路径;gitscriptstatic!README*.md 是从stdin 构建的stdin Docker 引擎通过管道本地构建镜像或远程构建上下文Dockerfile
当Dockerfile 不需要将文件复制到映像(COPY/ADD 将失败)时,省略构建上下文非常有用,并且由于没有文件发送到Docker 守护进程,因此可以加快构建速度。适合简单的图像构建
[root@vms107.liruilongs.github.io]-[/etc/systemd]$docker build - FROM Busybox RUN echo 'hello world' 将构建上下文发送到EOFSDocker 守护进程2.048kBStep 1/2 : FROM Busyboxlatest: 从中拉取Library/busybox5cc84ad355aa: Pull CompleteDigest: sha256:5acba83a746c7608ed544dc1533b87c737a0b0fb730301639a0179f9344b1678Status: Busybox : 下载最新的新镜像--- beae1 73ccac6 Step 2/2 : RUN echo 'hello world' --- 在c56fb8343c72hello world 上运行删除中间容器c56fb8343c72 -- - 95bc7e44435395bc7e444353 构建成功[root @vms107.liruilongs.github.io]-[/etc/systemd]$ 利用多阶段构建多阶段构建很难减少数量通过利用构建缓存,您可以显着减小最终映像的大小。中间层和文件。例如,让我们看一下:
FROM golang:1.18-alpine AS prebuild# 安装project所需工具RUN go get github.com/golang/dep/cmd/depCOPY Gopkg.lock Gopkg.toml /go/src/project/WORKDIR /go/src/project/RUN go build -o /bin/project# 这将创建一个单层图像。 FROMScratchCOPY --from=prebuild /bin/project /bin/projectENTRYPOINT ['/bin/project'] 可以使用多个语句。每个指令都可以使用不同的基础,并且每个指令都会启动构建的新阶段。您可以有选择地将工件从一个阶段复制到另一个阶段,将您不想要的所有内容留在最终图像中。
#syntax=docker/dockerfile:1.4FROM … AS build1COPY from=app1 . /srcFROM … AS build2COPY from=app2 . /srcFROM …COPY from=build1 /out/app1 /bin/COPY from=build2 /out/app2 /bin/4 博客文章部分内容引用,文中引用链接内容版权归原作者所有,如有侵权,敬请告知。
https://www.docker.com/
https://docs.docker.com/engine/reference/builder/
https://www.docker.com/blog/dockerfiles-now-support-multiple-build-contexts/
https://blog.devgenius.io/devops-in-k8s-write-dockerfile-efficiently-37eaedf87163
2018-2023 liruilonger@gmail.com,保留所有权利。署名-非商业性-相同方式共享(CC BY-NC-SA 4.0)








