首页 > 大数据 > > 「Docker」 - Dockerfile
Dockerfile:Docker特有的镜像构建定义文件。
Dockerfile是Docker中用于定义镜像自动化构建流程的配置文件,在Dockerfile中,包含了构建镜像过程中需要执行的命令和其他操作。通过Dockerfile可以更加清晰、明确的给定Docker镜像的制作过程,由于仅是简单、小体积的文件,在网络等介质中传递的速度快,能够更快的实现容器迁移和集群部署。

通常来说,对Dockerfile的定义就是针对一个名为Dockerfile的文件,其虽然没有扩展名,但本质就是一个文本文件,可以通过常见的文本编辑器或者IDE创建和编辑它。
Dockerfile的内容很简单,主要以两种形式呈现,一种是注释行,另一种是指令行。Dockerfile中,拥有一套独立的指令语法,用于给出镜像构建过程中所要执行的过程。Dockerfile里的指令行,就是由指令与其相应的参数所组成。
用开发中的常见流程来类比Dockerfile。
在一个完整的开发、测试、部署过程中,程序运行环境的定义通常是由开发人员来进行的,因为开发更熟悉程序运转的各个细节,更适合搭建适合程序的运行环境。
以此为前提,为了方便测试和运维搭建相同的程序运行环境,常用的做法是由开发人员编写一套环境搭建手册,帮助测试人员和运维人员了解环境搭建的流程。
Dockerfile就像这样一个环境搭建手册,因为其中包含的就是一个构建容器的过程。
而比环境搭建手册更好的是,Dockerfile在容器体系下能够完成自动构建,既不需要测试和运维人员深入理解环境中各个软件的具体细节,也不需要人工执行每一个搭建流程。
相对于提交容器修改再进行镜像迁移的方式相比,使用Dockerfile有很多优势:
实际开发使用中很少会选择容器提交这种方法来构建镜像,而是几乎采用Dockerfile来制作镜像。
A、Dockerfile编写
以下为完整的Dockerfile,用于构建Docker官方所提供的Redis镜像。
FROM debian:stretch-slim
RUN groupadd -r redis && useradd -r -g redis redis
ENV GOSU_VERSION 1.10
RUN set -ex; \
\
fetchDeps=" \
ca-certificates \
dirmngr \
gnupg \
wget \
"; \
apt-get update; \
apt-get install -y --no-install-recommends $fetchDeps; \
rm -rf /var/lib/apt/lists/*; \
\
dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
export GNUPGHOME="$(mktemp -d)"; \
gpg --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
gpgconf --kill all; \
rm -r "$GNUPGHOME" /usr/local/bin/gosu.asc; \
chmod +x /usr/local/bin/gosu; \
gosu nobody true; \
\
apt-get purge -y --auto-remove $fetchDeps
ENV REDIS_VERSION 3.2.12
ENV REDIS_DOWNLOAD_URL http://download.redis.io/releases/redis-3.2.12.tar.gz
ENV REDIS_DOWNLOAD_SHA 98c4254ae1be4e452aa7884245471501c9aa657993e0318d88f048093e7f88fd
RUN set -ex; \
\
buildDeps=' \
wget \
\
gcc \
libc6-dev \
make \
'; \
apt-get update; \
apt-get install -y $buildDeps --no-install-recommends; \
rm -rf /var/lib/apt/lists/*; \
\
wget -O redis.tar.gz "$REDIS_DOWNLOAD_URL"; \
echo "$REDIS_DOWNLOAD_SHA *redis.tar.gz" | sha256sum -c -; \
mkdir -p /usr/src/redis; \
tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1; \
rm redis.tar.gz; \
\
# disable Redis protected mode [1] as it is unnecessary in context of Docker
# (ports are not automatically exposed when running inside Docker, but rather explicitly by specifying -p / -P)
# [1]: https://github.com/antirez/redis/commit/edd4d555df57dc84265fdfb4ef59a4678832f6da
grep -q '^#define CONFIG_DEFAULT_PROTECTED_MODE 1$' /usr/src/redis/src/server.h; \
sed -ri 's!^(#define CONFIG_DEFAULT_PROTECTED_MODE) 1$!\1 0!' /usr/src/redis/src/server.h; \
grep -q '^#define CONFIG_DEFAULT_PROTECTED_MODE 0$' /usr/src/redis/src/server.h; \
# for future reference, we modify this directly in the source instead of just supplying a default configuration flag because apparently "if you specify any argument to redis-server, [it assumes] you are going to specify everything"
# see also https://github.com/docker-library/redis/issues/4#issuecomment-50780840
# (more exactly, this makes sure the default behavior of "save on SIGTERM" stays functional by default)
\
make -C /usr/src/redis -j "$(nproc)"; \
make -C /usr/src/redis install; \
\
rm -r /usr/src/redis; \
\
apt-get purge -y --auto-remove $buildDeps
RUN mkdir /data && chown redis:redis /data
VOLUME /data
WORKDIR /data
COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 6379
CMD ["redis-server"]B、Dockerfile结构
当调用构建命令让Docker通过Dockerfile构建镜像时,Docker会逐一按顺序解析Dockerfile中的指令,并根据它们不同的含义执行不同的操作。
可以将Dockerfile的指令简单分为五大类:
以下常见的Dockerfile指令,基本包含常用的90%功能。
A、FROM
通常来说,不会从零开始搭建一个镜像,而是会选择一个已经存在的镜像作为新镜像的基础。
在Dockerfile里,通过FROM指令指定一个基础镜像,之后所有的指令都是基于这个镜像所展开的。在镜像构建的过程中,Docker也会先获取到这个给出的基础镜像,再从这个镜像上进行构建操作。
FROM指令支持三种形式:
FROM <image> [AS <name>]
FROM <image>[:<tag>] [AS <name>]
FROM <image>[@<digest>] [AS <name>]选择一个基础镜像是构建新镜像的根本,则Dockerfile中的第一条指令必须是FROM指令,因为没有了基础镜像,一切构建过程都无法开展。在Dockerfile中可以多次出现FROM指令,当FROM第二次或者之后出现时,表示在此刻构建时,要将当前指出镜像的内容合并到此刻构建镜像的内容里。
B、RUN
镜像的构建虽然是按照指令执行的,但指令只是引导,最终大部分内容还是控制台中对程序发出的命令,而RUN指令就是用于向控制台发送命令的指令。
在RUN指令之后,直接拼接上需要执行的命令,在构建时,Docker就会执行这些命令,并将它们对文件系统的修改记录下来,形成镜像的变化。
RUN <command>
RUN ["executable", "param1", "param2"]RUN指令支持\换行,如果单行的长度过长,可以对内容进行切割,方便阅读。
C、ENTRYPOINT和CMD
基于镜像启动的容器,在容器启动时会根据镜像所定义的一条命令来启动容器中进程号为1的进程。而这个命令的定义,就是通过Dockerfile中的ENTRYPOINT和CMD实现的。
ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2
CMD ["executable","param1","param2"]
CMD ["param1","param2"]
CMD command param1 param2ENTRYPOINT指令和CMD指令的用法近似,都是给出需要执行的命令,并且它们都可以为空,或者说是不在Dockerfile里指出。
当ENTRYPOINT与CMD同时给出时,CMD中的内容会作为ENTRYPOINT定义命令的参数,最终执行容器启动的还是ENTRYPOINT中给出的命令。
D、EXPOSE
由于构建镜像时更了解镜像中应用程序的逻辑,也更加清楚它需要接收和处理来自哪些端口的请求,所以在镜像中定义端口暴露显然是更合理的做法。
通过EXPOSE指令就可以为镜像指定要暴露的端口。
EXPOSE <port> [<port>/<protocol>...]通过EXPOSE指令配置了镜像的端口暴露定义,基于这个镜像所创建的容器,在被其他容器通过--link选项连接时,就能够直接允许来自其他容器对这些端口的访问了。
E、VOLUME
在一些程序里,需要持久化一些数据,比如数据库中存储数据的文件夹就需要单独处理,可以通过数据卷来处理这些问题。
使用数据卷需要在创建容器时通过-v选项来定义,而有时候由于镜像的使用者对镜像了解程度不高,会漏掉数据卷的创建,从而引起不必要的麻烦。
制作镜像的人是最清楚镜像中程序工作的各项流程的,所以制作人来定义数据卷也是最合适的。在Dockerfile里,通过了VOLUME指令来定义基于此镜像的容器所自动建立的数据卷。
VOLUME ["/data"]在VOLUME指令中定义的目录,在基于新镜像创建容器时,会自动建立为数据卷,不需要再单独使用-v选项来配置。
F、COPY和ADD
制作新的镜像的时候,可能需要将一些软件配置、程序代码、执行脚本等直接导入到镜像内的文件系统里,使用COPY或ADD指令能够直接从宿主机的文件系统里拷贝内容到镜像里的文件系统中。
COPY [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]COPY与ADD指令的定义方式完全一样,两者的区别主要在于ADD能够支持使用网络端的URL地址作为src源,并且在源文件被识别为压缩包时,自动进行解压,而COPY没有这两个能力。
虽然看上去COPY能力稍弱,但对于那些不希望源文件被解压或没有网络请求的场景,COPY更为简单。
编写Dockerfile后,通过docker build命令构建镜像。
docker build ./webappdocker build的参数为目录路径(本地路径或URL路径),该目录会作为构建的环境目录,例如,使用COPY或是ADD拷贝文件到构建的新镜像时,会以这个目录作为基础目录。
默认情况下,docker build也会从这个目录下寻找名为Dockerfile的文件,将它作为Dockerfile内容的来源。如果Dockerfile文件路径不在这个目录下,或者有另外的文件名,可以通过-f选项单独给出Dockerfile文件的路径。
docker build -t webapp:latest -f ./webapp/a.Dockerfile ./webapp在构建时带上-t选项,用它来指定新生成镜像的名称。
docker build -t webapp:latest ./webappA、构建中使用变量
在实际编写Dockerfile时,与搭建环境相关的指令占有大部分比例的指令。搭建程序所需运行环境时,难免涉及到一些可变量,例如依赖软件的版本,编译的参数等等。
可以直接将这些数据写入到Dockerfile中,但是这些可变量会经常调整,在Dockerfile里,可以用ARG指令来建立一个参数变量,可以在构建时通过构建指令传入这个参数变量,并且在Dockerfile里使用它。
例如,希望通过参数变量控制Dockerfile中某个程序的版本,在构建时安装我们指定版本的软件,可以通过ARG定义的参数作为占位符,替换版本定义的部分。
FROM debian:stretch-slim
ARG TOMCAT_MAJOR
ARG TOMCAT_VERSION
RUN wget -O tomcat.tar.gz "https://www.apache.org/dyn/closer.cgi?action=download&filename=tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz"以上例子中,将Tomcat的版本号通过ARG指令定义为参数变量,在调用下载Tomcat包时,使用变量替换掉下载地址中的版本号。通过这样的定义,就可以在不对Dockerfile进行大幅修改的前提下,轻松实现对Tomcat版本的切换并重新构建镜像了。
如果需要通过这个Dockerfile文件构建Tomcat镜像,可以在构建时通过docker build的--build-arg选项来设置参数变量。
docker build --build-arg TOMCAT_MAJOR=8 --build-arg TOMCAT_VERSION=8.0.53 -t tomcat:8.0 ./tomcatB、环境变量
环境变量也是用来定义参数的东西,与ARG指令相类似,环境变量的定义是通过ENV指令来完成的。
FROM debian:stretch-slim
ENV TOMCAT_MAJOR 8
ENV TOMCAT_VERSION 8.0.53
RUN wget -O tomcat.tar.gz "https://www.apache.org/dyn/closer.cgi?action=download&filename=tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz"环境变量的使用方法与参数变量一样,都是能够直接替换指令参数中的内容。
与参数变量只能影响构建过程不同,环境变量不仅能够影响构建,还能够影响基于此镜像创建的容器。环境变量设置的实质,其实就是定义操作系统环境变量,所以在运行的容器里,一样拥有这些变量,而容器中运行的程序也能够得到这些变量的值。
另一个不同点是,环境变量的值不是在构建指令中传入的,而是在Dockerfile中编写的,所以如果要修改环境变量的值,需要到Dockerfile修改。不过即使这样,只要我们将ENV定义放在Dockerfile前部容易查找的地方,其依然可以很快的帮助切换镜像环境中的一些内容。
由于环境变量在容器运行时依然有效,所以运行容器时还可以对其进行覆盖,在创建容器时使用-e或是--env选项,可以对环境变量的值进行修改或定义新的环境变量。
docker run -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:5.7这种用法在开发中非常常见,也正是因为这种允许运行时配置的方法存在,环境变量和定义它的ENV指令,是更常使用的指令,会优先选择它们来实现对变量的操作。
通过ENV指令和ARG指令所定义的参数,在使用时都是采用$ + NAME这种形式来占位的,所以它们之间的定义就存在冲突的可能性。对于这种场景,ENV指令所定义的变量,永远会覆盖ARG所定义的变量,即使它们定时的顺序是相反的。
C、合并命令
上文中Redis镜像的Dockerfile中,RUN指令里聚合了大量的代码。
事实上,以下两种写法对于搭建的环境来说是没有太大区别的。
RUN apt-get update; \
apt-get install -y --no-install-recommends $fetchDeps; \
rm -rf /var/lib/apt/lists/*;
RUN apt-get update
RUN apt-get install -y --no-install-recommends $fetchDeps
RUN rm -rf /var/lib/apt/lists/*看似连续的镜像构建过程,其实是由多个小段组成。每当一条能够形成对文件系统改动的指令在被执行前,Docker先会基于上条命令的结果启动一个容器,在容器中运行这条指令的内容,之后将结果打包成一个镜像层,如此反复,最终形成镜像。

基于这个原理,绝大多数镜像会将命令合并到一条指令中,这种做法不但减少了镜像层的数量,也减少了镜像构建过程中反复创建容器的次数,提高了镜像构建的速度。
D、构建缓存
Docker在镜像构建的过程中,还支持缓存策略来提高镜像的构建速度。
由于镜像是多个指令所创建的镜像层组合而得,如果判断新编译的镜像层与已经存在的镜像层未发生变化,则完全可以直接利用之前构建的结果,而不需要再执行这条构建指令,这就是镜像构建缓存的原理。
基于这个原则,在条件允许的前提下,更建议将不容易发生变化的搭建过程放到Dockerfile的前部,充分利用构建缓存提高镜像构建的速度。另外,指令的合并也不宜过度,而是将易变和不易变的过程拆分,分别放到不同的指令里。
在另外一些时候,不希望Docker在构建镜像时使用构建缓存,这时可以通过--no-cache选项来禁用它。
docker build --no-cache ./webapp编写Dockerfile,阅读和思考前人的作品是必不可少的。
Docker官方提供的Docker Hub是Docker镜像的中央仓库,它除了镜像丰富之外,带来的另一项好处就是其大部分镜像都是能够直接提供Dockerfile文件,可以进行学习。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31537575/viewspace-3147315/ ,如需转载,请注明出处,否则将追究法律责任。