Docker Dockerfile

镜像的生成途径:html

  • Dockerfile
  • 基于容器制做

本篇介绍Dockerfile。node

文件说明

Dockerfile是一个包含用于组合映像的命令的文本文档。Docker经过读取Dockerfile中的指令自动生成镜像。nginx

基本结构

基本格式:web

# Comment
INSTRUCTION arguments

主要就2类一句,第一行是注释,第二行是指令。
指令(INSTRUCTION)大小写不敏感,为了将指令和参数或其余内容区分,一般指令使用全大写。
Docker以从上到下的顺序运行Dockerfile的指令。为了指定基本映像,第一条指令必须是FROM。一个声明以#字符开头则被视为注释。能够在Docker文件中使用RUN,CMD,FROM,EXPOSE,ENV等指令。docker

工做目录

制做镜像,首先要有一个文件目录,即工做目录。
制做镜像所引用的文件,都必须在工做目录下。
Dockerfile文件,文件名就是Dockerfile(首字母大写),写docker指令。
.dockeringore文件,写在该文件中的路径在打包时都不会打包。shell

docker build命令用于从Dockerfile构建镜像。能够在docker build命令中使用-f标志指向文件系统中任何位置的Dockerfile。vim

环境变量

制做镜像的过程当中,可使用环境变量。
直接为一个变量名赋值,这种只会在当前shell中有效。可是对在当前shell(父shell)中启动的其余shell(子shell)无效。好比赋值以后,调用了一个.sh脚本,在这个脚本中没有以前赋值的变量。
通常就用export命令来建立环境变量,这样是全局都有效的。
不管哪一种状况,只要会话关闭,就所有失效了。要想永久有效须要编辑文件。centos

引用变量
可使用$KEY${KEY}来使用环境变量。不带大括号的格式以后必须有空格,若是以后没有空格而是要接着其余内容,就须要用带大括号的格式。
另外,${KEY}这种形式还支持变量替换的特殊格式:数组

  • \${KEY:-VALUE}: VALUE是默认值,若是变量不存在就使用默认值
  • \${KEY:+VALUE}: 若是VALUE有值,则返回VALUE,不然返回空

Dockerfile 指令

在这里列出了一些经常使用的指令。缓存

FROM

FROM指定是最重要的一个指定,而且必须为Dockerfile文件开篇的第一个非注释行,用于为映像文件构建过程指定基本镜像,后续的指令运行于此基准镜像所提供的运行环境中。

实践中,基准镜像能够是任何可用镜像文件。默认状况下,dockerfile会在docker主机上查找指定的镜像文件,若是不存在,会自动从Registry上拉取。

语法格式:

FROM <repository>[:<tag>]
FROM <repository>@<digest>

上面两种格式均可以。第一行和拉取镜像或者运行镜像同样,一个镜像能够有多个版本,能够经过tag指定。第二行的格式是经过镜像的ID来指定,由于ID是惟一的,因此这种方式能够对镜像进行校验。基于名字引用镜像可能会有安全问题,防止引用被修改了名字的含有恶意代码的镜像。

MAINTAINER

维护者信息。该指令在旧版本中使用,建议用下面的LABEL来指定。

语法格式:

MAINTAINER <name>

能够是任何形式的文本信息,但约定俗成地使用做者名及邮箱地址:

MAINTAINER Steed Xu <x749b@163.com>

LABEL

用户能够为镜像指定各类元数据。

语法格式:

LABEL <key1>=<value1> <key2>=<value2> <key3>=<value3> ...

使用LABEL指定元数据时,一条LABEL指定能够指定一或多条元数据,指定多条元数据时不一样元数据之间经过空格分隔。推荐将全部的元数据经过一条LABEL指令指定,以避免生成过多的中间镜像。

COPY

用于从Docker主机复制文件至建立的新映像文件。

语法格式:

COPY <src> ... <dest>
COPY ["<src>", ... "<dest>"]

src是要复制的源文件或目录,支持使用通配符问号(?)和星号(*)。通常使用相对路径,起始路径就是工做目录。
dest为目标路径,通常为绝对路径。若是是相对路径,起始路径以WORKDIR(后面会讲)指定。

上面的两种格式均可以,第二行的列表格式能够支持包含空格的路径。

