本篇定位
前七篇覆盖了 K8s 日常操作的核心知识:架构,Pod,工作负载,Service,Ingress,配置,存储.
本篇是一个进阶索引: 快速介绍Scheduler调度策略,弹性伸缩,RBAC,Helm,Kustomize,CRD/Operator等进阶主题.
目标不是精讲每个领域,而是建立"知道有什么,什么时候需要深入"的全景地图.
Scheduler的调度策略
默认情况下,Scheduler 根据资源剩余量自动选择节点.但生产中常需要更精细的控制:
- "GPU Pod 只跑在 GPU 节点"
- "两个 Pod 不要放同一节点"
- "维护中的节点不接受新 Pod"
- ......
nodeSelector - 最简单的节点选择
spec: nodeSelector: gpu: "true"
|
简单但不灵活: 只支持精确匹配,不能表达"尽量放到 zone-a,放不下再去 zone-b".
Node Affinity - 灵活的节点亲和
spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: topology.kubernetes.io/zone operator: In values: ["us-east-1a", "us-east-1b"]
preferredDuringSchedulingIgnoredDuringExecution: - weight: 80 preference: matchExpressions: - key: node-type operator: In values: ["high-memory"]
|
| 类型 |
含义 |
效果 |
required...(硬亲和) |
必须满足 |
不满足 → Pod Pending |
preferred...(软亲和) |
尽量满足 |
不满足 → 还是会调度,只是优先级降低 |
Pod Affinity / Anti-Affinity
不是"Pod 要去哪个节点",而是"Pod 之间的关系":
spec: affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: app: cache topologyKey: kubernetes.io/hostname
podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app: web topologyKey: kubernetes.io/hostname
|
topologyKey 是什么
topologyKey 定义"同一/不同"的粒度.
kubernetes.io/hostname 表示节点级别(同/不同节点)
topology.kubernetes.io/zone 表示可用区级别(同/不同 AZ)
反亲和常用于高可用:同一个服务的 Pod 分散到不同节点/AZ,避免单点故障.
Taints 与 Tolerations
Affinity 是"Pod 主动选节点",Taint 是"节点主动排斥 Pod".
$ kubectl taint nodes node-gpu dedicated=gpu:NoSchedule
|
spec: tolerations: - key: dedicated operator: Equal value: gpu effect: NoSchedule
|
| Taint Effect |
含义 |
NoSchedule |
新 Pod 不调度到该节点(已有 Pod 不受影响) |
PreferNoSchedule |
尽量不调度(软性) |
NoExecute |
新 Pod 不调度 + 驱逐已有不容忍的 Pod |
典型组合: Taint: dedicated=gpu:NoSchedule + Toleration + nodeSelector: gpu=true → 只有 GPU 工作负载能跑在 GPU 节点,GPU 节点不会被普通 Pod 占用
Taint: node.kubernetes.io/not-ready:NoExecute(K8s 自动添加) → 节点 NotReady 时自动驱逐上面的 Pod(容错机制)
|
弹性伸缩 - HPA / VPA / CA
HPA - 水平 Pod 自动伸缩
HPA(Horizontal Pod Autoscaler)根据指标自动调整 Deployment/StatefulSet 的 replicas 数量.
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: api-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: api-server minReplicas: 3 maxReplicas: 50 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80
|
HPA 控制循环(默认每 15 秒一次): 1. 从 Metrics Server 获取当前所有 Pod 的 CPU/内存使用量 2. 计算平均利用率 3. 对比目标值(如 70%) 4. 期望副本数 = 当前副本数 × (当前指标 / 目标指标) 例:当前 3 Pod,平均 CPU 90%,目标 70% 期望 = 3 × (90/70) = 3.86 → 向上取整 = 4 5. 更新 Deployment 的 replicas 6. 冷却期(默认扩容 0s,缩容 5min)避免频繁伸缩
|
HPA 前提:必须设置 resources.requests
HPA 按"实际使用 / requests"计算利用率.
如果 Pod 没有设置 resources.requests.cpu,HPA 无法计算利用率,不会触发伸缩.同时需要集群安装 Metrics Server(大多数托管集群已自带).
VPA - 垂直 Pod 自动伸缩
VPA 不改副本数,而是自动调整单个 Pod 的 CPU/内存 requests 和 limits.
与 HPA 的区别:
- HPA:流量大了 → 加更多 Pod(水平扩展)
- VPA:单个 Pod 需要更多资源 → 加大 Pod 的 CPU/内存配额(垂直扩展)
适用场景:
- 不确定该给 Pod 分配多少资源(VPA 自动推荐)
- 无法水平扩展的应用(如单实例数据库)
- 优化资源利用率(避免过度分配)
VPA 的限制
- VPA 调整 requests 时需要重建 Pod(当前不支持热更新资源配额)
- HPA 和 VPA 不应该同时基于 CPU 指标(会冲突)
Cluster Autoscaler - 节点级伸缩
HPA 管 Pod 数量,Cluster Autoscaler(CA)管节点数量--两者配合实现全自动弹性:
- HPA 增加 Pod 副本数 → 节点资源不足 → Pod 卡在 Pending
- CA 检测到 Pending Pod → 向云平台申请新节点 → Pod 调度成功
- 流量回落 → HPA 缩减 Pod → 节点上 Pod 过少 → CA 回收空闲节点降成本
RBAC - 权限控制
RBAC(Role-Based Access Control)回答"谁能对什么资源做什么操作".
四个核心对象
┌────────────────────────────────────────────────────────────┐ │ Role / ClusterRole 定义"能做什么" │ │ - resources: [pods, services, deployments] │ │ - verbs: [get, list, create, update, delete] │ ├────────────────────────────────────────────────────────────┤ │ RoleBinding / ClusterRoleBinding 把"能做什么"绑给"谁" │ │ - subjects: [User / Group / ServiceAccount] │ │ - roleRef: 引用哪个 Role/ClusterRole │ └────────────────────────────────────────────────────────────┘
作用域: Role + RoleBinding → namespace 级别(只管这个 ns 内的资源) ClusterRole + ClusterRoleBinding → 集群级别(所有 ns + 集群资源)
|
apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: pod-reader namespace: default rules: - apiGroups: [""] resources: ["pods", "services"] verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: read-pods namespace: default subjects: - kind: ServiceAccount name: monitoring-sa namespace: default roleRef: kind: Role name: pod-reader apiGroup: rbac.authorization.k8s.io
|
最小权限原则
生产环境应为每个应用创建独立的 ServiceAccount,只授予它需要的最小权限.
默认的 default ServiceAccount 权限太少(好事)或被管理员额外赋权(危险).
永远不要给应用 cluster-admin 权限.
Helm - K8s 的包管理器
核心概念
| 概念 |
类比 |
含义 |
| Chart |
npm package / apt .deb |
一组模板化的 K8s YAML 文件 + 默认值 |
| Release |
npm install 的实例 |
Chart 安装到集群后的一个运行实例 |
| Repository |
npm registry |
存放 Chart 的远程仓库 |
| values.yaml |
配置文件 |
用户传入的变量值,覆盖 Chart 默认值 |
常用命令
$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx $ helm repo update
$ helm search repo ingress-nginx
$ helm install my-nginx ingress-nginx/ingress-nginx \ --namespace ingress \ --create-namespace \ --set controller.replicaCount=2
$ helm list -A
$ helm upgrade my-nginx ingress-nginx/ingress-nginx \ --set controller.replicaCount=3
$ helm rollback my-nginx 1
$ helm uninstall my-nginx -n ingress
|
Chart 目录结构
my-chart/ ├── Chart.yaml # Chart 元数据(名称,版本,描述) ├── values.yaml # 默认配置值 ├── templates/ # 模板文件目录 │ ├── deployment.yaml # {{ .Values.replicas }} 等模板变量 │ ├── service.yaml │ ├── ingress.yaml │ ├── configmap.yaml │ ├── _helpers.tpl # 模板辅助函数 │ └── NOTES.txt # 安装后的提示信息 └── charts/ # 依赖的子 Chart
|
Kustomize - 无模板的配置定制
Helm 是模板引擎(Go template),Kustomize 是另一种思路:不修改原始 YAML,而是在上面叠加补丁.
Helm:模板化 deployment.yaml.tpl → 填入 values → 生成最终 YAML 优点:灵活,一套模板适配多种场景 缺点:模板语法复杂,调试困难
Kustomize:叠加补丁 base/deployment.yaml(原始 YAML,可直接 kubectl apply) overlays/prod/patch.yaml(只写差异部分) → kustomize build overlays/prod → 合并输出最终 YAML 优点:原始 YAML 保持可读,补丁直观 缺点:复杂定制时补丁会很多
|
目录结构: base/ ├── kustomization.yaml ├── deployment.yaml # 原始 YAML(replicas: 1) └── service.yaml
overlays/ ├── dev/ │ ├── kustomization.yaml # resources: [../../base] patches: [...] │ └── replicas-patch.yaml # replicas: 1 └── prod/ ├── kustomization.yaml └── replicas-patch.yaml # replicas: 5
|
$ kubectl apply -k overlays/prod/
$ kubectl kustomize overlays/prod/
|
Helm vs Kustomize:怎么选
用 Helm:安装第三方应用(ingress-nginx,cert-manager,Prometheus)--它们只提供 Helm Chart;或者你的应用需要高度参数化的模板
用 Kustomize:管理自己团队的应用部署--原始 YAML 保持简单,通过 overlays 区分环境.
两者也可以结合:Helm 生成基础 YAML,Kustomize 在上面打补丁.
CRD + Operator - 扩展 K8s 的能力
CRD - 自定义资源
K8s 内置了 Pod,Service,Deployment 等资源类型.CRD(Custom Resource Definition)允许你定义自己的资源类型--扩展 K8s API.
apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: mysqlclusters.database.example.com spec: group: database.example.com versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: replicas: type: integer version: type: string storage: type: string scope: Namespaced names: plural: mysqlclusters singular: mysqlcluster kind: MySQLCluster
|
$ kubectl apply -f mysqlcluster.yaml $ kubectl get mysqlclusters NAME REPLICAS VERSION STORAGE prod-mysql 3 8.0 500Gi
|
apiVersion: database.example.com/v1 kind: MySQLCluster metadata: name: prod-mysql spec: replicas: 3 version: "8.0" storage: "500Gi"
|
Operator - CRD 的 Controller
CRD 只定义了数据结构--"K8s API 能存储 MySQLCluster 对象了".
但光存储不够,还需要有人执行:看到 replicas: 3 就真的创建 3 个 MySQL Pod,配置主从复制,处理故障切换.这个"执行者"就是 Operator.
Operator = CRD + 自定义 Controller
CRD 定义了"期望状态长什么样"(如 MySQLCluster spec) Operator(Controller)持续将实际状态调整为期望状态
MySQL Operator 的控制循环: watch MySQLCluster 对象变化 → replicas 从 1 变为 3? → 创建新的 StatefulSet Pod → 配置 MySQL 复制拓扑(主从) → 等 Pod Ready → 初始化数据同步 → 检测到 mysql-0 不健康? → 触发 failover:提升 mysql-1 为新主 → 更新 Service 端点 → 发送告警
本质:把 DBA 的运维经验编码成程序
|
CRD = 在 K8s 菜单上添加了一道新菜.
Operator = 知道怎么做这道菜的厨师.你点了一份"三副本 MySQL 集群",厨师(Operator)负责采购食材(创建 Pod/PVC),烹饪(配置复制),处理突发(节点挂了就切换主从).
常见 Operator 生态
| Operator |
管理对象 |
核心能力 |
| Prometheus Operator |
Prometheus / AlertManager |
自动发现监控目标,管理告警规则 |
| cert-manager |
Certificate / Issuer |
自动签发和续签 TLS 证书 |
| Strimzi |
Kafka Cluster |
Kafka 集群的声明式管理 |
| CloudNativePG |
PostgreSQL Cluster |
HA 切换,备份恢复,滚动升级 |
| Redis Operator |
Redis Cluster/Sentinel |
Redis 集群创建,扩缩,故障切换 |
| External Secrets Operator |
ExternalSecret |
从 Vault/AWS SM 同步密钥到 K8s Secret |
Operator 开发框架
如果你想开发自己的 Operator:Go 语言用 kubebuilder或 operator-sdk;它们自动生成脚手架代码,你只需要实现 Reconcile 函数(控制循环的核心逻辑).这是 Go 后端开发者深入 K8s 生态的一个自然方向.
NetworkPolicy - 网络层访问控制
默认情况下,集群内所有 Pod 可以互相通信(扁平网络).NetworkPolicy 允许你限制 Pod 的入站/出站流量.
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: api-policy namespace: production spec: podSelector: matchLabels: app: api policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: frontend - namespaceSelector: matchLabels: env: production ports: - port: 8080 protocol: TCP egress: - to: - podSelector: matchLabels: app: database ports: - port: 5432
|
NetworkPolicy 需要 CNI 支持
NetworkPolicy 对象只是声明: 实际执行依赖 CNI 插件.
Calico,Cilium,Weave 支持 NetworkPolicy;Flannel 默认不支持.安装 Flannel 的集群创建 NetworkPolicy 不会报错,但也不会生效.
资源管理进阶
LimitRange - namespace 级别默认值
apiVersion: v1 kind: LimitRange metadata: name: default-limits namespace: dev spec: limits: - default: cpu: 500m memory: 512Mi defaultRequest: cpu: 100m memory: 128Mi type: Container
|
ResourceQuota - namespace 资源配额
apiVersion: v1 kind: ResourceQuota metadata: name: dev-quota namespace: dev spec: hard: requests.cpu: "10" requests.memory: 20Gi limits.cpu: "20" limits.memory: 40Gi pods: "50" persistentvolumeclaims: "20"
|
配额的意义:多团队共享集群时,防止某个团队/namespace 把资源吃光.
可观测性快览
| 维度 |
工具 |
用途 |
| 指标(Metrics) |
Prometheus + Grafana |
CPU/内存/请求量/延迟 → 仪表盘 + 告警 |
| 日志(Logs) |
Fluentd/Filebeat + ES + Kibana |
集中式日志收集和查询 |
| 追踪(Traces) |
Jaeger / Tempo + OpenTelemetry |
分布式调用链追踪 |
| 事件(Events) |
kubectl get events / kube-state-metrics |
集群内资源状态变化 |
可观测性是一个独立的大主题,详见本仓库的"可观测性"分类.这里只需知道:K8s 原生提供 Events 和基础的 kubectl top(需要 Metrics Server),完整的监控体系需要额外部署 Prometheus 栈.
小结
快速回顾
- 调度策略:nodeSelector(简单)→ Node Affinity(灵活硬/软约束)→ Pod Anti-Affinity(分散部署)→ Taints/Tolerations(节点排斥)
- 弹性伸缩三层:HPA(Pod 数量)→ VPA(Pod 资源)→ Cluster Autoscaler(节点数量)
- RBAC:Role/ClusterRole 定义"能做什么",RoleBinding/ClusterRoleBinding 绑给"谁";最小权限原则
- Helm:K8s 的包管理器,Chart = 模板化 YAML 包;适合安装第三方应用和高度参数化场景
- Kustomize:无模板方式定制 YAML,base + overlays 分环境管理;适合团队自己的应用
- CRD + Operator:扩展 K8s API + 自定义 Controller;把运维逻辑编码成程序(如数据库自动主从切换)
- NetworkPolicy:Pod 级别的网络访问控制(谁能访问我,我能访问谁);需要 CNI 支持
- 资源管理:LimitRange(namespace 默认值)+ ResourceQuota(namespace 配额上限)
动手练习
- 给一个节点打 Taint(
env=staging:NoSchedule),创建一个不带 Toleration 的 Pod,验证它不会调度到该节点.然后加上 Toleration,验证调度成功.
- 创建一个 Deployment(replicas=2)+ Pod Anti-Affinity(hostname 级别),验证两个 Pod 是否分布在不同节点.
- 部署 Metrics Server(k3d 通常自带),创建一个 HPA(目标 CPU 50%).用 stress 工具模拟 CPU 压力,观察 Pod 是否自动扩容.
- 用 Helm 安装一个应用(如 ingress-nginx),熟悉
helm install / upgrade / rollback / uninstall 流程.
- 创建一个简单的 CRD(如 Greeting 对象,spec 只有 message 字段),用 kubectl apply 创建实例,用 kubectl get 查看.
- 创建一个 NetworkPolicy:只允许带
role=frontend 标签的 Pod 访问带 role=api 标签的 Pod 的 8080 端口.用 curl 验证策略是否生效.