Restruct and reorder chapters

This commit is contained in:
Baohua Yang
2026-02-21 22:22:17 -08:00
parent 2ab40eacc0
commit 33af380be1
141 changed files with 154 additions and 182 deletions

View File

@@ -0,0 +1,159 @@
## 14.1 基本架构
Docker 的架构设计简洁而高效主要由客户端和服务端两部分组成
### 核心架构图
Docker 采用了 **C/S (客户端/服务端)** 架构Client Daemon 发送请求Daemon 负责构建运行和分发容器
```mermaid
graph LR
Client[客户端 (Docker CLI)] -- docker run --> Dockerd
Client -- docker pull --> Dockerd
subgraph "Docker Host"
Dockerd(dockerd<br>守护进程)
Containers(Containers<br>容器)
Images(Images<br>镜像)
Dockerd -- 管理 --> Containers
Dockerd -- 管理 --> Images
end
```
---
### 组件详解
Docker 的内部架构如同洋葱一样分层每一层专注解决特定问题
#### 1. Docker CLI (客户端)
用户与 Docker 交互的主要方式它将用户命令 ( `docker run`) 转换为 API 请求发送给 dockerd
#### 2. Dockerd (守护进程)
Docker 的大脑
- 监听 API 请求
- 管理 Docker 对象 (镜像容器网络)
- 编排下层组件完成工作
#### 3. Containerd (高级运行时)
行业标准的容器运行时 (CNCF 毕业项目)
- 管理容器的完整生命周期 (启动停止)
- 镜像拉取与存储
- **不包含** 复杂的与容器无关的功能 (如构建API)
- Kubernetes 也可以直接使用 containerd (跳过 Docker)
#### 4. Runc (低级运行时)
用于创建和运行容器的 CLI 工具
- 直接与内核交互 (NamespacesCgroups)
- 遵循 OCI (Open Container Initiative) 规范
- **主要职责**根据配置启动一个容器然后退出 (将控制权交给容器进程)
#### 5. Shim
每个容器都有一个 shim 进程
- **解耦**允许 dockerd 重启而不影响容器运行
- **保持 IO**维持容器的标准输入输出
- **状态汇报** containerd 汇报容器退出状态
---
### 容器启动流程
当执行 `docker run -d nginx` 内部发生了什么
```mermaid
flowchart TD
User((用户))
subgraph DockerCLI [Docker CLI]
Cmd[docker run -d nginx]
end
subgraph DockerHost [Docker Host]
Dockerd[Dockerd]
Containerd[Containerd]
subgraph ContainerRuntime [Runtime]
Shim[Containerd-shim]
Runc[Runc]
Container[容器进程 (nginx)]
end
end
User --> Cmd
Cmd -- 1. REST API --> Dockerd
Dockerd -- 2. gRPC --> Containerd
Containerd -- 3. 准备镜像 & Bundle --> Containerd
Containerd -- 4. Fork --> Shim
Shim -- 5. Exec --> Runc
Runc -- 6. Create Namespaces/Cgroups --> Container
Runc -.-> |7. Exit| Runc
Shim -.-> |8. Monitor IO/Exit| Container
```
1. **CLI** 发送请求给 **Dockerd**
2. **Dockerd** 解析请求调用 **Containerd**
3. **Containerd** 准备镜像转换为 OCI Bundle
4. **Containerd** 创建 **Shim** 进程
5. **Shim** 调用 **Runc**
6. **Runc** 与系统内核交互创建 Namespaces Cgroups
7. **Runc** 启动 nginx 进程后退出
8. **Shim** 接管容器 IO 和生命周期监控
---
### Docker Engine v29+ 变化
Docker Engine v29 (2025/2026) 开始架构进一步简化和标准化
- **Containerd 镜像存储 (Image Store)**默认启用Docker 直接使用 Containerd 的镜像管理能力不再维护自己的一套 graphdriver
- **优势**多平台镜像支持更好镜像拉取更快 (lazy pulling) K8s 共享镜像
---
### Docker Desktop 架构
macOS Windows 因为内核差异架构稍微复杂
```mermaid
flowchart TD
subgraph HostOS ["MacOS / Windows"]
CLI["Docker CLI"]
subgraph LinuxVM ["Linux VM (虚拟机)"]
Engine["Dockerd <--> Containerd <--> Runc"]
end
CLI -- "(Socket 映射)" --> Engine
end
```
- 使用轻量级虚拟机 (Apple Virtualization / WSL 2) 运行 Linux 内核
- 文件挂载 (Bind Mount) 需要跨越 VM 边界 (这也是文件 I/O 慢的原因)
- 网络端口需要从宿主机转发到 VM
---
### 总结
相关信息如下表
| 组件 | 角色 | 关键职责 |
|------|------|----------|
| **CLI** | 指挥官 | 发送指令展示结果 |
| **Dockerd** | 大管家 | API 接口整体调度 |
| **Containerd** | 经理 | 容器生命周期镜像管理 |
| **Shim** | 监工 | 保持 IO允许无守护进程重启 |
| **Runc** | 工人 | 真正干活 (创建容器)干完就走 |
### 延伸阅读
- [命名空间](./18.2_namespace.md)Runc 如何隔离容器
- [控制组](./18.3_cgroups.md)Runc 如何限制资源
- [联合文件系统](./18.4_ufs.md)镜像如何存储

