高效率编写Dockerfile需要绕过的一些坑

前言

在日常的开发和运维中,我们时长会使用Dockerfile脚本制作镜像。
其实编写一个Dockerfile文件用到的标签并不会太多,但是不同的Dockerfile在制作后产生的镜像大小是不尽相同的,这篇文章就来梳理一下,编写脚本过程中,容易犯的错误和躺的坑。

一、拉取最新的镜像

在从镜像仓库拉取镜像时,不指定任何版本的情况下,默认会拉取最新(latest)的版本。这在我们构建集群和复用时会造成困扰,也就是说只要组件的镜像仓库还在更新,不同时间拉取到的镜像版本可能是不一样的,那样简直太混乱了,所以,在拉取镜像时,记得加上具体的版本号。

file

二、在构建时使用外部脚本命令

很多人在构建镜像时,没有明确的区分出在宿主机上和在容器内执行的区别。
构建映像时,Docker读取Dockerfile中的命令并从中创建镜像,不应该在文件里使用一些宿主机上的命令,因为这些环境在容器内,可能压根就没有。如:

# !!! 宿主机上有python环境,但容器内部可能没有,这是两套环境 !!!

RUN python start.py 

三、把容易变化的命令放前面

在保证逻辑执行正确的情况下,可以把不容易变化的命令放在前面,因为这样可以
我们应该把变化最少的部分放在 Dockerfile 的前面,这样可以充分利用镜像缓存。

什么是镜像缓存?

Dockerfile 中每一个指令都会创建一个镜像层,依赖顺序和命令的顺序刚好相反,比如最开始的from centos7.3 就是最底层的基础镜像。在构建时 会缓存已有镜像的镜像层,如果某镜像层已经存在,就直接使用,无需重新创建。

原镜像1.0版本构建后的某一天,某个小伙伴说:需要修改start.sh来创建一个新的1.1版本的镜像,由于合理的组织命令顺序,所以能命中很多的层,构建效率也大大加快了。

file

还想提一点的是,ENV,设置环境变量命令。

在Dockerfile中无论是后面的其它指令,如 RUN,还是在容器中,运行时的应用,都可以直接使用这里定义的环境变量。我们在创建某种组件的镜像时,常常将组件的版本号设置为一个环境变量,其好处是方便我们后的续修改。所以这里建议,在Dockerfile的后续命令中,没有用到的环境变量,尽量定义到最后面,原因你懂的(改版本后能尽量命中更多缓存)。

四、非正规的混乱多个镜像

举一个极端的例子,假如你想要构建的镜像既有python环境,又有java环境,可能会写出如下的命令:

FROM jdk:1.8.9
FROM python:3.5

不过这并不会达到预期的效果,Docker会使用最后的一个FROM,并且忽略掉前面所有的From内容。所以正确的做法应该先使用Dockerfile构建一个具有java和python环境的centos基础镜像,然后你的程序再依赖这个基础镜像即可。

注意,上述原理只在Docker v17.05 前,在这个版本后,为解决以上问题,开始支持多阶段构建 (multistage builds)。使用多阶段构建我们就可以很容易解决前面提到的问题,并且只需要编写一个 Dockerfile。

每条FROM指令可以使用不同的基础镜像,这样您可以选择性地将服务组件从一个阶段COPY到另一个阶段,在最终镜像中只保留需要的内容。

五、在一个容器中运行多个服务

在一个容器中运行多个服务,会导致编写的Dockerfile变得更加复杂和困难,并且,附加的一些依赖会让构建速度变慢,不利于调试和维护。

file

当然,如果是测试环境限制,可以在一个容器中运行多个服务,但是并不建议在生产环境这样做,不利于水平扩展单个应用。

六、在构建过程中使用数据卷

Dockerfile为我们提供数据卷的功能,在文件中,我们可以使用VOLUME命令来指定匿名数据卷,当然你可以在运行容器时指定,写在这里的目的是:为了防止运行时用户忘记指定数据卷映射,所以我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行。

VOLUME /data
RUN echo "构建过程中搞事情?!!!!" > /data/myfile.txt

CMD ["cat", "/data/myfile.txt"]

$ docker run volume-in-build
cat: can't open '/data/myfile.txt': No such file or directory

VOLUME /data
这里的 /data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置:

docker run -d -v host/data:/data xxxx

七、纠结如何选择ADD或COPY命令

file

虽然功能类似,ADD和COPY实际上是不同的命令。COPY是这两种方法中最简单的一种,因为它只是将文件或目录从您的主机复制到镜像中。ADD也能做到这一点,但它还有一些更神奇的特性,比如提取tar文件或从远程url获取文件。一般情况下,建议使用ADD来代替COPY命令,由图所知,一行ADD能做完的事,COPY需要三行。但是从另一个角度来说,为了降低Dockerfile的复杂性,并防止一些意外的行为,也可以选择使用COPY命令。

八、多个命令的合并

上面对比过ADD命令和COPY的区别,由于每一行指令会构建一个镜像层,所以在编写Dockerfile的时候,可以一些命令串联成一行,中间通过&&运算符,一行结束位置加上 \ 符号,融合命令后,能够降低镜像的体积。案例如下:

