简单的docker学习 第5章 Dockerfile

第5章 Dockerfile

5.1Dockerfile 简介

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

5.2 指令简介

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

  • 指令是大小不敏感的,但惯例是写为全大写。

  • 指令后至少会携带一个参数。

  • # 号开头的行为注释

5.2.1 FROM
  • 【语法】FROM <image>[:<tag>]

    【解析】用于指定基础镜像,且必须是第一条指令;若省略了 tag,则默认为 latest。

5.2.2 MAINTAINER
  • 【语法】MAINTAINER <name>

    【解析】MAINTAINER 指令的参数填写的一般是维护者姓名和信箱。不过,该指令官方已不建议使用,而是使用 LABEL 指令代替。

5.2.3 LABEL
  • 【语法】LABEL <key>=<value> <key>=<value>

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

5.2.4 ENV
  • 【语法 1】ENV <key> <value>

    【解析】用于指定环境变量,这些环境变量,后续可以被 RUN 指令使用,容器运行起来之后,也可以在容器中获取这些环境变量。

  • 【语法 2】ENV <key1>=<value1> <key2>=<value2> ...

    【解析】可以设置多个变量,每个变量为一对<key>=<value>指定。

5.2.5 WORKDIR
  • 【语法】WORKDIR path

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

5.2.6 RUN
  • 【语法 1】RUN <command>

    【解析】这里的<command>就是 shell 命令。docker build 执行过程中,会使用 shell 运行指定的 command。

  • 【语法 2】RUN ["EXECUTABLE","PARAM1","PARAM2", ...]

    【解析】在 docker build 执行过程中,会调用第一个参数"EXECUTABLE"指定的应用程序运行,并使用后面第二、三等参数作为应用程序的运行参数。

5.2.7 CMD
  • 【语法 1】CMD ["EXECUTABLE","PARAM1","PARAM2", ...]

    【解析】在容器启动后,即在执行完 docker run 后会立即调用执行"EXECUTABLE"指定的可执行文件,并使用后面第二、三等参数作为应用程序的运行参数。

  • 【语法 2】CMD command param1 param2, ...

    【解析】这里的 command 就是 shell 命令。在容器启动后会立即运行指定的 shell 命令。

  • 【语法 3】CMD ["PARAM1","PARAM2", ...]

    【解析】提供给 ENTERYPOINT 的默认参数。

5.2.8 ENTRYPOINT
  • 【语法 1】ENTRYPOINT ["EXECUTABLE","PARAM1","PARAM2", ...]

    【解析】在容器启动过程中,即在执行 docker run 时,会调用执行"EXECUTABLE"指定的应用程序,并使用后面第二、三等参数作为应用程序的运行参数。

  • 【语法 2】ENTRYPOINT command param1 param2, ...

    【解析】这里的 command 就是 shell 命令。在容器启动过程中,即在执行 docker run 时,会运行指定的 shell 命令。

5.2.9 EXPOSE
  • 【语法】EXPOSE [...]

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

5.2.10 ARG
  • 【语法】ARG < varname >[=]

    【解析】定义一个变量,该变量将会使用于镜像构建运行时。若要定义多个变量,则需要定义多个 ARG 指令。

5.2.11 ADD
  • 【语法 1】ADD

  • 【语法 2】ADD ["", ""] # 路径中存在空格时使用双引号引起来

    【解析】该指令将复制当前宿主机中指定文件 src 到容器中的指定目录 dest 中。src 可以是宿主机中的绝对路径,也可以时相对路径。但相对路径是相对于 docker build 命令所指定的路径的。src 指定的文件可以是一个压缩文件,压缩文件复制到容器后会自动解压为目录;src 也可以是一个 URL,此时的 ADD 指令相当于 wget 命令;src 最好不要是目录,其会将该目录中所有内容复制到容器的指定目录中。dest 是一个绝对路径,其最后面的路径必须要加上斜杠,否则系统会将最后的目录名称当做是文件名的

5.2.12 COPY
  • 【说明】功能与 ADD 指令相同,只不过 src 不能是 URL。若 src 为压缩文件,复制到容器后不会自动解压。
5.2.13 ONBUILD
  • 【语法】ONBUILD [INSTRUCTION]

    【解析】该指令用于指定当前镜像的子镜像进行构建时要执行的指令。

