欢迎您, 来到 宁时修博客.^_^

Docker系列4--利用dockerfile构建镜像

2018/11/18 林木立 Docker 856
Docker容器技术

一、Dockerfile基础

    镜像的定制实际是定制每一层所添加的配置、文件。如果把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是Dockerfile。

    Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),一条指令构建一层,每一条指令的内容,就是描述如何构建该层。


    例:以Dockerfile构建一个nginx镜像

    创建一个名为Dockerfile的文件,输入指令,然后在Dockerfile文件同级目录构建镜像:

$ cd /usr/local/mynginx/
$ vim Dockerfile
FROM nginx
RUN echo '<h1>Hello, Docker!<h1>' > /usr/share/nginx/html/index.html

$ docker build -t nginx:v1 .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM nginx
 ---> 62f816a209e6
Step 2/2 : RUN echo '<h1>Hello, Docker!<h1>' > /usr/share/nginx/html/index.html
 ---> Running in 818f0bd3ed2b
Removing intermediate container 818f0bd3ed2b
 ---> 977b63b8e83f
Successfully built 977b63b8e83f
Successfully tagged nginx:v1

$ docker image ls
.......


二、Dockerfile指令

    1、FROM    指定基础镜像

    定制镜像,是以一个镜像为基础,在其上进行定制。基础镜像是必须指定的。

    FROM  就是指定基础镜像,一个  Dockerfile  中  FROM  是必备的指令,并且必须是第一条指令。


    2、RUN   执行命令

    RUN  指令是用来执行命令行命令的。由于命令行的强大能力,RUN  指令在定制镜像时是最常用的指令之一。

    格式有两种:

    1)shell 格式: RUN <命令>  ,如同在命令行中输入命令一样。

RUN echo '<h1>Hello, Docker!<h1>' > /usr/share/nginx/html/index.html


    2)exec 格式: RUN ["可执行文件", "参数1", "参数2"]  ,像是函数调用中的格式。


    Dockerfile 中每一个指令都会建立一层,每一个 RUN  的行为,和手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后, commit  这一层的修改,构成新的镜像。

    写多个 RUN 是完全没有意义的,使用一个  RUN  指令,并使用 “&&” 将各个命令串联起来。在撰写 Dockerfile 的时候,是在定义每一层如何构建,很多运行时不需要的东西,不要装进镜像里,比如编译环境、更新的软件包等。因此Dockerfile文件最后做些清理工作:删除为了编译构建所需要的软件,清理所有下载、展开的文件,清理yum缓存文件。不然就产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。

    镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。 Docker 制作出了很臃肿的镜像的原因之一,就是忘记了每一层构建的最后一定要清理掉无关文件。这是很多初学 Docker 的人常犯的一个错误。

    Dockerfile 支持 Shell 类的行尾添加 “ \” 的命令换行方式,以及行首 “#” 进行注释的格式。良好的格式,比如换行、缩进、注释等,会让维护、排障更为容易,这是一个比较好的习惯。

    注意:Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过 127 层。


    3、COPY  复制文件

    有两种格式,一种类似于命令行,一种类似于函数调用。

    格式:

COPY [--chown=<user>:<group>] <源路径>... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]


    COPY  指令将从构建上下文目录中  <源路径>  的文件/目录复制到新的一层的镜像内的  <目标路径>  位置。比如:

COPY package.json /usr/src/app/


    <源路径>  可以是多个,甚至可以是通配符,其通配符规则要满足Go的filepath.Match  规则,如:

COPY hom* /mydir/
COPY hom?.txt /mydir/


    <目标路径>  可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用  WORKDIR  指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先创建缺失目录。

    还需要注意一点,使用  COPY  指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用,特别是构建相关文件都在使用 Git 进行管理的时候。

    

    在使用该指令的时候还可以加上  --chown=<user>:<group>  选项来改变文件的所

属用户及所属组。

COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
COPY --chown=1 files* /mydir/
COPY --chown=10:11 files* /mydir/


    4、ADD 更高级的复制文件

    ADD  指令和  COPY  的格式和性质基本一致。但是在  COPY  基础上增加了一些功能。

    比如  <源路径>  可以是一个  URL  ,这种情况下,Docker 引擎会试图去下载这个链接的文件放到  <目标路径>  去。下载后的文件权限自动设置为  600  ,如果这并不是想要的权限,那么还需要增加额外的一层  RUN  进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层  RUN  指令进行解压缩。所以不如直接使用  RUN  指令,然后使用  wget  或者  curl  工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。

    如果  <源路径>  为一个  tar  压缩文件的话,压缩格式为  gzip  ,  bzip2  以及xz  的情况下, ADD  指令将会自动解压缩这个压缩文件到  <目标路径>  去。在某些情况下,这个自动解压缩的功能非常有用,比如官方镜像  ubuntu  中:

FROM scratch
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /

    如果我们真的是希望复制个压缩文件进去,而不解压缩,这时就不可以使用  ADD  命令了。

    在 Docker 官方的 Dockerfile 最佳实践文档 中要求,尽可能的使用  COPY  ,因为COPY  的语义很明确,就是复制文件而已,而  ADD  则包含了更复杂的功能,其行为也不一定很清晰。最适合使用  ADD  的场合,就是所提及的需要自动解压缩的场合。

    另外需要注意的是, ADD  指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。

    因此在  COPY  和  ADD  指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用  COPY  指令,仅在需要自动解压缩的场合使用  ADD  。

    在使用该指令的时候还可以加上  --chown=<user>:<group>  选项来改变文件的所

属用户及所属组。

