Docker 容器化技术从入门到实践

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: 创建 PodnodeName 为空)
    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 构成了云原生时代的事实标准,本文从底层到上层完整解析了核心技术原理:

  1. 容器隔离——Namespace 提供进程、网络、文件系统等维度的隔离视图,Cgroup 精确控制资源使用上限
  2. 镜像分层——OverlayFS 通过写时复制实现高效的分层存储,多阶段构建可以将运行镜像体积压缩 80% 以上
  3. Kubernetes 架构——控制平面与工作节点分离,API Server 作为唯一入口承载认证、授权、准入和存储
  4. Pod 与控制器——Pod 是最小调度单元,Deployment 通过 ReplicaSet 实现声明式滚动更新与回滚
  5. 网络模型——Service 提供稳定的服务发现和负载均衡,IPVS 模式在大规模集群中性能更优

掌握这些原理后,遇到的绝大多数容器编排问题——容器启动失败、Pod 调度异常、服务无法访问、滚动更新卡住——都可以通过检查相应组件的工作日志和状态来准确定位。

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容