DevOps · #architecture#kubernetes#k8s

Kubernetes架构深度解析

2022.04.18 9 min 3.4k
// 目录 · contents

前言

Kubernetes(简称K8s)已成为容器编排领域的事实标准。要真正掌握Kubernetes,必须深入理解其架构设计。本文将从控制平面、节点组件、Pod生命周期、控制器模式和网络模型五个维度进行全面解析。

整体架构概览

Kubernetes采用Master-Worker架构,由控制平面(Control Plane)和工作节点(Worker Node)组成。

graph TB
    subgraph ControlPlane["Control Plane (Master)"]
        API["API Server"]
        ETCD["etcd"]
        SCHED["Scheduler"]
        CM["Controller Manager"]
        CCM["Cloud Controller Manager"]
    end

    subgraph WorkerNode1["Worker Node 1"]
        KL1["kubelet"]
        KP1["kube-proxy"]
        CR1["Container Runtime"]
        POD1["Pod A"]
        POD2["Pod B"]
    end

    subgraph WorkerNode2["Worker Node 2"]
        KL2["kubelet"]
        KP2["kube-proxy"]
        CR2["Container Runtime"]
        POD3["Pod C"]
        POD4["Pod D"]
    end

    API --> ETCD
    SCHED --> API
    CM --> API
    CCM --> API
    KL1 --> API
    KL2 --> API
    KP1 --> API
    KP2 --> API
    KL1 --> CR1
    KL2 --> CR2
    CR1 --> POD1
    CR1 --> POD2
    CR2 --> POD3
    CR2 --> POD4

控制平面组件

API Server

API Server是Kubernetes控制平面的前端,所有组件之间的通信都通过它进行。它提供RESTful API接口,是唯一与etcd直接交互的组件。

API Server的核心职责:

  1. 认证(Authentication):验证请求者身份
  2. 授权(Authorization):检查请求者权限(RBAC)
  3. 准入控制(Admission Control):对请求进行校验和修改
  4. 数据持久化:将资源状态写入etcd
sequenceDiagram
    participant Client as kubectl/客户端
    participant API as API Server
    participant Auth as 认证模块
    participant Authz as 授权模块
    participant Admit as 准入控制
    participant ETCD as etcd

    Client->>API: HTTP Request
    API->>Auth: 身份验证
    Auth-->>API: 验证通过
    API->>Authz: RBAC授权检查
    Authz-->>API: 授权通过
    API->>Admit: Mutating Admission
    Admit-->>API: 修改后的资源
    API->>Admit: Validating Admission
    Admit-->>API: 校验通过
    API->>ETCD: 持久化存储
    ETCD-->>API: 写入成功
    API-->>Client: 返回响应

API Server请求处理的关键代码路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// API Server处理链示意
func buildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
handler := apiHandler

// 准入控制
handler = genericapifilters.WithAdmission(handler, c.AdmissionControl)

// 授权
handler = genericapifilters.WithAuthorization(handler, c.Authorization.Authorizer)

// 认证
handler = genericapifilters.WithAuthentication(handler, c.Authentication.Authenticator)

// 审计
handler = genericapifilters.WithAudit(handler, c.AuditBackend, c.AuditPolicyRuleEvaluator)

// 限流
handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight)

return handler
}

etcd

etcd是一个高可用的分布式键值存储,是Kubernetes集群的”唯一真相来源(Single Source of Truth)“。所有集群状态数据都存储在etcd中。

