容器与 Kubernetes —— 比 VM 更轻的抽象

容器是 Linux 内核两个能力(namespace + cgroup)”打包”出来的产品——本质上是进程隔离而非虚拟化。本文从内核机制讲到 K8s 调度。

容器的两个内核基础

graph TB
  KERNEL[Linux Kernel]
  KERNEL --> NS[Namespace
资源视图隔离] KERNEL --> CG[Cgroup
资源消耗限制] NS --> N1[mnt:文件系统] NS --> N2[pid:进程树] NS --> N3[net:网络栈] NS --> N4[uts:主机名] NS --> N5[ipc:进程通信] NS --> N6[user:用户/UID] NS --> N7[cgroup:cgroup 视图] NS --> N8[time:时间] CG --> CG1[CPU] CG --> CG2[内存] CG --> CG3[IO] CG --> CG4[PID 数] CG --> CG5[device 访问]

容器 = 一组进程绑定到一组 namespace + cgroup。

namespace:视图隔离

1
2
3
4
5
6
7
8
9
10
11
12
# 查看进程的 namespace
ls -l /proc/$$/ns/
# net -> net:[4026531956]
# pid -> pid:[4026531836]
# ...

# 进入容器的 namespace
nsenter -t <pid> -n ip addr # 进入网络 ns 看 IP
nsenter -t <pid> -m # 进入挂载 ns

# 创建新 namespace 跑 shell
unshare --net --pid --fork bash

每个 namespace 独立一份资源视图——容器内的 PID 1 是宿主机的 PID 12345。

cgroup:资源限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# cgroup v1(旧)
ls /sys/fs/cgroup/

# cgroup v2(新,RHEL 9 / Ubuntu 22.04+ 默认)
mount | grep cgroup2

# 看进程的 cgroup
cat /proc/<pid>/cgroup

# 限制 CPU
echo "100000 100000" > /sys/fs/cgroup/<group>/cpu.max # 1 核

# 限制内存
echo "1G" > /sys/fs/cgroup/<group>/memory.max

cgroup v1 vs v2

1
2
3
4
5
6
7
8
9
10
cgroup v1(2007):
- 每个 controller 独立树
- CPU / memory / blkio 各管各的
- 复杂,难统一

cgroup v2(2016+,2022 默认):
- 统一树
- controller 在节点上启用
- 内核优化更多
- K8s 1.25+ 默认 cgroup v2

OCI 标准:容器的”协议”

容器市场曾经只有 Docker——后来 OCI(Open Container Initiative)标准化:

graph TB
  IMG[OCI Image Spec
镜像格式标准] RUN[OCI Runtime Spec
运行时标准] DIST[OCI Distribution Spec
镜像仓库标准] IMG --> R1[Docker / containerd 镜像] IMG --> R2[Buildah 构建] RUN --> R3[runc
默认运行时] RUN --> R4[crun
C 实现] RUN --> R5[kata-runtime
VM 后端] RUN --> R6[gVisor runsc
用户态内核]

任何符合 OCI 的工具链都可以互操作——这就是容器生态成功的关键。

容器运行时的演进

graph LR
  D[Docker
2013] --> D2[Docker + libcontainer] D2 --> CD[containerd
2017] CD --> CDV2[containerd 2.x
K8s 默认] D --> RUN[runc
2015 拆出] CRIO[CRI-O
K8s 专用]

Docker

1
2
3
4
5
6
7
8
9
Docker(2013):     
- 第一个让"容器易用"的工具
- 镜像分层 / Dockerfile / 仓库
- 早期是 daemon 单体

Docker 现状:
- 桌面开发仍主流(Docker Desktop)
- 服务器运行时已被 containerd 替代
- K8s 1.24+ 不再原生支持 Docker

containerd

1
2
3
4
5
6
7
8
9
10
11
containerd:
- Docker 拆出的运行时层
- K8s 默认运行时
- 简洁、稳定、性能好
- Apache 2.0 开源

工具:
ctr - containerd CLI
nerdctl - 类似 docker CLI

配置文件: /etc/containerd/config.toml

CRI-O

1
2
3
4
5
CRI-O:     
- Red Hat 主推
- K8s CRI(Container Runtime Interface)专用
- 最小化运行时
- OpenShift 默认

runc / crun

1
2
3
4
5
6
7
8
runc:     
- Go 实现的 OCI 运行时
- Docker / containerd 默认

crun:
- C 实现,启动快 50%
- Red Hat 主推
- Podman / CRI-O 推荐

