第一章:Docker 存储

1.1 管理卷

  • 卷出现的目的就是为了保存数据。
  • docker volume 命令可以对 Docker 自己管理的卷(/var/lib/docker/volumes/xx)目录进行操作。

1.1.1 列出所有卷

  • 命令:
  1. docker volume ls
  • 示例:
  1. docker volume ls

10.gif

1.1.2 创建卷

  • 命令:
  1. docker volume create xxx
  • 示例:
  1. docker volume create demo

11.gif

1.1.3 查询卷详情

  • 命令:
  1. docker volume inspect xxx
  • 示例:
  1. docker volume inspect demo

12.gif

1.1.4 删除卷

  • 命令:
  1. docker volume rm xxx
  • 示例:
  1. docker volume rm demo

13.gif

1.1.5 移除无用卷

  • 命令:
  1. docker volume prune
  • 示例:
  1. docker volume prune

14.gif

1.2 Docker 镜像如何存储?

  • 查看 Nginx 镜像的分层:
  1. docker history nginx

15.png

  • 查看 Nginx 镜像如何存储:
  1. docker image inspect nginx

16.gif

  • 其中,GraphDriver 的内容如下:
  1. "GraphDriver": {
  2. "Data": {
  3. "LowerDir": "/var/lib/docker/overlay2/d444ed42e9cdb93189118904b1fe896a364e9fc97829aa42423accd15becc932/diff:/var/lib/docker/overlay2/311b3d254b54de0a58bcb46726a1a26e009ee2fcb036853cd5c3e976fc7fca52/diff:/var/lib/docker/overlay2/25b50659028f1a9e8109f2cc4a797111ca60196ce5fa61cb8c0ec1ad5513eafc/diff:/var/lib/docker/overlay2/549a1e09e583ad1962dc4fc7279e552f32fa59f0efc3c8f3c5534f45e52fd25b/diff:/var/lib/docker/overlay2/88471fc9dd8656957aae4f68beb6349bec4a29f6eec4ca199aecb89f67a37e93/diff",
  4. "MergedDir": "/var/lib/docker/overlay2/ee25435b99705882168a432aa478a7bd94ace2b0dde052b88bb731b006eab128/merged",
  5. "UpperDir": "/var/lib/docker/overlay2/ee25435b99705882168a432aa478a7bd94ace2b0dde052b88bb731b006eab128/diff",
  6. "WorkDir": "/var/lib/docker/overlay2/ee25435b99705882168a432aa478a7bd94ace2b0dde052b88bb731b006eab128/work"
  7. },
  8. "Name": "overlay2"
  9. },
  • 其中,LowerDir 是底层目录,包含小型 Linux 和装好的软件:
  1. # 用户文件
  2. /var/lib/docker/overlay2/d444ed42e9cdb93189118904b1fe896a364e9fc97829aa42423accd15becc932/diff

21.png

  1. # 用户文件
  2. /var/lib/docker/overlay2/311b3d254b54de0a58bcb46726a1a26e009ee2fcb036853cd5c3e976fc7fca52/diff

20.png

  1. # nginx的启动命令
  2. /var/lib/docker/overlay2/25b50659028f1a9e8109f2cc4a797111ca60196ce5fa61cb8c0ec1ad5513eafc/diff

19.png

  1. # nginx的配置文件在这里
  2. /var/lib/docker/overlay2/549a1e09e583ad1962dc4fc7279e552f32fa59f0efc3c8f3c5534f45e52fd25b/diff

18.png

  1. # 小型 Linux 文件系统
  2. /var/lib/docker/overlay2/88471fc9dd8656957aae4f68beb6349bec4a29f6eec4ca199aecb89f67a37e93/diff

17.png

  • 其中,MergedDir 是合并目录。容器最终的完整工作目录全内容都在合并层;数据卷在容器层产生;所有的增删改都在容器层;

22.png

  • 其中,UpperDir:上层目录。
  • 其中,WorkDir:工作目录(临时层),比如:pid 等。
  • 总而言之,Docker 底层的 storage dirver 完成了以上的目录组织结果。

1.3 容器如何挂载

1.3.1 概述

23.png

  • Docker 支持三种挂载方式:
    • ① Docker 自动在外部创建文件夹,并自动挂载到容器内部指定的文件夹中(Dockerfile 中的 VOLUME 指令,即 /var/lib/docker/volumes/xx/目录)。
    • ② 自己在外部创建文件夹,手动进行挂载。
    • ③ 可以将数据挂载到内存中(不使用)。
  • 其中,--mount 参数挂载到 Linux 宿主机,但是需要手动挂载(不使用);-v 参数可以实现自动挂载,挂载 Linux 主机或 Docker 自动管理的区域。

1.3.2 volume(卷)

  • 匿名卷(什么也不需要写,也不要加冒号,直接写容器内的目录):
  1. # Docker 将创建出匿名卷,并保存容器 /usr/share/nginx/html 下面的内容
  2. docker run -d -P -v /usr/share/nginx/html nginx

24.gif

  • 具名卷:
  1. docker run -d -P -v nginx:/usr/share/nginx/html nginx

25.gif

1.3.3 绑定挂载

  • 如果将绑定安装或非空卷安装到存在某些文件或目录的容器中的目录中,则这些文件或目录会被安装遮盖,就像您将文件保存到 Linux 主机上的 /mnt 中一样,然后 将 USB 驱动器安装到 /mnt 中。在卸载 USB 驱动器之前,/mnt 的内容将被 USB 驱动器的内容遮盖。 被遮盖的文件不会被删除或更改,但是在安装绑定安装或卷时将无法访问。
  • 总而言之:外部目录覆盖内部容器目录内容,但不是修改。所以需要谨慎,外部空文件夹挂载方式可能会导致容器内部是空文件夹而导致容器启动失败。
  1. docker run -d -P -v /var/nginx/html:/usr/share/nginx/html nginx

26.png

1.3.4 总结

  • 当使用 -v 参数的时候,如果是 docker run 宿主机绝对路径:Docker容器内部绝对路径 的方式,就是挂载,会有空挂载的问题;如果是 docker run -v 不以/开头的路径:Docker容器内部绝对路径的方式,就是绑定,Docker 会自动管理,Docker 不会将它当做目录,而是当做卷。

  • 在实际开发中,我们如何使用?

  • ① 如果是开发测试,用 -v 绝对路径的方式。
  • ② 如果是生产环境,建议使用具名卷的方式。
  • ③ 除非特殊如 /var/run/docker.sock 需要挂载主机路径的操作需要使用 -v 绝对路径的方式,否则一般应该使用具名卷的方式。

1.3.5 模拟实际开发

  • 模拟实际开发中使用 Nginx :
  1. docker run -d -P -v nginxhtml:/usr/share/nginx/html -v nginxconf:/etc/nginx nginx

27.png

第二章:Dockerfile 解析

2.1 Dockerfile 是什么?

  • Dockerfile 是用来构建 Docker 镜像的文本文件,是有一条条构建镜像所需要的指令和参数所组成的脚本文件,类似于 Linux 中的 Shell 脚本文件。

1.bmp

2.2 使用 Dockerfile 文件构建镜像的步骤

  • ① 编写 Dockerfile 文件。
  • ② 使用 docker build 命令构建镜像。
  • ③ 使用 docker run 命令根据生成的镜像运行容器。

2.3 Dockerfile 的构建过程

2.3.1 Dockerfile 基础知识

  • ① 每条保留字指令都 必须为大写字母 且后面要跟随至少一个参数。
  • ② 指令按照从上到下的顺序依次执行。
  • # 表示注释。
  • ④ 每条指令都会创建一个新的镜像层并对镜像进行提交。

2.3.2 Docker 执行 Dockerfile 的大致流程

  • ① Docker 从基础镜像上运行一个容器。
  • ② 执行一条指令并对容器进行修改。
  • ③ 执行类似 docker commit 的操作提交一个新的镜像层。
  • ④ Docker 再基于刚才提交的镜像运行一个新的容器。
  • ⑤ 依次类推,直到 Dockerfile 文件中的所有指令都执行完成。

2.3.3 总结

  • 从应用软件的角度来看,Dockerfile、Docker 镜像和 Docker 容器分别代表软件的三个不同的阶段:
    • Dockerfile 是软件的原材料。
    • Docker 镜像是软件的交付品。
    • Docker 容器则可以认为是软件镜像的运行态,即根据镜像运行的容器实例。
  • Dockerfile 面向开发,Docker 镜像成为交付标准,Docker 容器则涉及部署和运维,三者缺一不可,合力充当了 Docker 体系的基石。

