Featured image of post Kubernetes 系列一:从集群架构到 Pod

Kubernetes 系列一:从集群架构到 Pod

从为什么需要 Kubernetes 开始,拆解控制平面与 Node 节点的组件职责,再通过 Pod、Deployment 和 Service 理解 Kubernetes 的基本运行模型

Kubernetes 常被简称为 K8s(K 和 s 之间省略了 8 个字母)。它是一个管理容器化应用的开源平台,负责把应用安排到集群里的机器上运行,并持续让实际状态接近期望状态。

学完 Docker 之后,我的理解是:Docker 解决的是“怎样把应用打包并运行起来”,Kubernetes 解决的是“应用跑在很多台机器上时,怎样持续管理这些容器”。

本文基于 Kubernetes 1.37,第六节的实验用 kind 在本地搭集群。

这是 K8s 学习笔记的第一篇,先把整体地图搭起来:

  • 为什么有了 Docker 和 Compose,还需要 Kubernetes。
  • 一个 Kubernetes 集群由哪些部分组成。
  • 控制平面负责什么。
  • Node 节点负责什么。
  • Pod 为什么是 Kubernetes 调度和运行应用的基本单位。

后面再分篇记录 Deployment、Service、Gateway API、ConfigMap、Secret、存储、调度和故障排查。

一、为什么要用 Kubernetes

从一个容器到一组服务

在本地运行一个 Web 服务很简单:准备镜像,执行 docker run,映射一个端口即可。服务数量增加后,问题会同时出现:

  • 容器应该运行在哪台机器上?这台机器资源够不够?
  • 容器崩溃后,谁负责重新启动?机器故障后,谁负责迁移?
  • 一个服务启动多个副本后,流量怎样分发?客户端应该连接哪个地址?
  • 发布新版本时,怎样逐步替换旧版本,避免所有实例同时中断?
  • 配置、密钥和持久化数据怎样与镜像分开管理?
  • 集群有几十或几百台机器时,怎样统一查看状态和执行变更?

Compose 能很好地描述一台机器上的多容器应用,但它管的是“在这个环境里把这一组服务启动起来”。应用一旦需要跨机器调度、自动恢复和持续发布,就需要一个集群级的控制系统,这正是 Kubernetes 做的事。

Kubernetes 提供了什么

在我看来,Kubernetes 的核心不是“又一条启动容器的命令”,而是把运行应用所需的状态交给一组控制器持续维护。

能力 Kubernetes 做什么 实际收益
调度 根据资源、约束和策略把 Pod 放到合适的 Node 不需要手动挑选机器
自愈 发现容器或节点异常,重新创建或调度工作负载 减少人工值守
服务发现 为一组动态变化的 Pod 提供稳定的 Service 地址 服务之间不依赖 Pod IP
水平扩展 修改副本数量,或根据指标自动扩缩容 应对流量变化
滚动更新 分批替换旧版本,控制不可用实例数量 降低发布风险
配置与资源管理 集中管理配置、密钥、CPU 和内存边界 镜像与运行配置分离

这些能力都建立在声明式模型上。我们提交的是“希望有 3 个副本、用哪个镜像、暴露哪个端口”,剩下的由控制器观察当前状态并不断修正差异。

  flowchart LR
    A[提交期望状态] --> B[API Server]
    B --> C[控制器与调度器]
    C --> D[选择 Node]
    D --> E[kubelet 启动容器]
    E --> F[实际运行状态]
    F -->|持续观察并修正差异| B

Kubernetes 不会自动解决所有问题

Kubernetes 不会替应用修复代码,也不会自动保证数据库事务、备份和权限设计正确。它提供的是运行平台:

  • 应用仍需要正确处理信号、超时和依赖重连。
  • 数据库等有状态服务需要独立的备份、恢复和容量规划。
  • 集群需要权限控制、镜像安全、日志、指标和审计。
  • 小型应用如果只需要单机部署,Compose 或托管平台可能更简单。

所以上不上 K8s,要看集群管理、可用性和交付流程上有没有真实需求,而不是“所有服务都得上 Kubernetes”。

二、Kubernetes 集群的整体架构

一个集群通常分为两类节点:

  1. 控制平面(Control Plane):保存集群状态,接受操作请求,做出调度和控制决策。
  2. 工作节点(Worker Node):运行实际的 Pod 和业务容器。

