跳到主要内容

11 篇博文 含有标签「Architecture」

软件和系统架构体系

查看所有标签

Kubernetes 全景解析 (5):安全体系与可观测性全景

· 阅读需 20 分钟
Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡

前言

当你的 Kubernetes 集群从开发环境走向生产环境,有两个话题再也无法回避——安全可观测性。安全是底线,确保只有正确的主体才能访问正确的资源;可观测性是生命线,让你在问题发生时能快速定位、快速恢复。

本文将从 K8s 安全的 4C 模型出发,深入 RBAC 权限控制、Pod 安全标准与服务账户管理;随后转向可观测性的三大支柱——指标、日志与链路追踪,构建一套完整的监控与诊断体系。


一、K8s 安全架构概览

1.1 4C 安全模型

Kubernetes 社区提出了经典的 4C 安全模型,将安全分为四个层次,从外到内逐层收紧:

层次名称关注点示例
第 1 层Cloud(云)云平台自身的安全配置IAM 策略、VPC、防火墙规则
第 2 层Cluster(集群)集群组件的访问控制RBAC、网络策略、审计日志
第 3 层Container(容器)容器运行时安全镜像签名、只读根文件系统、非 root 运行
第 4 层Code(代码)应用代码自身的安全输入校验、依赖漏洞、密钥管理
核心原则

4C 模型的关键思想是纵深防御(Defense in Depth)——任何单一层次的安全措施都不足以应对所有威胁,只有层层设防,才能构建真正可靠的安全体系。内层的安全措施不应依赖外层,每一层都应独立提供保护。

1.2 安全最佳实践总览

领域最佳实践
认证启用 TLS 双向认证,禁用匿名访问
授权最小权限原则,使用 RBAC 精细控制
Pod 安全强制非 root 运行,只读根文件系统
网络默认拒绝,按需开放 NetworkPolicy
镜像使用可信仓库,启用镜像签名验证
密钥使用外部密钥管理(Vault/AWS KMS),避免明文存储
审计启用 API Server 审计日志,记录所有变更

二、RBAC:基于角色的访问控制

2.1 核心概念

RBAC(Role-Based Access Control)是 K8s 授权机制中最常用、最推荐的方式。它通过四个核心对象实现权限管理:

对象作用范围说明
Role命名空间级别定义在某个命名空间内的权限规则
ClusterRole集群级别定义集群范围的权限规则
RoleBinding命名空间级别将 Role 绑定到用户/组/服务账户
ClusterRoleBinding集群级别将 ClusterRole 绑定到用户/组/服务账户
Role vs ClusterRole 的关键区别
  • Role 只能管理同一个命名空间内的资源
  • ClusterRole 可以管理所有命名空间以及非命名空间资源(如 Node、PersistentVolume、Namespace)

2.2 RBAC 权限模型

2.3 完整 YAML 示例

Role —— 命名空间管理员

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: namespace-admin
namespace: production
rules:
- apiGroups: ["", "apps", "batch"]
resources:
- pods
- pods/log
- deployments
- services
- configmaps
- secrets
- jobs
- cronjobs
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

ClusterRole —— 只读查看者

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cluster-readonly
rules:
- apiGroups: ["", "apps", "batch", "extensions"]
resources: ["*"]
verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/healthz", "/version", "/metrics"]
verbs: ["get"]

RoleBinding —— 将角色绑定到用户

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: bind-namespace-admin
namespace: production
subjects:
- kind: User
name: alice@example.com
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: dev-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: namespace-admin
apiGroup: rbac.authorization.k8s.io

ClusterRoleBinding —— 将只读角色绑定到服务账户

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: bind-readonly-to-monitor
subjects:
- kind: ServiceAccount
name: prometheus
namespace: monitoring
roleRef:
kind: ClusterRole
name: cluster-readonly
apiGroup: rbac.authorization.k8s.io

2.4 常见 RBAC 模式

模式适用场景实现方式
命名空间管理员团队负责人管理自己的命名空间Role + RoleBinding
只读查看者开发/测试人员查看集群状态ClusterRole + ClusterRoleBinding
开发人员开发者在指定命名空间部署应用Role(限制 verbs) + RoleBinding
运维人员运维管理节点、PV 等集群级资源ClusterRole(节点/PV 权限) + ClusterRoleBinding
聚合角色将多个 ClusterRole 合并为一个统一角色ClusterRole + aggregationRule
ClusterRole 聚合(Aggregated ClusterRoles)

Kubernetes 支持通过 aggregationRule 将多个 ClusterRole 聚合为一个组合角色。控制平面会自动监视匹配标签选择器的 ClusterRole,并将其规则合并到聚合角色的 rules 字段中。默认的用户角色(如 admineditview)就是通过聚合机制实现的,允许集群管理员通过添加匹配标签的 ClusterRole 来扩展默认角色的权限。

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.example.com/aggregate-to-monitoring: "true"
rules: [] # 控制平面自动填充规则
注意事项
  • roleRef 一旦创建不可修改,如需变更必须删除重建 Binding
  • 避免使用 verbs: ["*"]resources: ["*"] 的通配符,遵循最小权限原则
  • 定期审计集群中的 RoleBinding 和 ClusterRoleBinding,清理不再需要的权限

三、Pod 安全标准

3.1 从 PSP 到 PSS

Kubernetes 在 v1.21 中废弃了 PodSecurityPolicy(PSP),并在 v1.25 中完全移除。取而代之的是 Pod Security Standards(PSS) + Pod Security Admission(PSA) 机制。

特性PSP(已废弃)PSS + PSA(推荐)
实现方式准入控制器(Admission Controller)内置准入插件
配置粒度每个 Pod 可绑定不同策略按命名空间统一标签
策略继承支持不支持(需借助第三方 Gatekeeper)
状态v1.25 已移除v1.23+ 内置可用

3.2 三种策略级别

级别说明典型控制
Privileged无限制,完全开放无任何控制
Baseline最低限度的安全防护禁止特权容器、禁止挂载宿主机路径、限制 hostNetwork、限制 SELinux 类型、禁止探针/生命周期钩子中的 host 字段(v1.34+)、限制 AppArmor 配置文件、限制安全 sysctl
Restricted高度安全,生产推荐强制非 root、只读根文件系统、限制 Capabilities(仅允许 NET_BIND_SERVICE)、禁止 seccomp profile=unconfined、要求显式设置 seccomp
Baseline 策略的完整控制列表(基于 v1.34 官方文档)

根据 Pod Security Standards 官方文档,Baseline 策略包含以下控制:

控制项说明
HostProcess禁止 Windows HostProcess 容器
Host Namespaces禁止共享 hostNetwork/hostPID/hostIPC
Privileged Containers禁止特权容器
Capabilities限制可添加的 Capabilities(如 AUDIT_WRITE、CHOWN、NET_BIND_SERVICE 等)
HostPath Volumes禁止 hostPath 卷
Host Ports禁止或限制 hostPort
Host Probes / Lifecycle Hooks(v1.34+)禁止探针和生命周期钩子中的 host 字段
AppArmor限制 AppArmor 配置文件类型(RuntimeDefault/Localhost)
SELinux限制 SELinux 类型(container_t/container_init_t/container_kvm_t/container_engine_t),禁止自定义 user/role
/proc Mount Type要求使用默认 /proc 掩码
Seccomp禁止显式设置为 Unconfined
Sysctls仅允许安全 sysctl 子集

3.3 Pod Security Admission 配置

PSA 通过在命名空间上打标签来生效,支持三种执行模式:

# 在命名空间上配置 PSA 标签
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
# enforce: 违反策略的 Pod 将被拒绝
pod-security.kubernetes.io/enforce: restricted
# audit: 违反策略的 Pod 会记录审计日志
pod-security.kubernetes.io/audit: restricted
# warn: 违反策略的 Pod 会触发用户警告
pod-security.kubernetes.io/warn: restricted
# 指定策略版本
pod-security.kubernetes.io/enforce-version: latest

3.4 v1.34 新增安全控制详解

Kubernetes v1.34 在 Pod Security Standards 中引入了多项重要安全控制更新:

3.4.1 Host Probes / Lifecycle Hooks(v1.34+)

这是 v1.34 中 Baseline 策略新增的控制项,禁止在探针和生命周期钩子中使用 host 字段,防止容器通过 HTTP/TCP 探针访问宿主机网络。

受限字段包括:

  • livenessProbe.httpGet.host / readinessProbe.httpGet.host / startupProbe.httpGet.host
  • livenessProbe.tcpSocket.host / readinessProbe.tcpSocket.host / startupProbe.tcpSocket.host
  • lifecycle.postStart.httpGet.host / lifecycle.preStop.httpGet.host
  • lifecycle.postStart.tcpSocket.host / lifecycle.preStop.tcpSocket.host

允许值Undefined/nil 或空字符串 ""

这意味着在 Baseline 策略下,探针和生命周期钩子不能再指向宿主机的 IP 地址或主机名,有效防止了通过探针机制进行的 SSRF(服务端请求伪造)攻击。

3.4.2 SELinux 类型扩展

