Docker核心技术和实现原理

镜像制作及原因

镜像制作是因为某种需求,官方的镜像无法满足需求,需要我们通过一定手段来自定
义镜像来满足要求。
制作镜像往往因为以下原因
1. 编写的代码如何打包到镜像中直接跟随镜像发布
2. 第三方制作的内容安全性未知,如含有安全漏洞
3. 特定的需求或者功能无法满足,如需要给数据库添加审计功能
4. 公司内部要求基于公司内部的系统制作镜像,如公司内部要求使用自己的操作系
统作为基础镜像

Docker 镜像制作方式

制作容器镜像,主要有两种方法:
制作快照方式获得镜像(偶尔制作的镜像):在基础镜像上(比如 Ubuntu ),先登
录容器中,然后安装镜像需要的所有软件,最后整体制作快照。
Dockerfile 方式构建镜像(经常更新的镜像):将软件安装的流程写成 Dockerfile
使用 docker build 构建成容器镜像。

快照方式制作镜像

制作命令

docker commit

功能
从容器创建一个新的镜像。
参数
-a : 提交的镜像作者;
-c : 使用 Dockerfile 指令来创建镜像;可以修改启动指令
-m : 提交时的说明文字;
-p : commit 时,将容器暂停。
样例

Dockerfile 格式

该指令不区分大小写。然而,约定是它们是大写的,以便更容易地将它们与参数区分
开来。 Docker 按顺序运行指令 Dockerfile Docker 将以 开头 的行视为 # 注释,行中其他任何地方的标记 # 都被视为参数。这允许像 这样的语句:

为什么需要 Dockerfile

可以按照需求自定义镜像
docker commit 一样能够自定义镜像,官方的镜像可以说很少能直接满足
我们应用的,都需要我们自己打包自己的代码进去然后做成对应的应用镜像对外
使用。
很方便的自动化构建,重复执行
通过 dockerfile 可以自动化的完成镜像构建,而不是像 docker commit 一样,
手动一个命令一个命令执行,而且可以重复执行, docker commit 的话很容易忘
记执行了哪个命令,哪个命令没有执行。
维护修改方便,不再是黑箱操作
使用 docker commit 意味着所有对镜像的操作都是黑箱操作,生成的镜像也
被称为黑箱镜像 ,dockerfile 很容易二次开发。
更加标准化,体积可以做的更小
docker 容器启动后,系统运行会生成很多运行时的文件,如果使用 commit
导致这些文件也存储到镜像里面,而且 commit 的时候安装了很多的依赖文件,
没有有效的清理机制的话会导致镜像非常的臃肿。使用 Dockerfile 则会更加标准
化,而且提供多级构建,将编译和构建分开,不会有运行时的多余文件,更加的
标准化。

Dockerfile 指令

