Dockerfile 指令用法

切记:docker file 需要有一个工作目录 文件名开头要大写,所有需要用到的目录必须在这个目录或者子目录里面。如果目录里面有不需要打包进镜像里需要在“.dockeringore”文件中把文件名写进去

FROM

  • FROM指令是最重要的且必须为Dockerfile文件的开篇第一个非注释行,用于为镜像文件构建过程指定基准镜像,后续的指令运行于此基准镜像所提供的运行环境
  • 实践中,基准镜像可以是任何可用的镜像文件,默认情况下,docker build 会在docker主机上查找指定的镜像文件,在其不存在时,则会从docker hub registry上拉取所需的镜像文件

语法:

FROM <repository>[:<tag>]或
FROM <repository>@<digest>
  • <repository>:指定作为base image的名称
  • <tag>:base iamge的标签,为可选项,省略时默认为latest;

MAINTAINER 指定作者邮箱信息

  • 用于让Dockerfile制作者提供本人的详细信息
  • Dockerfile并不限制MAINTAINER指令可在出现的位置,单推荐将其放在FROM指令之后

语法:

MAINTAINER <authtor's detail>
    <authtor's detail> 可是任何文本信息,但约定要俗成地使用作者名称及邮件地址

示例:
MAINTAINER "liliang <[email protected]>"

COPY

  • 用于从Docker主机复制文件至创建的新镜像文件

语法:

COPY <src> ...<dest>
COPY ["src" ..."<dest>"]
  • <src>:要复制的源文件或目录,支持使用通配符
  • <dest>:目标路径,即正在创建的image的文件的系统路径;建议为<dest>使用绝对路径,否则COPY指定则以WORKDIR为起始路径**

==注意:在路径中有空白字符时,通常用第二种格式==

文件复制准则

  • <src>必须是build上下文中的路径,不能是父目录中的文件ro
  • 如果<src>目录,则其内部的文件或子目录会被递归复制,但<src>目录自身不会被复制
  • 如果制定了<src>,活在<src>中使用了通配符,则<dest>必须是目录,且必须以/结尾
  • 如果<dest>事先不存在,他将会被自动创建,这其中包含其父目录路径

    ADD

  • ADD指令类似COPY指令,ADD支持使用TAR和URL路径

语法:

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

操作准则

  • 同COPY指令
  • 如果<src>为URL且<dest>不以/结尾,则<src>指定的文件将被下载并直接创建为<dest>;如果<dest>以/极为,则文件名URL指定的文件将直接被下载并保存到<dest>/<filename>
  • 如果<src>是一个本地系统上的压缩格式的tar文件,他将直接被展开为目录,其行为类似于“tar -x”命令;然而,通过URL获取到的tar文件将不会自动展开;
  • 如果<src>有多个,或者间接或直接使用了通配符,则<dest>必须是一个已/结尾的目录路径;如果<dest>不以/结尾将被视作为普通文件,<src>的内容将被直接写入到<dest>文件中

    WORKDIR

  • 用于为Dockerfile中所有的RUN,CMD,ENTRYPOINT,COPY和ADD指定设定工作目录

语法:

WORKDIR <dirpath>
  • 在Dockerfile文件中,WORKDIR指令可出现多次,其路径也可为相对路径,不过,其相对路径是对此前一个WORKDIR指令的相对路径
  • 另外,WORKDIR也可以调用由ENV定义的变量

示例:

WORKDIR /var/usr/local
WORKDIR $PATH        

VOLUME

  • 用于在image中创建一个挂在点目录,以Docker host上的卷或其它容器的卷

语法:

VOLUME </data/html>
VOLUME ["</data/www>"]
  • 如果挂在点目录路径下之前在文件中存在,docker run 命令会在挂载完成之后将此前目录下的所有文件复制到挂在卷中

    EXPOSE 定义容器暴露的默认端口

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

语法:

EXPOSE <port>|[/<protocol>] <port>[/<protocol>]
EXPOSE 可一次指定多个端口,
例如:
    EXPOSE 80/tcp 80/udp

==注意:要真正让外部能访问到容器,需要在docker run 时加上-P选项==


ENV

  • 用于为镜像定义所需的环境变量,并可被Dockerfile文件中位于其后的指令(如ENV、ADD、COPY等)所调用
  • 调用格式为$variable_name或者${variable_name}

语法

