简单的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 语言的编译器

    yum install -y gcc gcc-c++
    

    image-20240709144900160

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

    yum install -y glibc-static
    

    image-20240709144953964

  • 创建 hello.c

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

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

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

    gcc --static -o hello hello.c
    

    image-20240709145432360

  • 创建 Dockerfile

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

    FROM scratch
    ADD hello /
    CMD ["/hello"]
    
  • 构建镜像

    docker build -t hello-my-world .
    

    image-20240709145857671

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

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

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

  • 运行新镜像

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

    docker run --name myhelloworld -it hello-my-world:latest
    

    image-20240709150703444

  • 为镜像重打标签

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

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

    docker tag hello-my-world hello-my-world:2.0
    

    image-20240709151004035

5.3.2 构建自己的 CentOS 镜像

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

  • 创建 Dockerfile

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

    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

    docker build -t centos:02 .
    

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

    image-20240709155621328

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

  • 运行新建镜像

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

    docker run --name mycentos -it centos:02
    

    image-20240709155833796

5.3.3 悬虚镜像

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

  • 修改 Dockerfile

    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>,即变为了悬虚镜像。

    image-20240709160441863

  • 删除悬虚镜像

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

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

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

    docker rm -f 1fd5d7b3db8a
    docker image prune
    

    image-20240709160818761

5.3.4 CMD ENTERYPOINT 用法

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

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

  • CMD-shell

    • 创建 Dockerfile

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

      FROM centos:7
      CMD cal
      
    • 构建镜像 build

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

      image-20240709164204828

    • 运行新建镜像

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

      docker run mycal	
      

      image-20240709164326602

    • 覆盖 CMD

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

      image-20240709164433151

    • 不能添加命令选项

      # 这种方式无法为 CMD 中指定的默认的命令指定选项
      docker run -it mycal -y
      

      image-20240709164533792

  • CMD-exec

    • 创建 Dockerfile

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

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

      使用 Dockerfile3 构建镜像 mycal:2.0

      docker build -f ./Dockerfile3 -t mycal:2.0 .
      

      image-20240709165213964

    • 运行新建镜像

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

      docker run mycal:2.0
      docker run mycal:2.0 date
      

      image-20240709165345608

    • 不能添加命令选项

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

      docker run -it mycal:2.0 -y
      

      image-20240709165523322

  • ENTRYPOINT-shell

    • 创建 Dockerfile

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

      FROM centos:7
      ENTRYPOINT cal
      
    • 构建镜像 build

      使用 Dockerfile4 构建镜像 mycal:3.0。

      docker build -f Dockerfile4 -t mycal:3.0 .
      

      image-20240709165838845

    • 运行新建镜像

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

      image-20240709170017813

  • ENTRYPOINT-exec

    • 创建Dockerfile

      FROM centos:7
      ENTRYPOINT [&quot;cal&quot;]jj
      
    • 构建镜像

      docker build -f Dockerfile5 -t mycal:4.0 .
      

      image-20240710090426540

    • 运行新建镜像

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

      docker run -it mycal:04
      

      image-20240710090555603

    • ENTRYPOINT 不会被覆盖

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

      image-20240710090706939

    • 添加命令选项生效

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

      image-20240710090853691

  • ENTRYPOINT CMD 同用

    • 创建 Dockerfile

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

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

      使用 Dockerfile6 构建镜像 myecho:latest

      docker build -f Dockerfile6 -t myecho .
      

      image-20240710083755093

    • 运行新建镜像

      docker run --name myecho -it myecho:latest
      

      image-20240710084015950

    • 覆盖 CMD 生效

      # 在 docker run –it myecho 命令后指定新的参数,用于覆盖 CMD 中的参数,生效。
      docker run --name myecho1 -it myecho:latest jack hello
      

      image-20240710084320892

    • 总结

      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。

    image-20240710092958827

  • 创建 Dockerfile

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

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

    使用 Dockerfile 构建镜像 addcopy

    docker build -f Dockerfile -t  addcopy . 
    

    image-20240710093247045

  • 运行新建镜像

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

    docker run --name mycopyadd -it addcopy:latest
    

    image-20240710093506726

    分别查看这两个目录发现,通过 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,内容如下:

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

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

    docker build -f Dockerfile -t myargs:1.0
    

    image-20240710095008693

  • 使用 ARG 指定值构建

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

    docker build -f Dockerfile -t myargs:2.0 --build-arg name=Jerry .
    

    image-20240710095203581

5.3.7 ONBUILD 指令

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

  • 创建父镜像操作

    • 创建父镜像 Dockerfile

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

      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

      docker build -f Dockerfile -t parent:1.0 .
      

      image-20240710100855276

    • 运行父镜像

      # 运行父镜像我们发现,其工作目录为/usr/local,且没有 wget 命令
      docker run -it parent:1.0
      

      image-20240710101005315

  • 创建子镜像操作

  • 创建子镜像 Dockerfile

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

    FROM parent:1.0
    
  • 构建子镜像

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

    docker build -f Dockerfile2 -t son:1.0 .
    

    image-20240710102043715

  • 运行子镜像

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

    docker run -it son:1.0
    

    image-20240710102509280

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 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>
    
  • 配置文件为

    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"
    
  • 启动类

    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);
        }
    }
    
  • 定时任务类

    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

    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

    image-20240710104924925

5.4.2 发布应用
  • 准备目录

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

    image-20240710105222881

  • 创建 Dockerfile

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

    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
    
  • 构建镜像

    docker build -f Dockerfile -t docker-hello:1.0 .
    

    image-20240710110607897

  • 运行容器

    # 以分离模式运行容器。
    docker run --name docker-hello01 -dp 9000:8088 docker-hello:1.0
    

    image-20240710110805909

  • 访问以及查看运行情况

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

    docker logs -f --tail=10 docker-hello01
    

    image-20240710111142703

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

    192.168.138.129:9000/some/test
    

    image-20240710111554050

5.5 build cache

5.5.1 测试环境构建

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

  • 新建 hello.log

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

    echo "hello Tom" > hello.log
    

    image-20240710134357800

  • Dockerfile 举例

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

    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 第一次构建镜像。

    image-20240710135235930

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

    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。

    docker build -f Dockerfile -t test:2.0 .
    

    image-20240710135957176

  • 查看各自history

    docker history test:1.0
    docker history test:2.0
    

    image-20240710140240265

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

  • 删除 test:1.0 镜像

    docker rmi test:1.0
    

    image-20240710140410181

  • 再构建新镜像

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

    docker build -f Dockerfile -t test:3.0 .
    

    image-20240710140531524

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。

    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 命令可以清除。

docker system prune
  • 21
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值