指令清单
FROM
功能
FROM 指令用于 为镜像文件构建过程指定基础镜像 ,后续的指令运行于此基
础镜像所提供的运行环境;
注意事项
FROM 指令必须是 Dockerfile 非注释行或者 ARG 之后的第一个指令
实践中,基准镜像可以是任何可用镜像文件,默认情况下, docker build 会在
docker 主机上查找指定的镜像文件 ,在其不存在时,则 会自动从 Docker 的公共
pull 镜像下来。如果找不到指定的镜像文件, docker build 会返回一个错误信
息;
FROM 可以在一个 Dockerfile 中出现多次,如果有需求在一个 Dockerfile
创建多个镜像 , 或将一个构建阶段作为另一个的依赖。
如果 FROM 语句没有指定镜像标签,则 默认使用 latest 标签
参数
<platform>: 构建的 cpu 架构,如 linux/amd64 , linux/arm64 ,
windows/amd64
<image> :指定作为 base image 的名称;
<tag> base image 的标签,省略时默认 latest
<digest> :是镜像的哈希码;
AS <name>: 指定构建步骤的名称,配合 COPY --from=<name> 可以完成多
级构建
LABEL
功能
为镜像添加元数据,元数据是 kv 对形式
语法
Shell
LABEL <key>=<value> <key>=<value> <key>=<value> ...
COPY
功能
用于从 docker 主机复制新文件或者目录至创建的新镜像指定路径中 。
语法
参数
<src> :要复制的源文件或目录, 支持使用通配符
<dest> :目标路径,即正在创建的 image 的文件系统路径; 建议 <dest> 使用
绝对路径 ,否则, COPY 指定以 WORKDIR 为当前路径
在路径中有空白字符时,通常使用第 2 种格式;
--chown :修改用户和组
--from <name> 可选项 :
可以从之前构建的步骤中拷贝内容,结合 FROM .. AS <name> 往往用作
多级构建,后续我们有实战课专门完成多级构建
注意事项
<src> 必须是 build 上下文中的路径, 不能是其父目录中的文件
如果 <src> 是目录 ,则其 内部文件或子目录会被递归复制 ,但 <src> 目录自身
不会被复制
如果指定了多个 <src> ,或在 <src> 中使用了通配符,则 <dest> 必须是一个目
录,且 必须以 / 结尾
如果 <dest> 事先不存在,它将 会被自动创建 ,这包括父目录路径。
ENV
功能
用于为镜像定义所需的 环境变量 ,并可被 Dockerfile 文件中 位于其后的 其它指
( ENV ADD COPY ) 所调用
调用格式为 $variable_name ${variable_name}
语法
WORKDIR
功能
Dockerfile 中所有的 RUN CMD ENTRYPOINT COPY ADD 指定
定工作目录
语法
注意事项
默认的工作目录是 /
如果提供了相对路径,它将相对于前一条 WORKDIR 指令的路径。
WORKDIR 指令可以解析先前使用设置的环境变量 ENV
ADD
功能
ADD 指令类似于 COPY 指令, ADD 支持使用 TAR 文件和 URL 路径 , 会自动
完成解压和下载
语法
参数
<src> :要复制的源文件或目录, 支持使用通配符
<dest> :目标路径,即正在创建的 image 的文件系统路径; 建议 <dest> 使用
绝对路径 ,否则, ADD 指定以 WORKDIR 为其实路径;在路径中有空白字符时,
通常使用第 2 种格式;
--chown :修改用户和组
RUN
功能
用于指定 docker build 过程中运行的程序,其可以是任何命令
语法
参数
第一种格式中, <command> 通常是一个 shell 命令 , 且 “/bin/sh -c” 来运
,Windows 默认为 cmd /S /C 。如果一个脚本 test.sh 不能自己执行,必须要
/bin/sh -c test.sh 的方式来执行,那么,如果使用 RUN shell 形式 ,最后
得到的命令相当于:
Bash
/bin/sh -c "/bin/sh -c 'test.sh'"
第二种语法格式中的参数是一个 JSON 格式的数组 ,其中 <executable> 为要
运行的命令,后面的 <paramN> 为传递给命令的选项或参数;然而,此种格式指
定的命令 不会以 “/bin/sh -c” 来发起 ,因此 常见的 shell 操作 如变量替换以及通配
(?,* ) 替换 将不会进行 ;不过,如果要运行的命令依赖于此 shell 特性的话,可
以将其替换为类似下面的格式。 RUN ["/bin/bash", "-c", "<executable>",
"<param1>"]
Shell
ENV WEB_SERVER_PACKAGE nginx-1.21.1.tar.gz
RUN cd ./src && tar -xf ${WEB_SERVER_PACKAGE}
CMD
功能
类似于 RUN 指令 CMD 指令也可用于运行任何命令或应用程序,不过,二
者的运行时间点不同
RUN 指令运行于映像文件 构建 过程 中,而 CMD 指令运行于基于 Dockerfile
构建出的新映像文件 启动 一个容器时
CMD 指令的首要 目的在于为启动的容器指定默认要运行的程序 ,且其运行结
束后,容器也将终止;不过, CMD 指定的命令其可以被 docker run 的命令行选
项所覆盖
Dockerfile 中可以存在 多个 CMD 指令,但仅最后一个会生效
注意事项
第二种则用于为 ENTRYPOINT 指令提供默认参数
json 数组中,要使用 双引号 ,单引号会出错
EXPOSE
功能
用于为容器 声明 打开指定要监听的端口 以实现与外部通信
EXPOSE 指令实际上并不发布端口。它充当构建图像的人和运行容器的人之
间的一种文档,关于要发布哪些端口。要在运行容器时实际发布端口,使用 -p
数发布和映射一个或多个端口,或者使用 -P flag 发布所有暴露的端口并将它们映
射宿主机端口。
语法
Shell
EXPOSE <port> [<port>/<protocol>...]
参数
<protocol> tcp/udp 协议
<port> :端口
样例
Shell
EXPOSE 80/tcp
ENTRYPOINT
功能
用于指定容器的启动入口
语法
参数
json 数组中,要使用 双引号 ,单引号会出错
样例
Shell
ENTRYPOINT ["nginx","-g","daemon off;"]
ARG
功能
ARG 指令类似 ENV ,定义了一个变量;区别于 ENV :用户可以在构建时
docker build --build-arg <varname> = <value> 进行对变量的修改; ENV 不可以;
如果用户指定了未在 Dockerfile 中定义的构建参数,那么构建输出警告。
语法
注意事项
Dockerfile 可以包含一个或多个 ARG 指令
ARG 支持指定默认值
使用范围:定义之后才能使用,定义之前为空,如下面的案例,执行命令
docker build --build-arg username=what_user . 第二行计算结果为
some_user ,不是我们指定的 build-arg 中的参数值 what_user
ENV ARG 同时存在, ENV 会覆盖 ARG
VOLUME
功能
用于在 image 中创建一个挂载点目录
通过 VOLUME 指令创建的挂载点,无法指定主机上对应的目录,是自动生成
的。
语法
参数
mountpoint: 挂载点目录
注意事项
如果挂载点目录路径下此前有文件存在,docker run 命令会在卷挂载完
成后将此前的所有文件复制到新挂载的卷中
其实 VOLUME 指令只是起到了声明了容器中的目录作为匿名卷,但是并
没有将匿名卷绑定到宿主机指定目录的功能。
volume 只是指定了一个目录,用以在用户忘记启动时指定 -v 参数也可以
保证容器的正常运行。比如 mysql ,你不能说用户启动时没有指定 -v ,然后
删了容器,就把 mysql 的数据文件都删了,那样生产上是会出大事故的,所
mysql dockerfile 里面就需要配置 volume ,这样即使用户没有指定 -v
容器被删后也不会导致数据文件都不在了。还是可以恢复的。
volume 与 -v 指令一样,容器被删除以后映射在主机上的文件不会被删除。
如果-v volume 指定了同一个位置,会以 -v 设定的目录为准,其实
volume 指令的设定的目的就是为了避免用户忘记指定 -v 的时候导致的数据丢
失,那么如果用户指定了 -v ,自然而然就不需要 volume 指定的位置了。
样例
Shell
VOLUME ["/data1","/data2"]
SHELL
功能
SHELL 指令允许覆盖用于 shell 命令形式的默认 shell
Linux 上的默认 shell ["/bin/sh" "-c"] ,在 Windows 上是 ["cmd" "/S"
"/C"]
SHELL 指令必须以 JSON 格式写入 Dockerfile
语法
Shell
SHELL ["executable", "parameters"]
参数
executable shell 可执行文件的位置
parameters shell 执行的参数
注意事项
SHELL 指令可以多次出现。
每个 SHELL 指令都会覆盖所有先前的 SHELL 指令,并影响所有后续指
令。
该 SHELL 指令在 Windows 上特别有用,因为 windows 行有两种不同的
shell cmd powershell
USER
功能
用于指定运行 image 时的或运行 Dockerfile 中任何 RUN CMD
ENTRYPOINT 指令定的程序时的用户名或 UID
默认情况下, container 的运行身份为 root 用户
语法
Shell
USER <user>[:<group>]
USER <UID>[:<GID>]
参数
user: 用户
group :用户组
uid: 用户 id
gid: id
注意事项
<UID>可以为任意数字,但实践中其必须为 /etc/passwd 中某用户的有效
UID, 否则将运行失败
HEALTHCHECK
功能
HEALTHCHECK 指令告诉 Docker 如何测试容器以检查它是否仍在工作。
即使服务器进程仍在运行,这也可以检测出陷入无限循环且无法处理新连接
Web 服务器等情况。
语法
Shell
HEALTHCHECK [OPTIONS] CMD command (check container health by
running a command inside the container)
HEALTHCHECK NONE (disable any healthcheck inherited from the base
image)
参数
OPTIONS 选项有:
--interval=DURATION (default: 30s):每隔多长时间探测一次,默认 30
-- timeout= DURATION (default: 30s):服务响应超时时长,默认 30
--start-period= DURATION (default: 0s):服务启动多久后开始探测,默
0
--retries=N (default: 3):认为检测失败几次为宕机,默认 3
返回值
0:容器成功是健康的,随时可以使用
1:不健康的容器无法正常工作
2:保留不使用此退出代码
ONBUILD
功能
用于在 Dockerfile 定义一个触发器
以该 Dockerfile 中的作为基础镜像由 FROM 指令在 build 过程中被执行 时,
触发 创建其 base image Dockerfile 文件中的 ONBUILD 指令 定义的触
发器
语法
Shell
ONBUILD <INSTRUCTION>
参数:
INSTRUCTION dockerfile 的一条指令
样例
Shell
ONBUILD ADD . /app/src
STOPSIGNAL
功能
STOPSIGNAL 指令设置将发送到容器的系统调用信号。
此信号可以是与内核的系统调用表中的位置匹配的有效无符号数,例如 9
或者 SIGNAME 格式的信号名,例如 SIGKILL
语法
Shell
STOPSIGNAL signal
参数
STOPSIGNAL 指令设置将发送到容器出口的系统调用信号。 此信号可以是与
内核的系统调用表中的位置匹配的有效无符号数,例如 9 ,或者 SIGNAME 格式
的信号名,例如 SIGKILL