ENV <key> <value>或
ENV <key>=<value> ...
  • 第一种格式中,<key>之后的所有内容均会被视作其<value>其组成部分,因此,一次只能设置一个变量;
  • 第二种格式,可一次设置多个变量,每一个变量为一个“<key>=<value>”的键值对,如果<value>中包含多个空格,可以以反斜线(\)进行转义,也可通过对<value>加引号进行标示;另外反斜线也可以用与续行;
  • 定义多个变量建议使用第二种格式,以便在同一层完成所有功能。

RUN

  • 用于指定docker bulid过程中运行的程序,其可以是任何命令

语法

RUN <command>
RUN ["executable", "param1", "param2"]
  • 第一种格式中<command>通常是一个shel命令,且以/bin/sh -c 来运行它,这意味着此进程在容器中pid不为1,不能接收Unix信号,因此使用docker stop <container>命令停止容器时,此进程接受不到SIGTERM信号
  • 第二种语法格式中的参数是一个JSON格式的数组,其中<executable> 为要运行的命令,后面的<paramN>为传递给命令的选项和参数,然而,此格式指定的命令不会以/bin/sh -c来运行,因此常见的shell操作如变量替换以及通配符(*.)等将不会运行,不过,要运行的命令依赖shell特性的话,可以将其替换为下面的格式;
    RUN ["/bin/sh","-c","exexcutable","param1","param2",....]

CMD 容器启动命令

  • 类似于RUN指令,CMD也可以运行任何命令或应用程序,不过二者运行时间点不同
    RUN指令运行在docker bulid构建为镜像文件的过程中,而CMD指令运行在基于Dockerfile构建出的镜像文件启动为为容器的过程中
  • CMD指令的首要目的在于为启动容器指定默认要运行的程序,且其运行结束后容器也将终止,不过,CMD指令其可以被docker run时命令行选项所覆盖
    CMD指令可以存在多个,但只有最后一个生效!

语法

CMD <command>或
CMD ["executable","param1","param2",...]
CMD ["param1","param2",...]
  • 前两种的意义都与RUN相同
  • 第三种语法则用于ENTRYPOINT指令提供默认参数

ENTRYPOINT

  • 类似于CMD指令的功能,用于为容器指定默认运行层序,从而使得容器像一个单独可执行的程序,
  • 与CMD不同的是,有ENTRYPOINT启动的程序不会被docker run命令行指定的参数所覆盖,而且,这些命令还会被当做参数传递给ENTRYPOINT指定的程序
    不过,docker run命令的--entrypoint选项的参数可覆盖ENTRYPOINT指令指定的程序

语法

ENTRYPOINT <command>
ENTRYPOINT ["<executable>","param1","param2",...]
  • docker run 命令传入的命令参数会覆盖CMD指令的内容并附加到ENTRYPOINT命令最后作为其参数使用
    Dockerfile文件中也可存在多个ENTRYPIONT指令,但仅有最后一个会生效

示例:利用entrypoint.sh脚本初始化nginx环境,可在docker run 通过-e选项对entyypoint.sh脚本传值

# dockerfile工作目录准备的文件
[root@node01 NG_Dockerfile]# ls
 Dockerfile  entrypoint.sh(+执行权限)  index.html
# entrypoint.sh文件,"注意要加执行权限"
[root@node01 NG_Dockerfile]# cat entrypoint.sh 
#!/bin/sh
#
cat > /etc/nginx/conf.d/www.conf <<EOF     ----利用cat的多行重定向创建www.conf。
server {
        server_name ${HOSTNAME};
        listen ${IP:-0.0.0.0}:${PORT:-8080}; 
        root /opt/www/html/;
}
EOF

exec "$@"  创建完www.conf,替换当前进程运行后续CMD指令指定的命令。
# index.html网页测试文件
[root@node01 NG_Dockerfile]# cat index.html 
<h1>welcome to Nginx Server.</h1>

#Dockerfile文件
[root@node01 NG_Dockerfile]# cat Dockerfile 
 FROM nginx:1.14-alpine                             指定bese image
 MAINTAINER "liliang [email protected]"    指定作者
 ENV WEB_ROOT="/opt/www/html/"                      设置变量名WEB_ROOT,其中为"/opt/www/html/"
 COPY index.html ${WEB_ROOT}                        复制index.html文件到/opt/www/html/目录下,"注意这里的index.html必须在dockerfile同级目录或子目录里"  
 COPY entrypoint.sh /bin/                           复制上面写好的且有执行权限的"entrypoint.sh"脚本到"/bin"目录下
 #EXPOSE 80/tcp                                     定义容器暴露80/tcp端口
 CMD ["/usr/sbin/nginx","-g","daemon off;"]         定义容器默认运行的程序
 ENTRYPOINT ["/bin/entrypoint.sh"]                  这里的作用就是先运行"/bin/entrypoint.sh"脚本,然后在运行CMD指定的程序。