5.2.14 VOLUME
  • 【语法】VOLUME ["dir1", "dir2", ...]

    【解析】在容器创建可以挂载的数据卷。

5.3 指令用法

5.3.1 构建自己的 HelloWorld 镜像

前面我们运行了拉取自镜像中心的 hello-world 镜像,这里我们要构建一个自己的hello-my-world 镜像

  • scratch 镜像

构建自己的镜像之前,首先要了解一个特殊的镜像 scratch。scratch 镜像是一个空镜像,是所有镜像的 Base Image(相当于面向对象编程中的 Object

类)。scratch 镜像只能在 Dockerfile 中被继承,不能通过 pull 命令拉取,不能 run,也没有 tag。并且它也不会生成镜像中的文件系统层。在 Docker 中,scratch 是一个保留字,用户不能作为自己的镜像名称使用

  • 安装编译器

    由于下面要编写、编译一段 C 语言代码,所以这里先安装一下 C 语言的编译器

    shell 复制代码
    yum install -y gcc gcc-c++

    由于后面在编译时要使用 C 的静态库,所以要再安装 glibc-static。

    sh 复制代码
    yum install -y glibc-static
  • 创建 hello.c

    在宿主机任意目录创建一个名称为 hello.c 的文件。这里在/root 下 mkdir 一个目录 hw,然后将 hello.c 文件创建在这里。文件内容如下:

    c++ 复制代码
    #include<stdio.h>
    int main()
    {
    	printf("hello my docker world\n");
    	return 0;
    }
  • 编译测试 hello.c

    使用 gcc 编译 hello.c 文件,出现可执行hello文件

    shell 复制代码
    gcc --static -o hello hello.c
  • 创建 Dockerfile

    在 hw 目录中新建 Dockerfile,内容如下:

    sh 复制代码
    FROM scratch
    ADD hello /
    CMD ["/hello"]
  • 构建镜像

    shell 复制代码
    docker build -t hello-my-world .

    -t 用于指定要生成的镜像的与。若省略 tag,则默认为 latest。

    最后的点(.)是一个宿主机的 URL 路径,构建镜像时会从该路径中查找 Dockerfile 文件。同时该路径也是在 Dockerfile 中 ADD、COPY 指令中若使用的是相对路径,那个相对路径就相对的这个路径。不过需要注意,即使 ADD、COPY 指令中使用绝对路径来指定源文件,该源文件所在路径也必须要在这个 URL 指定目录或子目录内,否则将无法找到该文件。

    通过 docker images 查看本地镜像,可以看到新构建的 hello-my-world 镜像。

  • 运行新镜像

    在任意目录下都可运行该镜像

    shell 复制代码
    docker run --name myhelloworld -it hello-my-world:latest
  • 为镜像重打标签

    某镜像被指定为 latest 后,后期又出现了更新的版本需要被指定为 latest,那么原 latest镜像就应被重打标签,否则,当最新版被发布为 latest 后,原镜像就会变为悬虚镜像。

    通过 docker tag 命令可对镜像重打标签。所谓重打标签,实际是复制了一份原镜像,并为新的镜像指定新的。当然,重新指定也是可以的。所以,新镜像的 ImageID、Digest 都有原镜像的相同

    shell 复制代码
    docker tag hello-my-world hello-my-world:2.0
5.3.2 构建自己的 CentOS 镜像

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

  • 创建 Dockerfile

    在宿主机任意目录创建一个文件,并命名为 Dockerfile。这里在/root 下 mkdir 一个目录dfs。然后将如下内容复制到该文件中:

    shell 复制代码
    FROM centos:7
    # 作者不推荐使用,现在推荐使用LABLE
    MAINTAINER lxc lxc@163.com
    # 信息键值对
    LABEL version="1.0" description="this is a custom centos image"
    # 定义工作目录变量,执行连接会进入此目录
    ENV WORKPATH /usr/local
    WORKDIR $WORKPATH
    # 因为默认镜像不好用,所以替换为阿里镜像
    RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
    RUN curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
    RUN yum -y install vim net-tools wget
    CMD /bin/bash
  • 构建镜像 build

    shell 复制代码
    docker build -t centos:02 .

    使用docker images 查看是否正确生成镜像文件

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

  • 运行新建镜像

    运行了新镜像后,发现默认路径是/usr/local 了,ifconfig、vim 命令可以使用了。

    shell 复制代码
    docker run --name mycentos -it centos:02