制作命令

docker build

功能
docker build 命令用于使用 Dockerfile 创建镜像。
语法
Shell
docker build [OPTIONS] PATH | URL |
关键参数
--build-arg=[] : 设置镜像创建时的变量;
-f : 指定要使用的 Dockerfile 路径;
--label=[] : 设置镜像使用的元数据;
--no-cache : 创建镜像的过程不使用缓存;
--pull : 尝试去更新镜像的新版本;
--quiet, -q : 安静模式,成功后只输出镜像 ID
--tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构
建中为一个镜像设置多个标签。
--network: 默认 default 。在构建期间设置 RUN 指令的网络模式
样例
Shell
docker build -t mynginx:v1 .

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. 减少不必要的包安装
只安装需要的包,不要安装无用的包,减少镜像体积。

镜像制作常见问题

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

仓库名、标签均为 <none> 的镜像被称为虚悬镜像,一般来说,虚悬镜像已经失去了存
在的价值,是可以随意删除的。
造成虚悬镜像的原因:
原因一:
原本有镜像名和标签的镜像,发布了新版本后,重新 docker pull *** 时,旧的镜像名
被转移到了新下载的镜像身上,而旧的镜像上的这个名称则被取消;
原因二:
docker build 同样可以导致这种现象。比如用 dockerfile1 构建了个镜像 tnone1:v1,
用另外一个 Dockerfile2 构建了一个镜像 tnone1:v1 ,这样之前的那个镜像就会变成空
悬镜像。
6. 中间层镜像是什么?
为了加速镜像构建、重复利用资源, Docker 会利用 中间层镜像 。所以在使用一段时间
后,可能会看到一些依赖的中间层镜像。默认的 docker image ls 列表中只会显示
顶层镜像,如果希望显示包括中间层镜像在内的所有镜像的话,需要加 -a 参数。
这样会看到很多无标签的镜像,与之前的虚悬镜像不同,这些无标签的镜像很多都是
中间层镜像,是其它镜像所依赖的镜像。这些无标签镜像不应该删除,否则会导致上
层镜像因为依赖丢失而出错。实际上,这些镜像也没必要删除,因为之前说过,相同
的层只会存一遍,而这些镜像是别的镜像的依赖,因此并不会因为它们被列出来而多
存了一份,无论如何你也会需要它们。只要删除那些依赖它们的镜像后,这些依赖的 中间层镜像也会被连带删除。