etcd的关键特性: - 使用Raft一致性算法保证强一致性 - 支持Watch机制,API Server通过Watch监听状态变化 - 推荐使用奇数节点部署(3或5节点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看etcd中存储的Kubernetes数据
ETCDCTL_API=3 etcdctl get /registry --prefix --keys-only | head -20

# 典型输出
# /registry/pods/default/nginx-deployment-xxx
# /registry/services/specs/default/kubernetes
# /registry/deployments/default/nginx-deployment
# /registry/configmaps/kube-system/coredns

# 查看etcd集群状态
ETCDCTL_API=3 etcdctl endpoint status --write-out=table \
--endpoints=https://10.0.0.1:2379,https://10.0.0.2:2379,https://10.0.0.3:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key

Scheduler

Scheduler负责将未调度的Pod分配到合适的节点。调度过程分为两个阶段:

flowchart LR
    A["待调度Pod"] --> B["预选 Filtering"]
    B --> C["优选 Scoring"]
    C --> D["绑定 Binding"]

    subgraph Filtering["预选阶段"]
        F1["NodeResourcesFit"]
        F2["NodeAffinity"]
        F3["PodTopologySpread"]
        F4["TaintToleration"]
    end

    subgraph Scoring["优选阶段"]
        S1["LeastRequestedPriority"]
        S2["BalancedResourceAllocation"]
        S3["ImageLocality"]
        S4["InterPodAffinity"]
    end

    B -.-> Filtering
    C -.-> Scoring

预选(Filtering):过滤掉不满足条件的节点(如资源不足、不匹配nodeSelector)。

优选(Scoring):对剩余节点打分排序,选出最优节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 自定义调度器配置示例
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: custom-scheduler
plugins:
score:
enabled:
- name: NodeResourcesBalancedAllocation
weight: 1
- name: ImageLocality
weight: 2
disabled:
- name: NodeResourcesLeastAllocated
filter:
enabled:
- name: NodeResourcesFit
- name: NodeAffinity

Controller Manager

Controller Manager运行着一组控制器,每个控制器负责管理特定类型的资源。核心控制器包括:

控制器 职责
Deployment Controller 管理Deployment和ReplicaSet
ReplicaSet Controller 确保Pod副本数符合期望
StatefulSet Controller 管理有状态应用
DaemonSet Controller 确保每个节点运行指定Pod
Job Controller 管理批处理任务
Node Controller 监控节点健康状态
Service Account Controller 管理ServiceAccount
Endpoint Controller 维护Service与Pod的映射

控制器遵循声明式的调谐循环(Reconcile Loop)模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 控制器核心调谐逻辑伪代码
func (c *DeploymentController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// 1. 获取当前状态
deployment := &appsv1.Deployment{}
if err := c.Get(ctx, req.NamespacedName, deployment); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}

// 2. 获取关联的ReplicaSet
replicaSets, err := c.getReplicaSetsForDeployment(ctx, deployment)
if err != nil {
return ctrl.Result{}, err
}

// 3. 比较期望状态与当前状态
if needsUpdate(deployment, replicaSets) {
// 4. 执行调谐操作
if err := c.scaleReplicaSet(ctx, deployment, replicaSets); err != nil {
return ctrl.Result{}, err
}
}

// 5. 更新状态
return ctrl.Result{}, c.updateDeploymentStatus(ctx, deployment, replicaSets)
}
graph LR
    A["Observe<br>观察当前状态"] --> B["Diff<br>比较期望状态"]
    B --> C["Act<br>执行调谐动作"]
    C --> A
    style A fill:#4CAF50,color:#fff
    style B fill:#FF9800,color:#fff
    style C fill:#2196F3,color:#fff

节点组件

kubelet

kubelet是每个节点上的核心代理,负责:

  1. 从API Server接收PodSpec
  2. 通过CRI(Container Runtime Interface)管理容器
  3. 执行健康检查(liveness/readiness/startup probe)
  4. 上报节点和Pod状态
graph TB
    API["API Server"] --> KL["kubelet"]
    KL --> CRI["CRI (Container Runtime Interface)"]
    KL --> CNI["CNI (Container Network Interface)"]
    KL --> CSI["CSI (Container Storage Interface)"]

    CRI --> Containerd["containerd"]
    CRI --> CriO["CRI-O"]
    Containerd --> RunC["runc"]
    CriO --> RunC

    CNI --> Calico
    CNI --> Flannel
    CNI --> Cilium

    CSI --> LocalPV["Local PV"]
    CSI --> NFS
    CSI --> Ceph

kubelet健康检查配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
apiVersion: v1
kind: Pod
metadata:
name: app-with-probes
spec:
containers:
- name: app
image: myapp:1.0
ports:
- containerPort: 8080
# 存活探针:失败则重启容器
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3
# 就绪探针:失败则从Service Endpoints中移除
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
# 启动探针:启动完成前不执行其他探针
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10

kube-proxy

kube-proxy负责实现Service的网络代理和负载均衡。它支持三种代理模式:

graph TB
    subgraph IPTables["iptables模式"]
        IT1["Service ClusterIP"] --> IT2["iptables规则"]
        IT2 --> IT3["Pod Endpoint 1"]
        IT2 --> IT4["Pod Endpoint 2"]
        IT2 --> IT5["Pod Endpoint 3"]
    end

    subgraph IPVS["IPVS模式"]
        IV1["Service ClusterIP"] --> IV2["IPVS虚拟服务器"]
        IV2 --> IV3["Pod Endpoint 1"]
        IV2 --> IV4["Pod Endpoint 2"]
        IV2 --> IV5["Pod Endpoint 3"]
    end
  • iptables模式:默认模式,通过iptables规则转发,适合中小规模集群
  • IPVS模式:基于内核IPVS模块,性能更好,支持多种负载均衡算法(rr/lc/dh/sh/sed/nq)
  • nftables模式:Kubernetes 1.29+引入,替代iptables
1
2
3
4
5
6
7
8
# 查看kube-proxy模式
kubectl get configmap kube-proxy -n kube-system -o yaml | grep mode

# 查看iptables规则
iptables -t nat -L KUBE-SERVICES -n | head -20

# 查看IPVS规则
ipvsadm -Ln

Pod生命周期

Pod是Kubernetes最小的调度单元,其生命周期状态流转如下:

stateDiagram-v2
    [*] --> Pending: 创建Pod
    Pending --> Running: 调度成功,容器启动
    Running --> Succeeded: 所有容器正常退出(restartPolicy=Never)
    Running --> Failed: 容器异常退出
    Running --> Unknown: 节点通信失败
    Failed --> Running: 容器重启(restartPolicy=Always/OnFailure)
    Unknown --> Running: 通信恢复
    Unknown --> Failed: 超时判定失败
    Succeeded --> [*]
    Failed --> [*]: restartPolicy=Never

Init Container

Init Container在应用容器启动前按顺序执行,常用于初始化工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: v1
kind: Pod
metadata:
name: app-with-init
spec:
initContainers:
# 等待数据库就绪
- name: wait-for-db
image: busybox:1.36
command: ['sh', '-c', 'until nc -z mysql-service 3306; do echo waiting for db; sleep 2; done']
# 下载配置文件
- name: download-config
image: busybox:1.36
command: ['wget', '-O', '/config/app.conf', 'http://config-server/app.conf']
volumeMounts:
- name: config-vol
mountPath: /config
containers:
- name: app
image: myapp:1.0
volumeMounts:
- name: config-vol
mountPath: /etc/app
volumes:
- name: config-vol
emptyDir: {}

Pod Hook

Kubernetes提供postStartpreStop钩子来处理容器启停事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: app
image: nginx:1.25
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo 'Container started' > /tmp/started"]
preStop:
exec:
# 优雅停机:通知应用停止接收新请求
command: ["/bin/sh", "-c", "nginx -s quit; while killall -0 nginx; do sleep 1; done"]
terminationGracePeriodSeconds: 60

控制器模式

Deployment滚动更新

sequenceDiagram
    participant User as 用户
    participant DC as Deployment Controller
    participant RS1 as ReplicaSet v1
    participant RS2 as ReplicaSet v2

    User->>DC: 更新镜像版本
    DC->>RS2: 创建新ReplicaSet(replicas=0)
    loop 滚动更新
        DC->>RS2: Scale Up (+1)
        Note over RS2: 新Pod就绪
        DC->>RS1: Scale Down (-1)
        Note over RS1: 旧Pod终止
    end
    Note over RS1: replicas=0
    Note over RS2: replicas=N(期望值)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 滚动更新时最多额外Pod数
maxUnavailable: 0 # 滚动更新时最多不可用Pod数
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi

StatefulSet

StatefulSet为有状态应用提供稳定的网络标识和持久化存储:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
volumeMounts:
- name: data
mountPath: /var/lib/mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: password
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: standard
resources:
requests:
storage: 10Gi

网络模型

Kubernetes网络模型的核心要求:

  1. Pod内通信:同一Pod内的容器共享网络命名空间,通过localhost通信
  2. Pod间通信:所有Pod可以直接通信,不需要NAT
  3. Service通信:通过ClusterIP实现服务发现和负载均衡
  4. 外部通信:通过NodePort、LoadBalancer或Ingress暴露服务