5.3.3 悬虚镜像

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

  • 修改 Dockerfile

    shell 复制代码
    FROM centos:7
    # 作者不推荐使用,现在推荐使用LABLE
    MAINTAINER lxc lxc@163.com
    # 信息键值对
    LABEL version="2.0" description="this is a custom centos image"
    # 定义工作目录变量,执行连接会进入此目录
    ENV WORKPATH /usr/local
    WORKDIR $WORKPATH
    # 因为默认镜像不好用,所以替换为阿里镜像
    RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
    RUN curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
    RUN yum -y install vim net-tools wget
    CMD /bin/bash
  • 构建镜像 build

    构建镜像时仍然指定镜像为 cucentos:02,与前面的镜像完全重名

    docker build -t centos:02 .
    

    构建完毕后,再次查看镜像,发现原来 cucentos:02 镜像的名称与 Tag 均变为了<none>,即变为了悬虚镜像。

  • 删除悬虚镜像

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

    对于悬虚镜像的删除,除了可以通过 docker rmi <imageID>进行删除外,还有专门的删除命令 docker image prune。该命令能够一次性删除本地全部的悬空镜像。不过有个前提,就是这些悬虚镜像不能是已经启动了容器的,无论容器是否是退出状态。当然,如果再加上-a选项,则会同时再将没有被任何容器使用的镜像也删除。

    另外,还有一个命令 docker system prune 也可以删除悬虚镜像。只不过,其不仅删除的是悬虚镜像,还有其它系统"无用"内容。在删除这个悬虚镜像之前,首先查看其是否启动了容器。如果启动了,则先将容器删除。

    shell 复制代码
    docker rm -f 1fd5d7b3db8a
    docker image prune
5.3.4 CMD ENTERYPOINT 用法

这两个指令都用于指定容器启动时要执行的命令,无论哪个指令,每个 Dockerfile 中都只能有一个 CMD/ENTERYPOINT 指令,多个 CMD/ENTERYPOINT 指令只会执行最后一个

