Dockerfile

248 阅读17分钟

Dockerfile介绍

docs.docker.com/reference/d…

Dockerfile 是用于构建 Docker 镜像的脚本文件,由一系列指令构成。通过 docker build命令构建镜像时,Dockerfile 中的指令会由上到下依次执行,每条指令都将会构建出一个镜像。这就是镜像的分层。因此,指令越多,层次就越多,创建的镜像就越多,效率就越低。所以在定义 Dockerfile 时,能在一个指令完成的动作就不要分为两条。

对于 Dockerfile 的指令,需要注意以下几点:

  • 指令是大小不敏感的,但推荐全大写。
  • 指令后至少会携带一个参数。
  • #号开头的行为注释。

构建新镜像的方式

  1. docker build:通过dockerfile构建一个新镜像
  2. docker commit:将一个容器文件系统的当前快照生成为一个新的镜像
  3. docker import:其所恢复的镜像就是新构建的镜像,与原镜像的 ImageID 不同;docker load 恢复的镜像与原镜像是同一个镜像,即 ImageID 相同
  4. docker hub中的automated builds

Dockerfile构建镜像示例

Dockerfile 构建自己的hello-docker镜像

  1. 了解scratch镜像:scratch 镜像是一个空镜像,是所有镜像的 Base Image(相当于面向对象编程中的 Object类)。scratch 镜像只能在 Dockerfile 中被继承,不能通过 pull 命令拉取,不能 run,也没有 tag。 并且它也不会生成镜像中的文件系统层。在 Docker 中,scratch 是一个保留字,用户不能作为自己的镜像名称使用。
  2. 安装c的编译器:yum install gcc gcc-c++ -y
  3. 安装c的静态库 yum install glibc-static -y
  4. 编写hello-docker.c
    #include<stdio.h>
    int main(){
       printf("hello docker!");
       return 0;
    }
    
  5. 编译并运行hello-docker.c 文件
    • 编译:gcc --static -o hello-docker hello-docker.c
    • 运行:./hello-docker image.png
  6. 编写Dockerfile
     FROM scratch
     ADD hello-docker /
     CMD ["/hello-docker"]
    
  7. 根据Dockerfile构建Docker镜像、运行所构建的docker镜像 image.png
    • docker build -t hello-docker .
      • -t 用于指定要生成的镜像的<repository>与<tag>。若省略 tag,则默认为 latest。
      • 最后的点(.)是一个宿主机的 URL 路径,构建镜像时会从该路径中查找 Dockerfile 文件。该路径也是在 Dockerfile 中 ADD、COPY 指令中若使用相对路径,那个相对路径就是相对的这个路径。注意,即使 ADD、COPY 指令中使用绝对路径来指定源文件,该源文件所在路径也必须要在这个 URL 指定目录或子目录内,否则将无法找到该文件
    • docker run hello-docker 运行新镜像

Dockerfile 构建自己的dawn-centos镜像

从镜像中心拉取来的 centos:7 镜像中是没有 vim、ifconfig、wget 等常用命令的,我们构建一个可以使用这些命令的centos7镜像。

  1. 创建Dockerfile

    cd /root/docker/dockerfile
    vim Dockerfile
    
    FROM centos:7
    MAINTAINER dawn dawn@qq.com
    LABEL author="dawn" email="dawn@163.com" description="basic tools are installed" version="1.0"
    ENV WORKPATH /usr/local
    WORKDIR $WORKPATH
    RUN yum install vim wget net-tools -y
    CMD /bin/bash
    
  2. 构建dawn-centos

    docker build -t dawn-centos:v1.0 /root/docker/dockerfile/
    

    image.png 通过 docker images 命令可以查看到刚刚生成的新的镜像。并且还发现,新镜像的大小要大于原镜像的,因为新镜像安装了新软件。

    image.png

  3. 启动dawn-centos:v1.0镜像

    docker run --name dawn-centos-v1.0 -it dawn-centos:v1.0
    

    运行CMD /bin/bash并且直接进入WORKDIR指定的/usr/local目录 image.png