控制平面在早期资料里叫 Master 节点,社区出于包容性命名已经统一改为 control-plane。新集群里 kubectl get nodes 的 ROLES 列显示的是 control-plane,旧教程里带 master 的标签和污点命令已经不适用了。

  flowchart TB
    Client[kubectl / CI / 外部系统] --> CP

    subgraph CP[控制平面]
        direction LR
        Scheduler[kube-scheduler] --> API[kube-apiserver]
        Controller[kube-controller-manager] --> API
        Cloud[cloud-controller-manager(可选)] --> API
        API <--> ETCD[(etcd)]
    end

    subgraph N1[Node 1]
        direction TB
        K1[kubelet] -->|CRI| R1[容器运行时] --> P1[Pod]
        Proxy1[kube-proxy]
    end

    subgraph N2[Node 2]
        direction TB
        K2[kubelet] -->|CRI| R2[容器运行时] --> P2[Pod]
        Proxy2[kube-proxy]
    end

    CP <-->|watch / 上报状态| N1
    CP <-->|watch / 上报状态| N2

图中控制平面与 Node 之间的双向箭头,表示 Node 上的组件与 kube-apiserver 保持通信:kubelet 获取分配给本节点的 Pod 并上报状态,kube-proxy 监听 Service 和 EndpointSlice 的变化来更新转发规则。

一次部署发生了什么

执行下面的命令时,kubectl 并不会直接登录某台 Node 创建容器:

1
kubectl apply -f app.yaml

我把请求的流程整理成下面几步:

  1. kubectl 把 YAML 发送给 kube-apiserver。
  2. API Server 校验请求,并把持久化状态写入 etcd。
  3. Deployment 控制器发现期望状态发生变化,创建或更新 ReplicaSet;ReplicaSet 控制器再创建 Pod 对象,此时 Pod 还没有分配 Node。
  4. Scheduler 为没有绑定 Node 的 Pod 选择合适的工作节点,并通过 API Server 写回绑定结果。
  5. 目标 Node 上的 kubelet 观察到 Pod 规格,调用容器运行时拉取镜像并启动容器。
  6. kubelet 持续汇报状态;控制器根据状态决定是否重建、扩容或更新 Pod。

可以看出,各个组件都是通过 API Server 协作的。我们平时只跟 API 打交道,不需要登录每台 Node 去启动容器。

三、控制平面的组件

控制平面组件可以运行在一台或多台机器上。生产环境通常使用多个控制平面实例,并为 API Server 和 etcd 设计高可用方案;单机学习集群则常把它们运行在同一个节点。

1. kube-apiserver:集群的统一入口

kube-apiserver 是 Kubernetes API 的 HTTP 入口。kubectl、控制器、Scheduler、kubelet 以及外部系统都会通过它读写资源对象。

它主要负责:

  • 认证:请求来自谁?
  • 授权:这个身份能操作哪些资源?
  • 准入控制:这次变更是否符合策略?
  • API 版本与对象校验:字段类型是否正确?
  • 把资源变更通知给观察者。

API Server 本身不运行容器。它接收并记录“应该是什么样”,具体动作由控制器、Scheduler 和 kubelet 完成。它也是唯一直接读写 etcd 的组件,其他组件都要经过它访问集群状态。

2. etcd:保存集群状态

etcd 是一个分布式键值存储,Kubernetes 用它保存 API 对象和集群状态,例如:

  • Deployment 希望有多少个副本。
  • Pod 被调度到了哪台 Node。
  • Service 的端口和 Selector 是什么。
  • Secret、ConfigMap、RBAC 和节点信息是什么。

etcd 可以理解成集群的状态数据库,但它不是给业务用的数据库。etcd 的数据需要备份和保护,丢失或损坏会影响控制平面的恢复能力。

etcd 通过 Raft 共识算法在多个成员之间复制数据(Raft 的原理我在分布式共识算法里整理过)。写入需要多数成员确认,所以生产环境通常部署 3 或 5 个成员:3 个成员可以容忍 1 个故障,5 个成员可以容忍 2 个。

3. kube-scheduler:为 Pod 选择 Node

Scheduler 负责处理还没有绑定 Node 的 Pod。它先过滤不满足条件的节点,再对候选节点打分,选择一个合适的目标。

它会考虑:

  • Pod 的 CPU、内存 requests 与 Node 的可分配资源。
  • Node 的 taint、Pod 的 toleration。
  • 节点选择器、亲和性和反亲和性。
  • 拓扑分布、卷约束和其他调度策略。