View File

@@ -0,0 +1,316 @@
命名空间 (Namespace) Linux 内核的一个强大特性为容器提供了隔离的运行环境
## 什么是 Namespace
> **Namespace Linux 内核提供的资源隔离机制它让容器内的进程仿佛运行在独立的操作系统中** Namespace 是容器技术的核心基础之一它回答了一个关键问题**如何让一个进程 以为 自己独占整个系统**
```mermaid
flowchart LR
subgraph Host ["宿主机视角"]
direction TB
H1["PID 1: systemd"]
H2["PID 2: sshd"]
H3["PID 3: dockerd"]
H4["PID 1234: nginx"]
H5["PID 1235: nginx worker"]
end
subgraph Container ["容器内视角"]
direction TB
C1["PID 1: nginx<br/>← 容器认为自己是 PID 1"]
C2["PID 2: nginx worker"]
end
H4 -. "(实际是宿主机的 1234" .- C1
```
### Namespace 的类型
Linux 内核提供了以下几种 NamespaceDocker 容器使用了全部
| Namespace | 隔离内容 | 容器中的效果 |
|-----------|---------|-------------|
| **PID** | 进程 ID | 容器内 PID 1 开始看不到其他容器和宿主机进程 |
| **NET** | 网络栈 | 独立的网卡IP 地址端口路由表 |
| **MNT** | 挂载点 | 独立的文件系统视图自己的根目录 |
| **UTS** | 主机名 | 独立的主机名和域名 |
| **IPC** | 进程间通信 | 独立的信号量消息队列共享内存 |
| **USER** | 用户/ ID | 容器内的 root 可以映射为宿主机的普通用户 |
| **Cgroup** | Cgroup 根目录 | 隔离 cgroup 层级视图 (Linux 4.6+)|
---
### PID Namespace
PID Namespace 负责进程 ID 的隔离使得容器内的进程彼此不可见
#### PID 的作用
隔离进程 ID让每个容器有自己的进程编号空间
#### PID 隔离效果
运行以下命令
```bash
## 宿主机上查看进程
$ ps aux | grep nginx
root 12345 0.0 0.1 nginx: master process
root 12346 0.0 0.1 nginx: worker process
## 容器内查看进程
$ docker exec mycontainer ps aux
PID USER COMMAND
1 root nginx: master process ← 在容器内是 PID 1
2 root nginx: worker process
```
#### PID 关键点
- 容器内的 PID 1 进程特殊重要它是容器的主进程退出则容器停止
- 容器内无法看到宿主机或其他容器的进程
- 宿主机可以看到所有容器内的进程 ( PID 不同)
---
### NET Namespace
NET Namespace 负责网络栈的隔离包括网卡路由表和 iptables 规则等
#### NET 的作用
隔离网络栈每个容器拥有独立的网络环境
#### NET 隔离效果
如下代码块所示展示了相关示例
```mermaid
flowchart LR
subgraph Host ["宿主机"]
direction TB
H1["eth0: 192.168.1.10<br/>端口 80 可用"]
H2["docker0: 172.17.0.1"]
end
subgraph Container ["容器"]
direction TB
C1["eth0: 172.17.0.2<br/>端口 80 可用"]
C2["(veth pair 连接)"]
end
H2 <--> C2
```
#### NET 关键点
- 每个容器有独立的网卡IP路由表iptables 规则
- 多个容器可以监听相同端口 (如都监听 80)
- Docker 使用 veth pair 连接容器网络和宿主机网桥
---
### MNT Namespace
MNT Namespace 负责文件系统挂载点的隔离确保容器看到独立的文件系统视图
#### MNT 的作用
隔离文件系统挂载点每个容器有自己的根目录
#### MNT 隔离效果
如下代码块所示展示了相关示例
```bash
宿主机文件系统: 容器内看到的:
/ / ← 容器的根目录
├── bin/ ├── bin/
├── home/ ├── home/
├── var/ ├── var/
│ └── lib/ │ └── lib/
│ └── docker/ │
│ └── overlay2/ │
│ └── merged/ ────┼─── 这个目录成为容器的 /
└── ... └── ...
```
#### chroot 的区别
相关信息如下表
| 特性 | chroot | MNT Namespace |
|------|--------|---------------|
| 安全性 | 可以逃逸 | 更安全 |
| 挂载隔离 | | 完全隔离 |
| /proc/mounts | 共享 | 独立 |
---
### UTS Namespace
UTS Namespace 主要用于隔离主机名和域名
#### UTS 的作用
隔离主机名和域名让每个容器可以有自己的主机名
#### UTS 隔离效果
运行以下命令
```bash
## 宿主机
$ hostname
my-server
## 容器内
$ docker run --hostname mycontainer ubuntu hostname
mycontainer
```
UTS = UNIX Time-sharing System是历史遗留的名称
---
### IPC Namespace
IPC Namespace 用于隔离进程间通信资源 System V IPC POSIX 消息队列
#### IPC 的作用
隔离 System V IPC POSIX 消息队列
#### 隔离的资源
- 信号量 (semaphores)
- 消息队列 (message queues)
- 共享内存 (shared memory)
#### IPC 关键点
- 同一容器内的进程可以通过 IPC 通信
- 不同容器的进程无法通过 IPC 通信 (除非显式共享)
---
### USER Namespace
USER Namespace 允许将容器内的用户 ID 映射到宿主机的不同用户 ID
#### USER 的作用
隔离用户和组 ID实现权限隔离
#### USER 隔离效果
如下代码块所示展示了相关示例
```mermaid
flowchart LR
subgraph Container ["容器内"]
direction TB
C1["UID 0 (root)"]
C2["UID 1 (daemon)"]
end
subgraph Host ["宿主机"]
direction TB
H1["UID 100000<br/>← 非特权用户"]
H2["UID 100001"]
end
C1 -- 映射 --> H1
C2 -- 映射 --> H2
```
#### 安全意义
容器内的 root 用户可以映射为宿主机上的普通用户即使容器被突破攻击者在宿主机上也只有普通权限
> 💡 笔者建议生产环境建议启用 User Namespace增强安全性
---
### 动手实验体验 Namespace
使用 `unshare` 命令可以在不使用 Docker 的情况下体验 Namespace
#### 实验 1UTS Namespace
运行以下命令
```bash
## 创建新的 UTS namespace 并启动 shell
$ sudo unshare --uts /bin/bash
## 修改主机名(只影响这个 namespace
$ hostname container-test
$ hostname
container-test
## 退出后查看宿主机主机名(未改变)
$ exit
$ hostname
my-server
```
#### 实验 2PID Namespace
运行以下命令
```bash
## 创建新的 PID 和 MNT namespace
$ sudo unshare --pid --mount --fork /bin/bash
## 挂载新的 /proc
$ mount -t proc proc /proc
## 查看进程(只能看到当前 shell
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 8960 4516 pts/0 S 10:00 0:00 /bin/bash
root 8 0.0 0.0 10072 3200 pts/0 R+ 10:00 0:00 ps aux
```
#### 实验 3NET Namespace
运行以下命令
```bash
## 创建新的网络 namespace
$ sudo unshare --net /bin/bash
## 查看网络接口(只有 lo
$ ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
```
---
### Namespace 的局限性
Namespace 提供了隔离但不是安全边界
| 方面 | 说明 |
|------|------|
| **共享内核** | 所有容器共享宿主机内核内核漏洞可能影响所有容器 |
| **部分资源未隔离** | /proc/sys 部分内容仍可见时间无法隔离 |
| **非虚拟化** | 比虚拟机隔离性弱 |
> 需要更强隔离时可考虑 gVisorKata Containers 等安全容器方案
---