文件复制准则:

  • src必须在工做目录中
  • 若是src是目录,其内部文件和子目录会递归复制,但src自己不会被复制
  • 若是指定多个src,或src中使用了通配符,则dest必须是一个目录,必须以/结尾
  • 若是dest不存在,它将会被自动建立,包括其父目录的路径

ADD

ADD与COPY相似,ADD支持使用tar文件和url路径。tar文件会自动解压,url会下载相似wget。

语法格式:

ADD <src> ... <dest>
ADD ["<src>", ... "<dest>"]

操做准则:

  • 若是src为url,且dest不以/结尾,则下载文件并建立为dest。若是dest以/结果,则将文件下载到dest的目录下。
  • 只有本地的tar文件,会自动展开。若是是url,下载下来的文件是tar文件,这个不会再自动展开。
  • 若是src有多个,或者使用了通配符,dest以/结尾,则全部文件下载到dest目录下。若是dest不以/结尾,则被视为文件,src的内容将直接写入到dest。

WORKDIR

用于为Dockerfile中全部的RUN、CMD、ENTRYPOINT、COPY和ADD指令设定工做目录。
工做目录不只影响以后命令的起始路径,也会影响容器启动后的工做目录,默认是根目录。

语法格式:

WORKDIR <dirpath>

WORKDIR能够出现屡次,路径也能够是相对路径。相对路径就是相对前一个WORKDIR指定指定的路径。
WORKDIR还能够调用由ENV(后面会讲)指令定义的变量。

示例:

# 工做目录为/a
WORKDIR /a
# 工做目录为/a/b
WORKDIR b
# 工做目录为/a/b/c
WORKDIR c

VOLUME

用于在image中建立一个挂载点目录。
在dockerfle中只能指定挂载点,没法指定宿主机上的路径。因此这里的挂载的只能是docker管理的卷。

语法格式:

VOLUME <mounpoint>
VOLUME ["<mounpoint1>", "<mounpoint2>", "<mounpoint3>"]

若是挂载点目录路径下以前存在文件,docker run 命令会在卷挂载完成后将此前的全部文件复制到新挂载的卷中。

EXPOSE

用于为容器打开指定要监听的端口以实现对外部通讯。

语法格式:

EXPOSE <port> [<port> ...]

默认是TCP协议,可使用/tcp或/udp来指定。指令还能够一次指定多个端口,示例:

EXPOSE 11211/tcp 11211/udp

ENV

用于为镜像定义所需的环境变量,并可被Dockerfile文件中位于其后的其它指令所调用。调用环境变量在开头已经讲过了。

语法格式:

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

第一种格式,key以后的全部内容都会被视为value,这样一次只能设置一个变量。
第二种格式,能够一次设置多个变量,若是value中包含空格,就用反斜杠(\)转义,也能够经过对value加引号进行标识。另外反斜杠也可用于续行。
建议使用第二种格式时,用上反斜杠续行,一行设置一对键值对:

ENV key1=value1 \
    key2=value2

容器的环境变量
除了制做镜像时能够设置环境变量,在启动容器时也能够设置环境变量。docker container run启动容器是使用-e参数:

-e, --env list                       Set environment variables

制做镜像时的设置的环境变量会影响ENV语句后面的指令。而且会一直保留这个环境变量,在容器运行时依然有效。在启动容器时能够设置新的环境变量或者把以前的环境变量的值替换掉。这不会影响到以前镜像制做的过程当中使用环境变量的值。镜像制做时遇到环境变量,会直接获取到当前该变量额值并进行操做。
小结:

  • 制做镜像时设置的环境变量不但在制做镜像过程当中有效,也能够把容器运行时须要的环境变量提早设置好。
  • 容器运行时设置的环境变量,只会对容器运行的过程有影响,不会影响到以前镜像制做过程当中的操做。
  • 上面的状况也有例外,好比容器默认执行的命令中带有环境变量,这个命令的内容就是环境变量而不是环境变量的值,因此直接把环境变量的引用写到默认命令中。等到容器启动的时候再运行这个命令,此时才会去获取当前环境变量的值,也就是被run命令的-e参数改变以后的值。

printenv 查看环境变量
使用printenv命令能够查看环境变量,env命令不带参数也同样:

$ docker container run --rm -e k1=v1 -e k2=v2 tinyweb1 printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=ee6793a43c8c
k1=v1
k2=v2
HOME=/root
$

这里就是启动容器时,指定运行printenv命令便可。