2.png

  • Dockerfile 定义了进程需要的一切东西。Dockerfile 涉及的内容包括执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程(当应用进程需要和系统服务以及内核进程打交道的时候,还需要考虑如何设计 namespace 的权限控制)等等。
  • Docker 镜像就是在编写了一个 Dockerfile 文件之后,使用 docker build 命令来产生一个镜像,当运行 Docker 镜像的时候会真正的提供服务。
  • Docker 容器是直接提供服务的。

2.4 Dockerfile 保留字指令

  • 一般而言,Dockerfile 可以分为四个部分:基础镜像信息维护者信息镜像操作指令启动时执行指令。 | 指令 | 说明 | | —- | —- | | FROM | 指定基础镜像。 | | MAINTAINER | 指定维护着信息,已过期,可以使用 LABEL xxx=yyy 来代替。 | | RUN | 镜像构建过程中运行的命令。 | | CMD | 指定启动容器时默认的命令。 | | ENTRYPOINT | 指定镜像的默认入口以及运行命令 。 | | EXPOSE | 声明镜像内服务监听的端口,一般而言,此指令只有指导意义,如:SpringBoot 项目的端口是 8080 ,而指定的 EXPOSE 是 8090 ,当然依据 8080 了。 | | ENV | 指定环境变量,可以在 docker run 的时候使用 -e 改变。 | | ADD | 复制指定的 src 路径下的内容到容器中的 dest 路径下,src 可以为 url 会自动下载,也可以为 tar 文件,会自动解压。 | | COPY | 复制本地主机的 src 路径下的内容到镜像中的 dest 路径下,但是不会自动解压等等。 | | LABEL | 指定生成镜像的元数据标签信息。 | | VOLUME | 创建数据卷挂载点。 | | USER | 指定运行容器时的用户名或 UID 。 | | WORKDIR | 配置工作目录,为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录。 | | ARG | 指定镜像内使用的参数(如版本号信息等),可以在 docker build 的时候,使用 —build-args 改变。 | | OBBUILD | 配置当创建的镜像作为其他镜像的基础镜像是,所指定的创建操作指令。 | | STOPSIGNAL | 容器退出的信号值。 | | HEALTHCHECK | 健康检查。 | | SHELL | 指定使用 shell 时的默认 shell 类型。 |

2.4.1 自定义镜像

  • 要求:CentOS 7 镜像具备 vim 、ifconfig 和 JDK8 的功能。

编写 Dockerfile ,可以使用 VsCode 编辑器,装上 Docker 插件,这样可以校验 Dockerfile 的语法。

  • ① 编写 Dockerfile :
  1. vim Dockerfile
  1. # 基础镜像信息
  2. FROM centos:7.9.2009
  3. # 维护者信息
  4. LABEL xudaxian=123456789@qq.com
  5. ENV DEF_PATH=/usr/local
  6. WORKDIR $DEF_PATH
  7. # 安装 vim 编辑器
  8. RUN yum -y install vim
  9. # 安装 ifconfig 命令
  10. RUN yum -y install net-tools
  11. # 安装 JDK8 以及 lib 库
  12. RUN yum -y install glibc.i686
  13. RUN mkdir -pv $DEF_PATH/java
  14. # 下载 JDK 并解压
  15. RUN curl https://files-cdn.liferay.com/mirrors/download.oracle.com/otn-pub/java/jdk/8u121-b13/jdk-8u121-linux-x64.tar.gz | tar -xzC $DEF_PATH/java
  16. # 配置 Java 环境变量
  17. ENV JAVA_HOME=$DEF_PATH/java/jdk1.8.0_121
  18. ENV JRE_HOME=$JAVA_HOME/jre
  19. ENV CLASSPATH=$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
  20. ENV PATH=$JAVA_HOME/bin:$PATH
  21. # CMD:启动时执行指令
  22. CMD /bin/bash

3.gif

  • ② 构建:
  1. docker build --no-cache --force-rm -t 镜像名称:TAG .

说明:

  • --no-cache : 表示构建的时候不使用之前的缓存。
  • --force-rm:删除构建过程中的中间容器层。
  • .:表示构建环境的上下文,通常而言是 . ,表示以 Dockerfile 所在的目录作为构建的起点。

4.gif

2.4.2 FROM

  • FROM 指定基础镜像,推荐使用 alpineslim 之类的基础小镜像。
  • scratch 镜像是一个空镜像,常常用于多阶段构建。

  • 『问』:如何确定我们需要的基础镜像?

  • 『答』:
    • ① 如果是 Java 应用,可以选择 Java 基础镜像或 Tomcat 基础镜像。
    • ② 如果是 JS 模块化应用,可以选择 nodejs 基础镜像。
    • ③ 每种语言都有自己的服务器或基础环境镜像,如:Python、Golang、Java、PHP 等。

2.4.3 LABEL

  • LABEL 用来标注镜像的一些说明信息,常常用来指定维护者的信息。
  1. # 下面的这种格式是可以的
  2. LABEL multi.label1="value1" multi.label2="value2" other="value3"
  1. # 下面的这种格式也是可以的
  2. LABEL multi.label1="value1" \
  3. multi.label2="value2" \
  4. other="value3"

2.4.4 RUN

  • RUN 指令在当前镜像层顶部的新层执行任何命令,并提交结果,生成新的镜像层。
  • 生成的提交镜像将用于 Dockerfile 中的下一步,分层运行 RUN 指令并生成提交符合 Docker 的核心概念,就像 Git 管理源代码一样。
  • 注意:多个 RUN 指令并没有上下文的关系,换言之,多个 RUN 指令都是在同一个目录操作的。
  • RUN 有两种格式:
  1. # shell 形式,/bin/bash -c 的方式运行,可以破坏 shell 字符串
  2. RUN <command>
  1. # exec 的形式
  2. RUN ["executable", "param1", "param2"]
  • 在 RUN 中可以使用 \ 将一条 RUN 指令继续到下一行。
  1. RUN /bin/bash -c 'source $HOME/.bashrc; \
  2. echo $HOME'
  3. # 等同于
  4. RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
  • 示例:
  1. vim Dockerfile
  1. # 测试案例
  2. FROM alpine
  3. LABEL maintainer=xudaxian
  4. ENV msg='hello Docker'
  5. RUN echo $msg
  6. RUN ["echo","$msg"]
  7. RUN /bin/sh -c 'echo $msg'
  8. RUN ["/bin/sh","-c","echo $msg"]
  9. CMD sleep 1000
  1. docker build -t test --force-rm --no-cache .

9.gif

  • 总结:
    • 由于 [] 不是 shell 形式,所以不能输出变量信息,而是输出 $msg 。其他任何 /bin/sh -c 的形式都可以输出变量信息。
    • shell 是 RUN /bin/sh -c <command> 的方式,RUN ["/bin/sh","-c",command] 的 exec 方式等同于 shell 方式,而 RUN ["/bin/sh",command] 的 exec 默认不会进行变量替换。

2.4.5 构建期和运行期

  • 构建期是指使用 Dockerfile 构建镜像的整个过程时期,如:docker build 等。
  • 运行期是指使用之前构建的镜像启动容器的过程,如:docker start 、docker run 等。

2.4.6 ARG

  • 语法:
  1. ARG name=defaultValue
  • ARG 指令定义了一个变量,用户可以在构建的时候使用 --build-arg name=value 传递,docker build 命令会将其传递给构建器。
  • --build-arg 指定参数会覆盖 Dockerfile 中指定的同名参数。
  • 如果用户指定了 未在 Dockerfile 中定义的构建参数 ,则构建会输出 警告
  • ARG 只在构建时期有效,运行时期无效。
  • 不建议使用构建时变量来传递注入 github 密码、用户凭据等机密,因为构建时变量的值可以通过 docker history 来观察到。
  • ARG 变量定义从 Dockerfile 定义的行开始生效。
  • 使用 ENV 指定定义的环境变量始终会覆盖同名的 ARG 指令。
  • 示例:
  1. vim Dockerfile
  1. # 选择基础镜像
  2. FROM alpine
  3. # 维护者信息
  4. LABEL maintainer="许大仙"
  5. # ARG 指令定义了一个变量,用户可以在构建的时候使用 `--build-arg name=value` 传递,docker build 命令会将其传递给构建器。
  6. # `--build-arg` 指定参数会覆盖 Dockerfile 中指定的同名参数。
  7. # 如果用户指定了 `未在 Dockerfile 中定义的构建参数` ,则构建会输出 `警告` 。
  8. # ARG 只在构建时期有效,运行时期无效。
  9. # 不建议使用构建时变量来传递注入 github 密码、用户凭据等机密,因为构建时变量的值可以通过 docker history 来观察到。
  10. # ARG 变量定义从 Dockerfile 定义的行开始生效。
  11. ARG param="Hi Docker"
  12. # 在构建时期会运行的指令(根据 Dockerfile 创建一个镜像的整个过程时期)
  13. RUN echo 1111
  14. RUN echo ${param}
  15. # 在运行时候会运行的指令(根据之前创建的镜像启动一个容器,容器启动默认运行的命令)
  16. # docker start 或 docker run
  17. CMD ["/bin/sh","-c","echo 2222;echo $param"]
  1. docker build -t test01 --force-rm --no-cache .
  1. docker build -t test02 --force-rm --no-cache --build-arg param=test .

