Docker 容器化与 Kubernetes 核心原理——从镜像到 Pod 调度
摘要
容器化技术已经成为云原生时代的基石,Docker 和 Kubernetes 分别解决了运行时打包和集群编排两个核心问题。本文从 Linux 内核的 Namespace 和 Cgroup 机制讲起,深入剖析容器镜像的分层构建原理、Dockerfile 最佳实践、Kubernetes 核心组件架构,以及 Pod、Deployment、Service 的工作机制。全文包含大量配置示例和 Mermaid 图解,帮助读者从底层理解云原生技术栈。
一、容器与镜像的底层实现
1.1 Namespace —— 隔离的基石
容器本质上是宿主机上的普通进程,通过 Linux Namespace 实现资源隔离。
graph TB
subgraph 宿主机内核
subgraph Container1[容器 1]
P1[进程 A<br/>PID=1] --> P2[进程 B<br/>PID=2]
NS1[Mount NS]
NS2[PID NS]
NS3[Net NS<br/>veth0: 172.17.0.2]
NS4[UTS NS<br/>hostname: c1]
end
subgraph Container2[容器 2]
P3[进程 C<br/>PID=1] --> P4[进程 D<br/>PID=2]
NS5[Mount NS]
NS6[PID NS]
NS7[Net NS<br/>veth0: 172.17.0.3]
NS8[UTS NS<br/>hostname: c2]
end
end
容器用到的六类 Namespace:
| Namespace 类型 | 系统调用参数 | 隔离内容 | 引入内核版本 |
|---|---|---|---|
| Mount | CLONE_NEWNS | 文件系统挂载点 | 2.4.19 |
| PID | CLONE_NEWPID | 进程编号 | 2.6.24 |
| Network | CLONE_NEWNET | 网络设备、协议栈、端口 | 2.6.29 |
| UTS | CLONE_NEWUTS | 主机名和域名 | 2.6.19 |
| IPC | CLONE_NEWIPC | System V IPC 和 POSIX 消息队列 | 2.6.19 |
| User | CLONE_NEWUSER | 用户和用户组 ID | 3.8 |
在容器内看到独立 PID 编号的原理: 当使用 CLONE_NEWPID 创建进程时,新进程在自身的 PID Namespace 中 PID 为 1,但在宿主机的 PID Namespace 中可能是 12345。宿主机可见该进程,但容器内进程无法感知宿主机上的其他进程。
1.2 Cgroup —— 资源限制的实现
Cgroup(Control Group)限制容器可以使用的 CPU、内存、磁盘 I/O 等资源。
/sys/fs/cgroup/cpu/docker//
├── cpu.cfs_period_us # 默认 100000 (100ms)
├── cpu.cfs_quota_us # 设置值:限制 CPU 使用时间
├── cpu.shares # CPU 权重
├── cpu.stat # CPU 使用统计
/sys/fs/cgroup/memory/docker//
├── memory.limit_in_bytes # 内存限制上限
├── memory.usage_in_bytes # 当前使用量
├── memory.memsw.limit_in_bytes # 内存+交换分区限制
Docker 运行参数与 Cgroup 的对应关系:
# --memory 对应 memory.limit_in_bytes
docker run --memory=512m nginx
# --cpus 对应 cpu.cfs_quota_us
# 1 个 CPU = 100000 us / 100000 us period
docker run --cpus=2 nginx
# 内部:echo 200000 > cpu.cfs_quota_us
1.3 OverlayFS —— 镜像分层原理
Docker 镜像采用分层构建(Layer),每个层是只读的。容器启动时在镜像层之上叠加一个可写层(Container Layer)。
graph TD
subgraph 容器可写层[Container Layer - 读写]
W[容器运行时写入<br/>如日志、修改的文件]
end
subgraph 镜像层[Image Layers - 只读]
L1[Layer 5: CMD / ENTRYPOINT]
L2[Layer 4: RUN apt install]
L3[Layer 3: COPY . /app]
L4[Layer 2: WORKDIR /app]
L5[Layer 1: FROM ubuntu:22.04]
L6[Base: Ubuntu rootfs]
end
W -.->|"COW - 读时共享
写时复制"| L1
写时复制(Copy-on-Write,COW)机制:
- 读文件时:若可写层没有该文件,从镜像层读取
- 修改文件时:先将文件从镜像层复制到可写层,然后修改可写层的副本
- 删除文件时:在可写层创建一个空白文件标记(whiteout file),覆盖镜像层的原文件
使用 docker history 查看镜像层:
$ docker history redis:7-alpine
IMAGE CREATED CREATED BY SIZE
d1a8a8e9c2d9 2 weeks ago /bin/sh -c #(nop) CMD ["redis-server"] 0B
2 weeks ago /bin/sh -c #(nop) EXPOSE 6379 0B
2 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B
2 weeks ago /bin/sh -c #(nop) COPY file:... /usr/local/bin 147B
2 weeks ago /bin/sh -c #(nop) WORKDIR /data 0B
2 weeks ago /bin/sh -c #(nop) RUN ... redis ... 15.2MB
2 weeks ago /bin/sh -c #(nop) RUN ... && ... 26.4MB
2 weeks ago /bin/sh -c #(nop) RUN ... 345MB
2 weeks ago /bin/sh -c #(nop) ADD alpine-minirootfs... 7.38MB
二、Dockerfile 最佳实践与多阶段构建
2.1 Dockerfile 指令详解
# 基础镜像选择——尽量选择 Alpine 或 slim 版本
FROM openjdk:17-jdk-slim AS builder
# 设置工作目录
WORKDIR /app
# 先复制依赖文件,利用缓存分层
COPY pom.xml .
COPY src ./src
# 构建应用
RUN ./mvnw package -DskipTests
# ---------- 多阶段构建 ----------
FROM openjdk:17-jre-slim
WORKDIR /app
# 从 builder 阶段复制产物
COPY --from=builder /app/target/*.jar app.jar
# 非 root 用户运行(安全最佳实践)
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
2.2 构建效率优化
层数优化: 合并 RUN 命令可以减少层数。
# ❌ 不推荐——产生多个层,且每层都生成缓存
RUN apt-get update
RUN apt-get install -y curl vim git
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/*
# ✅ 推荐——合并为同一层
RUN apt-get update && \
apt-get install -y curl vim git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
.dockerignore 文件:
# .dockerignore - 避免构建上下文包含不必要文件
node_modules
.git
*.md
*.log
.gitignore
Dockerfile
2.3 多阶段构建的典型模式
| 场景 | 构建阶段 | 运行阶段 | 体积节省 |
|---|---|---|---|
| Java | maven + jdk-slim | jre-slim | ~200MB |
| Go | golang:alpine | scratch | ~300MB |
| Node.js | node:alpine + npm install | node:alpine-pruned | ~50MB |
| Python | python:alpine + pip install | python:alpine-slim | ~30MB |
# Go 语言最佳实践:静态编译 + scratch
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-s -w' -o app .
FROM scratch
WORKDIR /
COPY --from=builder /app/app .
EXPOSE 8080
ENTRYPOINT ["/app"]
三、Kubernetes 核心组件架构
3.1 控制平面(Control Plane)
graph TB
subgraph 控制平面[Control Plane]
API[API Server<br/>:6443]
S[Scheduler]
CM[Controller Manager]
ETCD[etcd]
end
subgraph 工作节点1[Worker Node 1]
K1[kubelet]
P1[pod 1]
P2[pod 2]
end
subgraph 工作节点2[Worker Node 2]
K2[kubelet]
P3[pod 3]
end
API --> K1
API --> K2
S --> API
CM --> API
API --> ETCD
各组件职责:
| 组件 | 职责 | 高可用方式 |
|---|---|---|
| API Server | 集群入口,REST API,认证/授权/准入控制 | 多实例 + Load Balancer |
| Scheduler | 将 Pod 分配到合适的 Node | 选主(Leader Election) |
| Controller Manager | 运行各种控制器(Deployment/ReplicaSet/Node 等) | 选主 |
| etcd | 分布式键值存储,保存集群所有数据 | Raft 协议,奇数节点 |
| kubelet | 每个节点上的代理,与 API Server 通信,管理 Pod | 单一进程 |
| kube-proxy | 网络代理,维护节点上的网络规则 | DaemonSet |
3.2 API Server 请求流程
用户请求
↓
┌─────────────┐
│ 认证 │ ← TLS 客户端证书 / Bearer Token / OIDC
├─────────────┤
│ 授权 │ ← RBAC / ABAC / Webhook
├─────────────┤
│ 准入控制 │ ← MutatingAdmissionWebhook → 修改请求
│ │ ← ValidatingAdmissionWebhook → 验证请求
├─────────────┤
│ 对象校验 │ ← 对资源对象做 Schema 校验
├─────────────┤
│ 存储到 etcd │ ← 将对象序列化写入 etcd
└─────────────┘
3.3 Scheduler 调度流程
sequenceDiagram
participant P as Pending Pod
participant S as Scheduler
participant A as API Server
participant N as Node
P->>A: 创建 Pod(nodeName 为空)
A->>S: Watch 到未调度的 Pod
S->>S: Predicates(预选)<br/>- 资源是否充足<br/>- 污点容忍<br/>- 端口冲突
S->>S: Priorities(优选)<br/>- Least Requested<br/>- Balanced Resource<br/>- 节点亲和性
S->>S: Bind(绑定)
S->>A: 绑定到选定 Node
A->>N: kubelet 收到调度的 Pod
N->>N: 拉取镜像、启动容器
四、Pod 与 Deployment 工作机制
4.1 Pod 的本质
Pod 是 Kubernetes 的最小调度单元,包含一个或多个紧密耦合的容器。所有容器共享同一个 Network Namespace 和存储卷。
apiVersion: v1
kind: Pod
metadata:
name: web-app
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 5
periodSeconds: 10
- name: sidecar
image: fluentd:latest
volumeMounts:
- name: logs
mountPath: /var/log/nginx
volumes:
- name: logs
emptyDir: {}
Pod 中容器的资源共享:
- Network——共享 IP、端口空间、localhost 通信
- IPC——共享 System V IPC
- UTS——共享主机名
- Volume——共享存储卷
4.2 静态 Pod vs 控制器 Pod
- 静态 Pod——直接由 kubelet 管理,通过节点上的 manifest 文件定义,不通过 API Server
- 控制器 Pod——通过 Deployment/StatefulSet/DaemonSet 等控制器创建,由 Controller Manager 管理
4.3 Deployment —— 声明式更新
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 更新时最多额外 1 个 Pod
maxUnavailable: 0 # 更新时不可以有 Pod 不可用
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
滚动更新过程:
replicas=3, maxSurge=1, maxUnavailable=0
Step 1: 新 ReplicaSet 创建 1 个新 Pod(旧 3 + 新 1 = 4)
Step 2: 新 Pod 进入 Ready → 旧 ReplicaSet 缩至 2(总 3)
Step 3: 新 ReplicaSet 创建 1 个新 Pod(旧 2 + 新 2 = 4)
Step 4: 旧 ReplicaSet 缩至 1(总 3)
Step 5: 新 ReplicaSet 创建 1 个新 Pod(旧 1 + 新 3 = 4)
Step 6: 旧 ReplicaSet 缩至 0(总 3)
常用操作命令:
# 更新镜像
kubectl set image deployment/nginx-deployment nginx=nginx:1.26
# 回滚
kubectl rollout undo deployment/nginx-deployment
# 查看历史版本
kubectl rollout history deployment/nginx-deployment
# 暂停/恢复发布
kubectl rollout pause deployment/nginx-deployment
kubectl rollout resume deployment/nginx-deployment
# 查看发布状态
kubectl rollout status deployment/nginx-deployment
五、Service 网络机制
5.1 Service 类型
| 类型 | 访问方式 | 适用场景 | 实现原理 |
|---|---|---|---|
| ClusterIP | 集群内虚拟 IP | 内部服务暴露 | iptables/IPVS 规则 |
| NodePort | 节点 IP + 端口 | 外部测试访问 | NodePort → ClusterIP |
| LoadBalancer | 云 LB + NodePort | 外部生产访问 | LB → NodePort → ClusterIP |
| ExternalName | DNS CNAME | 引入外部服务 | DNS 解析 |
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: ClusterIP
selector:
app: nginx
ports:
- port: 80 # Service 端口
targetPort: 80 # Pod 容器端口
protocol: TCP
5.2 iptables 与 IPVS 模式
graph TD
subgraph iptables[iptables 模式 - 所有工作节点]
PREROUTING --> |"DNAT
KUBE-SVC-XXX"| KUBE_SVC
KUBE_SVC --> RULE1["50% → Pod_192.168.1.2:80"]
KUBE_SVC --> RULE2["50% → Pod_192.168.1.3:80"]
Note over RULE1,RULE2: 随机概率负载均衡<br/>规则顺序遍历 O(n)
end
subgraph ipvs[IPVS 模式 - 所有工作节点]
VIP[Virtual IP: 10.96.0.1] --> |"IPVS
Virtual Server"| BACKENDS
BACKENDS[后端 Pod 列表<br/>rr/wrr/lc 等调度算法]
Note over VIP,BACKENDS: 哈希查找 O(1)<br/>支持更多算法
end
iptables 模式的缺点: 随着 Service 数量增加,iptables 规则链呈指数级增长(每个 Service 约 5 条规则),更新规则时 CPU 占用高。
IPVS 模式的优点: 使用内核级哈希表,时间复杂度 O(1),在内核空间处理,性能远优于 iptables。建议集群规模大于 100 个 Service 时使用 IPVS。
5.3 kube-proxy 工作原理
# 启用 IPVS 模式
kubectl edit configmap -n kube-system kube-proxy
# ...
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
ipvs:
scheduler: "rr"
六、Ingress 与 DNS 解析
6.1 Nginx Ingress Controller
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.jydeep.cn
secretName: tls-secret
rules:
- host: api.jydeep.cn
http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
- path: /web
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
七、总结
Docker 和 Kubernetes 构成了云原生时代的事实标准,本文从底层到上层完整解析了核心技术原理:
- 容器隔离——Namespace 提供进程、网络、文件系统等维度的隔离视图,Cgroup 精确控制资源使用上限
- 镜像分层——OverlayFS 通过写时复制实现高效的分层存储,多阶段构建可以将运行镜像体积压缩 80% 以上
- Kubernetes 架构——控制平面与工作节点分离,API Server 作为唯一入口承载认证、授权、准入和存储
- Pod 与控制器——Pod 是最小调度单元,Deployment 通过 ReplicaSet 实现声明式滚动更新与回滚
- 网络模型——Service 提供稳定的服务发现和负载均衡,IPVS 模式在大规模集群中性能更优
掌握这些原理后,遇到的绝大多数容器编排问题——容器启动失败、Pod 调度异常、服务无法访问、滚动更新卡住——都可以通过检查相应组件的工作日志和状态来准确定位。


暂无评论内容