Docker 镜像原理

docker 是操作系统层的虚拟化,所以 docker 镜像的本质是在模拟操作系统。我们先看
下操作系统是什么。

操作系统基础

操作系统由:进程调度子系统、进程通信子系统、内存管理子系统、设备管理子系统、
文件管理子系统 、网络通信子系统、作业控制子系统组成。
Linux 文件管理子系统 bootfs rootfs 组成。
(1). bootfs :要包含 bootloader kernel, bootloader 主要是引导加载 kernel, Linux
启动时会加载 bootfs 文件系统,在 Docker 镜像的最底层是引导文件系统 bootfs 。这
一层与我们典型的 Linux/Unix 系统是一样的,包含 boot 加载器和内核。当 boot 加载
完成之后整个内核就都在内存中了,此时内存的使用权已由 bootfs 转交给内核,此时
系统也会卸载 bootfs
(2). rootfs : 在 bootfs 之上。包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc
标准目录和文件。 rootfs 就是各种不同的操作系统发行版,比如 Ubuntu Centos 等等。

Union FS(联合文件系统)

联合文件系统( Union File System ), 2004 年由纽约州立大学开发,它可以把多个
目录内容联合挂载到同一个目录下,而目录的物理位置是分开的。 UnionFS 可以把只
读和可读写文件系统合并在一起,具有写时复制功能,允许只读文件系统的修改可以
保存到可写文件系统当中。
UnionFS (联合文件系统)是一种分层、轻量级并且高性能的文件系统,它支持对
文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚
拟文件系统下。 UnionFS 是一种为 Linux FreeBSD NetBSD 操作系统设计的把其
他文件系统联合到一个联合挂载点的文件系统服务。它使用 branch 把不同文件系统的
文件和目录 透明地 覆盖,形成一个单一一致的文件系统。这些 branches 或者是
read-only 或者是 read-write 的,所以当对这个虚拟后的联合文件系统进行写操作的时
候,系统是真正写到了一个新的文件中。看起来这个虚拟后的联合文件系统是可以对
任何文件进行操作的,但是其实它并没有改变原来的文件,这是因为 unionfs 用到了一
个重要的资管管理技术叫写时复制。
写时复制( copy-on-write ,下文简称 CoW ),也叫隐式共享,是一种对可修改资
源实现高效复制的资源管理技术。它的思想是,如果一个资源是重复的,但没有任何
修改,这时候并不需要立即创建一个新的资源;这个资源可以被新旧实例共享。创建
新资源发生在第一次写操作,也就是对资源进行修改的时候。通过这种资源共享的方
式,可以显著地减少未修改资源复制带来的消耗, 但是也会在进行资源修改的时候增
加小部分的开销。

再看 Docker 镜像是什么

image 里面是一层层文件系统 Union FS 。联合文件系统,可以将几层目录挂载到
一起,形成一个虚拟文件系统。虚拟文件系统的目录结构就像普通 linux 的目录结构一
样, docker 通过这些文件再加上宿主机的内核提供了一个 linux 的虚拟环境。
每一层文件系统我们叫做一层 layer ,联合文件系统可以对每一层文件系统设置三
种权限,只读( readonly )、读写( readwrite )和写出( whiteout-able ),但是 docker
镜像中每一层文件系统都是只读的。
构建镜像的时候,从一个最基本的操作系统开始,每个构建的操作都相当于做一层
的修改,增加了一层文件系统。一层层往上叠加,上层的修改会覆盖底层该位置的可
见性,这也很容易理解,就像上层把底层遮住了一样。当你使用的时候,你只会看到
一个完全的整体,你不知道里面有几层,也不清楚每一层所做的修改是什么。
可以看到镜像分层结构有以下特性
(1)镜像共享宿主机的 kernel
(2) base 镜像是 linux 的最小发行版
(3)同一个 docker 主机支持不同的 Linux 发行版
(4)采用分层结构,可以上层引用下层,最大化的共享资源
(5)容器层位于可写层,采用 cow 技术进行修改,该层仅仅保持变化的部分,并不
修改镜像下面的部分
(6)容器层以下都是只读层
(7) docker 从上到下找文件

镜像实现原理

Docker 分层存储实现原理

1. 分层存储实现方式

docker 镜像技术的基础是联合文件系统 (UnionFS),其文件系统是分层的Linux 中各发行版实现的 UnionFS 各不相同,所以 docker 在不同 linux 发行版中使用的也不同。通过 docker info 命令可以查看当前系统所使用哪种 UnionFS,常见的几种
发行版使用如下:
overlay2 overlay 的升级版,官方推荐,更加稳定,而新版的 docker 默认也是这个
驱动, linux 的内核 4.0 以上或者或使用 3.10.0-514 或更高版本内核的 RHEL
CentOS

2. Union FS 的原理

