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进程 ID2.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用户和组 ID3.8容器内 root ≠ 宿主机 root
CgroupCgroup 根目录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
    • 节省空间和带宽

容器不是虚拟机

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