• Dockerfile详解


    Dockerfile是什么

    Dockerfile就是一个纯文本,里面记录了一系列的构建指令,如选择基础镜像、拷贝文件、运行脚本等等,RUNCOPYADD指令都会生成一个 Layer,而 Docker 顺序执行这个文件里的所有步骤,最后就会创建出一个新的镜像出来。

    一个简单的Dockerfile实例:

    1. # Dockerfile.busybox
    2. FROM busybox # 选择构建使用的基础镜像
    3. CMD echo "hello world" # 启动镜像默认运行的命令

    RUN, COPY, ADD 会生成新的镜像层,其它指令只会产生临时层,不影响构建大小。所以 Dockerfile 里不要滥用这些指令,尽量精简合并,否则太多的层会让镜像臃肿不堪。


    docker build

    Dockerfile 要经过 docker build 才能生效,命令行“docker”是一个简单的客户端,真正的镜像构建工作由服务器端的“Docker daemon”来完成,所以“docker”客户端只能把“构建上下文”目录打包上传(显示信息 Sending build context to Docker daemon ),这样服务器才能够获取本地的这些文件。

     

    这个机制会导致一些麻烦,如目录里有的文件(例如 readme/.git/.svn 等)不需要拷贝进镜像,docker 也一股脑地打包上传,效率很低。

    为避免这种问题,可以在“构建上下文”目录里建立一个 .dockerignore 文件,语法与 .gitignore 类似,排除那些不需要的文件。

    gitignore

    下面的简单的示例,表示不打包上传后缀是“swp”“sh”的文件:

    1. # docker ignore
    2. *.swp
    3. *.sh

    另外一般在命令行里用 -f 来显式指定 Dockerfile文件。但如果省略这个参数,docker build 会在当前目录下找名字是 Dockerfile 的文件。所以,若只有一个构建目标,文件直接叫“Dockerfile”最省事。

    此时构建出来的镜像只有“IMAGE ID”,没有名字。为此可以加上一个 -t 参数,指定镜像的标签(tag),这样 Docker 就会在构建完成后自动给镜像添加名字。标签必须要符合一定的命名规范,用 : 分隔名字和版本,如果不提供版本默认“latest”。


    docker inspect

    docker inspect 来查看镜像的分层信息,Docker 会检查是否有重复层,如果本地已存在就不会重复下载,如果层被其他镜像共享就不会删除,这样可以节约磁盘和网络成本。

    容器镜像由多个只读 Layer 构成,同一个 Layer 可以被不同的镜像共享,减少了存储和传输的成本。

    镜像分层好处:可重复使用未被改动的 layer,每次修改打包镜像,只需重新构建被改动的部分。


    完整的 Dockerfile 示例

    1. # Dockerfile
    2. # docker build -t ngx-app .
    3. # docker build -t ngx-app:1.0 .
    4. ARG IMAGE_BASE="nginx"
    5. ARG IMAGE_TAG="1.21-alpine"
    6. FROM ${IMAGE_BASE}:${IMAGE_TAG}
    7. COPY ./default.conf /etc/nginx/conf.d/
    8. RUN cd /usr/share/nginx/html \
    9. && echo "hello nginx" > a.txt
    10. EXPOSE 8081 8082 8083

    Dockerfile参数

    FROM

    功能:指定基础镜像,必须是第一条指令,所以基础镜像的选择非常关键

    如果关注的是镜像的安全和大小,那么一般会选择 Alpine;如果关注的是应用的运行稳定性,那么可选择 Ubuntu、Debian、CentOS。

    1. FROM alpine:3.15 # 选择Alpine镜像
    2. FROM ubuntu:bionic # 选择Ubuntu镜像

    如果不以任何镜像为基础,那么写法为:FROM scratch,同时意味着接下来所写指令将作为镜像第一层开始。

    格式:

    FROM 
    FROM :
    FROM : 

    三种写法,其中 是可选项,若没有选择,则默认值为latest。


    RUN

    功能:运行时指定的命令,可以执行任意 Shell 命令,如更新系统、安装应用、下载文件、创建目录、编译程序等,实现任意的镜像构建步骤,非常灵活。

    两种格式:

    1. RUN 
    2. RUN ["executable", "param1", "param2"]

    第一种后边直接跟shell命令

    • 在linux操作系统上默认 /bin/sh -c

    • 在windows操作系统上默认 cmd /S /C

    第二种类似于函数调用。

    可将executable理解成为可执行文件,后面是两个参数。

    两种写法比对:

    • RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME
    • RUN ["/bin/bash", "-c", "echo hello"]

    注意:多行命令不要写多个RUN,因为Dockerfile中每一个指令都会建立一层。多少RUN就构建多少层镜像,会造成镜像的臃肿、多层,不仅仅增加构件部署的时间,还容易出错。RUN书写时的换行符是\。

    RUN 通常是 Dockerfile 里最复杂的指令,会包含很多的 Shell 命令,但 Dockerfile 里一条指令只能是一行,所以有的 RUN 指令会在每行的末尾用续行符 \,命令间也会用 && 来连接,这样保证在逻辑上是一行,就像下面这样:

    1. RUN apt-get update \
    2. && apt-get install -y \
    3. build-essential \
    4. curl \
    5. make \
    6. unzip \
    7. && cd /tmp \
    8. && curl -fSL xxx.tar.gz -o xxx.tar.gz\
    9. && tar xzf xxx.tar.gz \
    10. && cd xxx \
    11. && ./config \
    12. && make \
    13. && make clean

    在 Dockerfile 里写这种超长的 RUN 指令不美观,且一旦写错,每次调试都要重新构建也很麻烦。所以可以采用一种变通的技巧:把这些 Shell 命令集中到一个脚本文件里,用 COPY 命令拷贝进去再用 RUN 来执行

    1. COPY setup.sh /tmp/ # 拷贝脚本到/tmp目录
    2. RUN cd /tmp && chmod +x setup.sh \ # 添加执行权限
    3. && ./setup.sh && rm setup.sh # 运行脚本后再删除脚本

    CMD

    功能:容器启动时运行的命令

    三种格式:

    1. CMD ["executable","param1","param2"]
    2. CMD ["param1","param2"]
    3. CMD command param1 param2

    第一种和第二种都是可执行文件加上参数的形式。

    第三种就是shell的执行方式和写法。

    举例说明两种写法:

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

    补充细节:这里包括参数的要用双引号,原因是参数传递后,docker解析的是一个JSON array。


    RUN & CMD

    RUN:构建镜像时就运行的命令且提交运行结果。

    CMD:启动镜像时执行的命令,构建时仅指定了这个命令是个什么样子。


    LABEL

    功能:为镜像指定标签

    格式:

    LABEL = = = ...

    Dockerfile中可有多个LABEL,如下:

    1. LABEL "com.example.vendor"="ACME Incorporated"
    2. LABEL com.example.label-with-value="foo"
    3. LABEL version="1.0"
    4. LABEL description="This text illustrates \
    5. that label-values can span multiple lines."

    但并不建议这样写,最好写成一行,如太长需换行的话则用\符号,如下:

    1. LABEL multi.label1="value1" \
    2. multi.label2="value2" \
    3. other="value3"

    说明:LABEL会继承基础镜像中的LABEL,如遇到key相同,则值覆盖。


    MAINTAINER

    功能:指定作者

    格式:

    MAINTAINER 

    EXPOSE

    功能:暴漏容器运行时的监听端口给宿主机。暴露容器内部端口给外部。

    但EXPOSE并不能使容器访问宿主机的端口,

    想使容器与主机的端口有映射关系,须在容器启动的时候加上 -P或-p参数。

    1. EXPOSE 443 # 默认是tcp协议
    2. EXPOSE 53/udp # 可以指定udp协议

    ENV

    功能:设置环境变量,镜像层和容器层参数

    两种格式:

    1. ENV  
    2. ENV = ...

    区别:第一种是一次设置一个,第二种是一次设置多个。

    1. # ARG 定义了基础镜像的名字(可以在FROM指令中使用),ENV 定义了两个环境变量
    2. ARG IMAGE_BASE="node"
    3. ARG IMAGE_TAG="alpine"
    4. ENV PATH=$PATH:/tmp
    5. ENV DEBUG=OFF

    ADD

    功能:复制命令,把文件复制到镜象中。

    如果把宿主机和容器想象成两台linux服务器的话,那这个命令就类似于scp,只是scp需要加用户名和密码的权限验证,而ADD不用。

    格式:

    1. ADD ... 
    2. ADD ["",... ""]

    可以是一个本地文件或是一个本地压缩文件,还可以是一个url。

    路径的填写可是容器内绝对路径,也可以是相对于工作目录的相对路径。

    如果把写成一个url,那么ADD就类似于wget命令

    如下写法可以:

    • ADD test relativeDir/ 
    • ADD test /relativeDir
    • ADD http://example.com/foobar /

    尽量不要是一个文件夹。如果是一个文件夹,会复制整个目录的内容,包括文件系统元数据。


    COPY

    功能:复制命令

    在本机上开发测试时会产生一些源码、配置等文件,需打包进镜像里,这时可用 COPY 命令,它的用法和 Linux  cp 差不多,不过拷贝的源文件必须是“构建上下文”路径里的,不能随意指定文件。也就是说,如果要从本机向镜像拷贝文件,就必须把这些文件放到一个专门的目录,然后在 docker build 里指定“构建上下文”到这个目录才行。

    这里两个 COPY 命令示例:

    1. COPY ./a.txt /tmp/a.txt # 把构建上下文里的a.txt拷贝到镜像的/tmp目录
    2. COPY /etc/hosts /tmp # 错误!不能使用构建上下文之外的文件

    格式:

    1. COPY ... 
    2. COPY ["",... ""]

    与ADD的区别

    COPY的只能是本地文件,其他用法一致。


    ENTRYPOINT

    功能:启动时的默认命令

    格式:

    1. ENTRYPOINT ["executable", "param1", "param2"]
    2. ENTRYPOINT command param1 param2

    第一种是可执行文件加参数。

    第二种就是写shell。

    与CMD比较说明(这俩命令太像了,而且还可以配合使用):

    1. 相同点:

    • 只能写一条,如果写了多条,只有最后一条生效。

    • 容器启动时才运行。

    2. 不同点:

    •  ENTRYPOINT不会被运行镜像时的command覆盖,而CMD会被覆盖。

    •  如果在Dockerfile中同时写了ENTRYPOINT和CMD,且CMD指令不是一个完整的可执行命令,那么CMD指定的内容将会作为ENTRYPOINT的参数。

    如下:

    FROM ubuntu
    ENTRYPOINT ["top", "-b"]
    CMD ["-c"]
    • 如果在Dockerfile同时写了ENTRYPOINT和CMD,且CMD是一个完整的指令,那么谁在最后谁生效。

    如下:

    FROM ubuntu
    ENTRYPOINT ["top", "-b"]
    CMD ls -al

    那么将执行ls -al,top -b不会执行。


    VOLUME

    功能:实现挂载功能,可以将内地文件夹或其他容器中的文件夹挂在到该镜像的容器中。

    格式:

    VOLUME ["/data"]

    说明:

      ["/data"]可以是一个JsonArray ,也可以是多个值。所以如下几种写法都正确:

    VOLUME ["/var/log/"]
    VOLUME /var/log
    VOLUME /var/log /var/db

    一般的使用场景为需要持久化存储数据。

    容器使用的是AUFS,这种文件系统不能持久化数据,当容器关闭后,所有的更改都会丢失。


    USER

    设置启动容器的用户。

    可以是用户名或UID,所以,下面的两种写法都是正确的

    格式:

    • USER daemo
    • USER UID

    注意:如果设置了容器以daemon用户去运行,那么RUN, CMD 和 ENTRYPOINT 都会以这个用户去执行。


    WORKDIR

    功能:设置工作目录,对RUN,CMD,ENTRYPOINT,COPY,ADD生效。若不存在则会创建,也可以设置多次。

    格式:

    WORKDIR /path/to/workdir

    如:

    WORKDIR /a
    WORKDIR b
    WORKDIR c
    RUN pwd

    pwd执行的结果:/a/b/c

    WORKDIR也可以解析环境变量

    如:

    ENV DIRPATH /path
    WORKDIR $DIRPATH/$DIRNAME
    RUN pwd

    pwd的执行结果:/path/$DIRNAME


    ARG

    功能:定义镜像层的环境变量

    格式:

    ARG [=]

    设置变量命令,ARG命令定义了一个变量,在docker build创建镜像的时候,使用 --build-arg =来指定参数

    如果用户在build镜像时指定了一个参数但没有定义在Dockerfile中,那么将有一个Warning

    提示如下:

    [Warning] One or more build-args [foo] were not consumed.

    也能给参数一个默认值:

    FROM busybox
    ARG user1=someuser
    ARG buildno=1
    ...

    若给了ARG定义的参数默认值,那么当build镜像时没有指定参数值,将会用这个默认值。

    例:

    1. # ARG 定义了基础镜像的名字(可以在FROM指令中使用),ENV 定义了两个环境变量
    2. ARG IMAGE_BASE="node"
    3. ARG IMAGE_TAG="alpine"
    4. ENV PATH=$PATH:/tmp
    5. ENV DEBUG=OFF

    ONBUILD

    功能:这个命令只对当前镜像的子镜像生效。

    格式:

    ONBUILD [INSTRUCTION]

    如当前镜像为A,在Dockerfile种添加:

    ONBUILD RUN ls -al

    这个 ls -al 命令不会在A镜像构建或启动时执行,若有一个镜像B是基于A镜像构建,则这个ls -al 命令会在B镜像构建的时候被执行。


    STOPSIGNAL

    功能:当容器退出时给系统发送什么样的指令。

    格式:

    STOPSIGNAL signal

    HEALTHCHECK

    功能:容器健康状况检查命令。

    两种格式:

    1. HEALTHCHECK [OPTIONS] CMD command
    2. HEALTHCHECK NONE

    第一个的功能是在容器内部运行一个命令来检查容器的健康状况。

    第二个的功能是在基础镜像中取消健康检查命令。

    [OPTIONS]选项支持三中选项:

        --interval=DURATION 两次检查默认的时间间隔为30秒

        --timeout=DURATION 健康检查命令运行超时时长,默认30秒

        --retries=N 当连续失败指定次数后,则容器被认为不健康,状态为unhealthy,默认次数是3

    注意:

    HEALTHCHECK命令只能出现一次。出现了多次,只有最后一个生效。

    CMD后边的命令的返回值决定了本次健康检查是否成功,具体的返回值如下:

    0: success - 表示容器健康

    1: unhealthy - 表示容器不健康

    2: reserved - 保留值

    例子:

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

    健康检查命令是:curl -f http://localhost/ || exit 1

    两次检查的间隔时间是5秒

    命令超时时间为3秒


    总结

    1. 创建镜像需编写 Dockerfile,写清楚创建镜像的步骤,每个指令都会生成一个 Layer
    2. Dockerfile 里,第一个指令必须是 FROM,用来选择基础镜像,常用的有 Alpine、Ubuntu 等。其他常用的指令有:COPYRUNEXPOSE,分别是拷贝文件,运行 Shell 命令,声明服务端口号。
    3. docker build 需要用 -f 来指定 Dockerfile,如果不指定就使用当前目录下名字为“Dockerfile”的文件。
    4. docker build 需要指定“构建上下文”,其中的文件会打包上传到 Docker daemon,所以尽量不要在“构建上下文”中存放多余的文件。
    5. 创建镜像的时候应使用 -t 参数,为镜像起一个有意义的名字,方便管理。
    6. Dockerfile 里的构建命令不区分大小写,如“FROM”也可写成“from”。

      参考:

    如何编写正确高效的Dockerfile - 牛奔 - 博客园 (cnblogs.com)icon-default.png?t=N3I4https://www.cnblogs.com/niuben/p/17178426.html

  • 相关阅读:
    2.3 Go语言中的字符型和常量定义
    DockerHub 镜像加速
    关于领域驱动设计,大家都理解错了
    安装K8s基础环境软件(二)
    【日更】 linux常用命令
    M4Singer Ubuntu 4060ti16G 笔记
    js检索(捕获)字符串中的正则表达式的匹配exec的使用
    java考试系统课程设计
    Solana「迷惑行为」:造手机、开门店
    gd32部分映射1/2,完全映射,备用功能选择等
  • 原文地址:https://blog.csdn.net/wangshiqi666/article/details/130597603