构建悬虚镜像

悬虚镜像是指既没有 Repository 又没有 Tag 的镜像。当新建了一个镜像后,为该镜像指定了一个已经存在的 TAG,那么原来的镜像就会变为悬空镜像。为了演示悬虚镜像的生成过程,这里先修改前面定义的 Dockerfile,然后再生成镜像,且生成的新的镜像与前面构建的镜像的名称与 Tag 均相同。

  1. 创建Dockerfile

    FROM centos:7
    MAINTAINER dawn dawn@qq.com
    LABEL author="dawn" email="dawn@163.com" description="basic tools are installed" version="2.0"
    ENV WORKPATH /usr/local
    WORKDIR $WORKPATH
    RUN yum install wget net-tools -y
    CMD /bin/bash
    

    在构建镜像之前,先查看前面构建的 dawn-centos:v1.0 镜像的 ID

    image.png

  2. 构建镜像:docker build -t dawn-centos:v1.0 ./
    image.png 构建完毕后,再次查看镜像,发现原来 dawn-centos:v1.0 镜像的名称与 Tag 均变为了<none>,即变为了悬虚镜像 image.png

  3. 删除悬虚镜像 悬虚镜像是一种“无用”镜像,其存在只能是浪费存储空间,所以一般都是要删除的。

    • docker rmi <imageID>

    • docker image prune 该命令能够一次性删除本地全部的悬空镜像。不过前提是这些悬虚镜像不能是已经启动了容器的,无论容器是否是退出状态。如果加上-a选项,则将没有被任何容器使用的镜像也删除。

      悬虚镜像如果有启动的容器是不能删除的需要将容器删除后再删除镜像。 image.png

    • docker system prune 这个命令不仅删除的是悬虚镜像,还有其它系统“无用”内容。在删除这个悬虚镜像之前,首先查看其是否启动了容器。如果启动了,则先将容器删除。

      使用 docker system prune 命令可删除系统中的四类“无用”内容,其中就包含悬虚镜像dangling images。 image.png

docker build

docker build根据Dockerfile构建一个镜像,docker build -t hello-docker .向docker daemon 提交了一个请求,并将Dockerfile 和 hello-docker文件发送到docker daemon中。

  • docker build -t hello-docker:v1 -t hello-docker:v2 -t hello-docker:v3 .构建3个镜像,但是镜像的ID是相同的。

image.png

docker tag

docker tag为镜像重打标签,当镜像被指定为latest,后期又出现了更新的版本需要被指定为 latest,那么原 latest镜像就应被重打<tag>标签,否则,当最新版被发布为 latest 后,原镜像就会变为悬虚镜像。所谓重打标签,实际是复制了一份原镜像,并为新的镜像指定新的<tag>。当然也可以重新指定<repository>。所以,新镜像的 ImageID、Digest 都和原镜像的相同。

image.png

image.png

dockerfile指令

FROM

  • 【语法】FROM <image>[:<tag>]
  • 【解析】用于指定基础镜像,且必须是第一条指令;若省略了 tag,则默认为 latest。

MAINTAINER

  • 【语法】MAINTAINER<name>
  • 【解析】MAINTAINER 的参数一般是维护者姓名和邮箱。但该指令官方已不建议使用,而是使用LABEL 代替

LABEL

  • 【语法】LABEL <key>=<value> <key>=<value>
  • 【解析】LABEL 以键值对的方式包含任意镜像的元数据信息,用于替代MAINTAINER 指令。通过 docker inspect 可查看到 LABEL 与 MAINTAINER 的内容。

ENV

  • 【语法 1】ENV <key> <value>
  • 【解析】用于指定环境变量,这些环境变量,后续可以被 RUN 指令使用,容器运行起来之后,也可以在容器中获取这些环境变量。
  • 【语法 2】ENV <key1>=<value1> <key2>=<value2> ...
  • 【解析】可以设置多个变量,每个变量为一对<key>=<value>指定。

