docker6:Dockerfile详解

简单介绍

  dockerfile 是一种可以被 docker 程序解释的脚本,由一条条指令组成。通过编写 dockerfile 可以做到:

  • 定制化 docker 镜像。
  • 整合下载镜像、启动容器、编辑可写层等等操作,可重复构建,提升效率。
  • 随时维护、修改、分享 dockerfile。

dockerfile编写格式

  1. dockerfile 整体由两类语句组成:

    • 注释信息。
    • 指令和参数(一行一个指令)。
  2. dockerfile 指令名不区分大小写,但为了方便和参数区分,通常使用大写字母。

  3. dockerfile 中指令按从上到下顺序依次执行。

  4. dockerfile 中第一个非注释行必须是 FROM 指令,其用来指定制作当前镜像依据哪个基础镜像。

  5. dockerfile 中需要调用的文件必须跟 dockerfile 文件在同一目录或者其子目录下,其它路径无效。

dockerfile指令介绍

FROM

  • 介绍

    • FROM 指令必须为 dockerfile 文件的第一个非注释行,用于指定构建镜像所使用的基础镜像,后续的指令运行都要依靠此基础镜像所提供的的环境(简单说就是假如 dockerfile 中所引用的基础镜像里面没有 mkdir 命令,那后续的指令是没法使用 mkdir 命令的。)
    • 实际使用中,如果没有指定仓库,docker build 会先从本机查找是否有此基础镜像,如果没有会默认去 Docker Hub Registry 拉取,再找不到就会报错。
  • 语法格式

    FROM [--platform=<platform>] <image> [AS <name>]
    FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
    FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
    
  • 语法说明

    • Digest:镜像的哈希码,防止镜像被冒名顶替。

MAINTAINER(deprecated)

  • 介绍

    • 用于让 dockerfile 的作者提供个人的信息。
    • dockerfile 并不限制 MAINTAINER 指令的位置,但是建议放在 FROM 指令之后。
    • 在较新的 docker 版本中,已经被 LABEL 替代。
  • 语法格式

    MAINTAINER <name>
    

LABEL

  • 介绍

    • 同 docker run -l。
    • 让用户为镜像指定各种元数据(键值对的格式)。
  • 语法格式

    LABEL <key>=<value> <key>=<value> <key>=<value> ...
    

COPY

  • 介绍

    • 复制宿主机上的文件到目标镜像中。
  • 语法格式

    COPY [--chown=<user>:<group>] <src>... <dest>
    COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
    
  • 语法说明

    • <src>:要复制的源文件或者目录,支持通配符。
    • <src> 必须是 build 上下文中的目录,不能是其父目录中的文件。
    • 如果 <src> 是目录,则其内部的文件或则子目录会被递归复制,但 <src> 目录本身不会被复制。
    • <dest>:目标路径,即正创建的镜像的文件系统路径,建议使用绝对路径,否则,COPY 指令会以 WORKDIR 为其起始路径。
    • 如果 <dest> 不存在,它将会被自动创建,包括其父目录路径。
    • 如果指定了多个 <src>,或者 <src> 中使用通配符,则 <dest> 必须是一个目录,且必须以 / 结尾。
    • 如果路径中如果包含空白字符,建议使用第二种格式用引号引起来,否则会被当成两个文件。

ADD

  • 介绍

    • ADD 指令跟 COPY 类似,不过它还支持使用 tar 文件和 URL 路径。

      • 当拷贝的源文件是 tar 文件时,会自动展开为一个目录并拷贝进新的镜像中,而通过 URL 获取到的 tar 文件不会自动展开。
      • 主机可以联网的情况下,docker build 可以将网络上的某文件引用下载并打包到新的镜像中。
  • 语法格式

    ADD [--chown=<user>:<group>] <src>... <dest>
    ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
    

WORKDIR

  • 介绍

    • 同 docker run -w。
    • 指定工作目录,可以指多个,每个 WORKDIR 只影响他下面的指令,直到遇见下一个 WORKDIR 为止。
    • WORKDIR 也可以调用由 ENV 指令定义的变量。
  • 语法格式

    WORKDIR path			# path为相对路径或者绝对路径。
    
  • 语法说明

    • 相对路径是相对于上一个 WORKDIR 指令的路径,如果上面没有 WORKDIR 指令,那就是当前 dockerfile 文件的目录。

VOLUME

  • 介绍

    • docker run -v 简化版。
    • 用于在镜像中创建一个挂载点目录。上一章中有提到 Volume 的几种挂载方式,忘记的可以回顾下。在 dockerfile 中只支持 docker 管理的卷,也就是说只能指定容器内的路径,不能指定宿主机的路径。
  • 语法格式

    VOLUME ["<mountpoint>"]
    