docker 镜像由 多个只读层叠加面成 ,启动容器时, docker 加载只读镜像层并在
镜像栈顶部加一个读写层;
如果运行中的容器修改了现有的一个已经存在的文件,那该文件将会从读写层下
面的只读层复制到读写层,该文件版本仍然存在,只是已经被读写层中该文件的副本
所隐藏,此即 写时复制 (COW)” 机制
如果一个文件在最底层是可见的,如果在 layer1 上标记为删除,最高的层是用户看到
Layer2 的层,在 layer0 上的文件,在 layer2 上可以删除,但是只是标记删除,用
户是不可见的,总之在到达最顶层之前,把它标记来删除,对于最上层的用户是不可
见的,当标记一删除,只有用户在最上层建一个同名一样的文件,才是可见的。

3. overlay2 实现

1. 架构
OverlayFS 将单个 Linux 主机上的两个目录合并成一个目录。这些目录被称为层,统
一过程被称为联合挂载 OverlayFS 底层目录称为 lowerdir , 高层目录称为
upperdir , 合并统一视图称为 merged
图中可以看到三个层结构,即 lowerdir upperdir merged
2. 分层
lowerdir 层
其中 lowerdir 是只读的镜像层 (image layer) ,其中就包含 bootfs/rootfs 层,
bootfs(boot file system) 主要包含 bootloader kernel bootloader 主要是引导加载
kernel ,当 boot 成功 kernel 被加载到内存中, bootfs 就被 umount 了, rootfs(root file
system) 包含的就是典型 Linux 系统中的 /dev /proc /bin /etc 等标准目录。
lowerdir 是可以分很多层的,除了 bootfs/rootfs 层以外,还可以通过 Dockerfile 建立很
image
upperdir 层
upper 是容器的读写层 , 采用了 CoW ( 写时复制 ) 机制 , 只有对文件进行修改才会将文件拷
贝到 upper , 之后所有的修改操作都会对 upper 层的副本进行修改。 upperdir 层是
lowerdir 的上一层,只有这一层可读可写的,其实就是 Container 层,在启动一个容器
的时候会在最后的 image 层的上一层自动创建,所有对容器数据的更改都会发生在这
一层。
workdir 层
它的作用是充当一个中间层的作用 , 每当对 upper 层里面的副本进行修改时 , 会先当到
workdir , 然后再从 workdir 移动 upper
merged 层
是一个统一图层 , mergedir 可以看到 lower , upper , workdir 中所有数据的整合 ,
个容器展现出来的就是 mergedir .merged 层就是联合挂载层,也就是给用户暴露
的统一视觉,将 image 层和 container 层结合,就如最上边的图中描述一致,同一文
件,在此层会展示离它最近的层级里的文件内容,或者可以理解为,只要 container
中有此文件,便展示 container 层中的文件内容,若 container 层中没有,则展示
image 层中的。

3. 如何完成读写

1 、读:
如果文件在 upperdir( 容器 ) 层,直接读取文件;
如果文件不在 upperdir( 容器 ) 层,则从镜像层 (lowerdir) 读取;
2 、写:
首次写入:如果 upperdir 中不存在, overlay overlay2 执行 copy_up 操作,把文件
lowdir 拷贝到 upperdir 中,由于 overlayfs 是文件级别的 ( 即使只有很少的一点修改,
也会产生 copy_up 的动作 ) ,后续对同一文件的再次写入操作将对已经复制到容器层的
文件副本进行修改,这也就是常常说的写时复制 ( copy-on-write )
删除文件或目录:当文件被删除时,在容器层 (upperdir) 创建 whiteout 文件,镜像层
(lowerdir) 的文件是不会被删除的,因为它们是只读的,但 whiteout 文件会阻止它们显
示,当目录被删除时,在容器层 (upperdir) 一个不透明的目录,这个和上边的 whiteout
的原理一样,组织用户继续访问, image 层不会发生改变
3 、注意事项
copy_up 操作只发生在文件首次写入,以后都是只修改副本。
容器层的文件删除只是一个 障眼法 ,是靠 whiteout 文件将其遮挡 ,image 层并没有删
除,这也就是为什么使用 docker commit 提交保存的镜像会越来越大,无论在容器层
怎么删除数据, image 层都不会改变。

docker 镜像加载原理

boots(boot file system )主要包含 bootloader Kernel, bootloader 主要是引导加
kernel,Linux 刚启动时会加 bootfs 文件系统,在 Docker 镜像的最底层是 bootfs 。这一
层与我们典型的 Linux/Unix 系统是一样的,包含 boot 加載器和内核。当 boot 加载完
成之后整个内核就都在内存中了,此时内存的使用权已由 bootfs 转交给内核,此时系
统也会卸载 bootfs。
rootfs root file system), bootfs 之上。包含的就是典型 Linux 系统中的
/dev,/proc,/bin,/etc 等标准目录和文件。 rootfs 就是各种不同的操作系统发行版,比如
Ubuntu,Centos 等等。
典型的 Linux 在启动后,首先将 rootfs 置为 readonly, 进行一系列检查 , 然后将其切换
“readwrite” 供用户使用。在 docker 中,起初也是将 rootfs readonly 方式加载并
检查,然而接下来利用 union mount 的将一个 readwrite 文件系统挂载在 readonly
rootfs 之上,并且允许再次将下层的 file system 设定为 readonly 并且向上叠加 , 这样
一组 readonly 和一个 writeable 的结构构成一个 container 的运行目录 , 每一个被称作
一个 Layer下面的这张图片非常好的展示了组装的过程,每一个镜像层都是建立在另一个镜像层
之上的,同时所有的镜像层都是只读的,只有每个容器最顶层的容器层才可以被用户
直接读写,所有的容器都建立在一些底层服务( Kernel )上,包括命名空间、控制组、
rootfs 等等,这种容器的组装方式提供了非常大的灵活性,只读的镜像层通过共享也能够减少磁盘的占用。

