目录
- 1. 镜像制作的原因和方式
- 2. 快照方式制作镜像
- 2.1 docker commit命令来制作镜像
- 2.2 实战C++ HelloWorld 镜像制作
- 3. Dockerfile 制作镜像
- 3.1 Dockerfile 是什么
- 3.2 为什么需要 Dockerfile
- 3.3 Dockerfile 指令
- 3.3.1 指令清单
- 3.3.2 FROM指令
- 3.3.3 MAINTAINER指令
- 3.3.4 LABEL指令
- 3.3.5 COPY指令
- 3.3.6 ENV指令
- 3.3.7 WORKDIR指令
- 3.3.8 ADD指令
- 3.3.9 RUN指令
- 3.3.10 CMD指令
- 3.3.11 EXPOSE指令
- 3.3.12 ENTRYPOINT指令
- 3.3.13 ARG指令
- 3.3.14 USER指令
- 3.4 制作命令docker build
- 4. Dockerfile 编写优秀实践
- 5. 正确使用 CMD 和 EntryPoint
- 5.1 基础知识
- 5.2 实战步骤
- 5.2.1 覆盖
- 5.2.2 Shell VS Exec
- 5.2.3 组合
- 6. 合理使用 dokerignore
- 6.1 基础知识
- 6.2 实战步骤
- 7. 多阶段构建
- 7.2 基础知识
- 7.2 实战步骤
- 8. 合理使用缓存
- 8.1 基础知识
- 8.2 实战步骤
- 9. Dockerfile结合docker compose搭建mysql主从同步
- 9.1 基础知识
- 9.2 主从同步的方式
- 9.3 MySQL 主从形式
- 9.4 mysql 主从集群搭建步骤
- 10. Dockerfile 结合 docker compose 搭建 redis 集群
- 11. Dockerfile 结合 docker compose 搭建 C++微服务站点
- 12. 镜像制作常见问题
1. 镜像制作的原因和方式
(1)镜像制作是因为某种需求,官方的镜像无法满足需求,需要我们通过一定手段来自定义镜像来满足要求。制作镜像往往因为以下原因:
- 编写的代码如何打包到镜像中直接跟随镜像发布。
- 第三方制作的内容安全性未知,如含有安全漏洞。
- 特定的需求或者功能无法满足,如需要给数据库添加审计功能。
- 公司内部要求基于公司内部的系统制作镜像,如公司内部要求使用自己的操作系统作为基础镜像。
(2)制作容器镜像,主要有两种方法:
- 制作快照方式获得镜像(偶尔制作的镜像):在基础镜像上(比如 Ubuntu),先登录容器中,然后安装镜像需要的所有软件,最后整体制作快照。
- Dockerfile 方式构建镜像(经常更新的镜像):将软件安装的流程写成 Dockerfile,使用 docker build 构建成容器镜像。
2. 快照方式制作镜像
docker_commit_14">2.1 docker commit命令来制作镜像
(1)功能:
- 从容器创建一个新的镜像。
(2)语法:
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
(3)参数:
- -a:提交的镜像作者;
- -c:使用 Dockerfile 指令来创建镜像;可以修改启动指令;
- -m:提交时的说明文字;
- -p:在 commit 时,将容器暂停。
(4)样例:
docker commit c3f279d17e0a maxhou/mynginx:v01
2.2 实战C++ HelloWorld 镜像制作
(1)创建临时工作目录:
mkdir -p /data/maxhou/commitimage
cd /data/maxhou/commitimage
(2)编写 c++源代码文件,vi demo.c:
#include <stdio.h>
int main()
{printf("hello docker!\n");return 0;
}
(3)启动一个 centos7 的容器:
[xiaomaker@xiaomaker-virtual-machine:commitimage]$ docker run -it --name mycppcommit centos:7 bash
[root@40de1bf45017 /]#
(4)替换为国内软件源:
[root@40de1bf45017 /]# sed -e 's|^mirrorlist=|#mirrorlist=|g' \
> -e 's|^#baseurl=http://mirror.centos.org/centos|baseurl=https://mirro
rs.ustc.edu.cn/centos|g' \
> -i.bak \
> /etc/yum.repos.d/CentOS-Base.repo
[root@40de1bf45017 /]# yum makecache
(5)安装编译软件,并创建源代码目录:
[root@40de1bf45017 /]# yum install -y gcc
[root@40de1bf45017 /]# mkdir /src/
(6)打开另外一个 shell,拷贝源代码到容器中:
[xiaomaker@xiaomaker-virtual-machine:commitimage]$ docker cp ./demo.c mycppcommit:/src
Successfully copied 2.048kB to mycppcommit:/src
#查看容器中
[root@40de1bf45017 /]# ls -l /src
total 4
-rw-r--r-- 1 root root 80 May 16 05:30 demo.c
(7)编译运行:
[root@40de1bf45017 /]# cd /src
[root@40de1bf45017 src]# gcc demo.c -o demo
[root@40de1bf45017 src]# ./demo
hello docker!
(8)提交为一个镜像:
[xiaomaker@xiaomaker-virtual-machine:commitimage]$ docker commit mycppcommit mycppimg:v1.0
sha256:97d178ba9e5da794dd8276fe0ee23dc73510abea7f03f7f3c3a59a978dc8fa2c
[xiaomaker@xiaomaker-virtual-machine:commitimage]$ docker images mycppimg
REPOSITORY TAG IMAGE ID CREATED SIZE
mycppimg v1.0 97d178ba9e5d 13 seconds ago 714MB
(9)测试镜像能否正常运行:
[xiaomaker@xiaomaker-virtual-machine:commitimage]$ docker run -it mycppimg:v1.0 ./src/demo
hello docker!
3. Dockerfile 制作镜像
3.1 Dockerfile 是什么
(1)概念如下:
- 镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,这个脚本就是 Dockerfile。
- Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction), 每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
(2)Dockerfile 格式:
# Comment
INSTRUCTION arguments
- 该指令不区分大小写。然而,约定是它们是大写的,以便更容易地将它们与参数区分开来。
- Docker 按顺序运行指令 Dockerfile。
- Docker 将以开头的行视为#注释,行中其他任何地方的标记#都被视为参数。这允许像这样的语句:
# Comment
RUN echo 'we are running some # of cool things'
(3)生活案例来理解什么是Dockerfile:
- Dockerfile 就像我们盖房子的时候的施工图纸,地基多深有多少层,每层是什么,以及多高。
3.2 为什么需要 Dockerfile
(1)可以按照需求自定义镜像:
- 和 docker commit 一样能够自定义镜像,官方的镜像可以说很少能直接满足我们应用的,都需要我们自己打包自己的代码进去然后做成对应的应用镜像对外使用。
(2)很方便的自动化构建,重复执行:
- 通过 dockerfile 可以自动化的完成镜像构建,而不是像 docker commit 一样,手动一个命令一个命令执行,而且可以重复执行, docker commit 的话很容易忘记执行了哪个命令,哪个命令没有执行。
(3)维护修改方便,不再是黑箱操作:
(4)更加标准化,体积可以做的更小:
- docker 容器启动后,系统运行会生成很多运行时的文件,如果使用 commit 会导致这些文件也存储到镜像里面,而且 commit 的时候安装了很多的依赖文件,没有有效的清理机制的话会导致镜像非常的臃肿。
- 使用 Dockerfile 则会更加标准化,而且提供多级构建,将编译和构建分开,不会有运行时的多余文件,更加的标准化。
3.3 Dockerfile 指令
3.3.1 指令清单
(1)指令如下以及官方文档链接:https://docs.docker.com/reference/dockerfile/。
指令 | 功能 | 备注 |
---|---|---|
FROM | 构建镜像基于哪个镜像,也就是基础镜像 | 必须掌握 |
MAINTAINER | 镜像维护者姓名或邮箱地址 | 已经废弃,被label 替代了 |
LABEL | 为镜像添加元数据 | |
COPY | 拷贝文件或目录到镜像中,跟 ADD 类似,但不具备自动下载或解压的功能 | 必须掌握 |
ADD | 拷贝文件或目录到镜像中,如果是 URL 或压缩包便会自动下载或自动解压 | 必须掌握 |
WORKDIR | 指定工作目录 | 必须掌握 |
RUN | 指定 docker build 过程中运行的程序 | 必须掌握 |
VOLUME | 指定容器挂载点 | |
EXPOSE | 声明容器的服务端口(仅仅是声明) | |
ENV | 设置环境变量 | 必须掌握 |
CMD | 运行容器时执行的命令 | 必须掌握 |
ENTRYPOINT | 运行容器时程序入口 | 必须掌握 |
ARG | 指定构建时的参数 | |
SHELL | 指定采用哪个 shell | 使用较少 |
USER | 指定当前用户 | |
HEALTHCHECK | 健康检测指令 | |
ONBUILD | 在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。 | 使用较少 |
STOPSIGNAL | 允许您覆盖发送到容器的默认信号。 | 使用较少 |
下面我们详细介绍 Dockerfile 当中重要的一些指令,一些不经常使用的参考官方文档。
3.3.2 FROM指令
(1)功能:
- FROM 指令用于为镜像文件构建过程指定基础镜像,后续的指令运行于此基础镜像所提供的运行环境;
(2)注意事项:
- FROM 指令必须是 Dockerfile 中非注释行或者 ARG 之后的第一个指令;
- 实践中,基准镜像可以是任何可用镜像文件,默认情况下, docker build 会在docker 主机上查找指定的镜像文件,在其不存在时,则会自动从 Docker 的公共库 pull 镜像下来。如果找不到指定的镜像文件, docker build 会返回一个错误信息;
- FROM 可以在一个 Dockerfile 中出现多次,如果有需求在一个 Dockerfile 中创建多个镜像,或将一个构建阶段作为另一个的依赖。
- 如果 FROM 语句没有指定镜像标签,则默认使用 latest 标签。
(3)语法:
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
(4)参数:
- :构建的 cpu 架构,如 linux/amd64,linux/arm64,windows/amd64
- <image>:指定作为 base image 的名称;
- <tag>: base image 的标签,省略时默认 latest;
- <digest>:是镜像的哈希码;
- AS <name>: 指定构建步骤的名称,配合 COPY --from=<name>可以完成多级构建。
(5)样例:
FROM busybox:latest
(6)实战:
- 创建 Docker 目录,确保目录中没有内容:
mkdir -p /data/myworkdir/dockerfile/web1
cd /data/myworkdir/dockerfile/web1
- 编辑 Dockerfile,测试 FROM 指令和注释,在 web1 目录中 vi Dockerfile,输入以下内容:
#我的 web 站点 by maxhou
FROM ubuntu:22.04 as buildbase
- 执行构建,打造镜像 v0.1 版本:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker build -t web1:v0.1 .
- 运行制作的镜像,可以看到操作系统版本:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker run --name web1 --rm -it web1:v0.1 cat /etc/*release*
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.2 LTS"
PRETTY_NAME="Ubuntu 22.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.2 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-andpolicies/privacy-policy"
UBUNTU_CODENAME=jammy
3.3.3 MAINTAINER指令
(1)功能:
- 用于让 dockerfile 制作者提供本人的详细信息
- 该功能已经废弃,由 label 替代
(2)语法:
MAINTAINER <authtor's detail>
(3)参数:
- <authtor’s detail>:作者信息。
(4)样例:
MAINTAINER "maxhou <maxhou@xiaomaker.com>"
(5)实战:
- 接 FROM 添加制作者信息,使用 MAINTAINER:
#我的 web 站点 by maxhou
FROM ubuntu:22.04 as buildbase
MAINTAINER "maxhou maxhou@xiaomaker.com"
- 再次编译 0.2 版本:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker build -t web1:v0.2 .
- 查看镜像信息,可以看到作者信息已经添加完成:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker image inspect web1:v0.2
[
{
....
{"DockerVersion": "","Author": "\"maxhou maxhou@xiaomaker.com\"",
}
....
3.3.4 LABEL指令
(1)功能:
- 为镜像添加元数据,元数据是 kv 对形式
(2)语法:
LABEL <key>=<value> <key>=<value> <key>=<value> ...
(3)样例:
LABEL com.example.label-with-value="foo"
LABEL multi.label1="value1" multi.label2="value2" other="value3"
(4)实战:
- 我们使用 LABEL 添加额外的元数据信息,继续 vi Dockerfile:
#我的 web 站点 by maxhou
FROM ubuntu:22.04 as buildbase
MAINTAINER "maxhou maxhou@xiaomaker.com"
LABEL company="com.bit" app="nginx"
- 我们继续构建 v0.3 版本:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker build -t web1:v0.3 .
- 查看镜像元数据:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker inspect web1:v0.3
[
....
"Config": {"Hostname": "","Domainname": "","User": "","AttachStdin": false,"AttachStdout": false,"AttachStderr": false,"Tty": false,"OpenStdin": false,"StdinOnce": false,"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
"],"Cmd": ["/bin/bash"],"Image": "","Volumes": null,"WorkingDir": "","Entrypoint": null,"OnBuild": null,"Labels": {"app": "nginx","company": "com.bit","org.opencontainers.image.ref.name": "ubuntu","org.opencontainers.image.version": "22.04"
}
},
3.3.5 COPY指令
(1)功能:
- 用于从 docker 主机复制新文件或者目录至创建的新镜像指定路径中 。
(2)语法:
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
(3)参数:
- <src>:要复制的源文件或目录, 支持使用通配符;
- <dest>:目标路径,即正在创建的 image 的文件系统路径; 建议使用绝对路径,否则, COPY 指定以 WORKDIR 为当前路径在路径中有空白字符时,通常使用第 2 种格式;
- - -chown:修改用户和组。
- - -from <name>可选项:可以从之前构建的步骤中拷贝内容,结合 FROM … AS <name>往往用作多级构建,后续我们有实战课专门完成多级构建
(4)注意事项:
- <src>必须是 build 上下文中的路径, 不能是其父目录中的文件;
- 如果<src>是目录,则其内部文件或子目录会被递归复制,但目录自身不会被复制;
- 如果指定了多个<src>,或在<src>中使用了通配符,则<dest>必须是一个目录,且必须以 / 结尾;
- 如果<dest>事先不存在,它将会被自动创建,这包括父目录路径。
(5)样例:
COPY index.html /data/web/html/ #要确保 dockerfile 同级路径下有index.html 文件
(6)实战:
- 创建一个 index.html,作为我们站点的首页 ,vi index.html 输入下面内容:
<html>
<h1>Hello ,My Home Page! by bit</h1>
</html>
- 我们通过 COPY 命令添加到我们的镜像中,并且我们指定我们的根目录为/data/web/www:
[xiaomaker@xiaomaker-virtual-machine:web1]$ cat Dockerfile
#我的 web 站点 by maxhou
FROM ubuntu:22.04 as buildbase
MAINTAINER "maxhou maxhou@xiaomaker.com"
LABEL company="com.bit" app="nginx"
COPY index.html /data/web/www/
- 我们再次编译 v0.4 版本镜像:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker build -t web1:v0.4 .
- 我们运行镜像,可以看到 index.html 已经进去了:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker run --name web1 --rm -it web1:v0.4 ls /data/web/www
index.html
3.3.6 ENV指令
(1)功能:
- 用于为镜像定义所需的环境变量,并可被 Dockerfile 文件中位于其后的其它指
令(如 ENV、 ADD、 COPY 等)所调用。 - 调用格式为$variable_name 或 ${variable_name}。
(2)语法:
ENV <key>=<value> ...
(3)样例:
ENV MY_NAME="John Doe"
(4)实战:
- 目录后面可能复用,我们将目录的位置提取为变量,通过 ENV 来设置,我们再次编辑 Dockerfile:
#我的 web 站点 by maxhou
FROM ubuntu:22.04 as buildbase
MAINTAINER "maxhou maxhou@xiaomaker.com"
LABEL company="com.bit" app="nginx"
ENV WEB_ROOT=/data/web/www/
COPY index.html ${WEB_ROOT}
- 我们再次编译 v0.5 版本镜像:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker build -t web1:v0.5 .
- 我们运行 v0.5 版本镜像,然后看下 index.html 是否在${WEB_ROOT}的目录下面
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker run --name web1 --rm -it web1:v0.5 ls /data/web/www
index.html
- 我们也可以通过 inspect 查看镜像,可以看到环境变量已经内置到镜像里面了
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker image inspect web1:v0.5
[
....
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
",
"WEB_ROOT=/data/web/www/"
],
3.3.7 WORKDIR指令
(1)功能:
- 为 Dockerfile 中所有的 RUN、 CMD、 ENTRYPOINT、 COPY 和 ADD 指定设定工作目录
(2)语法:
WORKDIR /path/to/workdir
(3)注意事项:
- 默认的工作目录是/。
- 如果提供了相对路径,它将相对于前一条 WORKDIR 指令的路径。
- WORKDIR 指令可以解析先前使用设置的环境变量 ENV。
(4)样例:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
#此处最终命令的输出 Dockerfile 为/a/b/c.
(5)实战:
- 后面我们要下载 nginx,我们指定一个工作目录,通过 WORKDIR 来指定,后续我们就可以使用相对路径来执行 nginx 的编译了。我们再次编辑 Dockerfile:
#我的 web 站点 by maxhou
FROM ubuntu:22.04 as buildbase
MAINTAINER "maxhou maxhou@xiaomaker.com"
LABEL company="com.bit" app="nginx"
ENV WEB_ROOT=/data/web/www/
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
- 我们再次编译镜像 v0.6:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker build -t web1:v0.6 .
- 执行 pwd 命令查看当前目录:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker run --name web1 --rm -it web1:v0.6 pwd
/usr/local
3.3.8 ADD指令
(1)功能:
- ADD 指令类似于 COPY 指令, ADD 支持使用 TAR 文件和 URL 路径,会自动
完成解压和下载
(2)语法:
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
(3)参数:
- <src>:要复制的源文件或目录, 支持使用通配符;
- <dest>:目标路径,即正在创建的 image 的文件系统路径; 建议使用
绝对路径,否则, ADD 指定以 WORKDIR 为其实路径;在路径中有空白字符时,通常使用第 2 种格式; - - -chown:修改用户和组。
(4)实战:
- 我们登录 nginx 官网 https://nginx.org/,找到最新稳定版本的 nginx 的下载地址https://nginx.org/en/download.html,可以看到最新版本为 1.22.1,我们复制该链接https://nginx.org/download/nginx-1.22.1.tar.gz:
- 有了链接地址后,我们发现这是一个 URL 地址,我们通过 ADD 命令下载,因为nginx 的未来版本还会变,所以我们可以提取 ningx 的版本为环境变量,我们再次编辑我们的 Dockerfile:
#我的 web 站点 by maxhou
FROM ubuntu:22.04 as buildbase
MAINTAINER "maxhou maxhou@xiaomaker.com"
LABEL company="com.bit" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.22.1"
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
- 我们执行编译 v0.7:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker build -t web1:v0.7 .
- 我们运行 v0.7 镜像查看,看是否已经完成了下载:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker run --name web1 --rm -it web1:v0.7 ls -l /usr/local/src
total 1052
-rw------- 1 root root 1073948 Oct 19 09:23 nginx-1.22.1.tar.gz
-
可以看到此时并没有被解压。
-
我们手动下载下来这个压缩包,放到我们的服务器目录,此时目录的结构如下:
[xiaomaker@xiaomaker-virtual-machine:web1]$ wget https://nginx.org/download/nginx-1.22.1.tar.gz
[xiaomaker@xiaomaker-virtual-machine:web1]$ ll -h
total 1.1M
drwxr-xr-x 2 root root 4.0K Mar 14 15:32 ./
drwxr-xr-x 4 root root 4.0K Mar 14 14:37 ../
-rw-r--r-- 1 root root 298 Mar 14 15:26 Dockerfile
-rw-r--r-- 1 root root 53 Mar 14 15:01 index.html
-rw-r--r-- 1 root root 1.1M Oct 19 17:23 nginx-1.22.1.tar.gz
- 我们再次编辑 Dockerfile,添加将 nginx 放到 src2 目录:
#我的 web 站点 by maxhou
FROM ubuntu:22.04 as buildbase
MAINTAINER "maxhou maxhou@xiaomaker.com"
LABEL company="com.bit" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.22.1"
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
ADD ${NGINX_VERSION}.tar.gz ./src2
- 执行命令编译 v0.8:
docker build -t web1:v0.8 .
- 我们运行 v0.8 版本的镜像,然后查看该镜像的/usr/local/src2 目录,可以看到nginx 已经被解压:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker run --name web1 --rm -it web1:v0.8 ls -l /usr/local/src2
total 4
drwxr-xr-x 8 1001 1001 4096 Oct 19 08:02 nginx-1.22.1
3.3.9 RUN指令
(1)功能:
- 用于指定 docker build 过程中运行的程序,其可以是任何命令
(2)语法:
#shell form
RUN <command>
#exec form
RUN ["executable", "param1", "param2"]
(3)参数:
- 第一种格式中, <command>通常是一个 shell 命令, 且以“/bin/sh -c”来运行它,Windows 默认为 cmd /S /C。如果一个脚本 test.sh 不能自己执行,必须要/bin/sh -c test.sh 的方式来执行,那么,如果使用 RUN 的 shell 形式,最后得到的命令相当于:
/bin/sh -c "/bin/sh -c 'test.sh'"
- 第二种语法格式中的参数是一个 JSON 格式的数组,其中<executable>为要运行的命令,后面的 <paramN>为传递给命令的选项或参数;然而,此种格式指定的命令不会以“/bin/sh -c”来发起,因此常见的 shell 操作如变量替换以及通配符(?,*等)替换将不会进行;不过,如果要运行的命令依赖于此 shell 特性的话,可以将其替换为类似下面的格式。 RUN [“/bin/bash”, “-c”, “<executable>”,“<param1>”]。
(4)样例:
ENV WEB_SERVER_PACKAGE nginx-1.21.1.tar.gz
RUN cd ./src && tar -xf ${WEB_SERVER_PACKAGE}
(5)实战:
- 接上,因为我们 nginx 是源码所以我们需要先解压 src 文件,通过 RUN 命令可以完成 nginx 的解压:
#我的 web 站点 by maxhou
FROM ubuntu:22.04 as buildbase
MAINTAINER "maxhou maxhou@xiaomaker.com"
LABEL company="com.bit" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.22.1"
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
ADD ${NGINX_VERSION}.tar.gz ./src2
#解压
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
- 再次编译镜像 v0.9,然后查看镜像/usr/local/src 目录是否已经解压:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker build -t web1:v0.9 .
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker run --name web1 --rm -it web1:v0.9 ls -l /usr/local/src
total 1056
drwxr-xr-x 8 1001 1001 4096 Oct 19 08:02 nginx-1.22.1
-rw------- 1 root root 1073948 Oct 19 09:23 nginx-1.22.1.tar.gz
- 因为是源码安装所以我们要编译安装 nginx,需要下载编译工具,已经依赖库信息,并通过 make 来完成编译,我们再次修改 dockerfile:
#我的 web 站点 by maxhou
FROM ubuntu:22.04 as buildbase
MAINTAINER "maxhou maxhou@xiaomaker.com"
LABEL company="com.bit" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.22.1"
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
ADD ${NGINX_VERSION}.tar.gz ./src2
#解压
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
#1.安装 build-essential 构建工具
#2.安装依赖包 libpcre3 libpcre3-dev zlib1g-dev 依赖库
#3.进入 nginx 目录
#4.执行编译和构建
RUN apt-get update -y && apt install -y build-essential libpcre3
libpcre3-dev zlib1g-dev
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
- 我们再次构建,然后查看构建的 nginx 是否成功生成:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker build -t web1:v1.0 .
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker run --name web1 --rm -it web1:v1.0 /usr/local/nginx/sbin/nginx -V nginx version: nginx/1.22.1
built by gcc 11.3.0 (Ubuntu 11.3.0-1ubuntu1~22.04)
configure arguments: --prefix=/usr/local/nginx
- nginx 的默认配置文件为/usr/local/nginx/conf/nginx.conf,我们修改 server 部分,配置一个我们自己的配置文件,然后覆盖它:
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local]
"$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent"
"$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root /data/web/html/;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on
127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on
127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME
/scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
}
- 我们可以看到我们的包的下载和编译特别耗时,我们调整下 Dockerfile 的顺序,然后添加我们配置的 nginx.conf,此时 Dockerfile 如下:
#我的 web 站点 by maxhou
FROM ubuntu:22.04 as buildbase
MAINTAINER "maxhou maxhou@xiaomaker.com"
LABEL company="com.bit" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.22.1"
#1.安装 build-essential 构建工具
#2.安装依赖包 libpcre3 libpcre3-dev zlib1g-dev 依赖库
RUN apt-get update -y && apt install -y build-essential libpcre3
libpcre3-dev zlib1g-dev
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
ADD ${NGINX_VERSION}.tar.gz ./src2
#解压
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
#3.进入 nginx 目录
#4.执行编译和构建
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
COPY nginx.conf ./nginx/conf
目录结构如下
- 我们再次构建镜像 v1.1:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker build -t web1:v1.1 .
- 我们再次运行 nginx 镜像查看是否正常运行:
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker run --name web1 --rm -it web1:v1.1 /usr/local/nginx/sbin/nginx -V nginx version: nginx/1.22.1
built by gcc 11.3.0 (Ubuntu 11.3.0-1ubuntu1~22.04)
configure arguments: --prefix=/usr/local/nginx
3.3.10 CMD指令
(1)功能:
- 类似于 RUN 指令, CMD 指令也可用于运行任何命令或应用程序,不过,二者的运行时间点不同。
- RUN 指令运行于映像文件构建过程中,而 CMD 指令运行于基于 Dockerfile构建出的新映像文件启动一个容器时。
- CMD 指令的首要目的在于为启动的容器指定默认要运行的程序,且其运行结束后,容器也将终止;不过, CMD 指定的命令其可以被 docker run 的命令行选项所覆盖。
- 在 Dockerfile 中可以存在多个 CMD 指令,但仅最后一个会生效。
(2)语法:
CMD ["executable","param1","param2"] (exec form, this is the
preferred form)
CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
CMD command param1 param2 (shell form)
(3)注意事项:
- 第二种则用于为 ENTRYPOINT 指令提供默认参数。
- json 数组中,要使用双引号,单引号会出错。
(4)样例:
CMD ["/usr/bin/wc","--help"]
(5)实战:
- 接上,此时因为 docker 是需要一个长时间后台运行的,我们让 nginx 进入前台运行,这就需要我们的 CMD 或者 EntryPoint 来完成,我们先用 CMD 来配置,修改Dockerfile 如下:
#我的 web 站点 by maxhou
FROM ubuntu:22.04 as buildbase
MAINTAINER "maxhou maxhou@xiaomaker.com"
LABEL company="com.bit" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.22.1"
#1.安装 build-essential 构建工具
#2.安装依赖包 libpcre3 libpcre3-dev zlib1g-dev 依赖库
RUN apt-get update -y && apt install -y build-essential libpcre3
libpcre3-dev zlib1g-dev
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
ADD ${NGINX_VERSION}.tar.gz ./src2
#解压
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
#3.进入 nginx 目录
#4.执行编译和构建
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
COPY nginx.conf ./nginx/conf/
CMD ["/usr/local/nginx/sbin/nginx","-g daemon off;"]
- 我们再次编译 v1.2 版本:
docker build -t web1:v1.2 .
docker run --name web1 --rm -d web1:v1.2
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker ps
CONTAINER ID IMAGE COMMAND CREATED
STATUS PORTS NAMES
eb88a30deaa4 web1:v1.2 "/usr/local/nginx/sb…" 3 seconds
ago Up 2 seconds web1
8f53962c6338 mybusybox:v1 "/bin/sh" 22 hours
ago Up 22 hours mybusybox1
3.3.11 EXPOSE指令
(1)功能:
- 用于为容器声明打开指定要监听的端口以实现与外部通信。
- 该 EXPOSE 指令实际上并不发布端口。它充当构建图像的人和运行容器的人之间的一种文档,关于要发布哪些端口。要在运行容器时实际发布端口,使用-p 参数发布和映射一个或多个端口,或者使用-Pflag 发布所有暴露的端口并将它们映射宿主机端口。
(2)语法:
EXPOSE <port> [<port>/<protocol>...]
(3)参数:
- <protocol>: tcp/udp 协议。
- <port>:端口。
(4)样例:
EXPOSE 80/tcp
(5)实战:
- 接上一个指令,我们通过外部的浏览器访问,然后发现无法访问,是因为我们端还没对外开放。
- 我们通过 EXPOSE 暴露端口看下,调整 Dockerfile 如下
FROM ubuntu:22.04 as buildbase
MAINTAINER "maxhou maxhou@bit.com"
LABEL company="com.bit" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.22.1"
#1.安装 build-essential 构建工具
#2.安装依赖包 libpcre3 libpcre3-dev zlib1g-dev 依赖库
RUN apt-get update -y && apt install -y build-essential libpcre3
libpcre3-dev zlib1g-dev
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
ADD ${NGINX_VERSION}.tar.gz ./src2
#解压
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
#3.进入 nginx 目录
#4.执行编译和构建
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
COPY nginx.conf ./nginx/conf/
EXPOSE 80/tcp
CMD ["/usr/local/nginx/sbin/nginx","-g daemon off;"]
- 此时我们构建 v1.3 版本
[xiaomaker@xiaomaker-virtual-machine:web1]$ docker build -t web1:v1.3
- 我们再次运行,然后通过浏览器访问,不过我们要先停止之前的容器
docker stop web1
docker run --name web1 --rm -d web1:v1.3
docker stop web1
docker run --name web1 --rm -p 80:80 -d web1:v1.3
- 此时通过浏览器访问可以看到我们的首页
3.3.12 ENTRYPOINT指令
(1)功能:
- 用于指定容器的启动入口。
(2)语法:
# exec from
ENTRYPOINT ["executable", "param1", "param2"]
# shell form
ENTRYPOINT command param1 param2
(3)参数:
- json 数组中,要使用双引号,单引号会出错。
(4)样例:
ENTRYPOINT ["nginx","-g","daemon off;"]
(5)实战:
- 我们将 CMD 调整为 EntryPoint 重新测试下,此时 Dockerfile 如下:
#我的 web 站点 by maxhou
FROM ubuntu:22.04 as buildbase
MAINTAINER "maxhou maxhou@xiaomaker.com"
LABEL company="com.bit" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.22.1"
#1.安装 build-essential 构建工具
#2.安装依赖包 libpcre3 libpcre3-dev zlib1g-dev 依赖库
RUN apt-get update -y && apt install -y build-essential libpcre3
libpcre3-dev zlib1g-dev
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
ADD ${NGINX_VERSION}.tar.gz ./src2
#解压
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
#3.进入 nginx 目录
#4.执行编译和构建
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
COPY nginx.conf ./nginx/conf/
EXPOSE 80/tcp
#CMD ["/usr/local/nginx/sbin/nginx","-g daemon off;"]
ENTRYPOINT ["/usr/local/nginx/sbin/nginx","-g daemon off;"]
- 再次构建,运行我们的镜像 v1.4:
docker build -t web1:v1.4 .
docker stop web1
docker run --name web1 --rm -p 80:80 -d web1:v1.4
- 此时通过浏览器可以再次访问,发现可以正常访问:
3.3.13 ARG指令
(1)功能:
- ARG 指令类似 ENV,定义了一个变量;区别于 ENV:用户可以在构建时
docker build --build-arg = 进行对变量的修改; ENV 不可以; - 如果用户指定了未在 Dockerfile 中定义的构建参数,那么构建输出警告。
(2)语法:
ARG <name>[=<default value>]
(3)注意事项:
- Dockerfile 可以包含一个或多个 ARG 指令。
- ARG 支持指定默认值。
- 使用范围:定义之后才能使用,定义之前为空,如下面的案例,执行命令docker build --build-arg username=what_user .第二行计算结果为some_user ,不是我们指定的 build-arg 中的参数值 what_user:
FROM busybox
USER ${username:-some_user}
ARG username
USER $username
# ...
- ENV 和 ARG 同时存在, ENV 会覆盖 ARG:
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=v1.0.0
RUN echo $CONT_IMG_VER
- 执行下面指令输出 v1.0.0:
docker build --build-arg CONT_IMG_VER=v2.0.1 .
- 我们可以优化写法为:
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=${CONT_IMG_VER:-v1.0.0}
RUN echo $CONT_IMG_VER
- 系统内置了一些 ARG 变量:
- HTTP_PROXY
- http_proxy
- HTTPS_PROXY
- https_proxy
- FTP_PROXY
- ftp_proxy
- NO_PROXY
- no_proxy
- ALL_PROXY
- all_proxy
(4)样例:
FROM busybox
ARG user1=someuser
ARG buildno=1
(5)实战:
- 突然有一天老板说把基础镜像升级到 22.10 吧,这个时候我们的 ARG 就排上用场了,我们定义一个 ARG 变量指定操作系统版本,修改后的 dockerfile 如下:
#我的 web 站点 by maxhou
ARG UBUNTU_VERSION=22.04
FROM ubuntu:${UBUNTU_VERSION} as buildbase
MAINTAINER "maxhou maxhou@bit.com"
LABEL company="com.bit" app="nginx"
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION="nginx-1.22.1"
#1.安装 build-essential 构建工具
#2.安装依赖包 libpcre3 libpcre3-dev zlib1g-dev 依赖库
RUN apt-get update -y && apt install -y build-essential libpcre3
libpcre3-dev zlib1g-dev
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
ADD ${NGINX_VERSION}.tar.gz ./src2
#解压
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
#3.进入 nginx 目录
#4.执行编译和构建
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
COPY nginx.conf ./nginx/conf/
EXPOSE 80/tcp
#CMD ["/usr/local/nginx/sbin/nginx","-g daemon off;"]
ENTRYPOINT ["/usr/local/nginx/sbin/nginx","-g daemon off;"]
- 我们再次构建 v1.5 版本的镜像,指定 ARG 参数值,可以看到此时镜像重新构建:
docker build --build-arg UBUNTU_VERSION=22.10 -t web1:v1.5 .
- 我们再次运行 v1.5 镜像可以看到容器正常运行:
docker stop web1
docker run --name web1 --rm -p 80:80 -d web1:v1.5
- 通过浏览器可以访问到我们的 web 站点:
3.3.14 USER指令
(1)功能:
- 用于指定运行 image 时的或运行 Dockerfile 中任何 RUN、 CMD 或ENTRYPOINT 指令定的程序时的用户名或 UID。
- 默认情况下, container 的运行身份为 root 用户。
(2)语法:
USER <user>[:<group>]
USER <UID>[:<GID>]
(3)参数:
- user:用户。
- group:用户组。
- uid:用户 id。
- gid:组 id。
(4)注意事项:
- <UID>可以为任意数字,但实践中其必须为/etc/passwd 中某用户的有效
UID,否则将运行失败。
(5)样例:
USER docker:docker
(6)实战:
- user 用于指定后续命令的运行用户,通常我们的程序建议不要直接用 root 用户操作。
- 我们创建一个目录:
mkdir -p /data/myworkdir/dockerfile/user
- 创建 Dockerfile,添加以下内容:
FROM ubuntu:22.04 as buildbase
RUN groupadd nginx
RUN useradd nginx -g nginx
USER nginx:nginx
RUN whoami > /tmp/user1.txt
USER root:root
RUN groupadd mysql
RUN useradd mysql -g mysql
USER mysql:mysql
RUN whoami > /tmp/user2.txt
- 执行编译:
docker build -t user:v0.1 .
- 运行查看我们的用户:
[xiaomaker@xiaomaker-virtual-machine:user]$ docker run --name user1 --rm -it user:v0.1 cat /tmp/user1.txt /tmp/user2.txt nginx
mysql
docker_build_1244">3.4 制作命令docker build
(1)功能:
- docker build 命令用于使用 Dockerfile 创建镜像。
(2)语法:
docker build [OPTIONS] PATH | URL | -
(3)关键参数:
- –build-arg=[]:设置镜像创建时的变量;
- -f:指定要使用的 Dockerfile 路径;
- –label=[]:设置镜像使用的元数据;
- –no-cache:创建镜像的过程不使用缓存;
- –pull:尝试去更新镜像的新版本;
- –quiet, -q:安静模式,成功后只输出镜像 ID;
- –tag, -t:镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。
- –network:默认 default。在构建期间设置 RUN 指令的网络模式
(4)样例:
docker build -t mynginx:v1 .
4. Dockerfile 编写优秀实践
(1)善用.dockerignore 文件:
- 使用它可以标记在执行 docker build 时忽略的路径和文件, 避免发送不必要的数据内容,从而加快整个镜像创建过程。
(2)镜像的多阶段构建:
- 通过多步骤创建,可以将编译和运行等过程分开,保证最终生成的镜像只包括运行应用所需要的最小化环境。当然,用户也可以通过分别构造编译镜像和运行镜像来达到类似的结果,但这种方式需要维护多个 Dockerfile。
(3)合理使用缓存:
- 如合理使用 cache,减少内容目录下的文件, 内容不变的指令尽量放在前面,这样可以尽量复用;
(4)基础镜像尽量使用官方镜像,并选择体积较小镜像:
- 容器的核心是应用,大的平台微服务可能几十上百个。选择过大的父镜像(如 Ubuntu系统镜像)会造成最终生成应用镜像的臃肿, 推荐选用瘦身过的应用镜像(如node:slim),或者较为小巧的系统镜像(如 alpine、 busybox 或 debian);
(5)减少镜像层数:
- 如果希望所生成镜像的层数尽量少,则要尽量合并 RUN、 ADD 和 COPY 指令。通常情况下,多个 RUN 指令可以合并为一条 RUN 指令;如 apt get update&&apt install 尽量写到一行。
(6)精简镜像用途:
- 尽量让每个镜像的用途都比较集中单一,避免构造大而复杂、多功能的镜像;
(7)减少外部源的干扰:
- 如果确实要从外部引入数据,需要指定持久的地址,并带版本信息等,让他人可以复用而不出错。
(8)减少不必要的包安装:
- 只安装需要的包,不要安装无用的包,减少镜像体积。
5. 正确使用 CMD 和 EntryPoint
5.1 基础知识
(1)ENTRYPOINT 和 CMD的简介:
- ENTRYPOINT 和 CMD 都是在 docker image 里执行一条命令, 但是他们有一些微妙的区别。一般来说两个大部分功能是相似的都能满足。
- 比如执行运行一个没有调用 ENTRYPOINT 或者 CMD 的 docker 镜像, 返回错误,一般的镜像最后都提供了 CMD 或者 EntryPoint 作为入口。
(2)覆盖:
- 在写 Dockerfile 时,ENTRYPOINT 或者 CMD 命令会自动覆盖之前的 ENTRYPOINT或者 CMD 命令。
- 在 docker 镜像运行时,用户也可以在命令指定具体命令,覆盖在 Dockerfile 里的命令。
- 如果你希望你的 docker 镜像只执行一个具体程序,不希望用户在执行 docker run 的时候随意覆盖默认程序。建议用 ENTRYPOINT。
(3)Shell 和 Exec 模式:ENTRYPOINT 和 CMD 指令支持 2 种不同的写法。shell 表示法和 exec 表示法。
- CMD 命令语法:
# EXEC 语法
CMD ["executable","param1","param2"] (exec form, this is the
preferred form)
#用于给 ENTRYPOINT 传入参数,推荐使用
CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
#shell 语法
CMD command param1 param2 (shell form)
- ENTRYPOINT 语法:
# EXEC 语法
ENTRYPOINT ["executable", "param1", "param2"] (exec form,
preferred)
# Shell 语法
ENTRYPOINT command param1 param2 (shell form)
- 当使用 shell 表示法时,命令行程序作为 sh 程序的子程序运行,docker 用/bin/sh -c 的语法调用。如果我们用 docker ps 命令查看运行的 docker,就可以看出实际运行的是/bin/sh -c 命令。这样运行的结果就是我们启动的程序的 PID 不是 1,如果从外部发送任何 POSIX 信号到 docker 容器,由于/bin/sh 命令不会转发消息给实际运行的命令,则不能安全得关闭 docker 容器。
- EXEC 语法没有启动/bin/sh 命令,而是直接运行提供的命令,命令的 PID 是 1。无论你用的是 ENTRYPOINT 还是 CMD 命令,都强烈建议采用 exec 表示法。
(4)组合模式:
- 组合使用 ENTRYPOINT 和 CMD,ENTRYPOINT 指定默认的运行命令,CMD 指定默认的运行参数。ENTRYPOINT 和 CMD 同时存在时,docker 把 CMD 的命令拼接到ENTRYPOINT 命令之后,拼接后的命令才是最终执行的命令。
5.2 实战步骤
5.2.1 覆盖
(1)多次覆盖:
- 我们创建一个 Dockerfile,指定多个 CMD,创建目录如下:
mkdir -p /data/myworkdir/dockerfile/cmd
# 创建 Dockerfile1
FROM busybox
CMD echo "hello world"
CMD echo "hello bit"
- 然后编译镜像,运行查看结果,可以看到第一个 CMD 被覆盖了:
docker build -t cmd1:v0.1 -f Dockerfile1 .
[xiaomaker@xiaomaker-virtual-machine:cmd]$ docker run cmd1:v0.1
hello bit
- 我们创建一个 Dockerfile2,指定多个 EntryPoint 然后运行查看结果:
FROM busybox
ENTRYPOINT echo "hello world"
ENTRYPOINT echo "hello bit"
- 编译构建,运行查看结果:
docker build -t cmd1:v0.2 -f Dockerfile2 .
[xiaomaker@xiaomaker-virtual-machine:cmd]$ docker run cmd1:v0.2
hello bit
(2)参数覆盖:
- 我们通过指定后面的启动参数,可以覆盖 CMD 的指令,但是 EntryPoint 的无法覆盖,执行效果如下:
[xiaomaker@xiaomaker-virtual-machine:cmd]$ docker run cmd1:v0.1 echo hello bit2
hello bit2
[xiaomaker@xiaomaker-virtual-machine:cmd]$ docker run cmd1:v0.2 echo hello bit2
hello bit
- 如果我们指定参数–entrypoint 就可以完成对 entrypoint 的替换:
[xiaomaker@xiaomaker-virtual-machine:cmd]$ docker run --entrypoint '/bin/sh' cmd1:v0.2 -c "echo hello bit2"
hello bit2
5.2.2 Shell VS Exec
- 我们编写下面的 Dockerfile3,执行 ping 命令:
FROM ubuntu:22.04
RUN apt-get update -y && apt install -y iputils-ping
CMD ping localhost
- 然后编译镜像,运行:
docker build -t cmd1:v0.3 -f Dockerfile3 .
docker run --name shell1 -d cmd1:v0.3
- 进入镜像里面查看,可以看到 PID 为 1 的其实是/bin/sh:
[xiaomaker@xiaomaker-virtual-machine:cmd]$ docker exec -it shell1 bash
root@3f0c00e8dbb4:/# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 13:41 ? 00:00:00 /bin/sh -c
ping localhost
root 6 1 0 13:41 ? 00:00:00 ping localhost
root 7 0 1 13:41 pts/0 00:00:00 bash
root 14 7 0 13:41 pts/0 00:00:00 ps -ef
- 我们新建 Dockerfile4,这个里面我们采用 EXEC 模式:
FROM ubuntu:22.04
RUN apt-get update -y && apt install -y iputils-ping
CMD ["ping", "localhost"]
- 我们编译镜像,然后运行镜像:
docker build -t cmd1:v0.4 -f Dockerfile4 .
docker run --name shell2 -d cmd1:v0.4
- 我们进入镜像查看,可以看到 PID 为 1 的进程是 ping 而不再是我们的/bin/sh:
[xiaomaker@xiaomaker-virtual-machine:cmd]$ docker exec -it shell2 bash
root@c7d04f6cd184:/# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 13:46 ? 00:00:00 ping localhost
root 6 0 0 13:46 pts/0 00:00:00 bash
root 13 6 0 13:47 pts/0 00:00:00 ps -ef
root@c7d04f6cd184:/#
- EntryPoint 也是一样大家可以尝试下。无论你用的是 ENTRYPOINT 还是 CMD 命令,都强烈建议采用 exec 表示法。
5.2.3 组合
- 我们新建 Dockerfile5,同时设置 ENTRYPOINT 和 CMD:
FROM ubuntu:22.04
RUN apt-get update -y && apt install -y iputils-ping
ENTRYPOINT ["/bin/ping","-c","3"]
CMD ["localhost"]
- 此时我们编译镜像然后启动运行看下效果是什么,可以看到 CMD 的内容作为ENTRYPOINT 的参数添加到了后面:
docker build -t cmd1:v0.5 -f Dockerfile5 .
[xiaomaker@xiaomaker-virtual-machine:cmd]$ docker run --name shell3 --rm cmd1:v0.5
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.113ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.032ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.032ms
- 因为 CMD 的内容可以被替换,如果我们运行的时候替换成另外的一个网站,我们可以看到他会 ping 的是另外一个网站,也就是说 CMD 的参数被替换掉了。
[xiaomaker@xiaomaker-virtual-machine:cmd]$ docker run --name shell3 --rm cmd1:v0.5 www.baidu.com
PING www.a.shifen.com (14.119.104.189) 56(84) bytes of data.
64 bytes from 14.119.104.189 (14.119.104.189): icmp_seq=1 ttl=53 time=4.67 ms
64 bytes from 14.119.104.189 (14.119.104.189): icmp_seq=2 ttl=53 time=4.54 ms
64 bytes from 14.119.104.189 (14.119.104.189): icmp_seq=3 ttl=53 time=4.55 ms
--- www.a.shifen.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 4.536/4.585/4.668/0.059 ms
6. 合理使用 dokerignore
6.1 基础知识
- Docker 是 C-S 架构,理论上 Client 和 Server 可以不在一台机器上。在构建 docker 镜像的时候,需要把所需要的文件由 Client 发送给 Server,这些要发送的文件叫做build context。
- 在我们之前讲到的构建镜像的命令, 在命令的最后我们加了一个.表示当前目
录,这个目录其实就代表了 build context 所指向的目录。
docker build -f <dockerfile> -t <dockerfile_name> .
- 如果想忽略掉一些传送给 Sever 端的文件, 这就会用到.dockerignore 文件。它会将记录的所有文件都忽略掉, 不会传送给 Server 端, 有效的避免一些和容器内应用运行无关的文件不会被复制到 Server 端, 即不会将无关的文件打入生成的镜像中。
6.2 实战步骤
(1)建立 Dockerfile,拷贝当前目录的所有文件到容器的根目录下
[xiaomaker@xiaomaker-virtual-machine:dockerfile]$ cat dockerfile
# 选择基础镜像
FROM centos:7
# 拷贝当前目录下所有文件到容器根目录
COPY ./* /
(2)建立 .dockerignore 文件, 忽略以.txt 为后缀的文件:
[xiaomaker@xiaomaker-virtual-machine:dockerfile]$ cat .dockerignore *.txt
(3)创建文件并查看文件目录:
[xiaomaker@xiaomaker-virtual-machine:dockerfile]$ tree ./
|-- 1.txt
|-- 2.txt
|-- 3.doc
|-- dockerfile
0 directories, 4 files
(4)构建镜像并查看结果:可以发现 1.txt 和 2.txt 已经被忽略掉了,只有 3.doc 拷贝和 dockerfile 拷贝成功。
[xiaomaker@xiaomaker-virtual-machine:dockerfile]$ docker build -f ./dockerfile -t
test_ignore:1.0 ./
Sending build context to Docker daemon 4.096kB
Step 1/2 : FROM centos
---> 5d0da3dc9764
Step 2/2 : COPY ./* /
---> 385dce113b52
Successfully built 385dce113b52
Successfully tagged test_ignore:1.0
[xiaomaker@xiaomaker-virtual-machine:dockerfile]$ docker container run -it test_ignore:1.0
[root@fc094479d014 /]# ls
3.doc bin dev dockerfile etc home lib lib64
lost+found media mnt opt proc root run sbin
srv sys tmp usr var
7. 多阶段构建
7.2 基础知识
(1)构建 docker 镜像可以有下面两种方式:
- 将全部组件及其依赖库的编译、测试、打包等流程封装进一个 docker 镜像中。但是这种方式存在一些问题, 比如 Dockefile 特别长,可维护性降低;镜像的层次多,体积大,部署时间长等问题
- 将每个阶段分散到多个 Dockerfile。一个 Dockerfile 负责将项目及其依赖库
编译测试打包好后,然后将运行文件拷贝到运行环境中,这种方式需要我们编写多个Dockerfile 以及一些自动化脚本才能将其两个阶段自动整合起来
(2)为了解决以上的两个问题, Docker 17.05 版本开始支持多镜像阶段构建。只需要编写一个 Dockerfile 即可解决上述问题。
7.2 实战步骤
(1)下面我们做个实验来验证一下:假如现在有一个 C 语言程序,我们想用 Docker 帮我们编译成可执行文件,然后执行该可执行文件。
#include <stdio.h>
int main()
{printf("hello docker!\n");return 0;
}
(2)编写 Dockerfile,因为要有 C 语言的编译环境,我们需要安装 gcc,这次我们选用centos:7:
FROM centos:7
# 设置版本
ENV VERSION 1.0
#设置国内源
RUN sed -e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^#baseurl=http://mirror.centos.org/centos|baseurl=https://mirro
rs.ustc.edu.cn/centos|g' \
-i.bak \
/etc/yum.repos.d/CentOS-Base.repo
RUN yum makecache
# 设置工作目录
WORKDIR /src
# 拷贝源文件
COPY demo.c .
# 安装 gcc
RUN yum install -y gcc && \
gcc -v
# 编译源文件
RUN gcc demo.c -o demo && \
rm -f demo.c && \
yum erase -y gcc
# 运行可执行文件
CMD ["/src/demo"]
(2)构建这个 Docker 镜像, 并且创建容器测试结果:
# 通过 Dockerfile 生成镜像 1.0
[xiaomaker@xiaomaker-virtual-machine:c_demo]$ docker build -t multi:v1.0 .
# 创建容器, 测试结果
[xiaomaker@xiaomaker-virtual-machine:c_demo]$ docker container run --name multi1 --rm -it multi:v1.0
hello docker!
# 查看镜像信息
[xiaomaker@xiaomaker-virtual-machine:c_demo]$ docker image ls | grep multi
REPOSITORY TAG IMAGE ID CREATED SIZE
cpp v0.2 9097d5ddcbc3 About a minute ago 904MB
从结果可以看到生成镜像非常的大,达到 900MB+。
(3)实际上当我们把 test.c 编译完以后,并不需要一个大的 GCC 编译环境,一个小的运行环境镜像即可。这时候我们就可以使用多阶段构建解决这个问题。
# 第一阶段构建
# AS 给第一阶段镜像取别名
FROM centos:7 as basebuilder
# 设置版本
ENV VERSION 1.0
#设置国内源
RUN sed -e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^#baseurl=http://mirror.centos.org/centos|baseurl=https://mirro
rs.ustc.edu.cn/centos|g' \
-i.bak \
/etc/yum.repos.d/CentOS-Base.repo
RUN yum makecache
# 设置工作目录
WORKDIR /src
# 拷贝源文件
COPY demo.c .
# 安装 gcc
RUN yum install -y gcc && \
gcc -v
# 编译源文件
RUN gcc demo.c -o demo && \
rm -f demo.c && \
yum erase -y gcc
# 运行可执行文件
CMD ["/src/demo"]
# 第二阶段构建
FROM centos:7
# 拷贝第一阶段生成的可执行程序
COPY --from=basebuilder /src/demo /src/demo
# 运行可执行程序
CMD ["/src/demo"]
(4)构建镜像并创建容器测试结果:
# 通过 Dockerfile 生成镜像 2.0
[xiaomaker@xiaomaker-virtual-machine:c_demo]$ docker image build -t multi:2.0 .
# 通过 Dockerfile 生成镜像 2.0
[xiaomaker@xiaomaker-virtual-machine:c_demo]$ docker run --name multi2 --rm multi:v2.0
hello docker!
# 查看镜像信息
[xiaomaker@xiaomaker-virtual-machine:c_demo]$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
multi v2.0 4596e8b0755d About a minute ago 204MB
可以看到这个生成的镜像是 204MB。
(5)我们进一步优化可以选取较小的基础镜像像 alpine, busybox, debian 都有很小的体积,我们以 busybox 为例再次构建, Dockerfile 如下:
# 第一阶段构建
# AS 给第一阶段镜像取别名
FROM centos:7 as basebuilder
# 设置版本
ENV VERSION 1.0
#设置国内源
RUN sed -e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^#baseurl=http://mirror.centos.org/centos|baseurl=https://mirro
rs.ustc.edu.cn/centos|g' \
-i.bak \
/etc/yum.repos.d/CentOS-Base.repo
RUN yum makecache
# 设置工作目录
WORKDIR /src
# 拷贝源文件
COPY demo.c .
# 安装 gcc
RUN yum install -y gcc && \
gcc -v
# 编译源文件
RUN gcc demo.c -o demo && \
rm -f demo.c && \
yum erase -y gcc
# 运行可执行文件
CMD ["/src/demo"]
# 第二阶段构建
FROM busybox
# 拷贝第一阶段生成的可执行程序
COPY --from=basebuilder /src/demo /src/demo
# 运行可执行程序
CMD ["/src/demo"]
(6)构建镜像并创建容器测试结果,可以看到我们将镜像已经缩小到了不到 5MB:
# 通过 Dockerfile 生成镜像 3.0
[xiaomaker@xiaomaker-virtual-machine:c_demo]$ docker image build -t multi:3.0 .
# 通过 Dockerfile 生成镜像 3.0
[xiaomaker@xiaomaker-virtual-machine:c_demo]$ docker run --name multi3 --rm multi:v3.0
hello docker!
# 查看镜像信息
[xiaomaker@xiaomaker-virtual-machine:c_demo]$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
multi v3.0 a09ad0559775 2 minutes ago 4.87MB
多阶段构建可以很好的解决镜像的层次多,体积大,部署时间长、维护性低等问题。
(7)对多阶构建为什么节省了空间?
- 因为我们编译使用的软件,都没有打到我们的运行态的软件里面,所以可以变得更小。
8. 合理使用缓存
8.1 基础知识
(1)缓存:
- 在镜像的构建过程中, Docker 会根据 Dockerfile 指定的顺序执行每个指令。在执行每条指令之前, Docker 都会在缓存中查找是否已经存在可重用的镜像,如果有就使用现存的镜像,不会再重复创建。
- 在上边提到 Dockerfile 中的每一条指令都会产生一层 image layer。当某一个layer 修改后,后面的所有 layer 我们都不能使用缓存, 这一点一定要注意。
- 如果不想在构建过程中使用缓存,你可以在 docker build 命令中使用 --nocache=true 选项。但是我们建议最好合理使用缓存, 这样可以加快构建镜像的效率。
8.2 实战步骤
(1)还是之前 C 语言的例子, 假如现在有一个 C 语言程序,我们想用 Docker 帮我们编译成可执行文件,然后执行该可执行文件。
#include <stdio.h>
int main()
{printf("hello docker!\n");return 0;
}
(2)编写 Dockerfile:
# 选择基础镜像
FROM centos:7
# 设置工作目录
WORKDIR /src
# 拷贝源文件
COPY demo.c .
#设置国内源
RUN sed -e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^#baseurl=http://mirror.centos.org/centos|baseurl=https://m
irrors.ustc.edu.cn/centos|g' \
-i.bak \
/etc/yum.repos.d/CentOS-Base.repo
RUN yum makecache
# 安装 gcc
RUN yum install -y gcc
# 编译源文件并删除源文件及 gcc 工具
RUN gcc demo.c -o demo && \
rm -f demo.c && \
yum erase -y gcc
# 运行可执行文件
CMD ["/src/demo"]
(3)构建镜像:
[xiaomaker@xiaomaker-virtual-machine:cache]$ docker image build -t cache:1.0 .
[+] Building 80.3s (13/13) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 584B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/centos:7 1.8s
=> [auth] library/centos:pull token for registry-1.docker.io 0.0s
=> [1/7] FROM
docker.io/library/centos:7@sha256:be65f488b7764ad3638f236b7b55b3678369a5124c47b8d32916d6487418ea4 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 27B 0.0s
=> CACHED [2/7] WORKDIR /src 0.0s
=> [3/7] COPY demo.c . 0.1s
=> [4/7] RUN sed -e 's|^mirrorlist=|#mirrorlist=|g'
-e 's|^#baseurl=http://mirror.centos.org/centos|baseurl=https://m
irrors.ustc.edu.cn/centos|g' -i.bak 0.4s
=> [5/7] RUN yum makecache 49.0s
=> [6/7] RUN yum install -y gcc 21.9s
=> [7/7] RUN gcc demo.c -o demo && rm -f demo.c && yum erase -y gcc 1.9s
=> exporting to image 4.9s
=> => exporting layers 4.9s
=> => writing image
sha256:4aca2a80d5302830d3ca55e6fbb6f0ddf99820a19cbe75c0af371bcc62107559 0.0s
=> => naming to docker.io/library/cache:1.0
可以发现由于是我们第一次构建镜像, 并没有使用到缓存,构建了整整 80s
(4)改变源代码文件重新创建镜像:
# 修改源文件输出 hello bit
[xiaomaker@xiaomaker-virtual-machine:cache_demo]$ cat demo.c
#include <stdio.h>
int main()
{printf("hello bit!\n");return 0;
}# 重新构建镜像
[xiaomaker@xiaomaker-virtual-machine:cache]$ docker image build -t cache:2.0 .
- 从上边的结果看起来这次依然没有用到缓存,时间还变长了变成了 85s, 还是要重新安装 gcc。那不对呀, 不是说在构建之前会查找可用的缓存吗, 按道理我们之前构建过, 这次应该能用到上次的缓存, 怎么没用到呢?这是因为我们修改了源文件 demo.c, 下面的所有指令都需要去重新构建。 那我们怎么避免这种情况呢?
- 可以调整 Dockerfile 命令执行的顺序,建议将不经常修改的内容放在 Dockerfile的前边, 经常修改的内容放在后边。
(5)调整 Dockerfile 的顺序:
FROM centos:7
#设置国内源
RUN sed -e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^#baseurl=http://mirror.centos.org/centos|baseurl=https://m
irrors.ustc.edu.cn/centos|g' \
-i.bak \
/etc/yum.repos.d/CentOS-Base.repo
RUN yum makecache
# 安装 gcc
RUN yum install -y gcc
# 设置工作目录
WORKDIR /src
# 拷贝源文件
COPY demo.c .
# 编译源文件并删除源文件及 gcc 工具
RUN gcc demo.c -o demo && \
rm -f demo.c && \
yum erase -y gcc
# 运行可执行文件
CMD ["/src/demo"]
(6)构建镜像:
# 修改 dockerfile 之后第一次构建镜像依然不会使用到缓存
[xiaomaker@xiaomaker-virtual-machine:cache]$ docker image build -t cache:3.0 .# 修改源文件 demo.c
[xiaomaker@xiaomaker-virtual-machine:cache_demo]$ cat demo.c
#include <stdio.h>
int main()
{printf("hello docker!\n");return 0;
}# 重新构建镜像
[xiaomaker@xiaomaker-virtual-machine:cache]$ docker image build -t cache:4.0 .
- 在重新构建镜像时发现此时已经可以使用缓存, 构建镜像的效率变得非常之高,我们只需要不到 3s 就可以完成构建了。
docker_composemysql_1843">9. Dockerfile结合docker compose搭建mysql主从同步
9.1 基础知识
(1)Docker compose的介绍见博客《Docker的容器编排》。这里就不在详谈了。
(2)什么是 mysql 主从同步??
- MySQL 主从复制是指数据可以从一个 MySQL 数据库服务器主节点复制到一个或多个从节点。 MySQL 默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可以在远程连接上进行,从节点可以复制主数据库中的所有数据库或者特定的数据库,或者特定的表。
(2)为什么要 mysql 主从同步??
- 读写分离,性能提升:让主库负责写,从库负责读,这样,即使主库出现了锁表的情景,通过读从库也可以保证业务的正常运作。扩展架构提升读写能力。
- 数据实时备份:主库数据实时保存到存库,万一主库故障也有从库的数据备份。
- 高可用 HA:当系统中某个节点发生故障时,可以方便的故障切换。
(3)主从同步的架构图:
- 在主从复制的过程中,会基于三个线程来操作,一个是 binlog dump 线程,位于master 节点上,另外两个线程分别是 I/O 线程和 SQL 线程,它们都分别位于 slave 节点上。
- 当 master 节点接收到一个写请求时,这个写请求可能是增删改操作,此时会把写请求的更新操作都记录到 binlog 日志中。
- master 节点会把数据复制给 slave 节点,如图中的 slave01 节点和 slave02 节点,这个过程,首先得要每个 slave 节点连接到 master 节点上,当 slave 节点连接到master 节点上时, master 节点会为每一个 slave 节点分别创建一个binlog dump 线程,用于向各个 slave 节点发送 binlog 日志。
- binlog dump 线程会读取 master 节点上的 binlog 日志,然后将 binlog 日志发送给slave 节点上的 I/O 线程。当主库读取事件的时候,会在 Binglog 上加锁,读取完成之后,再将锁释放掉。
- slave 节点上的 I/O 线程接收到 binlog 日志后,会将 binlog 日志先写入到本地的relaylog 中, relaylog 中就保存了 binlog 日志。
- slave 节点上的 SQL 线程,会来读取 relaylog 中的 binlog 日志,将其解析成具体的增删改操作,把这些在 master 节点上进行过的操作,重新在 slave 节点上也重做一遍,达到数据还原的效果,这样就可以保证 master 节点和 slave 节点的数据一致性了。
主从同步的数据内容其实是二进制日志(Binlog),它虽然叫二进制日志,实际上存储的是一个又一个的事件(Event),这些事件分别对应着数据库的更新操作,比如INSERT、 UPDATE、 DELETE 等。
(4)什么是 binlog:
- 主库每提交一次事务,都会把数据变更,记录到一个二进制文件中,这个二进制文件就叫 binlog。需注意:只有写操作才会记录至 binlog,只读操作是不会的(如 select、show 语句)。Bin Log 共有三种日志格式,可以 binlog_format 配置参数指定。
参数值 | 含义 |
---|---|
Statement | 记录原始 SQL 语句,会导致更新时间与原库不一致。比如 update_time=now() |
Row | 记录每行数据的变化,保证了数据与原库一致,缺点是数据量较大。 |
Mixed | Statement 和 Row 的混合模式,默认采用 Statement 模式,涉及日期、函数相关的时候采用 Row 模式,既减少了数据量,又保证了数据一致性。 |
9.2 主从同步的方式
(1)全同步方式:就是当主库执行完一个事务之后,要求所有的从库也都必须执行完该事务,才可以返回处理结果给客户端;因此,虽然全同步复制数据一致性得到保证了,但是主库完成一个事务需要等待所有从库也完成,性能就比较低了,从库如果挂了主库也受影响。
(2)异步同步:默认方式,主库在执行完客户端提交的事务后会立即将结果返给给客户端,并不关心从库是否已经接收并处理。
- 因为主库只管自己执行完事务,就可以将处理结果返回给客户端,而不用关心从库是否执行完事务,这就可能导致短暂的主从数据不一致的问题了,比如刚在主库插入的新数据,如果马上在从库查询,就可能查询不到。
- 而且,当主库提交事物后,如果宕机挂掉了,此时可能 binlog 还没来得及同步给从库,这时候如果为了恢复故障切换主从节点的话,就会出现数据丢失的问题,所以异步复制虽然性能高,但数据一致性上是最弱的。mysql 主从复制,默认采用的就是异步复制这种复制策略。
(3)半同步:基于传统异步存在的缺陷, mysql 在 5.5 版本推出半同步复制。可以说半同步复制是传统异步复制的改进,在 master 事务的 commit 之前,必须确保一个slave 收到 relay log 并且响应给 master 以后,才能进行事务的 commit。相当于添加多了一个从库反馈机制。在 MySQL5.7 版本中还增加了一个rpl_semi_sync_master_wait_for_slave_count 参数,我们可以对需要响应的从库数量进行设置,默认为 1,也就是说只要有一个从库进行了响应,就可以返回给客户端。
- 如果将这个参数调大,可以提升数据一致性的强度,但也会增加主库等待从库响应的时间。
- 对应配置参数为 rpl_semi_sync_master_wait_point=after_commit。核心流程为,主库执行完事务后,主库提交 commit ,同步 binlog 给从库,从库 ack 反馈接收到binlog,反馈给客户端,释放会话; (主库生成 binlog,主库提交,再同步 binlog,等ACK 反馈,返回客户端)
(4)半同步复制存在以下几个问题:
- 半同步复制的性能,相比异步复制而言有所下降,相比于异步复制是不需要等待任何从库是否接收到数据的响应,而半同步复制则需要等待至少一个从库确认接收到 binlog 日志的响应,性能上是损耗更大的。
- 主库等待从库响应的最大时长是可以配置的,如果超过了配置的时间,半同步复制就会变成异步复制,那么,异步复制的问题同样也就会出现了。
- 半同步复制存在着幻读问题的:当主库成功提交事物并处于等待从库确认的过程中,这个时候,从库都还没来得及返回处理结果给客户端,但因为主库存储引擎内部已经提交事务了,所以,其他客户端是可以到从主库中读到数据的。但是,如果下一秒主库突然挂了,此时正好下一次请求过来,因为主库挂了,就只能把请求切换到从库中,因为从库还没从主库同步完数据,所以,从库中当然就读不到这条数据了,和上一秒读取数据的结果对比,就造成了幻读的现象了。
(5)增强半同步复制:是对半同步复制做的一个改进,原理上几乎是一样的,主要是解决幻读的问题。
- 主库配置了参数 rpl_semi_sync_master_wait_point = AFTER_SYNC 后,主库在存储引擎提交事务前,必须先收到从库数据同步完成的确认信息后,才能提交事务,以此来解决幻读问题。
- 核心流程为主库执行完事务后,同步 binlog 给从库,从库 ack 反馈接收到 binlog,主库提交 commit,反馈给客户端,释放会话; (主库生成 binlog,再同步 binlog,等ACK 反馈,主库提交,返回客户端)但是 slave 对于 relay log 的应用仍然是异步进行的。
(6)组复制:MySQL 官方在 5.7.17 版本正式推出组复制(MySQL Group Replication,简称MGR)。
- 由若干个节点共同组成一个复制组,一个事务的提交,必须经过组内大多数节点(N / 2 + 1)决议并通过,才能得以提交。如上图所示,由 3 个节点组成一个复制组,Consensus 层为一致性协议层,在事务提交过程中,发生组间通讯,由 2 个节点决议(certify)通过这个事务,事务才能够最终得以提交并响应。
- 引入组复制,主要是为了解决传统异步复制和半同步复制可能产生数据不一致的问题。组复制依靠分布式一致性协议(Paxos 协议的变体),实现了分布式下数据的最终一致性,提供了真正的数据高可用方案(是否真正高可用还有待商榷)。其提供的多写方案,给我们实现多活方案带来了希望。
- MGR 的解决方案有一定的局限性,如仅支持 InnoDB 表,并且每张表一定要有一个主键,用于做 write set 的冲突检测;开启 GTID 特性等。
9.3 MySQL 主从形式
(1)一主一从:
(2)一主多从,提高系统的读性能:
一主一从和一主多从是最常见的主从架构,实施起来简单并且有效,不仅可以实现 HA,而且还能读写分离,进而提升集群的并发能力。
(3)多主一从:多主一从可以将多个 mysql 数据库备份到一台存储性能比较好的服务器上。
(4)双主复制:双主复制,也就是互做主从复制,每个 master 既是 master,又是另外一台服务器的slave。这样任何一方所做的变更,都会通过复制应用到另外一方的数据库中。
(5)级联复制:级联复制模式下,部分 slave 的数据同步不连接主节点,而是连接从节点。因为如果主节点有太多的从节点,就会损耗一部分性能用于 replication,那么我们可以让 3~5个从节点连接主节点,其它从节点作为二级或者三级与从节点连接,这样不仅可以缓解主节点的压力,并且对数据一致性没有负面影响。
9.4 mysql 主从集群搭建步骤
(1)创建目录:
mkdir -p /data/maxhou/mysqlcluster/
mkdir -p /data/maxhou/mysqlcluster/master/
mkdir -p /data/maxhou/mysqlcluster/slave/
(2)进入目录/data/maxhou/mysqlcluster/master,创建主库 Dockerfile 文件为Dockerfile-master:
FROM mysql:5.7
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
COPY ./master/master.sql /docker-entrypoint-initdb.d
(3)创建主库配置脚本 master.sql:
CREATE USER 'root'@'%' IDENTIFIED BY 'root';
grant replication slave, replication client on *.* to 'root'@'%';
flush privileges;
(4)进入目录/data/maxhou/mysqlcluster/slave/,创建从库 Dockerfile,文件为Dockerfile-slave:
FROM mysql:5.7
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
COPY ./slave/slave.sql /docker-entrypoint-initdb.d
(5)创建从库配置脚本 slave.sql:
change master to master_host='mysql-master',
master_user='root',master_password='root',master_port=3306;
start slave;
(6)进入/data/maxhou/mysqlcluster 目录,创建 docker-compose.yml 配置文件:
- server-id:服务的id。
- log-bin:打开二进制日志功能,配置 binlog 文件名。
- binlog-ignore-db:配置忽略的数据库。
- binlog_cache_size:在一个事务中 binlog 为了记录 SQL 状态所持有的 cache大小,如果经常使用大事务,可以增加此值来获取更大的性能。
- binlog_format:ROW/STATEMENT/MIXED。
- lower_case_table_names:表采用小写。
- character-set-server:配置字符集。
- collation-server:配置比较规则。
version: "3"
services:
mysql-master:
build:
context: ./
dockerfile: ./master/Dockerfile-master
image: mysqlmaster:v1.0
restart: always
container_name: mysql-master
volumes:
- ./mastervarlib:/var/lib/mysql
ports:
- 9306:3306
environment:
MYSQL_ROOT_PASSWORD: root
privileged: true
command: ['--server-id=1',
'--log-bin=master-bin',
'--binlog-ignore-db=mysql',
'--binlog_cache_size=256M',
'--binlog_format=mixed',
'--lower_case_table_names=1',
'--character-set-server=utf8',
'--collation-server=utf8_general_ci']
mysql-slave:
build:
context: ./
dockerfile: ./slave/Dockerfile-slave
image: mysqlslave:v1.0
restart: always
container_name: mysql-slave
volumes:
- ./slavevarlib:/var/lib/mysql
ports:
- 9307:3306
environment:
MYSQL_ROOT_PASSWORD: root
privileged: true
command: ['--server-id=2',
'--relay_log=slave-relay',
'--lower_case_table_names=1',
'--character-set-server=utf8',
'--collation-server=utf8_general_ci']
depends_on:
- mysql-master
mysql-slave2:
build:
context: ./
dockerfile: ./slave/Dockerfile-slave
image: mysqlslave:v1.0
restart: always
container_name: mysql-slave2
volumes:
- ./slavevarlib2:/var/lib/mysql
ports:
- 9308:3306
environment:
MYSQL_ROOT_PASSWORD: root
privileged: true
command: ['--server-id=3',
'--relay_log=slave-relay',
'--lower_case_table_names=1',
'--character-set-server=utf8',
'--collation-server=utf8_general_ci']
depends_on:
- mysql-master
(7)构建镜像:
[xiaomaker@xiaomaker-virtual-machine:mysqlcluster]$ docker compose build
(8)启动服务进行测试:
[xiaomaker@xiaomaker-virtual-machine:mysqlcluster]$ docker compose up -d
(9)状态查看正常:
[xiaomaker@xiaomaker-virtual-machine:mysqlcluster]$ docker compose ps
(10)连上主库,查看数据库可以看到运行正常:
[xiaomaker@xiaomaker-virtual-machine:mysqlcluster]$ docker exec -it mysql-master bash
root@bd3f9f453628:/# mysql -p
Enter password:
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.00 sec)
(11)查看数据库角色,和同步状态,连接上从库,在主库和从库上都可以执行这 2 个命令:
#查看命令
SHOW MASTER STATUS\G
SHOW SLAVE STATUS\G
(12)主库上创建数据库:
mysql> create database test;
Query OK, 1 row affected (0.00 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
+--------------------+
(13)连接上任意一个从库,查看从库上数据库自动创建:
[xiaomaker@xiaomaker-virtual-machine:mysqlcluster]$ docker exec -it mysql-slave bash
root@d7cedf19e729:/# mysql -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.36 MySQL Community Server (GPL)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current
input statement.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
+--------------------+
(14)主库上创建表,插入数据:
mysql> create table users(sno int,sname varchar(20));
Query OK, 0 rows affected (0.03 sec)
mysql> insert into users values (1,'pony');
Query OK, 1 row affected (0.02 sec)
mysql> insert into users values (2,'maxhou');
Query OK, 1 row affected (0.01 sec)
(15)查看从库上数据自动写入:
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| users |
+----------------+
1 row in set (0.00 sec)
mysql> select *from users;
+------+--------+
| uid | uname |
+------+--------+
| 1 | pony |
| 2 | maxhou |
+------+--------+
2 rows in set (0.00 sec)
至此可以看到我们搭建的 mysql 集群已经能够正常进行工作。
docker_compose__redis__2156">10. Dockerfile 结合 docker compose 搭建 redis 集群
(1)准备目录:
mkdir -p /data/maxhou/rediscluster/redis
cd /data/maxhou/rediscluster/redis
wget http://download.redis.io/releases/redis-7.0.11.tar.gz
(2)找到里面的配置文件模板, redis.conf,修改以下内容,完成配置文件创建,创建好的文件放到目录/data/maxhou/rediscluster/redis:
#表示前台运行
daemonize no
#端口
port 6379
#持久化
dir /data/redis
#启用集群
cluster-enabled yes
#集群参数配置
cluster-config-file nodes.conf
#集群超时时间
cluster-node-timeout 5000
#密码配置
requirepass 123456
#主节点密码配置
masterauth 123456
#表示远端可以连接
bind * -::*
(3)在目录/data/maxhou/rediscluster/redis,通过 vi Dockerfile 创建Dockerfile,用于构建自己的 redis 镜像:
FROM centos:7 as buildstage
RUN sed -e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^#baseurl=http://mirror.centos.org/centos|baseurl=https://mirro
rs.ustc.edu.cn/centos|g' \
-i.bak \
/etc/yum.repos.d/CentOS-Base.repo
RUN yum install -y centos-release-scl
RUN yum makecache
RUN yum install -y devtoolset-9-gcc devtoolset-9-gcc-c++
devtoolset-9-binutils make
#wget http://download.redis.io/releases/redis-7.0.11.tar.gz
ADD redis-7.0.11.tar.gz /
ADD redis.conf /redis/
WORKDIR /redis-7.0.11
RUN source /opt/rh/devtoolset-9/enable&& make
RUN mv /redis-7.0.11/src/redis-server /redis/ && mv /redis-
7.0.11/src/redis-cli /redis/
ENTRYPOINT ["/redis/redis-server", "/redis/redis.conf"]
FROM centos:7
RUN mkdir -p /data/redis && mkdir -p /redis
COPY --from=buildstage /redis /redis
EXPOSE 6379
ENTRYPOINT ["/redis/redis-server", "/redis/redis.conf"]
(4)通过 docker build -t 测试镜像构建:
[xiaomaker@xiaomaker-virtual-machine:redis]$ cd /data/maxhou/rediscluster/redis
[xiaomaker@xiaomaker-virtual-machine:mysqlcluster]$ docker build -t myredis:v1.0 .
(5)启动一个容器测试是服务能否正常运行,可以看到容器正常运行:
[xiaomaker@xiaomaker-virtual-machine:redis]$ docker run -d --name testmyredis myredis:v1.0
fd9495f93cec0f5b195df4631e03d22100c8d314634daa6d96dacde6d66a6871
(6)清理容器释放资源,因后续要启动的实例较多,要看下服务器资源是否足够:
[xiaomaker@xiaomaker-virtual-machine:redis]$ docker rm -f testmyredis
testmyredis
(7)编写 docker-compose.yml:
version: "3"
services:
redis01:
image: myredis:v1.0
build: ./redis
ports:
- 6379:6379
container_name: redis01
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis02:
image: myredis:v1.0
container_name: redis02
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis03:
image: myredis:v1.0
container_name: redis03
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis04:
image: myredis:v1.0
container_name: redis04
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis05:
image: myredis:v1.0
container_name: redis05
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis06:
image: myredis:v1.0
container_name: redis06
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis07:
image: myredis:v1.0
container_name: redis07
entrypoint: ["/redis/redis-cli","--
cluster","create","redis01:6379","redis02:6379","redis03:6379","re
dis04:6379","redis05:6379","redis06:6379","--clusterreplicas","1","-a","123456","--cluster-yes"]
depends_on:
redis01:
condition: service_healthy
redis02:
condition: service_healthy
redis03:
condition: service_healthy
redis04:
condition: service_healthy
redis05:
condition: service_healthy
redis06:
condition: service_healthy
(8)执行构建可以完成镜像构建,我们之前测试的时候已经构建过了,所以这一步非常快:
[xiaomaker@xiaomaker-virtual-machine:rediscluster]$ docker compose build
(9)启动服务:
[xiaomaker@xiaomaker-virtual-machine:rediscluster]$ docker compose up -d
[+] Running 8/8
✔ Network rediscluster_default Created 0.1s
✔ Container redis06 Healthy 15.7s
✔ Container redis01 Healthy 15.7s
✔ Container redis02 Healthy 15.7s
✔ Container redis03 Healthy 15.7s
✔ Container redis04 Healthy 15.7s
✔ Container redis05 Healthy 13.7s
✔ Container redis07 Started 16.0s
(10)查看容器状态:
[xiaomaker@xiaomaker-virtual-machine:rediscluster]$ docker compose ps -a
(11)查看 07 的日志是否创建成功:
[xiaomaker@xiaomaker-virtual-machine:rediscluster]$ docker logs -f redis07
(12)进入任意一个 01-06 的容器检查功能是否正常:
[root@37807e9bd5aa /]# /redis/redis-cli -c -a 123456
(13)释放资源:
docker compose down
docker_compose__C_2351">11. Dockerfile 结合 docker compose 搭建 C++微服务站点
(1)准备目录:
mkdir -p /data/maxhou/mydockerfile/mycppms
mkdir -p /data/maxhou/mydockerfile/mycppms/cppweb
mkdir -p /data/maxhou/mydockerfile/mycppms/nginx
(2)进入目录 cd /data/maxhou/mydockerfile/mycppms/cppweb,编写源代码main.cpp:
#include <iostream>
#include <netinet/in.h> //sockaddr_in 结构体头文件
#include <string.h> //memset()头文件
#include <assert.h> //assert()头文件
#include <unistd.h> //close()头文件
#include <pthread.h>struct pthread_data {struct sockaddr_in client_addr;int sock_fd;
};using namespace std;void* serverForClient(void* arg);
int main() {int socket_fd;int conn_fd;int res;int len;struct sockaddr_in sever_add;memset(&sever_add, 0, sizeof(sever_add)); //初始化sever_add.sin_family = PF_INET;sever_add.sin_addr.s_addr = htons(INADDR_ANY);sever_add.sin_port = htons(8081);len = sizeof(sever_add);//socket()int option = 1;socket_fd = socket(AF_INET, SOCK_STREAM, 0);assert(socket_fd >= 0);setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &option,sizeof(option));//bind()res = bind(socket_fd, (struct sockaddr*)&sever_add, len);perror("bind");assert(res != -1);//listen()res = listen(socket_fd, 1);assert(res != -1);cout << "server init" << endl;while (1) {struct sockaddr_in client;int client_len = sizeof(client);//accept()conn_fd = accept(socket_fd, (structsockaddr*)&client, (socklen_t*)&client_len);pthread_data pdata;pthread_t pt;pdata.client_addr = client;pdata.sock_fd = conn_fd;std::cout << "in " << conn_fd << endl;pthread_create(&pt, NULL, serverForClient, (void*)&pdata);}return 0;
}
void* serverForClient(void* arg) {struct pthread_data* pdata = (struct pthread_data*)arg;int conn_fd = pdata->sock_fd;std::cout << "process " << conn_fd << endl;if (conn_fd < 0) cout << "error" << endl;else {char request[1024];int len = recv(conn_fd, request, 1024, 0);if (len <= 0) {close(conn_fd);return nullptr;}request[strlen(request) + 1] = '\0';char buf[520] = "HTTP/1.1 200 ok\r\nconnection:close\r\n\r\n";//HTTP 响应int s = send(conn_fd, buf, strlen(buf), 0);//发送响应if (s <= 0) {perror("send");return nullptr;}else {char buf2[1024] = "\n""<!DOCTYPE html>\n""<html>\n""<head>\n""<title>Welcome to C++ webserver!< / title>\n""<style>\n""html { color-scheme: lightdark;}\n""body { width: 35em; margin: 0auto; \n""font-family: Tahoma, Verdana,Arial, sans - serif;}\n""</style>\n""</head>\n""<body>\n""<h1>Welcome to C++webserver!< / h1>\n""<p>If you see this page, the nginxweb server is successfully installed and \n""working. Further configuration isrequired.< / p>\n""\n""<p><em>Thank you for usingwebserver.< / em>< / p>\n""</body>\n""</html>";int s2 = send(conn_fd, buf2, strlen(buf2), 0);if (s2 <= 0) {perror("send");return nullptr;}//发送响应close(conn_fd);
}
}
return nullptr;
}
(3)在宿主机上构建:
[xiaomaker@xiaomaker-virtual-machine:mycppms]$ g++ main.cpp -o mycppweb -lpthread -std=c++11
(4)启动服务,测试服务是否能够正常运行:
(5)查看控制台:
[xiaomaker@xiaomaker-virtual-machine:mycppms]$ ./mycppweb
bind: Success
server init
in 4
process 4
(6)编写 c++应用的 Dockerfile:
FROM centos:7 as buildstage
RUN sed -e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^#baseurl=http://mirror.centos.org/centos|baseurl=https://mirro
rs.ustc.edu.cn/centos|g' \
-i.bak \
/etc/yum.repos.d/CentOS-Base.repo
RUN yum install -y centos-release-scl
RUN yum makecache
RUN yum install -y devtoolset-9-gcc devtoolset-9-gcc-c++
devtoolset-9-binutils make
WORKDIR /src
COPY ./main.cpp .
RUN source /opt/rh/devtoolset-9/enable&& g++ main.cpp -o mycppweb
-lpthread
CMD ["/src/mycppweb"]
FROM centos:7
COPY --from=buildstage /src/mycppweb /
CMD ["/mycppweb"]
(7)进入目录/data/maxhou/mydockerfile/mycppms/nginx 编写配置文件bit.conf:
upstream backend {server mycppweb:8081 weight=1;server mycppweb2:8081 weight=2;
}
server {listen 80;access_log off;location / {proxy_pass http://backend;}
}
(8)编写 nginx 的 Dockerfile:
FROM nginx:1.24.0
COPY ./bit.conf /etc/nginx/conf.d/
CMD ["nginx","-g","daemon off;"]
ENTRYPOINT ["/docker-entrypoint.sh"]
(9)进入目录/data/maxhou/mydockerfile/mycppms 编写 docker-compose.yml:
services:
web:
image: mynginx:v3.0
build:
context: ./nginx
ports:
- 8112:80
depends_on:
mycppweb:
condition: service_started
mycppweb:
build:
context: ./cppweb
image: mycppweb:v2.0
mycppweb2:
image: mycppweb:v2.0
(10)构建镜像:
[xiaomaker@xiaomaker-virtual-machine:mycppms]$ docker compose build
(11)启动服务:
[xiaomaker@xiaomaker-virtual-machine:mycppms]$ docker compose up -d
[+] Running 4/4
✔ Network mycppms_default Created 0.1s
✔ Container mycppms-mycppweb2-1 Started 1.1s
✔ Container mycppms-mycppweb-1 Started 1.1s
✔ Container mycppms-web-1 Started 1.3s
(12)访问服务 6 次可以看到按照权重负载均衡到了 2 个服务上面:
[xiaomaker@xiaomaker-virtual-machine:mycppms]$ docker logs -f mycppms-mycppweb-1
server init
bind: Success
in 4
process 4
in 5
process 5
[xiaomaker@xiaomaker-virtual-machine:mycppms]$ docker logs -f mycppms-mycppweb2-1
bind: Success
server init
in 4
process 4
in 5
process 5
in 4
process 4
in 5
process 5
in 4
process 4
(13)清理资源:
root@139-159-150-152:/data/maxhou/mydockerfile/mycppms# docker compose down
[+] Running 4/4
✔ Container mycppms-mycppweb2-1 Removed 10.2s
✔ Container mycppms-web-1 Removed 0.2s
✔ Container mycppms-mycppweb-1 Removed 10.1s
✔ Network mycppms_default Removed 0.1s
12. 镜像制作常见问题
(1)ADD 与 COPY 的区别:
- ADD:不仅能够将构建命令所在的主机本地的文件或目录,而且能够将远程 URL所对应的文件或目录,作为资源复制到镜像文件系统。所以,可以认为 ADD 是增强版的 COPY,支持将远程 URL 的资源加入到镜像的文件系统。
- COPY: COPY 指令能够将构建命令所在的主机本地的文件或目录,复制到镜像文件系统。有的时候就是只需要拷贝压缩包,那么我们就要用 COPY 指令了
(2)CMD 与 EntryPoint 的区别:
- ENTRYPOINT 容器启动后执行的命令,让容器执行表现的像一个可执行程序一样,与 CMD 的 区 别 是 不 可 以 被 docker run 覆 盖,会 把 docker run 后 面 的参 数 当 作 传 递 给 ENTRYPOINT 指令的参数。
- Dockerfile 中只能指定一个 ENTRYPOINT,如果指定了很多,只有最后一个 有效。docker run命令的-entrypoint 参 数可以把指定的参数继续传递给ENTRYPOINT组合使用 ENTRYPOINT 和 CMD, ENTRYPOINT 指定默认的运行命令,CMD指定默认的运行参数。
(3)多个 From 指令如何使用:
- 多个 FROM 指令并不是为了生成多根的层关系,最后生成的镜像,仍以最后一条FROM 为准,之前的 FROM 会被抛弃,那么之前的 FROM 又有什么意义呢?每一条 FROM 指令都是一个构建阶段,多条 FROM 就是多阶段构建,虽然最后生成的镜像只能是最后一个阶段的结果,但是,能够将前置阶段中的文件拷贝到后边的阶段中,这就是多阶段构建的最大意义。最大的使用场景是将编译环境和运行环境分离。
(4)快照和 dockerfile 制作镜像有什么区别?
- 等同于为什么要使用 Dockerfile。
(5)什么是空悬镜像(dangling ):
-
仓库名、标签均为 的镜像被称为虚悬镜像,一般来说,虚悬镜像已经失去了存在的价值,是可以随意删除的。造成虚悬镜像的原因:
-
可以用下面的命令专门显示这类镜像:
docker image ls -f dangling=true
(6)中间层镜像是什么?
- 为了加速镜像构建、重复利用资源, Docker 会利用 中间层镜像。所以在使用一段时间后,可能会看到一些依赖的中间层镜像。默认的 docker image ls 列表中只会显示顶层镜像,如果希望显示包括中间层镜像在内的所有镜像的话,需要加 -a 参数。
docker image ls -a
- 这样会看到很多无标签的镜像,与之前的虚悬镜像不同,这些无标签的镜像很多都是中间层镜像,是其它镜像所依赖的镜像。这些无标签镜像不应该删除,否则会导致上层镜像因为依赖丢失而出错。
- 实际上,这些镜像也没必要删除,因为之前说过,相同的层只会存一遍,而这些镜像是别的镜像的依赖,因此并不会因为它们被列出来而多存了一份,无论如何你也会需要它们。只要删除那些依赖它们的镜像后,这些依赖的中间层镜像也会被连带删除。