EXPOSE

  • 介绍

    • 同 docker run --expose。
    • 指定容器中待暴露的端口。比如容器提供的是一个 https 服务且需要对外提供访问,那就需要指定待暴露 443 端口,然后在使用此镜像启动容器时搭配 -P 的参数才能将待暴露的状态转换为真正暴露的状态,转换的同时 443 也会转换成一个随机端口,跟 -p :443 一个意思。
    • EXPOSE 指令可以一次指定多个端口,例如:EXPOSE 11111/udp 11112/tcp
  • 语法格式

    EXPOSE <port> [<port>/<protocol>...]
    
  • 语法说明

    • <protocol> 用于指定协议类型,如果不指定,默认 TCP 协议。

ENV

  • 介绍

    • 同 docker run -e。
    • 为镜像定义所需的环境变量,并可被 ENV 指令后面的其它指令所调用。调用格式为 $variable_name 或者 ${variable_name}。
    • docker run -e 的优先级高于 dockerfile 中的 ENV,但是不会影响到 dockerfile 中已经引用过此变量的文件名,下面有举例说明。
  • 语法格式

    ENV <key> <value>
    ENV <key>=<value> ...
    
  • 语法说明

    • 第一种格式一次只能定义一个变量,<key> 之后所有内容都会被视为 <value> 的组成部分。
    • 第二种格式一次可以定义多个变量,每个变量为一个 "=" 的键值对,如果 <value> 中包含空格,可以用反斜线 \ 进行转义,也可以为 <value> 加引号,另外参数过长时可用反斜线做续行。
    • 定义多个变量时,建议使用第二种方式,因为 dockerfile 中每一行都是一个镜像层,构建起来比较吃资源。
  • 举例

    # 删除历史容器(注意在测试主机操作,别误删掉重要容器)。
    [0 root@docker1,172.16.15.21:~]# docker stop `docker ps -aq`
    [0 root@docker1,172.16.15.21:~]# docker rm `docker ps -aq`
    # 编写dockerfile将testfile拷贝到容器内。
    [0 root@docker1,172.16.15.21:~]# mkdir -p /server/ops_dockerfile/test1
    [0 root@docker1,172.16.15.21:~]# cd /server/ops_dockerfile/test1
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test1]# echo 'test dockerfile1' >testfile
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test1]# cat Dockerfile
    # test dockerfile1
    FROM busybox:latest
    ENV file=aaa
    ADD ./testfile /usr/local/${file}/
    # 构建 dockerfile。
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test1]# docker build -t busybox:v1 .
    	# -t:指定镜像名和标签。
        # .:代表当前 PATH。
    # 根据此镜像启动容器,查看file变量的值。
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test1]# docker run --name bbox01 --rm busybox:v1 ls /usr/local/aaa/
    testfile
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test1]# docker run --name bbox01 --rm busybox:v1 printenv | grep file
    file=aaa
    # 接下来使用docker run -e来指定file变量,再查看file变量的值。
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test1]# docker run --name bbox01 -e file=bbb --rm busybox:v1 ls /usr/local/aaa/
    testfile
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test1]# docker run --name bbox01 -e file=bbb --rm busybox:v1 ls /usr/local/bbb/
    ls: /usr/local/bbb/: No such file or directory
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test1]# docker run --name bbox01 -e file=bbb --rm busybox:v1 printenv | grep file
    file=bbb
    # 由此例证明了上面指令介绍中的第三句话。这是因为docker build属于第一阶段,docker run属于第二阶段。第一阶段定义file变量的值aaa已经被引用了,生米已煮成熟饭,后续阶段再改file变量的值也影响不了aaa。
    

RUN

  • 介绍

    • 用于指定 docker build 过程中运行的程序,可以是任何命令。
    • RUN 指令后所执行的命令必须在 FROM 指令后的基础镜像中存在才行。
  • 语法格式

    RUN <command>
    RUN ["executable", "param1", "param2"]
    
  • 语法说明

    • <command> 通常是一个 shell 命令,系统默认会把后面的命令作为 shell 的子进程来运行,以 /bin/sh -c 来运行它,比如:RUN /bin/bash -c "echo $HOME" 。
    • 第二种格式会被解析成 JSON 数组,这代表必须括号内的引用必须使用双引号。此种方式调用 shell,需使用 RUN [ "sh", "-c", "echo $HOME" ],再比如RUN [ "python3", "test.py" ]

