Subsections of Container
Build Smaller Image
减小 Dockerfile 生成镜像体积的方法
1. 选择更小的基础镜像
# ❌ 避免使用完整版本
FROM ubuntu:latest
# ✅ 使用精简版本
FROM alpine:3.18
FROM python:3.11-slim
FROM node:18-alpine2. 使用多阶段构建 (Multi-stage Build)
这是最有效的方法之一:
# 构建阶段
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# 运行阶段 - 只复制必要文件
FROM alpine:3.18
WORKDIR /app
COPY --from=builder /app/myapp .
CMD ["./myapp"]3. 合并 RUN 指令
每个 RUN 命令都会创建一个新层:
# ❌ 多层,体积大
RUN apt-get update
RUN apt-get install -y package1
RUN apt-get install -y package2
# ✅ 单层,并清理缓存
RUN apt-get update && \
apt-get install -y package1 package2 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*4. 清理不必要的文件
RUN apt-get update && \
apt-get install -y build-essential && \
# 构建操作... && \
apt-get purge -y build-essential && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*5. 使用 .dockerignore 文件
# .dockerignore
node_modules
.git
*.md
.env
test/6. 只复制必要的文件
# ❌ 复制所有内容
COPY . .
# ✅ 只复制需要的文件
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY src/ ./src/7. 移除调试工具和文档
RUN apk add --no-cache python3 && \
rm -rf /usr/share/doc /usr/share/man8. 压缩和优化层
# 在单个 RUN 中完成所有操作
RUN set -ex && \
apk add --no-cache --virtual .build-deps gcc musl-dev && \
pip install --no-cache-dir -r requirements.txt && \
apk del .build-deps9. 使用专门的工具
- dive: 分析镜像层
dive your-image:tag - docker-slim: 自动精简镜像
docker-slim build your-image:tag
实际案例对比
优化前 (1.2GB):
FROM ubuntu:20.04
RUN apt-get update
RUN apt-get install -y python3 python3-pip
COPY . /app
WORKDIR /app
RUN pip3 install -r requirements.txt
CMD ["python3", "app.py"]优化后 (50MB):
FROM python:3.11-alpine AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
FROM python:3.11-alpine
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY app.py .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]关键要点总结
✅ 使用 Alpine 或 slim 镜像
✅ 采用多阶段构建
✅ 合并命令并清理缓存
✅ 配置 .dockerignore
✅ 只安装生产环境依赖
✅ 删除构建工具和临时文件
通过这些方法,镜像体积通常可以减少 60-90%!
Network Mode
Docker的网络模式决定了容器如何与宿主机、其他容器以及外部网络进行通信。
Docker主要提供了以下五种网络模式,默认创建的是 bridge 模式。
1. Bridge 模式
这是 默认 的网络模式。当你创建一个容器而不指定网络时,它就会连接到这个默认的 bridge 网络(名为 bridge)。
- 工作原理:Docker守护进程会创建一个名为
docker0的虚拟网桥,它相当于一个虚拟交换机。所有使用该模式的容器都会通过一个虚拟网卡(veth pair)连接到这个网桥上。Docker会为每个容器分配一个IP地址,并配置其网关为docker0的地址。 - 通信方式:
- 容器间通信:在同一个自定义桥接网络下的容器,可以通过容器名(Container Name)直接通信(Docker内嵌了DNS)。但在默认的
bridge网络下,容器只能通过IP地址通信。 - 访问外部网络:容器数据包通过
docker0网桥,再经过宿主机的IPtables进行NAT转换,使用宿主机的IP访问外网。 - 从外部访问容器:需要做端口映射,例如
-p 8080:80,将宿主机的8080端口映射到容器的80端口。
- 容器间通信:在同一个自定义桥接网络下的容器,可以通过容器名(Container Name)直接通信(Docker内嵌了DNS)。但在默认的
优劣分析
- 优点:
- 隔离性:容器拥有独立的网络命名空间,与宿主机和其他网络隔离,安全性较好。
- 端口管理灵活:通过端口映射,可以灵活地管理哪些宿主机端口暴露给外部。
- 通用性:是最常用、最通用的模式,适合大多数应用场景。
- 缺点:
- 性能开销:相比
host模式,多了一层网络桥接和NAT,性能有轻微损失。 - 复杂度:在默认桥接网络中,容器间通信需要使用IP,不如自定义网络方便。
- 性能开销:相比
使用场景:绝大多数需要网络隔离的独立应用,例如Web后端服务、数据库等。
命令示例:
# 使用默认bridge网络(不推荐用于多容器应用)
docker run -d --name my-app -p 8080:80 nginx
# 创建自定义bridge网络(推荐)
docker network create my-network
docker run -d --name app1 --network my-network my-app
docker run -d --name app2 --network my-network another-app
# 现在 app1 和 app2 可以通过容器名直接互相访问2. Host 模式
在这种模式下,容器不会虚拟出自己的网卡,也不会分配独立的IP,而是直接使用宿主机的IP和端口。
- 工作原理:容器与宿主机共享同一个Network Namespace。
优劣分析
- 优点:
- 高性能:由于没有NAT和网桥开销,网络性能最高,几乎与宿主机原生网络一致。
- 简单:无需进行复杂的端口映射,容器内使用的端口就是宿主机上的端口。
- 缺点:
- 安全性差:容器没有网络隔离,可以直接操作宿主机的网络。
- 端口冲突:容器使用的端口如果与宿主机服务冲突,会导致容器无法启动。
- 灵活性差:无法在同一台宿主机上运行多个使用相同端口的容器。
使用场景:对网络性能要求极高的场景,例如负载均衡器、高频交易系统等。在生产环境中需谨慎使用。
命令示例:
docker run -d --name my-app --network host nginx
# 此时,直接访问 http://<宿主机IP>:80 即可访问容器中的Nginx3. None 模式
在这种模式下,容器拥有自己独立的网络命名空间,但不进行任何网络配置。容器内部只有回环地址 127.0.0.1。
- 工作原理:容器完全与世隔绝。
优劣分析
- 优点:
- 绝对隔离:安全性最高,容器完全无法进行任何网络通信。
- 缺点:
- 无法联网:容器无法与宿主机、其他容器或外部网络通信。
使用场景:
- 需要完全离线处理的批处理任务。
- 用户打算使用自定义网络驱动(或手动配置)来完全自定义容器的网络栈。
命令示例:
docker run -d --name my-app --network none alpine
# 进入容器后,使用 `ip addr` 查看,只能看到 lo 网卡4. Container 模式
这种模式下,新创建的容器不会创建自己的网卡和IP,而是与一个已经存在的容器共享一个Network Namespace。通俗讲,就是两个容器在同一个网络环境下,看到的IP和端口是一样的。
- 工作原理:新容器复用指定容器的网络栈。
优劣分析
- 优点:
- 高效通信:容器间通信直接通过本地回环地址
127.0.0.1,效率极高。 - 共享网络视图:可以方便地为一个主容器(如Web服务器)搭配一个辅助容器(如日志收集器),它们看到的网络环境完全一致。
- 高效通信:容器间通信直接通过本地回环地址
- 缺点:
- 紧密耦合:两个容器的生命周期和网络配置紧密绑定,缺乏灵活性。
- 隔离性差:共享网络命名空间,存在一定的安全风险。
使用场景:Kubernetes中的"边车"模式,例如一个Pod内的主容器和日志代理容器。
命令示例:
docker run -d --name main-container nginx
docker run -d --name helper-container --network container:main-container busybox
# 此时,helper-container 中访问 127.0.0.1:80 就是在访问 main-container 的Nginx服务5. Overlay 模式
这是为了实现 跨主机的容器通信 而设计的,是Docker Swarm和Kubernetes等容器编排系统的核心网络方案。
- 工作原理:它会在多个Docker宿主机之间创建一个虚拟的分布式网络(Overlay Network),通过VXLAN等隧道技术,让不同宿主机上的容器感觉像是在同一个大的局域网内。
优劣分析
- 优点:
- 跨节点通信:解决了集群环境下容器间通信的根本问题。
- 安全:支持网络加密。
- 缺点:
- 配置复杂:需要额外的Key-Value存储(如Consul、Etcd)来同步网络状态(Docker Swarm模式内置了此功能)。
- 性能开销:数据包需要封装和解封装,有一定性能损耗,但现代硬件上通常可以接受。
使用场景:Docker Swarm集群、Kubernetes集群等分布式应用环境。
命令示例(在Swarm模式下):
# 初始化Swarm
docker swarm init
# 创建Overlay网络
docker network create -d overlay my-overlay-net
# 在Overlay网络中创建服务
docker service create --name web --network my-overlay-net -p 80:80 nginx总结对比
| 网络模式 | 隔离性 | 性能 | 灵活性 | 适用场景 |
|---|---|---|---|---|
| Bridge(默认) | 高 | 良好 | 高 | 通用场景,单机多容器应用 |
| Host | 无 | 最高 | 低 | 对性能要求极致,不介意端口冲突 |
| None | 最高 | - | 低 | 离线任务,完全自定义网络 |
| Container | 低 | 高 | 低 | 容器紧密协作(如边车模式) |
| Overlay | 高 | 良好 | 高 | 集群场景,跨主机容器通信 |
最佳实践建议
- 单机应用:优先使用 自定义的Bridge网络,它比默认Bridge网络提供了更好的DNS服务发现功能,方便容器间通过名称通信。
- 集群应用:必须使用 Overlay网络。
- 性能极致追求:在确认端口安全和无冲突的前提下,可考虑 Host模式。
- 安全隔离:对于无需网络的容器,使用 None模式。
- 避免在生产环境大量使用默认的
bridge网络和container模式,因为它们分别在DNS发现和容器耦合度上存在不足。
Container Principle
Linux 容器技术的基础原理
容器技术是现代云原生架构的基石。让我深入讲解 Linux 容器的核心原理。
🎯 容器的本质
容器 = 特殊的进程
容器不是虚拟机,它本质上是宿主机上的一个受限进程,通过 Linux 内核的三大核心技术实现隔离和资源限制:
┌─────────────────────────────────────────┐
│ Linux 容器三大支柱 │
├─────────────────────────────────────────┤
│ 1. Namespace (命名空间) - 隔离 │
│ 2. Cgroups (控制组) - 资源限制 │
│ 3. Union FS (联合文件系统) - 镜像分层 │
└─────────────────────────────────────────┘🔒 Namespace (命名空间) - 视图隔离
Namespace 是 Linux 内核提供的一种资源隔离机制,让进程只能看到属于自己的资源。
七种 Namespace
| Namespace | 隔离内容 | 内核版本 | 示例 |
|---|---|---|---|
| PID | 进程 ID | 2.6.24 | 容器内 PID 1 = 宿主机 PID 12345 |
| Network | 网络栈 | 2.6.29 | 独立的 IP、端口、路由表 |
| Mount | 文件系统挂载点 | 2.4.19 | 独立的根目录 |
| UTS | 主机名和域名 | 2.6.19 | 容器有自己的 hostname |
| IPC | 进程间通信 | 2.6.19 | 消息队列、信号量、共享内存 |
| User | 用户和组 ID | 3.8 | 容器内 root ≠ 宿主机 root |
| Cgroup | Cgroup 根目录 | 4.6 | 隔离 cgroup 视图 |
1️⃣ PID Namespace (进程隔离)
原理
每个容器有独立的进程树,容器内看不到宿主机或其他容器的进程。
演示
# 在宿主机上查看进程
ps aux | grep nginx
# root 12345 nginx: master process
# 进入容器
docker exec -it my-container bash
# 在容器内查看进程
ps aux
# PID USER COMMAND
# 1 root nginx: master process ← 容器内看到的 PID 是 1
# 25 root nginx: worker process
# 实际上宿主机上这个进程的真实 PID 是 12345手动创建 PID Namespace
// C 代码示例
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int child_func(void* arg) {
printf("Child PID: %d\n", getpid()); // 输出: 1
sleep(100);
return 0;
}
int main() {
printf("Parent PID: %d\n", getpid()); // 输出: 真实 PID
// 创建新的 PID namespace
char stack[1024*1024];
int flags = CLONE_NEWPID;
pid_t pid = clone(child_func, stack + sizeof(stack), flags | SIGCHLD, NULL);
waitpid(pid, NULL, 0);
return 0;
}核心特点
- 容器内第一个进程 PID = 1 (init 进程)
- 父进程(宿主机)可以看到子进程的真实 PID
- 子进程(容器)看不到父进程和其他容器的进程
2️⃣ Network Namespace (网络隔离)
原理
每个容器有独立的网络栈:独立的 IP、端口、路由表、防火墙规则。
架构图
宿主机网络栈
├─ eth0 (物理网卡)
├─ docker0 (网桥)
└─ veth pairs (虚拟网卡对)
├─ vethXXX (宿主机端) ←→ eth0 (容器端)
└─ vethYYY (宿主机端) ←→ eth0 (容器端)演示
# 创建新的 network namespace
ip netns add myns
# 列出所有 namespace
ip netns list
# 在新 namespace 中执行命令
ip netns exec myns ip addr
# 输出: 只有 loopback,没有 eth0
# 创建 veth pair (虚拟网卡对)
ip link add veth0 type veth peer name veth1
# 将 veth1 移到新 namespace
ip link set veth1 netns myns
# 配置 IP
ip addr add 192.168.1.1/24 dev veth0
ip netns exec myns ip addr add 192.168.1.2/24 dev veth1
# 启动网卡
ip link set veth0 up
ip netns exec myns ip link set veth1 up
ip netns exec myns ip link set lo up
# 测试连通性
ping 192.168.1.2容器网络模式
Bridge 模式(默认)
Container A Container B
│ │
[eth0] [eth0]
│ │
vethA ←─────┬─────────→ vethB
│
[docker0 网桥]
│
[iptables NAT]
│
[宿主机 eth0]
│
外部网络Host 模式
Container
│
└─ 直接使用宿主机网络栈 (没有网络隔离)3️⃣ Mount Namespace (文件系统隔离)
原理
每个容器有独立的挂载点视图,看到不同的文件系统树。
演示
# 创建隔离的挂载环境
unshare --mount /bin/bash
# 在新 namespace 中挂载
mount -t tmpfs tmpfs /tmp
# 查看挂载点
mount | grep tmpfs
# 这个挂载只在当前 namespace 可见
# 退出后,宿主机看不到这个挂载
exit
mount | grep tmpfs # 找不到容器的根文件系统
# Docker 使用 chroot + pivot_root 切换根目录
# 容器内 / 实际是宿主机的某个目录
# 查看容器的根文件系统位置
docker inspect my-container | grep MergedDir
# "MergedDir": "/var/lib/docker/overlay2/xxx/merged"
# 在宿主机上访问容器文件系统
ls /var/lib/docker/overlay2/xxx/merged
# bin boot dev etc home lib ...4️⃣ UTS Namespace (主机名隔离)
演示
# 在宿主机
hostname
# host-machine
# 创建新 UTS namespace
unshare --uts /bin/bash
# 修改主机名
hostname my-container
# 查看主机名
hostname
# my-container
# 退出后,宿主机主机名不变
exit
hostname
# host-machine5️⃣ IPC Namespace (进程间通信隔离)
原理
隔离 System V IPC 和 POSIX 消息队列。
演示
# 在宿主机创建消息队列
ipcmk -Q
# Message queue id: 0
# 查看消息队列
ipcs -q
# ------ Message Queues --------
# key msqid owner
# 0x52020055 0 root
# 进入容器
docker exec -it my-container bash
# 在容器内查看消息队列
ipcs -q
# ------ Message Queues --------
# (空,看不到宿主机的消息队列)6️⃣ User Namespace (用户隔离)
原理
容器内的 root 用户可以映射到宿主机的普通用户,增强安全性。
配置示例
# 启用 User Namespace 的容器
docker run --userns-remap=default -it ubuntu bash
# 容器内
whoami
# root
id
# uid=0(root) gid=0(root) groups=0(root)
# 但在宿主机上,这个进程实际运行在普通用户下
ps aux | grep bash
# 100000 12345 bash ← UID 100000,不是 rootUID 映射配置
# /etc/subuid 和 /etc/subgid
cat /etc/subuid
# dockremap:100000:65536
# 表示将容器内的 UID 0-65535 映射到宿主机的 100000-165535📊 Cgroups (Control Groups) - 资源限制
Cgroups 用于限制、记录、隔离进程组的资源使用(CPU、内存、磁盘 I/O 等)。
Cgroups 子系统
| 子系统 | 功能 | 示例 |
|---|---|---|
| cpu | 限制 CPU 使用率 | 容器最多用 50% CPU |
| cpuset | 绑定特定 CPU 核心 | 容器只能用 CPU 0-3 |
| memory | 限制内存使用 | 容器最多用 512MB 内存 |
| blkio | 限制块设备 I/O | 容器磁盘读写 100MB/s |
| devices | 控制设备访问 | 容器不能访问 /dev/sda |
| net_cls | 网络流量分类 | 为容器流量打标签 |
| pids | 限制进程数量 | 容器最多创建 100 个进程 |
CPU 限制
原理
使用 CFS (Completely Fair Scheduler) 调度器限制 CPU 时间。
关键参数
cpu.cfs_period_us # 周期时间(默认 100ms = 100000us)
cpu.cfs_quota_us # 配额时间
# CPU 使用率 = quota / period
# 例如: 50000 / 100000 = 50% CPUDocker 示例
# 限制容器使用 0.5 个 CPU 核心
docker run --cpus=0.5 nginx
# 等价于
docker run --cpu-period=100000 --cpu-quota=50000 nginx
# 查看 cgroup 配置
cat /sys/fs/cgroup/cpu/docker/<container-id>/cpu.cfs_quota_us
# 50000手动配置 Cgroups
# 创建 cgroup
mkdir -p /sys/fs/cgroup/cpu/mycontainer
# 设置 CPU 限制为 50%
echo 50000 > /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_quota_us
echo 100000 > /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_period_us
# 将进程加入 cgroup
echo $$ > /sys/fs/cgroup/cpu/mycontainer/cgroup.procs
# 运行 CPU 密集任务
yes > /dev/null &
# 在另一个终端查看 CPU 使用率
top -p $(pgrep yes)
# CPU 使用率被限制在 50% 左右内存限制
关键参数
memory.limit_in_bytes # 硬限制
memory.soft_limit_in_bytes # 软限制
memory.oom_control # OOM 行为控制
memory.usage_in_bytes # 当前使用量Docker 示例
# 限制容器使用最多 512MB 内存
docker run -m 512m nginx
# 查看内存限制
cat /sys/fs/cgroup/memory/docker/<container-id>/memory.limit_in_bytes
# 536870912 (512MB)
# 查看当前内存使用
cat /sys/fs/cgroup/memory/docker/<container-id>/memory.usage_in_bytesOOM (Out of Memory) 行为
# 当容器超过内存限制时
# 1. 内核触发 OOM Killer
# 2. 杀死容器内的进程(通常是内存占用最大的)
# 3. 容器退出,状态码 137
docker ps -a
# CONTAINER ID STATUS
# abc123 Exited (137) 1 minute ago ← OOM killed避免 OOM 的策略
# 设置 OOM Score Adjustment
docker run --oom-score-adj=-500 nginx
# 数值越低,越不容易被 OOM Killer 杀死
# 禁用 OOM Killer (不推荐生产环境)
docker run --oom-kill-disable nginx磁盘 I/O 限制
Docker 示例
# 限制读取速度为 10MB/s
docker run --device-read-bps /dev/sda:10mb nginx
# 限制写入速度为 5MB/s
docker run --device-write-bps /dev/sda:5mb nginx
# 限制 IOPS
docker run --device-read-iops /dev/sda:100 nginx
docker run --device-write-iops /dev/sda:50 nginx测试 I/O 限制
# 在容器内测试写入速度
docker exec -it my-container bash
dd if=/dev/zero of=/tmp/test bs=1M count=100
# 写入速度会被限制在 5MB/s📦 Union FS (联合文件系统) - 镜像分层
Union FS 允许多个文件系统分层叠加,实现镜像的复用和高效存储。
核心概念
容器可写层 (Read-Write Layer) ← 容器运行时的修改
─────────────────────────────────
镜像层 4 (Image Layer 4) ← 只读
镜像层 3 (Image Layer 3) ← 只读
镜像层 2 (Image Layer 2) ← 只读
镜像层 1 (Base Layer) ← 只读
─────────────────────────────────
统一挂载点
(Union Mount Point)常见实现
| 文件系统 | 特点 | 使用情况 |
|---|---|---|
| OverlayFS | 性能好,内核原生支持 | Docker 默认(推荐) |
| AUFS | 成熟稳定,但不在主线内核 | 早期 Docker 默认 |
| Btrfs | 支持快照,写时复制 | 适合大规模存储 |
| ZFS | 企业级功能,但有许可问题 | 高级用户 |
| Device Mapper | 块级存储 | Red Hat 系列 |
OverlayFS 原理
目录结构
/var/lib/docker/overlay2/<image-id>/
├── diff/ # 当前层的文件变更
├── link # 短链接名称
├── lower # 指向下层的链接
├── merged/ # 最终挂载点(容器看到的)
└── work/ # 工作目录(临时文件)实际演示
# 查看镜像的层结构
docker image inspect nginx:latest | jq '.[0].RootFS.Layers'
# [
# "sha256:abc123...", ← Layer 1
# "sha256:def456...", ← Layer 2
# "sha256:ghi789..." ← Layer 3
# ]
# 启动容器
docker run -d --name web nginx
# 查看容器的文件系统
docker inspect web | grep MergedDir
# "MergedDir": "/var/lib/docker/overlay2/xxx/merged"
# 查看挂载信息
mount | grep overlay
# overlay on /var/lib/docker/overlay2/xxx/merged type overlay (rw,lowerdir=...,upperdir=...,workdir=...)文件操作的 Copy-on-Write (写时复制)
# 1. 读取文件(从镜像层)
docker exec web cat /etc/nginx/nginx.conf
# 直接从只读的镜像层读取,无需复制
# 2. 修改文件
docker exec web bash -c "echo 'test' >> /etc/nginx/nginx.conf"
# 触发 Copy-on-Write:
# - 从下层复制文件到容器可写层
# - 在可写层修改文件
# - 下次读取时,从可写层读取(覆盖下层)
# 3. 删除文件
docker exec web rm /var/log/nginx/access.log
# 创建 whiteout 文件,标记删除
# 文件在镜像层仍存在,但容器内看不到Whiteout 文件(删除标记)
# 在容器可写层
ls -la /var/lib/docker/overlay2/xxx/diff/var/log/nginx/
# c--------- 1 root root 0, 0 Oct 11 10:00 .wh.access.log
# 字符设备文件,主次设备号都是 0,表示删除标记镜像分层的优势
1. 共享层,节省空间
# 假设有 10 个基于 ubuntu:20.04 的镜像
# 不使用分层:10 × 100MB = 1GB
# 使用分层:100MB (ubuntu base) + 10 × 10MB (应用层) = 200MB
# 节省空间:80%2. 快速构建
FROM ubuntu:20.04 # Layer 1 (缓存)
RUN apt-get update # Layer 2 (缓存)
RUN apt-get install -y nginx # Layer 3 (缓存)
COPY app.conf /etc/nginx/ # Layer 4 (需要重建)
COPY app.js /var/www/ # Layer 5 (需要重建)
# 如果只修改 app.js,只需要重建 Layer 5
# 前面的层都从缓存读取3. 快速分发
# 拉取镜像时,只下载本地没有的层
docker pull nginx:1.21
# Already exists: Layer 1 (ubuntu base)
# Downloading: Layer 2 (nginx files)
# Downloading: Layer 3 (config)🔗 容器技术完整流程
Docker 创建容器的完整过程
docker run -d --name web \
--cpus=0.5 \
-m 512m \
-p 8080:80 \
nginx:latest内部执行流程
1. 拉取镜像(如果本地没有)
└─ 下载各层,存储到 /var/lib/docker/overlay2/
2. 创建 Namespace
├─ PID Namespace (隔离进程)
├─ Network Namespace (隔离网络)
├─ Mount Namespace (隔离文件系统)
├─ UTS Namespace (隔离主机名)
├─ IPC Namespace (隔离进程间通信)
└─ User Namespace (隔离用户)
3. 配置 Cgroups
├─ cpu.cfs_quota_us = 50000 (50% CPU)
└─ memory.limit_in_bytes = 536870912 (512MB)
4. 挂载文件系统 (OverlayFS)
├─ lowerdir: 镜像只读层
├─ upperdir: 容器可写层
├─ workdir: 工作目录
└─ merged: 统一视图挂载点
5. 配置网络
├─ 创建 veth pair
├─ 一端连接到容器的 Network Namespace
├─ 另一端连接到 docker0 网桥
├─ 分配 IP 地址
└─ 配置 iptables NAT 规则 (端口映射)
6. 切换根目录
├─ chroot 或 pivot_root
└─ 容器内看到的 / 是 merged 目录
7. 启动容器进程
├─ 在新的 Namespace 中
├─ 受 Cgroups 限制
└─ 使用新的根文件系统
└─ 执行 ENTRYPOINT/CMD
8. 容器运行中
└─ containerd-shim 监控进程🛠️ 手动创建容器(无 Docker)
完整示例:从零创建容器
#!/bin/bash
# 手动创建一个简单的容器
# 1. 准备根文件系统
mkdir -p /tmp/mycontainer/rootfs
cd /tmp/mycontainer/rootfs
# 下载 busybox 作为基础系统
wget https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox
chmod +x busybox
./busybox --install -s .
# 创建必要的目录
mkdir -p bin sbin etc proc sys tmp dev
# 2. 创建启动脚本
cat > /tmp/mycontainer/start.sh <<'EOF'
#!/bin/bash
# 创建新的 namespace
unshare --pid --net --mount --uts --ipc --fork /bin/bash -c '
# 挂载 proc
mount -t proc proc /proc
# 设置主机名
hostname mycontainer
# 启动 shell
/bin/sh
'
EOF
chmod +x /tmp/mycontainer/start.sh
# 3. 启动容器
chroot /tmp/mycontainer/rootfs /tmp/mycontainer/start.sh配置 Cgroups 限制
# 创建 cgroup
mkdir -p /sys/fs/cgroup/memory/mycontainer
mkdir -p /sys/fs/cgroup/cpu/mycontainer
# 设置内存限制 256MB
echo 268435456 > /sys/fs/cgroup/memory/mycontainer/memory.limit_in_bytes
# 设置 CPU 限制 50%
echo 50000 > /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_quota_us
echo 100000 > /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_period_us
# 将容器进程加入 cgroup
echo $CONTAINER_PID > /sys/fs/cgroup/memory/mycontainer/cgroup.procs
echo $CONTAINER_PID > /sys/fs/cgroup/cpu/mycontainer/cgroup.procs🔍 容器 vs 虚拟机
架构对比
虚拟机架构:
┌─────────────────────────────────────┐
│ App A │ App B │ App C │
├─────────┼─────────┼─────────────────┤
│ Bins/Libs│ Bins/Libs│ Bins/Libs │
├─────────┼─────────┼─────────────────┤
│ Guest OS│ Guest OS│ Guest OS │ ← 每个 VM 都有完整 OS
├─────────┴─────────┴─────────────────┤
│ Hypervisor (VMware/KVM) │
├─────────────────────────────────────┤
│ Host Operating System │
├─────────────────────────────────────┤
│ Hardware │
└─────────────────────────────────────┘
容器架构:
┌─────────────────────────────────────┐
│ App A │ App B │ App C │
├─────────┼─────────┼─────────────────┤
│ Bins/Libs│ Bins/Libs│ Bins/Libs │
├─────────────────────────────────────┤
│ Docker Engine / containerd │
├─────────────────────────────────────┤
│ Host Operating System (Linux) │ ← 共享内核
├─────────────────────────────────────┤
│ Hardware │
└─────────────────────────────────────┘性能对比
| 维度 | 虚拟机 | 容器 |
|---|---|---|
| 启动时间 | 分钟级 | 秒级 |
| 资源占用 | GB 级内存 | MB 级内存 |
| 性能开销 | 5-10% | < 1% |
| 隔离程度 | 完全隔离(硬件级) | 进程隔离(OS 级) |
| 安全性 | 更高(独立内核) | 较低(共享内核) |
| 密度 | 每台物理机 10-50 个 | 每台物理机 100-1000 个 |
⚠️ 容器的安全性考虑
1. 共享内核的风险
# 容器逃逸:如果内核有漏洞,容器可能逃逸到宿主机
# 缓解措施:
# - 使用 User Namespace
# - 运行容器为非 root 用户
# - 使用 Seccomp 限制系统调用
# - 使用 AppArmor/SELinux2. 特权容器的危险
# 特权容器可以访问宿主机所有设备
docker run --privileged ...
# ❌ 危险:容器内可以:
# - 加载内核模块
# - 访问宿主机所有设备
# - 修改宿主机网络配置
# - 读写宿主机任意文件
# ✅ 最佳实践:避免使用特权容器3. Capability 控制
# 只授予容器必要的权限
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE nginx
# 默认 Docker 授予的 Capabilities:
# - CHOWN, DAC_OVERRIDE, FOWNER, FSETID
# - KILL, SETGID, SETUID, SETPCAP
# - NET_BIND_SERVICE, NET_RAW
# - SYS_CHROOT, MKNOD, AUDIT_WRITE, SETFCAP💡 关键要点总结
容器 = Namespace + Cgroups + Union FS
Namespace (隔离)
- PID: 进程隔离
- Network: 网络隔离
- Mount: 文件系统隔离
- UTS: 主机名隔离
- IPC: 进程间通信隔离
- User: 用户隔离
Cgroups (限制)
- CPU: 限制处理器使用
- Memory: 限制内存使用
- Block I/O: 限制磁盘 I/O
- Network: 限制网络带宽
Union FS (分层)
- 镜像分层存储
- Copy-on-Write
- 节省空间和带宽
容器不是虚拟机
- ✅ 容器是特殊的进程
- ✅ 共享宿主机内核
- ✅ 启动快、资源占用少
- ⚠️ 隔离性不如虚拟机
- ⚠️ 需要注意安全配置