RUN

用于指定docker build过程当中运行的程序,能够是任何命令。

语法格式:

RUN <command>
RUN ["executable", "param1", "param2"]

RUN指令可使用屡次。不过对于先后有关联的一系列指令,建议多条指令之间使用&&链接成为一条指令,再用续行符让一条指令写一行增长可读性。

经过shell来启动指令
第一种格式中,command是一条完整的shell命令,而且是以/bin/sh -c来运行的。
第二种格式是一个数组,executable是要运行的命令,后面都是要传递的参数。此种格式的命令不会以/bin/sh -c来发起,而是直接之内核建立进程。所以,常见的shell特性如变量替换以及通配符都无效。不过,若是须要依赖shell特性的话,能够用下面的格式:

RUN ["/bin/bash", "-c", "executable param1 param2"]

这里注意,最终要执行的命令包括这个命令的参数须要写成一个完整的字符串做为列表的一个元素,不能拆开。后面的制做镜像的示例中会有分析。

安装应用程序
大多数镜像在基于基础镜像安装应用程序时,都是编译安装的。这个编译安装的过程就须要RUN指令。
固然也可使用yum安装。不过须要注意,yum安装过程当中是会生成缓存的。这些缓存是不会自动删除,应该清除掉以节约镜像空间。下面的命令能够清除yum缓存:

yum clean all

另外,缓存的内容是保存在/var/cache/yum/目录下的。因此也能够把目录下的文件删除。

清除缓存
这部分的内容没有验证过,可能和上面是同样的做用。
RUN指令建立的中间镜像会被缓存,并会在下次构建中使用(这里的使用不是真的要用,而是由于这些缓存没有用了,却要占用镜像空间,应该要清除掉)。若是不想使用这些缓存镜像,能够在构建时指定--no-cache参数:

docker build --no-cache

CMD

为启动容器指定默认要运行的程序。这个就是容器中PID为1的进程,其运行结束后,容器也将终止。CMD指定相似于RUN指令,也是运行任何命令或应用程序,不过两者的运行时间点不一样。RUN是在制做镜像时执行的。CMD是在容器启动后执行的,而且只有最后一条CMD指令有效,就是指定容器的主进程。

语法格式:

CMD <command>
CMD ["executable", "param1", "param2"]
CMD ["param1", "param2"]

语法格式也和RUN是同样的,去对比查看RUN的指令格式说明和两种格式的差异便可。
这里还有第三种格式,内容形式上和第二种是没有差异的(形式上都是字符串,体现不出是命令仍是参数)。在设置了ENTRYPOINT(后面会讲)时,CMD列表里的元素就都做为ENTRYPOINT指令的参数了,因此就没有命令了。

下面两小段是引伸出来的内容,和使用容器无关。

exec命令
这一小段的内容只是要了解一下容器中启动的命令是如何成为PID为1的主进程的。
同RUN命令同样,第一种格式默认是用过/bin/sh -c启动的。这意味着启动的进程在容器中的PID不为1,不能接收Unix信号。所以,当使用docker stop命令中止容器时,此进程接收不到SIGTERM信号。而且shell才是PID为1的那个进程,那么程序启动完以后,容器就中止了。默认应该是使用了exec命令的机制,调用要启动的进程而且新的进程会替代父进程,也就是成为PID为1的进程。

在后台运行程序
这一小段的内容和容器无关,是讲一下传统的作法是如何直接在宿主机上启动后台程序的。
传统的方式在宿主机上运行应用,要启动这些服务,可使用systemctl start命令。
还有一种方式,能够手动启动,就是在命令行中执行命令。每个进程都应该是某一个进程的子进程。手动启动的程序默认是做为shell的子进程存在的。有些程序启动后会占据当前shell的终端,能够在命令后加&符号,让程序在后台运行。不过这并无改变这个应用程序是shell的子进程,一旦退出当前shell,任何一个进程终止时,都会先把它的子进程终止。要避免这种状况,还须要nobub命令,让以后的命令启动的进程剥离于当前shell进程的关系,使得程序能够在退出shell时继续运行。
因此可使用下面的方式启动程序而且保持运行:

nohup COMMAND &
nohup COMMAND > /dev/null 2>&1 &

ENTRYPOINT