28.gif

2.4.7 ENV

  • 语法:
  1. ENV name=value
  • ENV 和 ARG 很类似,但是 ENV 在构建期和运行期都有效,并且使用 ENV 指定定义的环境变量始终会覆盖同名的 ARG 指令。
  • 可以使用 docker run -e name=value 修改 ENV 定义的环境变量。

  • 示例:

  1. vim Dockerfile
  1. # 选择基础镜像
  2. FROM alpine
  3. # 维护者信息
  4. LABEL maintainer="许大仙"
  5. # ARG 指令定义了一个变量,用户可以在构建的时候使用 `--build-arg name=value` 传递,docker build 命令会将其传递给构建器。
  6. # `--build-arg` 指定参数会覆盖 Dockerfile 中指定的同名参数。
  7. # 如果用户指定了 `未在 Dockerfile 中定义的构建参数` ,则构建会输出 `警告` 。
  8. # ARG 只在构建时期有效,运行时期无效。
  9. # 不建议使用构建时变量来传递注入 github 密码、用户凭据等机密,因为构建时变量的值可以通过 docker history 来观察到。
  10. # ARG 变量定义从 Dockerfile 定义的行开始生效。
  11. ARG param="Hi Docker"
  12. # ENV 在构建期和运行期都有效,但是只能在运行期进行修改,修改通过 docker run -e name=value 命令。
  13. ENV app=taobao
  14. # 在构建时期会运行的指令(根据 Dockerfile 创建一个镜像的整个过程时期)
  15. RUN echo 1111
  16. RUN echo ${param}
  17. RUN echo ${app}
  18. # 在运行时候会运行的指令(根据之前创建的镜像启动一个容器,容器启动默认运行的命令)
  19. # docker start 或 docker run
  20. CMD ["/bin/sh","-c","echo 2222;echo $param;echo app_$app"]
  1. docker build -t test --force-rm --no-cache .
  1. docker run -it test

29.gif

  • :ENV 在构建期就会被解析并持久化,可以通过 docker inspect image 查看。

  • 示例:

  1. vim Dockerfile
  1. # ENV 的坑
  2. FROM alpine
  3. LABEL maintainer="许大仙"
  4. ENV msg="hello"
  5. ENV msg2=${msg}
  6. RUN echo ${msg}
  7. RUN echo ${msg2}
  8. # 如果运行期修改了 msg=666,那么 msg 和 msg2 的值是 666 和 hello ,因为 ENV 在构建期就会被解析并持久化。
  9. CMD ["/bin/sh","-c","echo $msg;echo $msg2;"]
  1. docker build -t test --force-rm --no-cache .
  1. docker run -it -e msg=666 test

30.gif

2.4.8 ADD

  • ADD 可以将上下文指定的内容添加(复制)到镜像中,如果是压缩包,ADD 会自动解压;如果是远程 URL ,ADD 会自动下载;但是,ADD 并没有自动下载远程压缩文件并解压的功能
  • 语法:
  1. ADD src dest
  • 注意

    • src 路径必须在构建的上下文,不能使用 ../../xxx 这种方式,因为 Docker 构建的第一步是将上下文目录(包括子目录)发送给 Docker 的守护进程。
    • 如果 src 是 URL ,并且 dest 不以 / 结尾,那么就会从 URL 下载文件并将其复制为 dest(名称)。
    • 如果 src 是 URL ,并且 dest 以 / 结尾,会自动推断出文件的名称(URL 的最后一部分)并保存到 dest(目录)中。
    • 如果 src 是目录,则将复制目录的整个内容,包括文件系统元数据。
  • 示例:

  1. vim Dockerfile
  1. FROM alpine
  2. LABEL maintainer="许大仙"
  3. # 如果是远程文件,自动下载
  4. # 如果是压缩文件,自动解压
  5. # 注意:ADD 并没有自动下载远程压缩文件并解压的功能
  6. # 将当前内容复制到 alpine 中
  7. ADD https://download.redis.io/releases/redis-6.2.6.tar.gz /dest
  8. # 注意,RUN 指令上下并没有上下文的关系。
  9. RUN ls -l
  1. docker build -t test --force-rm --no-cache .

31.gif

  • 示例:
  1. vim Dockerfile
  1. FROM alpine
  2. LABEL maintainer="许大仙"
  3. # 如果是远程文件,自动下载
  4. # 如果是压缩文件,自动解压
  5. # 注意:ADD 并没有自动下载远程压缩文件并解压的功能
  6. ADD https://download.redis.io/releases/redis-6.2.6.tar.gz /dest/
  7. # 注意,RUN 指令上下并没有上下文的关系。
  8. RUN ls -l
  9. RUN cd /dest && ls -l
  1. docker build -t test --force-rm --no-cache .

32.gif

  • 示例:
  1. wget https://download.redis.io/releases/redis-6.2.6.tar.gz
  1. vim Dockerfile
  1. FROM alpine
  2. LABEL maintainer="许大仙"
  3. # 如果是远程文件,自动下载
  4. # 如果是压缩文件,自动解压
  5. # 注意:ADD 并没有自动下载远程压缩文件并解压的功能
  6. ADD redis-6.2.6.tar.gz /dest/
  7. # 注意,RUN 指令上下并没有上下文的关系。
  8. RUN ls -l
  9. RUN cd /dest && ls -l
  1. docker build -t test --force-rm --no-cache .

33.gif

2.4.9 COPY

  • 语法:
  1. COPY [--chown=<user>:<group>] <src>... <dest>
  1. COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
  • COPY 和 ADD 类似,都有将上下文指定的内容添加(复制)到镜像中的功能,只不过 ADD 的自动下载或解压压缩文件的功能。
  • --chown 功能仅在用于构建 Linux 容器的 Dockerfile 上受支持,而在 Windows 容器上不起作用。

  • 示例:略。

2.4.10 USER

  • 语法:
  1. USER <user>[:<group>]
  1. USER <UID>[:<GID>]
  • USER 指令和 WORKDIR 指令类似,都是改变环境状态并影响以后的层,WORKDIR 是改变工作目录,USER 则是改变之后层的执行 RUN 、CMD 、以及 ENTRYPOINT 这类命令的身份。
  • 注意:USER 只是帮助我们切换到指定的用户而已,这个用户必须事先建立好的,否则无法切换。

  • 示例:USER 身份问题

  1. touch a.txt
  1. vim Dockerfile
  1. FROM alpine
  2. LABEL maintainer="许大仙"
  3. # 创建用户和组
  4. RUN addgroup -S test && adduser -S test -G test -h /home/test
  5. # USER 只是帮准我们切换到指定的用户而已,这个用户必须事先建立好的,否则无法切换。USER 则是改变之后层的执行 RUN、CMD、以及 ENTRYPOINT 这类命令的身份。
  6. USER test:test
  7. COPY *.txt /test/
  8. # 注意:一旦声明了 USER 之后,USER 后面的 RUN、CMD、ENTRYPOINT 的身份就是 test ,而 a.txt 是主机生成的,身份是 root ,必然会报错,权限不对。
  9. RUN cd /test && ls -l && echo 1111 > a.txt
  1. docker build -t test --force-rm --no-cache .

34.gif

  • 示例:使用 USER + COPY 解决上面示例的权限等问题
  1. touch a.txt
  1. vim Dockerfile
  1. FROM alpine
  2. LABEL maintainer="许大仙"
  3. # 创建用户和组
  4. RUN addgroup -S test && adduser -S test -G test -h /home/test
  5. # USER 只是帮准我们切换到指定的用户而已,这个用户必须事先建立好的,否则无法切换。USER 则是改变之后层的执行 RUN、CMD、以及 ENTRYPOINT 这类命令的身份。
  6. USER test:test
  7. # 通过 COPY 指定的 chown 功能改变复制文件的权限
  8. COPY --chown=test:test *.txt /test/
  9. # 注意:一旦声明了 USER 之后,USER 后面的 RUN、CMD、ENTRYPOINT 的身份就是 test ,而 a.txt 是主机生成的,身份是 root ,但是,因为使用了 COPY --chown=test:test ,所以文件的权限是 test
  10. RUN cd /test && ls -l && echo 1111 > a.txt
  1. docker build -t test --force-rm --no-cache .

35.gif