View File

@@ -0,0 +1,305 @@
## 14.3 控制组
控制组 (Cgroups) Linux 内核提供的另一种关键机制主要用于资源的限制和审计
### 什么是控制组
控制组 (Control Groups简称 cgroups) Linux 内核的一个特性用于 **限制记录和隔离** 进程组的资源使用 (CPU内存磁盘 I/O网络等)
> **核心作用**让多个容器公平共享宿主机资源防止单个容器耗尽系统资源
```mermaid
flowchart LR
subgraph NoLimit ["无 cgroups 限制"]
direction TB
subgraph HostRes1 ["宿主机资源"]
A["容器 A<br/>占用所有<br/>内存和 CPU"]
B["容器 B、C 饥饿"]
end
end
subgraph Limit ["有 cgroups 限制"]
direction TB
subgraph HostRes2 ["宿主机资源"]
direction LR
C_A["A<br/>1GB<br/>2核"]
C_B["B<br/>1GB<br/>1核"]
C_C["C<br/>1GB<br/>1核"]
end
end
```
---
### cgroups 的历史
相关信息如下表
| 时间 | 事件 |
|------|------|
| 2006 | Google 工程师提出 cgroups 概念 |
| 2008 | Linux 2.6.24 正式支持 cgroups v1 |
| 2016 | Linux 4.5 引入 cgroups v2 |
| 现在 | Docker 默认使用 cgroups v2 (如系统支持)|
---
### cgroups 可以限制的资源
相关信息如下表
| 资源类型 | 子系统 | 说明 |
|---------|--------|------|
| **CPU** | `cpu`, `cpuset` | CPU 使用时间和核心分配 |
| **内存** | `memory` | 内存使用上限和 swap |
| **块设备 I/O** | `blkio` | 磁盘读写速度限制 |
| **网络** | `net_cls`, `net_prio` | 网络带宽优先级 |
| **进程数** | `pids` | 限制进程/线程数量 |
---
### Docker 中的资源限制
Docker 提供了丰富的参数来配置容器的资源限制主要包括内存CPU磁盘 I/O
#### 内存限制
运行以下命令
```bash
## 限制容器最多使用 512MB 内存
$ docker run -m 512m myapp
## 限制内存 + swap
$ docker run -m 512m --memory-swap 1g myapp
## 软限制(超过时警告,不会 OOM Kill
$ docker run --memory-reservation 256m myapp
```
| 参数 | 说明 |
|------|------|
| `-m` / `--memory` | 硬限制 (超过会 OOM Kill)|
| `--memory-swap` | 内存 + swap 总限制 |
| `--memory-reservation` | 软限制 (内存竞争时生效)|
| `--oom-kill-disable` | 禁用 OOM Killer (谨慎使用)|
#### CPU 限制
运行以下命令
```bash
## 限制使用 1.5 个 CPU 核心
$ docker run --cpus=1.5 myapp
## 限制使用 CPU 0 和 1
$ docker run --cpuset-cpus="0,1" myapp
## 设置 CPU 使用权重(相对值,默认 1024
$ docker run --cpu-shares=512 myapp
```
| 参数 | 说明 |
|------|------|
| `--cpus` | 限制 CPU 核心数 ( 1.5)|
| `--cpuset-cpus` | 绑定到特定 CPU 核心 |
| `--cpu-shares` | CPU 时间片权重 (相对值)|
| `--cpu-period` / `--cpu-quota` | 精细控制 CPU 配额 |
#### 磁盘 I/O 限制
运行以下命令
```bash
## 限制设备写入速度为 10MB/s
$ docker run --device-write-bps /dev/sda:10mb myapp
## 限制设备读取速度
$ docker run --device-read-bps /dev/sda:10mb myapp
## 限制 IOPS
$ docker run --device-write-iops /dev/sda:100 myapp
```
#### 进程数限制
运行以下命令
```bash
## 限制最多 100 个进程
$ docker run --pids-limit=100 myapp
```
---
### 查看容器资源使用
运行以下命令
```bash
## 实时监控所有容器的资源使用
$ docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
abc123 web 0.50% 45.5MiB / 512MiB 8.89% 1.2kB / 0B 0B / 0B
def456 db 2.30% 256MiB / 1GiB 25.00% 5.6kB / 3.2kB 4.1MB / 2.3MB
## 查看特定容器
$ docker stats mycontainer
## 查看容器的 cgroup 配置
$ docker inspect mycontainer --format '{{json .HostConfig}}' | jq
```
---
### 资源限制的效果
本节涵盖了相关内容与详细描述主要探讨以下几个方面
#### 内存超限
运行以下命令
```bash
## 启动限制 100MB 内存的容器
$ docker run -m 100m stress --vm 1 --vm-bytes 200M
## 容器会被 OOM Killer 杀死
$ docker ps -a
CONTAINER ID STATUS NAMES
abc123 Exited (137) 5 seconds ago hopeful_darwin
## 137 = 128 + 9表示被 SIGKILL9 杀死
...
```
#### CPU 限制验证
运行以下命令
```bash
## 不限制 CPU
$ docker run --rm stress --cpu 4
## 占满所有 CPU
## 限制为 1 个核心
$ docker run --rm --cpus=1 stress --cpu 4
## 只能使用约 100% CPU1 个核心)
...
```
---
### cgroups v1 vs v2
相关信息如下表
| 特性 | cgroups v1 | cgroups v2 |
|------|-----------|-----------|
| 层级结构 | 多层级 (每个资源单独)| 统一层级 |
| 管理复杂度 | 复杂 | 简化 |
| 资源分配 | 基于层级 | 基于子树 |
| PSI (压力监控)| | |
| rootless 容器 | 部分支持 | 完整支持 |
#### 概述
总体概述了以下内容
#### 检查系统使用的版本
运行以下命令
```bash
## 查看 cgroup 版本
$ mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
## 如果显示 cgroup2 表示 v2
## 或者
$ cat /proc/filesystems | grep cgroup
nodev cgroup
nodev cgroup2
```
---
### Compose 中设置限制
Compose 中设置限制配置如下
```yaml
services:
web:
image: nginx
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
```
---
### 最佳实践
在使用 Cgroups 限制资源时遵循一些最佳实践可以避免潜在的问题
#### 1始终设置内存限制
运行以下命令
```bash
## 防止 OOM 影响宿主机
$ docker run -m 1g myapp
```
#### 2为关键应用设置 CPU 保证
运行以下命令
```bash
$ docker run --cpus=2 --cpu-shares=2048 critical-app
```
#### 3监控资源使用
运行以下命令
```bash
## 配合 Prometheus + cAdvisor 监控
$ docker run -d --name cadvisor \
-v /:/rootfs:ro \
-v /var/run:/var/run:ro \
-v /sys:/sys:ro \
-v /var/lib/docker:/var/lib/docker:ro \
gcr.io/cadvisor/cadvisor
```
---