ADD

  • 【语法 1】ADD <src> <dest>
  • 【语法 2】ADD ["<src>", "<dest>"] # 路径中存在空格时使用双引号引起来
  • 【解析】复制当前宿主机中指定文件 src 到容器中的指定目录 dest 中。src 可以是宿主机中的绝对路径,也可以是相对路径。相对路径是相对于 docker build 命令所指定的路径。src 指定的文件可以是一个压缩文件,压缩文件复制到容器后会自动解压为目录;src 也可以是一个 URL,此时的 ADD 指令相当于 wget 命令;src 最好不要是目录,其会将该目录中所有内容复制到容器的指定目录中。dest 是一个绝对路径,其最后面的路径必须要加上斜杠,否则系统会将最后的目录名称当做是文件名。

COPY

【说明】功能与 ADD 指令相同,只不过 src 不能是 URL。若 src 为压缩文件,复制到容器后不会自动解压。

WORKDIR

  • 【语法】WORKDIR path
  • 【解析】容器打开后默认进入的目录,一般在后续的 RUN、CMD、ENTRYPOINT、ADD 等指令中会引用该目录。可以设置多个 WORKDIR 指令。后续 WORKDIR 指令若用的是相对路径,则会基于之前 WORKDIR 指令指定的路径。在使用 docker run 运行容器时,可以通过-w 参数覆盖构建时所设置的工作目录

RUN

  • 【语法 1】RUN <command>
  • 【解析】这里的就是 shell 命令。docker build 执行过程中,会使用 shell 运行指定的 command。
  • 【语法 2】RUN ["EXECUTABLE","PARAM1","PARAM2", ...]
  • 【解析】在 docker build 执行过程中,会调用第一个参数"EXECUTABLE"指定的应用程序运行,并使用后面第二、三等参数作为应用程序的运行参数。

CMD

  • 【语法 1】CMD ["EXECUTABLE","PARAM1","PARAM2", ...]
  • 【解析】在容器启动后,即在执行完 docker run 后会立即调用执行"EXECUTABLE"指定的可执行文件,并使用后面第二、三等参数作为应用程序的运行参数。
  • 【语法 2】CMD command param1 param2, ...
  • 【解析】这里的 command 就是 shell 命令。在容器启动后会立即运行指定的 shell 命令。
  • 【语法 3】CMD ["PARAM1","PARAM2", ...]
  • 【解析】提供给 ENTERYPOINT 的默认参数

ENTRYPOINT

  • 【语法 1】ENTRYPOINT ["EXECUTABLE","PARAM1","PARAM2", ...]
  • 【解析】在容器启动过程中,即在执行 docker run 时,会调用执行"EXECUTABLE"指定的应用程序,并使用后面第二、三等参数作为应用程序的运行参数。
  • 【语法 2】ENTRYPOINT command param1 param2, ...
  • 【解析】这里的 command 就是 shell 命令。在容器启动过程中,即在执行 docker run 时,会运行指定的 shell 命令。

EXPOSE

  • 【语法】EXPOSE <port> [<port>...]
  • 【解析】指定容器准备对外暴露的端口号,但该端口号并不会真正的对外暴露。若要真正暴露,则需要在执行 docker run 命令时使用-p(小 p)来指定说要真正暴露出的端口号。

ARG

  • 【语法】ARG < varname >[=<default value>]
  • 【解析】定义一个变量,该变量将会使用于镜像构建运行时。若要定义多个变量,则需要定义多个 ARG 指令。

ONBUILD

  • 【语法】ONBUILD [INSTRUCTION]
  • 【解析】该指令用于指定当前镜像的子镜像进行构建时要执行的指令。

VOLUME

  • 【语法】VOLUME ["dir1", “dir2”, ...]
  • 【解析】在容器创建可以挂载的数据卷。

CMD 与 ENTERYPOINT 用法

  • CMD 与 ENTERYPOINT都用于指定容器启动时要执行的命令,每个 Dockerfile 中都只能有一个 CMD/ENTERYPOINT 指令,多个 CMD/ENTERYPOINT 指令只会执行最后一个。
  • CMD容器启动时默认的命令;ENTRYPOINT容器启动时一定会执行的命令;docker run 时若指定了要运行的命令,Dockerfile 中的 CMD 指令指定的命令是不会执行的,而 ENTERYPOINT 中指定的命令是一定会执行的

