zoukankan      html  css  js  c++  java
  • docker(六):Dockerfile详解

    一、Dockerfile Introduction

      前面的docker镜像管理章节有说到,构建镜像的方式有两种:一种是基于容器制作,另一种就是通过Dockerfile。Dockerfile其实就是我们用来构建Docker镜像的源码,当然这不是所谓的编程源码,而是一些命令的组合,只要理解它的逻辑和语法格式,就可以编写Dockerfile了。

      简要概括Dockerfile的作用:它可以让用户个性化定制Docker镜像。因为工作环境中的需求各式各样,网络上的镜像很难满足实际的需求。

    二、Dockerfile Format

    1. Dockerfile整体就两类语句组成:

      • # Comment 注释信息
      • Instruction arguments 指令 参数,一行一个指令。
    2. Dockerfile文件名首字母必须大写。

    3. Dockerfile指令不区分大小写,但是为方便和参数做区分,通常指令使用大写字母。

    4. Dockerfile中指令按顺序从上至下依次执行。

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

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

    三、Dockerfile Instructions

    FROM

    • Introduction

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

      • FROM <Repository>[:<Tag>]

      • FROM <Repository>@<Digest>

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

    MAINTAINER(deprecated)

    • Introduction

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

      • MAINTAINER "merle@example.com"

    LABEL

    • Introduction

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

      • LABEL <key>=<value> <key>=<value>

    COPY

    • Introduction

      • 复制宿主机上的文件到目标镜像中
    • Syntax

      • COPY <src>... <dest>

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

        • <src>:要复制的源文件或者目录,支持通配符
        • <dest>:目标路径,即正创建的镜像的文件系统路径,建议使用绝对路径,否则,COPY指令会以WORKDIR为其起始路径。
        • 如果路径中如果包含空白字符,建议使用第二种格式用引号引起来,否则会被当成两个文件。
    • Rules

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

    ADD

    • Introduction

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

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

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

    WORKDIR

    • Introduction

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

      • WORKDIR 相对路径或者绝对路径

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

    VOLUME

    • Introduction

      • docker run -v简化版
      • 用于在镜像中创建一个挂载点目录。上一章中有提到Volume有两种类型:绑定挂载卷和docker管理的卷。在dockerfile中只支持docker管理的卷,也就是说只能指定容器内的路径,不能指定宿主机的路径。
    • Syntax

      • VOLUME <mountpoint>
      • VOLUME ["<mountpoint>"]

    EXPOSE

    • Introduction

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

      • EXPOSE <port>[/<protocol>] [<port>[/<protocol>] ...]
      • <protocol>用于指定协议类型,如果不指定,默认TCP协议。

    ENV

    • Introduction

      • 同docker run -e
      • 为镜像定义所需的环境变量,并可被ENV指令后面的其它指令所调用。调用格式为$variable_name或者${variable_name}
      • 使用docker run启动容器的时候加上 -e 的参数为variable_name赋值,可以覆盖Dockerfile中ENV指令指定的此variable_name的值。但是不会影响到dockerfile中已经引用过此变量的文件名。下面有举例说明:
    • Syntax

      • ENV <key> <value>

      • ENV <key>=<value> ...

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

    # 基于busybox启动一个镜像,将test文件拷贝至容器内的/usr/local/aaa/目录下。
    [root@docker1 docker]# pwd
    /server/docker--rm 
    [root@docker1 docker]# echo 1111 >test
    [root@docker1 docker]# vim Dockerfile 
    # Description: test image
    FROM busybox
    ENV file=aaa
    ADD ./test /usr/local/$file/
    [root@docker1 docker]# docker build -t busy:v1 ./
    # 根据此镜像启动容器并查看文件是否拷贝成功,并且查看file变量的值
    [root@docker1 docker]# docker run --name busy02 --rm busy:v1 ls /usr/local/aaa
    test
    [root@docker1 docker]# docker run --name busy02 --rm busy:v1 printenv
    PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    HOSTNAME=57111b7b246c
    file=aaa
    HOME=/root
    # 接下来我们在启动容器的时候加上-e参数为file变量指定一个新值,并且查看file变量的值
    [root@docker1 docker]# docker run --name busy02 -e file=bbb --rm busy:v1 printenv
    PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    HOSTNAME=787ad8585fc0
    file=bbb
    HOME=/root
    # 此时再看test的文件,依然是在aaa的目录下的
    [root@docker1 docker]# docker run --name busy02 -e file=bbb --rm busy:v1 ls /usr/local/aaa
    test
    # 这是因为docker build属于第一阶段,而docker run属于第二阶段。第一阶段定义file变量的值aaa已经被引用了,生米已经煮成熟饭了,后续阶段再改file变量的值也影响不了aaa。
    

    RUN

    • Introduction

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

      • RUN <command>

      • RUN ["executable", "param1", "param2"]

        • <command>通常是一个shell命令,系统默认会把后面的命令作为shell的子进程来运行,以"/bin/sh -c"来运行它,也就意味着此进程在容器中的PID一定不为1,如果是1完事就结束了哇。
        • 第二种格式的参数是一个JSON格式的数组,其中"executable"为要运行的命令,后面的"paramN"为传递给命令的选项或参数。此格式指定的命令不会以"/bin/sh -c"来发起,也就是直接由内核创建,因此不具备shell特性,类似于RUN [ "echo", "$HOME" ],是无法识别 $ 的;如果想要依赖shell特性,可以替换命令为这样的格式[ "/bin/sh", "-c", "echo $HOME" ]。

    CMD

    • Introduction

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

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

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

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

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

      • CMD command param1 param2

      • CMD ["executable","param1","param2"]

      • CMD ["param1","param2"]

        • 前两种语法格式同RUN指令。第一种用法对于CMD指令基本没有意义,因为它运行的程序PID不为1。
        • 第三种则需要结合ENTRYPOINT指令使用,CMD指令后面的命令作为ENTRYPOINT指令的默认参数。如果docker run命令行结尾有参数指定,那CMD后面的参数不生效。

    ENTRYPOINT

    • Introduction

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

      • ENTRYPOINT command param1 param2
      • ENTRYPOINT ["executable", "param1", "param2"]
    • Example

    # 还是以httpd服务做举例,先以CMD指令开始
    [root@docker1 docker]# vim Dockerfile
    # Description: test image
    FROM busybox
    LABEL maintainer="merle <merle@freeit.com>" app="httpd"
    ENV WEBDIR="/data/web/html"
    RUN mkdir -p ${WEBDIR} && 
        echo 'this is a test web' > ${WEBDIR}/index.html
    CMD [ "sh","-c","/bin/httpd -f -h ${WEBDIR}" ]
    [root@docker1 docker]# docker build -t httpd:v1 ./
    [root@docker1 docker]# docker run --name web01 -it --rm httpd:v1
    # 此时为前台运行,复制一个窗口,kill掉容器,然后开始docker run结尾传入新的指令
    [root@docker1 ~]# docker kill web01 
    web01
    [root@docker1 docker]# docker run --name web01 -it --rm httpd:v1 ls /data/web/html
    index.html
    # 可以看出命令行的参数已经替代了原本的CMD指令指定的程序。下面我们再用ENTRYPOINT指令做测试。
    -------------------------------------------------------------------------------------------------------------
    [root@docker1 docker]# vim Dockerfile
    # Description: test image
    FROM busybox
    LABEL maintainer="merle <merle@freeit.com>" app="httpd"
    ENV WEBDIR="/data/web/html"
    RUN mkdir -p ${WEBDIR} && 
        echo 'this is a test web' > ${WEBDIR}/index.html
    ENTRYPOINT [ "sh","-c","/bin/httpd -f -h ${WEBDIR}" ]
    [root@docker1 docker]# docker build -t httpd:v2 ./
    [root@docker1 docker]# docker run --name web01 -it --rm httpd:v2
    # 也是前台启动,复制一个窗口,kill掉容器,然后开始docker run结尾传入新的指令
    [root@docker1 docker]# docker run --name web01 -it --rm httpd:v2 ls /data/web/html
    # 可以看到没有反应,这种情况其实是吧ls /data/web/html当做参数传给了/bin/httpd -f -h ${WEBDIR}程序。只是httpd不识别罢了。我们kill掉容器。加上--entrypoint参数再试一下
    [root@docker1 docker]# docker run --name web01 -it --rm --entrypoint="" httpd:v2 ls /data/web/html
    index.html
    # 使用--entrypoint参数替换命令成功。
    # 再测试下CMD的第三种语法,CMD指令的后面的命令作为参数传给ENTRYPOINT指令后的命令
    [root@docker1 docker]# vim Dockerfile 
    # Description: test image
    FROM busybox
    LABEL maintainer="merle <merle@freeit.com>" app="httpd"
    ENV WEBDIR="/data/web/html"
    RUN mkdir -p ${WEBDIR} && 
        echo 'this is a test web' > ${WEBDIR}/index.html
    CMD [ "/bin/httpd -f -h ${WEBDIR}" ]
    ENTRYPOINT [ "sh","-c" ]
    [root@docker1 docker]# docker build -t httpd:v3 ./
    [root@docker1 docker]# docker run --name web01 -it --rm httpd:v3
    # OK的,前面有说过:指定ENTRYPOINT的情况下,如果docker run命令行结尾有参数指定,那CMD后面的参数不生效,下面咱再试试,还用v3的镜像。
    [root@docker1 docker]# docker run --name web01 -it --rm httpd:v3 "ls /data/web/html"
    index.html
    

    USER

    • Introduction

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

      • USER <user>[:<group>]

      • USER <UID>[:<GID>]

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

    HEALTHCHECK

    • Introduction

      • 顾名思义,健康检查。此指令的就是告诉docker如果检查容器是否正常工作。拿nginx举例,即便进程运行,服务也不一定正常,因为万一root指错了呢?
    • Syntax

      • HEALTHCHECK [OPTIONS] CMD command

      • HEALTHCHECK NONE

        • HEALTHCHECK指令让我们去定义一个CMD,在CMD后面编写一条命令去判断我们的服务运行是否正常。检查肯定不是一次性的,所以OPTIONS就是指定检查的频率等等。

          • --interval=DURATION(默认值:30s):每隔多久检查一次,默认30s

          • --timeout=DURATION(默认值:30s):超时时长,默认30s

          • --start-period=DURATION(默认值:0s):启动健康检查的等待时间。因为容器启动成功时,进程不一定立马就启动成功,那过早开始检查就会返回不健康。

          • --retries=N(默认值:3):如果检查一次失败就返回不健康未免太武断,所以默认三次机会。

          • CMD健康检测命令发出时,返回值有三种情况

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

    • Example

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

    SHELL

    • Introduction

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

      • SHELL ["executable", "parameters"]

    STOPSIGNAL

    • Introduction

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

      • STOPSIGNAL signal

    ARG

    • Introduction

      • ARG命令同EVN类似,也是指定一个变量,但不同的是,ENV指令配合-e参数可以在docker run过程中传参,而使用ARG指令配合--build-arg参数可以在docker build过程中传参,这方便了我们为不同场景构建不同镜像。
    • Syntax

      • ARG <name>[=<default value>]
    • Example

    [root@docker1 docker]# vim Dockerfile 
    
    FROM nginx:1.14-alpine
    ARG AUTHOR="merle <merle@freeit.com>"    # 指定默认值
    LABEL maintainer=$AUTHOR
    ENV NGXDIR='/data/web/html/'
    ADD index.html $NGXDIR
    ADD entrypoint.sh /bin/
    CMD ["nginx", "-g", "daemon off;"]
    ENTRYPOINT ["/bin/entrypoint.sh"]
    [root@docker1 docker]# docker build --build-arg AUTHOR="Jtt <Jtt@freeit.com>" -t nginx:v4 ./
    [root@docker1 docker]# docker image inspect nginx:v4 | grep maintainer
                    "maintainer": "Jtt <Jtt@freeit.com>"
    # 上面只是以maintainer举例,实践环境中可以修改为不同jar包的名字构建不同java程序镜像。
    

    ONBUILD

    • Introduction

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

      • ONBUILD [INSTRUCTION]

        • 几乎任何指令都可以成为触发器指令,但ONBUILD不能自我嵌套,且不会触发FROM和MAINTAINER指令,多数情况是使用RUN或者ADD。
        • 另外在使用COPY指令时,应该注意后续引用该镜像的Dockerfile的同级目录下是否有被拷贝的文件。
    • Example

    [root@docker1 docker]# vim Dockerfile 
    FROM nginx:1.14-alpine
    LABEL maintainer="merle <merle@freeit.com>"
    ENV NGXDIR='/data/web/html/'
    ADD index.html $NGXDIR
    ADD entrypoint.sh /bin/
    ONBUILD add http://nginx.org/download/nginx-1.14.0.tar.gz /usr/local/
    CMD ["nginx", "-g", "daemon off;"]
    ENTRYPOINT ["/bin/entrypoint.sh"]
    [root@docker1 docker]# docker build -t nginx:v5 ./
    [root@docker1 docker]# docker run -it --name web01 --rm nginx:v5 ls /usr/local/
    bin    lib    share
    # 上面的结果或者docker build的过程都可以看出并没有执行ONBUILD后面的指令。我们修改Dockerfile中FROM指定的基础镜像为上面构建完的nginx:v5,然后删除ONBUILD指令。
    [root@docker1 docker]# vim Dockerfile 
    FROM nginx:v5
    LABEL maintainer="merle <merle@freeit.com>"
    ENV NGXDIR='/data/web/html/'
    ADD index.html $NGXDIR
    ADD entrypoint.sh /bin/
    CMD ["nginx", "-g", "daemon off;"]
    ENTRYPOINT ["/bin/entrypoint.sh"]
    [root@docker1 docker]# docker build -t nginx:v6 ./
    Sending build context to Docker daemon  6.656kB
    Step 1/7 : FROM nginx:v5
    # Executing 1 build trigger
    Downloading  [=======
    # 这里查看构建过程就会发现从第一层构建就开始执行上面的ONBUILD后面的指令了。我们启动容器并查看下/usr/local目录下的文件。
    [root@docker1 docker]# docker run -it --name web01 --rm nginx:v6 ls /usr/local
    bin                  nginx-1.14.0.tar.gz
    lib                  share
    


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

  • 相关阅读:
    C#连接MySQL
    国双面试题
    Redis入门安装配置
    vs2013密钥
    单例模式
    用R画韦恩图
    Snipaste截图
    秩和检验
    用R包中heatmap画热图
    OTU(operational taxonomic units),即操作分类单元
  • 原文地址:https://www.cnblogs.com/ccbloom/p/11174186.html
Copyright © 2011-2022 走看看