Scheduler 只做“放在哪里”的决定:选定 Node 后,它通过 API Server 把结果写回 Pod 的 spec.nodeName,并不直接联系目标 Node。Pod 被绑定到 Node 后,由该 Node 的 kubelet 负责实际创建容器。

4. kube-controller-manager:持续修正状态

Controller Manager 运行多个控制器。每个控制器都采用类似的循环:

1
2
3
4
5
6
7
读取期望状态和当前状态
        ↓
计算两者之间的差异
        ↓
创建、更新或删除对象
        ↓
再次观察,直到状态收敛

例如,Deployment 控制器负责把 Deployment 转换为 ReplicaSet,ReplicaSet 控制器负责让 Pod 副本数达到目标。Pod 被删除后,控制器会发现副本数不足并创建替代 Pod。

控制器不是执行一次就结束,而是一直在观察,所以 Kubernetes 才能在运行期间自己处理故障和变化。

5. cloud-controller-manager:对接云平台(可选)

使用公有云时,cloud-controller-manager 可以把 Kubernetes 对象与云平台能力连接起来,例如:

  • 云厂商的负载均衡器。
  • 云主机节点生命周期。
  • 云硬盘的创建和挂载。
  • 云平台路由。

裸机或本地实验集群通常不需要这个组件。

控制平面组件的关系

组件 核心问题 是否直接运行业务容器
kube-apiserver 谁可以读取和修改集群对象? 否
etcd 集群状态保存在哪里? 否
kube-scheduler Pod 应该放到哪台 Node? 否
kube-controller-manager 怎样让实际状态接近期望状态? 否
cloud-controller-manager 怎样使用云平台资源? 否,负责云资源协作

控制平面组件可以被部署为系统服务或静态 Pod,具体方式取决于安装工具和集群发行版。这里要把“组件怎么运行”和“组件负责什么”分开看。

四、Node 节点:运行 Pod 的地方

Node 是集群中真正承载工作负载的机器,可以是虚拟机或物理机。每台 Node 至少需要 kubelet 和容器运行时;网络组件通常由 CNI 插件和 kube-proxy 等组件提供。

1. kubelet:Node 上的代理

kubelet 运行在每台 Node 上,负责让这台机器上的 Pod 处于期望状态。它会:

  • 从 API Server 获取分配给本节点的 Pod 规格。
  • 调用容器运行时创建、启动和停止容器。
  • 挂载卷,执行探针,并上报 Pod 状态。
  • 发现容器退出或探针失败后,按 Pod 的重启策略处理。

注意 kubelet 不负责调度,它只管理被分配到本节点的 Pod。

2. 容器运行时:真正启动容器

Kubernetes 通过 CRI(Container Runtime Interface)与容器运行时通信。常见运行时包括 containerd 和 CRI-O。

运行时负责拉取镜像、创建容器并管理容器的生命周期。现在的集群一般直接用 containerd 或 CRI-O,Node 上不需要装 Docker(早期是通过内置的 dockershim 对接 Docker Engine,1.24 已移除)。

这不影响用 Docker 构建的镜像:Docker 入门与实战里 docker build 出来的镜像符合 OCI 规范,推到镜像仓库后,containerd 和 CRI-O 都能直接拉取运行。

3. kube-proxy:Service 流量转发

kube-proxy 在 Node 上维护 Service 相关的转发规则,把访问 Service 虚拟地址的流量转发到后端 Pod。目前默认是 iptables 模式,较新的 Linux 内核上可以改用 nftables 模式(1.33 起 GA)。以前常见的 IPVS 模式已在 1.35 弃用。

它不是完整的网络插件。Pod IP 分配、跨 Node 路由和网络策略通常由 CNI 插件负责,例如 Calico、Cilium 或 Flannel;容器运行时创建 Pod 时会调用 CNI 插件配置网络。有些 CNI(例如 Cilium)还能用 eBPF 直接接管 Service 转发,这时集群中可以不运行 kube-proxy。具体组件取决于集群安装方案。

4. Node 的资源与状态

Scheduler 根据 Node 的容量和可分配资源做决定。Node 不只是“能运行或不能运行”,还拥有一组状态和约束:

  • CPU、内存和临时存储容量。
  • Ready、MemoryPressure、DiskPressure 等条件。
  • labels,用于节点选择和拓扑分布。
  • taints,用于阻止不匹配的 Pod 被调度。