SELinux 控制在 Baseline 策略中进一步收紧:

  • 允许的 SELinux 类型container_tcontainer_init_tcontainer_kvm_tcontainer_engine_t(自 Kubernetes 1.31起新增)
  • 禁止:自定义 SELinux userrole 选项(允许值仅为 Undefined/""

container_engine_t 类型适用于需要与容器引擎交互的特殊工作负载场景。

3.4.3 安全 Sysctls 扩展

自 Kubernetes 1.29 起,Baseline 策略允许的安全 sysctl 列表新增了以下 TCP keepalive 相关参数:

Sysctl 名称说明新增版本
net.ipv4.tcp_keepalive_timeTCP keepalive 探测间隔since 1.29
net.ipv4.tcp_fin_timeoutTCP FIN 超时时间since 1.29
net.ipv4.tcp_keepalive_intvlTCP keepalive 探测间隔since 1.29
net.ipv4.tcp_keepalive_probesTCP keepalive 探测次数since 1.29

完整的安全 sysctl 列表还包括:kernel.shm_rmid_forcednet.ipv4.ip_local_port_rangenet.ipv4.ip_unprivileged_port_startnet.ipv4.tcp_syncookiesnet.ipv4.ping_group_rangenet.ipv4.ip_local_reserved_ports(since 1.27)。

3.4.4 AppArmor 控制

Baseline 策略对 AppArmor 配置文件的控制:

  • 允许的类型RuntimeDefaultLocalhost,或通过注解 container.apparmor.security.beta.kubernetes.io/* 设置 runtime/defaultlocalhost/*
  • 目的:防止禁用或绕过默认的 AppArmor 安全配置文件

3.5 SecurityContext 配置

SecurityContext 是 Pod 安全的核心配置点,可以在 Pod 级别和容器级别分别设置:

apiVersion: v1
kind: Pod
metadata:
name: secure-pod
namespace: production
spec:
securityContext:
# Pod 级别:所有容器共享
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginx:1.25
securityContext:
# 容器级别:仅对当前容器生效
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /var/cache/nginx
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
为什么需要 emptyDir 挂载?

当设置 readOnlyRootFilesystem: true 时,Nginx 等应用需要写入 /tmp 和缓存目录。通过 emptyDir 提供临时可写目录,既满足了应用需求,又保持了根文件系统的只读安全。


四、服务账户与 Token 管理

4.1 ServiceAccount 概念

Kubernetes 中每个 Pod 都关联一个服务账户(ServiceAccount),用于标识 Pod 的身份。当 Pod 需要访问 API Server 时,使用的就是 ServiceAccount 的凭证。

# 创建专用服务账户
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-service-account
namespace: production
automountServiceAccountToken: false

4.2 自动挂载 Token 的风险

默认情况下,K8s 会将 ServiceAccount Token 自动挂载到每个 Pod 的 /var/run/secrets/kubernetes.io/serviceaccount/ 目录。这意味着:

  • 任何能进入容器的人都能获取 Token
  • 如果容器被攻破,攻击者可以利用 Token 访问 API Server
  • 对于不需要访问 API Server 的 Pod,这是不必要的安全暴露
# 方式一:在 Pod 规范中禁用自动挂载
apiVersion: v1
kind: Pod
metadata:
name: app-no-token
spec:
serviceAccountName: app-service-account
automountServiceAccountToken: false
containers:
- name: app
image: myapp:latest
# 方式二:在 ServiceAccount 上全局禁用
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-service-account
namespace: production
automountServiceAccountToken: false

4.3 外部 Token 供应

对于需要更精细控制 Token 生命周期(如短时效 Token、Token 轮换)的场景,可以使用 TokenRequest API 获取面向 Pod 的绑定 Token:

# 使用 projected volume 注入短时效 Token
apiVersion: v1
kind: Pod
metadata:
name: app-with-projected-token
spec:
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: token
mountPath: /var/run/secrets/tokens
volumes:
- name: token
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600 # Token 有效期 1 小时
audience: api
最佳实践
  1. 不需要访问 API Server 的 Pod:设置 automountServiceAccountToken: false
  2. 需要访问 API Server 的 Pod:创建专用的 ServiceAccount,配合最小权限的 RBAC
  3. 高安全要求场景:使用 projected volume + 短时效 Token,实现自动轮换

五、可观测性体系概览

5.1 三大支柱

可观测性(Observability)的三大支柱构成了完整的系统诊断能力:

支柱回答的问题典型工具数据特征
Metrics(指标)系统现在是什么状态?Prometheus、Thanos结构化数值,适合聚合与告警
Logs(日志)系统发生了什么?Fluent Bit、Loki、ELK非结构化文本,适合问题排查
Traces(链路追踪)请求经过了哪些路径?Jaeger、Zipkin、Tempo结构化事件流,适合性能分析与故障定位
为什么需要三大支柱?

单一支柱无法回答所有问题。指标告诉你"CPU 使用率 90%",但不能告诉你"是哪个请求导致的";日志告诉你"请求超时了",但不能告诉你"请求在哪个服务卡住了"。只有三者结合,才能快速、准确地定位问题。


六、Prometheus + Grafana:指标监控

6.1 Prometheus 在 K8s 中的部署

在生产环境中,推荐使用 kube-prometheus-stack(基于 Prometheus Operator)一键部署完整的监控栈:

# 通过 Helm 安装 kube-prometheus-stack
# helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# helm install prometheus prometheus-community/kube-prometheus-stack \
# --namespace monitoring --create-namespace

Prometheus Operator 引入了四个自定义资源(CRD)来声明式管理监控配置:

CRD作用
Prometheus管理 Prometheus 实例的生命周期
ServiceMonitor基于 Service 选择器自动发现监控目标
PodMonitor基于 Pod 标签直接发现监控目标
PrometheusRule声明式定义告警规则和录制规则

6.2 核心监控指标

级别关键指标说明
容器级container_cpu_usage_seconds_total容器 CPU 使用量
container_memory_working_set_bytes容器实际内存使用(含 Page Cache)
container_fs_usage_bytes容器文件系统使用量
Pod 级kube_pod_status_phasePod 当前阶段(Pending/Running/Succeeded/Failed)
kube_pod_container_status_restarts_totalPod 重启次数
kube_pod_container_status_waiting_reason容器等待原因(ImagePullBackOff 等)
Node 级node_cpu_seconds_total节点 CPU 使用量
node_memory_MemAvailable_bytes节点可用内存
kube_node_status_condition节点状态(Ready/NotReady)

6.3 ServiceMonitor 示例

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: app-service-monitor
namespace: monitoring
labels:
release: prometheus # 匹配 Prometheus 实例的选择器
spec:
selector:
matchLabels:
app: my-application # 匹配目标 Service 的标签
namespaceSelector:
names:
- production # 监控 production 命名空间中的 Service
endpoints:
- port: http # Service 中的端口名
path: /metrics # 指标暴露路径
interval: 30s # 采集间隔
scrapeTimeout: 10s # 采集超时

6.4 告警规则示例

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: app-alerting-rules
namespace: monitoring
labels:
release: prometheus
spec:
groups:
- name: app-alerts
rules:
- alert: PodCrashLooping
expr: rate(kube_pod_container_status_restarts_total[15m]) * 60 * 5 > 0
for: 15m
labels:
severity: critical
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 处于 CrashLoopBackOff"
description: "容器 {{ $labels.container }} 在过去 15 分钟内重启超过 5 次"

- alert: HighMemoryUsage
expr: |
(container_memory_working_set_bytes / container_spec_memory_limit_bytes) > 0.9
and container_spec_memory_limit_bytes > 0
for: 5m
labels:
severity: warning
annotations:
summary: "容器 {{ $labels.container }} 内存使用率超过 90%"
description: "当前使用 {{ $value | humanizePercentage }},限制 {{ $labels.container }}"

- alert: NodeNotReady
expr: kube_node_status_condition{condition="Ready",status="true"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "节点 {{ $labels.node }} 处于 NotReady 状态"
告警设计原则
  • Critical 告警:需要立即响应(如节点宕机、Pod CrashLoop)
  • Warning 告警:需要关注但不必立即处理(如磁盘使用率 > 80%)
  • 设置合理的 for 持续时间,避免瞬时抖动触发误报
  • 告警信息要包含足够的上下文,让值班人员无需额外查询即可判断影响范围

七、日志收集体系

7.1 日志收集模式对比

模式原理优点缺点
Sidecar每个 Pod 注入一个日志采集容器隔离性好,可按应用定制资源开销大,管理复杂
DaemonSet每个 Node 运行一个采集 Agent资源开销低,管理简单需要共享日志卷
直连应用直接推送日志到后端无需额外组件与后端强耦合,侵入应用

7.2 常见方案对比

方案组合特点适用场景
PLG StackPromtail + Loki + Grafana轻量,与 Grafana 深度集成已有 Grafana 的团队
EFK StackFluentd/Fluent Bit + Elasticsearch + Kibana功能强大,全文搜索大规模日志分析
Fluent Bit + LokiFluent Bit + Loki + Grafana极低资源消耗资源敏感的环境

7.3 Fluent Bit DaemonSet 配置

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit
namespace: logging
labels:
app: fluent-bit
spec:
selector:
matchLabels:
app: fluent-bit
template:
metadata:
labels:
app: fluent-bit
spec:
serviceAccountName: fluent-bit
tolerations:
- key: node-role.kubernetes.io/control-plane
effect: NoSchedule
containers:
- name: fluent-bit
image: fluent/fluent-bit:3.0.0
volumeMounts:
- name: varlog
mountPath: /var/log
readOnly: true
- name: containers
mountPath: /var/lib/docker/containers
readOnly: true
- name: config
mountPath: /fluent-bit/etc/fluent-bit.conf
subPath: fluent-bit.conf
volumes:
- name: varlog
hostPath:
path: /var/log
- name: containers
hostPath:
path: /var/lib/docker/containers
- name: config
configMap:
name: fluent-bit-config

八、链路追踪

8.1 OpenTelemetry 在 K8s 中的集成

OpenTelemetry(OTel) 是 CNCF 的可观测性标准框架,统一了指标、日志和链路追踪的数据采集。在 K8s 中,通常部署 OpenTelemetry Collector 作为数据的统一入口:

apiVersion: apps/v1
kind: Deployment
metadata:
name: otel-collector
namespace: observability
spec:
replicas: 2
selector:
matchLabels:
app: otel-collector
template:
metadata:
labels:
app: otel-collector
spec:
containers:
- name: otel-collector
image: otel/opentelemetry-collector-contrib:0.96.0
args:
- --config=/etc/otelcol/config.yaml
ports:
- containerPort: 4317 # OTLP gRPC
- containerPort: 4318 # OTLP HTTP
- containerPort: 8889 # Prometheus metrics
volumeMounts:
- name: config
mountPath: /etc/otelcol
volumes:
- name: config
configMap:
name: otel-collector-config

8.2 分布式追踪最佳实践

实践说明
统一 SDK 接入所有服务使用 OpenTelemetry SDK,自动注入 Trace Context
合理的采样策略生产环境使用尾部采样(Tail Sampling),基于错误率/延迟/状态码决定是否保留
上下文传播确保跨服务调用时 TraceID 正确传播(HTTP Header、gRPC Metadata)
关联日志与追踪在日志中注入 TraceID,实现日志与追踪的联动查询
设置合理的 Span 层级避免过深的 Span 嵌套,关注关键路径和外部调用
TraceID 与日志联动

在应用日志中添加 TraceID 字段,可以在 Grafana 中实现从告警到日志再到追踪的完整排查链路:

# 应用日志示例
2026-04-09 10:23:45 INFO [trace_id=abc123 span_id=def456] Processing order request from user_1234
2026-04-09 10:23:46 ERROR [trace_id=abc123 span_id=def456] Failed to call payment service: timeout

九、本章小结

本文从安全与可观测性两个维度,系统梳理了 Kubernetes 生产环境中的关键能力:

安全体系方面,我们覆盖了从 4C 安全模型到具体实践的完整链路:

  • RBAC 提供了精细的权限控制,通过 Role/ClusterRole 和 Binding 的组合,实现最小权限原则。ClusterRole 还支持 aggregationRule 聚合机制,便于扩展默认角色权限。
  • Pod Security Standards 替代了已废弃的 PSP,通过 PSA 标签为命名空间设置安全基线。v1.34 中 Baseline 策略新增了 Host Probes/Lifecycle Hooks 控制,SELinux 新增 container_engine_t 类型,安全 sysctl 列表也进一步扩展。
  • ServiceAccount Token 管理 教会我们如何控制 Pod 的 API Server 访问权限,避免不必要的凭证暴露。

可观测性体系方面,我们构建了三大支柱的完整图景:

  • Prometheus + Grafana 负责指标采集、可视化与告警,是监控体系的核心
  • Fluent Bit + Loki 提供轻量高效的日志收集与查询能力
  • OpenTelemetry + Jaeger 实现了分布式链路追踪,让跨服务调用路径一目了然

安全与可观测性不是孤立的话题——良好的可观测性是安全事件响应的基础,而安全策略本身也需要被持续监控。在下一篇文章中,我们将深入 Kubernetes 的调度策略与资源管理,探讨如何让集群的每一份资源都物尽其用。


十、官方文档参考

本文内容基于 Kubernetes v1.34 官方文档校验,以下是相关官方文档链接:

安全相关

可观测性相关


系列导航

章节主题状态
0架构设计与核心概念✅ 已发布
1工作负载与 Pod 生命周期深度解析✅ 已发布
2网络模型与服务发现全链路解析✅ 已发布
3存储体系与配置管理深度剖析✅ 已发布
4调度器、资源管理与弹性伸缩✅ 已发布
5安全体系与可观测性全景✅ 已发布
6生产级微服务架构实战✅ 已发布
7有状态应用与 Operator 模式实战✅ 已发布

Kubernetes 全景解析 (4):调度器、资源管理与弹性伸缩

· 阅读需 21 分钟
Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡

前言

在 Kubernetes 集群中,调度器(Scheduler)扮演着"决策中枢"的角色——每一个 Pod 应该运行在哪个节点上,都由它来决定。而资源管理(Resource Management)与弹性伸缩(Autoscaling)则是保障集群高效、稳定运行的关键机制。这三者共同构成了 K8s 资源层的核心能力。

本文将从资源模型出发,深入剖析 kube-scheduler 的调度机制,全面讲解 HPA/VPA/Cluster Autoscaler 三级弹性伸缩策略,并介绍 ResourceQuota、LimitRange、PriorityClass 等资源治理工具,帮助你构建一套完整的 K8s 资源管理知识体系。


一、资源模型基础

1.1 Kubernetes 可管理资源类型

Kubernetes 对容器可以使用的资源进行了抽象,目前支持以下几种核心资源类型:

资源类型缩写说明可压缩
CPUcpu计算资源,以核心(core)为单位,支持小数(如 0.5 = 500m)
Memorymemory内存资源,以字节为单位(支持 Ki/Mi/Gi)
Ephemeral Storageephemeral-storage临时存储(日志、EmptyDir 等),以字节为单位
Extended Resourcesnvidia.com/gpu扩展资源(如 GPU、Infiniband),由设备插件注册
可压缩 vs 不可压缩资源
  • 可压缩资源(Compressible):CPU 是可压缩的。当 Pod 超过 CPU Limit 时,不会被杀掉,而是被限流(throttled),表现为性能下降。
  • 不可压缩资源(Incompressible):内存和存储是不可压缩的。当 Pod 超过 Memory Limit 时,会被 OOM Killer 杀掉并重启。

理解这一区别对于合理设置资源配额至关重要。

1.2 Request vs Limit

Kubernetes 通过 requestslimits 两个字段来控制容器的资源使用:

  • Request(请求量):调度器依据此值决定将 Pod 调度到哪个节点(保证最低可用资源)
  • Limit(限制量):运行时容器可使用的资源上限(硬限制)
apiVersion: v1
kind: Pod
metadata:
name: resource-demo
spec:
containers:
- name: app
image: nginx:1.25
resources:
requests:
cpu: "250m" # 0.25 核,调度依据
memory: "256Mi" # 256 MiB,调度依据
limits:
cpu: "500m" # 0.5 核,运行时上限
memory: "512Mi" # 512 MiB,运行时上限,超出则 OOM Kill
Limit 可以省略吗?

如果只设置了 requests 而没有设置 limits,在默认情况下(未配置 LimitRange),limits 默认等于节点可分配资源上限,这意味着容器可以无限使用资源。在生产环境中,强烈建议同时设置 requests 和 limits

1.3 QoS 服务质量类别

Kubernetes 根据资源配额的设置方式,将 Pod 分为三个 QoS(Quality of Service)等级。当节点资源不足时,K8s 会优先驱逐低 QoS 的 Pod。

三个 QoS 等级的详细对比:

QoS 等级条件CPU 行为内存行为驱逐优先级
Guaranteed所有容器 Request == Limit(CPU + Memory)稳定,不会被限流超限则 OOM Kill最低(最后驱逐)
Burstable至少一个容器设置了 Request 或 Limit(但不满足 Guaranteed)可能被限流超限则 OOM Kill中等
BestEffort所有容器均未设置 Request 和 Limit最先被限流最先被 OOM Kill最高(最先驱逐)
生产环境建议

对于核心业务(如支付、订单),建议设置为 Guaranteed 级别,确保资源独占;对于一般业务(如日志处理),设置为 Burstable 即可;BestEffort 仅适用于测试或临时任务。


二、kube-scheduler:调度器深度解析

2.1 调度流程概览

kube-scheduler 是 Kubernetes 的核心组件之一,负责将新创建的、未调度的 Pod 分配到合适的节点上。根据官方文档,kube-scheduler 通过**两步操作(2-step operation)**完成节点选择:

kube-scheduler selects a node for the pod in a 2-step operation: 1. Filtering 2. Scoring —— Kubernetes Scheduler 官方文档

Scheduling Profiles vs Scheduling Policies

官方文档指出,有两种方式配置调度器的过滤和打分行为:

  1. Scheduling Profiles(推荐):通过配置插件(Plugins)实现不同调度阶段,包括 QueueSortFilterScoreBindReservePermit 等扩展点。
  2. Scheduling Policies(旧版方式):通过配置 Predicates(过滤)和 Priorities(打分)来定义调度策略。

2.2 过滤阶段(Filtering)

过滤阶段的目标是快速排除不满足条件的节点,缩小候选范围。通过过滤的节点称为可行节点(Feasible Nodes)。以下是常见的过滤策略:

过滤策略说明配置方式
NodeName直接指定节点名称,跳过所有调度逻辑spec.nodeName
nodeSelector基于节点标签的简单匹配spec.nodeSelector
nodeAffinity基于节点标签的高级匹配(支持软约束)spec.affinity.nodeAffinity
podAffinityPod 亲和性,倾向于与某些 Pod 部署在一起spec.affinity.podAffinity
podAntiAffinityPod 反亲和性,倾向于远离某些 Podspec.affinity.podAntiAffinity
Taint/Toleration节点污点与 Pod 容忍度匹配spec.tolerations
资源检查节点剩余资源是否满足 Pod 的 Request自动
Volume 检查节点是否能挂载 Pod 所需的存储卷自动

2.3 打分阶段(Scoring)

经过过滤后,调度器对剩余的候选节点进行打分,选择得分最高的节点。常见的打分策略包括:

打分策略权重说明
NodeResourcesFit默认 1资源均衡分配,倾向于选择资源使用率更均衡的节点
NodeAffinity默认 1满足节点亲和性软约束的节点获得加分
PodAffinity默认 1满足 Pod 亲和性软约束的节点获得加分
ImageLocality默认 1节点上已存在 Pod 所需镜像时加分(减少拉取时间)
TaintToleration默认 1容忍节点污点的 Pod 获得加分
InterPodAffinity默认 1Pod 间亲和/反亲和性评分
VolumeBinding默认 1PV/PVC 绑定评分
自定义调度器与 Scheduler Framework

Kubernetes 允许你编写自定义调度器(Custom Scheduler),通过实现 Scheduler Framework 的扩展点(Extension Points)来插入自定义逻辑。Scheduling Profiles 配置中支持的插件阶段包括:QueueSortFilterScoreBindReservePermitPreBindPostBind 等。更多详情请参阅 kube-scheduler 配置参考

2.4 nodeSelector:简单节点选择

nodeSelector 是最简单的节点选择方式,通过标签匹配来约束 Pod 的调度目标:

apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
nodeSelector:
gpu: "nvidia-tesla-t4" # 只调度到具有此标签的节点
disktype: "ssd"
containers:
- name: cuda-app
image: nvidia/cuda:12.0-base
resources:
limits:
nvidia.com/gpu: "1" # 申请 1 块 GPU

2.5 nodeAffinity:高级节点亲和性

nodeAffinity 支持更灵活的匹配规则,包括硬约束(required)软约束(preferred)

apiVersion: v1
kind: Pod
metadata:
name: affinity-pod
spec:
affinity:
nodeAffinity:
# 硬约束:必须满足,否则 Pod 无法调度
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["us-east-1a", "us-east-1b"]
- key: node-type
operator: NotIn
values: ["spot-instance"]
# 软约束:尽量满足,不满足也能调度
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: node-role.kubernetes.io/worker
operator: In
values: ["high-memory"]
containers:
- name: app
image: nginx:1.25
操作符说明

nodeAffinity 支持的操作符:InNotInExistsDoesNotExistGtLt。其中 GtLt 仅用于数值比较。

2.6 podAffinity 与 podAntiAffinity

Pod 亲和性用于控制 Pod 之间的部署位置关系:

apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
affinity:
# Pod 亲和性:与 cache Pod 部署在同一可用区
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: cache
topologyKey: topology.kubernetes.io/zone
# Pod 反亲和性:不同副本尽量分布在不同节点
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: web
topologyKey: kubernetes.io/hostname
containers:
- name: web
image: nginx:1.25
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
topologyKey 的重要性

topologyKey 是亲和性规则的关键字段,它定义了拓扑域的划分维度。常用的值包括:

  • kubernetes.io/hostname:节点级别
  • topology.kubernetes.io/zone:可用区级别
  • topology.kubernetes.io/region:区域级别

选择合适的 topologyKey 对于实现高可用部署至关重要。

2.7 Taint 与 Toleration:污点容忍机制

Taint(污点)是作用于节点上的标记,用于排斥不容忍该污点的 Pod。Toleration(容忍度)是 Pod 上的声明,表示可以容忍特定的污点。

# 给节点添加污点
# kubectl taint nodes node1 dedicated=gpu:NoSchedule

apiVersion: v1
kind: Pod
metadata:
name: gpu-workload
spec:
# 声明容忍度
tolerations:
- key: "dedicated"
operator: "Equal"
value: "gpu"
effect: "NoSchedule"
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
containers:
- name: cuda-app
image: nvidia/cuda:12.0-base

Taint 的 effect 类型:

Effect说明
NoSchedule不调度新的不容忍 Pod(已运行的 Pod 不受影响)
PreferNoSchedule尽量不调度(软约束)
NoExecute不调度,且驱逐已运行的不容忍 Pod
常见内置 Taint

Kubernetes 自动为特定场景的节点添加 Taint:

  • node.kubernetes.io/not-ready:节点未就绪
  • node.kubernetes.io/unreachable:节点不可达
  • node.kubernetes.io/memory-pressure:内存压力
  • node.kubernetes.io/disk-pressure:磁盘压力
  • node.kubernetes.io/network-unavailable:网络不可用
  • node.kubernetes.io/unschedulable:节点被标记为不可调度(cordon)

三、HPA:水平 Pod 自动伸缩

3.1 HPA 工作原理

Horizontal Pod Autoscaler(HPA)通过监控 Pod 的资源使用指标,自动调整 Deployment/ReplicaSet/StatefulSet 的副本数量,使应用能够根据负载变化自动扩缩。

3.2 扩缩算法详解

HPA 的核心算法如下:

期望副本数 = ceil(当前副本数 × (当前指标值 / 目标指标值))
算法示例

假设 Deployment 当前有 4 个副本,目标 CPU 利用率为 50%:

  • 当前平均 CPU 利用率为 80% → 期望副本数 = ceil(4 × 80/50) = ceil(6.4) = 7
  • 当前平均 CPU 利用率为 20% → 期望副本数 = ceil(4 × 20/50) = ceil(1.6) = 2

注意:HPA 不会将副本数缩减到低于 spec.replicas.minReplicas(默认为 1)。

3.3 指标类型

HPA 支持四种指标类型:

指标类型数据来源示例
ResourceMetrics Server(CPU/内存)CPU 利用率 50%
Container ResourceMetrics Server(容器级别)容器内存使用量
Object自定义指标(如 Ingress QPS)每秒请求数
External外部指标系统(如 Prometheus)Kafka 队列积压消息数

3.4 行为配置

从 Kubernetes 1.18 开始,HPA 引入了 behavior 字段,允许精细控制扩缩行为:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
- type: Resource
resource:
name: memory
target:
type: AverageValue
averageValue: "500Mi"
behavior:
# 缩容冷却窗口与策略
scaleDown:
stabilizationWindowSeconds: 300 # 5 分钟内不重复缩容
policies:
- type: Percent
value: 10 # 每次最多缩容 10%
periodSeconds: 60 # 每 60 秒允许一次缩容
- type: Pods
value: 2 # 或每次最多缩容 2 个 Pod
periodSeconds: 60
selectPolicy: Min # 取两种策略中更保守的
# 扩容策略
scaleUp:
stabilizationWindowSeconds: 0 # 扩容不需要冷却
policies:
- type: Percent
value: 100 # 允许一次扩容 100%
periodSeconds: 15 # 每 15 秒允许一次扩容
- type: Pods
value: 4 # 或每次最多扩容 4 个 Pod
periodSeconds: 15
selectPolicy: Max # 取两种策略中更激进的
扩缩行为建议
  • 扩容应该快:用户不希望等待太久,建议设置较短的 periodSeconds(如 15s)
  • 缩容应该慢:避免频繁缩容导致的服务抖动,建议设置较长的 stabilizationWindowSeconds(如 300s)
  • 使用 selectPolicy:扩容选 Max(激进),缩容选 Min(保守)

四、VPA:垂直 Pod 自动伸缩

4.1 VPA 工作模式

Vertical Pod Autoscaler(VPA)通过调整 Pod 的 CPU 和内存 Request/Limit 来实现垂直伸缩。VPA 支持三种工作模式:

模式说明适用场景
Auto自动更新 Pod 的资源配额(会重启 Pod)非关键业务,可容忍重启
Recreate自动更新,且在更新时重启 Pod同 Auto
Off仅提供建议,不执行变更评估阶段,收集数据

4.2 VPA 与 HPA 的协作与冲突

VPA 与 HPA 的冲突

VPA 和 HPA 不能同时基于 CPU/内存指标工作。原因很简单:HPA 通过增加副本数来降低单个 Pod 的资源使用率,而 VPA 通过增加单个 Pod 的资源配额来满足需求。两者同时基于 CPU/内存工作会导致"扩容循环"——HPA 增加 Pod 数量导致单个 Pod 负载降低,VPA 随之降低资源配额,然后 HPA 又需要更多 Pod 来满足需求。

解决方案:HPA 基于 CPU/内存指标伸缩,VPA 仅基于自定义指标(如内存使用量)工作;或者使用 HPA 基于 CPU 伸缩,VPA 仅调整内存。

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: web-app-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
updatePolicy:
updateMode: "Auto" # Auto / Recreate / Off
resourcePolicy:
containerPolicies:
- containerName: "*"
minAllowed:
cpu: "100m"
memory: "128Mi"
maxAllowed:
cpu: "2"
memory: "2Gi"
controlledResources: ["cpu", "memory"]
recommenders:
- name: custom-recommender # 可自定义推荐器

五、Cluster Autoscaler:节点级伸缩

5.1 CA 工作原理

Cluster Autoscaler(CA)负责在集群级别自动调整节点数量。当 Pod 因资源不足而无法调度(Pending)时,CA 会自动添加新节点;当节点利用率持续偏低时,CA 会自动移除空闲节点。

CA 的核心工作逻辑:

  1. 扩容触发:检测到有 Pod 处于 Pending 状态,且原因是资源不足
  2. 评估节点组:根据 Pod 的资源需求、节点亲和性等约束,选择合适的节点组
  3. 创建节点:调用云厂商 API 创建新节点
  4. 缩容触发:节点利用率持续低于阈值(默认 50%),且节点上的 Pod 可以被迁移到其他节点
  5. 驱逐 Pod:安全驱逐节点上的 Pod(非 Critical Pod、非 DaemonSet Pod)
  6. 删除节点:调用云厂商 API 删除节点

5.2 CA 与 HPA 的配合

CA 和 HPA 的配合构成了 Kubernetes 的两级弹性伸缩体系

层级组件伸缩维度响应速度粒度
第一级HPAPod 数量秒级细粒度
第二级Cluster Autoscaler节点数量分钟级粗粒度

典型工作流程:负载增加 → HPA 增加 Pod 副本 → 节点资源不足 → Pod Pending → CA 添加新节点 → 新 Pod 被调度到新节点。

5.3 云厂商集成

CA 需要与云厂商的 API 集成来管理节点池:

云厂商节点组实现CA 配置
AWSASG(Auto Scaling Group) / EKS Managed Node Group--nodegroup-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled
GCPMIG(Managed Instance Group) / GKE Node Pool自动发现
AzureVMSS(Virtual Machine Scale Set) / AKS Node Pool--nodegroup-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled
阿里云ECI / ACK 节点池--nodegroup-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled
CA 的安全限制

Cluster Autoscaler 不会驱逐以下 Pod:

  • 通过 PDB(PodDisruptionBudget)保护的 Pod(驱逐会违反最小可用数)
  • 没有被 Controller 管理的裸 Pod(Bare Pod)
  • 使用本地存储的 Pod(数据会随节点删除而丢失)
  • kube-system 命名空间中且不是 DaemonSet 管理的 Pod
  • 配置了 "cluster-autoscaler.kubernetes.io/safe-to-evict": "false" 注解的 Pod

六、资源配额与限制

6.1 ResourceQuota:命名空间级资源配额

ResourceQuota 用于限制一个命名空间中可以创建的资源总量,防止某个团队或项目占用过多集群资源。

apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-quota
namespace: team-a
spec:
hard:
# 计算资源配额
requests.cpu: "20" # 该命名空间最多请求 20 核 CPU
requests.memory: "40Gi" # 最多请求 40Gi 内存
limits.cpu: "40" # 最多限制 40 核 CPU
limits.memory: "80Gi" # 最多限制 80Gi 内存
# 对象数量配额
pods: "50" # 最多 50 个 Pod
services: "10" # 最多 10 个 Service
persistentvolumeclaims: "20" # 最多 20 个 PVC
configmaps: "20" # 最多 20 个 ConfigMap
secrets: "20" # 最多 20 个 Secret
# 存储
requests.storage: "100Gi" # 最多请求 100Gi 存储
ResourceQuota 的作用范围

ResourceQuota 只限制设置了 requestslimits 的 Pod。如果一个 Pod 没有设置资源请求,它不会计入 ResourceQuota 的配额,但也不会被调度(除非命名空间配置了 LimitRange 来提供默认值)。

6.2 LimitRange:默认资源限制

LimitRange 用于为命名空间中的 Pod 或 Container 设置默认的资源请求和限制值,确保即使 Pod 的 YAML 中没有声明资源,也能获得合理的默认值。

apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: team-a
spec:
limits:
# Container 级别默认值
- type: Container
default: # 默认 Limit
cpu: "500m"
memory: "512Mi"
defaultRequest: # 默认 Request
cpu: "100m"
memory: "128Mi"
max: # 最大允许值
cpu: "2"
memory: "2Gi"
min: # 最小允许值
cpu: "50m"
memory: "64Mi"
# Pod 级别限制
- type: Pod
max:
cpu: "4"
memory: "4Gi"

6.3 PriorityClass:优先级与抢占

PriorityClass 用于定义 Pod 的优先级。当集群资源不足时,高优先级的 Pod 可以抢占低优先级 Pod 的资源。

# 定义高优先级类
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
description: "核心业务 Pod,优先调度"
value: 1000000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
# 定义低优先级类
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: low-priority
description: "批处理任务,资源空闲时运行"
value: 100
globalDefault: false
preemptionPolicy: PreemptLowerPriority
# 使用优先级类
apiVersion: v1
kind: Pod
metadata:
name: critical-app
spec:
priorityClassName: high-priority
containers:
- name: app
image: nginx:1.25
resources:
requests:
cpu: "1"
memory: "1Gi"
limits:
cpu: "2"
memory: "2Gi"
抢占机制

当高优先级 Pod 无法调度时,调度器会尝试驱逐低优先级 Pod 来释放资源。抢占过程分为两步:

  1. 驱逐(Eviction):找到可以抢占的低优先级 Pod
  2. 等待(Waiting):等待被驱逐的 Pod 优雅终止(graceful shutdown)

系统保留的 PriorityClass:

  • system-cluster-critical(值 2000000000):系统组件专用
  • system-node-critical(值 2000001000):节点关键组件专用(如 kube-proxy)

七、调度策略最佳实践

7.1 生产环境调度建议

场景建议策略
核心业务Guaranteed QoS + nodeAffinity 硬约束 + 高优先级 + PDB 保护
批处理任务BestEffort/Burstable QoS + 低优先级 + 反亲和性避免影响在线业务
GPU 工作负载nodeSelector + Taint/Toleration + ResourceQuota 限制 GPU 数量
高可用部署podAntiAffinity(hostname 级别)+ topologySpreadConstraints
多可用区部署nodeAffinity(zone 级别)+ topologySpreadConstraints

7.2 常见调度问题排查

问题现象可能原因排查命令
Pod 一直 Pending资源不足 / 亲和性不满足 / Taint 不匹配kubectl describe pod <name>
Pod 频繁被驱逐节点资源压力 / QoS 等级过低kubectl get events --sort-by=.metadata.creationTimestamp
节点资源不均衡缺少反亲和性配置 / 打分权重不合理kubectl top nodes
HPA 不生效未安装 Metrics Server / 指标未就绪kubectl get hpa -w
VPA 与 HPA 冲突同时基于 CPU/内存工作检查 VPA 和 HPA 的指标配置
排查 Pending Pod 的黄金法则

当 Pod 处于 Pending 状态时,第一步永远是执行 kubectl describe pod <name>,查看 Events 部分。调度器会在 Events 中详细记录过滤失败的原因,例如:

0/5 nodes are available: 1 Insufficient cpu, 3 node(s) had taint {node.kubernetes.io/disk-pressure:}, that the pod didn't tolerate, 1 node(s) didn't match node selector.

这条信息清晰地告诉你:1 个节点 CPU 不足,3 个节点有磁盘压力污点,1 个节点不匹配 nodeSelector。


八、官方文档参考

本文内容基于 Kubernetes v1.34 官方文档校验,以下是相关官方文档链接:

调度相关

资源管理相关

配额与限制


九、本章小结

本文深入剖析了 Kubernetes 调度与资源管理的核心机制,主要知识点回顾如下:

  1. 资源模型:K8s 通过 Request/Limit 控制容器的资源使用,QoS 等级(Guaranteed/Burstable/BestEffort)决定了资源不足时的驱逐顺序。

  2. 调度器:kube-scheduler 通过官方定义的"Filtering → Scoring"两步操作完成节点选择,随后通过 Binding 将 Pod 绑定到目标节点。nodeSelector/nodeAffinity/podAffinity/Taint-Toleration 提供了灵活的调度约束。

  3. 弹性伸缩

    • HPA:水平伸缩,调整副本数量,响应最快
    • VPA:垂直伸缩,调整资源配额,适合优化资源利用率
    • Cluster Autoscaler:节点级伸缩,配合 HPA 实现完整的弹性能力
  4. 资源治理:ResourceQuota 限制命名空间资源总量,LimitRange 提供默认资源值,PriorityClass 实现优先级与抢占。

  5. 最佳实践:核心业务使用 Guaranteed QoS + 高优先级 + PDB 保护;批处理任务使用低优先级 + 反亲和性;多可用区部署使用 topologySpreadConstraints。

掌握这些机制,你就能在生产环境中构建一个高效、稳定、弹性的 Kubernetes 资源管理体系。

Kubernetes 全景解析 (3):存储体系与配置管理深度剖析

· 阅读需 31 分钟
Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡

前言

在 Kubernetes 的核心能力矩阵中,存储与配置管理是决定应用能否在生产环境稳定运行的关键支柱。容器天生是"用完即弃"的——Pod 被删除后,其文件系统上的所有数据都会随之消失。然而,数据库需要持久化数据文件,日志系统需要保留历史记录,微服务需要在不重新构建镜像的前提下修改配置参数。这些需求共同构成了 K8s 存储体系与配置管理的设计动机。

本文将从存储体系的分层架构出发,逐层深入 Volume、PV/PVC、StorageClass 的设计原理与使用方法,再到 ConfigMap 与 Secret 的配置外部化实践,帮助你建立一套完整的 K8s 存储与配置认知框架。


一、K8s 存储体系概览

1.1 为什么容器需要持久化存储

容器的文件系统由一层层只读的镜像层(Image Layer)和一个可写的容器层(Container Layer)组成。当容器被删除时,可写层也会被一并回收。这意味着:

  • 数据库数据会丢失:MySQL、PostgreSQL 等有状态服务将无法正常工作
  • 日志会丢失:应用产生的日志随容器消亡而消失
  • 文件上传会丢失:用户上传的附件、图片等临时存储在容器内会不可恢复

为了解决这些问题,Kubernetes 引入了 Volume 机制,将外部存储挂载到容器内部的指定路径,使数据独立于容器的生命周期而存在。

1.2 存储体系的分层设计

Kubernetes 的存储体系采用了经典的分层抽象设计,从底层到上层依次为:

层级概念作用域核心职责
L1VolumePod定义 Pod 内部存储的挂载方式
L2PV / PVC集群 / 命名空间解耦存储的供给与消费
L3StorageClass集群实现存储的动态供给与分类

这种分层设计的核心思想是关注点分离:开发人员只需声明"我需要多少存储"(PVC),运维人员负责"提供什么样的存储"(PV/StorageClass),两者通过标准的接口进行匹配。

设计哲学

K8s 存储体系的设计遵循"供需分离"原则。就像云计算中的 IaaS 层将计算资源虚拟化一样,K8s 将存储资源抽象为标准化的 API 对象,使上层应用无需关心底层存储的具体实现(NFS、Ceph、AWS EBS、GCE PD 等)。


二、Volume:Pod 级存储抽象

2.1 Volume 的基本概念

Volume 是 Kubernetes 中最基础的存储抽象,它定义在 Pod 规格中,与 Pod 的生命周期紧密绑定。当 Pod 被创建时,Kubelet 会负责将 Volume 挂载到容器内指定的路径;当 Pod 被删除时,Volume 的处理方式取决于其类型——临时 Volume 会被清除,而持久化 Volume 中的数据则会被保留。

Volume 的几个关键特性:

  • Volume 定义在 Pod 级别:同一个 Pod 中的多个容器可以共享同一个 Volume
  • Volume 的生命周期与 Pod 绑定:Pod 不存在时,Volume 也不存在(临时类型)
  • Volume 支持多种后端:K8s 内置了数十种 Volume 类型,同时通过 CSI(Container Storage Interface)支持第三方存储

2.2 常见 Volume 类型

Volume 类型持久性适用场景说明
emptyDir临时Pod 内容器间共享数据、缓存Pod 删除后数据丢失,可选用内存作为介质
hostPath节点级开发调试、访问节点文件直接挂载宿主机路径,存在安全风险
nfs持久多 Pod 共享文件存储需要外部 NFS 服务器
configMap配置注入配置数据以文件或环境变量形式挂载
secret敏感配置注入密钥、证书Base64 编码存储,支持加密
persistentVolumeClaim持久有状态应用的数据持久化引用 PV,生命周期独立于 Pod
csi取决于后端接入第三方存储通过 CSI 驱动对接各种存储系统

2.3 Volume 生命周期与 Pod 的绑定关系

# Volume 生命周期与 Pod 的关系
# 1. Pod 被调度到某个 Node
# 2. Kubelet 调用 Volume 插件挂载存储到宿主机
# 3. Kubelet 将宿主机上的挂载点绑定到容器内
# 4. Pod 运行期间,容器读写 Volume 中的数据
# 5. Pod 被删除时,Kubelet 执行卸载操作
# 6. 临时 Volume 数据被清除,持久化 Volume 数据被保留
注意

emptyDir 的生命周期严格绑定到 Pod。即使 Pod 被驱逐(Eviction)或节点维护导致 Pod 被重新调度到其他节点,原节点上的 emptyDir 数据也会丢失。

2.4 完整 YAML 示例

以下示例展示了如何在 Pod 中使用多种 Volume:

apiVersion: v1
kind: Pod
metadata:
name: volume-demo
labels:
app: volume-demo
spec:
containers:
# 容器 1:写入数据到共享 Volume
- name: writer
image: busybox:1.36
command: ["/bin/sh", "-c"]
args:
- |
while true; do
date >> /data/log.txt
echo "Writer: data written" >> /data/log.txt
sleep 5
done
volumeMounts:
- name: shared-data
mountPath: /data

# 容器 2:从共享 Volume 读取数据
- name: reader
image: busybox:1.36
command: ["/bin/sh", "-c"]
args: ["tail -f /data/log.txt"]
volumeMounts:
- name: shared-data
mountPath: /data

# 容器 3:使用 hostPath 访问宿主机时间
- name: time-checker
image: busybox:1.36
command: ["/bin/sh", "-c"]
args: ["while true; do cat /host-time/hostname; sleep 10; done"]
volumeMounts:
- name: host-time
mountPath: /host-time

volumes:
# emptyDir:Pod 内容器间共享的临时存储
- name: shared-data
emptyDir:
sizeLimit: 256Mi # 可选:限制存储大小

# hostPath:挂载宿主机目录(仅用于开发/调试)
- name: host-time
hostPath:
path: /etc
type: Directory # Directory/DirectoryOrCreate/File/FileOrCreate/Socket/CharDevice/BlockDevice

三、PV 与 PVC:持久化存储的供需模型

3.1 PV(PersistentVolume):集群级存储资源

PersistentVolume 是集群级别的一块存储资源,由集群管理员创建,或者由 StorageClass 动态供给。PV 独立于 Pod 的生命周期存在,即使使用它的 Pod 被删除,PV 中的数据仍然保留。

PV 的核心属性包括:

  • 容量(Capacity):存储大小,如 10Gi
  • 访问模式(Access Modes):定义如何访问存储
  • 回收策略(Reclaim Policy):定义 PVC 释放后 PV 的处理方式
  • 存储类(StorageClass):定义存储的类型和参数
  • 挂载选项(Mount Options):传递给挂载命令的额外参数

3.2 PVC(PersistentVolumeClaim):命名空间级存储请求

PersistentVolumeClaim 是用户对存储资源的"申请单"。PVC 在命名空间内创建,用户通过 PVC 声明所需的存储大小、访问模式等需求,K8s 控制平面会自动将满足条件的 PV 绑定到该 PVC。

PV 与 PVC 的关系

可以将 PV 理解为"机房里的一块硬盘",PVC 理解为"申请一块硬盘的工单"。管理员提前准备硬盘(静态供给),或者设置自动采购流程(动态供给),用户只需提交工单即可。

3.3 供给模式:静态供给 vs 动态供给

静态供给(Static Provisioning)

管理员预先创建 PV,用户创建 PVC 后,K8s 控制平面根据容量和访问模式进行匹配绑定。这种方式适合存储资源有限、需要精确控制的场景。

动态供给(Dynamic Provisioning)

管理员预先创建 StorageClass,用户在 PVC 中指定 StorageClass,K8s 自动调用对应的 Provisioner 创建 PV 并绑定到 PVC。这种方式是生产环境的主流选择,极大简化了存储管理。

3.4 访问模式

访问模式缩写说明
ReadWriteOnceRWO存储可被单个节点以读写模式挂载
ReadOnlyManyROX存储可被多个节点以只读模式挂载
ReadWriteManyRWX存储可被多个节点以读写模式挂载
ReadWriteOncePodRWOP存储可被单个 Pod 以读写模式挂载(K8s 1.27+ 稳定)
访问模式与存储后端

并非所有存储后端都支持所有访问模式。例如,AWS EBS、GCE PD、Azure Disk 等块存储通常只支持 RWO;而 NFS、CephFS、GlusterFS 等文件存储支持 RWX。在选择存储后端时,务必确认其支持的访问模式是否满足你的需求。

3.5 回收策略

回收策略说明适用场景
RetainPVC 释放后,PV 被保留但标记为 Released,数据不丢失需要手动处理数据的场景
DeletePVC 释放后,PV 和底层存储资源被自动删除动态供给的云盘等临时存储
Recycle执行 rm -rf 清空数据后重新可用已废弃,不建议使用
Recycle 回收策略已废弃

官方明确说明:The Recycle reclaim policy is deprecated. Instead, the recommended approach is to use dynamic provisioning.

Recycle 策略仅对支持该操作的底层卷插件有效,执行基本的 rm -rf /thevolume/* 清理操作。在生产环境中,推荐使用以下替代方案:

  • 动态供给(Dynamic Provisioning):通过 StorageClass 自动创建和管理 PV,设置 reclaimPolicy: DeletereclaimPolicy: Retain
  • Retain + 手动管理:设置 reclaimPolicy: Retain,由管理员手动清理和复用 PV

3.6 PV/PVC 绑定流程

3.7 Storage Object in Use Protection

Storage Object in Use Protection(存储对象使用中保护)机制确保正在被 Pod 使用的 PVC 和已绑定到 PVC 的 PV 不会被意外删除,从而防止数据丢失。

PVC 保护机制

当 PVC 正在被 Pod 使用时,如果用户删除 PVC,PVC 不会立即被删除。PVC 的删除会被推迟,直到它不再被任何 Pod 使用。

PV 保护机制

当管理员删除已绑定到 PVC 的 PV 时,PV 不会立即被删除。PV 的删除会被推迟,直到它不再绑定到 PVC。

查看保护状态

# 查看 PVC 的保护状态
kubectl describe pvc hostpath

输出示例:

Name: hostpath
Namespace: default
StorageClass: example-hostpath
Status: Terminating
Finalizers: [kubernetes.io/pvc-protection] # PVC 保护 finalizer
# 查看 PV 的保护状态
kubectl describe pv task-pv-volume

输出示例:

Name: task-pv-volume
Labels: type=local
Finalizers: [kubernetes.io/pv-protection] # PV 保护 finalizer
StorageClass: standard
Status: Terminating
Claim: default/hostpath
Reclaim Policy: Delete
Access Modes: RWO
Capacity: 1Gi
Finalizer 的作用

Finalizer 是 Kubernetes 中的一种机制,用于确保资源在被删除前完成必要的清理操作。kubernetes.io/pvc-protectionkubernetes.io/pv-protection 这两个 finalizer 确保存储资源在使用中时不会被意外删除。

3.8 PV 删除保护 Finalizer(v1.33 Stable)

FEATURE STATE: Kubernetes v1.33 [stable](默认启用)

除了基础的 Storage Object in Use Protection,Kubernetes v1.33 引入了更强大的 PV 删除保护 finalizer 机制,确保具有 Delete 回收策略的 PV 仅在底层存储被删除后才从 Kubernetes 中移除。

两种 Finalizer

Finalizer 名称引入版本适用范围说明
external-provisioner.volume.kubernetes.io/finalizerv1.31CSI 卷(动态和静态供给)确保底层存储删除后才移除 PV 对象
kubernetes.io/pv-controllerv1.31In-tree 插件卷(仅动态供给)确保底层存储删除后才移除 PV 对象

CSI 卷示例

kubectl describe pv pvc-2f0bab97-85a8-4552-8044-eb8be45cf48d

输出示例:

Name: pvc-2f0bab97-85a8-4552-8044-eb8be45cf48d
Annotations: pv.kubernetes.io/provisioned-by: csi.vsphere.vmware.com
Finalizers: [kubernetes.io/pv-protection external-provisioner.volume.kubernetes.io/finalizer]
StorageClass: fast
Status: Bound
Claim: demo-app/nginx-logs
Reclaim Policy: Delete
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 200Mi
Source:
Type: CSI
Driver: csi.vsphere.vmware.com

In-tree 插件卷示例

kubectl describe pv pvc-74a498d6-3929-47e8-8c02-078c1ece4d78

输出示例:

Name: pvc-74a498d6-3929-47e8-8c02-078c1ece4d78
Annotations: kubernetes.io/provisioned-by: kubernetes.io/vsphere-volume
Finalizers: [kubernetes.io/pv-protection kubernetes.io/pv-controller]
StorageClass: vcp-sc
Status: Bound
Claim: default/vcp-pvc-1
Reclaim Policy: Delete
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 1Gi
Source:
Type: vSphereVolume
Finalizer 的作用

这些 finalizer 确保:

  1. PV 对象仅在底层存储被删除后才从 Kubernetes 中移除
  2. 无论 PV 和 PVC 的删除顺序如何,都能确保底层存储被正确删除
  3. 当启用 CSIMigration{provider} 特性门控时,kubernetes.io/pv-controller 会被 external-provisioner.volume.kubernetes.io/finalizer 替代

3.9 PVC 扩容(v1.24 Stable)

FEATURE STATE: Kubernetes v1.24 [stable](默认启用)

PVC 扩容功能允许用户在 PVC 创建后增加其存储容量,而无需重新创建 PVC。

支持扩容的卷类型

  • CSI 卷(包括部分迁移的卷类型)
  • flexVolume(已废弃)
  • portworxVolume(已废弃)

前提条件

PVC 的 StorageClass 必须设置 allowVolumeExpansion: true

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: example-vol-default
provisioner: vendor-name.example/magicstorage
parameters:
resturl: "http://192.168.10.100:8080"
allowVolumeExpansion: true # 允许扩容

扩容步骤

  1. 编辑 PVC 对象,增加 spec.resources.requests.storage 的值
  2. Kubernetes 控制平面触发底层卷的扩容操作
  3. 不会创建新的 PV,而是扩展现有卷
直接编辑 PV 容量

不要直接编辑 PV 的容量。如果先编辑 PV 的容量,再编辑 PVC 使其与 PV 匹配,Kubernetes 控制平面会认为卷已被手动扩容,不会触发自动扩容操作。

3.10 CSI 卷扩容(v1.24 Stable)

FEATURE STATE: Kubernetes v1.24 [stable](默认启用)

CSI 卷扩容功能默认启用,但需要特定的 CSI 驱动程序支持卷扩容。请参考具体 CSI 驱动程序的文档以获取更多信息。

3.11 VolumeAttributesClasses(VAC)

VolumeAttributesClasses(VAC)是 Kubernetes 中用于管理存储属性的新 API,允许在不重新创建卷的情况下修改存储的某些属性(如性能级别、IOPS、吞吐量等)。

VAC 的作用

  • 修改存储卷的性能参数(IOPS、吞吐量)
  • 切换存储的备份策略
  • 调整存储的加密级别
  • 其他由存储后端支持的属性

VAC 示例

apiVersion: storage.k8s.io/v1alpha1
kind: VolumeAttributesClass
metadata:
name: fast-ssd
driverName: csi.example.com
parameters:
iops: "5000"
throughput: "500"
encryption: "true"
VAC 的使用

VAC 目前仍处于 Alpha 阶段,具体支持程度取决于 CSI 驱动程序的实现。在生产环境使用前,请确认您的存储后端是否支持 VAC。

3.12 完整 YAML 示例

# ============================================
# 1. 静态供给:先创建 PV,再创建 PVC
# ============================================

# PersistentVolume:集群级存储资源
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
nfs:
server: 10.0.0.100
path: /data/mysql
# 可选:挂载选项
mountOptions:
- hard
- nfsvers=4.1

---
# PersistentVolumeClaim:用户存储请求
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
namespace: database
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: manual # 必须与 PV 的 storageClassName 一致

---
# 使用 PVC 的 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
namespace: database
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root-password
ports:
- containerPort: 3306
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: mysql-pvc

四、StorageClass:存储的"类"与动态供给

4.1 StorageClass 的作用与参数

StorageClass 为管理员提供了一种描述存储"类别"的方式。不同的 StorageClass 可以映射到不同的存储后端、不同的性能等级、不同的备份策略等。用户在创建 PVC 时只需指定 StorageClass,K8s 就会自动完成存储的创建和绑定。

StorageClass 的核心参数:

参数说明示例
provisioner存储供给插件kubernetes.io/aws-ebs
parameters供给插件的具体参数type: gp3, iopsPerGB: "50"
reclaimPolicy默认回收策略Delete
volumeBindingMode卷绑定模式WaitForFirstConsumer
allowVolumeExpansion是否允许扩容true

4.2 动态供给工作流

动态供给的核心流程如下:

  1. 用户创建 PVC,指定 storageClassName
  2. PVC Controller 发现没有现成的 PV 可匹配
  3. PVC Controller 调用对应 StorageClass 的 Provisioner
  4. Provisioner 调用底层存储 API(如 AWS CreateVolume)创建实际的存储卷
  5. Provisioner 创建 PV 对象并关联到该 PVC
  6. PVC 状态变为 Bound,Pod 可以使用该 PVC
WaitForFirstConsumer

对于拓扑感知的存储(如 AWS EBS、GCE PD),建议设置 volumeBindingMode: WaitForFirstConsumer。这样 PV 不会在 PVC 创建时立即分配,而是等到第一个使用该 PVC 的 Pod 被调度后,再在与 Pod 相同的可用区(AZ)创建存储卷,避免跨可用区挂载的问题。

4.3 常见 Provisioner

Provisioner存储类型支持的访问模式说明
kubernetes.io/aws-ebsAWS EBSRWOAWS 弹性块存储
kubernetes.io/gce-pdGCE Persistent DiskRWO/RWXGoogle Cloud 持久磁盘
kubernetes.io/azure-diskAzure DiskRWOAzure 磁盘
kubernetes.io/azure-fileAzure FileROX/RWXAzure 文件存储
nfs.csi.k8s.ioNFSROX/RWXNFS CSI 驱动
cephfs.csi.ceph.comCephFSROX/RWXCeph 文件系统
rbd.csi.ceph.comCeph RBDRWOCeph 块存储
local-path-provisioner本地路径RWO轻量级本地存储,适合开发测试

4.4 完整 YAML 示例

# ============================================
# StorageClass:定义存储类别
# ============================================

# 高性能 SSD 存储类(AWS EBS gp3)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
iopsPerGB: "50"
throughput: "250"
fsType: ext4
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true

---
# 标准 HDD 存储类(AWS EBS st1)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard-hdd
provisioner: kubernetes.io/aws-ebs
parameters:
type: st1
fsType: ext4
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: false

---
# 使用动态供给的 PVC(无需预先创建 PV)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data-pvc
namespace: production
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd # 指定 StorageClass,触发动态供给
resources:
requests:
storage: 50Gi

---
# 本地路径 Provisioner(适合开发/测试环境)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-path
provisioner: rancher.io/local-path
parameters:
archiveOnDelete: "true"
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer

五、ConfigMap:配置外部化

5.1 ConfigMap 的创建方式

ConfigMap 用于将非敏感的配置数据从容器镜像中解耦出来,使配置可以在不重新构建镜像的情况下被修改和更新。ConfigMap 支持以下几种创建方式:

方式一:字面量创建

kubectl create configmap app-config \
--from-literal=LOG_LEVEL=info \
--from-literal=MAX_CONNECTIONS=100 \
--from-literal=FEATURE_FLAGS=dark-mode,experimental-api

方式二:从文件创建

kubectl create configmap nginx-config \
--from-file=nginx.conf=./configs/nginx.conf \
--from-file=default.conf=./configs/default.conf

方式三:从目录创建

# 将目录下所有文件作为 ConfigMap 的键值对
kubectl create configmap app-settings \
--from-file=./configs/

方式四:从 env-file 创建

kubectl create configmap app-env \
--from-env-file=./.env

5.2 在 Pod 中使用 ConfigMap

ConfigMap 可以通过三种方式注入到 Pod 中:

注入方式适用场景热更新说明
环境变量简单配置项不支持Pod 重启后生效
Volume 挂载配置文件支持文件内容实时更新
命令行参数启动参数不支持通过 $(ENV_VAR) 引用

5.3 ConfigMap 热更新机制与限制

当通过 Volume 挂载方式使用 ConfigMap 时,K8s 的 kubelet 会定期(默认每 60 秒)检查 ConfigMap 的更新,并将最新的内容同步到挂载的文件中。应用可以通过监听文件变化来重新加载配置。

热更新的限制
  1. 通过环境变量注入的 ConfigMap 不支持热更新,必须重启 Pod 才能生效
  2. 已挂载的文件内容会延迟更新,更新间隔由 kubelet 的 sync period 决定(默认 60 秒)
  3. 应用需要自行实现配置重载逻辑,K8s 只负责更新文件内容,不会自动通知应用
  4. subPath 挂载不支持热更新,使用 subPath 时,ConfigMap 更新不会反映到容器中

5.4 完整 YAML 示例

# ============================================
# ConfigMap:配置外部化
# ============================================

# 创建 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: production
data:
# 简单键值对
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"
CACHE_TTL: "300"

# 完整配置文件
application.yml: |
server:
port: 8080
shutdown: graceful
spring:
datasource:
url: jdbc:postgresql://db:5432/mydb
hikari:
maximum-pool-size: 20
logging:
level:
root: INFO
com.example: DEBUG

# Redis 配置
redis.conf: |
bind 0.0.0.0
port 6379
maxmemory 256mb
maxmemory-policy allkeys-lru
appendonly yes

---
# Pod 中使用 ConfigMap(三种方式)
apiVersion: v1
kind: Pod
metadata:
name: configmap-demo
namespace: production
spec:
containers:
- name: app
image: my-app:latest
ports:
- containerPort: 8080

# 方式 1:作为环境变量
env:
- name: APP_LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
- name: APP_MAX_CONNECTIONS
valueFrom:
configMapKeyRef:
name: app-config
key: MAX_CONNECTIONS

# 方式 1 补充:一次性导入所有键为环境变量
envFrom:
- configMapRef:
name: app-config
# 可选:前缀
prefix: APP_

# 方式 2:作为 Volume 挂载(支持热更新)
volumeMounts:
- name: config-vol
mountPath: /etc/app/config
readOnly: true
- name: redis-config
mountPath: /etc/redis/redis.conf
subPath: redis.conf # 注意:subPath 不支持热更新

# 方式 3:作为命令行参数
args: ["--log-level", "$(APP_LOG_LEVEL)"]

volumes:
- name: config-vol
configMap:
name: app-config
# 可选:指定要挂载的键
items:
- key: application.yml
path: application.yml
- key: redis.conf
path: redis.conf
# 可选:设置文件权限
defaultMode: 0644
- name: redis-config
configMap:
name: app-config

六、Secret:敏感信息管理

6.1 Secret 的类型

Secret 与 ConfigMap 类似,但专门用于存储敏感信息。Secret 中的数据以 Base64 编码存储(并非加密),在生产环境中应结合加密机制使用。

Secret 类型用途说明
Opaque通用类型默认类型,存储任意键值对
kubernetes.io/dockerconfigjsonDocker 镜像仓库认证用于拉取私有镜像
kubernetes.io/tlsTLS 证书存储 TLS 证书和私钥
kubernetes.io/basic-auth基本认证存储用户名和密码
kubernetes.io/ssh-authSSH 认证存储 SSH 私钥
kubernetes.io/service-account-tokenServiceAccount Token自动为 SA 生成 Token

6.2 与 ConfigMap 的区别

对比维度ConfigMapSecret
数据编码明文存储Base64 编码
大小限制1 MiB1 MiB
etcd 加密通常不加密可配置 EncryptionConfiguration 加密
访问控制标准 RBAC更严格的 RBAC 审计
使用场景应用配置、环境变量密码、密钥、证书、Token
安全警告

Secret 的 Base64 编码不是加密,任何人只要有访问 etcd 的权限就能解码。在生产环境中,务必启用 etcd 静态加密(Encryption at Rest),并严格限制 Secret 的访问权限。

6.3 生产环境 Secret 加密架构与最佳实践

由于 K8s 的 Base64 编码毫无机密性可言(类似于把中文字符翻译成拼音,任何人都能还原),一旦黑客获取了 etcd 数据库的备份文件、或者通过提权直接读取了物理机的磁盘,所有的密码与证书都将彻底暴露。

因此,在生产环境中,必须结合真正的加密机制来保护 Secret 数据。目前主流的三种架构方案如下:

方案一:etcd 静态加密与 KMS (Key Management Service) 插件

Kubernetes 原生支持在存储层进行加密(Encryption at Rest),即 kube-apiserver 在将 Secret 数据写入 etcd 前先将其加密,在客户端读取时再进行解密。

最基础的静态加密是利用本地密钥文件:

# /etc/kubernetes/pki/secrets-encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <base64-encoded-32-byte-key> # 本地对称加密密钥
- identity: {} # 兜底:未加密的 Secret 仍可读取

进阶:结合 KMS 插件(生产强烈推荐) 将主密钥放在 apiserver 的本地配置文件中仍有泄漏风险,因此大型生产环境通常会接入云厂商的 KMS(如 AWS KMS、阿里云 KMS)HashiCorp Vault。 在这种架构下,K8s 采用信封加密(Envelope Encryption)

  1. kube-apiserver 随机生成一个 DEK(数据加密密钥)把 Secret 加密。
  2. API Server 通过 Unix Socket 调用 KMS 插件,KMS 插件将 DEK 发给外部密钥管理系统。
  3. 外部 KMS 使用它的 KEK(密钥加密密钥)把 DEK 加密后返回给 K8s。
  4. K8s 将加密后的 DEK加密后的 Secret一并存入 etcd,实现了彻头彻尾的解耦和高安全性。

方案二:GitOps 工作流与 Sealed Secrets

如果在团队中实施 GitOps(如搭配 ArgoCD / FluxCD),所有的 K8s YAML 配置都要提交到 Git 仓库进行版本管理。但如果你将带有 Base64 编码的 Secret YAML 直推到 Github,无异于公开密码。

Bitnami Sealed Secrets 完美解决了这个问题:

  1. 集群中部署一个 Sealed Secrets Controller,它会在集群内部生成一对非对称的公私钥,并严格保护私钥。
  2. 开发者在本地使用 kubeseal 命令行工具,结合前面获取的公钥,将原本的 Secret 加密为一个名为 SealedSecret 的自定义资源(CRD)。
  3. 无论谁拿到这个 SealedSecret 的密文内容都无法解密,因此可以放心地将其公开 Push 到 Git 仓库。
  4. ArgoCD 将其同步进 K8s 集群后,处于集群内的 Controller 识别到该对象,利用极密私钥将其解密,并在集群内存中隐式还原为一个标准的 Kubernetes Secret 对象,供 Pod 挂载消费。

方案三:密码外置化与 External Secrets Operator (ESO)

对于多微服务多集群的企业级架构,将机密信息散落在各个 K8s 集群中维护是非常痛苦的。更现代的做法是引入外置机密治理中心

工作原理

  1. 真正的数据库密码、API Key 等完全不用 kubectl create secret 来建,而是由安全团队统一录入类似 AWS Secrets Manager, Google Secret ManagerHashiCorp Vault 这种专门的极高安全等级保险箱内。
  2. 随后在 K8s 集群部署 External Secrets Operator
  3. 运维人员在 K8s 声明一个 ExternalSecret 对象,指明要去远程保险箱获取哪一个 Key。
  4. 部署的控制器拿着具备精细权限配比的凭证去远程保险箱调接口,读取到数据以后,动态地在 K8s 中生成一个原生 Secret 给应用使用。并在云端密码发生变更时自动回滚和同步(甚至触发目标 Pod 重启)。

这实现了机密数据的"唯一真实数据源 (Single Source of Truth)"理念。

方案四:严格的 RBAC 控制(防线兜底)

不论采用何种加密架构,K8s 内部的 kube-apiserver 依然会自动向有权限的请求方分发解密好的数据。因此,防备越权读取的最后一公里,永远是最小权限原则(Principle of Least Privilege)

# 仅允许特定 ServiceAccount 读取某个指定的 Secret
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: specific-secret-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["db-credentials", "api-keys"] # 切忌使用 * 或者留空
verbs: ["get"]

方案五:实操演练(直接窥探 etcd 证明 Base64 脆弱性)

为了更直观地理解为什么必须搭配 KMS/Sealed Secrets 等加密机制,你可以通过 etcdctl 直接查看集群底层的原生数据:

1. 创建一个普通的 Secret

kubectl create secret generic my-secret \
--from-literal=password=super_secret_pwd

2. 登录到 master 节点上,使用 etcdctl 直接读取数据:

ETCDCTL_API=3 etcdctl \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
get /registry/secrets/default/my-secret

3. 如果未开启静态加密,你会直接看到明文 base64 payload:

/registry/secrets/default/my-secret
... "password": "c3VwZXJfc2VjcmV0X3B3ZA==" ...

任何人只要拿到这串字符,便可用 echo "c3Vw...==" | base64 -d 变为 super_secret_pwd

4. 如果配置了 etcd 静态加密(aescbc),底层返回的将是彻底的密文乱码:

/registry/secrets/default/my-secret
k8s:enc:aescbc:v1:key1:Q@c*a`X&_...

6.4 完整 YAML 示例

# ============================================
# Secret:敏感信息管理
# ============================================

# 1. Opaque Secret:通用密钥
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
data:
# 值必须为 Base64 编码
# echo -n 'my-password' | base64
username: bXktdXNlcm5hbWU= # my-username
password: bXktcGFzc3dvcmQ= # my-password
connection-string: amRiYzpwb3N0Z3Jlc3FsOi8vZGItaG9zdDo1NDMyL215ZGI= # jdbc:postgresql://db-host:5432/mydb
---
# 2. Docker Registry Secret:镜像仓库认证
apiVersion: v1
kind: Secret
metadata:
name: docker-registry-secret
namespace: production
type: kubernetes.io/dockerconfigjson
data:
# 通过命令创建:kubectl create secret docker-registry ...
.dockerconfigjson: eyJhdXRocyI6eyJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOnsidXNlcm5hbWUiOiJ1c2VyIiwicGFzc3dvcmQiOiJwYXNzIiwiYXV0aCI6ImRYTmxjanB3WVhOeiJ9fX0=
---
# 3. TLS Secret:HTTPS 证书
apiVersion: v1
kind: Secret
metadata:
name: tls-secret
namespace: production
type: kubernetes.io/tls
data:
tls.crt: <base64-encoded-cert>
tls.key: <base64-encoded-key>
---
# 4. 在 Pod 中使用 Secret
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: api-server
template:
metadata:
labels:
app: api-server
spec:
# 拉取私有镜像
imagePullSecrets:
- name: docker-registry-secret

containers:
- name: api
image: my-registry.com/api-server:v2.1.0
ports:
- containerPort: 8080

# 方式 1:作为环境变量
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password

# 方式 2:作为 Volume 挂载
volumeMounts:
- name: db-secret-vol
mountPath: /etc/secrets/db
readOnly: true
- name: tls-vol
mountPath: /etc/tls
readOnly: true

volumes:
- name: db-secret-vol
secret:
secretName: db-credentials
defaultMode: 0400 # 仅所有者可读
- name: tls-vol
secret:
secretName: tls-secret
defaultMode: 0400

七、存储选型决策指南

7.1 存储类型对比

存储类型持久性性能共享能力适用场景推荐度
emptyDir临时高(内存)/ 中(磁盘)Pod 内缓存、临时文件、容器间数据共享
hostPath节点级同节点 Pod开发调试、DaemonSet 日志收集
PV/PVC + NFS持久多节点共享文件存储、内容管理系统
PV/PVC + 云盘持久单节点数据库、高性能单实例应用
PV/PVC + Ceph持久多节点企业级存储、大规模集群
CSI 第三方存储取决于后端取决于后端取决于后端特殊存储需求(对象存储、SAN 等)
ConfigMap配置--应用配置、环境变量、配置文件
Secret敏感配置--密码、密钥、证书、Token

7.2 存储选型决策树

生产环境推荐方案
  • 有状态服务(数据库、消息队列):使用云盘 SSD + StorageClass 动态供给,设置 volumeBindingMode: WaitForFirstConsumer
  • 共享文件(上传文件、静态资源):使用 NFS / CephFS / 对象存储(通过 CSI)
  • 应用配置:ConfigMap + Volume 挂载,配合 Reloader 等工具实现自动滚动更新
  • 敏感信息:Secret + etcd 加密 + External Secrets Operator(对接 Vault 或云厂商密钥管理服务)
  • 日志与监控数据:使用 emptyDir + sidecar 模式收集,或直接写入外部存储

八、本章小结

本文深入剖析了 Kubernetes 存储体系与配置管理的核心机制,让我们回顾一下关键要点:

存储体系的三层架构

  • Volume 是最基础的存储抽象,定义在 Pod 级别,生命周期与 Pod 绑定
  • PV/PVC 实现了存储的供需分离,PV 是集群级资源,PVC 是命名空间级请求
  • StorageClass 实现了存储的动态供给,用户无需预先创建 PV

存储保护与安全机制

  • Storage Object in Use Protection:通过 kubernetes.io/pvc-protectionkubernetes.io/pv-protection finalizer 保护正在使用的存储资源
  • PV 删除保护 Finalizer(v1.33 Stable):external-provisioner.volume.kubernetes.io/finalizerkubernetes.io/pv-controller 确保底层存储被正确删除后才移除 PV 对象
  • Recycle 回收策略已废弃:推荐使用动态供给替代

存储扩容与属性管理

  • PVC 扩容(v1.24 Stable):需要 StorageClass 设置 allowVolumeExpansion: true
  • CSI 卷扩容(v1.24 Stable):需要 CSI 驱动程序支持
  • VolumeAttributesClasses(VAC):新 API,允许在不重建卷的情况下修改存储属性

配置管理的两大工具

  • ConfigMap 用于管理非敏感配置,支持环境变量注入和 Volume 挂载(支持热更新)
  • Secret 用于管理敏感信息,生产环境必须配合 etcd 加密和严格 RBAC 使用

选型的核心原则

  • 根据数据的持久性需求选择临时存储或持久化存储
  • 根据数据的共享需求选择块存储(RWO)或文件存储(RWX)
  • 根据数据的敏感性选择 ConfigMap 或 Secret
  • 优先使用 StorageClass 动态供给,减少运维负担

系列导航

章节主题状态
0架构设计与核心概念✅ 已发布
1工作负载与 Pod 生命周期深度解析✅ 已发布
2网络模型与服务发现全链路解析✅ 已发布
3存储体系与配置管理深度剖析✅ 已发布
4调度器、资源管理与弹性伸缩✅ 已发布
5安全体系与可观测性全景✅ 已发布
6生产级微服务架构实战✅ 已发布
7有状态应用与 Operator 模式实战✅ 已发布

相关阅读

Kubernetes 全景解析 (2):网络模型与服务发现全链路解析

· 阅读需 29 分钟
Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡

前言

在 Kubernetes 的众多核心概念中,网络模型与服务发现机制是最为复杂也最为关键的部分之一。一个健康的 K8s 集群,其网络层必须满足 Pod 之间、Pod 与 Node 之间、以及 Pod 与外部世界的无缝通信。同时,随着微服务架构的普及,服务发现、负载均衡、流量路由等能力变得不可或缺。

本文将深入剖析 K8s 网络模型的三层架构、Service 的四种类型与流量路径、Ingress 控制器原理,以及 NetworkPolicy 安全策略,帮助你全面掌握 K8s 网络的核心机制。

一、K8s 网络模型基础

1.1 K8s 网络的三个基本要求

Kubernetes 对集群网络提出了三个基本要求,这些要求构成了 K8s 网络模型的核心:

  1. 所有 Pod 可以在没有 NAT 的情况下相互通信

    • 每个 Pod 都拥有独立的 IP 地址
    • Pod 之间可以直接通信,无需网络地址转换
    • 无论 Pod 位于哪个 Node 上
  2. 所有 Node 可以与所有 Pod 通信

    • Node 可以直接访问 Pod 的 IP
    • 无需额外的端口映射或代理
  3. Pod 看到的自己 IP 与其他 Pod 看到的 IP 相同

    • 不存在 IP 欺骗或地址转换
    • 网络行为可预测
设计理念

K8s 采用"扁平网络"模型,避免了传统容器网络中的复杂 NAT 配置,使得网络行为更加透明和可预测。这种设计大大简化了应用的开发和调试。

1.2 CNI(Container Network Interface)插件机制

Kubernetes 本身不实现具体的网络功能,而是通过 CNI(Container Network Interface)标准将网络实现委托给第三方插件。CNI 定义了一套接口规范,网络插件只需实现这些接口即可与 K8s 集成。

CNI 插件的主要职责包括:

  • IP 地址分配:为 Pod 分配 IP 地址
  • 网络配置:设置 Pod 的网络命名空间、路由表、防火墙规则
  • 网络连接:创建 veth pair、网桥、路由等网络设备

1.3 常见 CNI 插件对比

插件名称网络模式性能功能特性适用场景
FlannelVXLAN/Host-GW中等简单易用,支持多种后端中小规模集群,快速部署
CalicoBGP/纯路由强大的网络策略,支持 IPAM需要细粒度网络控制的企业级应用
CiliumeBPF极高基于 eBPF 的可观测性和安全高性能、高安全要求的场景
Weave NetVXLAN/加密中等内置加密,简单部署对安全性要求较高的场景
选择建议
  • Flannel:适合入门学习和中小规模集群
  • Calico:适合需要网络策略和企业级功能的场景
  • Cilium:适合追求极致性能和可观测性的场景
  • Weave Net:适合对数据加密有强需求的场景

1.4 K8s 网络三层模型

上图展示了 K8s 网络的三层架构:

  • 第 1 层:节点网络 - 物理机或虚拟机的网络,用于节点间通信
  • 第 2 层:Pod 网络 - 覆盖网络(Overlay)或纯路由网络,用于 Pod 间通信
  • 第 3 层:服务网络 - 虚拟 IP 网络,用于服务发现和负载均衡

二、Service:服务发现与负载均衡

2.1 Service 的四种类型

Service 是 K8s 中用于服务发现和负载均衡的核心抽象。它为一组功能相同的 Pod 提供稳定的访问入口,并实现流量分发。

2.1.1 ClusterIP(默认类型)

ClusterIP 是 Service 的默认类型,它为 Service 分配一个集群内部的虚拟 IP(VIP),仅在集群内部可访问。

apiVersion: v1
kind: Service
metadata:
name: nginx-clusterip
namespace: default
spec:
type: ClusterIP
selector:
app: nginx
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP

访问方式

  • 集群内部:http://nginx-clusterip.default.svc.cluster.local:80
  • 集群内部(短名):http://nginx-clusterip:80
最佳实践

对于不需要外部访问的内部服务(如数据库、缓存、内部 API),始终使用 ClusterIP 类型以确保安全性。

2.1.2 NodePort

NodePort 在每个 Node 上开放一个端口(默认范围 30000-32767),外部流量可以通过 NodeIP:NodePort 访问 Service。

apiVersion: v1
kind: Service
metadata:
name: nginx-nodeport
namespace: default
spec:
type: NodePort
selector:
app: nginx
ports:
- name: http
port: 80
targetPort: 80
nodePort: 30080
protocol: TCP

访问方式

  • 外部访问:http://<任意NodeIP>:30080
  • 集群内部:http://nginx-nodeport.default.svc.cluster.local:80
注意事项

NodePort 会占用 Node 的端口,且端口范围有限(默认 30000-32767)。生产环境不建议直接使用 NodePort 暴露服务,应结合 Ingress 或 LoadBalancer 使用。

2.1.3 LoadBalancer

LoadBalancer 类型会向云服务商申请一个负载均衡器,并将外部流量分发到后端的 Node。

apiVersion: v1
kind: Service
metadata:
name: nginx-loadbalancer
namespace: default
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
# 云服务商特定配置
loadBalancerIP: "203.0.113.10"
externalTrafficPolicy: Local

访问方式

  • 外部访问:http://<LoadBalancerIP>:80
  • 集群内部:http://nginx-loadbalancer.default.svc.cluster.local:80
云服务商依赖

LoadBalancer 类型需要云服务商的支持(如 AWS ELB、GCP Load Balancer、Azure Load Balancer)。在本地开发环境(如 Minikube、Kind)中,通常无法正常工作。

2.1.4 ExternalName

ExternalName 类型将 Service 映射到外部 DNS 名称,不创建 ClusterIP,主要用于访问外部服务。

apiVersion: v1
kind: Service
metadata:
name: external-database
namespace: default
spec:
type: ExternalName
externalName: database.example.com

访问方式

  • 集群内部:http://external-database.default.svc.cluster.local
  • 实际会被 DNS 解析为:database.example.com

2.6 Service 应用协议(appProtocol)

Service 的 appProtocol 字段(v1.20 Stable)允许为每个 Service 端口指定应用层协议,为实现提供更丰富的行为提示。

支持的协议类型

协议描述
IANA 标准服务名httphttpsftpsmtp 等(参考 IANA Service Names
kubernetes.io/h2cHTTP/2 over cleartext(明文 HTTP/2,参考 RFC 7540)
kubernetes.io/wsWebSocket over cleartext(明文 WebSocket,参考 RFC 6455)
kubernetes.io/wssWebSocket over TLS(加密 WebSocket,参考 RFC 6455)
自定义前缀协议mycompany.com/my-custom-protocol

使用示例

apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: myapp
ports:
- name: http
port: 80
targetPort: 9376
protocol: TCP
appProtocol: http # 指定应用协议
- name: grpc
port: 9090
targetPort: 9090
protocol: TCP
appProtocol: kubernetes.io/h2c # HTTP/2 cleartext
appProtocol 的作用

appProtocol 字段的值会自动同步到对应的 Endpoints 和 EndpointSlice 对象中,供 Ingress Controller、Service Mesh 等组件使用,以提供协议感知的路由、健康检查等增强功能。

2.7 Service 名称验证规则(v1.34 Alpha)

FEATURE STATE: Kubernetes v1.34 [alpha](默认关闭)

Kubernetes v1.34 引入了 RelaxedServiceNameValidation 特性门控,允许 Service 对象名称以数字开头。

默认规则(特性门控关闭):

  • Service 名称必须是有效的 RFC 1035 标签名称
  • 必须以字母开头,只能包含字母、数字和连字符(-)

放宽规则(特性门控开启):

  • Service 名称可以是有效的 RFC 1123 标签名称
  • 允许以数字开头
启用 RelaxedServiceNameValidation

要启用此特性,需要在 API Server 和 Controller Manager 的启动参数中添加:

--feature-gates=RelaxedServiceNameValidation=true

2.2 kube-proxy 工作模式

kube-proxy 是 K8s 网络的核心组件,负责维护 Service 的网络规则。它支持三种工作模式:

2.2.1 iptables 模式

iptables 模式使用 iptables 规则实现负载均衡,每个 Service 会创建一系列 iptables 规则。

优点

  • 实现简单,兼容性好
  • 无需额外依赖

缺点

  • 规则数量随 Service 数量线性增长,性能下降明显
  • 不支持高级负载均衡算法(仅支持随机轮询)
  • 调试困难
  • iptables 正逐步被 Linux 内核的 nftables 框架取代

2.2.2 IPVS 模式

IPVS(IP Virtual Server)是基于 Linux 内核的负载均衡技术,性能远高于 iptables。

优点

  • 性能优异,支持大规模 Service
  • 支持多种负载均衡算法(rr、lc、dh、sh、sed、nq)
  • 连接跟踪更精确

缺点

  • 需要加载 IPVS 内核模块
  • 配置相对复杂

2.2.3 nftables 模式(v1.33 GA)

nftables 是 Linux 内核新一代的防火墙和数据包过滤框架,旨在取代传统的 iptables。kube-proxy 的 nftables 模式自 v1.29 引入(Alpha),v1.33 达到 GA(正式可用)。

优点

  • 替代逐步淘汰的 iptables 框架,面向未来
  • 规则处理性能优于 iptables
  • 统一了 IPv4/IPv6/ARP/桥接的过滤框架
  • 内核 5.13+ 及 nft 命令行工具 1.0.1+ 支持

缺点

  • 需要较新的内核版本(>= 5.13)
  • 需要 nft 命令行工具(>= 1.0.1)
  • 生态成熟度仍在提升中
性能优化建议

对于生产环境,建议根据内核版本选择 kube-proxy 模式:

  • 内核 >= 5.13:优先使用 nftables 模式(面向未来,性能更优)
  • 内核 < 5.13:使用 IPVS 模式以获得更好的性能

可以通过修改 kube-proxy 的配置来启用:

apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "nftables" # 可选值: "iptables" | "ipvs" | "nftables"

2.3 DNS 服务发现(CoreDNS)

CoreDNS 是 K8s 集群的默认 DNS 服务器,它为 Service 和 Pod 提供 DNS 解析服务。

DNS 记录格式

资源类型DNS 记录格式示例
Service(普通)<service-name>.<namespace>.svc.cluster.localnginx.default.svc.cluster.local
Service(Headless)<pod-ip>.<service-name>.<namespace>.svc.cluster.local10.244.1.2.nginx.default.svc.cluster.local
Pod<pod-ip>.<namespace>.pod.cluster.local10.244.1.2.default.pod.cluster.local

CoreDNS 配置示例

apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}

2.4 Headless Service 与 StatefulSet 的配合

Headless Service 是一种特殊的 Service,它不分配 ClusterIP,而是直接将 DNS 解析到后端的 Pod IP。这种设计常与 StatefulSet 配合使用,用于有状态应用。

apiVersion: v1
kind: Service
metadata:
name: nginx-headless
namespace: default
spec:
clusterIP: None # Headless Service 的关键配置
selector:
app: nginx
ports:
- name: http
port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-stateful
namespace: default
spec:
serviceName: nginx-headless # 关联 Headless Service
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21
ports:
- containerPort: 80

DNS 解析结果

# 解析 Headless Service
$ dig nginx-headless.default.svc.cluster.local

;; ANSWER SECTION:
nginx-headless.default.svc.cluster.local. 5 IN A 10.244.1.2
nginx-headless.default.svc.cluster.local. 5 IN A 10.244.2.2
nginx-headless.default.svc.cluster.local. 5 IN A 10.244.3.2

# 解析单个 Pod
$ dig nginx-stateful-0.nginx-headless.default.svc.cluster.local

;; ANSWER SECTION:
nginx-stateful-0.nginx-headless.default.svc.cluster.local. 5 IN A 10.244.1.2
应用场景

Headless Service 适用于以下场景:

  • StatefulSet 应用(如数据库、消息队列)
  • 需要直接访问 Pod IP 的应用
  • 自定义负载均衡策略
  • 服务网格(Service Mesh)

2.8 Service 四种类型流量路径对比

上图展示了四种 Service 类型的流量路径:

  1. NodePort:外部流量通过 NodeIP:NodePort 进入,分发到各个 Node
  2. LoadBalancer:外部流量通过云负载均衡器进入,分发到各个 Node
  3. ClusterIP:集群内部流量通过虚拟 IP 分发到后端 Pod
  4. ExternalName:DNS 解析到外部服务名称

三、Ingress:七层路由入口

3.1 Ingress vs Service 的区别

特性ServiceIngress
网络层四层(L4 - TCP/UDP)七层(L7 - HTTP/HTTPS)
路由能力基于 IP 和端口基于 Host、Path、Header
协议支持TCP/UDPHTTP/HTTPS/gRPC
TLS 终止不支持支持
使用场景服务间通信、内部服务外部访问、多域名路由
核心区别

Service 是四层负载均衡,只能基于 IP 和端口进行路由;Ingress 是七层负载均衡,可以基于 HTTP Host、Path、Header 等进行智能路由。Ingress 通常作为多个 Service 的统一入口。

3.2 Ingress Controller

Ingress Controller 是 Ingress 资源的执行器,负责根据 Ingress 规则配置负载均衡器。常见的 Ingress Controller 包括:

Controller特点适用场景
Nginx Ingress成熟稳定,功能丰富通用场景,生产环境首选
Traefik自动配置,支持多种后端微服务架构,动态环境
APISIX高性能,支持云原生高流量、高并发场景
Istio Gateway服务网格集成已使用 Istio 的场景

Nginx Ingress Controller 部署示例

apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
replicas: 2
selector:
matchLabels:
app: ingress-nginx
template:
metadata:
labels:
app: ingress-nginx
spec:
containers:
- name: controller
image: k8s.gcr.io/ingress-nginx/controller:v1.8.1
args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
---
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
spec:
type: LoadBalancer
ports:
- name: http
port: 80
targetPort: 80
- name: https
port: 443
targetPort: 443
selector:
app: ingress-nginx

3.3 Ingress 资源配置

3.3.1 基于 Host 的路由

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-host-based
namespace: default
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
- host: web.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80

3.3.2 基于 Path 的路由

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-path-based
namespace: default
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: nginx
rules:
- host: example.com
http:
paths:
- path: /api(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: api-service
port:
number: 80
- path: /web(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: web-service
port:
number: 80
- path: /(.*)
pathType: ImplementationSpecific
backend:
service:
name: frontend-service
port:
number: 80

3.3.3 TLS 配置

apiVersion: v1
kind: Secret
metadata:
name: tls-secret
namespace: default
type: kubernetes.io/tls
data:
tls.crt: LS0tLS1CRUdJTi... # Base64 编码的证书
tls.key: LS0tLS1CRUdJTi... # Base64 编码的私钥
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-tls
namespace: default
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- secure.example.com
secretName: tls-secret
rules:
- host: secure.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: secure-service
port:
number: 443
TLS 最佳实践
  1. 使用 Let's Encrypt 自动获取和更新证书(推荐使用 cert-manager)
  2. 强制 HTTPS 重定向(如 nginx.ingress.kubernetes.io/ssl-redirect: "true")
  3. 定期轮换证书
  4. 使用强加密套件

3.4 Ingress 路由分发流程

上图展示了 Ingress 的路由分发流程:

  1. 外部客户端发起 HTTP/HTTPS 请求
  2. Ingress Controller 接收请求,根据 Ingress 资源规则进行匹配
  3. 根据 HostPath 将请求路由到对应的 Service
  4. Service 将请求负载均衡到后端的 Pod

3.5 Gateway API:下一代服务路由标准

Gateway API 是 Kubernetes 官方项目,专注于 L4 和 L7 路由,代表了 Kubernetes Ingress、负载均衡和服务网格 API 的下一代演进方向。相比传统的 Ingress API,Gateway API 提供了更强大、更灵活的路由能力。

Gateway API 的核心优势

特性Ingress APIGateway API
角色导向单一角色支持基础设施提供者、集群操作者、应用开发者三种角色
表达能力依赖注解实现高级功能原生支持流量加权、Header 匹配等高级路由
可扩展性有限支持自定义资源扩展
跨命名空间不支持支持共享 Gateway 和跨命名空间路由
协议支持主要 HTTP/HTTPS支持 HTTP、gRPC、TCP、UDP 等多种协议
服务网格集成不支持通过 GAMMA 倡议支持服务网格

Gateway API 的核心资源

  • GatewayClass:定义 Gateway 的实现类型(如 Envoy、Nginx、Contour)
  • Gateway:定义网络入口点,监听端口、TLS 配置等
  • HTTPRoute/GRPCRoute:定义路由规则,支持流量权重、Header 匹配等
Gateway API vs Ingress

Gateway API 不是 Ingress 的简单替代,而是一个更强大、更灵活的 API 框架:

  • Gateway API for Ingress:管理南北向流量(从外部到集群内部)
  • Gateway API for Service Mesh (GAMMA):管理东西向流量(集群内部服务间通信)

Gateway API 已成为 SIG-Network 的官方项目,多个主流实现(如 Envoy Gateway、Kong Gateway、Contour)都已支持。

Gateway API 示例

# GatewayClass:定义 Gateway 的实现类型
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: eg
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller

---
# Gateway:定义网络入口点
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: eg
spec:
gatewayClassName: eg
listeners:
- name: http
protocol: HTTP
port: 80
hostname: "*.example.com"

---
# HTTPRoute:定义路由规则
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: backend
spec:
parentRefs:
- name: eg
hostnames:
- "api.example.com"
rules:
- backendRefs:
- name: api-service
port: 80
何时使用 Gateway API

Gateway API 适合以下场景:

  • 需要更强大的路由能力(流量加权、Header 匹配、灰度发布)
  • 需要跨命名空间共享 Gateway
  • 需要支持多种协议(HTTP、gRPC、TCP、UDP)
  • 计划使用服务网格(Service Mesh)

对于简单的 HTTP/HTTPS 路由需求,传统的 Ingress API 仍然足够。

四、NetworkPolicy:网络策略与安全隔离

4.1 默认网络策略

Kubernetes 集群默认情况下,所有 Pod 之间都是互通的(全通模式)。这种默认行为在多租户环境中可能带来安全风险。

默认全通模式

# 默认情况下,无需任何 NetworkPolicy,所有 Pod 都可以相互通信

默认全拒模式

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: default
spec:
podSelector: {} # 空选择器匹配所有 Pod
policyTypes:
- Ingress
- Egress
安全建议

在生产环境中,建议采用"默认拒绝,显式允许"的策略。首先创建默认拒绝所有流量的 NetworkPolicy,然后为需要通信的 Pod 创建允许规则。

4.2 ingress/egress 规则

NetworkPolicy 支持 ingress(入站)和 egress(出站)两种规则类型。

4.2.1 仅允许入站流量

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-only
namespace: default
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080

4.2.2 仅允许出站流量

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-only
namespace: default
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432

4.2.3 同时允许入站和出站流量

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-both
namespace: default
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: UDP
port: 53

4.3 标签选择器与策略匹配

NetworkPolicy 使用标签选择器来匹配 Pod 和 Namespace。

选择器类型用途示例
podSelector匹配同一 Namespace 中的 Podapp: frontend
namespaceSelector匹配 Namespacename: production
podSelector + namespaceSelector匹配特定 Namespace 中的 PodNamespace: team: backend, Pod: app: api

复杂选择器示例

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: complex-selector
namespace: default
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
# 允许同一 Namespace 中标签为 app: frontend 的 Pod
- podSelector:
matchLabels:
app: frontend
# 允许 production Namespace 中所有 Pod
- namespaceSelector:
matchLabels:
env: production
# 允许 monitoring Namespace 中标签为 app: prometheus 的 Pod
- namespaceSelector:
matchLabels:
team: monitoring
podSelector:
matchLabels:
app: prometheus
ports:
- protocol: TCP
port: 8080

4.4 NetworkPolicy 规则匹配示意图

上图展示了 NetworkPolicy 的规则匹配逻辑:

  1. 外部流量被拒绝(未在 ingress 规则中)
  2. frontend Namespace 中的 Pod 可以访问 backend(匹配 app: frontend
  3. production Namespace 中的 Pod 可以访问 backend(匹配 env: production
  4. backend Pods 可以访问 database(匹配 app: database
  5. backend Pods 可以访问 DNS(匹配 DNS 规则)
  6. backend Pods 无法访问互联网(未在 egress 规则中)
NetworkPolicy 依赖

NetworkPolicy 的实现依赖于 CNI 插件。并非所有 CNI 插件都支持 NetworkPolicy:

  • 支持:Calico、Cilium、Weave Net、Romana
  • 不支持:Flannel(需要配合 Canal 使用)

在选择 CNI 插件时,如果需要网络策略功能,请确保插件支持 NetworkPolicy。

五、EndpointSlice 与服务网格简介

5.1 EndpointSlice 机制

EndpointSlice 是 K8s 1.17+ 引入的新特性(v1.21 达到 Stable),用于替代传统的 Endpoints 资源。它将 Pod 端点信息分片存储,解决了大规模集群中 Endpoints 资源过大的问题。

Endpoints API 已废弃(v1.33)

自 Kubernetes v1.33 起,传统的 Endpoints API 已被正式标记为废弃(Deprecated)。相比 EndpointSlice,旧的 Endpoints API 存在以下问题:

  • 不支持双栈集群:无法同时表示 IPv4 和 IPv6 端点
  • 缺少新特性支持:不包含 trafficDistribution 等新功能所需的信息
  • 端点截断:当端点数量超过 1000 时,会截断端点列表,导致部分后端无法接收流量

官方推荐所有用户迁移到 EndpointSlice API。EndpointSlice 由 EndpointSlice 控制器自动管理,无需手动创建。

EndpointSlice 的优势

特性Endpoints(已废弃)EndpointSlice(推荐)
存储方式单个大对象分片存储(每个切片最多 100 个端点)
扩展性受限于单个对象大小(1000 端点截断)支持大规模集群
双栈支持不支持支持 IPv4/IPv6 双栈
拓扑感知不支持支持 Zone、Node 等拓扑信息
性能频繁更新导致性能下降增量更新,性能更好
新特性兼容不支持 trafficDistribution 等完整支持

EndpointSlice 示例

apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: nginx-service-abc123
namespace: default
labels:
kubernetes.io/service-name: nginx-service
addressType: IPv4
ports:
- name: http
protocol: TCP
port: 80
endpoints:
- addresses:
- "10.244.1.2"
nodeName: node-1
zone: zone-a
conditions:
ready: true
- addresses:
- "10.244.1.3"
nodeName: node-1
zone: zone-a
conditions:
ready: true
- addresses:
- "10.244.2.2"
nodeName: node-2
zone: zone-b
conditions:
ready: true
自动管理

EndpointSlice 由 EndpointSlice 控制器自动管理,无需手动创建。当创建 Service 时,控制器会自动创建对应的 EndpointSlice。

5.2 Service Mesh 与 K8s 网络的关系

Service Mesh(服务网格)是建立在 K8s 网络之上的应用层网络抽象,它通过在每个 Pod 中注入 Sidecar 代理来实现流量管理、安全、可观测性等功能。

主流 Service Mesh 对比

特性IstioLinkerdConsul Connect
架构控制平面 + 数据平面控制平面 + 数据平面控制平面 + 数据平面
SidecarEnvoyproxy-wasmEnvoy
性能中等中等
功能丰富中等中等
学习曲线陡峭平缓平缓

Service Mesh 与 K8s 网络的关系

上图展示了 Service Mesh 与 K8s 网络的层次关系:

  1. 应用层:应用容器通过 gRPC/HTTP 协议通信
  2. Sidecar 代理:拦截应用流量,实现流量管理、安全、可观测性
  3. K8s 网络:Service 和 NetworkPolicy 提供四层网络能力
  4. CNI 网络:提供 Pod 间通信的三层网络能力
  5. 节点网络:物理或虚拟机的二层网络
何时使用 Service Mesh

Service Mesh 并非必需,适合以下场景:

  • 微服务数量多(> 50 个服务)
  • 需要细粒度的流量控制(灰度发布、A/B 测试)
  • 需要统一的安全策略(mTLS、授权)
  • 需要深度可观测性(分布式追踪、指标)

对于小规模集群或简单应用,K8s 原生的 Service 和 Ingress 已足够。

六、网络故障排查常用命令

6.1 Pod 网络排查

# 1. 检查 Pod 的 IP 地址
kubectl get pod <pod-name> -o wide

# 2. 检查 Pod 的网络配置
kubectl exec -it <pod-name> -- ip addr
kubectl exec -it <pod-name> -- ip route

# 3. 检查 Pod 的 DNS 解析
kubectl exec -it <pod-name> -- nslookup kubernetes.default
kubectl exec -it <pod-name> -- cat /etc/resolv.conf

# 4. 测试 Pod 间连通性
kubectl exec -it <pod-name-1> -- ping <pod-ip-2>
kubectl exec -it <pod-name-1> -- curl http://<service-name>:<port>

# 5. 检查 Pod 的网络策略
kubectl get networkpolicy -n <namespace>
kubectl describe networkpolicy <policy-name> -n <namespace>

6.2 Service 网络排查

# 1. 检查 Service 的详细信息
kubectl get svc <service-name> -o yaml
kubectl describe svc <service-name>

# 2. 检查 Endpoints/EndpointSlice
kubectl get endpoints <service-name>
kubectl get endpointslice -l kubernetes.io/service-name=<service-name>

# 3. 检查 kube-proxy 日志
kubectl logs -n kube-system -l k8s-app=kube-proxy

# 4. 检查 iptables 规则(在 Node 上执行)
sudo iptables -t nat -L KUBE-SERVICES -n
sudo iptables -t nat -L KUBE-SVC-<hash> -n

# 5. 检查 IPVS 规则(如果使用 IPVS 模式)
sudo ipvsadm -Ln

6.3 Ingress 网络排查

# 1. 检查 Ingress 资源
kubectl get ingress
kubectl describe ingress <ingress-name>

# 2. 检查 Ingress Controller
kubectl get pods -n ingress-nginx
kubectl logs -n ingress-nginx <ingress-controller-pod>

# 3. 检查 Ingress Controller 配置
kubectl exec -n ingress-nginx <ingress-controller-pod> -- cat /etc/nginx/nginx.conf

# 4. 测试 Ingress 路由
curl -v http://<host>/path
curl -v -H "Host: <host>" http://<ingress-ip>/path

# 5. 检查 TLS 证书
kubectl get secret <tls-secret-name> -o yaml
openssl x509 -in <cert-file> -text -noout

6.4 CNI 网络排查

# 1. 检查 CNI 插件 Pod
kubectl get pods -n kube-system | grep -E "calico|cilium|flannel|weave"

# 2. 检查 CNI 插件日志
kubectl logs -n kube-system <cni-plugin-pod>

# 3. 检查节点网络配置(在 Node 上执行)
ip addr
ip route
brctl show # 如果使用网桥模式

# 4. 检查 veth pair(在 Node 上执行)
ip link show type veth

# 5. 测试节点间连通性
ping <other-node-ip>
traceroute <other-node-ip>
排查思路

网络故障排查的一般思路:

  1. 从内到外:先检查 Pod 内部网络,再检查 Service,最后检查 Ingress
  2. 从下到上:先检查 CNI 网络层,再检查 K8s 网络层,最后检查应用层
  3. 分层验证:每层网络都要验证连通性、DNS 解析、端口监听
  4. 日志优先:优先查看相关组件的日志,获取错误信息

七、本章小结

本文深入剖析了 Kubernetes 网络模型与服务发现的核心机制,涵盖了以下关键内容:

核心要点回顾

  1. K8s 网络模型

    • 三个基本要求:Pod 互通、Node 与 Pod 互通、Pod IP 一致性
    • CNI 插件机制:Flannel、Calico、Cilium、Weave Net 各有优劣
    • 三层网络架构:节点网络、Pod 网络、服务网络
  2. Service 服务发现

    • 四种类型:ClusterIP、NodePort、LoadBalancer、ExternalName
    • kube-proxy 工作模式:iptables、IPVS、nftables(v1.33 GA)
    • CoreDNS 服务发现机制
    • Headless Service 与 StatefulSet 的配合使用
    • appProtocol 字段(v1.20 Stable):支持 HTTP/2、WebSocket 等应用协议
    • RelaxedServiceNameValidation(v1.34 Alpha):允许 Service 名称以数字开头
  3. Ingress 七层路由

    • Ingress vs Service 的本质区别
    • 主流 Ingress Controller:Nginx、Traefik、APISIX
    • 基于 Host、Path 的路由配置
    • TLS 证书管理
    • Gateway API:下一代服务路由标准,支持角色导向、流量加权、跨命名空间等高级功能
  4. NetworkPolicy 网络安全

    • 默认全通 vs 默认全拒策略
    • ingress/egress 规则配置
    • 标签选择器的灵活使用
  5. 高级特性

    • EndpointSlice 机制解决大规模集群问题(Endpoints API v1.33 已废弃
    • Gateway API 与 Ingress 的演进关系
    • Service Mesh 与 K8s 网络的关系
  6. 故障排查

    • Pod、Service、Ingress、CNI 各层排查命令
    • 分层验证的排查思路

最佳实践建议

  1. CNI 插件选择

    • 中小集群:Flannel(简单易用)
    • 企业级应用:Calico(功能丰富)
    • 高性能场景:Cilium(eBPF)
  2. Service 类型选择

    • 内部服务:ClusterIP
    • 外部访问:Ingress + LoadBalancer
    • 特殊场景:NodePort(开发测试)、ExternalName(外部服务)
  3. 安全策略

    • 默认拒绝,显式允许
    • 最小权限原则
    • 定期审计 NetworkPolicy
  4. 性能优化

    • 优先使用 nftables 模式(v1.33 GA),其次 IPVS 模式
    • 使用 EndpointSlice(替代已废弃的 Endpoints API)
    • 合理规划 Service 数量

下一步学习

  • 深入学习特定 CNI 插件的配置和调优
  • 掌握 Service Mesh(Istio/Linkerd)的部署和使用
  • 学习网络可观测性工具(如 Cilium Hubble)
  • 实践网络故障排查和性能优化
  • 探索 Gateway API 在生产环境中的应用

Kubernetes 网络是一个庞大而复杂的主题,本文涵盖了其核心概念和最佳实践。在实际应用中,建议根据具体的业务需求和集群规模,选择合适的网络方案,并持续监控和优化网络性能。


相关阅读

作者简介:本文作者专注于云原生技术,拥有丰富的 Kubernetes 实践经验,致力于分享云原生技术最佳实践。

Kubernetes 全景解析 (1):工作负载与 Pod 生命周期深度解析

· 阅读需 21 分钟
Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡

"Pods are the atomic unit of scheduling in Kubernetes — not containers."

在 Kubernetes 的世界里,工作负载 (Workload) 是你与应用交互的核心抽象。无论你是部署一个无状态的 Web 服务、一个有状态的数据库,还是在每个节点上运行监控 Agent,K8s 都提供了专门的工作负载资源来满足需求。而所有这些工作负载的基础,都建立在 Pod 之上。

本文将从 Pod 的本质出发,逐层深入解析 Kubernetes 中五大核心工作负载资源的设计理念、编排策略与最佳实践,帮助你在架构选型时做出正确的决策。


一、Pod:K8s 的原子调度单元

1.1 为什么 Pod 是最小部署单元而非容器

许多初学者会困惑:既然 Docker 已经有了容器概念,为什么 Kubernetes 还要引入 Pod?答案在于 "超亲密容器"(Hyper-privileged Containers) 的设计哲学。

在现实世界中,一个应用往往不是孤立运行的。例如:

  • 一个 Web 服务器需要配合一个日志采集 Sidecar
  • 一个主进程需要配合一个健康检查辅助进程
  • 一个数据管道需要同时运行 ingest 和 transform 两个紧密协作的进程

这些进程需要共享网络命名空间(可以通过 localhost 互相通信)、共享存储卷(可以读写同一份数据),并且需要作为一个原子单元被调度到同一个节点上。Pod 正是为了解决这一需求而诞生的。

核心原则

Pod 是 Kubernetes 中最小的可调度单元。一个 Pod 可以包含一个或多个容器,这些容器共享相同的网络和存储命名空间,始终被调度到同一个节点上,并作为一个整体进行生命周期管理。

1.2 Pod 的设计理念

Pod 的设计围绕三个核心能力展开:

能力说明典型场景
共享网络同一 Pod 内的所有容器共享同一个 IP 地址和端口空间,可以通过 localhost 互相访问主容器 + Sidecar 代理
共享存储Pod 可以声明多个 Volume,这些 Volume 可以被 Pod 内的任意容器挂载主容器写日志,Sidecar 读取并转发
原子调度Pod 内的所有容器作为一个整体被调度到同一个节点保证本地通信的低延迟

Sidecar 模式 是 Pod 多容器设计中最经典的模式。例如,Istio 服务网格通过在每个 Pod 中注入一个 Envoy Sidecar 代理来实现流量管理、安全通信和可观测性,而无需修改应用代码。

1.3 Pod 的 YAML 结构详解

下面是一个完整的 Pod YAML 示例,每个字段都附有详细注释:

apiVersion: v1 # API 版本,Pod 属于核心 v1 组
kind: Pod # 资源类型
metadata:
name: nginx-pod # Pod 名称,在同一个 Namespace 内必须唯一
namespace: default # 命名空间,不指定则使用 default
labels: # 标签,用于选择器和组织资源
app: nginx
tier: frontend
annotations: # 注解,用于存储非标识性的元数据
description: "A sample nginx pod"
spec:
# --- 重启策略 ---
restartPolicy: Always # 容器退出后的重启策略:Always / OnFailure / Never

# --- 节点选择 ---
nodeSelector: # 通过标签选择节点
disktype: ssd
tolerations: # 容忍度,用于调度到有特定 Taint 的节点
- key: "dedicated"
operator: "Equal"
value: "gpu"
effect: "NoSchedule"

# --- 容器定义 ---
containers:
- name: nginx # 容器名称
image: nginx:1.27 # 镜像地址(含标签)
imagePullPolicy: IfNotPresent # 镜像拉取策略:Always / IfNotPresent / Never
ports:
- containerPort: 80 # 容器暴露的端口(仅声明,不自动发布)
protocol: TCP
resources: # 资源请求与限制
requests: # 调度时保证的最小资源量
cpu: "100m" # 100 millicores = 0.1 核
memory: "128Mi"
limits: # 容器可使用的最大资源量
cpu: "500m"
memory: "256Mi"
env: # 环境变量
- name: ENVIRONMENT
value: "production"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
volumeMounts: # 挂载存储卷
- name: nginx-data
mountPath: /usr/share/nginx/html
livenessProbe: # 存活探针
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe: # 就绪探针
httpGet:
path: /ready
port: 80
initialDelaySeconds: 5
periodSeconds: 5
startupProbe: # 启动探针(K8s 1.18+)
httpGet:
path: /startup
port: 80
failureThreshold: 30 # 最多失败 30 次(即最多等待 300 秒)

# --- Init 容器 ---
initContainers:
- name: init-db
image: busybox:1.36
command: ['sh', '-c', 'until nslookup db-service; do echo waiting for db; sleep 2; done']

# --- 存储卷 ---
volumes:
- name: nginx-data
persistentVolumeClaim:
claimName: nginx-pvc # 引用 PVC
- name: config-volume
configMap:
name: nginx-config # 引用 ConfigMap

# --- DNS 配置 ---
dnsPolicy: ClusterFirst # DNS 策略:ClusterFirst / Default / ClusterFirstWithHostNet / None
最佳实践
  1. 始终设置 resources.requestsresources.limits,避免资源争抢导致节点不稳定。
  2. 使用 imagePullPolicy: IfNotPresent 而非 Always(除非使用 :latest 标签),以减少镜像拉取延迟。
  3. 为生产环境的镜像使用明确的 digest(如 nginx@sha256:abc123...),确保部署的可重复性。

1.4 Pod 生命周期

Pod 从创建到终止会经历一系列状态变化。理解这些状态对于排查问题至关重要。

各状态说明:

状态含义
PendingPod 已被 Kubernetes 集群接受,但一个或多个容器尚未创建并就绪。包括等待调度和下载镜像的时间。
RunningPod 已绑定到节点,所有容器已创建,至少一个容器仍在运行,或正在启动/重启中。
SucceededPod 中的所有容器已成功终止,且不会重启。
FailedPod 中的所有容器已终止,且至少一个容器以失败状态退出(非零退出码或被系统终止)。
Unknown由于某种原因无法获取 Pod 状态,通常是与节点通信失败。
CrashLoopBackOff 和 Terminating 不是 Pod Phase

CrashLoopBackOffTerminating 可能会出现在 kubectl 命令的 Status 输出中,但它们不是 Pod 的 phase 值。Pod phase 是 Kubernetes 数据模型中的显式字段,只有五个值:PendingRunningSucceededFailedUnknown

  • CrashLoopBackOff:容器反复崩溃退出,K8s 在每次重启之间增加指数退避等待时间(10s → 20s → 40s → ...,上限 300s)。
  • Terminating:Pod 正在被删除,处于优雅终止过程中(默认 30 秒宽限期)。

参考文档:Pod Lifecycle - Pod phase

1.5 Probe 机制:Liveness / Readiness / Startup

Kubernetes 通过三种探针来监控容器的健康状态:

探针类型作用失败后果
Liveness Probe检测容器是否活着重启容器
Readiness Probe检测容器是否就绪(能否接收流量)从 Service Endpoints 中移除
Startup Probe检测容器是否启动完成在启动期间禁用其他探针

探针支持四种检测方式:

  • httpGet:向容器发送 HTTP GET 请求,2xx/3xx 状态码视为成功。
  • tcpSocket:尝试与容器的指定端口建立 TCP 连接。
  • exec:在容器内执行命令,返回码为 0 视为成功。
  • grpc(v1.24+ 稳定):使用 gRPC 健康检查协议,服务状态为 SERVING 视为成功。
注意事项
  • 不要省略探针。没有探针的 Pod 在容器进程僵死(如死锁)时无法被自动恢复。
  • Startup Probe 是慢启动应用的救星。如果你的应用启动需要较长时间(如 Java 应用加载类库),务必配置 Startup Probe,否则 Liveness Probe 可能在应用尚未就绪时就杀死容器。
  • Readiness Probe 不应过于严格,否则可能导致滚动更新时出现流量中断。

参考文档:Configure Liveness, Readiness and Startup Probes


二、Deployment:无状态应用的编排之王

Deployment 是 Kubernetes 中最常用的工作负载资源,专门用于管理无状态应用。它提供了声明式更新、滚动发布和回滚等生产级特性。

2.1 ReplicaSet 与 Deployment 的关系

Deployment → 管理 → ReplicaSet → 管理 → Pod

ReplicaSet (RS) 是下一层的控制器,负责确保指定数量的 Pod 副本始终在运行。而 Deployment 是更高层的抽象,它在 ReplicaSet 之上增加了版本管理滚动更新能力。

实践建议

在日常使用中,几乎不需要直接操作 ReplicaSet。你应该始终通过 Deployment 来管理应用,让 K8s 自动处理 ReplicaSet 的创建和清理。

2.2 滚动更新(Rolling Update)策略

Deployment 支持两种更新策略:

策略说明适用场景
RollingUpdate(默认)逐步替换旧 Pod 为新 Pod,保证零停机生产环境
Recreate先删除所有旧 Pod,再创建新 Pod不兼容多版本共存的场景

RollingUpdate 通过两个关键参数控制更新节奏:

  • maxSurge:更新期间允许超出期望副本数的最大 Pod 数(可以是绝对数或百分比)。
  • maxUnavailable:更新期间允许不可用的最大 Pod 数(可以是绝对数或百分比)。
默认值

Deployment 默认的滚动更新策略为:maxSurge: 25%maxUnavailable: 25%。这意味着在更新期间,可用副本数至少为 75%,最多为 125%。

参考文档:Deployments - Updating a Deployment

2.3 回滚机制(Rollback)

Deployment 的每一次更新都会创建一个新的 ReplicaSet,并保留历史版本(由 revisionHistoryLimit 控制,默认为 10)。这使得回滚操作变得极其简单:

# 查看部署历史
kubectl rollout history deployment/nginx-deployment

# 回滚到上一个版本
kubectl rollout undo deployment/nginx-deployment

# 回滚到指定版本
kubectl rollout undo deployment/nginx-deployment --to-revision=2

2.4 完整 YAML 示例

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: default
labels:
app: nginx
spec:
replicas: 3 # 期望的 Pod 副本数
revisionHistoryLimit: 10 # 保留的历史 ReplicaSet 数量
strategy:
type: RollingUpdate # 更新策略:RollingUpdate / Recreate
rollingUpdate:
maxSurge: 1 # 滚动更新时最多多创建 1 个 Pod
maxUnavailable: 0 # 滚动更新时最多允许 0 个 Pod 不可用
selector:
matchLabels: # 必须与 Pod template 的 labels 匹配
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
生产环境建议
  • 设置 maxUnavailable: 0 可以确保滚动更新过程中服务容量不会下降,代价是更新期间需要更多资源(maxSurge 需要大于 0)。
  • 配合 Pod Disruption Budget (PDB) 使用,可以在节点维护时确保最少可用副本数。

三、StatefulSet:有状态应用的首选

StatefulSet 专为需要稳定网络标识持久存储的有状态应用设计,如数据库(MySQL、PostgreSQL)、消息队列(Kafka、RabbitMQ)和分布式存储系统(Elasticsearch、etcd)。

3.1 与 Deployment 的核心区别

特性DeploymentStatefulSet
Pod 标识随机生成的名称(如 nginx-7b9f...固定的序号名称(如 mysql-0, mysql-1
网络标识Pod IP 随重建而变化每个 Pod 有稳定的 DNS 名称
存储所有 Pod 共享相同的 PVC每个 Pod 有独立的 PVC
部署顺序并行创建按序号顺序创建(0 → 1 → 2 → ...)
删除顺序并行删除按序号逆序删除(... → 2 → 1 → 0)
扩缩容随机选择 Pod 删除/创建严格按序号操作

3.2 有序部署/扩展/删除

StatefulSet 的核心特性之一是有序性 (Ordinality)。当创建或扩展 StatefulSet 时,Pod 会按照序号从 0 开始依次创建,且只有前一个 Pod 进入 Running 且 Ready 状态后,才会创建下一个 Pod。

3.3 稳定的网络标识和持久存储

StatefulSet 为每个 Pod 提供以下稳定性保证:

  • 稳定的网络标识:Pod 名称格式为 <statefulset-name>-<ordinal>(如 mysql-0),且关联的 Headless Service 会为每个 Pod 创建一个稳定的 DNS 记录:<pod-name>.<headless-service>.<namespace>.svc.cluster.local
  • 持久存储:通过 volumeClaimTemplates,StatefulSet 会为每个 Pod 自动创建独立的 PVC。即使 Pod 被重新调度到其他节点,只要绑定了相同的 PVC,数据就不会丢失。

3.4 完整 YAML 示例

apiVersion: v1
kind: Service # Headless Service,用于稳定的网络标识
metadata:
name: mysql
labels:
app: mysql
spec:
clusterIP: None # Headless Service:不分配 ClusterIP
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql # 必须指向关联的 Headless Service
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root-password
volumeMounts:
- name: data
mountPath: /var/lib/mysql
livenessProbe:
exec:
command: ["mysqladmin", "ping", "-h", "localhost"]
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command: ["mysql", "-h", "localhost", "-e", "SELECT 1"]
initialDelaySeconds: 5
periodSeconds: 5
volumeClaimTemplates: # 为每个 Pod 自动创建独立的 PVC
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: standard
resources:
requests:
storage: 10Gi
注意事项
  • StatefulSet 不会自动创建 Headless Service,你需要手动创建。
  • 删除 StatefulSet 时,默认不会删除关联的 PVC,以防止数据丢失。如需同时删除,需要手动清理。
  • StatefulSet 的滚动更新默认使用 OnDelete 策略(即手动删除 Pod 后才会重建),如需自动滚动更新,需设置 .spec.updateStrategy.type: RollingUpdate

参考文档:StatefulSets


四、DaemonSet:节点级守护进程

DaemonSet 确保集群中的每个(或特定)节点上都运行一个 Pod 副本。当节点加入集群时,DaemonSet 会自动为其创建 Pod;当节点移除时,这些 Pod 也会被自动回收。

4.1 典型使用场景

场景示例
日志收集Fluentd、Filebeat、Promtail
监控 AgentPrometheus Node Exporter、Datadog Agent
网络插件Calico、Cilium、Flannel
存储守护进程Ceph、GlusterFS 客户端
安全合规Falco(运行时安全检测)、Twistlock

4.2 滚动更新策略

DaemonSet 支持三种更新策略:

策略说明
RollingUpdate(默认)逐个节点更新 Pod,可通过 maxUnavailable 控制并发度
OnDelete手动删除旧 Pod 后才会创建新 Pod
Surging(K8s 1.22+)先创建新 Pod 再删除旧 Pod,节点上会短暂运行两个 Pod

4.3 完整 YAML 示例

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
namespace: monitoring
labels:
app: node-exporter
spec:
selector:
matchLabels:
app: node-exporter
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # 最多允许 1 个节点上的 Pod 不可用
template:
metadata:
labels:
app: node-exporter
spec:
hostNetwork: true # 使用宿主机网络(监控场景常见)
hostPID: true # 使用宿主机 PID 命名空间
tolerations: # 容忍所有 Taint,确保在所有节点上运行
- operator: Exists
containers:
- name: node-exporter
image: prom/node-exporter:v1.8.0
args:
- "--web.listen-address=:9100"
- "--path.procfs=/host/proc"
- "--path.sysfs=/host/sys"
ports:
- containerPort: 9100
hostPort: 9100
volumeMounts:
- name: proc
mountPath: /host/proc
readOnly: true
- name: sys
mountPath: /host/sys
readOnly: true
resources:
limits:
cpu: 200m
memory: 100Mi
volumes:
- name: proc
hostPath:
path: /proc
- name: sys
hostPath:
path: /sys
最佳实践
  • DaemonSet 通常需要使用 hostNetwork: truehostPID: truehostPath 卷来访问节点级别的资源。
  • 始终配置 tolerations 以确保 DaemonSet Pod 可以被调度到带有 Taint 的节点(如 Master 节点)。
  • 为 DaemonSet Pod 设置严格的资源限制,避免它们占用过多节点资源影响业务应用。

五、Job 与 CronJob:任务编排

5.1 一次性任务(Job)

Job 用于运行一次性任务,确保 Pod 成功执行完毕后终止。Job 会持续跟踪 Pod 的完成状态,并在失败时根据重试策略重新创建 Pod。

核心字段说明:

字段说明默认值
completions需要成功完成的 Pod 数1
parallelism并行运行的 Pod 数1
backoffLimit最大重试次数6
activeDeadlineSecondsJob 超时时间(秒),超时后标记为失败无限制
ttlSecondsAfterFinishedJob 完成后的自动清理时间(K8s 1.23+)永不清理

5.2 并行任务(Parallelism + Completions)

Job 的 parallelismcompletions 字段可以组合出不同的执行模式:

parallelismcompletions模式
11单次顺序执行
N1N 个 Pod 并行竞争,任一成功即完成
NNN 个 Pod 并行工作队列模式
1N顺序执行 N 个任务

5.3 定时任务(CronJob)

CronJob 基于 Cron 表达式来定期创建 Job。它的调度规则与 Linux 的 crontab 基本一致,格式为:

# ┌───────────── 分钟 (0 - 59)
# │ ┌───────────── 小时 (0 - 23)
# │ │ ┌───────────── 日 (1 - 31)
# │ │ │ ┌───────────── 月 (1 - 12)
# │ │ │ │ ┌───────────── 星期 (0 - 6, 0 = 周日)
# │ │ │ │ │
# * * * * *
CronJob 时区

从 Kubernetes v1.25 起,CronJob 支持 timeZone 字段,可以指定 Cron 表达式所使用的时区(IANA 时区格式,如 "Asia/Shanghai")。未指定时默认使用 UTC 时区。

参考文档:CronJobs

apiVersion: batch/v1
kind: CronJob
metadata:
name: database-backup
spec:
schedule: "0 2 * * *" # 每天凌晨 2 点执行
concurrencyPolicy: Forbid # 禁止并发运行
successfulJobsHistoryLimit: 3 # 保留最近 3 个成功的 Job
failedJobsHistoryLimit: 1 # 保留最近 1 个失败的 Job
jobTemplate:
spec:
backoffLimit: 2
activeDeadlineSeconds: 3600 # 超过 1 小时则标记为失败
template:
spec:
containers:
- name: backup
image: postgres:16
command:
- /bin/bash
- -c
- pg_dump -h db-service -U $DB_USER -d $DB_NAME > /backup/$(date +%Y%m%d).sql
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-secret
key: username
- name: DB_NAME
value: "myapp"
volumeMounts:
- name: backup-data
mountPath: /backup
restartPolicy: Never # Job 必须设置为 Never 或 OnFailure
volumes:
- name: backup-data
persistentVolumeClaim:
claimName: backup-pvc
重要提醒
  • Job 的 Pod restartPolicy 必须设置为 NeverOnFailure,不能是 Always
  • concurrencyPolicy: Forbid 确保上一次任务尚未完成时不会启动新任务,对于数据库备份等场景至关重要。
  • 务必设置 successfulJobsHistoryLimitfailedJobsHistoryLimit,避免历史 Job 堆积消耗 etcd 存储空间。

5.4 Job 完整 YAML 示例

apiVersion: batch/v1
kind: Job
metadata:
name: data-migration
spec:
completions: 1 # 需要成功完成 1 次
parallelism: 1 # 同时运行 1 个 Pod
backoffLimit: 3 # 最多重试 3 次
activeDeadlineSeconds: 600 # 超时 10 分钟
ttlSecondsAfterFinished: 86400 # 完成后 24 小时自动清理
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: myapp:v2.0
command: ["python", "manage.py", "migrate"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: database-url

六、ReplicaSet / ReplicationController(简述)

ReplicationController (RC)

ReplicationController 是 Kubernetes 最早的副本管理机制,用于确保指定数量的 Pod 副本始终运行。它已被 ReplicaSet 取代,目前仅存在于 API 中以保持向后兼容。

ReplicaSet (RS)

ReplicaSet 是 ReplicationController 的升级版,主要改进是支持基于集合的标签选择器(Set-based Selector),使得选择逻辑更加灵活。

特性ReplicationControllerReplicaSet
标签选择器仅支持等值匹配(environment=production支持集合匹配(environment in (production, staging)
推荐使用已废弃作为 Deployment 的底层实现,不直接使用
实践建议

不要直接创建 ReplicaSet。使用 Deployment 来管理无状态应用,Deployment 会在底层自动创建和管理 ReplicaSet。只有在需要执行非常特殊的操作(如手动管理 Pod 副本)时,才考虑直接使用 ReplicaSet。


七、工作负载选择决策树

面对不同的业务需求,如何选择合适的工作负载资源?以下决策树可以帮助你快速做出判断:

快速参考表

工作负载核心特征典型场景Pod 数量
Deployment无状态、可滚动更新、可回滚Web 服务、API 服务、微服务可变(replicas)
StatefulSet有状态、稳定标识、有序部署数据库、消息队列、分布式存储可变(replicas)
DaemonSet每节点一个 Pod日志收集、监控 Agent、网络插件= 节点数
Job一次性任务,完成后终止数据迁移、批处理、CI/CD固定(completions)
CronJob定时创建 Job数据库备份、报表生成、清理任务每次执行创建新 Job

八、本章小结

本文从 Pod 的本质出发,系统地解析了 Kubernetes 中五大核心工作负载资源:

  1. Pod 是 Kubernetes 的原子调度单元,通过共享网络和存储实现了"超亲密容器"的协作模式。理解 Pod 的生命周期和探针机制是排查问题的基础。

  2. Deployment 是无状态应用的首选,通过 ReplicaSet 实现版本管理,支持零停机的滚动更新和一键回滚。

  3. StatefulSet 为有状态应用提供了稳定的网络标识、独立的持久存储和有序的部署/删除策略,是运行数据库和消息队列等场景的最佳选择。

  4. DaemonSet 确保每个节点运行一个 Pod 副本,是部署基础设施组件(日志、监控、网络插件)的标准方式。

  5. Job 与 CronJob 覆盖了一次性任务和定时任务的需求,支持灵活的并行度和重试策略。

选择工作负载资源时,核心判断依据是:应用是否有状态是否需要长期运行是否需要在每个节点上运行。掌握这些工作负载的特性与适用场景,是构建可靠 Kubernetes 应用的基石。

在下一篇文章中,我们将深入探讨 Kubernetes 的 Service 与网络模型,解析 ClusterIP、NodePort、LoadBalancer 和 Ingress 的工作原理与选型策略。


参考文档

本文内容基于 Kubernetes 官方文档校验,以下为核心参考链接:

Kubernetes 全景解析 (0):架构设计与核心概念

· 阅读需 20 分钟
Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡

"如果你觉得 Kubernetes 太复杂,那是因为它解决的问题本身就很复杂。"

在云原生(Cloud Native)的世界里,Kubernetes(简称 K8s)已经从"可选技能"变成了"基础设施常识"。无论你是后端工程师、DevOps 工程师,还是架构师,理解 K8s 的设计思想和运行机制,都是构建现代分布式系统的必修课。

然而,K8s 的学习曲线之陡峭也是出了名的。官方文档动辄数千页,概念繁多且相互关联,很容易让人陷入"见树木不见森林"的困境。

本系列文章将从架构设计出发,逐层深入到核心概念、工作负载管理、网络模型、存储体系、调度策略与安全机制,帮助你构建一套完整、系统的 K8s 认知框架。本文作为系列的第零章,将聚焦于最根本的问题:Kubernetes 是什么?它是如何设计的?它由哪些核心部分组成?


一、为什么需要 Kubernetes

1.1 容器化演进的必然之路

要理解 K8s 存在的意义,我们需要先回顾应用部署方式的演进历程:

阶段部署方式隔离性资源利用率启动速度运维复杂度
物理机时代应用直接部署在物理服务器上-
虚拟机时代Hypervisor 划分多个 VM分钟级
容器时代Docker 等容器引擎秒级
编排时代Kubernetes 等编排系统秒级低(自动化)

物理机时代,一个应用独占一台服务器,资源浪费严重。为了提高利用率,我们开始在同一个物理机上部署多个应用,但随之而来的是依赖冲突、端口争抢、一个应用崩溃拖垮整台机器等问题。

虚拟机时代,Hypervisor(如 VMware、KVM)在物理机上虚拟出多个独立的操作系统实例,实现了良好的隔离。但虚拟机本身就很重——一个 VM 动辄几个 GB,启动需要数分钟,而且携带了完整的 Guest OS,大量资源被浪费在运行重复的系统服务上。

容器时代,Docker 横空出世。容器共享宿主机的内核,不需要 Guest OS,一个镜像通常只有几十 MB,启动只需毫秒级。容器通过 Namespace 实现视图隔离,通过 Cgroups 实现资源限制,在轻量和隔离之间找到了一个绝佳的平衡点。

但容器解决的是**"如何打包和运行应用"的问题,却没有解决"如何管理成百上千个容器"**的问题。当你的应用从 3 个容器扩展到 3000 个,跨越几十台机器时,以下问题接踵而至:

  • 哪个容器应该运行在哪台机器上?
  • 容器挂了谁来重启?机器挂了谁来迁移?
  • 如何实现滚动更新而不中断服务?
  • 如何让前端服务发现后端服务的地址?
  • 如何让外部流量均匀地分发到多个实例?

这就是 Kubernetes 登场的时刻。

1.2 K8s 解决的核心问题

Kubernetes 作为一个容器编排平台(Container Orchestration Platform),本质上解决的是一个大规模自动化管理的问题。它将运维人员从手工操作中解放出来,通过声明式配置和自动化控制循环,实现了:

  • 自动部署与回滚:声明期望状态,K8s 负责将实际状态趋近期望状态
  • 服务发现与负载均衡:内置 DNS 和 Service 机制,无需外部注册中心
  • 自动扩缩容:根据 CPU/内存/自定义指标自动调整实例数量
  • 自我修复:容器崩溃自动重启,节点故障自动迁移
  • 滚动更新与蓝绿部署:零停机更新应用

1.3 K8s 的设计哲学

理解 K8s 的设计哲学,比记住一百个命令更有价值。K8s 的三个核心设计理念贯穿了它的每一个组件:

声明式(Declarative)而非命令式(Imperative)

命令式 vs 声明式
  • 命令式:你告诉系统"做什么"——docker run nginxkubectl scale deployment nginx --replicas=3
  • 声明式:你告诉系统"你要什么"——提交一个 YAML 文件描述"我需要 3 个 Nginx 实例",K8s 会持续工作直到实际状态与期望状态一致

声明式的好处是:可重复、可审计、可版本化。同一个 YAML 文件,无论执行多少次,结果都是一致的。

不可变基础设施(Immutable Infrastructure)

K8s 中的容器镜像一旦构建就不应该被修改。需要更新时,你应该构建新镜像、创建新版本,而不是 SSH 进容器里改配置。这与传统"登录服务器打补丁"的运维方式截然不同。

最终一致性(Eventual Consistency)

K8s 不保证你的请求立即生效,但保证系统最终会收敛到期望状态。这种设计牺牲了即时性,换取了极高的可靠性和容错能力。即使你同时提交了多个冲突的变更,系统也能通过控制循环最终达到一个一致的状态。


二、K8s 整体架构

Kubernetes 采用经典的主从架构(Master-Worker Architecture),分为**控制面(Control Plane)数据面(Data Plane)**两个层级。理解这两个层面的职责划分,是理解 K8s 一切行为的基础。

2.1 架构全景图

2.2 控制面组件详解

控制面是 K8s 集群的"大脑",负责全局决策和集群状态的维护。在生产环境中,为了保证高可用,控制面组件通常部署在多个独立的 Master 节点上。

kube-apiserver:集群的统一入口

kube-apiserver 是整个 K8s 系统的唯一入口。所有组件——无论是内部的 Scheduler、Controller,还是外部的 kubectl、CI/CD Pipeline——都通过 API Server 进行通信。

为什么所有通信都要经过 API Server?

这种设计被称为 "Hub-and-Spoke" 模式。它的好处是:

  1. 统一鉴权:所有请求都在一个地方进行认证(Authentication)、授权(Authorization)和准入控制(Admission Control)
  2. 解耦:各组件不需要知道彼此的存在,只需要与 API Server 交互
  3. 审计:所有操作都有统一的日志记录

API Server 本身是无状态的,它可以水平扩展。所有的状态数据都存储在 etcd 中。

etcd:集群的"真相之源"

etcd 是一个分布式的、一致的键值存储系统,基于 Raft 共识算法实现。它是 K8s 集群的唯一数据源(Single Source of Truth)——集群中所有的一切:节点信息、Pod 状态、配置数据、Secret……全部存储在 etcd 中。

etcd 是 K8s 的命脉
  • etcd 的性能直接决定了整个集群的响应速度
  • etcd 的数据丢失意味着集群状态的丢失(虽然 Pod 可以重建,但某些运行时状态无法恢复)
  • 生产环境建议部署 3 或 5 个 etcd 节点(奇数个,满足 Raft 多数派要求)
  • 必须定期备份 etcd 数据

kube-scheduler:调度决策者

当你创建一个 Pod 时,API Server 只是将这个 Pod 的信息记录到了 etcd 中——此时 Pod 还处于 Pending 状态,因为它还没有被分配到任何节点上。Scheduler 的职责就是为每个未调度的 Pod 选择一个最合适的节点。

调度过程分为三个阶段:

  1. 过滤(Filtering):排除不满足条件的节点(资源不足、端口冲突、污点容忍不匹配等)
  2. 打分(Scoring):对剩余节点进行优先级打分(亲和性、镜像本地性、负载均衡等),选择得分最高的节点
  3. 绑定(Binding):将调度决策应用到集群,将 Pod 与选定节点进行绑定
调度框架(Scheduling Framework)

从 v1.19 起,Kubernetes 调度器采用插件化架构(Scheduling Framework),上述三个阶段对应调度框架中的核心扩展点。整个调度过程分为调度周期(Scheduling Cycle)绑定周期(Binding Cycle),调度周期串行执行,绑定周期可并发执行。

参考文档:Scheduling Framework

kube-controller-manager:状态守护者

Controller Manager 运行着多个控制器(Controller),每个控制器都是一个独立的控制循环(Reconciliation Loop),负责监控集群的某一部分状态,并持续将其推向期望状态。

常见的内置控制器包括:

控制器职责
Deployment Controller确保 Deployment 管理的 Pod 副本数符合期望
ReplicaSet Controller确保 ReplicaSet 管理的 Pod 副本数符合期望
Node Controller监控节点健康状态,节点失联时触发 Pod 驱逐
Service Account Controller为命名空间创建默认 ServiceAccount
EndpointSlice Controller维护 Service 与 Pod 的映射关系(推荐使用 EndpointSlice 替代已废弃的 Endpoints)
控制器的本质:控制循环

每个控制器的工作模式都是相同的:

while true:
实际状态 = 从 API Server 获取当前状态
期望状态 = 从配置中获取期望状态
if 实际状态 != 期望状态:
执行调谐操作(创建/删除/更新)
sleep(一段时间)

这就是 K8s 所谓的**"调谐(Reconciliation)"**机制。

cloud-controller-manager:云平台桥梁

如果你在 AWS、GCP、Azure 等云平台上运行 K8s,CCM 负责与云厂商的 API 交互,管理云平台特有的资源:

  • Node Controller:调用云 API 查询节点地址和状态
  • Route Controller:配置云平台的路由规则
  • Service Controller:创建云平台的负载均衡器(如 AWS ELB)

CCM 的引入使得 K8s 的核心代码不需要耦合任何特定云厂商的 SDK,实现了云平台无关性。

2.3 数据面组件详解

数据面(也叫 Worker Node)是实际运行应用工作负载的地方。每个 Worker 节点上运行着三个核心组件:

kubelet:节点上的"车间主任"

kubelet 是运行在每个 Worker 节点上的代理,它的职责是:

  1. 接收指令:从 API Server 获取分配到本节点的 Pod 规格(Spec)
  2. 管理容器:通过 CRI(Container Runtime Interface)调用容器运行时,创建和管理 Pod 中的容器
  3. 健康检查:定期执行 Liveness Probe、Readiness Probe 和 Startup Probe
  4. 状态上报:将节点和 Pod 的状态信息上报给 API Server

你可以把 kubelet 理解为一个"车间主任"——它不制定生产计划(那是 Scheduler 的事),但负责确保分配到自己车间的生产任务被正确执行。

kube-proxy:网络规则的维护者

kube-proxy 运行在每个节点上,负责维护节点的网络规则,实现 Service 的负载均衡和网络代理。它通过操作 iptables 或 IPVS 规则,将访问 Service 的流量转发到后端的 Pod。

简单来说,当你访问一个 Service 的 ClusterIP 时,kube-proxy 维护的规则会将流量自动转发到该 Service 关联的某个 Pod 上。

kube-proxy 是可选组件(v1.34+)

根据 Kubernetes 官方文档,kube-proxy 是一个可选组件。如果你使用的网络插件(CNI)自身实现了 Service 的数据包转发功能,并提供与 kube-proxy 等效的行为,那么你不需要在集群节点上运行 kube-proxy

例如,Cilium 等现代 CNI 插件可以直接替代 kube-proxy 的功能,通过 eBPF 实现更高效的 Service 负载均衡和网络代理。

参考文档:Kubernetes Architecture - kube-proxy (optional)

Container Runtime:容器的实际执行者

kubelet 本身并不直接运行容器,而是通过 CRI(Container Runtime Interface) 标准接口调用容器运行时。常见的 CRI 兼容运行时包括:

  • containerd:目前最主流的选择,Docker 的核心运行时组件独立出来的版本
  • CRI-O:专为 Kubernetes 设计的轻量级运行时,由 Red Hat 主导
  • Kata Containers:提供虚拟机级别的隔离,适用于安全要求极高的场景
Docker 与 K8s 的"分手"

在 K8s 1.20 之前的版本中,kubelet 通过一个名为 dockershim 的内置组件直接与 Docker 通信。但从 K8s 1.24 开始,dockershim 被正式移除。这并不意味着你不能在 K8s 中运行 Docker 镜像——Docker 镜像遵循 OCI 标准,containerd 和 CRI-O 都能运行它们。移除的只是对 Docker daemon 的直接依赖。


三、核心概念模型

Kubernetes 中的一切都被抽象为 API 对象(API Object)。理解这些对象及其关系,是使用 K8s 的基础。

3.1 API 对象与声明式配置

在 K8s 中,你不需要告诉系统"如何做",而是描述"你要什么"。你通过提交 YAML(或 JSON)文件来定义 API 对象,API Server 会将你的声明持久化到 etcd,然后各个控制器会持续工作,确保实际状态趋近于你声明的期望状态。

3.2 YAML 配置结构解析

每个 K8s API 对象的 YAML 配置都遵循以下结构:

apiVersion: apps/v1 # API 版本,标识对象属于哪个 API 组
kind: Deployment # 对象类型,K8s 内置了几十种对象类型
metadata: # 对象的元数据(名称、标签、注解等)
name: nginx-deployment
namespace: default
labels:
app: nginx
tier: frontend
spec: # 对象的规格(期望状态)
replicas: 3 # 期望运行 3 个 Pod 副本
selector: # 选择器,用于关联管理的 Pod
matchLabels:
app: nginx
template: # Pod 模板,定义如何创建 Pod
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
YAML 配置的四个必填字段
  1. apiVersion:指定 API 版本。v1 是核心 API 组,apps/v1networking.k8s.io/v1 等是扩展 API 组
  2. kind:对象类型,如 Pod、Deployment、Service、ConfigMap 等
  3. metadata:对象的"身份证",至少包含 name,通常还包含 labelsannotations
  4. spec:对象的"期望状态",不同类型的对象有不同的 spec 结构

3.3 API 对象层级关系

K8s 的 API 对象之间存在着清晰的层级关系。上层对象管理下层对象,形成了一个从粗粒度到细粒度的管理链。

Pod:K8s 的最小调度单元

初学者常有一个疑问:为什么 K8s 的最小单位是 Pod 而不是 Container?

Pod 是一个逻辑概念,它可以包含一个或多个紧密耦合的容器。这些容器共享:

  • 网络命名空间:同一个 Pod 中的容器可以通过 localhost 互相访问
  • 存储卷:可以挂载共享的 Volume
  • 运行约束:总是被调度到同一个节点上

一个典型的多容器 Pod 场景是"Sidecar 模式":主容器运行应用逻辑,Sidecar 容器负责日志收集、监控数据导出或代理转发。


四、一次请求的完整旅程

理论讲了不少,现在让我们通过一个具体的场景来串联所有知识:当你执行 kubectl apply -f nginx-deployment.yaml 时,K8s 内部到底发生了什么?

4.1 完整时序图

4.2 控制循环(Reconciliation Loop)详解

时序图展示了一次性的创建流程,但 K8s 的真正威力在于其持续运行的控制循环。让我们深入理解这个机制:

第一步:感知变化(Watch 机制)

K8s 的各个组件并不是定期轮询 API Server,而是通过基于 HTTP 长连接的 Watch 机制 来感知变化。当你创建、修改或删除一个对象时,API Server 会通过 Watch 通道将事件推送给所有订阅者。事件类型包括:

  • ADDED:新对象被创建
  • MODIFIED:对象被更新
  • DELETED:对象被删除

这种基于事件驱动的设计,比轮询更高效,延迟更低。

第二步:计算差异(Diff)

控制器收到事件后,会将实际状态期望状态进行对比。例如,Deployment Controller 发现期望的副本数是 3,但当前只有 2 个 Pod 在运行。

第三步:执行调谐(Reconcile)

控制器计算出需要执行的操作后,会通过 API Server 发起调谐请求。在上面的例子中,Deployment Controller 会创建一个新的 ReplicaSet,ReplicaSet Controller 会创建一个新的 Pod。

第四步:重复

整个过程是一个无限循环。即使某个操作失败了,下一轮循环也会再次尝试。这就是 K8s 实现自愈能力的根本原因。

水平扩展:自定义控制器

K8s 的控制器模式不仅限于内置资源。通过 CRD(Custom Resource Definition)Operator 模式,你可以定义自己的 API 对象和对应的控制器。例如,Prometheus Operator 可以管理 Prometheus 实例的生命周期,Cert-Manager Operator 可以自动化 TLS 证书的申请和续期。这就是 K8s 被称为"平台的平台"的原因。


五、本章小结与下一篇预告

本文作为 Kubernetes 全景解析系列的第零章,我们从宏观视角梳理了 K8s 的全貌:

维度核心要点
设计哲学声明式配置、不可变基础设施、最终一致性
架构分层控制面(决策)+ 数据面(执行)
控制面组件API Server(入口)、etcd(存储)、Scheduler(调度)、Controller Manager(控制循环)
数据面组件kubelet(节点代理)、kube-proxy(网络代理)、CRI(容器运行时)
核心抽象一切皆 API 对象,通过 YAML 声明期望状态
运行机制Watch 事件驱动 + 控制循环持续调谐

掌握这些基础知识后,你将不再对 K8s 感到迷茫——它的每一个行为都可以追溯到上述架构和机制中。

下一篇,我们将深入 K8s 的工作负载管理: 从 Pod 的生命周期到 Deployment、StatefulSet、DaemonSet 等工作负载控制器的使用场景与最佳实践。理解了工作负载,你就能真正开始在 K8s 上部署和运行应用了。


系列导航

章节主题状态
0架构设计与核心概念✅ 已发布
1工作负载与 Pod 生命周期深度解析✅ 已发布
2网络模型与服务发现全链路解析✅ 已发布
3存储体系与配置管理深度剖析✅ 已发布
4调度器、资源管理与弹性伸缩✅ 已发布
5安全体系与可观测性全景✅ 已发布
6生产级微服务架构实战✅ 已发布
7有状态应用与 Operator 模式实战✅ 已发布

参考文档

本文内容基于 Kubernetes 官方文档校验,以下为核心参考链接:

Harness 工程深度解析:驾驭 AI 智能体与构建生产级 CI/CD 的终极指南

· 阅读需 21 分钟
Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡

在现代软件工程中,"Harness"(原意为马具/束具)一词具有强大的双重含义。一方面,它指的是驾驭 AI 智能体(AI Agents)的基础设施——通过约束、反馈循环和任务拆分策略,将模型的原始能力转化为可靠的输出。另一方面,它是 Harness Open Source 的名字——一个集源码控制、CI/CD 流水线、开发环境和制品库于一体的端到端开发者平台。

本文将深入探讨这两个维度:从智能体 Harness 设计的概念性突破,到使用 Harness 平台的生产级 CI/CD 实战。


第一部分:什么是 Harness 工程?

马与骑手的隐喻

OpenAI 的工程团队提出了一个生动的隐喻:AI 模型就像一匹马——力量强大,但不可预测且容易跑偏。而 Harness 则是工程师围绕它构建的一切——Linter、结构化测试、文档标准、反馈循环——这些装置能够有效地引导这股力量产出成果。

在这种范式下,模型是 CPU,而 Harness 是操作系统 (OS)。CPU 负责计算,但 OS 负责文件系统、内存管理、权限控制和 I/O 调度。没有 OS 的 CPU 只是一个发热的芯片;没有 Harness 的模型只是一个产生 Token 的预测器。

工程师的主要职责正在发生转变:从编写代码转变为设计环境构建反馈循环,从而让 AI 智能体能够可靠地运行。

— OpenAI, "Harness engineering: leveraging Codex in an agent-first world" (2026年2月)

三大支柱

Harness 工程建立在三个基本支柱之上:

支柱含义示例
上下文工程 (Context Engineering)确保智能体在上下文中拥有恰到好处的信息压缩 (Compaction)、即时加载 (JIT)、结构化记忆
架构约束 (Architectural Constraints)通过机械化手段强制执行“好代码”的标准Linter、依赖规则、结构化测试
熵增管理 (Entropy Management)长期保持代码库的连贯性垃圾回收、代码重构智能体

如果没有 Harness,即使是前沿模型产出的结果也往往是功能尚可但弱不禁风的——重复的代码、漏洞百出的核心逻辑、平庸的设计以及逐渐腐化的上下文。而通过精心设计的 Harness,同一个模型可以在 10 个冲刺(Sprint)中构建出一个拥有 16 个功能、视觉风格统一且完全可运行的应用程序。


第二部分:上下文工程——有限的资源

上下文窗口(Context Window)是智能体编程中最宝贵的资源。Anthropic 将其描述为一种收益递减的有限预算——每一个 Token 都至关重要,将无关的历史记录塞满窗口会主动降低模型的性能。

策略 1:上下文压缩 (Compaction)

压缩 (Compaction) 将完整的上下文窗口浓缩为高保真的摘要。当对话接近 Token 上限时,系统会原地总结之前的轮次,仅保留最关键的细节。

压缩前:系统提示词 + 4 轮对话 + 12 次工具调用 + 冗长的输出
→ 180K tokens (接近上限)

压缩后:压缩后的摘要:关键状态 + 当前任务 + 核心上下文
→ 30K tokens (留出更多工作空间)

虽然压缩保持了连贯性,但它并没有给智能体一个全新的开始,这意味着上下文焦虑 (Context Anxiety) 可能依然存在。

— Anthropic, "Harness design is key to performance at the frontier"

策略 2:上下文重置 (Context Reset)

上下文重置 (Context Reset) 采取了更彻底的方法:完全清空上下文窗口,启动一个新的智能体阶段(Session),并使用结构化的移交产物 (Handoff Artifact) 来承载上一个智能体的状态。

这解决了一个被称为上下文焦虑的关键失效模式——即当模型认为自己接近上下文极限时,会过早地结束工作。全新的上下文为智能体提供了一个没有累赘的清晰起点。

移交产物通常包含:

  • 当前进度——哪些功能已完成,哪些正在进行中
  • 文件清单——关键文件及其用途
  • 后续步骤——接下来要执行的具体任务
  • 已知问题——推迟处理的 Bug 或决策

策略 3:即时加载 (JIT Loading)

对于拥有数百万行代码的大型仓库,不可能将所有代码都塞进上下文。JIT 加载利用工具(如 grepast-grep)按需检索代码片段,并在智能体请求时将其动态注入上下文。


第三部分:被忽视的核心——结构化约束

除了上下文管理,Harness 工程中还有几个常被忽视但至关重要的概念:

1. 上下文压舱物 (Context Ballast)

智能体需要一门“通用语言”来理解项目。在仓库中加入 CLAUDE.mdAGENTS.md 不仅是为了人类开发者,更是为了给智能体提供压舱物 (Ballast)

实战示例:一个典型的 CLAUDE.md 片段

# Agent 架构规范
1. **状态管理**:严禁使用 Redux。我们统一使用 Zustand 进行状态管理。
2. **样式**:所有组件必须使用 Tailwind CSS,不要创建任何 `.css` 文件。
3. **数据库**:所有数据库查询必须通过 Prisma ORM 进行,严禁直接拼接 Raw SQL。

这为智能体提供了极其清晰的操作结界,极大地减少了产生“技术债”的可能。

2. 机械化 guardrails (硬性约束)

不要指望通过 Prompt 告诉智能体“请不要在业务层写 SQL”。哪怕你在提示词中重复三遍,一旦上下文变长,它依然可能违规。

在 Harness 中,你应该配置一个 Linter 规则(例如使用 ast-grep 或 ESLint),在智能体生成代码后强制拦截:

# 机械化拦截示例:运行于工具调用完成后
$ npm run lint
❌ Error: [no-raw-sql] Detected Prisma raw query in src/services/user.ts:42

因为模型在面对“确凿的报错堆栈信息”时的修复能力,远远强于面对“你不应该怎么做”的软性建议时的遵循能力。这就是用程序的确定性去驾驭 LLM 的混沌性。

3. LLM-as-Auditor (审计者模式)

在生成者(Generator)输出代码后,由一个更高阶的模型(或配置了不同 System Prompt 的模型)担任 Auditor (审计者)。审计者不负责写代码,只负责在 Harness 环境中寻找逻辑漏洞和架构违规。


第四部分:多智能体协作——生成与评估的分离

近期 Harness 研究中最具影响力的见解或许是生成与评估的分离。受生成对抗网络 (GAN) 的启发,Anthropic 设计了一个三智能体系统,其表现显著优于单智能体方法。

三个角色

1. 规划者智能体 (Planner Agent)

规划者接收简单的 1-4 句话的提示词,并将其扩展为完整的产品规格说明书。它的指令是在保证雄心勃勃的范围的同时,专注于产品上下文而非细碎的技术细节。

输入: "给我做一个复古视频游戏制作器"

输出: RetroForge - 2D 复古游戏制作工具
涵盖 10 个冲刺的 16 个功能:
- 项目仪表盘与管理
- 基于瓦片的地图编辑器
- 像素画精灵编辑器
- 可视化实体行为系统
- 可运行的测试模式
- AI 辅助精灵生成器
- 音效与音乐系统
- 带分享链接的游戏导出
...

关键设计决策:规划者刻意避免指定具体的颗粒化技术实现。如果它在早期就定死了技术细节且不幸定错了,这些错误会一直向下游传递。

2. 生成者智能体 (Generator Agent)

生成者按冲刺 (Sprint) 进行开发,每次实现一个功能,使用标准的技术栈(如 React + Vite + FastAPI + SQLite/PostgreSQL)。它使用 Git 进行版本控制,并在每个冲刺结束时进行自测,然后移交给 QA。

3. 评估者智能体 (Evaluator Agent)

评估者使用 Playwright MCP 像真实用户一样与运行中的程序交互——点击页面、测试 UI 功能、验证 API 端点、检查数据库状态。它从四个维度对每个冲刺进行评分:

准则权重衡量标准
设计质量它感觉像是一个连贯的整体,还是零散部件的堆砌?
原创性是否有刻意的创意选择,还是仅仅是模板默认值?
工艺 (Craft)正常排版层级、间距、色彩和谐度、对比度
功能性正常用户能否理解界面并完成任务?

每个维度都有一个硬性门槛——如果任何一个低于门槛,该冲刺即宣告失败,生成者将收到详细的反馈意见。

冲刺合同谈判

在每个冲刺开始前,生成者和评估者会协商一份合同:在编写任何代码之前,它们会对“完成”的定义达成一致——即具体的、可测试的行为和验收标准。这弥合了高层用户故事与可验证实现之间的鸿沟。

自我评估的困境

一个关键洞察:当要求智能体评估自己的工作时,它们会习惯性地倾向于给出正面评价——将平庸的输出美化为优秀。将评估者从生成者中分离出来,使得调节怀疑精神变得更加容易:

同一智能体(自我评估):
"设计简洁且功能齐全。评分:9/10" ← 过度宽容

独立的评估者(调优为怀疑立场):
"布局使用了固定高度的面板,浪费了空间。工作流过于呆板
——没有任何指引告知用户在填充关卡前应先创建精灵。
核心游戏逻辑对输入无响应。评分:4/10" ← 诚实的评估

真实结果对比

指标单个智能体3 智能体 Harness
成本$6$124
耗时30 分钟4 小时
功能实现部分完成(核心逻辑断裂)16 个功能全量完成(运行正常)
运行模式无法运行具有物理效果的可玩模式
设计连贯性通用模板一致的视觉身份

虽然 Harness 的成本高出 20 倍,但产出质量的差距是惊人的:单智能体跑出来的游戏完全无法运行——实体显示在屏幕上但对输入毫无反应。而 Harness 运行产出了一个具有物理效果、AI 辅助精灵生成和连贯设计语言的可玩游戏。


第五部分:Harness 开源平台概述

除了“作为智能体基础设施的 Harness”这一概念外,还有一个具体的开源平台也叫 Harness——它是传说中的 Drone CI 的下一代演进。

什么是 Harness 开源平台?

Harness Open Source 是一个端到端的开发者平台,集成了四大模块:

模块描述关键特性
源码控制 (Source Control)基于 Git 的代码托管仓库、PR、代码审查、分支保护
CI/CD 流水线自动化构建与部署基于 Docker 的步骤、YAML 配置、并行执行
Gitspaces托管的开发环境云原生工作区、秒级启动
制品库 (Artifact Registry)包管理支持 Docker、Maven、npm 制品

Drone 的遗产

Harness 代表了对下一代 Drone巨大投入。Drone 仅专注于持续集成,而 Harness 增加了源码托管、开发环境和制品仓库——为团队提供了一个完整的开源 DevOps 平台。

Drone 的代码库目前作为一个功能分支继续存在,供那些在迁移期间仍需其特定流水线能力的用户使用。

技术架构

  • 后端:Go 语言编写,单二进制部署
  • 数据库:开发环境使用 SQLite,生产环境使用 PostgreSQL
  • 前端:基于 React 的 Web UI
  • 流水线:在 Docker 容器内执行
  • API:提供完整的 REST API 和 Swagger 文档

第六部分:实战——部署 Harness 并构建 CI/CD 流水线

第一步:使用 Docker 部署 Harness

运行 Harness 只需要一条命令:

docker run -d \
-p 3000:3000 \
-p 3022:3022 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /tmp/harness:/data \
--name harness \
--restart always \
harness/harness

容器启动后,在浏览器中访问 http://localhost:3000

重要提示-v /tmp/harness:/data 挂载卷是必不可少的。如果没有它,当容器停止时,所有的仓库和配置都会丢失。生产环境请使用命名卷或持久化目录。

第二步:初始设置

  1. 创建管理员账号——首次访问时,系统会提示你创建初始管理员(默认:admin / changeit
  2. 生成 PAT (个人访问令牌) 用于 API 访问:
# 登录 CLI
./gitness login

# 生成个人访问令牌(有效期 1 年)
./gitness user pat "my-pat-uid" 2592000
  1. 测试 API
curl http://localhost:3000/api/v1/user \
-H "Authorization: Bearer $TOKEN"

第三步:创建仓库

进入 Web 界面并创建一个新仓库。Harness 提供完整的 Git 托管功能,包括:

  • 分支保护规则
  • Pull Request 开发流
  • 代码审查与评论
  • Webhook 集成

第四步:配置 CI/CD 流水线

在你的仓库根目录下创建 .harness/ 目录,并添加一个流水线配置文件:

# .harness/pipeline.yaml
kind: pipeline
spec:
stages:
- type: ci
spec:
steps:
- name: clone
type: run
spec:
container: alpine/git
script: |
git clone $REPO_URL .
echo "✅ 克隆完成"

- name: install
type: run
spec:
container: node:20-alpine
script: |
npm ci
echo "✅ 依赖安装完成"

- name: build
type: run
spec:
container: node:20-alpine
script: |
npm run build
echo "✅ 构建成功"

- name: test
type: run
spec:
container: node:20-alpine
script: |
npm run test -- --coverage
echo "✅ 单元测试全量通过"

- name: agent-qa-eval
type: run
spec:
container: python:3.11-alpine
script: |
pip install deepeval
echo "🤖 启动评估者智能体进行架构审查..."
deepeval test run tests/architecture_audit.py
echo "✅ 架构合规性扫描通过"

- name: docker-build
type: plugin
spec:
name: docker
inputs:
repo: myregistry/myapp
tags: latest,${DRONE_COMMIT_SHA:0:8}
dockerfile: Dockerfile

第五步:流水线触发器

Harness 支持自动流水线触发:

# 仅在推送到 main 分支时触发
trigger:
branch:
- main
event:
- push
- pull_request

每次向 main 分支执行 git push 时,系统将自动执行:

  1. 克隆仓库
  2. 安装依赖
  3. 构建项目
  4. 运行测试并统计覆盖率
  5. 构建并推送 Docker 镜像

第六步:流水线的 Docker 配置

Harness 流水线在 Docker 容器内运行。应用程序会自动与你的守护进程协商 Docker API 版本。

针对非标准的 Docker 运行时:

# Rancher Desktop
sudo ln -sf ~/.rd/docker.sock /var/run/docker.sock

# Colima
sudo ln -sf ~/.colima/default/docker.sock /var/run/docker.sock

# 或者在 .local.env 中设置环境变量:
GITNESS_DOCKER_HOST=unix:///Users/<username>/.rd/docker.sock

第七步:从源码构建(进阶)

对于贡献者或想要尝试最新功能的用户:

# 前提条件
# - Go 1.20+, Node.js (最新稳定版), protobuf v3.21.11

# 安装 Go 工具
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2.0

# 安装依赖
make dep
make tools

# 构建 Web 界面
pushd web && yarn install && yarn build && popd

# 构建 Go 二进制文件
make build

# 运行服务器
./gitness server .local.env # → http://localhost:3000

第八步:访问 Swagger API

Harness 提供了详尽的 API 文档:

  • 主 APIhttp://localhost:3000/swagger
  • OpenAPI 规格书http://localhost:3000/openapi.yaml
  • 制品库 APIhttp://localhost:3000/registry/swagger/

你可以自动生成 UI 所使用的客户端代码:

./gitness swagger > web/src/services/code/swagger.yaml
cd web && yarn services

第七部分:连接两个世界——智能体 Harness + 平台 Harness

"Harness" 的两种含义——智能体基础设施与 DevOps 平台——正在合流。请设想一个现代 AI 驱动的开发工作流架构:

闭环流程

  1. 智能体 Harness 通过“规划者 → 生成者 → 评估者”循环生成高质量代码。
  2. CI/CD 集成:将此测试放入 Harness CI 的步骤中。当 Agent 生成代码或内容后,自动触发评估,只有通过阈值的 PR 才能合并。

如何使用 Aider 构建你的 Harness?

Aider 是目前最接近“Harness 工程”落地且高 Start (22k+) 的工具。它不仅是一个命令行聊天机器人,更是一个利用 Git 自动管理 Harness 的“协同工程师”。

实战步骤:

  1. 自动上下文映射:Aider 启动时会读取 .gitignore,并根据你的文件树生成 REPOMAP(仓库地图)。这本质上是 JIT Loading

  2. Git 原子化提交:每当 Aider 完成一个功能的修改并运行测试后,它会自动进行 Git 提交。这为智能体提供了一个可回退的阶段检查点

  3. 内置测试 Harness

    # 让 Aider 带着测试运行
    aider --test "npm test" src/payment.ts

    如果测试失败,Aider 会自动分析报错,尝试修复并重新运行测试,直到通过为止。这就是典型的反馈循环 (Feedback Loop)

  4. Git 提交将批准后的代码推送到 Harness 托管的仓库。

  5. Harness 流水线自动执行构建、测试和部署。

  6. 监控与反馈流回系统,启发下一轮迭代。

这就是未来:AI 智能体不仅仅是在写代码,而是通过真实的 CI/CD 流水线交付生产级软件

Aider 与 Agent 框架的定位对比

在实践中,我们需要区分交互式编码辅助系统级 Agent 框架

特性维度Aider (终端交互统帅)OpenHarness (系统指挥官)
主要使用者人类开发者(终端交互)系统/微服务(API/脚本驱动)
上下文形态基于 JIT / .gitignore 动态抓取CLAUDE.md + 会话记忆归档
回退机制依赖 Git 本地提交流多节点流式循环(API Retry)
典型应用场景快速实现单个 Feature、本地 Debug搭建自动评估管线、后台群体协作

第八部分:行业标杆——OpenHarness 开源框架

在所有的开源尝试中,OpenHarness (oh) 是一个值得深入研究的范本。它由 HKUDS 团队开发,旨在提供一个工业级的、与模型无关的智能体驾驭层。

OpenHarness 的设计完全印证了我们之前讨论的“Harness 即 OS”的观点。它的核心架构由五个关键维度组成:

1. 强化的 Agent Loop

不同于简单的“提示-回复”循环,OpenHarness 实现了流式工具调用循环 (Streaming Tool-Call Cycle)。它支持:

  • 并行工具执行:同时启动多个 Shell 或 Search 任务。
  • 指数退避重试:自动处理 API 速率限制和网络抖动。
  • 消耗追踪:实时计算 Token 成本,防止智能体陷入无限递归导致的账单爆炸。

2. 动态工具箱 (Harness Toolkit)

OpenHarness 内置了 43 个核心工具,涵盖文件操作、Shell 执行、Web 搜索和 MCP(Model Context Protocol)支持。其最独特之处在于:

  • 按需加载 Skill:通过读取 .md 文档,智能体可以即时学会如何操作特定的内部系统。
  • 插件生态:支持钩子(Hooks)和自定义智能体注入。

3. 记忆与上下文管理

它原生支持 CLAUDE.md 发现机制,将项目规范自动注入初始上下文。同时:

  • Auto-Compaction:当长对话接近窗口上限时,自动触发总结。
  • MEMORY.md:将关键决策和进度持久化到磁盘,支持跨 Session 的记忆继承。

4. 治理与权限 (Governance)

这是 OpenHarness 区别于简单的“实验脚本”的关键:

  • 多层级权限:可以配置只读、受限写入或完全控制。
  • 交互式审批:在执行 rm -rf 或危险 Shell 命令前,弹出对话框请求人类确认。

5. 群体协同 (Swarm)

支持 Subagent Spawning。主智能体可以将子任务委派给专门的子智能体(如“文档专家”或“单元测试生成器”),实现高度并发的任务处理。

核心亮点:代码即 Harness (实战演示)

为了感受这种框架的力量,看看我们如何用 OpenHarness 启动一个带审批流的智能体会话:

from openharness import Agent, ToolRegistry, Memory

# 1. 加载工具箱与上下文
tools = ToolRegistry.load(["shell", "file_system", "mcp_playwright"])
memory = Memory.load_from("MEMORY.md")

# 2. 带有严格 Harness 的智能体
agent = Agent(
model="claude-3-5-sonnet",
tools=tools,
memory=memory,
governance={
"require_human_approval": ["shell.exec(rm*)", "shell.exec(docker*)"]
}
)

# 3. 启动流式调用循环
for step in agent.run("重构项目数据库层,并运行测试"):
if step.type == "tool_call":
print(f"[{step.tool}] 执行中...")
elif step.type == "approval_required":
user_input = input(f"智能体试图执行 {step.command},是否允许?(y/n)")
step.approve(user_input == 'y')

与直接调用 OpenAI 或 Anthropic 的 API 相比,OpenHarness 将开发者从繁琐的 Token 截断、工具执行状态机、报错重试逻辑中彻底解放了出来。


第九部分:最佳实践与经验教训

关于智能体 Harness 设计

  1. 寻找最简单的方案,仅在必要时增加复杂度。 Harness 中的每一个组件都编码了你对模型能力的负面假设——而随着模型进步,这些假设往往会失效。

  2. 当新模型发布时,重新审视你的 Harness。 移除那些不再起支撑作用的部件,添加以前无法实现的新能力。Anthropic 团队在从 Opus 4.5 升级到 4.6 时移除了冲刺拆分逻辑,因为新模型能够原生处理长时间的连贯会话。

  3. 独立调优评估者。 让一个独立的评估者保持怀疑态度立场的难度,远低于让生成者对自己代码进行批判。

  4. 当观察到“上下文焦虑”时,优先选择上下文重置而非压缩。 清洁的起点往往比总结后的持续更可靠。

  5. 有趣的 Harness 空间并不会随模型进步而萎缩——它在迁移。 AI 工程师的乐趣在于不断发现新的组合方式。

关于 Harness 平台部署

  1. 生产环境务必使用持久化卷 (-v /persistent/path:/data)。
  2. 多用户环境使用 PostgreSQL 替代 SQLite。
  3. 配置 Webhook 密钥以确保流水线触发的安全性。
  4. 设置分支保护规则,防止直接向 main 分支推送代码。
  5. 使用 REST API 进行编排式管理与集成。

结语

"Harness 工程" 代表了我们思考 AI 辅助软件开发的根本性转变。它不再仅仅是给模型一个提示并寄希望于最好的结果——而是关于设计环境约束反馈循环,从而有系统地引导 AI 智能体走向高质量、生产级的产出。

无论你是在构建能够自主交付全栈应用的多智能体系统,还是在部署自托管的 CI/CD 平台来自动化团队的工作流,其核心准则是通用的:AI 周边的基础设施与 AI 本身同样重要

马很有力,而 Harness 让它变得有用。


参考资料

破除厂商绑定:开源 GraphQL 联邦替代方案 WunderGraph Cosmo 全景解析

· 阅读需 17 分钟
Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡

在上一篇文章 《Apollo Router 由浅入深》 中,我们深入探讨了 Apollo 为微服务架构提供的高性能联邦网关解决方案。然而在实际落地时,许多团队会面临一个严峻的挑战:Apollo 的控制面板(GraphOS / Studio)是闭源的商业 SaaS 产品,存在较高的费用门槛和厂商锁定(Vendor Lock-in)风险

为了解决这个问题,社区与业界孕育了完全开源(Apache 2.0)的替代方案 —— WunderGraph Cosmo。它旨在成为 Apollo Federation 的"Drop-in"级增强替代品。

Apollo Router 由浅入深:从 Federation 到请求生命周期的全链路剖析

· 阅读需 9 分钟
Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡

当你的 GraphQL 服务从一个 monolith 发展到十几个甚至几十个微服务时,如何让客户端只面对一个端点、同时让后端团队各自独立迭代?Apollo 的答案是 Federation(联邦架构) 和一个用 Rust 编写的高性能入口——Apollo Router

本文将带你从最基础的概念一路走到生产级配置,完整覆盖 Apollo Router 的请求生命周期(Request Lifecycle)

图算法全景指南:从基础理论到分布式图数据库实战

· 阅读需 15 分钟
Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡

图(Graph)不仅仅是一种数据结构,更是一种看待世界联系的思维方式。在海量数据的今天,简单的关系型数据库已难以应对复杂的关联查询。

本文旨在重塑你对图算法的认知。我们采用渐进式深度的设计,从最直观的遍历开始,逐步深入到复杂的结构分析,并最终探讨工业级的图存储方案。