CMD示例

  1. 创建CMDDockerfile文件内容如下

     FROM centos:7
     CMD ["echo","hello docker cmd"]
    

    说明:-f 用于指定本次构建所要使用的 Dockerfile 文件。如果文件名不是 docker build 默认加载的 Dockerfile 这个名称。

  2. 构建dawn-cmd镜像

    image.png

  3. 运行dawn-cmd镜像

    image.png

  4. 运行cmd-dawn镜像并覆盖CMD命令

    • 不可以直接加参数,会将参数认为直接替代的命令 image.png

    • 可直接替换命令 image.png

    • 可以替换命令+参数 image.png

    在 docker run 命令中指定要执行的命令,Dockerfile 中通过 CMD 指定的默认的命令就不会在执行

ENTERYPOINT示例

  1. 创建ENTRYPOINTDockerfile文件

    FROM centos:7
    ENTRYPOINT cal
    
  2. 构建并运行dawn-entrypoint镜像 image.png

  3. ENTRYPOINT 指定的命令是不会被 docker run 中指定的命令给覆盖掉的。 image.png ENTRYPOINT shell命令不会接收 docker run 中指定的args参数 image.png

  4. 修改ENTRYPOINTDockerfile文件,再次构建

    FROM centos:7
    ENTRYPOINT ["cal"]
    
    • ENTRYPOINT ["cal"]命令行不可以替换命令,但是可以设置命令选项 image.png

ENTRYPOINT 与 CMD 同时使用示例

  • 覆盖CMD参数生效
  • 添加命令选项生效
  • 可同时替换CMD参数和命令选项 image.png

CMD 与 ENTERYPOINT 总结

  • CMD:通过CMD指定的[command]或[“EXECUTABLE”]可被docker run命令中的[COMMAND]替代并且是不能添加参数[ARG]。如果命令中的 IMAGE 后仍有内容,此时docker daemon首先认为是替代用的[COMMAND],如果有两个或两个以上的内容,后面的内容才会认为是[ARG]。所以,添加的-y 会报错,因为没有-y 这样的[COMMAND]。

  • ENTRYPOINT:通过ENTRYPOINT指定的[command]或[“EXECUTABLE”]docker run中是可以添加参数[ARG]的。因为 Dockerfile中的ENTRYPOINT是不可被命令中的[COMMAND]所替代。如果命令中的 IMAGE 后仍有内容,此时对于 docker daemon来说,其只能是[ARG]。不过,docker daemon 对于 ENTRYPOINT 指定的[command]与[“EXECUTABLE”]的处理方式是不同的。如果是[command]指定的 shell,daemon 会直接运行,而不会与 docker run 中的[ARG]进行拼接后运行;如果是[“EXECUTABLE”]指定的命令,daemon 会先与 docker run 中的[ARG]进行拼接,然后再运行拼接后的结果

结论:无论是 CMD 还是 ENTRYPOINT,使用[“EXECUTABLE”]方式的通用性会更强些。

ADD and COPY 示例

1 创建copy_add_dockerfile文件 ```dockerfile FROM centos:7 WORKDIR /root ADD jdk-17_linux-x64_bin.tar.gz add/ COPY jdk-17_linux-x64_bin.tar.gz copy/

ADD jdk-17_linux-x64_bin.tar.gz add_jdk
COPY jdk-17_linux-x64_bin.tar.gz copy_jdk

CMD /bin/bash
```

2. 构建镜像 image.png image.png

- ADD命令会自动解压,COPY只是复制
- 如果目录后不带`/`表示重命名

注意:`docker build -t dawn-copy-add:v1.0 -f .` 后面的`.`表示dockerfile文件或者dockerfile中ADD 或者 COPY 等命令使用到本地目录时会以`.`为当前的相对目录。

ARG 示例

ARG指令用于定义一个变量,该变量将会在镜像构建时使用。注意不是容器启动时,容器启动时镜像构建早已完成。

  1. 创建Dockerfile文件