RUN set -ex && \
    apk upgrade --no-cache && \
    apk add --no-cache bash tini libc6-compat && \
    mkdir -p /opt/spark && \
    mkdir -p /opt/spark/work-dir \
    touch /opt/spark/RELEASE && \
    rm /bin/sh && \
    ln -sv /bin/bash /bin/sh && \
    chgrp root /etc/passwd && chmod ug+rw /etc/passwd

总结

本篇文章,介绍了在编写Dockerfile中的一些心得和体会,编写一个好的Dockerfile将会让我们更熟悉Docker的工作机制,降低我们构建镜像所花的时间。提升我们在日常开发中的工作效率。

### 回答1: DockerfileDocker 容器的构建文件,它包含了创建 Docker 容器所需的所有指令和信息。 要编写 Dockerfile,你需要遵循以下步骤: 1. 在文本编辑器中新建一个文件,并将其命名为 Dockerfile。 2. 在 Dockerfile 中指定基础镜像,使用 `FROM` 关键字。例如: ``` FROM ubuntu:20.04 ``` 3. 可以使用 `RUN` 关键字执行命令来安装软件包、创建文件夹、下载文件等。例如: ``` RUN apt-get update && apt-get install -y nginx ``` 4. 使用 `COPY` 关键字将本地文件或文件夹复制到容器中。例如: ``` COPY /path/to/local/file.txt /path/to/container/file.txt ``` 5. 使用 `EXPOSE` 关键字指定容器对外暴露的端口。例如: ``` EXPOSE 80 ``` 6. 使用 `ENTRYPOINT` 关键字指定容器启动时运行的命令。例如: ``` ENTRYPOINT ["nginx", "-g", "daemon off;"] ``` 7. 保存 Dockerfile 并退出文本编辑器。 示例 Dockerfile: ``` FROM ubuntu:20.04 RUN apt-get update && apt-get install -y nginx COPY /path/to/local/file.txt /path/to/container/file.txt EXPOSE 80 ENTRYPOINT ["nginx", "-g", "daemon off;"] ``` 要使用 Dockerfile 构建 Docker 容器,你可以在终端中使用 `docker build` 命令。例如: ``` docker build -t my-image . ``` ### 回答2: Dockerfile是用于构建Docker镜像的脚本文件,它包含了一系列的指令和参数,用于定义构建镜像的步骤和配置。 编写Dockerfile的过程中,我们需要定义基于哪个基础镜像构建,可以选择官方提供的各种语言和操作系统的基础镜像。接着,我们可以使用COPY指令将本地文件或目录复制到镜像中,使用WORKDIR指定工作目录,RUN指令执行一些系统命令和安装软件的操作,并使用EXPOSE指令声明容器运行时的监听端口。 在编写Dockerfile时,我们还可以使用ENV指令设置环境变量,使用VOLUME指令指定容器中的挂载点,使用CMD或ENTRYPOINT指令定义容器启动时执行的命令或脚本。同时,我们还可以使用LABEL指令为镜像添加一些元数据,方便管理和标识。 编写Dockerfile的关键是要根据实际需求合理选择指令和参数,并按照正确的顺序和逻辑编写,以提高构建镜像的效率和稳定性。此外,使用Dockerfile还可以借助缓存机制,提高构建镜像的速度。 最后,在编写Dockerfile后,我们可以使用docker build命令来构建镜像,根据Dockerfile中的指令和参数,自动化地完成构建过程。构建完成后,我们可以使用docker run命令来创建并运行基于这个镜像的容器。 总结来说,编写Dockerfile是一个描述和定义镜像构建过程的重要步骤,可以帮助我们更好地管理和部署应用程序。通过合理编写Dockerfile,我们可以方便地构建出符合需求的Docker镜像,提高开发和部署的效率。 ### 回答3: 编写Dockerfile是为了定义和构建Docker镜像的文件。下面是一个简单的示例Dockerfile: ``` # 使用基础镜像 FROM ubuntu:latest # 设置工作目录 WORKDIR /app # 复制应用程序代码到容器中 COPY . /app # 安装应用程序需要的依赖 RUN apt-get update && apt-get install -y python3 # 设置环境变量 ENV APP_NAME myapp ENV PORT 8080 # 暴露容器端口 EXPOSE $PORT # 运行应用程序 CMD [ "python3", "app.py" ] ``` 以上是一个基于Ubuntu镜像的Dockerfile示例。它首先指定了所使用的基础镜像,然后设置了工作目录并将应用程序代码复制到容器中。接下来是安装所需的依赖,这里以安装Python3为例。然后设置了一些环境变量,例如应用程序的名称和所使用的端口号。最后,通过EXPOSE指令暴露容器的端口,并通过CMD指令运行应用程序。 编写Dockerfile时,可以根据具体的需求,选择合适的基础镜像、设置工作目录、复制文件、安装依赖、配置环境变量、暴露端口和运行应用程序等步骤。编写完成后,可以使用`docker build`命令根据Dockerfile构建镜像,并使用该镜像创建和运行容器。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值