相似CMD指令的功能,用于为容器指定默认运行程序。可是有ENTRYPOINT启动的程序不会被docker run命令指定的参数所覆盖。并且命令行参数会被当作参数传递给ENTRYPOINT指令指定的程序。
在docker run的时候,仍是能够经过--entrypoint参数来覆盖ENTRYPOINT指令指定的程序。

语法格式:

ENTRYPOINT <command>
ENTRYPOINT ["executable", "param1", "param2"]

docker run命令传入的命令参数会覆盖CMD指令的内容而且附加到ENTRYPOINT命令最后做为其参数使用。

关于ENTRYPOINT的应用,仍是看下以后的动态生成配置文件的示例。

USER

用于指定运行image时,或运行Dockerfile中任何RUN、CMD、ENTRYPOINT指令指定的程序时的用户名或UID。默认状况下,container运行身份为root用户。

语法格式:

USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group

这里的uid、gid能够是任意数字,可是必须为有效的,不然docker run命令将会失败。

HEALTHCHECK

HEALTHCHECK指令告诉Docker如何测试容器以检查它是否仍在工做。即便服务器进程仍在运行,这也能够检测到陷入无限循环且没法处理新链接的Web服务器等状况。

语法格式:

HEALTHCHECK [OPTIONS] CMD <command>
HEALTHCHECK NONE

第一行,经过在容器内运行命令来检查容器运行情况。
第二行,禁用从基础映像继承的任何运行情况检查。
容器默认就能够有健康状态检测的方法。只是默认的方式有的场景不适用,这就须要自定义。CMD是关键字,后面跟用于健康监测的命令。

参数说明:

  • --interval: 轮询时间,默认30秒
  • --timeout: 超时时间,默认30秒
  • --start-priod: 容器启动多久后开始检测,默认0秒。若是主进程启动比较慢,须要设置一下一个参数
  • retries: 确认健康检查失败,须要检查失败的次数,默认3次。

CMD关键字后面跟的就是健康状态检查的命令,docker根据这个命令的返回值来判断健康状态。运行结果返回值的含义:

  • 0: 成功
  • 1: 不健康
  • 2: 预留值,无心义

指令示例
这是个简单的例子:

HEALTHCHECK --interval=5m --timeout=3s \
    CMD curl -f http://localhost/ || exit 1

可能没有curl命令,使用wget命令也是同样的:

CMD wget -O - -q http://localhost/

这个命令可能还须要修改,每次wget成功都会输出一条消息。虽然使用了-q静默了日志输出,可是wget下载的内容仍是要输出的。这里用了-O参数指定到标准输出了。但愿没有任何输出的话仍是经过重定向到/dev/null来解决。

若是检查的逻辑不是那么简单,那么可能须要写一个脚原本调用:

HEALTHCHECK --interval=10s --timeout=5s --retries=3 \
    CMD /bin/sh /opt/health_test.sh

健康检查脚本的内容:

#!/bin/sh
ss -antl | grep 80
if [ $? ==0 ]; then
    exit 0
else
    exit 1
fi

检查容器健康状态
当容器指定了运行情况检查时,除了正常状态外,它还具备运行情况。这个状态最初是starting。每当健康检查经过时,它就会变成healthy(之前的状态)。通过必定数量的连续失败后,它就变成了unhealthy。

使用docker ps命令查看容器状态,能够看到健康检查的状态:

[root@Docker img4]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                           PORTS                   NAMES
b6dea4e8a808        ngx1-1              "nginx -g 'daemon of…"   2 seconds ago       Up 1 second (health: starting)   0.0.0.0:32776->80/tcp   ngx1
[root@Docker img4]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                   PORTS                   NAMES
b6dea4e8a808        ngx1-1              "nginx -g 'daemon of…"   4 seconds ago       Up 3 seconds (healthy)   0.0.0.0:32776->80/tcp   ngx1
[root@Docker img4]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS                   NAMES
21c80cbd2898        ngx1-1              "nginx -g 'daemon of…"   19 seconds ago      Up 18 seconds (unhealthy)   0.0.0.0:32777->80/tcp   ngx1
[root@Docker img4]#

这里有3种状态,启动后未作检查的(health: starting),检查成功的(healthy),确认失败的(unhealthy)。

SHELL

SHELL指令容许覆盖用于命令行的默认shell。Linux上的默认shell是["/bin/sh", “-c”],而在Windows上[“cmd”, “/S”, “/C”]。

