怎么看待Dockerfile最佳实践,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
虽然 Dockerfile 简化了镜像构建的过程,并且把这个过程可以进行版本控制,但是不正当的 Dockerfile 使用也会导致很多问题:
docker 镜像太大。如果你经常使用镜像或者构建镜像,一定会遇到那种很大的镜像,甚至有些能达到 数G
docker 镜像的构建时间过长。每个 build 都会耗费很长时间,对于需要经常构建镜像(比如单元测试)的地方这可能是个大问题
重复劳动。多次镜像构建之间大部分内容都是完全一样而且重复的,但是每次都要做一遍,浪费时间和资源
容器模型是进程而不是机器,不需要开机初始化。在需要时运行,不需要时停止,能够删除后重建,并且配置和启动的最小化。
在 docker build 的时候,忽略部分无用的文件和目录可以提高构建的速度。比如.git目录。dockerignore的定义类似gitignore。具体使用方式可以参考链接。
为了减少镜像的复杂性、镜像大小和构建时间,应该避免安装无用的包。
一个容器只运行一个进程。容器起到了隔离应用隔离数据的作用,不同的应用运行在不同的容器让集群的纵向扩展以及容器的复用都变的更加简单。需要多个应用交互时请使用 link 命令进行组合或者使用docker-compose。
需要掌握好Dockerfile的可读性和镜像层数之间的平衡。不推荐使用过多的镜像层。
命令行按字母顺序排序有助于避免重复执行和提高Dockerfile可读性。apt-get update 应与 apt-get install 组合,换行使用反斜杠(\)。例如:
RUN apt-get update && apt-get install -y \ bzr \ cvs \ git \ mercurial \ subversion
Dockerfile的每条指令都会将结果提交为新的镜像。下一条指令基于上一条指令的镜像进行构建。如果一个镜像拥有相同的父镜像和指令(除了 ADD ),Docker将会使用镜像而不是执行该指令,即缓存。
因此,为了有效的利用缓存,尽量保持Dockerfile一致,并且将不变的放在前面而经常改变放在末尾。
如不希望使用缓存,在执行 docker build 的时候加上参数 --no-cache=true 。
Docker匹配镜像决定是否使用缓存的规则如下:
从缓存中存在的基础镜像开始,比较所有子镜像,检查它们构建的指令是否和当前的是否完全一致。如果不一致则缓存不匹配。
多数情况中,比较Dockerfile中的指令是足够的。然而,特定的指令需要做更多的判断。
ADD COPY 指令中,将要添加到镜像中的文件也要被检查。通常是检查文件的校验和(checksum)。
缓存匹配检查并不检查容器中的文件。例如,当使用 RUN apt-get -y update 命令更新了容器中的文件,并不会被缓存检查策略作为缓存匹配的依据。
官方Image 使用的时区基本上都是标准的 UTC 时间,如果容器想使用中国标准时间,基于Debian的系统在Dockerfile中加入
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo "Asia/Shanghai" >> /etc/timezone
基于Centos的系统在Dockerfile中加入
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
有时候你可能感觉官方的源更新或者安装软件比较慢,可以在Dockerfile修改官方默认源,例如alpine想使用阿里的源可以在Dockerfile中加入:
RUN echo -e "http://mirrors.aliyun.com/alpine/v3.5/main\nhttp://mirrors.aliyun.com/alpine/v3.5/community" > /etc/apk/repositories
推荐使用官方仓库中的镜像作为基础镜像。
把复杂的或过长的 RUN 语句写成以 \ 结尾的多行的形式,以提高可读性和可维护性。
apt-get update 和 apt-get install 一起执行,否则 apt-get install 会出现异常。
避免运行 apt-get upgrade 或 dist-upgrade ,在无特权的容器中,很多 必要 的包不能正常升级。如果基础镜像过时了,应当联系维护者。 推荐 apt-get update && apt-get install -y package-a package-b 这种方式,先更新,之后安装最新的软件包。
RUN apt-get update && apt-get install -y \ aufs-tools \ automake \ build-essential \ curl \ dpkg-sig \ libcap-dev \ libsqlite3-dev \ mercurial \ reprepro \ ruby1.9.1 \ ruby1.9.1-dev \ s3cmd=1.1.* \ && rm -rf /var/lib/apt/lists/*
此外,你可以通过移除/var/lib/apt/lists减少镜像大小。
注意:官方的Ubuntu和Debian会自动运行apt-get clean,所以不需要显式的调用
推荐使用 CMD ["executable","param1","param2"] 这样的格式。如果镜像是用来运行服务,需要使用 CMD["apache2","-DFOREGROUND"] ,这种格式的指令适用于任何服务性质的镜像。
ENTRYPOINT 应该用于 镜像的主命令,并使用 CMD 作为默认设置,以 s3cmd 为例:
ENTRYPOINT ["s3cmd"] CMD ["--help"]
获取帮助:
docker run s3cmd
或者执行命令:
docker run s3cmd ls s3://mybucket
这在镜像名与程序重名时非常有用。
ENTRYPOINT 也可以启动自定义脚本: 定义脚本:
#!/bin/bash set -e if [ "$1" = 'postgres' ]; then chown -R postgres "$PGDATA" if [ -z "$(ls -A "$PGDATA")" ]; then gosu postgres initdb fi exec gosu postgres "$@" fi exec "$@"
注意:这段脚本使用了exec命令以确保最终应用程序在容器内启动的PID为1。这段脚本允许容器接收Unix signals。
COPY ./docker-entrypoint.sh / ENTRYPOINT ["/docker-entrypoint.sh"]
这段脚本为用户提供了多种和 Postgres 交互的途径:
你可以简单地启动 Postgres:
docker run postgres。
或者运行 postgres 并传入参数:
docker run postgres postgres --help。
你甚至可以从镜像中启动一个完全不同的程序,比如 Bash:
docker run --rm -it postgres bash
应该尽可能地使用默认端口。例如Apache web服务使用EXPOSE 80,MongoDB使用EXPOSE 27017。
可以使用ENV更新PATH环境变量。例如,ENV PATH /usr/local/nginx/bin:$PATH可以确保CMD [“nginx”]正常运行。
ENV还可以提供程序所需要的环境变量,例如Postgres的 PGDATA。
ENV可以设置版本等信息。使版本信息更易于维护。
ENV PG_MAJOR 9.3 ENV PG_VERSION 9.3.4 RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && … ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
虽然 ADD 与 COPY 功能类似,但推荐使用 COPY 。 COPY 只支持基本的文件拷贝功能,更加的可控。而 ADD 具有更多特定,比如tar文件自动提取,支持URL。 通常需要提取tarball中的文件到容器的时候才会用到 ADD 。
如果在Dockerfile中使用多个文件,每个文件应使用单独的 COPY 指令。这样,只有出现文件变化的指令才会不使用缓存。
为了控制镜像的大小,不建议使用 ADD 指令获取URL文件。正确的做法是在 RUN 指令中使用 wget 或 curl 来获取文件,并且在文件不需要的时候删除文件。
RUN mkdir -p /usr/src/things \ && curl -SL http://example.com/big.tar.gz \ | tar -xJC /usr/src/things \ && make -C /usr/src/things all
VOLUME 通常用作数据卷,对于任何可变的文件,包括数据库文件、代码库、或者容器所创建的文件/目录等都应该使用 VOLUME 挂载。
如果服务不需要特权来运行,使用 USER 指令切换到非root用户。使用 **RUN groupadd -r mysql && useradd -r -g mysql mysql **之后用 USER mysql 切换用户
要避免使用 sudo 来提升权限,因为它带来的问题远比它能解决的问题要多。如果你确实需要这样的特性,那么可以选择使用 gosu 。
最后,不要反复的切换用户。减少不必要的layers。
为了清晰和可维护性,应该使用WORKDIR来定义工作路径。推荐使用WORKDIR来代替RUN cd … && do-something 这样的指令。
Dockerfile 中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD 是为了帮助别人定制自己而准备的。
ONBUILD指令用来设置一些触发的指令,用于在当该镜像被作为基础镜像来创建其他镜像时(也就是Dockerfile中的FROM为当前镜像时)执行一些操作,ONBUILD中定义的指令会在用于生成其他镜像的Dockerfile文件的FROM指令之后被执行,上述介绍的任何一个指令都可以用于ONBUILD指令,可以用来执行一些因为环境而变化的操作,使镜像更加通用。
注意:
ONBUILD中定义的指令在当前镜像的build中不会被执行。
可以通过查看docker inspect <image>命令执行结果的OnBuild键来查看某个镜像ONBUILD指令定义的内容。
ONBUILD中定义的指令会当做引用该镜像的Dockerfile文件的FROM指令的一部分来执行,执行顺序会按ONBUILD定义的先后顺序执行,如果ONBUILD中定义的任何一个指令运行失败,则会使FROM指令中断并导致整个build失败,当所有的ONBUILD中定义的指令成功完成后,会按正常顺序继续执行build。
ONBUILD中定义的指令不会继承到当前引用的镜像中,也就是当引用ONBUILD的镜像创建完成后将会清除所有引用的ONBUILD指令。
ONBUILD指令不允许嵌套,例如ONBUILD ONBUILD ADD . /data是不允许的。
ONBUILD指令不会执行其定义的FROM或MAINTAINER指令。
例如,Dockerfile使用如下的内容创建了镜像 image-A :
[...] ONBUILD ADD . /app/src ONBUILD RUN /usr/local/bin/python-build --dir /app/src [...]
如果基于 image-A 创建新的镜像时,新的Dockerfile中使用FROM image-A指定基础镜像时,会自动执行ONBUILD指令内容,等价于在后面添加了两条指令。
FROM image-A #Automatically run the following ADD . /app/src RUN /usr/local/bin/python-build --dir /app/src
假设我们要制作 Node.js 所写的应用的镜像。我们都知道 Node.js 使用 npm 进行包管理,所有依赖、配置、启动信息等会放到 package.json 文件里。在拿到程序代码后,需要先进行 npm install 才可以获得所有需要的依赖。然后就可以通过 npm start 来启动应用。因此,一般来说会这样写 Dockerfile:
FROM node:slim RUN mkdir /app WORKDIR /app COPY ./package.json /app RUN [ "npm", "install" ] COPY . /app/ CMD [ "npm", "start" ]
把这个 Dockerfile 放到 Node.js 项目的根目录,构建好镜像后,就可以直接拿来启动容器运行。但是如果我们还有第二个 Node.js 项目也差不多呢?好吧,那就再把这个 Dockerfile 复制到第二个项目里。那如果有第三个项目呢?再复制么?文件的副本越多,版本控制就越困难,让我们继续看这样的场景维护的问题。
如果第一个 Node.js 项目在开发过程中,发现这个 Dockerfile 里存在问题,比如敲错字了、或者需要安装额外的包,然后开发人员修复了这个 Dockerfile,再次构建,问题解决。第一个项目没问题了,但是第二个项目呢?虽然最初 Dockerfile 是复制、粘贴自第一个项目的,但是并不会因为第一个项目修复了他们的 Dockerfile,而第二个项目的 Dockerfile 就会被自动修复。
那么我们可不可以做一个基础镜像,然后各个项目使用这个基础镜像呢?这样基础镜像更新,各个项目不用同步 Dockerfile 的变化,重新构建后就继承了基础镜像的更新?好吧,可以,让我们看看这样的结果。那么上面的这个 Dockerfile 就会变为:
FROM node:slim RUN mkdir /app WORKDIR /app CMD [ "npm", "start" ]
这里我们把项目相关的构建指令拿出来,放到子项目里去。假设这个基础镜像的名字为 my-node 的话,各个项目内的自己的 Dockerfile 就变为:
FROM my-node COPY ./package.json /app RUN [ "npm", "install" ] COPY . /app/
基础镜像变化后,各个项目都用这个 Dockerfile 重新构建镜像,会继承基础镜像的更新。
那么,问题解决了么?没有。准确说,只解决了一半。如果这个 Dockerfile 里面有些东西需要调整呢?比如 npm install 需要统一加一些参数,那怎么办?这一行 RUN 是不可能放入基础镜像的,因为涉及到了当前项目的 ./package.json,难道又要一个个修改么?所以说,这样制作基础镜像,只解决了原来的 Dockerfile 的前4条指令的变化问题,而后面三条指令的变化则完全没办法处理。
ONBUILD 可以解决这个问题。让我们用 ONBUILD 重新写一下基础镜像的 Dockerfile:
FROM node:slim RUN mkdir /app WORKDIR /app ONBUILD COPY ./package.json /app ONBUILD RUN [ "npm", "install" ] ONBUILD COPY . /app/ CMD [ "npm", "start" ]
这次我们回到原始的 Dockerfile,但是这次将项目相关的指令加上 ONBUILD,这样在构建基础镜像的时候,这三行并不会被执行。然后各个项目的 Dockerfile 就变成了简单地:
FROM my-node
是的,只有这么一行。当在各个项目目录中,用这个只有一行的 Dockerfile 构建镜像时,之前基础镜像的那三行 ONBUILD 就会开始执行,成功的将当前项目的代码复制进镜像、并且针对本项目执行 npm install,生成应用镜像。
类似Java,Go等编译型项目,可以使用ONBUILD指令进行优化Dockerfile。
编写onbuild Dockerfile如下:
FROM maven:3-jdk-8 RUN mkdir -p /usr/src/app WORKDIR /usr/src/app ONBUILD ADD . /usr/src/app ONBUILD RUN mvn install
然后所有依赖maven编译的项目Dockerfile可以简化成如下形式:
FROM maven:3.3-jdk-8-onbuild CMD ["java","-jar","/usr/src/app/target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar"]
关于怎么看待Dockerfile最佳实践问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注亿速云行业资讯频道了解更多相关知识。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。