Files
security-book/00.基础阶段/02.Linux基础/15.Docker/01.Docker介绍与安装.md
2025-08-27 14:13:17 +08:00

939 lines
52 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 01.Docker介绍与安装
## 01.Docker介绍与安装
### 1. Docker是什么
Docker是一个在2013年开源的应用程序并且是一个基于go语言编写的PAAS服务。
Docker最早采用LXC技术之后改为自己研发并开源的runc技术运行容器。
Docker相比虚拟机的交付速度更快资源消耗更低Docker采用客户端、服务端架构使用远程api来管理和创建Docker容器。
Docker的三大理念是build构建、ship运输、run运行
Docker通过namespace、cgroup等技术来提供容器的资源隔离与安全保障。
### 2. Docker与虚拟机之间的对比
![img](01.Docker介绍与安装/image-20191028193918568.png)
| **虚拟化** | **容器** |
| ------------------------------------------------ | ------------------------------------------------------- |
| 隔离性强有独立的GUEST OS | 共享内核和OS隔离性弱! |
| 虚拟化性能差(>15%) | 计算/存储无损耗无Guest OS内存开销(~200M) |
| 虚拟机镜像庞大(十几G~几十G), 且实例化时不能共享 | Docker容器镜象200~300M且公共基础镜象实例化时可以共享 |
| 虚拟机镜象缺乏统一标准 | Docker提供了容器应用镜象事实标准OCI推动进一 步标准化 |
| 虚拟机创建慢(>2分钟) | 秒级创建(<10s)相当于建立索引 |
| 虚拟机启动慢(>30s) 读文件逐个加载 | 秒级(<1s,不含应用本身启动) |
| 资源虚拟化粒度低单机10~100虚拟机 | 单机支持1000+容器密度很高适合大规模的部署 |
- 资源利用率更高一台物理机可以运行数百个容器但一般只能运行数十个虚拟机
- 开销更小不需要启动单独的虚拟机占用硬件资源
- 启动速度更快可以在数秒内完成启动
### 3. Docker的组成
官网https://docs.docker.com/get-started/overview/
Docker主机 host一个物理机或者虚拟机用于运行docker服务进程和容器
Docker服务端 ServerDocker守护进程运行docker容器
Docker客户端 client客户端使用docker命令或其他工具调用docker api
Docker仓库 registry保存镜像的仓库类似于git或svn这样的版本控制器
Docker镜像 images镜像可以理解为创建实例使用的模板
Docker容器 container容器是从镜像生成对外提供服务的一个或一组服务
![image-20210531152229098](01.Docker介绍与安装/image-20210531152229098.png)
![image-20210531152329941](01.Docker介绍与安装/image-20210531152329941.png)
### 4. Docker服务端软件选择
Docker CECommunity Edition社区版 Docker EEEnterprise Edition企业版 Docker 产品的两个主要版本它们之间的主要区别在于目标用户功能集支持和维护等方面
1. **目标用户**
- **Docker CE**面向个人开发者小团队以及技术爱好者主要用于开发和测试环境
- **Docker EE**面向大型企业和组织提供企业级的功能和支持
2. **功能集**
- **Docker CE**提供基本的容器化功能包括构建运行和共享容器
- **Docker EE**除了包含 CE 版本的所有功能外还提供了额外的企业级特性如增强的安全管理可扩展性和集成性
3. **支持和维护**
- **Docker CE**社区支持适合自我解决问题的开发者
- **Docker EE**提供商业支持和专业服务适合需要稳定运行环境的企业
4. **安全性**
- **Docker CE**安全性相对较低适合非生产环境
- **Docker EE**提供更高级的安全特性如镜像扫描安全策略和合规性报告
5. **管理**
- **Docker CE**通常不需要复杂的管理工具
- **Docker EE**提供 Docker Universal Control Plane (UCP) Docker Trusted Registry (DTR) 等管理工具帮助企业更有效地管理容器环境
6. **成本**
- **Docker CE**免费
- **Docker EE**需要购买许可证
7. **更新和生命周期**
- **Docker CE**更新频繁可能包含实验性功能生命周期较短
- **Docker EE**更新周期更稳定更注重稳定性和兼容性生命周期较长
### 5. Docker安装
- 安装docker-ce
```bash
[root@localhost ~]# yum install -y yum-utils
[root@localhost ~]# yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
[root@localhost ~]# sed -i 's+download.docker.com+mirrors.aliyun.com/docker-ce+' /etc/yum.repos.d/docker-ce.repo
[root@localhost ~]# yum install docker-ce -y
```
- 启动docker
```bash
[root@localhost ~]# systemctl enable --now docker
Created symlink /etc/systemd/system/multi-user.target.wants/docker.service → /usr/lib/systemd/system/docker.service.
[root@localhost ~]# systemctl status docker
● docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; preset: disabled)
Active: active (running) since Sun 2025-03-23 23:36:27 CST; 10s ago
TriggeredBy: ● docker.socket
Docs: https://docs.docker.com
Main PID: 29081 (dockerd)
Tasks: 10
Memory: 25.7M
CPU: 141ms
CGroup: /system.slice/docker.service
└─29081 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
```
#### 5.1 镜像加速配置
由于Docker Image仓库在国外目前从23年底开始国内陆续访问不到了所以要通过一些镜像加速器才能获取到镜像
```bash
[root@localhost ~]# docker pull nginx
Error response from daemon: Get "https://registry-1.docker.io/v2/": context deadline exceeded
# 无法从docker官方镜像仓库docker.io获取镜像
```
**使用国内镜像加速器**不稳定会经常变化
```bash
[root@localhost ~]# mkdir -p /etc/docker
[root@localhost ~]# vim /etc/docker/daemon.json
{
"registry-mirrors": [
"https://docker.m.daocloud.io"
]
}
# 重启容器服务
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl restart docker
# 可选加速地址(不一定有用,随时会跑路):
1、https://docker.m.daocloud.io
2、https://docker.1panelproxy.com
3、https://atomhub.openatom.cn
4、https://docker.1panel.live
5、https://dockerhub.jobcher.com
6、https://hub.rat.dev
7、https://docker.registry.cyou
8、https://docker.awsl9527.cn
9、https://do.nark.eu.org/
10、https://docker.ckyl.me
11、https://hub.uuuadc.top
12、https://docker.chenby.cn
13、https://docker.ckyl.me
# 实在不行的话可以在拉去的时候手动加上镜像加速器因为有时候写到daemon.json中也有可能访问不到
[root@localhost ~]# docker pull nginx
[root@localhost ~]# docker pull docker.m.daocloud.io/nginx
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 53a18edff809 6 weeks ago 192MB
```
#### 5.2 快速开始
```bash
[root@localhost ~]# docker pull nginx
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest d1a364dc548d 5 days ago 133MB
[root@localhost ~]# docker run -d -p 80:80 nginx
e617ca1db9a5d242e6b4145b9cd3dff9f7955c6ab1bf160f13fb6bec081a29e4
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e617ca1db9a5 nginx "/docker-entrypoint.…" 6 seconds ago Up 5 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp intelligent_turing
[root@localhost ~]# docker exec -it e617ca1db9a5 bash
root@e617ca1db9a5:/# cd /usr/share/nginx/html/
root@e617ca1db9a5:/usr/share/nginx/html# ls
50x.html index.html
root@e617ca1db9a5:/usr/share/nginx/html# echo 'docker nginx test' > index.html
[root@localhost ~]# curl 192.168.88.10
docker nginx test
[root@admin ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED NAMES
e0a818c40b7e nginx "/docker-entrypoint.…" About an hour ago 0.0.0.0:9000->9000/tcp, :::9000->9000/tcp determined_sanderson
9b066ef4bcd2 nginx "/docker-entrypoint.…" About an hour ago 90->80/tcp, :::90->80/tcp vigorous_hypatia
[root@admin ~]# docker stop e0a818c40b7e
e0a818c40b7e
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
```
- **`-i``--interactive`**
- **功能**保持标准输入stdin打开即使没有附加终端
- **作用**允许用户与容器内的命令进行交互
- **`-t``--tty`**
- **功能**分配一个伪终端TTY)。
- **作用**为用户创建一个类似本地终端的交互环境支持颜色显示光标操作等终端特性
##### 5.2.1 Docker快速搭建RPG小游戏
```bash
[root@localhost ~]# docker pull registry.cn-guangzhou.aliyuncs.com/welldene/games:rpg_game
[root@localhost ~]# docker run -d -p 8000:8000 -p 8787:8787 --name rpg -e HOST_IP=192.168.88.10 registry.cn-guangzhou.aliyuncs.com/welldene/games:rpg_game
9c4bc95c98836a1df0453c282196083e4cb0b5d06e507d5d4567a4c018c13272
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
35a6b44d5645 registry.cn-guangzhou.aliyuncs.com/welldene/games:rpg_game "bash run.sh" 3 seconds ago Up 2 seconds 0.0.0.0:8000->8000/tcp, [::]:8000->8000/tcp, 0.0.0.0:8787->8787/tcp, [::]:8787->8787/tcp rpg
```
**浏览器访问: http://IP:8787**
![image-20250324001839505](01.Docker介绍与安装/image-20250324001839505.png)
##### 5.2.2 Docker参数说明
```bash
-d, --detach: 以守护进程方式运行容器
-p, --publish: 映射容器端口到宿主机端口
格式: `-p [hostPort]:[containerPort]`
-P大写随机端口映射
-v, --volume: 挂载数据卷
格式: `-v [hostPath]:[containerPath]`
-e, --env: 设置环境变量
--name: 为容器指定名称
--network: 指定容器所属网络
--restart: 容器退出时的重启策略
可选值: `no`, `on-failure`, `unless-stopped`, `always`
-i, --interactive: 保持标准输入打开
-t, --tty: 分配一个伪终端
-u, --user: 指定运行容器的用户
--entrypoint: 覆盖容器的默认入口点
--rm: 容器退出后自动删除
--hostname: 设置容器主机名
--add-host: 添加自定义主机名到 IP 的映射
--link: 添加到另一个容器的链接
--expose: 暴露容器端口
--volume-driver: 指定数据卷驱动程序
--cpu-shares: 设置 CPU 权重
--memory: 设置容器内存限制
```
### 6. Docker核心技术
#### 6.1 Linux namespace技术
如果一个宿主机运行了N个容器多个容器带来的以下问题怎么解决
1. 怎么样保证每个容器都有不同的文件系统并且能互不影响
2. 一个docker主进程内的各个容器都是其子进程那么如何实现同一个主进程下不同类型的子进程各个子进程间通信能相互访问吗
3. 每个容器怎么解决IP以及端口分配的问题
4. 多个容器的主机名能一样吗
5. 每个容器都要不要有root用户怎么解决账户重名问题呢
以上问题怎么解决
Docker Namespace 技术是实现容器隔离的核心机制之一它通过 Linux Namespace 提供的隔离功能为每个容器创建独立的资源视图从而实现容器之间的隔离
**namespace**是Linux系统的底层概念在内核层实现即有一些不同类型的命名空间都部署在核内**各个docker容器运行在同一个docker主进程并且共用同一个宿主机系统内核**各个docker容器运行在宿主机的用户空间每个容器都要有类似于虚拟机一样的相**互隔离的运行空间**但是容器技术是在一个进程内实现运行指定服务的运行环境并且还可以保护宿主机内核不受其他进程的干扰和影响如文件系统网络空间进程空间等目前主要通过以下技术实现容器运行空间的相互隔离
| 隔离类型 | 功能 | 系统调用参数 | 内核 |
| -------------------------------------------- | ---------------------------------- | ------------- | ------ |
| MNT Namespacemount | 提供磁盘挂载点和文件系统的隔离能力 | CLONE_NEWNS | 2.4.19 |
| IPC NamespaceInter-Process Communication | 提供进程间通信的隔离能力 | CLONE_NEWIPC | 2.6.19 |
| UTS NamespaceUNIX Timesharing System | 提供主机名隔离能力 | CLONE_NEWUTS | 2.6.19 |
| PID NamespaceProcess Identification | 提供进程隔离能力 | CLONE_NEWPID | 2.6.24 |
| Net Namespacenetwork | 提供网络隔离能力 | CLONE_NEWNET | 2.6.29 |
| User Namespaceuser | 提供用户隔离能力 | CLONE_NEWUSER | 3.8 |
##### 6.1.1 MNT Namespace
每个容器都要有独立的根文件系统有独立的用户空间以实现容器里面启动服务并且使用容器的运行环境
- 启动三个容器
```bash
[root@localhost ~]# docker run -d --name nginx-1 -p 80:80 nginx
0e72f06bba417073d1d4b2cb53e62c45b75edc699b737e46a157a3249f3a803e
[root@localhost ~]# docker run -d --name nginx-2 -p 81:80 nginx
c8ce6a0630b66e260eef16d8ecf48049eed7b893b87459888b634bf0e9e40f23
[root@localhost ~]# docker run -d --name nginx-3 -p 82:80 nginx
1cddbd412b5997f8935815c2f588431e100b752595ceaa92b95758ca45179096
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b42378a51c40 nginx "/docker-entrypoint.…" 2 seconds ago Up 1 second 0.0.0.0:82->80/tcp, [::]:82->80/tcp nginx-3
d30f033c2f29 nginx "/docker-entrypoint.…" 5 seconds ago Up 5 seconds 0.0.0.0:81->80/tcp, [::]:81->80/tcp nginx-2
d34a012dcebc nginx "/docker-entrypoint.…" 10 seconds ago Up 10 seconds 0.0.0.0:80->80/tcp, [::]:80->80/tcp nginx-1
```
- 连接进入某一个容器中并创建一个文件
```bash
[root@localhost ~]# docker exec -it nginx-1 bash
root@d34a012dcebc:/# echo 'hello world test!' > /opt/test_nginx-1
root@d34a012dcebc:/# exit
```
- 宿主机是使用了chroot技术把容器锁定到一个指定的运行目录里
```bash
[root@localhost ~]# find / -name test_nginx-1
/var/lib/docker/overlay2/075b51fb5d33011d4b449fde8c14199c1e30f86224862f68a6116b1cb1dacfdf/diff/opt/test_nginx-1
/var/lib/docker/overlay2/075b51fb5d33011d4b449fde8c14199c1e30f86224862f68a6116b1cb1dacfdf/merged/opt/test_nginx-1
```
Docker 文件系统是通过分层存储机制实现的这与 Docker 的镜像和容器的架构有关看到的两个文件路径反映了 Docker 的存储驱动 Overlay2的工作原理
###### 6.1.1.1 Docker 的存储架构
1. **镜像层Read-Only**
- Docker 镜像是由多个只读层组成的每一层代表了镜像的某个状态或修改
- 这些层是不可变的一旦创建不会被修改
2. **容器层Read-Write**
- 当你运行一个容器时Docker 会在镜像层之上添加一个可写层
- 容器的所有写操作如创建文件修改文件等都会在这个可写层中进行而不会影响下面的镜像层
###### 6.1.1.2 Overlay2 存储驱动
Docker 默认使用 Overlay2 存储驱动在支持的系统上)。Overlay2 的工作机制如下
- **`merged` 目录**
- 这是容器的根文件系统是镜像层和容器层的联合视图
- 当你在容器中访问文件时看到的是 `merged` 目录中的内容
- 例如你在容器中创建的文件 `/opt/test_nginx-1`在宿主机上可以通过 `/var/lib/docker/overlay2/<id>/merged/opt/test_nginx-1` 访问
- **`diff` 目录**
- 这是容器的可写层记录了容器对文件系统的修改
- 当你在容器中创建或修改文件时实际的文件数据会存储在 `diff` 目录中
- 例如你在容器中创建的文件 `/opt/test_nginx-1`其实际数据存储在 `/var/lib/docker/overlay2/<id>/diff/opt/test_nginx-1`
- 当你在容器中创建文件 `/opt/test_nginx-1`
- 文件的实际数据被写入到 `diff` 目录中
- `merged` 目录中通过联合文件系统OverlayFS的机制 `diff` 目录中的文件映射到 `merged` 目录中让你在容器中看到完整的文件系统视图
##### 6.1.2 IPC Namespace
一个容器内的进程间通信允许一个容器内的不同进程数据互相访问但是不能跨容器访问其他容器的数据
UTS Namespace包含了运行内核的名称版本底层体系结构类型等信息用于系统表示其中包含了hostname和域名它使得一个容器拥有属于自己hostname标识这个主机名标识独立于宿主机系统和其上的其他容器
##### 6.1.3 PID Namespace
Linux系统中有一个pid为1的进程init/systemd是其他所有进程的父进程那么在每个容器内也要有一个父进程来管理其下属的进程那么多个容器的进程通PID namespace进程隔离
- 安装软件包
```bash
[root@localhost ~]# docker exec -it 065f06e5caa4 bash
root@0e72f06bba41:/# apt update
# ifconfig
root@0e72f06bba41:/# apt install -y net-tools
root@0e72f06bba41:/# apt install -y procps
root@0e72f06bba41:/# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 10 0 03:20 ? 00:00:00 nginx: master process nginx -g d
nginx 32 1 0 03:20 ? 00:00:00 nginx: worker process
nginx 33 1 0 03:20 ? 00:00:00 nginx: worker process
nginx 34 1 0 03:20 ? 00:00:00 nginx: worker process
nginx 35 1 0 03:20 ? 00:00:00 nginx: worker process
nginx 36 1 0 03:20 ? 00:00:00 nginx: worker process
nginx 37 1 0 03:20 ? 00:00:00 nginx: worker process
nginx 38 1 0 03:20 ? 00:00:00 nginx: worker process
nginx 39 1 0 03:20 ? 00:00:00 nginx: worker process
root 59 0 0 03:35 pts/0 00:00:00 bash
root 503 59 0 03:42 pts/0 00:00:00 ps -ef
```
**那么宿主机的PID与容器内的PID是什么关系**
```bash
[root@localhost ~]# yum install psmisc
[root@localhost ~]# pstree -p
[root@localhost ~]# pstree -p
systemd(1)─┬─NetworkManager(769)─┬─{NetworkManager}(772)
│ └─{NetworkManager}(775)
├─agetty(817)
├─atd(799)
├─auditd(693)─┬─sedispatch(695)
│ ├─{auditd}(694)
│ └─{auditd}(696)
├─bluetoothd(742)
├─chronyd(753)
├─containerd(28884)─┬─{containerd}(28886)
│ ├─{containerd}(28887)
│ ├─{containerd}(28888)
│ ├─{containerd}(28889)
│ ├─{containerd}(28890)
│ ├─{containerd}(28891)
│ ├─{containerd}(28892)
│ └─{containerd}(28894)
├─containerd-shim(30330)─┬─bash(30801)
│ ├─nginx(30353)─┬─nginx(30426)
│ │ ├─nginx(30427)
│ │ ├─nginx(30428)
│ │ └─nginx(30429)
│ ├─{containerd-shim}(30332)
│ ├─{containerd-shim}(30333)
│ ├─{containerd-shim}(30334)
│ ├─{containerd-shim}(30335)
│ ├─{containerd-shim}(30336)
│ ├─{containerd-shim}(30337)
│ ├─{containerd-shim}(30338)
│ ├─{containerd-shim}(30339)
│ ├─{containerd-shim}(30340)
│ ├─{containerd-shim}(30703)
│ └─{containerd-shim}(30909)
├─containerd-shim(30447)─┬─nginx(30469)─┬─nginx(30546)
│ │ ├─nginx(30547)
│ │ ├─nginx(30548)
│ │ └─nginx(30549)
│ ├─{containerd-shim}(30448)
│ ├─{containerd-shim}(30449)
│ ├─{containerd-shim}(30450)
│ ├─{containerd-shim}(30451)
│ ├─{containerd-shim}(30452)
│ ├─{containerd-shim}(30453)
│ ├─{containerd-shim}(30454)
│ ├─{containerd-shim}(30455)
│ ├─{containerd-shim}(30456)
│ └─{containerd-shim}(30705)
├─containerd-shim(30566)─┬─nginx(30587)─┬─nginx(30662)
│ │ ├─nginx(30663)
│ │ ├─nginx(30664)
│ │ └─nginx(30665)
│ ├─{containerd-shim}(30567)
│ ├─{containerd-shim}(30568)
│ ├─{containerd-shim}(30569)
│ ├─{containerd-shim}(30570)
│ ├─{containerd-shim}(30571)
│ ├─{containerd-shim}(30572)
│ ├─{containerd-shim}(30573)
│ ├─{containerd-shim}(30574)
│ ├─{containerd-shim}(30593)
│ ├─{containerd-shim}(30706)
│ └─{containerd-shim}(30780)
├─crond(801)
├─dbus-broker-lau(719)───dbus-broker(723)
├─dockerd(29156)─┬─docker-proxy(30381)─┬─{docker-proxy}(30382)
│ │ ├─{docker-proxy}(30383)
│ │ ├─{docker-proxy}(30384)
│ │ ├─{docker-proxy}(30385)
│ │ ├─{docker-proxy}(30386)
│ │ └─{docker-proxy}(30388)
│ ├─docker-proxy(30387)─┬─{docker-proxy}(30389)
│ │ ├─{docker-proxy}(30390)
│ │ ├─{docker-proxy}(30391)
│ │ ├─{docker-proxy}(30392)
│ │ ├─{docker-proxy}(30393)
│ │ ├─{docker-proxy}(30394)
│ │ ├─{docker-proxy}(30395)
│ │ └─{docker-proxy}(30396)
│ ├─docker-proxy(30497)─┬─{docker-proxy}(30498)
│ │ ├─{docker-proxy}(30499)
│ │ ├─{docker-proxy}(30500)
│ │ ├─{docker-proxy}(30501)
│ │ ├─{docker-proxy}(30502)
│ │ ├─{docker-proxy}(30503)
│ │ ├─{docker-proxy}(30504)
│ │ └─{docker-proxy}(30505)
│ ├─docker-proxy(30506)─┬─{docker-proxy}(30507)
│ │ ├─{docker-proxy}(30508)
│ │ ├─{docker-proxy}(30509)
│ │ ├─{docker-proxy}(30510)
│ │ ├─{docker-proxy}(30511)
│ │ ├─{docker-proxy}(30512)
│ │ └─{docker-proxy}(30513)
│ ├─docker-proxy(30616)─┬─{docker-proxy}(30617)
│ │ ├─{docker-proxy}(30618)
│ │ ├─{docker-proxy}(30619)
│ │ ├─{docker-proxy}(30620)
│ │ ├─{docker-proxy}(30621)
│ │ ├─{docker-proxy}(30622)
│ │ ├─{docker-proxy}(30624)
│ │ └─{docker-proxy}(30625)
│ ├─docker-proxy(30623)─┬─{docker-proxy}(30626)
│ │ ├─{docker-proxy}(30627)
│ │ ├─{docker-proxy}(30628)
│ │ ├─{docker-proxy}(30629)
│ │ ├─{docker-proxy}(30630)
│ │ ├─{docker-proxy}(30631)
│ │ └─{docker-proxy}(30632)
│ ├─{dockerd}(29157)
│ ├─{dockerd}(29159)
│ ├─{dockerd}(29161)
│ ├─{dockerd}(29163)
│ ├─{dockerd}(29407)
│ ├─{dockerd}(29420)
│ ├─{dockerd}(29430)
│ ├─{dockerd}(29537)
│ ├─{dockerd}(29711)
│ ├─{dockerd}(29712)
│ ├─{dockerd}(29823)
│ ├─{dockerd}(29844)
│ ├─{dockerd}(29845)
│ ├─{dockerd}(30633)
│ └─{dockerd}(30683)
├─irqbalance(729)───{irqbalance}(738)
├─lsmd(730)
├─mcelog(734)
├─polkitd(944)─┬─{polkitd}(975)
│ ├─{polkitd}(976)
│ ├─{polkitd}(979)
│ ├─{polkitd}(980)
│ ├─{polkitd}(981)
│ ├─{polkitd}(982)
│ └─{polkitd}(1004)
├─rsyslogd(1063)─┬─{rsyslogd}(1095)
│ └─{rsyslogd}(1096)
├─sshd(786)─┬─sshd(1527)───sshd(1541)───bash(1542)───docker(30781)─┬─{docker}(30782)
│ │ ├─{docker}(30783)
│ │ ├─{docker}(30784)
│ │ ├─{docker}(30785)
│ │ ├─{docker}(30786)
│ │ ├─{docker}(30787)
│ │ ├─{docker}(30788)
│ │ └─{docker}(30807)
│ └─sshd(30928)───sshd(30932)───bash(30933)───pstree(31158)
├─systemd(1532)───(sd-pam)(1534)
├─systemd-journal(635)
├─systemd-logind(739)
├─systemd-udevd(648)
└─tuned(791)─┬─{tuned}(1106)
├─{tuned}(1137)
└─{tuned}(1138)
[root@localhost ~]# ps aux | grep b42378a51c40
root 30566 0.0 0.9 1237984 16524 ? Sl 18:42 0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id b42378a51c402d7ffa408d331a61ebdefe1b920eb723cd343ebc8e5781bec03d -address /run/containerd/containerd.sock
root 31171 0.0 0.1 3880 2048 pts/1 S+ 18:54 0:00 grep --color=auto b42378a51c40
```
**在宿主机上查看容器的进程**
```bash
[root@localhost ~]# docker top nginx-1
UID PID PPID C STIME TTY TIME CMD
root 30353 30330 0 18:42 ? 00:00:00 nginx: master process nginx -g daemon off;
101 30426 30353 0 18:42 ? 00:00:00 nginx: worker process
101 30427 30353 0 18:42 ? 00:00:00 nginx: worker process
101 30428 30353 0 18:42 ? 00:00:00 nginx: worker process
101 30429 30353 0 18:42 ? 00:00:00 nginx: worker process
root 30801 30330 0 18:46 pts/0 00:00:00 bash
```
首先可以看到容器内的进程在宿主机上的 PID容器内的进程只能看到自己命名空间中的进程而无法看到宿主机或其他容器的进程
所以说明docker采用PID Namespace技术将容器内部的进程与宿主机的进程进行了隔离
并且容器内部的进程和宿主机上的进程还存在一定的对应或者映射关系
1. **独立的 PID 命名空间**:
- 每个 Docker 容器都有自己独立的 PID 命名空间
- 容器内的进程 PID 1 开始编号,与宿主机上的 PID 是相互独立的
2. **PID 映射**:
- 容器内的进程 PID 与宿主机上的进程 PID 之间是有映射关系的
3. **PID 可见性**:
- 容器内的进程只能看到容器内部的 PID
- 宿主机上的进程可以看到容器内部的 PID,但容器内的进程无法看到宿主机上的 PID
4. **PID 隔离**:
- 容器内的进程无法访问或影响宿主机上的其他进程
- 宿主机上的进程可以访问和管理容器内的进程
##### 6.1.4 Net Namespace
每一个容器都类似于虚拟机一样有自己的网卡监听端口TCP/IP协议栈等Docker使用network namespace启动一个vethX接口这样容器将拥有它自己的桥接IP地址通常是docker0而docker0实质就是linux的虚拟网桥
查看容器内部的IP网络信息发现有一个eth0的网卡
```bash
root@d34a012dcebc:/# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.2 netmask 255.255.0.0 broadcast 172.17.255.255
ether ce:f2:f5:63:47:16 txqueuelen 0 (Ethernet)
RX packets 4713 bytes 10994487 (10.4 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 3888 bytes 212050 (207.0 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
```
而我们的宿主机上的网卡中多了个docker0的虚拟网桥这样以来通过Net Namespace将容器的网络与宿主机的网络进行隔离并且通过虚拟网桥docker0与容器进行网络通信
```bash
[root@localhost ~]# ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
inet6 fe80::38a5:cdff:fe6b:7dbc prefixlen 64 scopeid 0x20<link>
ether 3a:a5:cd:6b:7d:bc txqueuelen 0 (Ethernet)
RX packets 5820 bytes 11520579 (10.9 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 9483 bytes 11508761 (10.9 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
ens160: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.88.10 netmask 255.255.255.0 broadcast 192.168.88.255
inet6 fe80::20c:29ff:fe26:8384 prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:26:83:84 txqueuelen 1000 (Ethernet)
RX packets 235433 bytes 340010957 (324.2 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 54812 bytes 14709198 (14.0 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
veth51c3173: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet6 fe80::d8fa:18ff:fe0f:d176 prefixlen 64 scopeid 0x20<link>
ether da:fa:18:0f:d1:76 txqueuelen 0 (Ethernet)
RX packets 3888 bytes 212050 (207.0 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 4714 bytes 10994557 (10.4 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
veth813b530: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet6 fe80::e498:2eff:fe08:2c5b prefixlen 64 scopeid 0x20<link>
ether e6:98:2e:08:2c:5b txqueuelen 0 (Ethernet)
RX packets 3 bytes 126 (126.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 15 bytes 1118 (1.0 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
vethc11c399: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet6 fe80::a482:dfff:fe74:dec7 prefixlen 64 scopeid 0x20<link>
ether a6:82:df:74:de:c7 txqueuelen 0 (Ethernet)
RX packets 3 bytes 126 (126.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 18 bytes 1244 (1.2 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
```
- **`docker0` 网桥**
- 它是 Docker 默认创建的一个虚拟网桥用于管理容器的网络通信
- 它为连接到它的容器提供了一个内部网络环境允许容器之间通过这个网桥进行通信
- **`veth3ad3c5b` 接口**
- 这是一个虚拟以太网接口用于连接容器和宿主机的网络
- 它的一端连接到 `docker0` 网桥另一端连接到容器的网络命名空间
- 当容器启动时Docker 会自动创建这样的 veth pair并将一端连接到 `docker0`另一端连接到容器的网络命名空间
逻辑图
![image-20210603144141780](01.Docker介绍与安装/image-20210603144141780.png)
##### 6.1.5 User Namespace
各个容器内可能会出现重名的用户和用户组名称或重复的用户UID或者GID那么怎么隔离各个容器内的用户空间呢
User Namespace允许在宿主机的各个容器空间内创建相同的用户名以及相同的uid和gid只是此用户的有效范围仅仅是当前的容器内不能访问另外一个容器内的文件系统即相互隔离互不影响永不相见
#### 6.2 Linux control groups
在一个容器内部如果不对其做任何资源限制则宿主机会允许其占用无限大的内存空间有时候会因为代码bug程序会一直申请内存直到把宿主机内存占完为了避免此类的问题出现宿主机有必要对容器进行资源分配限制比如cpu内存等Linux Cgroups的全称是Linux control Groups它最重要的作用就是限制一个进程组能够使用的资源上线包括cpu内存磁盘网络等等
- 验证系统内核层已经默认开启cgroup功能
```bash
[root@localhost ~]# cat /boot/config-5.14.0-427.13.1.el9_4.x86_64 | grep cgroup -i
CONFIG_CGROUPS=y
# CONFIG_CGROUP_FAVOR_DYNMODS is not set
CONFIG_BLK_CGROUP=y
CONFIG_CGROUP_WRITEBACK=y
CONFIG_CGROUP_SCHED=y
CONFIG_CGROUP_PIDS=y
CONFIG_CGROUP_RDMA=y
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_HUGETLB=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_PERF=y
CONFIG_CGROUP_BPF=y
CONFIG_CGROUP_MISC=y
# CONFIG_CGROUP_DEBUG is not set
CONFIG_SOCK_CGROUP_DATA=y
CONFIG_BLK_CGROUP_RWSTAT=y
CONFIG_BLK_CGROUP_IOLATENCY=y
CONFIG_BLK_CGROUP_FC_APPID=y
# CONFIG_BLK_CGROUP_IOCOST is not set
# CONFIG_BLK_CGROUP_IOPRIO is not set
# CONFIG_BFQ_CGROUP_DEBUG is not set
CONFIG_NETFILTER_XT_MATCH_CGROUP=m
CONFIG_NET_CLS_CGROUP=y
CONFIG_CGROUP_NET_PRIO=y
CONFIG_CGROUP_NET_CLASSID=y
# CONFIG_DEBUG_CGROUP_REF is not set
```
- 关于内存的模块
```bash
[root@localhost ~]# cat /boot/config-3.10.0-957.el7.x86_64 | grep mem -i | grep cg -i
CONFIG_MEMCG=y
CONFIG_MEMCG_SWAP=y
CONFIG_MEMCG_SWAP_ENABLED=y
CONFIG_MEMCG_KMEM=y
```
扩展阅读
https://blog.csdn.net/qyf158236/article/details/110475457
##### 6.2.1 Docker 中的 cgroups 资源限制
###### 6.2.1.1 **CPU 资源限制**
Docker 提供了多种方式来限制容器的 CPU 使用
- **`--cpus`**限制容器可以使用的 CPU 核心数量例如`--cpus="1.5"` 表示容器最多可以使用 1.5 CPU
- **`--cpu-shares`**设置容器的 CPU 使用权重默认值为 1024值越高分配的 CPU 时间片越多
- **`--cpu-period` `--cpu-quota`**更细粒度地控制 CPU 时间片`--cpu-period` 设置 CPU 时间片的周期单位为微秒`--cpu-quota` 设置每个周期内容器可以使用的 CPU 时间
###### 6.2.1.2 **内存资源限制**
Docker 可以通过以下参数限制容器的内存使用
- **`-m` `--memory`**限制容器的物理内存使用量例如`-m 512m` 表示限制容器使用 512MB 的物理内存
- **`--memory-swap`**限制容器的总内存使用量物理内存 + 交换空间)。例如`--memory-swap=1g` 表示容器可以使用 1GB 的总内存
###### 6.2.1.3 **磁盘 I/O 资源限制**
Docker 可以限制容器的磁盘 I/O 使用
- **`--blkio-weight`**设置容器的块设备 I/O 权重范围为 10 1000
- **`--device-read-bps` `--device-write-bps`**限制特定设备的读写速率例如`--device-read-bps /dev/sda:1mb` 表示限制容器对 `/dev/sda` 的读取速率为 1MB/s
##### 6.2.2 查看和管理 cgroups 资源限制
- **查看 cgroups 配置**可以通过访问 `/sys/fs/cgroup` 目录来查看容器的 cgroups 配置例如`/sys/fs/cgroup/cpu/docker/<container_id>` 目录下包含了容器的 CPU 资源限制文件
- **动态调整资源限制**在容器运行时可以通过修改 cgroups 文件的内容来动态调整资源限制
##### 6.2.3 使用压缩工具测试
```plain
[root@bogon ~]# docker pull lorel/docker-stress-ng
Using default tag: latest
latest: Pulling from lorel/docker-stress-ng
c52e3ed763ff: Pull complete
a3ed95caeb02: Pull complete
7f831269c70e: Pull complete
Digest: sha256:c8776b750869e274b340f8e8eb9a7d8fb2472edd5b25ff5b7d55728bca681322
Status: Downloaded newer image for lorel/docker-stress-ng:latest
```
###### 6.2.3.1 测试CPU
不限制cpu使用
```bash
[root@bogon ~]# docker container run --name stress -it --rm lorel/docker-stress-ng:latest --cpu 4
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 8 cpu
[root@bogon ~]# docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
92b0b8d916c1 stress 101.54% 15.81MiB / 983.3MiB 1.61% 648B / 0B 0B / 0B 9
[root@bogon ~]# top
top - 19:15:49 up 2 days, 2:38, 2 users, load average: 7.02, 3.00, 1.15
Tasks: 131 total, 10 running, 121 sleeping, 0 stopped, 0 zombie
%Cpu(s): 99.7 us, 0.3 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 1006892 total, 100680 free, 320704 used, 585508 buff/cache
KiB Swap: 2097148 total, 2096628 free, 520 used. 422732 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
40035 root 20 0 6908 4180 252 R 12.6 0.4 0:12.79 stress-ng-cpu
40037 root 20 0 6908 4180 252 R 12.6 0.4 0:12.78 stress-ng-cpu
40038 root 20 0 6908 2136 252 R 12.6 0.2 0:12.78 stress-ng-cpu
40040 root 20 0 6908 2136 252 R 12.6 0.2 0:12.78 stress-ng-cpu
40036 root 20 0 6908 2136 252 R 12.3 0.2 0:12.77 stress-ng-cpu
40039 root 20 0 6908 2136 252 R 12.3 0.2 0:12.78 stress-ng-cpu
40041 root 20 0 6908 4180 252 R 12.3 0.4 0:12.77 stress-ng-cpu
40042 root 20 0 6908 2136 252 R 12.3 0.2 0:12.77 stress-ng-cpu
1 root 20 0 128484 7208 4196 S 0.0 0.7 0:10.12 systemd
```
可以看到cpu使用已经满了
重新启动容器加入CPU限制参数
```bash
[root@bogon ~]# docker container run --name stress --cpus=0.5 -it --rm lorel/docker-stress-ng:latest --cpu 8
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 8 cpu
[root@bogon ~]# docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
845220ef9982 stress 51.57% 20.05MiB / 983.3MiB 2.04% 648B / 0B 0B / 0B 9
```
### 7. 容器规范
#### 7.1 容器技术及其标准化组织 OCI
容器技术是一种轻量级的虚拟化技术用于隔离应用程序及其依赖项使其能够在不同的环境中一致地运行除了 Docker 之外还有其他多种容器运行时和工具例如 CoreOS rkt阿里的 Pouch 和红帽的 Podman为了确保容器生态系统的标准性和可持续发展Linux 基金会Docker微软红帽谷歌和 IBM 等公司在 2015 6 月共同成立了 **Open Container Initiative (OCI)** 组织
##### 7.1.1 **OCI 的目标**
OCI 的主要目标是制定开放的容器规范以确保不同容器技术之间的可移植性和互操作性目前OCI 已经发布了两个核心规范
1. **Runtime Spec**定义了容器运行时的规范包括容器的生命周期管理资源隔离和安全等
2. **Image Format Spec**定义了容器镜像的格式和元数据确保镜像可以在不同的容器运行时之间共享和运行
通过遵循这些规范不同的容器运行时和工具可以实现互操作性从而推动容器技术的标准化和健康发展
##### 7.1.2 主流容器运行时
容器运行时是真正运行容器的地方它需要与操作系统的内核紧密合作为容器提供隔离的运行环境以下是目前主流的三种容器运行时
###### 7.1.2.1 **LXC (Linux Containers)**
- **简介**LXC Linux 上早期的容器运行时它利用 Linux 内核的 Namespace Cgroups 技术来实现进程隔离和资源管理
- **特点**
- 提供了完整的 Linux 系统环境支持多种 Linux 发行版
- 早期 Docker 也曾使用 LXC 作为其默认的运行时
- **适用场景**适用于需要完整 Linux 系统环境的容器化应用
###### 7.1.2.2 **Runc**
- **简介**Runc 是目前 Docker 默认的容器运行时它是一个轻量级的命令行工具用于运行和管理容器
- **特点**
- 完全遵循 OCI Runtime Spec 规范确保与 OCI 标准的兼容性
- 由于其轻量级和高性能的特点Runc 已经成为许多容器运行时的底层实现
- **适用场景**适用于需要高性能和轻量级容器运行环境的场景
###### 7.1.2.3 **Rkt (Rocket)**
- **简介**Rkt 是由 CoreOS 开发的容器运行时旨在提供一个安全可靠且符合 OCI 规范的容器运行环境
- **特点**
- Docker 不同Rkt 本身是一个独立的容器运行时不依赖 Docker 的守护进程
- 提供了更好的安全性和隔离性例如通过 AppArmor SELinux 等安全机制
- **适用场景**适用于对安全性要求较高的容器化应用
容器技术的发展离不开标准化的推动OCI 通过制定 Runtime Spec Image Format Spec为容器运行时和工具提供了统一的标准确保了不同容器技术之间的互操作性和可移植性目前主流的容器运行时 LXCRunc Rkt都遵循这些规范从而推动了容器技术的广泛应用和发展
### 8. docker info信息
```bash
[root@localhost ~]# docker info
Client:
Context: default
Debug Mode: false
Plugins:
app: Docker App (Docker Inc., v0.9.1-beta3)
buildx: Build with BuildKit (Docker Inc., v0.5.1-docker)
scan: Docker Scan (Docker Inc.)
Server:
Containers: 2 # 当前主机运行容器总数
Running: 1 # 有几个容器是正在运行的
Paused: 0 # 有几个容器是暂停的
Stopped: 1 # 有几个容器是停止的
Images: 1 # 当前服务器的镜像数
Server Version: 20.10.6 # 服务端版本
Storage Driver: overlay2 # 正在使用的存储引擎
Backing Filesystem: xfs # 后端文件系统,即服务器的磁盘文件系统
Supports d_type: true # 是否支持d_type
Native Overlay Diff: true # 是否支持差异数据存储
userxattr: false
Logging Driver: json-file # 日志文件类型
Cgroup Driver: cgroupfs # cgroups类型
Cgroup Version: 1
Plugins: # 插件
Volume: local # 卷
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
Swarm: inactive # 是否支持swarm
Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc
Default Runtime: runc # 默认的runtime
Init Binary: docker-init # 初始化容器的守护进程
containerd version: d71fcd7d8303cbf684402823e425e9dd2e99285d
runc version: b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7
init version: de40ad0
Security Options: # 安全选项
seccomp
Profile: default
Kernel Version: 3.10.0-693.el7.x86_64 # 宿主机内核版本
Operating System: CentOS Linux 7 (Core) # 宿主机操作系统
OSType: linux # 宿主机操作系统类型
Architecture: x86_64 # 宿主机架构
CPUs: 1 # 宿主机cpu数量
Total Memory: 1.781GiB # 宿主机总内存
Name: docker-server # 宿主机主机名
ID: ARN5:ESPO:FEZ4:KDZ6:RWGG:WQ3X:SIXN:3FVG:ATXH:JAXA:ENGH:RAVE
Docker Root Dir: /var/lib/docker # 宿主机数据保存目录
Debug Mode: false
Registry: https://index.docker.io/v1/ # 镜像仓库
Labels:
Experimental: false # 是否是测试版
Insecure Registries:
127.0.0.0/8
Live Restore Enabled: false # 是否开启活动容器(重启不关闭容器)
```
### 9. docker 存储引擎
Docker 的存储引擎是 Docker 平台中用于管理容器和镜像数据的核心组件它负责容器的文件系统网络配置权限管理等以及其他与容器运行相关的任务以下是对 Docker 存储引擎的详细介绍
#### 9.1 **核心概念及工作原理**
Docker 存储引擎的核心思想是的概念镜像是由多个只读层组成的而容器则在镜像的基础上添加了一个可读写的层这种分层架构使得镜像的复用和部署变得非常方便同时减少了容器的体积
Docker 使用联合文件系统Union File System来管理容器的文件系统联合文件系统允许将多个目录或文件系统合并为一个统一的文件系统视图Docker 的存储引擎通过这种机制将镜像层和容器层合并在一起使得容器能够看到一个完整的文件系统
Docker 支持多种存储引擎每种存储引擎都有其特点和适用场景以下是一些常见的存储引擎
**AUFSAnother Union File System**
- **特点**AUFS 是一种文件级的存储驱动允许多个目录共享相同的文件系统层次结构它通过联合挂载技术将多个目录挂载到一个单一的文件系统上
- **适用场景**AUFS 曾是 Docker 早期版本的默认存储驱动但在较新的 Docker 版本中已被 Overlay2 替代
**OverlayFS**
- **特点**OverlayFS 是一种更现代的联合文件系统 Linux 内核 3.18 开始支持它将文件系统简化为两层一个只读的下层lowerdir和一个可读写的上层upperdir统一后的视图称为合并层merged)。
- **优势**OverlayFS 支持页缓存共享多个容器如果读取相同层的同一个文件可以共享页缓存从而提高内存利用率此外OverlayFS 在性能和稳定性方面表现更好是目前 Docker 的默认存储驱动
#### 9.2 Docker 的 Overlay2 存储驱动介绍
1. **什么是 Overlay2**
Overlay2 Docker 中的一种存储驱动用于管理容器和镜像的文件系统它是 OverlayFS 的改进版本解决了早期 Overlay 驱动可能遇到的 inode 耗尽问题Overlay2 使用联合文件系统Union File System技术将多个文件系统层合并为一个统一的文件系统视图从而实现高效的容器文件系统管理
2. **Overlay2 的工作原理**
Overlay2 通过以下三个主要目录来管理文件系统
- **`LowerDir`**只读层包含基础镜像的文件系统可以有多个只读层每层都是独立的
- **`UpperDir`**读写层用于存储容器运行时的文件系统变更 diff )。
- **`MergedDir`**联合挂载后的视图容器看到的完整文件系统它将 `LowerDir` `UpperDir` 合并为一个统一的文件系统视图
- **`WorkDir`**工作目录用于联合挂载的内部操作挂载后内容被清空
当启动一个容器时Overlay2 会将镜像层`LowerDir`和容器层`UpperDir`联合挂载到 `MergedDir`容器通过这个目录看到完整的文件系统