ADD --chown=55:mygroup files* /mydir/
ADD --chown=bin files* /mydir/
ADD --chown=1 files* /mydir/
ADD --chown=10:11 files* /mydir/



    5、CMD  容器启动命令

    CMD  指令的格式和  RUN  相似,也是两种格式:

shell格式: CMD <命令>
exec格式: CMD ["可执行文件", "参数1", "参数2"...]

    参数列表格式: CMD ["参数1", "参数2"...]  。在指定了  ENTRYPOINT  指令后,用  CMD  指定具体的参数。


    Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。 CMD  指令就是用于指定默认的容器主进程的启动命令的。


    在运行时可以指定新的命令来替代镜像设置中的这个默认命令,比如, ubuntu镜像默认的  CMD  是  /bin/bash  ,如果我们直接  docker run -it ubuntu  的话,会直接进入  bash  。我们也可以在运行时指定运行别的命令,如  docker run -it ubuntu cat /etc/os-release  。这就是用  cat /etc/os-release命令替换了默认的  /bin/bash  命令了,输出了系统版本信息。

    在指令格式上,一般推荐使用  exec  格式,这类格式在解析时会被解析为 JSON数组,因此一定要使用双引号  "  ,而不要使用单引号 '。

    如果使用  shell  格式的话,实际的命令会被包装为  sh -c  的参数的形式进行执行。比如:

CMD echo $HOME

    在实际执行中,会将其变更为:

CMD [ "sh", "-c", "echo $HOME" ]

    这就是为什么可以使用环境变量,因为这些环境变量会被 shell 进行解析处理。

    

    提到  CMD  就不得不提容器中应用在前台执行和后台执行的问题。这是初学者常出现的一个混淆。一些初学者将  CMD  写为:

CMD service nginx start

    然后发现容器执行后就立即退出了。甚至在容器内使用  systemctl  命令,结果却发现根本执行不了。这是没有搞明白前台、后台的概念,没有区分容器和虚拟机的差异,依旧在以传统虚拟机的角度去理解容器。

    对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。

    使用  service nginx start  命令,是希望以后台守护进程形式启动  nginx  服务。而 “CMD service nginx start ”会被理解为  CMD ["sh", "-c", "service nginx start"]  ,主进程实际上是  sh  。当service nginx start  命令结束后, sh  也就结束了, sh  作为主进程退出了,自然就会令容器退出。

    正确的做法是直接执行  nginx  可执行文件,并且要求以前台形式运行。比如:

CMD ["nginx", "-g", "daemon off;"]



    6、ENTRYPOINT 入口点

    ENTRYPOINT  的格式和  RUN  指令格式一样,分为  exec  格式和  shell  格式。

    ENTRYPOINT  的目的和  CMD  一样,都是在指定容器启动程序及参数。

    ENTRYPOINT  在运行时也可以替代,不过比  CMD  要略显繁琐,需要通过docker run  的参数  --entrypoint  来指定。

    当指定了  ENTRYPOINT  后, CMD  的含义就发生了改变,不再是直接的运行其令,而是将  CMD  的内容作为参数传给  ENTRYPOINT  指令,换句话说实际执行时,将变为:

<ENTRYPOINT> "<CMD>"




    7、ENV 设置环境变量

    格式有两种:

ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...


    这个指令就是定义环境变量而已,无论是后面的其它指令(RUN ),还是运行时的应用,都可以直接使用这里定义的环境变量。

ENV VERSION=1.0 DEBUG=on \
    NAME="Happy Feet"

    

    定义了环境变量,在后续的指令中,可以直接使用这个环境变量。

    比如在官方node  镜像  Dockerfile  中,就有类似这样的代码:

ENV NODE_VERSION 7.2.0
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NOD
E_VERSION-linux-x64.tar.xz" \
    && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS25
6.txt.asc" \
    && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.tx
t.asc \
    && grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.t
xt | sha256sum -c - \
    && tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/loc
al --strip-components=1 \
    && rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.as
c SHASUMS256.txt \
    && ln -s /usr/local/bin/node /usr/local/bin/nodejs


    先定义了环境变量  NODE_VERSION  ,其后的  RUN  这层里,多次使用$NODE_VERSION  来进行操作定制。将来升级镜像构建版本的时候,只需要更新  7.2.0  即可, Dockerfile  构建维护变得更轻松了。


    下列指令可以支持环境变量:

ADD、 COPY、 ENV、 EXPOSE、 LABEL、 USER、 WORKDIR、 VOLUME、STOPSIGNAL、ONBUILD


    从这个指令列表里感觉到,环境变量可以使用的地方很多,很强大。通过环境变量,我们可以让一份  Dockerfile  制作更多的镜像,只需使用不同的环境变量即可。



    8、ARG   构建参数

    格式: ARG <参数名>[=<默认值>]

    ARG 和  ENV  的效果一样,都是设置环境变量。所不同的是 ARG  所设置的环境变量,在将来容器运行时是不会存在的。不要因此就使用  ARG  保存密码之类的信息,因为  docker history  还是可以看到所有值的。

    Dockerfile  中的  ARG  指令定义参数名称,及定义其默认值。该默认值可以在构建命令  docker build  中用 “--build-arg <参数名>=<值>”来覆盖。

    在 1.13 之前的版本,要求  --build-arg  中的参数名,必须在  Dockerfile  中用  ARG  定义过了,换句话说,就是  --build-arg  指定的参数,必须在Dockerfile  中使用了。如果对应参数没有被使用,则会报错退出构建。从 1.13开始,这种严格的限制被放开,不再报错退出,而是显示警告信息,并继续构建。这对于使用 CI 系统,用同样的构建流程构建不同的  Dockerfile  的时候比较有助,避免构建命令必须根据每个 Dockerfile 的内容修改。


点赞
说说你的看法

所有评论: (0)