Docker / containerd 实战

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 老版 Docker
docker run -d -p 80:80 nginx
docker ps
docker logs <container>
docker exec -it <container> bash
docker images
docker pull nginx:alpine

# containerd(ctr)
ctr image pull docker.io/library/nginx:latest
ctr run -d docker.io/library/nginx:latest nginx-container

# nerdctl(更友好)
nerdctl run -d -p 80:80 nginx
nerdctl ps
nerdctl logs <container>

# Podman(无 daemon Docker 替代)
podman run -d -p 80:80 nginx
podman ps

镜像分层

1
2
3
4
5
FROM ubuntu:22.04         # Layer 1
RUN apt update # Layer 2
RUN apt install nginx -y # Layer 3
COPY config /etc/nginx/ # Layer 4
CMD ["nginx", "-g", "daemon off;"]

每个 Layer 是只读 tarball——多容器共享相同 Layer,节省磁盘。

1
2
3
# 看镜像分层
docker image history nginx:latest
docker inspect nginx:latest | jq .[0].RootFS

镜像格式:

1
2
3
4
5
6
7
8
9
10
11
12
OCI Image Format:
config.json
layer1.tar.gz
layer2.tar.gz
...
manifest.json

仓库:
Docker Hub(默认)
GitHub Container Registry(ghcr.io)
Quay.io
自建: Harbor / Nexus / Artifactory

容器网络

graph TB
  NETMODES[容器网络模式]
  NETMODES --> M1[bridge
默认
NAT] NETMODES --> M2[host
共享主机] NETMODES --> M3[none
无网络] NETMODES --> M4[overlay
跨主机] NETMODES --> M5[macvlan
每容器独立 MAC] NETMODES --> M6[CNI
K8s 用]

CNI(Container Network Interface)

K8s 网络抽象,K8s 通过 CNI 调用网络插件:

1
2
3
4
5
6
7
8
9
10
CNI plugin 主流:
Flannel: 简单,VXLAN
Calico: BGP,性能好
Cilium: eBPF,最现代
Weave: 端到端加密
Multus: 多网卡 CNI 适配器

调用方式:
K8s 创建 Pod → kubelet 调 CNI plugin
→ plugin 创建 veth pair / 配置 IP / 路由

Cilium:eBPF-based CNI

1
2
3
4
5
6
Cilium 是 2024-2026 年趋势:
- 数据面用 eBPF(不用 iptables)
- L3-L7 策略
- 服务网格能力(Cilium Mesh)
- 完全替代 kube-proxy
- 性能:vs iptables / IPVS 快几倍

Kubernetes 架构

graph TB
  subgraph CP["控制面"]
    APIS[API Server]
    SCH[Scheduler]
    CTRL[Controller Manager]
    ETCD[etcd
状态存储] CCM[Cloud Controller] end subgraph WK["Worker Node × N"] KUBELET[kubelet] KP[kube-proxy / Cilium] CRI[containerd] CNI[CNI plugin] KUBELET --> CRI KUBELET --> CNI end APIS --> KUBELET CTRL --> APIS SCH --> APIS ETCD --- APIS

K8s 核心概念:

1
2
3
4
5
6
7
8
9
10
11
12
13
Pod:       一组容器(共享 net + storage namespace)
Deployment:声明式管理 Pod 副本
Service: 负载均衡 + 服务发现
ConfigMap / Secret:配置和密钥
Namespace(K8s 概念,与内核不同): 租户/项目隔离
PersistentVolume: 持久化存储
Ingress: L7 路由
DaemonSet: 每节点一份
StatefulSet: 带状态副本
Job / CronJob: 一次性 / 定时任务

CRD: 扩展资源类型
Operator: 用 CRD + Controller 实现自动化运维

K8s 调度器

graph TB
  POD[新 Pod 创建]
  POD --> SCH[Scheduler]
  SCH --> F1[Filter
过滤不能放的节点] F1 --> F2[节点亲和 / 反亲和] F1 --> F3[资源够不够] F1 --> F4[Taint / Toleration] F1 --> SC[Score
剩余节点评分] SC --> S1[资源平衡] SC --> S2[镜像本地化] SC --> S3[拓扑分布] SC --> SEL[选最优节点] SEL --> KUB[kubelet 启动 Pod]

调度过程:

1
2
3
4
5
6
7
1. 用户提交 Pod yaml → APIs Server
2. APIs 写 etcd
3. Scheduler 读到未绑定的 Pod
4. Filter 阶段:排除不合适的节点
5. Score 阶段:剩下节点打分
6. 选最高分节点,写绑定到 etcd
7. 该节点 kubelet 收到 → 启动容器

调度策略:

1
2
3
4
5
节点亲和(NodeAffinity):     "只在 GPU 节点跑"
Pod 亲和(PodAffinity): "和 X Pod 同节点"
反亲和(AntiAffinity): "和 X Pod 不同节点"
Taint / Toleration: "节点专用,只接受能容忍的 Pod"
Topology Spread: "Pod 均匀分布在多个 zone"

kubelet 与 Node Agent

每节点上跑 kubelet:

1
2
3
4
5
6
7
8
9
10
11
kubelet 职责:
- 接 APIs Server 的 Pod 创建/删除
- 通过 CRI 调 containerd 启动容器
- 通过 CNI 配置网络
- 通过 CSI 挂载存储
- 心跳上报节点状态
- 健康检查(Liveness / Readiness)
- 资源监控(CPU/MEM)上报

systemd 单元: kubelet.service
日志: journalctl -u kubelet

CSI:存储插件

1
2
3
4
5
6
7
8
9
10
11
CSI(Container Storage Interface):
- 把存储和 K8s 解耦
- 各厂家实现自己 CSI driver
- K8s 不需要硬编码各种存储

主流 CSI:
AWS EBS / Azure Disk / GCP PD
Ceph CSI(RBD / CephFS)
NFS CSI
local-path / Longhorn
存储厂家:Pure / NetApp / 戴尔 / 华为 OceanStor

K8s 网络模型

K8s 网络的 4 个要求:

1
2
3
4
5
6
7
8
9
10
1. Pod 与 Pod 之间:不需要 NAT,直连
2. Pod 与 Node 之间:双向直连
3. Node 上有"集群 IP" → Pod IP 的路由
4. Service 提供稳定 VIP

实现方案:
- Cilium:eBPF
- Calico:BGP
- Flannel:VXLAN / Host-GW
- 各家云厂家自家网络

Service 与 kube-proxy

1
2
3
4
5
6
7
8
9
ClusterIP(默认):     集群内部 VIP
NodePort: 每节点开同一端口,对外
LoadBalancer: 云提供 LB(AWS ELB 等)
ExternalName: DNS 别名

kube-proxy 实现:
iptables 模式: 规则数随 Service 数线性增(性能差)
IPVS 模式: 哈希表,性能好
eBPF(Cilium): 最高性能,规则数 O(1)

可观测:metrics、logs、tracing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Metrics:
- Prometheus + Grafana(标准组合)
- kube-state-metrics
- cAdvisor(容器指标)
- node-exporter(节点指标)

Logs:
- Fluent Bit / Fluentd(采集)
- Elasticsearch / Loki(存储)
- Kibana / Grafana(查询)

Tracing:
- OpenTelemetry(标准)
- Jaeger / Tempo(后端)

K8s 在 AI 集群里的角色

1
2
3
4
5
6
7
8
9
10
11
12
13
传统 K8s:     微服务编排
AI K8s:
- GPU 资源管理(nvidia-device-plugin)
- 多 GPU Pod 调度
- 大模型训练 Pod 间通信(NCCL)
- PyTorchJob / MPIJob CRD(Kubeflow)
- Volcano / KAI Scheduler(gang scheduling)

AI 集群专用 K8s 发行版:
Run.ai(NVIDIA 收购): GPU 池化
Kubeflow: ML 平台
Determined AI: 训练管理
KubeRay: Ray on K8s

容器镜像安全

1
2
3
4
5
6
7
8
9
10
11
12
13
扫描镜像漏洞:
Trivy: 最常用
Clair: CoreOS 出品
Snyk: 商业
Anchore: 商业

签名 / 验签:
cosign: Sigstore 出品
Notary v2: Docker 老的

SBOM(Software Bill of Materials): 软件物料清单
syft: 生成
grype: 检测

容器的几个老坑

坑 1:忘记设资源限制

1
2
3
4
5
6
7
8
# 没设 limits → Pod 把节点吃光
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 1000m
memory: 1Gi

不设限制就是定时炸弹——某个 Pod 内存泄漏把整节点 OOM。

坑 2:用 latest tag

1
2
3
4
5
6
7
image: nginx:latest
→ 各节点拉的可能不同版本
→ 重启 Pod 后行为变了
→ 难追溯

正确: image: nginx:1.26.2
更好: image: nginx@sha256:xxx