不同的是,CMD 指定的是容器启动时默认的命令 ,而 ENTRYPOINT 指定的是容器启动时一定会执行的命令。即 docker run 时若指定了要运行的命令,Dockerfile 中的 CMD 指令指定的命令是不会执行的,而 ENTERYPOINT 中指定的命令是一定会执行的。

  • CMD-shell

    • 创建 Dockerfile

      在 dfs 目录中新建文件 Dockerfile2,并定义内容如下。

      shell 复制代码
      FROM centos:7
      CMD cal
    • 构建镜像 build

      shell 复制代码
      # 说明:-f 用于指定本次构建所要使用的 Dockerfile 文件。如果文件名不是 docker build 默认加载的 Dockerfile 这个名称。
      docker build -f ./Dockerfile2 -t mycal .
    • 运行新建镜像

      运行后可以查看到当前月份的日历

      shell 复制代码
      docker run mycal	
    • 覆盖 CMD

      shell 复制代码
      # 在 docker run 命令中指定要执行的命令,Dockerfile 中通过 CMD 指定的默认的命令就不会在执行。
      docker run mycal date
    • 不能添加命令选项

      shell 复制代码
      # 这种方式无法为 CMD 中指定的默认的命令指定选项
      docker run -it mycal -y
  • CMD-exec

    • 创建 Dockerfile

      在 dfs 目录中新建文件 Dockerfile3,并将如下内容复制到文件中。

      shell 复制代码
      FROM centos:7
      # 要执行/bin/bash -c表示要执行内容来自命令行,cal表示展示日历命令
      CMD ["/bin/bash", "-c", "cal"]
    • 构建镜像 build

      使用 Dockerfile3 构建镜像 mycal:2.0

      shell 复制代码
      docker build -f ./Dockerfile3 -t mycal:2.0 .
    • 运行新建镜像

      运行结果与 shell 方式的相同。

      docker run mycal:2.0
      docker run mycal:2.0 date
      
    • 不能添加命令选项

      虽然在 CMD 中指定可以从命令行接收选项,但运行结果与 shell 方式的相同,也不能添加命令选项。这是由 CMD 命令本身决定的

      docker run -it mycal:2.0 -y
      
  • ENTRYPOINT-shell

    • 创建 Dockerfile

      在 dfs 目录中新建文件 Dockerfile4,并将如下内容复制到文件中。

      shell 复制代码
      FROM centos:7
      ENTRYPOINT cal
    • 构建镜像 build

      使用 Dockerfile4 构建镜像 mycal:3.0。

      shell 复制代码
      docker build -f Dockerfile4 -t mycal:3.0 .
    • 运行新建镜像

      shell 复制代码
      # 直接执行
      docker run mycal:3.0
      # cmd覆盖无效
      docker run mycal:3.0 date
      # 添加命令选项无效 -y指的是展示全年日历
      docker run mycal:3.0 date -y
  • ENTRYPOINT-exec

    • 创建Dockerfile

      ishell 复制代码
      FROM centos:7
      ENTRYPOINT [&quot;cal&quot;]jj
    • 构建镜像

      sh 复制代码
      docker build -f Dockerfile5 -t mycal:4.0 .
    • 运行新建镜像

      运行结果与 shell 方式的相同。

      shell 复制代码
      docker run -it mycal:04
    • ENTRYPOINT 不会被覆盖

      运行结果会报错,系统认为 date 是 cal 的非法参数。

    • 添加命令选项生效

      与之前不同的是,这种情况下在 docker run 中添加的命令选项是有效的

  • ENTRYPOINT CMD 同用

    • 创建 Dockerfile

      在 dfs 目录中新建文件 Dockerfile6,并将如下内容复制到文件中。此时的 CMD 中给出的就是 ENTRYPOINT 的参数,注意不能是选项。

      shell 复制代码
      FROM centos:7
      # CMD内容作为ENTRYPOINT参数
      CMD ["hello world"]
      # 执行命令
      ENTRYPOINT ["echo"]
    • 构建镜像 build

      使用 Dockerfile6 构建镜像 myecho:latest

      shell 复制代码
      docker build -f Dockerfile6 -t myecho .
    • 运行新建镜像

      shell 复制代码
      docker run --name myecho -it myecho:latest
    • 覆盖 CMD 生效

      shell 复制代码
      # 在 docker run --it myecho 命令后指定新的参数,用于覆盖 CMD 中的参数,生效。
      docker run --name myecho1 -it myecho:latest jack hello
    • 总结

      Dockerfile 中的[command]或["EXECUTABLE"]如果是通过 CMD 指定的,则该镜像的启动命令 docker run 中是不能添加参数[ARG]的。因为 Dockerfile 中的 CMD 是可以被命令中的**[COMMAND]**替代的。如果命令中的 IMAGE 后仍有内容,此时对于 docker daemon 来说,其首先认为是替代用的[COMMAND],如果有两个或两个以上的内容,后面的内容才会认为是[ARG]。所以,添加的-y 会报错,因为没有-y 这样的[COMMAND]。

      Dockerfile 中的[command]或["EXECUTABLE"]如果是通过 ENTRYPOINT 指定的,则该镜像的启动命令 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"]方式的通用性会更强些。

5.3.5 ADD 与 COPY指令
  • 准备工作

    在宿主机/root 目录中 mkdir 一个目录 ac。将事先下载好的任意某 tar.gz 包上传到/root/ac目录。本例在 zookeeper 官网 https://zookeeper.apache.org 下载了 zookeeper 的 tar.gz 压缩包,在上传到/root/ac 目录后,为了方便又重命名了这个压缩包为 zookeeper.tar.gz。

  • 创建 Dockerfile

    在/root/ac 目录中新建文件 Dockerfile,内容如下:

    shell 复制代码
    FROM centos:7
    WORKDIR /opt
    ADD zookeeper.tar.gz /opt/add/
    COPY zookeeper.tar.gz /opt/copy/
    CMD /bin/bash
  • 构建镜像 build

    使用 Dockerfile 构建镜像 addcopy

    shell 复制代码
    docker build -f Dockerfile -t  addcopy . 
  • 运行新建镜像

    启动 addcopy 镜像,在容器的/opt 目录中发现自动生成两个目录 addh 与 copy。

    shell 复制代码
    docker run --name mycopyadd -it addcopy:latest

    分别查看这两个目录发现,通过 ADD 指令添加的是解压过的目录,而通过 COPY 指令添加的是未解压的。这就是 ADD 与 COPY 指令的区别

  • 注意

    • 我们需要添加add 或者 copy 的文件我们要跟Dockerfile文件放置于同一目录,否则build时候会出现文件找不到问题
    • add 或者copy 后面的destnation目录需要加 "/" eg: /opt/local/ ,否则复制活添加文件会变成 /opt/local 中的local文件,local不再是目录