Docker 卷原理

Docker 卷机制
Docker 又是如何做到把一个宿主机上的目录或者文件,挂载到容器里面去呢?
当容器进程被创建之后,尽管开启了 Mount Namespace ,但是在它执行 chroot
chroot 就是可以改变某进程的根目录,使这个程序不能访问目录之外的其他目录,
这个跟我们在一个容器中是很相似的)之前,容器进程一直可以看到宿主机上的整个
文件系统。 而宿主机上的文件系统,也自然包括了我们要使用的容器镜像。这个镜像的各个层,
保存在 /var/lib/docker/overlay2/{layer id}/diff 目录下,在容器进程启动后,它们会被联
合挂载在 /var/lib/docker/{layerid}/merged/ 目录中,这样容器所需的 rootfs 就准备好了。
所以,我们只需要在 rootfs 准备好之后,在执行 chroot 之前,把 Volume 指定的宿主
机目录(比如 /home 目录),挂载到指定的容器目录(比如 /test 目录)在宿主机上对
应的目录(即 /var/lib/docker/aufs/mnt/[ 可读写层 ID]/test )上,这个 Volume 的挂载工
作就完成了。 由于执行这个挂载操作时,“ 容器进程 已经创建了,也就意味着此时 Mount
Namespace 已经开启了。所以,这个挂载事件只在这个容器里可见。你在宿主机上,
是看不见容器内部的这个挂载点的。这就保证了容器的隔离性不会被 Volume 打破。

Docker 网络原理

Linux 常见网络虚拟化

什么是虚拟网卡