graph TB
    subgraph Node1["节点1 (10.0.1.1)"]
        subgraph Pod1["Pod A (10.244.1.2)"]
            C1["Container 1<br>:8080"]
            C2["Container 2<br>:9090"]
        end
        subgraph Pod2["Pod B (10.244.1.3)"]
            C3["Container 3<br>:8080"]
        end
        Bridge1["cbr0 (10.244.1.0/24)"]
    end

    subgraph Node2["节点2 (10.0.1.2)"]
        subgraph Pod3["Pod C (10.244.2.2)"]
            C4["Container 4<br>:8080"]
        end
        Bridge2["cbr0 (10.244.2.0/24)"]
    end

    C1 <-.->|localhost| C2
    Pod1 --> Bridge1
    Pod2 --> Bridge1
    Pod3 --> Bridge2
    Bridge1 <-->|"VXLAN/BGP<br>Overlay/Underlay"| Bridge2

实战:使用kubeadm搭建集群

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Master节点初始化
kubeadm init \
--apiserver-advertise-address=10.0.0.1 \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=10.96.0.0/12 \
--kubernetes-version=v1.30.0

# 配置kubectl
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

# 安装CNI插件(Calico)
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml

# Worker节点加入集群
kubeadm join 10.0.0.1:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash>

# 验证集群状态
kubectl get nodes -o wide
kubectl get pods -n kube-system
kubectl cluster-info

总结

Kubernetes的架构设计体现了以下核心理念:

  • 声明式API:用户描述期望状态,系统负责实现
  • 控制器模式:通过调谐循环不断收敛到期望状态
  • 松耦合:各组件通过API Server通信,职责单一
  • 可扩展:CRI/CNI/CSI等接口允许灵活替换底层实现

理解这些核心架构概念,是高效使用和运维Kubernetes的基础。后续文章将深入探讨网络模型、资源管理和调度策略等进阶话题。


踩坑记录

团队从 Docker Compose 迁移到 K8s 后第一次大促,Pod 开始出现 OOMKilled。排查发现我们没有给 Java 应用设置 -XX:MaxRAMPercentage,JVM 堆大小默认按宿主机内存计算(128G),而 Pod limit 只有 2G,堆开到 96G 后直接被 cgroup 杀掉。

修复很简单:在 JVM 参数里加 -XX:MaxRAMPercentage=75.0,让 JVM 感知容器内存限制。但这个坑需要亲自踩一次才能记住——Docker 容器下 JVM 的内存感知问题在 JDK 8u191 之后才得到修复,很多老教程根本没提这个。

第二个坑是 requestslimits 设置。刚迁移时图省事,所有服务都不设 requests,结果 Scheduler 把大量 Pod 调度到同一个节点,节点内存耗尽后 kubelet 开始随机驱逐 Pod,触发了一次意外的服务中断。设置合理的 requests 是 K8s 资源管理的基础,不能省。

实测结果

迁移前:12 台 8 核 16G 的 ECS,平均 CPU 利用率 15%,内存 40%(大量浪费)

指标 迁移前(ECS) 迁移后(K8s)
机器利用率(CPU) 15% 62%
机器利用率(内存) 40% 78%
滚动发布耗时 平均 45 分钟 平均 8 分钟
故障恢复(Pod 自愈) 手动,平均 10 分钟 自动,< 30 秒

我的看法

K8s 最大的价值不是弹性伸缩,而是统一了运维标准。迁移之前,每个服务的部署方式都不一样,运维靠口口相传的经验。K8s 强制你用声明式 YAML 描述服务状态,这本身就逼着团队把运维知识沉淀到代码里,可以 review、可以 diff、可以回滚。

弹性伸缩是锦上添花,标准化才是核心价值。如果团队规模小、服务数量少,K8s 的学习和运维成本未必值得,Docker Compose 反而更轻量实用。

作者 · authorzt
发布 · date2022-04-18
篇幅 · length3.4k 字 · 9 min
许可 · licenseCC BY-SA 4.0
$ echo "comments" · 评论