常用检查命令:

1
2
3
kubectl get nodes
kubectl describe node NODE_NAME
kubectl top node

kubectl top 需要集群中安装 Metrics Server;没有它时,节点和 Pod 仍然可以运行,但不能直接通过这个命令查看资源使用率。

五、Pod:Kubernetes 的基本运行单位

Pod 不是“一个容器的别名”

Pod 是 Kubernetes 可以创建和管理的最小部署单位。一个 Pod 可以包含一个或多个紧密协作的容器:

  • Pod 中的容器共享同一个网络命名空间。
  • 它们通过 localhost 访问同一个 Pod 内的其他容器端口。
  • 它们可以共享挂载卷。
  • Pod 拥有自己的 IP,但这个 IP 会随着 Pod 重建而变化。

最常见的做法是一个 Pod 运行一个主应用容器。多容器 Pod 适合日志代理、代理 sidecar 这类要和主容器紧密协作的进程,不相关的服务不应该塞进同一个 Pod。sidecar 从 1.33 起可以原生声明:写在 initContainers 里并设置 restartPolicy: Always,它会先于主容器启动,并在主容器退出后才停止。

  flowchart LR
    subgraph Pod[Pod:共享网络与生命周期]
        App[应用容器<br>:8080]
        Sidecar[Sidecar 容器<br>日志或代理]
        Vol[(共享卷)]
        App --- Vol
        Sidecar --- Vol
    end
    App -.localhost.-> Sidecar
    Service[Service 稳定地址] --> Pod

Pod 的生命周期

Pod 不是永久服务器。它可能因为发布、驱逐、节点故障或配置变化而被删除并重新创建。重新创建后的 Pod 通常拥有新的 IP 和 UID,所以 Pod IP 不能当作稳定地址,数据也不能只写在容器可写层里。

Pod 的阶段(status.phase)只有下面五种:

阶段 含义
Pending Pod 已创建,但还没有完成调度、镜像拉取或容器启动
Running Pod 已绑定 Node,至少有容器正在运行或启动中
Succeeded Pod 中的容器都以成功状态结束
Failed Pod 中的容器都已终止,且至少有一个以失败状态结束
Unknown 控制平面暂时无法取得 Pod 的状态

READY 列表示 Pod 中已经通过就绪检查的容器数量,不等于容器进程“还活着”。一个进程存活但尚未准备好接收请求时,应该保持未就绪,避免 Service 把流量发送过去。

Pod 的 IP 与 Service

Pod IP 用于集群内部通信,但它不是服务发现地址。扩容或重建会让后端 Pod 集合变化,客户端不应该写死某个 Pod IP。

Service 通过 Selector 找到一组标签匹配的 Pod,并提供稳定的虚拟地址与 DNS 名称:

1
2
3
4
客户端 → web.default.svc.cluster.local:80
                    ↓ Service Selector
             web-xxxxx Pod:8080
             web-yyyyy Pod:8080

在同一个 Namespace 中,通常可以直接使用 Service 名称,例如 http://web:80。Service 的 port 是客户端访问的端口,targetPort 是后端容器监听的端口,两者可以不同:上图中 Service 端口是 80,Pod 监听 8080;第六节的 Nginx 示例两者都是 80。

Pod 与工作负载对象的关系

生产环境通常不直接创建裸 Pod,而是使用更高层的工作负载对象:

  • Deployment 管理无状态应用的 ReplicaSet 和 Pod。
  • StatefulSet 管理需要稳定身份和持久存储的副本。
  • DaemonSet 确保每个符合条件的 Node 运行一个 Pod 副本。
  • Job 和 CronJob 管理一次性或定时任务。

这些对象最终都会创建 Pod,但它们负责的生命周期不同。删除由 Deployment 管理的 Pod,Deployment 会通过 ReplicaSet 补回副本;直接删除裸 Pod 就真的没了,没有上层对象会把它补回来。

六、用一个最小示例把对象连起来

下面用一个最小示例把前面的对象串起来:两个 Nginx Pod 加一个 Service,不依赖业务代码。

动手前需要一个集群。我这里用 kind:它用 Docker 容器模拟 Kubernetes 节点,正好复用之前装好的 Docker;用 Docker Desktop 的话,也可以直接在设置里打开内置的 Kubernetes。先按官方文档装好 kubectl 和 kind,再执行:

1
2
kind create cluster --name k8s-lab
kubectl get nodes