5.3.6 ARG 指令

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

  • 创建 Dockerfile

    mkdir 一个名称为 arg 的目录,在其中新建文件 Dockerfile,内容如下:

    shell 复制代码
    FROM centos:7
    ARG name=Tom
    # RUN 指令用于指定在 docker build 执行时要执行的内容。
    RUN echo $name
  • 使用 ARG 默认值构建

    使用 Dockerfile 构建镜像 myargs:1.0。我们可以看到,在镜像构建时读取了 ARG 中参数,只不过 docker build 中并没有给变量 name 赋予新值,所以 name 使用的是其默认值 Tom。

    shell 复制代码
    docker build -f Dockerfile -t myargs:1.0
  • 使用 ARG 指定值构建

    使用 Dockerfile 构建镜像 myargs:2.0。在 docker build 命令中指定了 ARG 中参数值,覆盖了默认值

    shell 复制代码
    docker build -f Dockerfile -t myargs:2.0 --build-arg name=Jerry .
5.3.7 ONBUILD 指令

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

  • 创建父镜像操作

    • 创建父镜像 Dockerfile

      mkdir 一个名称为 onbuild 的目录,并在其中新建文件 Dockerfile,内容如下:

      shell 复制代码
      FROM centos:7
      ENV WORKPATH /usr/local
      WORKDIR $WORKPATH
      # 1、备份备份官方的原yum源的配置
      RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
      
      # 2、下载Centos-7.repo文件
      # wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
      
      # 注意:部分小伙伴可能没有安装wget,需要先安装wget,或者用下面的命令下载repo文件
      RUN curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
      
      # 子镜像进行build时候才会执行下载工具
      ONBUILD RUN yum -y install wget
      CMD /bin/bash

      当前镜像及其将来子镜像的工作目录都将是/usr/local,将来以交互模式运行后都会直接进入到 bash 命令行。ONBUILD 中指定要安装的 wget 命令,是在子镜像进行 docker build 时会 RUN 的安装命令。

    • 构建父镜像

      使用 Dockerfile 构建镜像 parent:1.0

      shell 复制代码
      docker build -f Dockerfile -t parent:1.0 .
    • 运行父镜像

      shell 复制代码
      # 运行父镜像我们发现,其工作目录为/usr/local,且没有 wget 命令
      docker run -it parent:1.0
  • 创建子镜像操作

  • 创建子镜像 Dockerfile

    在 onbuild 目录中新建文件 Dockerfile2,内容仅包含一句话,指定父镜像。

    shell 复制代码
    FROM parent:1.0
  • 构建子镜像

    子镜像在构建过程中下载了 wget 命令。

    shell 复制代码
    docker build -f Dockerfile2 -t son:1.0 .
  • 运行子镜像

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

    shell 复制代码
    docker run -it son:1.0
5.3.8 构建新镜像的方式总结

可以构建出新的镜像的方式有:

  • docker build

  • docker commit

  • docker import (注意,docker load 并没有构建出新的镜像,其与原镜像是同一个镜像)

  • docker compose

  • docker hub 中完成 Automated Builds

5.4 应用发布

开发出的应用程序如何通过 Dockerfile 部署到 Docker 容器中?下面就通过将一个 Spring Boot 应用部署到 Docker 为例来说明这个部署过程

5.4.1 准备应用