虚拟网卡(又称虚拟网络适配器),即用软件模拟网络环境,模拟网络适配器。简单来
说就是软件模拟的网卡。
虚拟网卡:tun/tap
简介
tap/tun 虚拟了一套网络接口,这套接口和物理的接口无任何区别,可以配置 IP
可以路由流量,不同的是,它的流量只在主机内流通。
tun tap 是两个相对独立的虚拟网络设备,其中 tap 模拟了以太网设备,操作二
层数据包(以太帧), tun 则模拟了网络层设备,操作三层数据包( IP 报文)。
使用 tun/tap 设备的目的是实现把来自协议栈的数据包先交由某个打开了
/dev/net/tun 字符设备的用户进程处理后,再把数据包重新发回到链路中。你可以通俗
地将它理解为这块虚拟化网卡驱动一端连接着网络协议栈,另一端连接着用户态程序,
而普通的网卡驱动则是一端连接着网络协议栈,另一端连接着物理网卡。
最典型的 VPN 应用程序为例,程序发送给 tun 设备的数据包
应用程序通过 tun 设备对外发送数据包后, tun 设备,便会把数据包通过字符设备
发送给 VPN 程序, VPN 收到数据包,会修改后再重新封装成新报文,譬如数据包原
本是发送给 A 地址的, VPN 把整个包进行加密,然后作为报文体,封装到另一个发送
B 地址的新数据包当中。然后通过协议栈发送到物理网卡发送出去。
使用 tun/tap 设备传输数据需要经过两次协议栈,不可避免地会有一定的性能损耗
所以引入了新的网卡实现方式 veth.
虚拟网卡:veth
简介
Linux Kernel 2.6 版本, Linux 开始支持网络名空间隔离的同时,也提供了专门的虚
拟以太网( Virtual Ethernet ,习惯简写做 veth )让两个隔离的网络名称空间之间可以
互相通信。
直接把 veth 比喻成是虚拟网卡其实并不十分准确,如果要和物理设备类比,它应该相
当于由交叉网线连接的一对物理网卡。形象化的理解如下:
veth 实际上不是一个设备,而 是一对设备 ,因而也常被称作 veth pair 。要使用 veth
必须在两个独立的网络名称空间中进行才有意义,因为 veth pair 是一端连着协议栈,
另一端彼此相连的,在 veth 设备的 其中一端输入数据,这些数据就会从设备的另外一
端原样不变地流出 .
veth 通信不需要反复多次经过网络协议栈,这让 veth 比起 tap/tun 具有更好的性能。
veth 实现了点对点的虚拟连接,可以通过 veth 连接两个 namespace ,如果我们需要
3 个或者多个 namespace 接入同一个二层网络时,就不能只使用 veth 在物理
网络中,如果需要连接多个主机,我们会使用网桥,或者又称为交换机。 Linux 也提供
了网桥的虚拟实现
虚拟交换机
简介
使用 veth pair 将两个隔离的 netns 连接在了一起 , 在现实世界里等同于用一根网线把两
台电脑连接在了一起,但是在现实世界里往往很少会有人这样使用。因为一台设备不
仅仅只需要和另一台设备通信,它需要和很多很多的网络设备进行通信,如果还使用
这样的方式,需要十分复杂的网络接线,并且现实世界中的普通网络设备也没有那么
多网络接口。
那么,想要让某一台设备和很多网络设备都可以通信需要如何去做呢?在我们的日常
生活中,除了手机和电脑,最常见的网络设备就是路由器了,我们的手机连上 WI-FI
电脑插到路由器上,等待从路由器的 DHCP 服务器上获取到 IP ,他们就可以相互通信
了,这便是路由器的二层交换功能在工作。 Linux Bridge 最主要的功能就是二层交换,
是对现实世界二层交换机的模拟 .
Linux Bridge ,由 brctl 命令创建和管理。 Linux Bridge 创建以后,真实的物理设备
(如 eth0 )抑或是虚拟的设备( veth 或者 tap )都能与 Linux Bridge 配合工作。
虚拟组网 VxLan
物理网络的拓扑结构是相对固定的。云原生时代的分布式系统的逻辑拓扑结构变动频
率,譬如服务的扩缩、断路、限流,等等,都可能要求网络跟随做出相应的变化。
正因如此,软件定义网络( Software Defined Network SDN )的需求在云计算和分布
式时代变得前所未有地迫切, SDN 的核心思路是在物理的网络之上再构造一层虚拟化
的网络。
SDN 里位于下层的物理网络被称为 Underlay ,它着重解决网络的连通性与可管理性,
位于上层的逻辑网络被称为 Overlay ,它着重为应用提供与软件需求相符的传输服务和
网络拓扑。
vlan
交换机是一个 L2 设备,插在同一个交换机的网络设备组成了一个 L2 网络, L2 网络之
间通过 MAC 地址通信,同时这个 L2 网络也是一个广播域。
同属于一个广播域的两个设备想要通信,一设备须得向网络中的所有设备发送请求信
息,只有对应 MAC 地址的设备才是真正的接收方,但实际上却是数据帧传遍整个网络,
所有设备都会收到,且直接丢弃。
如此一来,将造成一系列不好的后果:网络整体带宽被占用、潜在的信息安全风险、
占用 CPU 资源 ……
因此, VLAN 应运而生!
Vlan(Virtual Local Area Network) 即虚拟局域网,是一个将物理局域网在逻辑上划分成
多个广播域技术。通过在交换机上配置 Vlan ,可以实现在同一 Vlan 用户可以进行二层
互访,在不同 Vlan 间的用户被二层隔离,这样既能够隔离广播域,又可以提升网络安
全性
VLAN 究竟能够解决什么问题?
1 、限制广播域。广播域被限制在一个局域网内,节省了带宽,提高了网络处理能力。
2 、增强局域网的安全性。不同局域网内的报文在传输时是相互隔离的,即一个 VLAN
内的用户不能和其它 VLAN 内的用户直接通信,如果不同 VLAN 要进行通信,则需要
通过路由器或三层交换机等三层设备。
3 、灵活构建虚拟工作组。用局域网可以划分不同的用户到不同的工作组,同一工作组
的用户也不必局限于某一固定的物理范围,网络构建和维护更方便灵活。
不过 VLAN 也并非没有缺点。
1 、随着虚拟化技术的发展,一台物理服务器往往承载了多台虚拟机,公有云或其它大
型虚拟化云数据中心动辄需容纳上万甚至更多租户, VLAN 技术最多支持 4000 多个
VLAN ,逐渐无法满足需求。
2、公有云提供商的业务要求将实体网络租借给多个不同的用户,这些用户对于网络的
要求有所不同,而不同用户租借的网络有很大的可能会出现 IP 地址、 MAC 地址的重
叠。传统的 VLAN 并没有涉及这个问题,因此需要一种新的技术来保证在多个租户网
络中存在地址重叠的情况下依旧能有效通信的技术。
3 、虚拟化技术使得单台主机可以虚拟化出多台虚拟机同时运行,而每台虚拟机都会有
其唯一的 MAC 地址。这样,为了保证集群中所有虚机可以正常通信,交换机必须保存
每台虚机的 MAC 地址,这样就导致了交换机中的 MAC 表异常庞大,从而影响交换机
的转发性能。
vxlan
VXLAN 是另一种网络虚拟化技术,有点类似于 VLAN ,但功能更强大。
在传统的 VLAN 网络中,共享同一底层 L2 网段的 VLAN 不能超过 4096 个。只有 12
比特用于对 Ethernet Frame 格式中的 VLAN ID 字段进行编码。
VXLAN 协议定义了 8 个字节的 VXLAN Header ,引入了类似 VLAN ID 的网络标识,
称为 VNI VXLAN Network ID ),由 24 比特组成,这样总共是 1600 多万个,从而满
足了大规模不同租户之间的标识、隔离需求。
VXLAN 是基于 L3 网络构建的虚拟 L2 网络,是一种 Overlay 网络。 VXLAN 不关心底
层物理网络拓扑,它将 Ethernet Frame 封装在 UDP 包中,只要它能承载 UDP 数据包,
就可以在远端网段之间提供以太网 L2 连接。
每个 VXLAN 节点上的出站 L2 Ethernet Frame 都会被捕获,然后封装成 UDP 数据包,
并通过 L3 网络发送到目标 VXLAN 节点。当 L2 Ethernet Frame 到达 VXLAN 节点时,
就从 UDP 数据包中提取(解封装),并注入目标设备的网络接口。这种技术称为隧道。
因此, VXLAN 节点会创建一个虚拟 L2 网段,从而创建一个 L2 广播域。
MacVLan
MACVLAN 允许对同一个网卡设置多个 IP 地址,还允许对同一张网卡上设置多个
MAC 地址,这也是 MACVLAN 名字的由来。原本 MAC 地址是网卡接口的 身份证
应该是严格的一对一关系,而 MACVLAN 打破这层关系,方法是在物理设备之上、网
络栈之下生成多个虚拟的 Device ,每个 Device 都有一个 MAC 地址,新增 Device
操作本质上相当于在系统内核中注册了一个收发特定数据包的回调函数,每个回调函
数都能对一个 MAC 地址的数据包进行响应,当物理设备收到数据包时,会先根据
MAC 地址进行一次判断,确定交给哪个 Device 来处理。
许多网卡在硬件层面对支持的 MAC 地址数量存在限制。超过该限制会导致性能的下降。
所以 macvlan 虽然近乎完美还是没有实际推广完成。