FROM centos:7
ARG password=a123456
RUN echo $password
  1. 构建镜像

    在镜像构建时读取了并打印了ARG的参数 image.png docker build中指定了ARG就会覆盖默认值
    image.png

ONBUILD 示例

ONBUILD 指令只对当前镜像的子镜像进行构建时有效。 示例需求:父镜像中没有 wget 命令,但子镜像中会增加。

  1. 构建并运行父镜像
  • 创建dockersfile

      FROM centos:7
      WORKDIR /usr/local
      ONBUILD RUN yum install wget -y
      CMD /bin/bash
    
  • 构建运行镜像

    运行结果:工作目录为/usr/local,且没有 wget 命令 image.png

  1. 构建并运行子镜像

    • 创建dockerfile
    FROM parent:v1.0
    
    • 构建运行镜像 image.png

    • 子镜像不仅能够直接进入到 bash 命令行,工作目录为/usr/local,其还直接具有 wget 命令。而这些功能除了继承自父镜像外,就是在构建过程中来自于 ONBUILD 指定的指令

发布一个SpringBoot应用

  1. 创建一个名为web-starter的spring boot项目,编写测试Api并打包

  2. 将web-starter.jar包上传到服务器上并创建dockerfile文件

    FROM openjdk:17
    WORKDIR /application
    COPY dawn-web-starter-0.0.1-SNAPSHOT.jar ./
    ENTRYPOINT ["java","-jar","dawn-web-starter-0.0.1-SNAPSHOT.jar"]
    EXPOSE 8080
    

    image.png

    image.png

    image.png

build cache

理解镜像构建过程中 build cache 机制,需要先了解镜像的生成过程。

Docker 镜像的构建过程,大量应用了镜像间的父子关系。即下层镜像是作为上层镜像的父镜像出现,下层镜像是作为上层镜像的输入出现;上层镜像是在下层镜像的基础之上变化而来。

FROM centos:7
LABEL author="dawn" email="dawn@163.com" description="basic tools are installed" version="2.0"
COPY hi.log /var/log/
RUN yum install iptraf -y
CMD /bin/bash
  1. FROM centos:7

    FROM 指令是必须的,它为最终构建出的镜像设定了一个基础镜像(Base Image)。该指令并不会产生新的镜像层而是使用指定的镜像作为基础镜像层。docker build 在解析FROM centos:7时Docker Daemon会首先从 centos:7 镜像的文件系统获取到该镜像的 ID,然后再根据镜像 ID 提取出该镜像的 json 文件内容,以备下一条指令镜像层构建时使用。

  2. LABEL author="dawn" ...

    LABEL 指令仅修改上一步中提取出的镜像 json 文件内容,在 json 中添加 LABEL author="dawn",无需更新镜像文件系统。但也会生成一个新的镜像层,只不过该镜像层中只记录了 json 文件内容的修改变化,没有文件系统的变化。

    如果该指令就是最后一条指令,那么此时形成的镜像的文件系统其实就是原来 FROM 后指定镜像的文件系统,只是json文件发生了变化。而json 文件内容的变化也会产生新的镜像层

  3. COPY hi.log /var/log/

    COPY 指令会将宿主机中的指定文件复制到容器中的指定目录,所以会改变该镜像层文 件系统大小,并生成新的镜像层文件系统内容。所以 json 文件中的镜像 ID 也就发生了变化,产生了新的镜像层。

  4. RUN yum install iptraf -y

    RUN 指令本身并不会改变镜像层文件系统大小,但由于其 RUN 的命令是 yum install,而该命令运行的结果是下载并安装一个工具,所以导致 RUN 命令最终也改变了镜像层文件系统大小,所以也就生成了新的镜像层文件系统内容。所以 json 文件中的镜像 ID 也就发生了变化,产生了新的镜像层。

  5. CMD /bin/bash

    对于 CMD 或 ENTRYPOINT 指令,其是不会改变镜像层文件系统大小的,因为其不会在docker build 过程中执行。所以该条指令没有改变镜像层文件系统大小。但对于 CMD 或 ENTRYPOINT 指令,由于其是将来容器启动后要执行的命令,所以会将该条指令写入到 json 文件中,会引发 json 文件的变化。所以 json 文件中的镜像 ID 也就发生 了变化,产生了新的镜像层。

  6. 构建dockerfile image.png

  7. 修改dockerfile继续构建

    image.png

    FROM centos:7
    LABEL author="dawn" email="dawn@163.com" description="basic tools are installed" version="2.0"
    RUN yum install iptraf -y
    CMD /bin/bash
    RUN echo hello world
    