CMD

  • 介绍

    • 指定启动容器的默认要运行的程序,也就是 PID 为 1 的进程命令,其运行结束后容器也会终止。如果不指定,默认是 bash。

    • CMD 指令指定的默认程序会被 docker run 命令行指定的参数所覆盖。

    • dockerfile 中可以存在多个 CMD 指令,但仅最后一个生效。因为一个 docker 容器只能运行一个 PID 为 1 的进程。

    • 类似于 RUN 指令,也可以运行任意命令或程序,但是两者的运行时间点不同。

      • RUN 指令运行在 docker build 的过程中,而 CMD 指令运行在基于新镜像启动容器时(docker run)。
  • 语法格式

    CMD command param1 param2
    CMD ["executable","param1","param2"]
    CMD ["param1","param2"]
    
  • 语法说明

    • 前两种语法格式同 RUN 指令。
    • 第三种则需要结合 ENTRYPOINT 指令使用,CMD 指令后面的命令作为 ENTRYPOINT 指令的默认参数。如果 docker run 命令行结尾有参数指定,那 CMD 后面的参数不生效。

ENTRYPOINT

  • 介绍

    • 类似 CMD 指令的功能,用于为容器指定默认运行程序。
    • dockerfile 中可以存在多个 ENTRYPOINT 指令,但仅最后一个生效。
    • 与 CMD 区别在于,由 ENTRYPOINT 启动的程序不会被 docker run 命令行指定的参数所覆盖,而且这些命令行参数会被当做参数传递给 ENTRYPOINT 指令指定的程序。
    • 不过,docker run 的 --entrypoint 选项的参数可以覆盖 ENTRYPOINT 指定的默认程序,下面举例说明。
  • 语法格式

    ENTRYPOINT command param1 param2
    ENTRYPOINT ["executable", "param1", "param2"]
    
  • 举例

    • CMD 指令:测试命令行参数可以代理 dockerfile 中 CMD 指定的程序。

      # 编写dockerfile
      [0 root@docker1,172.16.15.21:~]# mkdir -p /server/ops_dockerfile/test2
      [0 root@docker1,172.16.15.21:~]# cd /server/ops_dockerfile/test2
      [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test2]# cat Dockerfile
      # test dockerfile2
      FROM busybox:latest
      LABEL maintainer="merle <merle@freeit.com>" app="httpd"
      ENV WEBDIR="/server/sites/test/"
      RUN mkdir -p ${WEBDIR} && echo 'This is a test web' >${WEBDIR}/index.html
      CMD [ "sh", "-c", "/bin/httpd -f -h ${WEBDIR}" ]
      # 构建
      [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test2]# docker build -t httpd:v1 .
      # 启动容器,并在结尾传入命令。
      [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test2]# docker run -it --rm --name web01 httpd:v1 ls /server/sites/test/ 
      index.html
      # 可以看出命令行的参数已经替代了原本的CMD指令指定的httpd。
      
    • ENTRYPOINT 指令。

      # 编写dockerfile
      [0 root@docker1,172.16.15.21:~]# mkdir -p /server/ops_dockerfile/test3
      [0 root@docker1,172.16.15.21:~]# cd /server/ops_dockerfile/test3
      [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test3]# cat Dockerfile
      # test dockerfile3
      FROM busybox:latest
      LABEL maintainer="merle <merle@freeit.com>" app="httpd"
      ENV WEBDIR="/server/sites/test/"
      RUN mkdir -p ${WEBDIR} && echo 'This is a test web' >${WEBDIR}/index.html
      ENTRYPOINT [ "sh", "-c", "/bin/httpd -f -h ${WEBDIR}" ]
      # 构建
      [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test3]# docker build -t httpd:v2 .
      # 启动容器,并在结尾传入命令,可以看到夯在了前台没有反应。这是因为容器把"ls /server/sites/test/"当做参数传给了httpd程序,只是httpd不识别罢了
      [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test3]# docker run -it --rm --name web01 httpd:v2 ls /server/sites/test/
      # 新打开一个xshell窗口,kill掉web01容器,使用--entrypoint参数再测试一下。
      [0 root@docker1,172.16.15.21:~]# docker kill web01
      [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test3]# docker run -it --rm --entrypoint="" --name web01 httpd:v2 ls /server/sites/test/
      index.html			# 可以看出使用"--entrypoint"参数后,docker run结尾的参数覆盖了ENTRYPOINT指定的默认程序。
      
    • CMD 指令 + ENTRYPOINT 指令。

      # 使用CMD的第三种语法:CMD指令后面的内容作为参数传给ENTRYPOINT指定后面的程序。
      [0 root@docker1,172.16.15.21:~]# mkdir -p /server/ops_dockerfile/test4
      [0 root@docker1,172.16.15.21:~]# cd /server/ops_dockerfile/test4
      [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test4]# cat Dockerfile
      # test dockerfile4
      FROM busybox:latest
      LABEL maintainer="merle <merle@freeit.com>" app="httpd"
      ENV WEBDIR="/server/sites/test/"
      RUN mkdir -p ${WEBDIR} && echo 'This is a test web' >${WEBDIR}/index.html
      CMD [ "/bin/httpd -f -h ${WEBDIR}" ]
      ENTRYPOINT [ "sh", "-c" ]
      # 构建。
      [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test4]# docker build -t httpd:v3 .
      # 启动容器,并在结尾传入命令,此种方式docker run结尾的参数需要用双引号。
      [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test4]# docker run -it --rm --name web01 httpd:v3 "ls /server/sites/test/"
      index.html			# 可以看到docker run结尾的参数覆盖了CMD后面指定的参数。
      

