mirror of
https://github.com/yeasy/docker_practice.git
synced 2026-03-23 02:13:50 +00:00
Fix and update
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
## 基本架构
|
||||
|
||||
Docker 的架构设计简洁而高效,主要由客户端和服务端两部分组成。
|
||||
|
||||
### 核心架构图
|
||||
|
||||
Docker 采用了 **C/S (客户端/服务端)** 架构。Client 向 Daemon 发送请求,Daemon 负责构建、运行和分发容器。
|
||||
@@ -25,29 +27,34 @@ graph LR
|
||||
|
||||
Docker 的内部架构如同洋葱一样分层,每一层专注解决特定问题:
|
||||
|
||||
#### 1. Docker CLI (客户端)
|
||||
#### 1. Docker CLI(客户端)
|
||||
|
||||
用户与 Docker 交互的主要方式。它将用户命令(如 `docker run`)转换为 API 请求发送给 dockerd。
|
||||
|
||||
#### 2. Dockerd (守护进程)
|
||||
#### 2. Dockerd(守护进程)
|
||||
|
||||
Docker 的大脑。
|
||||
- 监听 API 请求
|
||||
- 管理 Docker 对象(镜像、容器、网络、卷)
|
||||
- 编排下层组件完成工作
|
||||
|
||||
#### 3. Containerd (高级运行时)
|
||||
#### 3. Containerd(高级运行时)
|
||||
|
||||
行业标准的容器运行时(CNCF 毕业项目)。
|
||||
- 管理容器的完整生命周期(启动、停止)
|
||||
- 镜像拉取与存储
|
||||
- **不包含** 复杂的与容器无关的功能(如构建、API)
|
||||
- Kubernetes 也可以直接使用 containerd(跳过 Docker)
|
||||
|
||||
#### 4. Runc (低级运行时)
|
||||
#### 4. Runc(低级运行时)
|
||||
|
||||
用于创建和运行容器的 CLI 工具。
|
||||
- 直接与内核交互(Namespaces, Cgroups)
|
||||
- 遵循 OCI (Open Container Initiative) 规范
|
||||
- **主要职责**:根据配置启动一个容器,然后退出(将控制权交给容器进程)
|
||||
|
||||
#### 5. Shim
|
||||
|
||||
每个容器都有一个 shim 进程。
|
||||
- **解耦**:允许 dockerd 重启而不影响容器运行
|
||||
- **保持 IO**:维持容器的标准输入输出
|
||||
@@ -88,11 +95,11 @@ flowchart TD
|
||||
Shim -.-> |8. Monitor IO/Exit| Container
|
||||
```
|
||||
|
||||
1. **CLI** 发送请求给 **Dockerd**
|
||||
2. **Dockerd** 解析请求,调用 **Containerd**
|
||||
1. **CLI**发送请求给**Dockerd**
|
||||
2. **Dockerd**解析请求,调用**Containerd**
|
||||
3. **Containerd** 准备镜像,转换为 OCI Bundle
|
||||
4. **Containerd** 创建 **Shim** 进程
|
||||
5. **Shim** 调用 **Runc**
|
||||
4. **Containerd**创建**Shim** 进程
|
||||
5. **Shim**调用**Runc**
|
||||
6. **Runc** 与系统内核交互,创建 Namespaces 和 Cgroups
|
||||
7. **Runc** 启动 nginx 进程后退出
|
||||
8. **Shim** 接管容器 IO 和生命周期监控
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
## 命名空间
|
||||
|
||||
命名空间(Namespace)是 Linux 内核的一个强大特性,为容器提供了隔离的运行环境。
|
||||
|
||||
### 什么是 Namespace
|
||||
|
||||
> **Namespace 是 Linux 内核提供的资源隔离机制,它让容器内的进程仿佛运行在独立的操作系统中。**
|
||||
@@ -35,19 +37,25 @@ Linux 内核提供了以下几种 Namespace,Docker 容器使用了全部:
|
||||
|
||||
### PID Namespace
|
||||
|
||||
PID Namespace 负责进程 ID 的隔离,使得容器内的进程彼此不可见。
|
||||
|
||||
#### 作用
|
||||
|
||||
隔离进程 ID,让每个容器有自己的进程编号空间。
|
||||
|
||||
#### 效果
|
||||
|
||||
运行以下命令:
|
||||
|
||||
```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
|
||||
@@ -64,12 +72,16 @@ PID USER COMMAND
|
||||
|
||||
### NET Namespace
|
||||
|
||||
NET Namespace 负责网络栈的隔离,包括网卡、路由表和 iptables 规则等。
|
||||
|
||||
#### 作用
|
||||
|
||||
隔离网络栈,每个容器拥有独立的网络环境。
|
||||
|
||||
#### 效果
|
||||
|
||||
具体内容如下:
|
||||
|
||||
```
|
||||
宿主机 容器
|
||||
┌─────────────────────┐ ┌─────────────────────┐
|
||||
@@ -89,12 +101,16 @@ PID USER COMMAND
|
||||
|
||||
### MNT Namespace
|
||||
|
||||
MNT Namespace 负责文件系统挂载点的隔离,确保容器看到独立的文件系统视图。
|
||||
|
||||
#### 作用
|
||||
|
||||
隔离文件系统挂载点,每个容器有自己的根目录。
|
||||
|
||||
#### 效果
|
||||
|
||||
具体内容如下:
|
||||
|
||||
```
|
||||
宿主机文件系统: 容器内看到的:
|
||||
/ / ← 容器的根目录
|
||||
@@ -120,18 +136,24 @@ PID USER COMMAND
|
||||
|
||||
### UTS Namespace
|
||||
|
||||
UTS Namespace 主要用于隔离主机名和域名。
|
||||
|
||||
#### 作用
|
||||
|
||||
隔离主机名和域名,让每个容器可以有自己的主机名。
|
||||
|
||||
#### 效果
|
||||
|
||||
运行以下命令:
|
||||
|
||||
```bash
|
||||
## 宿主机
|
||||
|
||||
$ hostname
|
||||
my-server
|
||||
|
||||
## 容器内
|
||||
|
||||
$ docker run --hostname mycontainer ubuntu hostname
|
||||
mycontainer
|
||||
```
|
||||
@@ -142,6 +164,8 @@ UTS = "UNIX Time-sharing System",是历史遗留的名称。
|
||||
|
||||
### IPC Namespace
|
||||
|
||||
IPC Namespace 用于隔离进程间通信资源,如 System V IPC 和 POSIX 消息队列。
|
||||
|
||||
#### 作用
|
||||
|
||||
隔离 System V IPC 和 POSIX 消息队列。
|
||||
@@ -161,12 +185,16 @@ UTS = "UNIX Time-sharing System",是历史遗留的名称。
|
||||
|
||||
### USER Namespace
|
||||
|
||||
USER Namespace 允许将容器内的用户 ID 映射到宿主机的不同用户 ID。
|
||||
|
||||
#### 作用
|
||||
|
||||
隔离用户和组 ID,实现权限隔离。
|
||||
|
||||
#### 效果
|
||||
|
||||
具体内容如下:
|
||||
|
||||
```
|
||||
容器内 宿主机
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
@@ -189,16 +217,21 @@ UTS = "UNIX Time-sharing System",是历史遗留的名称。
|
||||
|
||||
#### 实验 1:UTS Namespace
|
||||
|
||||
运行以下命令:
|
||||
|
||||
```bash
|
||||
## 创建新的 UTS namespace 并启动 shell
|
||||
|
||||
$ sudo unshare --uts /bin/bash
|
||||
|
||||
## 修改主机名(只影响这个 namespace)
|
||||
|
||||
$ hostname container-test
|
||||
$ hostname
|
||||
container-test
|
||||
|
||||
## 退出后查看宿主机主机名(未改变)
|
||||
|
||||
$ exit
|
||||
$ hostname
|
||||
my-server
|
||||
@@ -206,14 +239,19 @@ my-server
|
||||
|
||||
#### 实验 2:PID 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
|
||||
@@ -222,11 +260,15 @@ root 8 0.0 0.0 10072 3200 pts/0 R+ 10:00 0:00 ps aux
|
||||
|
||||
#### 实验 3:NET 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
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
## 控制组
|
||||
|
||||
控制组(Cgroups)是 Linux 内核提供的另一种关键机制,主要用于资源的限制和审计。
|
||||
|
||||
### 什么是控制组
|
||||
|
||||
控制组(Control Groups,简称 cgroups)是 Linux 内核的一个特性,用于**限制、记录和隔离**进程组的资源使用(CPU、内存、磁盘 I/O、网络等)。
|
||||
@@ -46,16 +48,23 @@
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
@@ -68,14 +77,19 @@ $ docker run --memory-reservation 256m myapp
|
||||
|
||||
#### 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
|
||||
```
|
||||
|
||||
@@ -88,21 +102,29 @@ $ docker run --cpu-shares=512 myapp
|
||||
|
||||
#### 磁盘 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
|
||||
```
|
||||
|
||||
@@ -110,17 +132,22 @@ $ 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
|
||||
```
|
||||
|
||||
@@ -130,28 +157,42 @@ $ 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,表示被 SIGKILL (9) 杀死
|
||||
## 137 = 128 + 9,表示被 SIGKILL(9) 杀死
|
||||
|
||||
具体内容如下:
|
||||
|
||||
```
|
||||
|
||||
#### CPU 限制验证
|
||||
|
||||
运行以下命令:
|
||||
|
||||
```bash
|
||||
## 不限制 CPU
|
||||
|
||||
$ docker run --rm stress --cpu 4
|
||||
## 占满所有 CPU
|
||||
|
||||
## 限制为 1 个核心
|
||||
|
||||
$ docker run --rm --cpus=1 stress --cpu 4
|
||||
## 只能使用约 100% CPU(1 个核心)
|
||||
|
||||
具体内容如下:
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
@@ -168,13 +209,17 @@ $ docker run --rm --cpus=1 stress --cpu 4
|
||||
|
||||
#### 检查系统使用的版本
|
||||
|
||||
运行以下命令:
|
||||
|
||||
```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
|
||||
@@ -184,6 +229,8 @@ nodev cgroup2
|
||||
|
||||
### 在 Compose 中设置限制
|
||||
|
||||
在 Compose 中设置限制 配置如下:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
@@ -202,23 +249,33 @@ services:
|
||||
|
||||
### 最佳实践
|
||||
|
||||
在使用 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 \
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
## 联合文件系统
|
||||
|
||||
联合文件系统(UnionFS)是 Docker 镜像分层存储的基础,它允许将多个目录挂载为同一个虚拟文件系统。
|
||||
|
||||
### 什么是联合文件系统
|
||||
|
||||
联合文件系统(UnionFS)是一种**分层、轻量级**的文件系统,它将多个目录"联合"挂载到同一个虚拟目录,形成一个统一的文件系统视图。
|
||||
@@ -31,8 +33,12 @@
|
||||
|
||||
### 为什么 Docker 使用联合文件系统
|
||||
|
||||
Docker 选择联合文件系统作为其存储驱动,主要基于以下几个核心优势。
|
||||
|
||||
#### 1. 镜像分层复用
|
||||
|
||||
具体内容如下:
|
||||
|
||||
```
|
||||
nginx:alpine myapp:latest
|
||||
│ │
|
||||
@@ -94,7 +100,7 @@ Docker 可使用多种联合文件系统实现:
|
||||
|
||||
| 存储驱动 | 说明 | 推荐程度 |
|
||||
|---------|------|---------|
|
||||
| **overlay2** | 现代 Linux 默认驱动,性能优秀 | ✅ **推荐** |
|
||||
| **overlay2**| 现代 Linux 默认驱动,性能优秀 | ✅**推荐** |
|
||||
| **aufs** | 早期默认,兼容性好 | 遗留系统 |
|
||||
| **btrfs** | 使用 Btrfs 子卷 | 特定场景 |
|
||||
| **zfs** | 使用 ZFS 数据集 | 特定场景 |
|
||||
@@ -113,6 +119,8 @@ Docker 可使用多种联合文件系统实现:
|
||||
|
||||
#### 查看当前存储驱动
|
||||
|
||||
运行以下命令:
|
||||
|
||||
```bash
|
||||
$ docker info | grep "Storage Driver"
|
||||
Storage Driver: overlay2
|
||||
@@ -161,8 +169,11 @@ overlay2 是目前最推荐的存储驱动:
|
||||
|
||||
### 查看镜像层
|
||||
|
||||
运行以下命令:
|
||||
|
||||
```bash
|
||||
## 查看镜像的层信息
|
||||
|
||||
$ docker history nginx:alpine
|
||||
IMAGE CREATED CREATED BY SIZE
|
||||
a6eb2a334a9f 2 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B
|
||||
@@ -173,6 +184,7 @@ a6eb2a334a9f 2 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B
|
||||
...
|
||||
|
||||
## 查看层的存储位置
|
||||
|
||||
$ docker inspect nginx:alpine --format '{{json .GraphDriver.Data}}' | jq
|
||||
{
|
||||
"LowerDir": "/var/lib/docker/overlay2/.../diff:/var/lib/docker/overlay2/.../diff",
|
||||
@@ -186,15 +198,21 @@ $ docker inspect nginx:alpine --format '{{json .GraphDriver.Data}}' | jq
|
||||
|
||||
### 最佳实践
|
||||
|
||||
为了构建高效、轻量的镜像,我们在使用联合文件系统时应注意以下几点。
|
||||
|
||||
#### 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/*
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
Docker 的网络实现其实就是利用了 Linux 上的网络命名空间和虚拟网络设备(特别是 veth pair)。建议先熟悉了解这两部分的基本概念再阅读本章。
|
||||
|
||||
### 基本原理
|
||||
|
||||
首先,要实现网络通信,机器需要至少一个网络接口(物理接口或虚拟接口)来收发数据包;此外,如果不同子网之间要进行通信,需要路由机制。
|
||||
|
||||
Docker 中的网络接口默认都是虚拟的接口。虚拟接口的优势之一是转发效率较高。
|
||||
@@ -11,6 +12,7 @@ Linux 通过在内核中进行数据复制来实现虚拟接口之间的数据
|
||||
Docker 容器网络就利用了这项技术。它在本地主机和容器内分别创建一个虚拟接口,并让它们彼此连通(这样的一对接口叫做 `veth pair`)。
|
||||
|
||||
### 创建网络参数
|
||||
|
||||
Docker 创建一个容器的时候,会执行如下操作:
|
||||
* 创建一对虚拟接口,分别放到本地主机和新容器中;
|
||||
* 本地主机一端桥接到默认的 docker0 或指定网桥上,并具有一个唯一的名字,如 veth65f9;
|
||||
@@ -26,6 +28,7 @@ Docker 创建一个容器的时候,会执行如下操作:
|
||||
* `--net=none` 让 Docker 将新容器放到隔离的网络栈中,但是不进行网络配置。之后,用户可以自己进行配置。
|
||||
|
||||
### 网络配置细节
|
||||
|
||||
用户使用 `--net=none` 后,可以自行配置网络,让容器达到跟平常一样具有访问网络的权限。通过这个过程,可以了解 Docker 配置网络的细节。
|
||||
|
||||
首先,启动一个 `/bin/bash` 容器,指定 `--net=none` 参数。
|
||||
|
||||
Reference in New Issue
Block a user