Container

Articles

FQA

Q1: difference between docker\podmn\buildah

You can add standard markdown syntax:

  • multiple paragraphs
  • bullet point lists
  • emphasized, bold and even bold emphasized text
  • links
  • etc.
...and even source code

the possibilities are endless (almost - including other shortcodes may or may not work)

Mar 7, 2025

Subsections of Container

Build Smaller Image

减小 Dockerfile 生成镜像体积的方法

1. 选择更小的基础镜像

# ❌ 避免使用完整版本
FROM ubuntu:latest

# ✅ 使用精简版本
FROM alpine:3.18
FROM python:3.11-slim
FROM node:18-alpine

2. 使用多阶段构建 (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/man

8. 压缩和优化层

# 在单个 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-deps

9. 使用专门的工具

  • 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%!

Mar 7, 2024

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端口。

优劣分析

  • 优点:
    • 隔离性:容器拥有独立的网络命名空间,与宿主机和其他网络隔离,安全性较好。
    • 端口管理灵活:通过端口映射,可以灵活地管理哪些宿主机端口暴露给外部。
    • 通用性:是最常用、最通用的模式,适合大多数应用场景。
  • 缺点:
    • 性能开销:相比 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 即可访问容器中的Nginx

3. None 模式

在这种模式下,容器拥有自己独立的网络命名空间,但不进行任何网络配置。容器内部只有回环地址 127.0.0.1。

  • 工作原理:容器完全与世隔绝。

优劣分析

  • 优点:
    • 绝对隔离:安全性最高,容器完全无法进行任何网络通信。
  • 缺点:
    • 无法联网:容器无法与宿主机、其他容器或外部网络通信。

使用场景:

  1. 需要完全离线处理的批处理任务。
  2. 用户打算使用自定义网络驱动(或手动配置)来完全自定义容器的网络栈。

命令示例:

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 高 良好 高 集群场景,跨主机容器通信

最佳实践建议

  1. 单机应用:优先使用 自定义的Bridge网络,它比默认Bridge网络提供了更好的DNS服务发现功能,方便容器间通过名称通信。
  2. 集群应用:必须使用 Overlay网络。
  3. 性能极致追求:在确认端口安全和无冲突的前提下,可考虑 Host模式。
  4. 安全隔离:对于无需网络的容器,使用 None模式。
  5. 避免在生产环境大量使用默认的bridge网络和container模式,因为它们分别在DNS发现和容器耦合度上存在不足。
Mar 7, 2024

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-machine

5️⃣ 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,不是 root

UID 映射配置

# /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% CPU

Docker 示例

# 限制容器使用 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_bytes

OOM (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/SELinux

2. 特权容器的危险

# 特权容器可以访问宿主机所有设备
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

  1. Namespace (隔离)

    • PID: 进程隔离
    • Network: 网络隔离
    • Mount: 文件系统隔离
    • UTS: 主机名隔离
    • IPC: 进程间通信隔离
    • User: 用户隔离
  2. Cgroups (限制)

    • CPU: 限制处理器使用
    • Memory: 限制内存使用
    • Block I/O: 限制磁盘 I/O
    • Network: 限制网络带宽
  3. Union FS (分层)

    • 镜像分层存储
    • Copy-on-Write
    • 节省空间和带宽

容器不是虚拟机

  • ✅ 容器是特殊的进程
  • ✅ 共享宿主机内核
  • ✅ 启动快、资源占用少
  • ⚠️ 隔离性不如虚拟机
  • ⚠️ 需要注意安全配置