USER

  • 介绍

    • 用于指定 docker build 过程中任何 RUN、CMD 等指令的用户名或者 UID。
    • 默认情况下容器的运行用户为 root。
  • 语法格式

    USER <user>[:<group>]
    USER <UID>[:<GID>]
    
  • 语法说明

    • 实践中 UID 需要是 /etc/passwd 中某用户的有效 UID,否则 docker run 命令将运行失败。

HEALTHCHECK

  • 介绍

    • 顾名思义,健康检查。此指令的就是告诉 docker 如何检查容器是否正常工作。
  • 语法格式

    HEALTHCHECK [OPTIONS] CMD command
    HEALTHCHECK NONE
    
  • 语法说明

    • HEALTHCHECK 指令就是定义一个 CMD,在 CMD 后面编写一条命令去判断我们的服务运行是否正常。

    • 检查肯定不是一次性的,所以 OPTIONS 就是指定检查的频率。

      • --interval=DURATION:每隔多久检查一次,默认30s。
      • --timeout=DURATION:超时时长,默认30s。
      • --start-period=DURATION:启动健康检查的等待时间,默认 0s。因为容器启动成功时,进程不一定立马就启动成功,那过早开始检查就会返回不健康。
      • --retries=N:检查一次失败就返回不健康未免太武断,所以默认值是3,三次重试。
    • CMD 健康检测命令发出时,返回值有三种情况:

      • 0:成功。
      • 1:不健康。
      • 2:保留,无实际意义。
    • HEALTHCHECK NONE 就是不做健康检查。

SHELL

  • 介绍

    • 用来指定运行程序默认要使用的 shell 类型,因为 windows 环境默认是 powershell。
    • 此指令一般不会使用。
  • 语法格式

    SHELL ["executable", "parameters"]
    

STOPSIGNAL

  • 介绍

    • 指定发送使容器退出的系统调用信号。docker stop 之所以能停止容器,就是发送了15的信号给容器内 PID 为 1 的进程。
    • 此指令一般不会使用。
  • 语法格式

    STOPSIGNAL signal
    

ARG

  • 介绍

    • ARG 命令跟 ENV 类似,也是指定一个变量,但 ENV 指令配合 -e 参数是在 docker run 过程中传参,而使用 ARG 指令配合 --build-arg 参数是在 docker build 过程中传参,下面举例说明。
  • 语法格式

    ARG <name>[=<default value>]
    
  • 举例

    # 编写dockerfile
    [0 root@docker1,172.16.15.21:~]# mkdir -p /server/ops_dockerfile/test5
    [0 root@docker1,172.16.15.21:~]# cd /server/ops_dockerfile/test5
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test5]# echo 'This is a test web' >index.html
    # 测试脚本内容
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test5]# cat entrypoint.sh 
    #!/bin/sh
    
    cat > /etc/nginx/conf.d/www.conf <<EOF
    server {
        listen ${IP:-0.0.0.0}:${PORT:-80};
        server_name $HOSTNAME;
        root ${NGINX_DIR};
    }
    EOF
    exec "$@"			# 上面说过,CMD和ENTRYPOINT联用时,CMD后面的内容会作为参数传递给ENTRYPOINT后面的程序。此处的"$@"意思是接收entrypoint.sh后面的所有参数(也就是CMD后面的所有内容),exec意思是执行接收到的参数并替换当前进程。
    # 添加可执行权限。
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test5]# chmod +x entrypoint.sh
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test5]# cat Dockerfile
    # test dockerfile5
    FROM nginx:1.18.0-alpine
    ARG AUTHOR="merle <merle@freeit.com>"
    LABEL maintainer=${AUTHOR}
    ENV NGINX_DIR="/server/sites/test/"
    ADD index.html ${NGINX_DIR}
    ADD entrypoint.sh /bin/
    CMD [ "nginx", "-g", "daemon off;" ]
    ENTRYPOINT [ "/bin/entrypoint.sh" ]		# 利用此脚本初始化一个配置文件,然后调用脚本中的exec "$@"来执行CMD后面的内容,也就是前台启动nginx。
    # 构建。
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test5]# docker build --build-arg AUTHOR="onion <onion@freeit.com>" -t nginx:v1 .
    # 可以看到命令行--build-arg传递的参数覆盖了dockerfile中的AUTOR。
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test5]# docker image inspect -f '{{.ContainerConfig.Labels}}' nginx:v1
    map[maintainer:onion <onion@freeit.com>]
    # PS:从处只是以maintainer举例,还可以在构建不同java镜像时传递不通的jar包名等等。
    