坑 3:root 用户运行

1
2
3
4
5
6
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]

容器逃逸的第一道屏障——非 root + 只读文件系统。

坑 4:默认 ServiceAccount 权限过大

1
2
3
4
5
6
7
8
默认 namespace 的 default ServiceAccount
→ 自动挂载 token
→ 容器内可以 kubectl 操作集群

正确:
- 用专用 ServiceAccount
- 用 RBAC 限制
- automountServiceAccountToken: false

坑 5:镜像太大

1
2
3
4
5
6
7
8
9
10
11
基础镜像选择:
ubuntu:24.04: ~80 MB
debian:slim: ~30 MB
alpine: ~5 MB
distroless: ~20 MB(Google 出品,无 shell)
scratch: 0 MB(静态二进制)

镜像越小:
- 拉取快
- 攻击面小
- 节省存储

坑 6:忽视健康检查

1
2
3
4
5
6
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet: { path: /ready, port: 8080 }

无健康检查 = 死了的容器还在收流量。

坑 7:节点资源碎片

1
2
3
4
5
6
7
8
节点 64 核,已用 40 核分散在多个 Pod
新建 Pod 要 32 核 → 找不到节点(虽然总剩余 24 核分散)
→ 调度失败

解决:
- bin-packing 调度策略
- HPA / VPA 弹性扩缩
- 节点资源池规划

K8s 多集群和联邦

1
2
3
4
5
6
7
8
9
10
11
12
单集群上限:~5000 节点(API Server 性能瓶颈)

多集群方案:
Karmada(华为开源): 联邦标准
Cluster API: K8s 风格管 K8s
Rancher / OpenShift: 多集群管理面
Submariner: 跨集群网络

应用:
- 跨地域容灾
- 法规合规(数据不能跨境)
- 资源池突破单集群上限

云原生 vs 传统

1
2
3
4
5
6
7
8
9
10
11
12
云原生(Cloud Native)特征:
- 容器化打包
- 微服务架构
- 声明式 API
- 不可变基础设施
- DevOps 流程
- 12-Factor App

CNCF(Cloud Native Computing Foundation):
- K8s 主家
- 200+ 项目
- 全球云原生标准

一些查询命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 容器层面
docker ps / nerdctl ps / crictl ps
docker inspect <container>
docker logs -f <container>
docker exec -it <container> sh

# K8s 层面
kubectl get nodes
kubectl get pods -A
kubectl describe pod <name>
kubectl logs <pod> -c <container> -f
kubectl exec -it <pod> -- sh
kubectl top pods
kubectl top nodes

# debug
kubectl debug node/<node-name> -it --image=busybox
kubectl get events --sort-by='.lastTimestamp'

# 配置
kubectl config get-contexts
kubectl config use-context <ctx>

一些数字直觉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
容器启动:     
小镜像: 100-500 ms
大镜像(首次拉): 秒到分钟级

Pod 启动(含调度):
普通业务: 1-3 秒
GPU Pod: 5-10 秒(需要 device plugin)

K8s 集群规模:
推荐: < 5000 节点
超大: 联邦 / 多集群

K8s 控制面消耗:
小集群: 1-2 vCPU + 4 GB
万节点: 8-16 vCPU + 32-64 GB(etcd 重)

国产云原生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
阿里云 ACK:     深度定制 K8s
腾讯 TKE: K8s + 自家增强
华为 CCE: K8s + 容器服务
青云 KubeSphere: 国产 K8s 管理面(开源)
Rancher: 原 SUSE,被广泛用
KubeEdge: 边缘计算 K8s(华为开源)

国产容器引擎:
iSulad(华为开源): 轻量容器引擎
PouchContainer(阿里开源): 已停

国产容器仓库:
Harbor(VMware / CNCF): 最广泛
Quay: Red Hat

待补充:国产 K8s 发行版的实际部署比例。

小结

  • 容器 = namespace(视图隔离) + cgroup(资源限制)
  • OCI 标准化让生态健康
  • containerd 已替代 Docker 成为 K8s 默认运行时
  • Cilium / eBPF 是 K8s 网络的下一代
  • K8s 是云原生事实标准,调度器和 CRD 模型是核心
  • AI 集群有专用 K8s 发行版(Kubeflow / Volcano / Run.ai)
  • 国产 K8s 发行版(阿里 ACK / 腾讯 TKE / 华为 CCE)已成熟

下一篇讲内核内部——调度器、网络栈、文件系统。