语法格式:

SHELL [“executable”, “parameters”]

这里只有一种格式,必须使用JSON的格式。
了解一下,通常用不上

STOPSIGNAL

STOPSIGNAL指令设置将发送到容器的系统调用信号以退出。此信号能够是与内核的系统调用表中的位置匹配的有效无符号数,例如9,或SIGNAME格式的信号名,例如SIGKILL。

语法格式:

STOPSIGNAL signal

也是了解一下,通常用不上,而且这里也没有讲清楚。

ARG

设置变量命令,用于指定传递给构建运行时的变量。ARG命令定义了一个变量,在docker build建立镜像的时候,使用以下的build命令的选项来指定参数:

--build-arg <varname>=<value>

有多个变量,就屡次使用这个选项。

语法格式:

ARG <name>[=<default value>]

使用ARG定义变量的时候,能够加上默认值。

在docker build调用时,是没有--env选项来传环境变量的值的。因此要使用环境变量就只能使用环境变量默认值。而使用ARG变量,就能够向ARG变量传值了。
这个功能使得一个dockerfile能够适用于多个不一样的场景。

ONBUILD

用于在Dockerfile中定义一个触发器。当所构建的镜像被用作其它镜像的基础镜像时,该镜像中的触发器将会被触发。

语法格式:

ONBUILD <INSTRUCTION>

关键字后面跟的是一条正常的dockerfile的指令。

使用包含ONBUILD指令的Dockerfile构建的镜像应该使用特殊的标签,好比:ruby:2.0-onbuild

制做镜像

这里来制做几个镜像,把上面的指令试一下。

httpd镜像

建立工做目录:

[root@Docker ~]# mkdir img1
[root@Docker ~]# cd img1/
[root@Docker img1]# echo "<h1>Dockerfile httpd v1</h1>" >> index.html```

进入到工做目录中,建立一个index.html文件。

建立Dockerfile文件:

[root@Docker img1]# vim Dockerfile
# Description: First image
FROM busybox
LABEL maintainer="Steed Xu <x749b@163.com>"
COPY index.html /var/www/
VOLUME /var/www/
EXPOSE 80

工做目录建立镜像:

[root@Docker img1]# pwd
/root/img1
[root@Docker img1]# docker build -t tinyweb1 .
Sending build context to Docker daemon  3.072kB
Step 1/5 : FROM busybox
 ---> db8ee88ad75f
Step 2/5 : LABEL maintainer="Steed Xu <x749b@163.com>"
 ---> Running in 786069882d16
Removing intermediate container 786069882d16
 ---> 890d4ab97cd1
Step 3/5 : COPY index.html /var/www/
 ---> 5ba063c97abb
Step 4/5 : VOLUME /var/www/
 ---> Running in 18351ed13a43
Removing intermediate container 18351ed13a43
 ---> 50c1ae0d6350
Step 5/5 : EXPOSE 80
 ---> Running in 2e7885323425
Removing intermediate container 2e7885323425
 ---> 0656cb8b15bc
Successfully built 0656cb8b15bc
Successfully tagged tinyweb1:latest
[root@Docker img1]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
tinyweb1            latest              0656cb8b15bc        About a minute ago   1.22MB
busybox             latest              db8ee88ad75f        7 days ago           1.22MB
[root@Docker img1]#

运行镜像:

[root@Docker img1]# docker container run --name app1 --rm -d -P tinyweb1 httpd -f -h /var/www/
[root@Docker img1]# docker container port app1
80/tcp -> 0.0.0.0:32769
[root@Docker img1]#

这里使用了-P参数,映射的端口是随机的,使用命令查看到端口映射的状况。

进入到镜像内部。这个容器内执行的命令是httpd,里面没有shell,因此要用exec起一个shell而后才能进入到内部进行操做:

[root@Docker img1]# docker container exec -it app1 /bin/sh
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 httpd -f -h /var/www/
    7 root      0:00 /bin/sh
   11 root      0:00 ps
/ #

在容器内部使用mount命令查看挂载的卷:

[root@Docker ~]# docker container exec app1 mount | grep /var/www
/dev/mapper/centos-root on /var/www type xfs (rw,seclabel,relatime,attr2,inode64,noquota)
[root@Docker ~]#

使用环境变量

和上面的例子差很少,此次用到了环境变量,而且指定了CMD的命令:

[root@Docker img2]# pwd
/root/img2
[root@Docker img2]# vi Dockerfile 
# Description: Second image
FROM busybox
LABEL maintainer="Steed Xu <x749b@163.com>" \
      app="httpd"
ENV WEB_DOC_ROOT="/var/www/"
COPY index.html $WEB_DOC_ROOT
RUN echo '<h2>Hello</h2>' >> ${WEB_DOC_ROOT}/index.html
CMD httpd -f -h ${WEB_DOC_ROOT}
VOLUME $WEB_DOC_ROOT
EXPOSE 80

建立镜像:

[root@Docker img2]# docker build -t tinyweb2 .
Sending build context to Docker daemon  3.072kB
Step 1/8 : FROM busybox
 ---> db8ee88ad75f
Step 2/8 : LABEL maintainer="Steed Xu <x749b@163.com>"       app="httpd"
 ---> Running in e32a21495f34
Removing intermediate container e32a21495f34
 ---> 67f721eaedfa
Step 3/8 : ENV WEB_DOC_ROOT="/var/www/"
 ---> Running in ba2d4b1b2cf1
Removing intermediate container ba2d4b1b2cf1
 ---> 844a0dc0bbcc
Step 4/8 : COPY index.html $WEB_DOC_ROOT
 ---> 1af7b350289f
Step 5/8 : RUN echo '<h2>Hello</h2>' >> ${WEB_DOC_ROOT}/index.html
 ---> Running in f61511fe6020
Removing intermediate container f61511fe6020
 ---> 2305f131626e
Step 6/8 : CMD httpd -f -h ${WEB_DOC_ROOT}
 ---> Running in aff5ee08fb46
Removing intermediate container aff5ee08fb46
 ---> e24a76680fb0
Step 7/8 : VOLUME $WEB_DOC_ROOT
 ---> Running in b59115d147c3
Removing intermediate container b59115d147c3
 ---> a2e176849eaf
Step 8/8 : EXPOSE 80
 ---> Running in c981ab7e0137
Removing intermediate container c981ab7e0137
 ---> b8f7225afb7a
Successfully built b8f7225afb7a
Successfully tagged tinyweb2:latest
[root@Docker img2]#

运行容器:

[root@Docker img2]# docker container run --name app2 --rm -dP tinyweb2
7cbf240f0b323c329deb3dd36fd2ae6f1fe630c394f035f723f6124d5d6c5094
[root@Docker img2]# docker container port app2
80/tcp -> 0.0.0.0:32770
[root@Docker img2]#

这里没设么问题。

使用inspect命令查看容器默认启动额命令。能够查看镜像的inspect:

[root@Docker img2]# docker image inspect tinyweb2

也能够查看容器的inspect:

[root@Docker img2]# docker container inspect app2

查看Cmd的内容以下:

"Cmd": [
    "/bin/sh",
    "-c",
    "httpd -f -h ${WEB_DOC_ROOT}"
],

这里回过来看下Dockerfile中的CMD指令:

CMD httpd -f -h ${WEB_DOC_ROOT}

这里自动加上了/bin/sh -c。由于上面的CMD指令使用了第一种格式。若是使用指令的第二种格式,须要这么写:

CMD ["/bin/sh", "-c", "httpd -f -h ${WEB_DOC_ROOT}"]

或者避免使用环境变量,就能够不使用/bin/sh -c,这样是直接经过内核启动:

CMD ["httpd", "-f", "-h", "/var/www/"]

CMD错误的指定参数
CMD的第二种格式,列表的第一个元素是要执行的命令,以后的元素都是这个命令的参数。一个参数做为一个元素。这里的httpd命令包括它的参数所有加一块儿才是一个完整的/bin/sh命令的参数。
下面这条指令是不对的:

CMD ["/bin/sh", "-c", "httpd", "-f", "-h ${WEB_DOC_ROOT}"]

使用docker ps -a --no-trunc查看COMMAND字段,

"/bin/sh -c httpd -f '-h ${WEB_DOC_ROOT}'"
"/bin/sh -c 'httpd -f -h ${WEB_DOC_ROOT}'"

第一行是这里的错误格式解析出来的样子,这里的逻辑是执行命令/bin/sh,而且这个命令有4个参数。然而实际的状况是/bin/sh命令只有2个参数,一个是-c,还有一个是后面全部的内容。
第二行才是正确解析出来的样子,一个命令2个参数。