USER 指定当前用户

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

语法:

USER <UID>|<UserNmae>

==需要注意的是,<UID>可以是任意数字,但实践中必须为/etc/passwd中某用户的有效UID,否则,docker run 命令将会运行失败==


HEALTHCHECK 健康检查

"语法"

HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令
HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
  • HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12 引入的新指令。
  • 在没有 HEALTHCHECK 指令前,Docker 引擎只可以通过容器内主进程是否退出来判断容器是否状态异常。很多情况下这没问题,但是如果程序进入死锁状态,或者死循环状态,应用进程并不退出,但是该容器已经无法提供服务了。在 1.12 以前,Docker 不会检测到容器的这种状态,从而不会重新调度,导致可能会有部分容器已经无法提供服务了却还在接受用户请求。
  • 而自 1.12 之后,Docker 提供了 HEALTHCHECK 指令,通过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。
  • 当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting,在 HEALTHCHECK 指令检查成功后变为 healthy,如果连续一定次数失败,则会变为 unhealthy。

HEALTHCHECK 支持下列选项:

  • --interval=<间隔>:两次健康检查的间隔,默认为 30 秒;
  • --timeout=<时长>:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;
  • --retries=<次数>:当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3 次。
    和 CMD, ENTRYPOINT 一样,HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。
    在 HEALTHCHECK [选项] CMD 后面的命令,格式和 ENTRYPOINT 一样,分为 shell 格式,和 exec 格式。命令的返回值决定了该次健康检查的成功与否:0:成功;1:失败;2:保留,不要使用这个值。
  • 假设我们有个镜像是个最简单的 Web 服务,我们希望增加健康检查来判断其 Web 服务是否在正常工作,我们可以用 curl 来帮助判断,其 Dockerfile 的 HEALTHCHECK 可以这么写:
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
  CMD curl -fs http://localhost/ || exit 1

这里我们设置了每 5 秒检查一次(这里为了试验所以间隔非常短,实际应该相对较长),如果健康检查命令超过 3 秒没响应就视为失败,并且使用 curl -fs http://localhost/ || exit 1 作为健康检查命令。

使用 docker build 来构建这个镜像:

$ docker build -t myweb:v1 .

构建好了后,我们启动一个容器:

$ docker run -d --name web -p 80:80 myweb:v1

当运行该镜像后,可以通过 docker container ls 看到最初的状态为 (health: starting):

$ docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                            PORTS               NAMES
03e28eb00bd0        myweb:v1            "nginx -g 'daemon off"   3 seconds ago       Up 2 seconds (health: starting)   80/tcp, 443/tcp     web

在等待几秒钟后,再次 docker container ls,就会看到健康状态变化为了 (healthy):

$ docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                    PORTS               NAMES
03e28eb00bd0        myweb:v1            "nginx -g 'daemon off"   18 seconds ago      Up 16 seconds (healthy)   80/tcp, 443/tcp     web

如果健康检查连续失败超过了重试次数,状态就会变为 (unhealthy)。

为了帮助排障,健康检查命令的输出(包括 stdout 以及 stderr)都会被存储于健康状态里,可以用 docker inspect 来查看。

$ docker inspect --format '{{json .State.Health}}' web | python -m json.tool
{
    "FailingStreak": 0,
    "Log": [
        {
            "End": "2016-11-25T14:35:37.940957051Z",
            "ExitCode": 0,
            "Output": "<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</title>\n<style>\n    body {\n        width: 35em;\n        margin: 0 auto;\n        font-family: Tahoma, Verdana, Arial, sans-serif;\n    }\n</style>\n</head>\n<body>\n<h1>Welcome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.</p>\n\n<p>For online documentation and support please refer to\n<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\nCommercial support is available at\n<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n<p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n",
            "Start": "2016-11-25T14:35:37.780192565Z"
        }
    ],
    "Status": "healthy"
}

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 的内容修改。

    ONBUILD 为他人做嫁衣裳

语法

格式:ONBUILD <其它指令>。
  • ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
  • Dockerfile 中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD 是为了帮助别人定制自己而准备的。
  • 假设我们要制作 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,生成应用镜像。

猜你喜欢

转载自blog.51cto.com/13598893/2642193