View File

@@ -0,0 +1,237 @@
## 14.4 联合文件系统
联合文件系统 (UnionFS) Docker 镜像分层存储的基础它允许将多个目录挂载为同一个虚拟文件系统
### 什么是联合文件系统
联合文件系统 (UnionFS) 是一种 **分层轻量级** 的文件系统它将多个目录 联合 挂载到同一个虚拟目录形成一个统一的文件系统视图
> **核心思想**将多个只读层叠加最上层可写形成完整的文件系统
```mermaid
flowchart TD
ContainerFS["容器看到的文件系统<br/>/bin /etc /lib /usr /var /app /data"]
UnionFS["UnionFS 联合挂载"]
ContainerLayer["容器层 (读写) : /app/data/log.txt (新写入)"]
ImageLayer3["镜像层3 (只读) : /app/app.py"]
ImageLayer2["镜像层2 (只读) : /usr/local/bin/python"]
ImageLayer1["镜像层1 (只读) : /bin /etc /lib (基础系统)"]
ContainerFS --> UnionFS
UnionFS --> ContainerLayer --> ImageLayer3 --> ImageLayer2 --> ImageLayer1
```
---
### 为什么 Docker 使用联合文件系统
Docker 选择联合文件系统作为其存储驱动主要基于以下几个核心优势
#### 1镜像分层复用
如下代码块所示展示了相关示例
```mermaid
flowchart TD
Nginx["nginx:alpine"] --> Alpine["alpine:3.19 (共享基础层)"]
MyApp["myapp:latest"] --> Alpine
```
多个镜像共享相同的底层节省磁盘空间
#### 2快速构建
每个 Dockerfile 指令创建一层只有变化的层需要重建
```docker
FROM node:20 # 层1基础镜像
COPY package.json ./ # 层2依赖定义
RUN npm install # 层3安装依赖
COPY . . # 层4应用代码
```
代码变化时只需重建层 4 1-3 使用缓存
#### 3容器启动快
容器启动时不需要复制镜像只需
1. 在镜像层上创建一个薄的可写层
2. 联合挂载所有层
---
### Copy-on-Write (写时复制)
当容器修改只读层中的文件时
```mermaid
flowchart LR
subgraph Before ["修改前"]
direction TB
B_C["容器层 (空)"]
B_I["镜像层<br/>/etc/nginx.conf"]
end
subgraph After ["修改后"]
direction TB
A_C["容器层<br/>/etc/nginx.conf ← 复制到容器层后修改"]
A_I["镜像层<br/>/etc/nginx.conf (原文件仍在,但被遮蔽)"]
end
B_C --- B_I
A_C --- A_I
```
**流程**
1. 从只读层读取文件
2. 复制到容器的可写层
3. 在可写层中修改
4. 后续读取使用可写层的版本
---
### Docker 支持的存储驱动
Docker 可使用多种联合文件系统实现
| 存储驱动 | 说明 | 推荐程度 |
|---------|------|---------|
| **overlay2**| 现代 Linux 默认驱动性能优秀 | **推荐** |
| **aufs** | 早期默认兼容性好 | 遗留系统 |
| **btrfs** | 使用 Btrfs 子卷 | 特定场景 |
| **zfs** | 使用 ZFS 数据集 | 特定场景 |
| **devicemapper** | 块设备级存储 | 遗留系统 |
| **vfs** | 不使用 CoW每层完整复制 | 仅测试 |
#### 各发行版推荐
相关信息如下表
| Linux 发行版 | 推荐存储驱动 |
|-------------|-------------|
| Ubuntu 16.04+ | overlay2 |
| Debian Stretch+ | overlay2 |
| CentOS 7+ | overlay2 |
| RHEL 8+ | overlay2 |
| Fedora | overlay2 |
#### 查看当前存储驱动
运行以下命令
```bash
$ docker info | grep "Storage Driver"
Storage Driver: overlay2
```
---
### overlay2 工作原理
overlay2 是目前最推荐的存储驱动
```mermaid
flowchart TD
Merged["merged (合并视图)<br/>容器看到的完整文件系统"]
OverlayFS["OverlayFS"]
Upper["upper<br/>(容器层)<br/>读写"]
Lower2["lower2<br/>(镜像层)<br/>只读"]
Lower1["lower1<br/>(基础层)<br/>只读"]
Merged --> OverlayFS
OverlayFS --> Upper
OverlayFS --> Lower2
OverlayFS --> Lower1
```
- **lowerdir**只读的镜像层 (可以有多个)
- **upperdir**可写的容器层
- **workdir**OverlayFS 的工作目录
- **merged**联合挂载后的视图
#### 概述
总体概述了以下内容
#### 文件操作行为
相关信息如下表
| 操作 | 行为 |
|------|------|
| **读取** | 从上到下查找第一个匹配的文件 |
| **创建** | upper 层创建 |
| **修改** | 如果在 lower 先复制到 upper 层再修改 |
| **删除** | upper 层创建 whiteout 文件标记删除 |
---
### 查看镜像层
运行以下命令
```bash
## 查看镜像的层信息
$ docker history nginx:alpine
IMAGE CREATED CREATED BY SIZE
a6eb2a334a9f 2 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B
<missing> 2 weeks ago STOPSIGNAL SIGQUIT 0B
<missing> 2 weeks ago EXPOSE map[80/tcp:{}] 0B
<missing> 2 weeks ago ENTRYPOINT ["/docker-entrypoint.sh"] 0B
<missing> 2 weeks ago COPY 30-tune-worker-processes.sh /docker-ent… 4.62kB
...
## 查看层的存储位置
$ docker inspect nginx:alpine --format '{{json .GraphDriver.Data}}' | jq
{
"LowerDir": "/var/lib/docker/overlay2/.../diff:/var/lib/docker/overlay2/.../diff",
"MergedDir": "/var/lib/docker/overlay2/.../merged",
"UpperDir": "/var/lib/docker/overlay2/.../diff",
"WorkDir": "/var/lib/docker/overlay2/.../work"
}
```
---
### 最佳实践
为了构建高效轻量的镜像我们在使用联合文件系统时应注意以下几点
#### 1减少镜像层数
如下代码块所示展示了相关示例
```docker
## ❌ 每条命令创建一层
RUN apt-get update
RUN apt-get install -y nginx
RUN rm -rf /var/lib/apt/lists/*
## ✅ 合并为一层
RUN apt-get update && \
apt-get install -y nginx && \
rm -rf /var/lib/apt/lists/*
```
#### 2避免在容器中写入大量数据
容器层的写入性能低于直接写入大量数据应使用
- 数据卷 (Volume)
- 绑定挂载 (Bind Mount)
#### 3使用dockerignore
排除不需要的文件可以
- 减小构建上下文
- 避免创建不必要的层
---