下面的应用就是一个名称为 hello-docker 的最简单的 Spring Boot 工程。

  • pom文件

    xml 复制代码
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>spring-batch</artifactId>
            <groupId>cn.git</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>docker-hello</artifactId>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-autoconfigure</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
  • 配置文件为

    yaml 复制代码
    spring:
      application:
        name: docker-hello
    server:
      port: 8088
    
    logging:
      pattern:
        console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
  • 启动类

    java 复制代码
    package cn.git;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * @description: activiti迁移测试类
     * @program: bank-credit-sy
     * @author: lixuchun
     * @create: 2024-06-07
     */
    @SpringBootApplication(scanBasePackages = "cn.git")
    public class helloApplication {
        public static void main(String[] args) {
            SpringApplication.run(helloApplication.class, args);
        }
    }
  • 定时任务类

    java 复制代码
    package cn.git.task;
    
    import org.springframework.scheduling.annotation.EnableScheduling;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    /** 
     * @description: 简单定时任务
     * @program: bank-credit-sy
     * @author: lixuchun
     * @create: 2024-07-10
     */
    @Component
    @EnableScheduling
    public class TimerTask {
    
        /**
         * 每5秒执行一次
         */
        @Scheduled(cron = "0/1 * * * * ?")
        public void timer() {
            System.out.println("定时任务执行 : " + System.currentTimeMillis());
        }
    }
  • controller

    java 复制代码
    package cn.git.controller;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @description: 测试controller
     * @program: bank-credit-sy
     * @author: lixuchun
     * @create: 2024-07-10
     */
    @RequestMapping("/some")
    @RestController
    public class SomeController {
    
        @GetMapping("/test")
        public String someHandle() {
            System.out.println("================Docker file test==============");
            return "hello world";
        }
    
    }
  • 打为 Jar

5.4.2 发布应用
  • 准备目录

    在宿主机中需要为应用创建一个专门的目录。该目录不仅用于存放应用的 Jar 包,还用于存放应用的 Dockerfile 文件与数据卷目录。目录名随意,一般为项目名称。本例在/root 下 mkdir 一个目录 docker-hello,并将前面应用打好的 Jar 包上传到该目录。

  • 创建 Dockerfile

    在/root/hello-docker 目录中创建 Dockerfile 文件,文件内容如下:

    shell 复制代码
    FROM openjdk:8u102
    MAINTAINER lxc lxc@163.com
    LABEL version="1.0" description="first docker app"
    COPY docker-hello-1.0-SNAPSHOT.jar hd.jar
    ENTRYPOINT ["java", "-jar", "hd.jar"]
    # 映射端口 9000,对外访问端口,但是没有生效,如果想生效还是要使用 -p 命令,一般设置为应用原有端口,比如此处设置为8088
    EXPOSE 8088
  • 构建镜像

    shell 复制代码
    docker build -f Dockerfile -t docker-hello:1.0 .
  • 运行容器

    shell 复制代码
    # 以分离模式运行容器。
    docker run --name docker-hello01 -dp 9000:8088 docker-hello:1.0
  • 访问以及查看运行情况

    使用日志查看命令查看日志生成情况

    shell 复制代码
    docker logs -f --tail=10 docker-hello01

    使用浏览器访问结果如下所示

    properties 复制代码
    192.168.138.129:9000/some/test

5.5 build cache

5.5.1 测试环境构建

为了了解什么是 build cache,理解镜像构建过程中 build cache 机制,这里需要先搭建一个测试环境

  • 新建 hello.log

    在/root 下 mkdir 一个目录 cache,在其中新建 hello.log 文件。

    shell 复制代码
    echo "hello Tom" > hello.log
  • Dockerfile 举例

    在/root/cache 中创建一个 Dockerfile 文件,内容如下:

    shell 复制代码
    FROM centos:7
    LABEL auth="Tom"
    RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
    RUN curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
    COPY hello.log /var/log/
    RUN yum -y install vim
    CMD /bin/bash
  • 第一次构建镜像

    这是使用前面的 Dockerfile 第一次构建镜像。

5.5.2 镜像的生成过程

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

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