2.4.11 WORKDIR

  • 语法:
  1. WORKDIR /a/b/c
  • WORKDIR 指令为 Dockerfile 中跟随它后面的 RUN 、CMD 、ENTRYPOINT、 COPY、ADD 指令设置工作目录。
  • WORKDIR 指令可在 Dockerfile 中多次使用。 如果提供了相对路径,则它将相对于上一个 WORKDIR 指令的路径,如:
  1. WORKDIR /a
  2. WORKDIR b
  3. WORKDIR c
  4. RUN pwd
  5. # 效果:/a/b/c
  • WORKDIR 指令也可以用在环境变量上,如:
  1. ENV DIRPATH=/path
  2. WORKDIR $DIRPATH/$DIRNAME
  3. RUN pwd
  4. # 效果:/path/$DIRNAME
  • 示例:
  1. touch a.txt
  1. vim Dockerfile
  1. FROM alpine
  2. RUN pwd && ls -l
  3. WORKDIR /app
  4. RUN pwd && ls -l
  5. COPY *.txt .
  6. RUN ls -l
  1. docker build -t test --force-rm --no-cache .

36.gif

2.4.12 VOLUME

  • 语法:
  1. #可以是JSON数组
  2. VOLUME ["/var/log/"]
  1. #可以直接写
  2. VOLUME /var/log
  1. #可以空格分割多个
  2. VOLUME /var/log /var/db
  • 注意:用 VOLUME 声明了卷,那么以后对于卷内容的修改会被丢弃,所以,一定要在 volume 声明之前修改内容 。

  • 示例:

  1. vim Dockerfile
  1. FROM alpine
  2. # 挂载 容器指定的文件夹,如果不存在,会自动创建。
  3. # 指定了 VOLUME 指令后,即使启动容器的时候没有指定 -v 参数,也会自动进行匿名卷挂载。
  4. VOLUME [ "/demo","/app" ]
  5. CMD ping www.baidu.com
  1. docker build -t test --force-rm --no-cache .

37.gif

  • 示例:用 VOLUME 声明了卷,那么以后对于卷内容的修改会被丢弃,所以,一定要在 volume 声明之前修改内容 。
  1. vim Dockerfile
  1. FROM alpine
  2. RUN mkdir /demo && mkdir /app
  3. RUN echo 111 > /demo/a.txt
  4. RUN echo 222 > /app/b.txt
  5. # 挂载 容器指定的文件夹,如果不存在,会自动创建。
  6. # 指定了 VOLUME 指令后,即使启动容器的时候没有指定 -v 参数,也会自动进行匿名卷挂载。容器内的 /demo 和 /app ,需要在启动容器的时候,需要使用 -v 参数进行挂载。
  7. # VOLUME 挂载出去的东西,容器改变也不会最终在 docker commit 的时候生效。
  8. # -v 和 VOLUME 挂载出去的目录,主机变,容器里面也会发生变化,但是
  9. # ① docker commit 提交当前容器的所有变化为镜像,就会丢弃。
  10. # ② VOLUME [ "/demo","/app" ] 容器会自动挂载,在之后对这些目录所操作的变化,也会丢弃
  11. # ③ 挂载仅仅是为了将外边的数据同步到容器里面
  12. # VOLUME 的最佳实践是写在 CMD 或 ENTRYPOINT 前面
  13. VOLUME [ "/demo","/app" ]
  14. # 下面的 2 个 RUN 指令没有生效,因为 VOLUME 指定的挂载目录是固化配置,当执行到 VOLUME 的时候就已经写入到容器中了,即使后面容器怎么变,也不会改变。
  15. RUN echo 333 > /demo/a.txt
  16. RUN echo 444 > /app/b.txt
  17. CMD ping www.baidu.com
  1. docker build -t test --force-rm --no-cache .

38.gif

2.4.13 EXPOSE

  • 语法:
  1. EXPOSE <port> [<port>/<protocol>...]
  1. EXPOSE [80,443]
  1. EXPOSE 80/tcp
  1. EXPOSE 80/udp
  • EXPOSE 指令通知 Docker 容器在运行的时候在指定的网络端口上进行侦听,可以指定端口是侦听 TCP 还是 UDP ,如果没有指定,默认就是 TCP 。
  • EXPOSE 指令实际上不会发布端口,它充当了构建镜像人员和运行容器人员之间的一种文档,即打算发布那些端口的信息,要在运行容器时映射端口,需要使用 docker run -p xxx:xxxdocker run -P 的命令。

  • 示例:略。

2.4.14 CMD 和 ENTRYPOINT

  • CMD 的语法:
  1. # exec 方式, 首选方式
  2. CMD ["executable","param1","param2"]
  1. # 为 ENTRYPOINT 提供默认参数
  2. CMD ["param1","param2"]
  1. # shell 形式
  2. CMD command param1 param2
  • ENTRYPOINT 的语法:
  1. # exec 方式, 首选方式
  2. ENTRYPOINT ["executable", "param1", "param2"]
  1. # shell 形式
  2. ENTRYPOINT command param1 param2

注意

  • ① 如果 Dockerfile 文件中,使用多个 CMD 或 ENTRYPOINT 作为唯一的入口,即写多个 CMD 或 ENTRYPOINT ,则会产生覆盖现象,只有最后一个生效。
  • ② shell 方式是可以读取环境变量的值的(如:${xxx}),默认情况下,exec 的方式是读取不了环境变量值的,但是 exec 方式的 [“/bin/sh”,”-c”,”xxx”] 等同于 shell 方式,也是可以读取环境变量值。
  • ③ 官方推荐使用 RUN 、CMD 以及 ENTRYPOINT 使用 exec 的方式。
  • ④ 如果既有 CMD 的 exec 方式,又有 ENTRYPOINT 的 exec 方式,那么 CMD 是作为 ENTRYPOINT 的参数的(最佳实践)。
  • ⑤ 使用 docker run -d xxx CMD 命令是可以覆盖 Dockerfile 中的 CMD 指令的,不是覆盖 exec 方式数组中的一个,而是全部。
  • 示例:证明注意 ①
  1. vim Dockerfile
  1. FROM alpine
  2. # CMD 和 ENTRYPOINT 作为唯一入口,写多个,只有最后一个生效
  3. CMD ping baidu.com
  4. CMD ping bing.com
  1. docker build -t test --force-rm --no-cache .
  1. # --rm 表示容器退出,自动删除
  2. docker run -it --rm test

39.gif

  • 示例:证明注意 ①
  1. vim Dockerfile
  1. FROM alpine
  2. # CMD 和 ENTRYPOINT 作为唯一入口,写多个,只有最后一个生效
  3. ENTRYPOINT ping baidu.com
  4. ENTRYPOINT ping bing.com
  1. docker build -t test --force-rm --no-cache .
  1. # --rm 表示容器退出,自动删除
  2. docker run -it --rm test

40.gif

  • 示例:证明注意 ④
  1. vim Dockerfile
  1. FROM alpine
  2. # java -jar xxx.jar --spring.profile.active=dev --server.port=8888
  3. # CMD [ "-jar","xxx.jar","--spring.profile.active=dev","--server.port=8888"]
  4. # ENTRYPOINT [ "java" ]
  5. CMD ["baidu.com"]
  6. ENTRYPOINT ["ping"]
  1. docker build -t test --force-rm --no-cache .
  1. # --rm 表示容器退出,自动删除
  2. docker run -it --rm test

41.gif

  • 示例:证明注意 ⑤
  1. vim Dockerfile
  1. FROM alpine
  2. # java -jar xxx.jar --spring.profile.active=dev --server.port=8888
  3. # CMD [ "-jar","xxx.jar","--spring.profile.active=dev","--server.port=8888"]
  4. # ENTRYPOINT [ "java" ]
  5. CMD ["baidu.com"]
  6. ENTRYPOINT ["ping"]
  1. docker build -t test --force-rm --no-cache .
  1. # --rm 表示容器退出,自动删除
  2. docker run -it --rm test bing.com

42.gif

2.5 虚悬镜像

2.5.1 是什么?

  • 虚悬镜像就是仓库名和标签名都是 <none> 的镜像,俗称 dangling image
  • 使用 Dockerfile 写一个虚悬镜像:
  1. vim Dockerfile
  1. FROM ubuntu
  2. CMD echo 'action is success'

5.gif

  • 根据 Dockerfile 构建镜像:
  1. docker build .

6.gif

2.5.2 查看

  • 命令:
  1. docker images -f dangling=true
  • 示例:
  1. docker images -f dangling=true

7.png

2.5.3 删除

  • 命令:
  1. docker image prune
  • 示例:
  1. docker image prune

8.gif

2.6 多阶段构建