View File

@@ -0,0 +1,3 @@
## 14.5 容器格式
最初Docker 采用了 `LXC` 中的容器格式 0.7 版本以后开始去除 LXC转而使用自行开发的 [libcontainer](https://github.com/docker/libcontainer),从 1.11 开始,则进一步演进为使用 [runC](https://github.com/opencontainers/runc) 和 [containerd](https://github.com/containerd/containerd)。

View File

@@ -0,0 +1,74 @@
## 14.6 Docker 网络实现
Docker 的网络实现其实就是利用了 Linux 上的网络命名空间和虚拟网络设备 (特别是 veth pair)建议先熟悉了解这两部分的基本概念再阅读本章
### 基本原理
首先要实现网络通信机器需要至少一个网络接口 (物理接口或虚拟接口) 来收发数据包此外如果不同子网之间要进行通信需要路由机制
Docker 中的网络接口默认都是虚拟的接口虚拟接口的优势之一是转发效率较高
Linux 通过在内核中进行数据复制来实现虚拟接口之间的数据转发发送接口的发送缓存中的数据包被直接复制到接收接口的接收缓存中对于本地系统和容器内系统看来就像是一个正常的以太网卡只是它不需要真正同外部网络设备通信速度要快很多
Docker 容器网络就利用了这项技术它在本地主机和容器内分别创建一个虚拟接口并让它们彼此连通 (这样的一对接口叫做 `veth pair`)
### 创建网络参数
Docker 创建一个容器的时候会执行如下操作
* 创建一对虚拟接口分别放到本地主机和新容器中
* 本地主机一端桥接到默认的 docker0 或指定网桥上并具有一个唯一的名字 veth65f9
* 容器一端放到新容器中并修改名字作为 eth0这个接口只在容器的命名空间可见
* 从网桥可用地址段中获取一个空闲地址分配给容器的 eth0并配置默认路由到桥接网卡 veth65f9
完成这些之后容器就可以使用 eth0 虚拟网卡来连接其他容器和其他网络
可以在 `docker run` 的时候通过 `--net` 参数来指定容器的网络配置 4 个可选值
* `--net=bridge` 这个是默认值连接到默认的网桥
* `--net=host` 告诉 Docker 不要将容器网络放到隔离的命名空间中即不要容器化容器内的网络此时容器使用本地主机的网络它拥有完全的本地主机接口访问权限容器进程可以跟主机其它 root 进程一样可以打开低范围的端口可以访问本地网络服务比如 D-bus还可以让容器做一些影响整个主机系统的事情比如重启主机因此使用这个选项的时候要非常小心如果进一步的使用 `--privileged=true`容器会被允许直接配置主机的网络堆栈
* `--net=container:NAME_or_ID` Docker 将新建容器的进程放到一个已存在容器的网络栈中新容器进程有自己的文件系统进程列表和资源限制但会和已存在的容器共享 IP 地址和端口等网络资源两者进程可以直接通过 `lo` 环回接口通信
* `--net=none` Docker 将新容器放到隔离的网络栈中但是不进行网络配置之后用户可以自己进行配置
### 网络配置细节
用户使用 `--net=none` 可以自行配置网络让容器达到跟平常一样具有访问网络的权限通过这个过程可以了解 Docker 配置网络的细节
首先启动一个 `/bin/bash` 容器指定 `--net=none` 参数
```bash
$ docker run -i -t --rm --net=none base /bin/bash
root@63f36fc01b5f:/#
```
在本地主机查找容器的进程 id并为它创建网络命名空间
```bash
$ docker inspect -f '{{.State.Pid}}' 63f36fc01b5f
2778
$ pid=2778
$ sudo mkdir -p /var/run/netns
$ sudo ln -s /proc/$pid/ns/net /var/run/netns/$pid
```
检查桥接网卡的 IP 和子网掩码信息
```bash
$ ip addr show docker0
21: docker0: ...
inet 172.17.42.1/16 scope global docker0
...
```
创建一对 veth pair 接口 A B绑定 A 到网桥 `docker0`并启用它
```bash
$ sudo ip link add A type veth peer name B
$ sudo brctl addif docker0 A
$ sudo ip link set A up
```
B 放到容器的网络命名空间命名为 eth0启动它并配置一个可用 IP (桥接网段) 和默认网关
```bash
$ sudo ip link set B netns $pid
$ sudo ip netns exec $pid ip link set dev B name eth0
$ sudo ip netns exec $pid ip link set eth0 up
$ sudo ip netns exec $pid ip addr add 172.17.42.99/16 dev eth0
$ sudo ip netns exec $pid ip route add default via 172.17.42.1
```
以上就是 Docker 配置网络的具体过程
当容器结束后Docker 会清空容器容器内的 eth0 会随网络命名空间一起被清除A 接口也被自动从 `docker0` 卸载
此外用户可以使用 `ip netns exec` 命令来在指定网络命名空间中进行配置从而配置容器内的网络

View File

@@ -0,0 +1,20 @@
# 第十一章 底层实现
Docker 底层的核心技术包括 Linux 上的命名空间 (Namespaces)控制组 (Control groups)Union 文件系统 (Union file systems) 和容器格式 (Container format)
我们知道传统的虚拟机通过在宿主主机中运行 hypervisor 来模拟一整套完整的硬件环境提供给虚拟机的操作系统虚拟机系统看到的环境是可限制的也是彼此隔离的
这种直接的做法实现了对资源最完整的封装但很多时候往往意味着系统资源的浪费
例如以宿主机和虚拟机系统都为 Linux 系统为例虚拟机中运行的应用其实可以利用宿主机系统中的运行环境
我们知道在操作系统中包括内核文件系统网络PIDUIDIPC内存硬盘CPU 等等所有的资源都是应用进程直接共享的
要想实现虚拟化除了要实现对内存CPU网络 IO硬盘 IO存储空间等的限制外还要实现文件系统网络PIDUIDIPC 等等的相互隔离
前者相对容易实现一些后者则需要宿主机系统的深入支持
随着 Linux 系统对于命名空间功能的完善实现程序员已经可以实现上面的所有需求让某些进程在彼此隔离的命名空间中运行大家虽然都共用一个内核和某些运行时环境 (例如一些系统命令和系统库)但是彼此却看不到都以为系统中只有自己的存在这种机制就是容器 (Container)利用命名空间来做权限的隔离控制利用 cgroups 来做资源分配
* [基本架构](18.1_arch.md)
* [命名空间](18.2_namespace.md)
* [控制组](18.3_cgroups.md)
* [联合文件系统](18.4_ufs.md)
* [容器格式](18.5_container_format.md)
* [网络](18.6_network.md)

View File

@@ -0,0 +1,46 @@
## 本章小结
相关信息如下表
| Namespace | 隔离内容 | 一句话说明 |
|-----------|---------|-----------|
| PID | 进程 ID | 容器有自己的进程树 |
| NET | 网络 | 容器有自己的 IP 和端口 |
| MNT | 文件系统 | 容器有自己的根目录 |
| UTS | 主机名 | 容器有自己的 hostname |
| IPC | 进程间通信 | 容器间 IPC 隔离 |
| USER | 用户 ID | 容器 root 宿主机 root |
### 延伸阅读
- [控制组 (Cgroups)](18.3_cgroups.md)资源限制机制
- [联合文件系统](18.4_ufs.md)分层存储的实现
- [安全](../17_security/README.md)容器安全实践
- [Linux Namespace 官方文档](https://man7.org/linux/man-pages/man7/namespaces.7.html)
| 资源 | 限制参数 | 示例 |
|------|---------|------|
| **内存** | `-m` | `-m 512m` |
| **CPU 核心数** | `--cpus` | `--cpus=1.5` |
| **CPU 绑定** | `--cpuset-cpus` | `--cpuset-cpus="0,1"` |
| **磁盘 I/O** | `--device-write-bps` | `--device-write-bps /dev/sda:10mb` |
| **进程数** | `--pids-limit` | `--pids-limit=100` |
### 延伸阅读
- [命名空间](18.2_namespace.md)资源隔离
- [安全](../17_security/README.md)容器安全概述
- [Docker Stats](../05_container/README.md)监控容器资源
| 概念 | 说明 |
|------|------|
| **UnionFS** | 将多层目录联合挂载为一个文件系统 |
| **Copy-on-Write** | 写时复制修改时才复制到可写层 |
| **overlay2** | Docker 默认推荐的存储驱动 |
| **分层好处** | 镜像复用快速构建快速启动 |
### 延伸阅读
- [镜像](../02_basic_concept/2.1_image.md)理解镜像分层
- [容器](../02_basic_concept/2.2_container.md)容器存储层
- [构建镜像](../04_image/4.5_build.md)Dockerfile 层的创建