build cache 机制

Docker Daemnon 通过 Dockerfile 构建镜像时,当发现即将新构建出的镜像(层)与本地 已存在的某镜像(层)重复时,默认会复用已存在镜像(层)而不是重新构建新的镜像(层),这种机制称为 docker build cache 机制。该机制不仅加快了镜像的构建过程,同时也大量节省了Docker 宿主机的空间。

docker build cache 并不是占用内存的 cache,而是一种对磁盘中相应镜像层的检索、复用机制。所以,无论是关闭 Docker 引擎,还是重启 Docker 宿主机,只要该镜像(层)存在于本地,那么就会复用。

build cache 失效

  1. Dockerfile 文件发生变化

    当 Dockerfile 文件中某个指令内容发生变化,那么从发生变化的这个指令层开始的所有镜像层 cache 全部失效。即从该指令行开始的镜像层将构建出新的镜像层,而不再使用 build cache,即使后面的指令并未发生变化。因为镜像关系本质上是一种树状关系,只要其上层节点变了,那么该发生变化节点的所有下层节点也就全部变化了

  2. ADD 或 COPY 指令内容变化

    Dockerfile 文件内容没有变化,但 ADD 或 COPY 指令所复制的文件内容发生了变化,同样会使从该指令镜像层开始的后面所有镜像层的 build cache 失效。

  3. RUN 指令外部依赖变化

    与 ADD/COPY 指令相似。Dockerfile 文件内容没有变化,但 RUN 命令的外部依赖发生了变化,比如要安装的 vim 软件源发生了变更(版本变化、下载地址变化等),那么从发生变化的这个指令层开始的所有镜像层 cache 全部失效。

  4. 指定不使用 build cache

    有些时候为了确保在镜像构建过程中使用到新的数据,在镜像构建 docker build 时,通过--no-cache 选项指定不使用 build cache

清理 dangling build cache

dangling build cache,即悬虚 build cache,指的是无法使用的 build cache。一般为悬虚镜像 dangling image 所产生的 build cache。通过 docker system prune 命令可以清除。

总结

  1. Dockerfile 就是一个由一条条指令构成的批处理文件,是一个构建镜像的计划文件。

  2. CMD 与 ENTRYPOINT 指令的["EXECUTABLE"]语法形式能用性要强于[command]形式。体现在两点上:

    • ["EXECUTABLE"]形式对于 shell 与非 shell 命令都支持
    • ENTRYPOINT 的["EXECUTABLE"]形式支持在 docker run 中指定[ARG]
  3. docker build 命令中的最后是一个 PATH 路径,其具有两个功能:

    • 指定要查找 Dockerfile 的路径
    • Dockerfile中的 ADD 与 COPY 指令中的<src>路径是相对于该路径的
  4. 对于 Java 应用的发布,一般 Dockerfile 中都会通过 ENTRYPOINT ["java", "-jar", "xxx.jar"]来启动应用

  5. 对于 EXPOSE 指令,虽然其意义为指定准备对外暴露的端口号,但实际在设置时,是使 用应用原端口号,是为了让使用该 Dockerfile 构建的镜像的用户来看的

  6. 镜像生成过程:只要镜像层文件系统或其 JSON 文件内容发生了变化,就会产生新的镜像层,生成新的镜像 ID

  7. docker build cache 机制:这种机制是一种本地磁盘镜像层的检索复用技术,用于加快镜像的构建过程,节省 docker 宿主机的磁盘空间。