2.6.1 使用

  • 官网
  • 多阶段构建出现的目的就是为了解决如何让一个镜像变得更小。

  • 示例:常规打包

  1. ### 我们如何打包一个 Java 镜像
  2. FROM maven
  3. WORKDIR /app
  4. COPY . .
  5. RUN mvn clean package
  6. COPY /app/target/*.jar /app/app.jar
  7. ENTRYPOINT java -jar app.jar
  8. ## 这样的镜像有多大?
  9. ## 我们最小做到多大??

2.6.2 生产示例

  • 需求:将 SpringBoot 项目使用多阶段构建打包成 Docker 镜像,并进行启动。

  • ① 环境要求:

    • JDK :8。
    • Maven :3.5+。
    • IDEA:2.22+。
    • SpringBoot :2.5.10。
  • ② 新建 SpringBoot 工程:

43.png

  • ② pom.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.5.10</version>
  9. </parent>
  10. <packaging>jar</packaging>
  11. <groupId>com.github.demo</groupId>
  12. <artifactId>demo</artifactId>
  13. <version>0.0.1</version>
  14. <name>demo</name>
  15. <description>demo</description>
  16. <properties>
  17. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  18. <java.version>1.8</java.version>
  19. </properties>
  20. <dependencies>
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-web</artifactId>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter-test</artifactId>
  28. <scope>test</scope>
  29. </dependency>
  30. </dependencies>
  31. <build>
  32. <finalName>${project.artifactId}</finalName>
  33. <plugins>
  34. <plugin>
  35. <groupId>org.springframework.boot</groupId>
  36. <artifactId>spring-boot-maven-plugin</artifactId>
  37. <version>2.5.10</version>
  38. <configuration>
  39. <layout>ZIP</layout>
  40. </configuration>
  41. <executions>
  42. <execution>
  43. <goals>
  44. <goal>repackage</goal>
  45. </goals>
  46. </execution>
  47. </executions>
  48. </plugin>
  49. <plugin>
  50. <groupId>org.apache.maven.plugins</groupId>
  51. <artifactId>maven-compiler-plugin</artifactId>
  52. <version>3.10.0</version>
  53. <configuration>
  54. <source>1.8</source>
  55. <target>1.8</target>
  56. </configuration>
  57. </plugin>
  58. </plugins>
  59. </build>
  60. <!-- 为了加速下载需要在 pom 文件中复制如下 -->
  61. <repositories>
  62. <repository>
  63. <id>aliyun</id>
  64. <name>Nexus Snapshot Repository</name>
  65. <url>https://maven.aliyun.com/repository/public</url>
  66. <layout>default</layout>
  67. <releases>
  68. <enabled>true</enabled>
  69. </releases>
  70. <!-- snapshots默认是关闭的,需要开启 -->
  71. <snapshots>
  72. <enabled>true</enabled>
  73. </snapshots>
  74. </repository>
  75. </repositories>
  76. <pluginRepositories>
  77. <pluginRepository>
  78. <id>aliyun</id>
  79. <name>Nexus Snapshot Repository</name>
  80. <url>https://maven.aliyun.com/repository/public</url>
  81. <layout>default</layout>
  82. <releases>
  83. <enabled>true</enabled>
  84. </releases>
  85. <snapshots>
  86. <enabled>true</enabled>
  87. </snapshots>
  88. </pluginRepository>
  89. </pluginRepositories>
  90. </project>
  • ③ 启动类:
  1. package com.github.demo;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. /**
  5. * @author 许大仙
  6. * @version 1.0
  7. * @since 2022-03-01 11:24:50
  8. */
  9. @SpringBootApplication
  10. public class Application {
  11. public static void main(String[] args) {
  12. SpringApplication.run(Application.class, args);
  13. }
  14. }
  • ④ Dockerfile :
  1. # 以下所有前提 保证 Dockerfile 和项目在同一个文件夹
  2. # 第一阶段:环境构建
  3. FROM maven:3.8.4-openjdk-8-slim AS builder
  4. WORKDIR /app
  5. # 此时有坑,想想 Maven 的标准目录结构
  6. COPY src ./src/
  7. COPY pom.xml .
  8. RUN mvn clean package -Dmaven.test.skip=true
  9. # 第二阶段,最小运行时环境,只需要 jre;第二阶段并不会有第一阶段哪些没用的层
  10. # jdk springboot-actutor(jdk)
  11. FROM openjdk:8u282-slim
  12. LABEL maintainer="xxxx@qq.com"
  13. # 从上一个阶段复制内容
  14. COPY --from=builder /app/target/*.jar /app.jar
  15. # 修改时区
  16. RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone && touch /app.jar
  17. # 环境变量
  18. # docker run -e JAVA_OPTS="-Xmx512m -Xms64m" -e PARAMS="--spring.profiles.active=dev --server.port=8080" xxx
  19. ENV JAVA_OPTS=""
  20. ENV PARAMS=""
  21. # 运行 jar 包
  22. ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom $JAVA_OPTS -jar /app.jar $PARAMS" ]
  • ⑤ 构建镜像命令:
  1. docker build -t test --force-rm --no-cache .
  • ⑥ 启动容器命令:
  1. docker run -d -P -e JAVA_OPTS="-Xmx512m -Xms64m" -e PARAMS="--spring.profiles.active=dev --server.port=8080" test

2.7 镜像瘦身

  • ① 选择最小的基础镜像。
  • ② 合并 RUN 环节的所有指令,少生成一些镜像层。
  • ③ RUN 期间可能安装其它程序会生成临时缓存,要自行删除,如:
  1. # 开发期间,逐层验证正确的
  2. RUN xxx
  3. RUN xxx
  4. RUN aaa \
  5. aaa \
  6. vvv \
  1. # 生产环境
  2. RUN apt-get update && apt-get install -y \
  3. bzr \
  4. cvs \
  5. git \
  6. mercurial \
  7. subversion \
  8. && rm -rf /var/lib/apt/lists/*
  • ④ 使用 .dockerignore 文件,排除上下文中无需参与构建的资源。
  • ⑤ 合理使用多阶段构建。
  • ⑥ 合理使用构建缓存加速构建,但是有时也会有坑,开发的时候建议还是 docker build -t xxx --no-cache --force-rm . 来构建镜像。

第三章:Docker网络

3.1 是什么?

  • Docker 默认启动的时候,会为我们创建三个网络:
  1. docker network ls

44.png

3.2 常用基本命令

  • Docker 网络的帮助命令:
  1. docker network --help

45.png

  • 查看网络:
  1. docker network ls

46.png

  • 查看网络源数据:
  1. docker network inspect xxx

47.gif

  • 创建网络:
  1. docker network create xxx

48.gif

  • 删除网络:
  1. docker network rm xxx

49.gif

3.3 能干嘛?

  • ① 容器间的互联、通信以及端口映射。
  • ② 可以通过服务名直接通信,而不受容器 IP 变化的影响。

3.4 网络模式

3.4.1 概述

  • bridge 模式(默认):
  1. docker run --network bridge xxx
  • host 模式:
  1. docker run --network host xxx
  • none 模式:
  1. docker run --network none xxx
  • container 模式:
  1. docker run --network 容器名称|容器ID xxx

3.4.2 容器实例内默认 IP 生产的规则

  • Docker 容器内部的 IP 是有可能变化的。

  • 证明:

50.gif

3.4.3 bridge 模式

51.bmp

  • Docker 使用 Linux 桥接,在宿主机虚拟一个 Docker 容器网桥( docker0 ),Docker 启动一个容器时会根据 Docker 网桥的网段分配给容器一个 IP 地址,称为 Container-IP ,同时 Docker 网桥是每个容器的默认网关。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的 Container-IP 直接通信。
  • docker run 的时候,没有指定 network 的话默认使用的网桥模式就是 bridge ,使用的就是 docker0 。在宿主机使用 ifconfig 命令就可以看到 docker0 和自己 create 的 network 的 eth0,eth1,eth2……代表网卡一,网卡二,网卡三……,lo 代表127.0.0.1,即 localhost ,inet addr 用来表示网卡的 IP 地址。
  • 网桥 docker0 创建一对对等虚拟设备接口一个叫 veth,另一个叫 eth0 ,成对匹配。
    • 整个宿主机的网桥模式都是 docker0,类似一个交换机有一堆接口,每个接口叫 veth,在本地主机和容器内分别创建一个虚拟接口,并让他们彼此联通(这样一对接口叫 veth pair)。
    • 每个容器实例内部也有一块网卡,每个接口叫 eth0 。
    • docker0 上面的每个 veth 匹配某个容器实例内部的 eth0 ,两两配对,一一匹配。
  • 综上所述,将宿主机上的所有容器都连接到这个内部网络上,两个容器在同一个网络下,会从这个网关下各自拿到分配的 ip ,此时两个容器的网络是互通的。

  • 证明:

  1. docker run -d -p 8081:8080 --name tomcat81 billygoo/tomcat8-jdk8
  1. docker run -d -p 8082:8080 --name tomcat82 billygoo/tomcat8-jdk8

52.gif

3.4.4 host 模式

53.bmp

  • 容器不会获得一个独立的 Network Namespace,而是和宿主机共用一个 NetWork Namespace ,容器将不会虚拟出自己的网卡,而是使用宿主机的 IP 和端口。

  • 证明:

  1. docker run -d --network host --name tomcat83 billygoo/tomcat8-jdk8
  1. curl -XGET http://127.0.0.1:8080

54.gif

3.4.5 none 模式

  • 禁用网络功能,只有 lo 标识(就是 127.0.0.1 表示本地回环)。

  • 证明:

  1. docker run -d -p 8084:8080 --network none --name tomcat84 billygoo/tomcat8-jdk8

55.gif

3.4.6 container 模式

56.bmp

  • 新建的容器和已经存在的一个容器共享一个网络 ip 配置而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。

  • 证明:最佳实践,redis 中没有 ifconfig 等命令,但是 alpine 中有,此时就可以用 alpine 来测试 redis

  1. docker run -d -P --name redis redis
  1. docker run -it --network container:redis --name alpine alpine /bin/sh

57.gif

3.4.7 自定义网络

  • 自定义网络默认使用的是桥接网络 bridge 。
  • 自定义网络本身就维护好了主机名和 ip 的对应关系(ip 和域名都能通),常用。

  • 示例:

  1. docker network create demo
  1. docker run -it --network demo --name alpine1 alpine /bin/sh
  1. docker run -it --network demo --name alpine2 alpine /bin/sh

58.gif

第四章:Docker-compose 容器编排

4.1 是什么?

  • Docker Compose 是 Docker 公司推出的一个工具软件,可以管理多个 Docker 容器组成一个应用。你需要定义一个 YAML 格式的配置文件 docker-compose.yml,写好多个容器之间的调用关系。之后,只要一个命令,就能同时启动/关闭这些容器。

4.2 能干嘛?

  • Docker 建议我们每一个容器中只运行一个服务,因为 Docker 容器本身占用资源极少,所以最好是将每个服务单独的分割开来但是这样我们又面临了一个问题?
  • 如果我需要同时部署好多个服务,难道要每个服务单独写 Dockerfile ,然后再去构建镜像、构建容器,太累了,所以 Docker 官方给我们提供了 docker-compose 多服务部署的工具。
  • 例如要实现一个 Web 微服务项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库 mysql 服务容器,redis 服务器,注册中心 eureka ,甚至还包括负载均衡容器等等。
  • Docker Compose 允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。
  • Docker Compose 可以很容易地用一个配置文件定义一个多容器的应用,然后使用一条指令安装这个应用的所有依赖,完成构建。Docker-Compose 解决了容器与容器之间如何管理编排的问题。

4.2 安装和卸载

  • 安装:
  1. # Compose目前已经完全支持Linux、Mac OS和Windows,在我们安装Compose之前,需要先安装Docker。下面我 们以编译好的二进制包方式安装在Linux系统中。
  2. curl -L https://github.com/docker/compose/releases/download/1.29.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
  1. # 国内的地址
  2. curl -L https://get.daocloud.io/docker/compose/releases/download/1.29.2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
  1. # 设置文件可执行权限
  2. chmod 777 /usr/local/bin/docker-compose
  1. # 创建软链接
  2. sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
  1. # 查看版本信息
  2. docker-compose -version

59.gif

  • 卸载:
  1. # 二进制包方式安装的,删除二进制文件即可
  2. rm /usr/local/bin/docker-compose

4.3 Docker Compose 的核心概念

  • 一个文件:docker-compose.yml。
  • 两要素:
    • 服务(service):一个个应用容器实例,比如订单微服务、库存微服务、mysql 容器、nginx 容器或者 redis 容器。
    • 工程(project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。

4.4 Docker Compose 的使用步骤

  • ① 编写 Dockerfile 定义各个微服务应用并构建出对应的镜像文件。
  • ② 使用 docker-compose.yml 定义一个完整业务单元,安排好整体应用中的各个容器服务。
  • ③ 最后,执行 docker-compose up 命令来启动并运行整个应用程序,完成一键部署上线。

4.5 DOcker Compose 常用命令

  • 查看帮助:
  1. docker-compose -h
  • 启动所有 docker-compose 服务:
  1. docker-compose up
  • 启动所有 docker-compose 服务并后台运行:
  1. docker-compose up -d
  • 停止并删除容器、网络、卷、镜像:
  1. docker-compose down
  • 进入容器实例内部:
  1. # 服务id 在 docker-compose.yml 中定义的
  2. docker exec -it 服务id /bin/bash
  • 展示当前 docker-compose 编排过的运行的所有容器:
  1. docker-compose ps
  • 展示当前 docker-compose 编排过的容器进程:
  1. docker-compose top
  • 查看容器输出日志:
  1. docker-compose logs 服务id
  • 检查配置:
  1. docker-compose config
  • 检查配置,有问题才有输出:
  1. docker-compose config -q
  • 重启服务:
  1. docker-compose restart
  • 启动服务:
  1. docker-compose start
  • 停止服务:
  1. docker-compose stop

常用的命令组合:

  • docker-compose build --force-rm --no-cache && docker-compose up -d

3.5 Docker Compose 生产示例

  • 需求:使用 Docker Compose 部署项目。

60.png

  • ① 环境要求:
    • JDK :1.8。
    • MySQL :5.7。
    • IDEA :2022+。
    • Maven :3.5+。
    • SpringBoot:2.5.10。
    • Redis:5.0.14。
  • ② 新建 SpringBoot 工程:

61.png

  • ③ pom.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.5.10</version>
  9. </parent>
  10. <packaging>jar</packaging>
  11. <groupId>com.github.demo</groupId>
  12. <artifactId>demo</artifactId>
  13. <version>0.0.1</version>
  14. <name>demo</name>
  15. <description>demo</description>
  16. <properties>
  17. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  18. <java.version>1.8</java.version>
  19. </properties>
  20. <dependencies>
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-web</artifactId>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter-test</artifactId>
  28. <scope>test</scope>
  29. </dependency>
  30. <dependency>
  31. <groupId>org.springframework.boot</groupId>
  32. <artifactId>spring-boot-starter-data-redis</artifactId>
  33. </dependency>
  34. <dependency>
  35. <groupId>com.alibaba</groupId>
  36. <artifactId>druid-spring-boot-starter</artifactId>
  37. <version>1.2.8</version>
  38. </dependency>
  39. <dependency>
  40. <groupId>org.projectlombok</groupId>
  41. <artifactId>lombok</artifactId>
  42. <scope>provided</scope>
  43. </dependency>
  44. <dependency>
  45. <groupId>com.baomidou</groupId>
  46. <artifactId>mybatis-plus-boot-starter</artifactId>
  47. <version>3.5.1</version>
  48. </dependency>
  49. <dependency>
  50. <groupId>mysql</groupId>
  51. <artifactId>mysql-connector-java</artifactId>
  52. </dependency>
  53. <dependency>
  54. <groupId>io.gitee.zero-wsh</groupId>
  55. <artifactId>acTable</artifactId>
  56. <version>2.0.5</version>
  57. </dependency>
  58. <dependency>
  59. <groupId>org.apache.commons</groupId>
  60. <artifactId>commons-pool2</artifactId>
  61. <version>2.10.0</version>
  62. </dependency>
  63. </dependencies>
  64. <!-- 创建多环境 -->
  65. <profiles>
  66. <!-- 定义具体的环境:开发环境 -->
  67. <profile>
  68. <!-- 定义环境对应的唯一id -->
  69. <id>dev</id>
  70. <!-- 定义环境中专用的属性值 -->
  71. <properties>
  72. <spring.profiles.active>dev</spring.profiles.active>
  73. <port>8080</port>
  74. <mysql.host>192.168.0.16</mysql.host>
  75. <mysql.port>3306</mysql.port>
  76. <mysql.username>root</mysql.username>
  77. <mysql.password>123456</mysql.password>
  78. <mysql.database>demo</mysql.database>
  79. <redis.host>127.0.0.1</redis.host>
  80. <redis.port>6379</redis.port>
  81. <redis.password></redis.password>
  82. <redis.database>0</redis.database>
  83. </properties>
  84. </profile>
  85. <!-- 定义具体的环境:测试环境 -->
  86. <profile>
  87. <id>test</id>
  88. <properties>
  89. <spring.profiles.active>test</spring.profiles.active>
  90. <port>8080</port>
  91. <mysql.host>192.168.65.100</mysql.host>
  92. <mysql.port>3306</mysql.port>
  93. <mysql.username>root</mysql.username>
  94. <mysql.password>123456</mysql.password>
  95. <mysql.database>demo</mysql.database>
  96. <redis.host>192.168.65.100</redis.host>
  97. <redis.port>6379</redis.port>
  98. <redis.password></redis.password>
  99. <redis.database>0</redis.database>
  100. </properties>
  101. </profile>
  102. <profile>
  103. <id>prod</id>
  104. <properties>
  105. <spring.profiles.active>prod</spring.profiles.active>
  106. <port>8080</port>
  107. <!-- 服务名或容器名 -->
  108. <mysql.host>mysql</mysql.host>
  109. <!-- 此处有坑,如果在 Docker 的自定义网络中,必须通过内部的端口,即 3306 ,而不是外部暴露的端口 -->
  110. <mysql.port>3306</mysql.port>
  111. <mysql.username>root</mysql.username>
  112. <mysql.password>123456</mysql.password>
  113. <mysql.database>demo</mysql.database>
  114. <!-- 服务名或容器名 -->
  115. <redis.host>redis</redis.host>
  116. <!-- 此处有坑,如果在 Docker 的自定义网络中,必须通过内部的端口,即 6379 ,而不是外部暴露的端口 -->
  117. <redis.port>6379</redis.port>
  118. <redis.password></redis.password>
  119. <redis.database>0</redis.database>
  120. </properties>
  121. <!-- 设置默认启动 -->
  122. <activation>
  123. <activeByDefault>true</activeByDefault>
  124. </activation>
  125. </profile>
  126. </profiles>
  127. <build>
  128. <finalName>${project.artifactId}</finalName>
  129. <plugins>
  130. <plugin>
  131. <groupId>org.springframework.boot</groupId>
  132. <artifactId>spring-boot-maven-plugin</artifactId>
  133. <version>2.5.10</version>
  134. <configuration>
  135. <layout>ZIP</layout>
  136. </configuration>
  137. <executions>
  138. <execution>
  139. <goals>
  140. <goal>repackage</goal>
  141. </goals>
  142. </execution>
  143. </executions>
  144. </plugin>
  145. <plugin>
  146. <groupId>org.apache.maven.plugins</groupId>
  147. <artifactId>maven-compiler-plugin</artifactId>
  148. <version>3.10.0</version>
  149. <configuration>
  150. <source>1.8</source>
  151. <target>1.8</target>
  152. </configuration>
  153. </plugin>
  154. </plugins>
  155. </build>
  156. <!-- 为了加速下载需要在 pom 文件中复制如下 -->
  157. <repositories>
  158. <repository>
  159. <id>aliyun</id>
  160. <name>Nexus Snapshot Repository</name>
  161. <url>https://maven.aliyun.com/repository/public</url>
  162. <layout>default</layout>
  163. <releases>
  164. <enabled>true</enabled>
  165. </releases>
  166. <!-- snapshots默认是关闭的,需要开启 -->
  167. <snapshots>
  168. <enabled>true</enabled>
  169. </snapshots>
  170. </repository>
  171. </repositories>
  172. <pluginRepositories>
  173. <pluginRepository>
  174. <id>aliyun</id>
  175. <name>Nexus Snapshot Repository</name>
  176. <url>https://maven.aliyun.com/repository/public</url>
  177. <layout>default</layout>
  178. <releases>
  179. <enabled>true</enabled>
  180. </releases>
  181. <snapshots>
  182. <enabled>true</enabled>
  183. </snapshots>
  184. </pluginRepository>
  185. </pluginRepositories>
  186. </project>
  • ④ application.yml
server: # 服务器配置
  port: @port@ # 端口
  servlet:
    context-path: /api
  tomcat:
    uri-encoding: utf-8
    threads:
      max: 800
      min-spare: 50
  max-http-header-size: 10KB

spring: # Spring 配置
  profiles:
    active: @spring.profiles.active@
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://@mysql.host@:@mysql.port@/@mysql.database@?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
    username: @mysql.username@
    password: @mysql.password@
    type: com.alibaba.druid.pool.DruidDataSource # 配置数据库连接池的类型
    druid:
      initial-size: 5 # 初始化连接池大小
      min-idle: 10 # 最小连接
      max-active: 20 # 最大连接
      max-wait: 6000 # 配置获取连接等待超时的时间
      time-between-eviction-runs-millis: 2000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      min-evictable-idle-time-millis: 600000 # 配置一个连接在池中最小生存的时间,单位是毫秒
      max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位是毫秒
      validation-query: SELECT 1 # 数据库的最小检测
      test-while-idle: true # 检测连接是否可用
      test-on-borrow: false # 在获取连接之前是否进行测试
      test-on-return: false # 归还连接之前是否进行测试
      pool-prepared-statements: false # 不缓存 pstmt
      max-pool-prepared-statement-per-connection-size: 20 # 配置 pstmt 缓存个数
      stat-view-servlet: # 监控界面配置
        enabled: true # 启动监控界面
        allow: 127.0.0.1  # 白名单
        deny: # 黑名单
        login-username: admin # 用户名
        login-password: 123456 # 密码
        url-pattern: /druid/* # 访问路径
      web-stat-filter: # WEB 访问监控
        enabled: true # 开启 WEB 访问监控
        url-pattern: /* # 对所有的路径进行监控
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" # 排除一些不必要的url
        session-stat-enable: true # 开启 session 统计功能,默认为 true
        session-stat-max-count: 1000 # sessionStatMaxCount 配置,默认为 1000
        profile-enable: true # 配置profileEnable能够监控单个url调用的sql列表
      filter: # 配置 Filter
        stat: # 配置状态
          enabled: true
          merge-sql: true # 统计相同的 SQL 命令
          log-slow-sql: true # 记录慢 SQL
          slow-sql-millis: 3000 # 慢 SQL 执行的时间标准 3000
          db-type: mysql # 数据库的类型
        wall: # 防火墙配置
          enabled: true
          db-type: mysql
          config:
            delete-allow: false # 不允许执行删除
            update-where-none-check: true
            multi-statement-allow: true # 允许批处理
        slf4j: # 日志配置
          enabled: true
          statement-executable-sql-log-enable: true # 记录执行日志
          data-source-log-enabled: true # 开启日志功能
          statement-sql-pretty-format: true # 美化 SQL 语句
      aop-patterns: com.github.fairy.era.* # Spring 监控 AOP 切入点,如x.y.z.service.*,配置多个英文逗号分隔
  mvc:
    throw-exception-if-no-handler-found: true # 发现404 的时候直接抛出异常
    format:
      date: yyyy-MM-dd HH:mm:ss
  web:
    resources:
      add-mappings: false # 关闭默认的静态资源路径映射
  jackson:
    locale: zh_CN
    date-format: yyyy-MM-dd HH:mm:ss
  servlet: # 文件上传
    multipart:
      # 单个文件大小
      max-file-size: 100MB
      # 设置总上传的文件大小
      max-request-size: 200MB
  redis:
    host: @redis.host@
    port: @redis.port@
    database: @redis.database@
    password: @redis.password@
    lettuce:
      pool:
        max-wait: -1ms
        min-idle: 0
        max-idle: 8
        max-active: 8


mybatis-plus: # MyBatis-Plus 配置
  global-config:
    db-config:
      id-type: assign_id  # 主键生成策略
      table-underline: true
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-delete-field: beenDeleted
      logic-not-delete-value: 0
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl   # 开启日志输出
    map-underscore-to-camel-case: true # 开启驼峰功能

zero: # zero 配置
  ac-table:
    model: add_or_update
    entity-package: com.github.demo.entity
  • ⑤ 启动类:
package com.github.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2022-03-01 11:24:50
 */
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}
  • ⑥ 实体类:
package com.github.demo.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.gitee.zerowsh.actable.annotation.AcColumn;
import io.gitee.zerowsh.actable.annotation.AcTable;
import lombok.Data;

/**
 * (Dept)表实体类
 *
 * @author 许大仙
 * @version 1.0
 * @since 2022-03-03 10:12:47
 */
@Data
@TableName("`dept`")
@AcTable(name = "`dept`", comment = "部门")
public class Dept {

    @TableId
    @AcColumn(name = "`id`", comment = "主键")
    private String id;

    @AcColumn(name = "`name`", comment = "部门名称")
    @TableField("`name`")
    private String name;

    @AcColumn(name = "`description`", comment = "部门描述")
    @TableField("`description`")
    private String description;
}
  • ⑦ mapper 接口:
package com.github.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.github.demo.entity.Dept;
import org.apache.ibatis.annotations.Mapper;

/**
 * (Dept)表数据库访问层
 *
 * @author 许大仙
 * @version 1.0
 * @since 2022-03-03 10:12:49
 */
@Mapper
public interface DeptMapper extends BaseMapper<Dept> {

}
  • ⑧ 业务层接口及实现类:
package com.github.demo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.github.demo.entity.Dept;

/**
 * (Dept)表服务接口
 *
 * @author 许大仙
 * @version 1.0
 * @since 2022-03-03 10:12:46
 */