下面将针对上面的例子逐条指令的分析镜像的构建过程。

  • FROM centos:7

    FROM 指令是 Dockerfile 中唯一不可缺少的指令,它为最终构建出的镜像设定了一个基础镜像(Base Image)。该语句并不会产生新的镜像层,它是使用指定的镜像作为基础镜像层的。docker build 命令解析 Dockerfile 的 FROM 指令时,可以立即获悉在哪一个镜像基础上完成下一条指令镜像层构建。

    对于本例,Docker Daemon 首先从 centos:7 镜像的文件系统获取到该镜像的 ID,然后再根据镜像 ID 提取出该镜像的 json 文件内容,以备下一条指令镜像层构建时使用。

  • LABEL auth="Tom"

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

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

  • COPY hello.log /var/log/

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

  • RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup

    执行RUN命令,修改镜像内部文件修改,镜像层文件内容修改,形成新镜像

  • RUN curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo

    下载文件修改源文件,镜像层文件修改,新增文件大小变化,形成新镜像

  • RUN yum -y install vim

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

  • CMD /bin/bash

    对于 CMD 或 ENTRYPOINT 指令,其是不会改变镜像层文件系统大小的,因为其不会在docker build 过程中执行。所以该条指令没有改变镜像层文件系统大小。

    但对于 CMD 或 ENTRYPOINT 指令,由于其是将来容器启动后要执行的命令,所以会将该条指令写入到 json 文件中,会引发 json 文件的变化。所以 json 文件中的镜像 ID 也就发生了变化,产生了新的镜像层

5.5.3 修改 Dockerfile 后重新构建
  • 修改 Dockerfile

    shell 复制代码
    FROM centos:7
    LABEL auth="Tom"
    COPY hello.log /var/log/
    RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
    RUN curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
    RUN yum -y install vim
    CMD /bin/bash
    EXPOSE 9000
  • 构建新镜像

    此时再构建新的镜像 test:2.0,会发现没有下载安装 vim 的过程了,但发现了很多的 Using cache。说明这是使用了 build cache。

    shell 复制代码
    docker build -f Dockerfile -t test:2.0 .
  • 查看各自history

    shell 复制代码
    docker history test:1.0
    docker history test:2.0

    发现 test:2.0 中的镜像层,除了新增加指令 EXPOSE 镜像层外,其它层完全与 test:1.0 的相同。test:2.0 在构建时复用了 test:1.0 的镜像层

  • 删除 test:1.0 镜像

    shell 复制代码
    docker rmi test:1.0
  • 再构建新镜像

    再次构建 test:3.0 镜像。发现仍然使用了大量的 build cache,就连 EXPOSE 指令镜像也使用了 build cache。

    shell 复制代码
    docker build -f Dockerfile -t test:3.0 .
5.5.4 build cache 机制

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

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

5.5.5 build cache 失效

docker build cache 在以下几种情况下会失效。

  • Dockerfile 文件发生变化

    当 Dockerfile 文件中某个指令内容发生变化,那么从发生变化的这个指令层开始的所有镜像层 cache 全部失效。即从该指令行开始的镜像层将构建出新的镜像层,而不再使用 build cache,即使后面的指令并未发生变化。

    因为镜像关系本质上是一种树状关系,只要其上层节点变了,那么该发生变化节点的所有下层节点也就全部变化了。

  • ADD COPY 指令内容变化

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

  • RUN 指令外部依赖变化

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

  • 指定不使用 build cache

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

    shell 复制代码
    docker build -f Dockerfile --no-cache -t test:5.0 .
5.5.6 清理玄虚镜像 prune

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

shell 复制代码
docker system prune
相关推荐
公贵买其鹿4 分钟前
List深拷贝后,数据还是被串改
java
摸鱼也很难1 小时前
Docker 镜像加速和配置的分享 && 云服务器搭建beef-xss
运维·docker·容器
xlsw_3 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹4 小时前
基于java的改良版超级玛丽小游戏
java
鸠摩智首席音效师4 小时前
Docker 中如何限制CPU和内存的使用 ?
docker·容器
Michaelwubo4 小时前
Docker dockerfile镜像编码 centos7
运维·docker·容器
jingyu飞鸟4 小时前
centos-stream9系统安装docker
linux·docker·centos
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭4 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
好像是个likun5 小时前
使用docker拉取镜像很慢或者总是超时的问题
运维·docker·容器
暮湫5 小时前
泛型(2)
java