docker 网络分类

docker 常见的网络的分类如下,其中 bridge 网络、 host 网络、 container 网络、 none
网络、 overlay 网络、 macvlan 网络、 ipvlan 网络。其中 overlay 网络往往配合 swarm
k8s 来使用。
1. bridge 网络
bridge 驱动会在 Docker 管理的主机上创建一个 Linux 网桥。默认情况下,网桥上
的容器可以相互通信。也可以通过 bridge 驱动程序配置,实现对外部容器的访问。桥
接网络如下
2. host 网络
如果启动容器的时候使用 host 模式,那么这个容器将不会获得一个独立的
Network Namespace ,而是和宿主机共用一个 Network Namespace 。容器将不会虚
拟出自己的网卡,配置自己的 IP 等,而是使用宿主机的 IP 和端口。但是,容器的其
他方面,如文件系统、进程列表等还是和宿主机隔离的。 host 网络结构如下:
3. container 网络
这个模式指定新创建的容器和引进存在的一个容器共享一个 network namespace
而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 ip ,而是和一
个指定的容器共享 ip ,端口等,两个容器除了网络方面,其他的如文件系统、进程列
表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信。容器网络原理如下:
4. none 网络
Docker 容器拥有自己的 Network Namespace ,但是,并不为 Docker 容器进行任
何网络配置。也就是说,这个 Docker 容器没有网卡、 IP 、路由等信息。需要我们自己
Docker 容器添加网卡、配置 IP 等。
5. overlay 网络
Overlay 驱动创建一个支持多主机网络的覆盖网络。
在不改变现有网络基础设施的前提下,通过某种约定通信协议,把二层报文封装
IP 报文之上的新的数据格式。
Overlay 网络实际上是目前最主流的容器跨节点数据传输和路由方案,底层原理
VXLAN.
Overlay 网络将多个 Docker 守护进程连接在一起,允许不同机器上相互通讯,同
时支持对消息进行加密,实现跨主机的 docker 容器之间的通信, Overlay 网络将多个
Docker 守护进程连接在一起,使 swarm 服务能够相互通信。这种策略消除了在这些
容器之间进行操作系统级路由的需要。下图是个典型的 overlay 网络
6. macvlan 网络
为每个容器的虚拟网络接口分配一个 MAC 地址,使其看起来是直接连接到物理网络
的物理网络接口。在这种情况下,您需要在 Docker 主机上指定一个物理接口以用
.macvlan 以及 子网和网关 macvlan 。您甚至可以 macvlan 使用不同的物理网络接
口隔离您的网络。注意要把对应的网卡开启混杂模式命令为 ifconfig 网卡名称 ( eth0)
promisc
macvlan 典型的拓扑如下:
7. ipvlan 网络
ipvlan overlay 都可以实现不同主机上的容器之间的通讯,但是 ipvlan 是所有容器都
在一个网段,相当于在一个 vlan 里面,然后可以通过不同的子接口对应不同网段,实
现不同容器之间的通讯。而 overlay 可以实现不同网段之间的通讯。 ipvlan l2 网络
示例如下:

深入理解 docker Bridge 网络

网络介绍
Docker Bridge 网络采用内置的 bridge 驱动, bridge 驱动底层采用的是 Linux 内核中
Linux bridge 技术。就网络而言, bridge 网络是在网络段之间转发流量的链路层设备,
而网桥可以是在主机内核中运行的硬件设备或软件设备;就 Docker 而言,桥接网络使
用软件网桥 docker0 ,它允许连接到同一网桥网络的容器进行通信,同时提供与未连
接到该网桥网络容器的隔离。
Docker Container bridge 桥接模式可以参考下图:
创建步骤
Bridge 桥接模式的实现步骤主要如下:
1. Docker Daemon 利用 veth pair 技术,在宿主机上创建两个虚拟网络接口设备,假
设为 veth0 veth1 。而 veth pair 技术的特性可以保证无论哪一个 veth 接收到网络报
文,都会将报文传输给另一方。
2. Docker Daemon veth0 附加到 Docker Daemon 创建的 docker0 网桥上。保证
宿主机的网络报文可以发往 veth0
3. Docker Daemon veth1 添加到 Docker Container 所属的 namespace 下,并被
改名为 eth0 。如此一来,保证宿主机的网络报文若发往 veth0 ,则立即会被 eth0 接收,
实现宿主机到 Docker Container 网络的联通性;同时,也保证 Docker Container 单独使用 eth0 ,实现容器网络环境的隔离性。
至此本文结束,感谢观看!
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值