public interface DeptService extends IService<Dept> {

}
package com.github.demo.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.demo.entity.Dept;
import com.github.demo.mapper.DeptMapper;
import com.github.demo.service.DeptService;
import org.springframework.stereotype.Service;

/**
 * (Dept)表服务实现类
 *
 * @author 许大仙
 * @version 1.0
 * @since 2022-03-03 10:12:48
 */
@Service("deptService")
public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements DeptService {

}
  • ⑨ Web 层:
package com.github.demo.web;

import com.github.demo.entity.Dept;
import com.github.demo.service.DeptService;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2022-03-01 13:37:26
 */
@RestController
@RequiredArgsConstructor
@RequestMapping(value = "/dept")
public class DeptAction {

    @NonNull
    private DeptService deptService;

    @GetMapping(value = "/list")
    public List<Dept> list() {
        return deptService.list();
    }
}
  • ⑩ Dockerfile 和 docker-compose.yaml :
# 以下所有前提 保证 Dockerfile 和项目在同一个文件夹
# 第一阶段:环境构建
FROM maven:3.8.4-openjdk-8-slim AS builder
WORKDIR /app
# 此时有坑,想想 Maven 的标准结构
COPY src ./src
COPY pom.xml .
ARG profile=dev
RUN mvn clean package -Dmaven.test.skip=true -P${profile}
# 第二阶段,最小运行时环境,只需要 jre;第二阶段并不会有第一阶段哪些没用的层
# jdk springboot-actutor(jdk)
FROM openjdk:8u282-slim
LABEL maintainer="xxxx@qq.com"
# 从上一个阶段复制内容
COPY --from=builder /app/target/*.jar /app.jar
# 修改时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone && touch /app.jar
# 环境变量
# docker run -e JAVA_OPTS="-Xmx512m -Xms64m" -e PARAMS="--spring.profiles.active=dev --server.port=8080" xxx
ENV JAVA_OPTS=""
ENV PARAMS=""
# 运行 jar 包
ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom $JAVA_OPTS -jar /app.jar $PARAMS" ]
version: '3.8' # 指定版本号

services: # 所有需要启动的服务
  mysql: # mysql 服务
    image: mysql:5.7
    container_name: mysql57
    environment:
      - MYSQL_ROOT_PASSWORD=123456
      - MYSQL_ROOT_HOST=%
      - MYSQL_DATABASE=demo
      - TZ=Asia/Shanghai
    command:
      - --default-authentication-plugin=mysql_native_password
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_general_ci
      - --lower_case_table_names=1
    volumes:
      - /var/mysql/conf:/etc/mysql
      - /var/mysql/logs:/logs
      - /var/mysql/data:/var/lib/mysql
      - /etc/localtime:/etc/localtime:ro
    ports: # 宿主机和容器的端口映射关系
      - "33060:3306" # 左边宿主机端口:右边容器端口
    networks: # 配置容器连接的网络,引用顶级networks下的条目
      - backend
    restart: always
  redis: # redis 服务
    image: redis:5.0.14-alpine
    container_name: redis5
    command: redis-server --appendonly yes
    ports: # 宿主机和容器的端口映射关系
      - "63790:6379" # 左边宿主机端口:右边容器端口
    networks: # 配置容器连接的网络,引用顶级networks下的条目
      - backend
    restart: always
  demo-back: # 后台服务
    build: # 指定根据哪个Dockerfile构建容器
      context: ./
      dockerfile: Dockerfile
      args:
        profile: prod
    image: demo-back
    container_name: demo-back # 容器名称,默认为“工程名称_服务条目名称_序号”
    environment:
      - TZ=Asia/Shanghai
    ports: # 宿主机和容器的端口映射关系
      - "8080:8080" # 左边宿主机端口:右边容器端口
    networks: # 配置容器连接的网络,引用顶级networks下的条目
      - frontend
      - backend
    restart: always
    depends_on:
      - mysql
      - redis
# 定义网络,可以多个,如果不声明,默认会创建一个网络名称为“工程名称_default”的bridge网络
networks:
  frontend: # 一个具体网络的条目名称
    name: frontend # 网络名称,默认为“工程名称_网络条目名称”
    driver: bridge # 网络模式,默认为bridge
  backend: # 一个具体网络的条目名称
    name: backend # 网络名称,默认为“工程名称_网络条目名称”
    driver: bridge # 网络模式,默认为bridge
  • ⑪ 构建镜像和启动容器命令:
docker-compose build --force-rm --no-cache && docker-compose up -d

第五章:Docker 轻量级可视化工具 Portainer

5.1 什么是 Portainer

  • 官网
  • Portainer 社区版 2.0 拥有超过 50 万的普通用户,是功能强大的开源工具集,可让您轻松地在 Docker,Swarm ,Kubernetes 和 Azure ACI 中构建和管理容器。 Portainer 的工作原理是在易于使用的 GUI 后面隐藏使管理容器变得困难的复杂性。通过消除用户使用 CLI,编写 YAML 或理解清单的需求,Portainer 使部署应用程序和解决问题变得如此简单,任何人都可以做到。

5.2 安装

  • 服务端部署:
# 访问 9000 端口即可
docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce
  • agent 端部署:
docker run -d -p 9001:9001 --name portainer_agent --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v /var/lib/docker/volumes:/var/lib/docker/volumes portainer/agent

第六章:Docker 容器监控之 CAdvisor + InfluxDB + Granfana

6.1 原生命令查看监控

  • 命令:
docker stats
  • 示例:
docker stats

62.gif

通过 docker stats 命令可以很方便的看到当前宿主机上所有容器的 CPU 、 内存以及网络流量等数据,但是,docker stats 统计结果只能是当前宿主机的全部容器,查看的数据是实时的,没有地方存储、没有健康指标过线预警等功能。

6.2 容器监控三剑客

  • CAdvisor(监控收集)+ InfluxDB(存储数据)+ Granfana(展示图表)。

6.2.1 CAdvisor

  • CAdvisor 是一个容器监控工具,包括容器的内存、CPU、网络 IO 、磁盘 IO 等监控,同时提供一个 WEB 页面用于查看容器的实时运行状态。CAdvisor 默认存储 2 分钟的数据,而且只是针对于单物理机。不过,CAdvisor 提供了很多数据集成接口,支持 InfluxDB 、 Redis 、Kafka 、ElasticSearch 等,可以将监控数据发送给这些数据库存储起来。
  • CAdvisor 功能主要有两点:
    • 展示 HOST 和 容器两个层次的监控数据。
    • 展示历史变化数据。

6.2.2 InfluxDB

  • InfluxDB 是用 Go 语言编写的一个开源分布式时序、事件和指标数据库,无需外部依赖。
  • InfluxDB 的主要功能:
    • 基于时间序列,支持和时间有关的相关函数(如:最大、最小、求和等)。
    • 可度量性:可以实时对大量数据进行计算。
    • 基于事件:支持任意的事件数据。

6.2.3 Granfana

  • Granfana 是一个开源的数据监控分析可视化平台,支持多种数据源配置(支持的数据源包括 InfluxDB 、MySQL、ElasticSearch 等)和丰富的插件以及模板功能,支持图表权限控制和报警。
  • Granfana 的主要特性:
    • 灵活丰富的图形化选项。
    • 可以混合多种风格。
    • 支持白天和夜间模式。
    • 支持多个数据源。

6.3 基于 Docker Compose 容器编排搭建 CIG

  • docker-compose.yml
version: '3.8' # 指定版本号

services:
  influx:
    image: tutum/influxdb:0.13
    ports:
      - "8083:8083"
      - "8086:8086"
   environment:
     - PRE_CREATE_DB=cadvisor
    volumes:
      - "/var/influxdb:/var/lib/influxdb"
  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"
      links:
      - influxdb:influxsrv
    volumes:
      - grafana_data:/var/lib/grafana 
    environment:
        - HTTP_USER=admin
        - HTTP_PASS=admin
        - INFLUXDB_HOST=influxsrv
        - INFLUXDB_PORT=8086
        - INFLUXDB_NAME=cadvisor
        - INFLUXDB_USER=root
        - INFLUXDB_PASS=root  
  cadvisor:
    image: google/cadvisor
    ports:
      - "8080:8080"
    links:
    - influxdb:influxsrv  
    hostname: '{{.Node.Hostname}}'
    command: -logtostderr -docker_only -storage_driver=influxdb -storage_driver_db=cadvisor -storage_driver_host=influxsrv:8086
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:rw
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    depends_on:
      - influx

volumes:
  influx:
    driver: local
  grafana:
    driver: local
  • 启动命令:
docker-compose build --force-rm --no-cache && docker-compose up -d