下面这条指令也是错误的:

CMD ["httpd", "-f", "-h /var/www/"]

须要把路径做为一个单独的参数。

遇到问题,有一下方式检查。
使用不带--rm参数的方式启动容器,这样保证容器启动失败后,不会自动删除。
使用docker logs命令查看容器内的日志,主要是默认启动命令执行后的错误消息。
使用docker ps -a --no-trunc查看完整的启动命令,这里能看到命令执行时的样子。
使用docker run命令的-it参数,而且修改默认启动命令为/bin/sh,这样能够进入到容器内部,执行命令。调试一个能够被正确执行的命令。

动态生成配置文件

这里是实现的是根据环境变量动态生成配置文件。首先用ENTRYPOINT指令运行一个脚本完成配置文件的动态生成,而且最后经过调用exec "$@"命令,使得CMD指令的内容能够被调用并替换称为主进程。

自定义一个脚本,完成动态写入配置文件的功能:

[root@Docker img3]# pwd
/root/img3
[root@Docker img3]# vi entrypoint.sh 
#!/bin/sh
cat > /etc/nginx/conf.d/www.conf << EOF
server {
    server_name ${HOSTNAME};
    listen ${IP:-0.0.0.0}:${PORT:-80};
    root ${NGX_DOC_ROOT};
}
EOF
exec "$@"

首先用cat配合EOF将内容写入到文件。cat在调用的写入内容的时候使用到了环境变量,最终写入的内容是根据环境变量替换后的内容。
完成脚本写入后,调用了exec "$@"。这句命令将后面的参数做为命令调用而且替换掉当前的进程。
还要给这个脚本赋予可执行权限:

[root@Docker img3]# chmod a+x entrypoint.sh

上面的脚本是要放在ENTRYPOINT中的,Dockerfile文件内容以下:

[root@Docker img3]# vi Dockerfile
# Description: Nginx image
FROM nginx:alpine
LABEL maintainer="Steed Xu <x749b@163.com>" \
      app="nginx"
ADD entrypoint.sh /bin/
CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT ["/bin/entrypoint.sh"]
EXPOSE 80

建立镜像:

[root@Docker img3]# docker build -t ngx1 .
Sending build context to Docker daemon  3.072kB
Step 1/7 : FROM nginx:alpine
 ---> 55ceb2abad47
Step 2/7 : LABEL maintainer="Steed Xu <x749b@163.com>"       app="nginx"
 ---> Running in 41050cf8bb89
Removing intermediate container 41050cf8bb89
 ---> 3bfa564b1ac3
Step 3/7 : ENV NGX_DOC_ROOT="/usr/share/nginx/html/"
 ---> Running in b8c929746310
Removing intermediate container b8c929746310
 ---> 2cb3fc5f5c8d
Step 4/7 : ADD entrypoint.sh /bin/
 ---> ecf314c9c29c
Step 5/7 : CMD ["nginx", "-g", "daemon off;"]
 ---> Running in 93d44caaa473
Removing intermediate container 93d44caaa473
 ---> eaf4da71e3c7
Step 6/7 : ENTRYPOINT ["/bin/entrypoint.sh"]
 ---> Running in ecffdcda0486
Removing intermediate container ecffdcda0486
 ---> 71f4222fc33e
Step 7/7 : EXPOSE 80
 ---> Running in 4582fa6067d2
Removing intermediate container 4582fa6067d2
 ---> 24de242b6dfc
Successfully built 24de242b6dfc
Successfully tagged ngx1:latest
[root@Docker img3]#

看一下配置文件:

[root@Docker img3]# docker container run --name app3 --rm ngx1 cat /etc/nginx/conf.d/www.conf
server {
    server_name 38dd4a661a6f;
    listen 0.0.0.0:80;
    root /usr/share/nginx/html/;
}
[root@Docker img3]# docker container run --name app3 --rm -e HOSTNAME=ngx1 ngx1 cat /etc/nginx/conf.d/www.conf
server {
    server_name ngx1;
    listen 0.0.0.0:80;
    root /usr/share/nginx/html/;
}
[root@Docker img3]#

配置文件会根据环境变量来动态改变。

大多数的dockerfile都是经过ENTRYPOINT脚本接受参数之后,再启动服务的。也就是这里的作法。

相关文章
相关标签/搜索