ONBUILD

  • 介绍

    • 用于在 dockerfile 中定义一个触发器。
    • ONBUILD 后面指定的指令在 docker build 时不执行,构建完的镜像在被另一个 dockerfile 文件中 FROM 指令所引用的时才会触发执行。
  • 语法格式

    ONBUILD <INSTRUCTION>
    
  • 语法说明

    • 由于 ONBUILD 不能自我嵌套,且不会触发 FROM 和 MAINTAINER 指令,所以一般情况下使用 RUN 或者 ADD 作为触发指令。
    • 在使用 COPY 指令时,应该注意后续引用该镜像的 dockerfile 的同级目录下是否有被拷贝的文件。
  • 举例

    # 编写dockerfile
    [0 root@docker1,172.16.15.21:~]# mkdir -p /server/ops_dockerfile/test6
    [0 root@docker1,172.16.15.21:~]# cd /server/ops_dockerfile/test6
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# cp ../test5/* ./
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# cat Dockerfile 
    # test dockerfile6
    FROM nginx:1.18.0-alpine
    LABEL maintainer="merle <merle@freeit.com>"
    ENV NGINX_DIR="/server/sites/test/"
    ADD index.html ${NGINX_DIR}
    ADD entrypoint.sh /bin/
    ONBUILD add http://nginx.org/download/nginx-1.18.0.tar.gz /usr/local/
    CMD [ "nginx", "-g", "daemon off;" ]
    ENTRYPOINT [ "/bin/entrypoint.sh" ]
    # 构建
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# docker build -t nginx:v2 .
    # 启动容器会发现ONBUILD指令并没有执行。
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# docker run -di --name web01 nginx:v2 
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# docker exec -it web01 ls /usr/local/
    bin    lib    share
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# docker exec -it web01 ps
    PID   USER     TIME  COMMAND
        1 root      0:00 nginx: master process nginx -g daemon off;
        8 nginx     0:00 nginx: worker process
       27 root      0:00 ps
    # 现在修改dockerfile,将FROM指向刚刚构建的镜像,并删除ONBUILD指令。
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# cat Dockerfile
    # test dockerfile6
    FROM nginx:v2
    LABEL maintainer="merle <merle@freeit.com>"
    ENV NGINX_DIR="/server/sites/test/"
    ADD index.html ${NGINX_DIR}
    ADD entrypoint.sh /bin/
    CMD [ "nginx", "-g", "daemon off" ]
    ENTRYPOINT [ "/bin/entrypoint.sh" ]
    # 构建新镜像。
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# docker build -t nginx:v3 .
    ...
    # Executing 1 build trigger
    Downloading [==================================================>]   1.04MB/1.04MB			# 触发执行了nginx:v2中ONBUILD后面的指令。
    ...
    # 此时查看ONBUILD及后面的指令也被执行了。
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# docker run -di --name web02 nginx:v3
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# docker exec -it web02 ls /usr/local/
    bin                  nginx-1.18.0.tar.gz
    lib                  share
    [0 root@docker1,172.16.15.21:/server/ops_dockerfile/test6]# docker exec -it web02 ps
    PID   USER     TIME  COMMAND
        1 root      0:00 nginx: master process nginx -g daemon off;
        9 nginx     0:00 nginx: worker process
       16 root      0:00 ps
    

参考资料

写作不易,转载请注明出处,谢谢~~

posted @ 2019-07-23 16:49  loosenc  阅读(25519)  评论(0编辑  收藏  举报