首次创建要拉取节点镜像,等节点的 STATUS 变成 Ready 就可以继续。kind 默认只有一个节点,ROLES 显示 control-plane,控制平面组件和业务 Pod 跑在同一个节点上,也就是前面说的单机学习集群。第四节的检查命令也可以在这个集群中执行。

保存为 web.yaml:

 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
39
40
41
42
43
44
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:1.30-alpine
          ports:
            - name: http
              containerPort: 80
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              memory: 128Mi
          readinessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds: 2
            periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
    - name: http
      port: 80
      targetPort: http

resources.requests 就是第三节提到的调度依据:Scheduler 按 requests 而不是实时用量判断 Node 能否容纳这个 Pod。limits.memory 是容器可用内存的上限,超出后容器会被 OOM Kill。

创建和观察

1
2
3
4
kubectl apply -f web.yaml
kubectl get deployment web
kubectl get pods -l app=web -o wide
kubectl get service web

预期 Deployment 的 READY 最终为 2/2,两个 Pod 都进入 Running,Service 获得 ClusterIP。镜像首次拉取需要时间,等待期间可以执行:

1
2
kubectl rollout status deployment/web
kubectl describe pod -l app=web

kubectl get pods 的 STATUS 列显示的不只是上一节表格里的 Pod 阶段:拉取镜像、创建容器期间会显示 ContainerCreating,镜像拉取失败时显示 ErrImagePull 或 ImagePullBackOff,容器反复崩溃时显示 CrashLoopBackOff。这些是容器处于等待状态的原因,排查时先看 kubectl describe pod 输出末尾的 Events。

Service 未指定 type 时默认是 ClusterIP,只能在集群内部访问。在本地实验集群中,用端口转发从宿主机访问 Service:

1
kubectl port-forward service/web 8080:80

保持这个命令运行,在另一个终端执行:

1
curl http://127.0.0.1:8080

port-forward 是本地调试入口,不是生产环境的外部暴露方案。它指向 Service 时,kubectl 只会从中选择一个 Pod 建立连接,请求不经过 Service 的负载均衡;这个 Pod 被删除后,转发会直接断开。

生产环境一般通过 Gateway API 或云负载均衡器把流量引入集群。以前常用的 Ingress 已经冻结,不再增加新功能,社区版 ingress-nginx 控制器也在 2026 年 3 月退役了。

观察自愈行为

先在运行 port-forward 的终端按 Ctrl+C 结束转发(它连着的 Pod 可能正好是要删的那个),再删除一个 Pod:

1
2
3
POD=$(kubectl get pods -l app=web -o jsonpath='{.items[0].metadata.name}')
kubectl delete pod "$POD"
kubectl get pods -l app=web --watch

Deployment 期望有两个副本。一个 Pod 被删除后,ReplicaSet 会创建替代 Pod。新 Pod 的名字和 UID 一定与旧 Pod 不同,IP 通常也会变化,但 Service 仍然通过标签找到可用副本。

清理实验

1
kubectl delete -f web.yaml

这个动作删除 Deployment、ReplicaSet、Pod 和 Service。它不会删除集群本身,也不会删除镜像仓库中的镜像。

不再需要实验集群时,再删除 kind 集群:

1
kind delete cluster --name k8s-lab

七、先记住这张关系图

  flowchart TB
    CP[控制平面<br>保存并协调集群状态]
    CP -.控制器调谐.-> Deploy[Deployment]
    CP -.调度器分配 Node.-> Kubelet[kubelet / Node]
    Deploy -->|管理| RS[ReplicaSet]
    RS -->|维持副本数| Pod
    Kubelet -->|启动容器、上报状态| Pod
    Svc[Service] -->|Selector 匹配标签| Pod

    subgraph Pod[Pod]
        direction TB
        C[容器]
        Net[共享网络]
        Vol[(共享卷)]
    end

最后用五句话总结一下:

  1. Kubernetes 用声明式 API 管理容器化应用的期望状态。
  2. 控制平面负责接收状态、保存状态、调度 Pod 和持续修正差异。
  3. Node 提供运行 Pod 所需的 kubelet、容器运行时和网络能力。
  4. Pod 是最小部署单位,容器在 Pod 内共享网络和生命周期边界。
  5. Deployment 管理 Pod 副本,Service 为变化的 Pod 提供稳定访问入口。
comments powered by Disqus
使用 Hugo 构建
主题 Stack 由 Jimmy 设计