Kubernetes 模式
用于可靠部署、管理和调试工作负载的生产级 Kubernetes 模式。
何时激活
- 编写 Kubernetes 清单(Deployment、Service、Ingress、Job)
- 配置资源请求/限制、存活/就绪探针
- 设置 RBAC、命名空间或 ServiceAccount
- 管理 K8s 中的配置和密钥
- 调试 CrashLoopBackOff、OOMKilled、Pending Pod 或镜像拉取错误
- 配置 HPA(水平 Pod 自动扩缩容)或 PodDisruptionBudget
- 审核 K8s YAML 的安全性或正确性
何时使用
与上方的 何时激活 相同。此别名满足仓库技能格式约定。在编写、审核或调试 Kubernetes YAML 和工作负载时使用此技能。
工作原理
此技能提供可直接复制使用的生产级 YAML 模式和按任务组织的 kubectl 调试命令:
- Deployment 模板 — 一个完全配置的生产级
Deployment,包含安全上下文、滚动更新策略、全部三种探针类型、资源限制以及从 ConfigMap/Secret 注入的环境变量。 - 探针 — 启动探针、存活探针和就绪探针的决策表,包含正确的
failureThreshold × periodSeconds计算。 - Service 与 Ingress — ClusterIP、LoadBalancer 和带有 cert-manager 注解的 TLS Ingress 模式。
- ConfigMap 与 Secret —
envFrom、文件挂载和外部密钥的指导。 - 资源管理 — 按工作负载类型(Web API、JVM、Worker、Sidecar)的请求与限制经验法则。
- RBAC — 最小权限的 ServiceAccount → Role → RoleBinding 链。
- HPA 与 PDB — 自动扩缩容和节点排空安全配置。
- Job 与 CronJob — 一次性或定时工作负载模式,带有正确的
restartPolicy。 - kubectl 速查表 — 日志、exec、回滚、端口转发、dry-run 和常见错误诊断命令。
- 反模式与检查清单 — 不该做什么,以及安全、可靠性、可观测性检查清单。
示例
请参见以下部分以获取完整、可运行的示例。快速参考:
| 任务 | 跳转至 |
|---|---|
| 完整的生产级 Deployment YAML | 核心工作负载模式 |
| 探针配置 | 探针 |
| RBAC 最小权限设置 | RBAC |
| 调试 CrashLoopBackOff | kubectl 调试速查表 |
| 自动扩缩容 | HPA |
核心工作负载模式
Deployment — 生产环境模板
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: my-namespace
labels:
app: my-app
version: "1.0.0"
spec:
replicas: 3
selector:
matchLabels:
app: my-app
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 更新期间允许 1 个额外 Pod
maxUnavailable: 0 # 永远不低于期望副本数
template:
metadata:
labels:
app: my-app
version: "1.0.0"
spec:
# Pod 级别的安全上下文
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
# 优雅关闭
terminationGracePeriodSeconds: 30
containers:
- name: my-app
image: ghcr.io/org/my-app:1.0.0 # 永远不要使用 :latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
protocol: TCP
# 资源请求和限制都必需
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
# 容器安全上下文
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
# 探针(参见下方探针章节)
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 0
periodSeconds: 30
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 2
# 来自 ConfigMap 和 Secret 的环境变量
envFrom:
- configMapRef:
name: my-app-config
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-app-secrets
key: db-password
# 当 readOnlyRootFilesystem: true 时需要可写 tmp 目录
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
探针 — 存活、就绪、启动
了解何时使用每种探针至关重要:
| 探针 | 失败时的操作 | 用途 |
|---|---|---|
startupProbe | 杀死容器 | 启动缓慢的应用(JVM、Python) |
livenessProbe | 重启容器 | 死锁 / 挂起进程检测 |
readinessProbe | 从 Service 端点移除 | 临时不可用(数据库重连) |
# 正确的模式:startupProbe 覆盖慢启动,
# 然后 liveness/readiness 接管
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30 # 30 * 5s = 150s 最大启动时间
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 30
failureThreshold: 3 # 3 * 30s = 90s 后重启
readinessProbe:
httpGet:
path: /ready # 独立端点:检查数据库、缓存等
port: 8080
periodSeconds: 10
failureThreshold: 2
# 错误:没有使用 startupProbe 而使用 initialDelaySeconds
# 如果应用需要 60s 启动,应使用 startupProbe
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 60 # 不好:任意等待,存在竞态条件
Service 和 Ingress
Service 类型
# ClusterIP(默认)— 仅内部
apiVersion: v1
kind: Service
metadata:
name: my-app
namespace: my-namespace
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: ClusterIP
# LoadBalancer — 外部流量(云服务商)
spec:
type: LoadBalancer
ports:
- port: 443
targetPort: 8080
带 TLS 的 Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
namespace: my-namespace
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
secretName: my-app-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
ConfigMap 和 Secret
ConfigMap — 非敏感配置
apiVersion: v1
kind: ConfigMap
metadata:
name: my-app-config
namespace: my-namespace
data:
LOG_LEVEL: "info"
APP_ENV: "production"
MAX_CONNECTIONS: "100"
# 对于复杂配置,挂载为文件
app.yaml: |
server:
port: 8080
timeout: 30s
# 将 ConfigMap 挂载为文件
volumes:
- name: config
configMap:
name: my-app-config
items:
- key: app.yaml
path: app.yaml
volumeMounts:
- name: config
mountPath: /etc/app
readOnly: true
Secret — 敏感数据
# 从字面值创建 Secret(CLI,然后存储在 Vault/SOPS 中)
kubectl create secret generic my-app-secrets \
--from-literal=db-password='s3cr3t' \
--namespace=my-namespace \
--dry-run=client -o yaml | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: my-app-secrets
namespace: my-namespace
type: Opaque
# 值是 base64 编码的(未加密——请使用 Sealed Secrets 或 ESO 进行真正的加密)
data:
db-password: czNjcjN0 # base64 of 's3cr3t'
重要: 原始 Kubernetes Secret 仅经过 base64 编码,除非集群配置了加密,否则不会静态加密。对于生产环境,请使用 Sealed Secrets 或 External Secrets Operator。
资源请求和限制
resources:
requests: # 调度器使用它来放置 Pod
cpu: "100m" # 100 毫核 = 0.1 CPU
memory: "128Mi"
limits: # 容器超过此限制将被杀死或限流
cpu: "500m"
memory: "256Mi"
经验法则:
| 工作负载类型 | CPU 请求 | 内存请求 | 说明 |
|---|---|---|---|
| Web API | 100–250m | 128–256Mi | 设置限制为请求的 2-4 倍 |
| Worker/consumer | 250–500m | 256–512Mi | 为了可预测性,内存限制 = 请求 |
| JVM 应用 | 500m–1 | 512Mi–2Gi | 在 -Xmx 之上留出 JVM 开销空间 |
| Sidecar | 10–50m | 32–64Mi | 保持最小 |
# 错误:没有请求或限制 — 不可预测的调度、OOM 驱逐
containers:
- name: app
image: myapp:latest
# 缺少 resources: {} — 这在生产环境中很危险
# 错误:有限制但没有请求 — 请求默认为限制,过度预留容量
resources:
limits:
cpu: "2"
memory: "1Gi"
# 缺少 requests — 将默认使用限制值
RBAC — 角色和 ServiceAccount
最小权限原则
根据应用是否调用 Kubernetes API 采用两种模式:
模式 A — 应用不需要 Kubernetes API(大多数应用)
在 ServiceAccount 上禁用令牌自动挂载。不需要 Role/RoleBinding。
# 禁用令牌的 ServiceAccount — 最安全的默认设置
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: my-namespace
automountServiceAccountToken: false # 不会向 Pod 注入 K8s API 令牌
# 在 Deployment 中引用 — 无令牌,无 API 访问
spec:
template:
spec:
serviceAccountName: my-app-sa
automountServiceAccountToken: false # 双重保障:也设置在 Pod 级别
模式 B — 应用需要 Kubernetes API(Operator、控制器、配置监控器)
启用令牌并仅授予实际需要的权限。
# 1. ServiceAccount — 为此 SA 启用令牌
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: my-namespace
automountServiceAccountToken: true # 需要令牌:应用调用 K8s API
# 2. Role — 仅授予应用所需权限(命名空间范围)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: my-app-role
namespace: my-namespace
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"] # 只读,特定资源
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["my-app-secrets"] # 按名称限制到特定 Secret
verbs: ["get"]
# 3. 将 Role 绑定到 ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: my-app-rolebinding
namespace: my-namespace
subjects:
- kind: ServiceAccount
name: my-app-sa
namespace: my-namespace
roleRef:
kind: Role
apiGroup: rbac.authorization.k8s.io
name: my-app-role
# 4. 在 Deployment 中引用 SA
spec:
template:
spec:
serviceAccountName: my-app-sa
# automountServiceAccountToken 默认为 SA 的 true — 令牌已注入
水平 Pod 自动扩缩容 (HPA)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
namespace: my-namespace
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 2 # 至少始终保持 2 个副本以实现高可用
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # 当平均 CPU > 70% 时扩容
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
HPA 要求在所有容器上设置
resources.requests——它计算利用率为current / request。
PodDisruptionBudget (PDB)
防止在节点排空或滚动更新期间过多的 Pod 中断:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
namespace: my-namespace
spec:
minAvailable: 2 # 或使用 maxUnavailable: 1
selector:
matchLabels:
app: my-app
命名空间和多租户
# 创建命名空间并设置资源配额
kubectl create namespace my-namespace
# 应用 ResourceQuota 以限制命名空间消耗
kubectl apply -f - <<EOF
apiVersion: v1
kind: ResourceQuota
metadata:
name: my-namespace-quota
namespace: my-namespace
spec:
hard:
requests.cpu: "4"
requests.memory: 4Gi
limits.cpu: "8"
limits.memory: 8Gi
pods: "20"
EOF
Job 和 CronJob
# 一次性 Job(数据库迁移、数据处理)
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
namespace: my-namespace
spec:
backoffLimit: 3 # 失败后最多重试 3 次
ttlSecondsAfterFinished: 3600 # 1 小时后自动删除
template:
spec:
restartPolicy: OnFailure # 对于 Job 使用 Never(不是 Always)
containers:
- name: migrate
image: ghcr.io/org/my-app:1.0.0
command: ["python", "manage.py", "migrate"]
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
# 定时 CronJob(每 6 小时执行)
apiVersion: batch/v1
kind: CronJob
metadata:
name: data-cleanup
namespace: my-namespace
spec:
schedule: "0 */6 * * *" # 每 6 小时
concurrencyPolicy: Forbid # 防止重叠执行
startingDeadlineSeconds: 600 # 如果错过调度时间,10 分钟内仍可启动
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: cleanup
image: ghcr.io/org/data-cleanup:1.0.0
args: ["--retention", "30d"]
resources:
requests:
cpu: "50m"
memory: "64Mi"
kubectl 调试速查表
# --- 日志 ---
# 实时查看 Pod 日志
kubectl logs -f deployment/my-app -n my-namespace
# 查看先前的容器(CrashLoopBackOff 时)
kubectl logs -p my-pod -n my-namespace
# 所有容器日志,按标签过滤
kubectl logs -l app=my-app -n my-namespace --tail=100
# --- 执行命令 ---
# 在 Pod 中运行交互式 Shell
kubectl exec -it deployment/my-app -n my-namespace -- sh
# 测试数据库连接
kubectl exec deployment/my-app -n my-namespace -- nc -zv db-service 5432
# --- 调试没有 Shell 的容器 ---
# 使用临时侧车(需要 ephemeral containers feature gate)
kubectl alpha debug -it my-pod --image=busybox:1.28 --target=my-app
# --- 端口转发 ---
# 将本地 8080 转发到 Pod 8080
kubectl port-forward deployment/my-app 8080:8080 -n my-namespace
# --- 回滚 ---
# 查看部署历史
kubectl rollout history deployment/my-app -n my-namespace
# 回滚到上一个版本
kubectl rollout undo deployment/my-app -n my-namespace
# 回滚到特定版本
kubectl rollout undo deployment/my-app --to-revision=3 -n my-namespace
# --- Dry-run / 验证 ---
# 客户端验证 + 打印 YAML(不应用)
kubectl apply -f manifest.yaml --dry-run=client -o yaml
# 服务端验证(由准入控制器评估)
kubectl apply -f manifest.yaml --dry-run=server
# --- 常见诊断命令 ---
kubectl describe pod my-pod -n my-namespace # 查看事件和条件
kubectl get events -n my-namespace --sort-by='.lastTimestamp' # 按时间排序的事件
kubectl top pod -n my-namespace # 当前资源使用率
kubectl get endpoints my-app-service -n my-namespace # Service 端点健康检查
# --- 调试特定错误 ---
# CrashLoopBackOff — 查看日志和事件
kubectl logs -p my-pod -n my-namespace
kubectl describe pod my-pod -n my-namespace | grep -A 20 Events
# ImagePullBackOff / ErrImagePull
kubectl describe pod my-pod -n my-namespace | grep -i image
kubectl get events -n my-namespace | grep -i pull
# 挂起 Pod(节点资源不足)
kubectl describe pod my-pod -n my-namespace | grep -i events
kubectl describe node my-node | grep -A 10 -i "Non-terminated"
# OOMKilled
kubectl logs -p my-pod -n my-namespace | grep -i "out of memory"
kubectl describe pod my-pod -n my-namespace | grep -i "OOMKilled"
# 修复:增加 resources.limits.memory 或配置垂直 Pod 自动扩缩容
# 网络连接问题
kubectl exec deployment/my-app -n my-namespace -- nc -zv service-name 80
kubectl describe networkpolicy -n my-namespace # 如果有网络策略
# --- 调试 YAML 与 Linter ---
kubectl explain deployment.spec.template.spec.containers.resources
# 使用 kubeval 或 kubeconform 验证清单:
# curl -s https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master-standalone/deployment-v1.json | ...
反模式和安全检查清单
反模式(避免这些)
| 反模式 | 原因 | 解决方案 |
|---|---|---|
使用 :latest 标签 | 不可重现、不可回滚 | 使用语义版本标签或提交哈希 |
设置 privileged: true | 完全容器逃逸 | 删除或使用特定 capabilities |
| 容器内以 root 运行 | 权限提升风险 | 设置 runAsNonRoot: true |
| 省略资源限制 | OOM 杀死、不可预测的节点 | 始终设置 resources.limits |
| 省略资源请求 | 调度器可能过度订阅节点 | 始终设置 resources.requests |
| 在 livenessProbe 中访问外部服务 | 级联故障 | 使用不同的端点或无探针 |
| 共享进程命名空间 | 容器隔离被破坏 | 仅对调试容器启用 |
| 在所有 Pod 上挂载 ServiceAccount 令牌 | 攻击面增加 | 禁用不需要 API 的 Pod 的令牌 |
对 Job 使用 restartPolicy: Always | Job 永远不会完成 | 对 Jobs 使用 OnFailure 或 Never |
initialDelaySeconds 而非 startupProbe | 竞态条件、部署缓慢 | 使用 startupProbe 处理慢启动 |
| 为存储依赖使用 emptyDir | Pod 重启会丢失数据 | 使用 PersistentVolumeClaim |
安全检查清单
- 为非 K8s API 工作负载禁用 ServiceAccount 令牌(
automountServiceAccountToken: false) - 使用具名 ServiceAccount(避免
defaultSA,除非必要时) - 限制容器 capabilities(至少
drop: ["ALL"]) - 设置
runAsNonRoot: true和readOnlyRootFilesystem: true - 将机密存储在 External Secrets Operator 或 Sealed Secrets 中,而非原始 Secret
- 对 Service 端点使用
publishNotReadyAddresses: false(默认) - 对生产环境实施网络策略
- 设置 PodDisruptionBudget 以确保高可用
- 配置 Pod 反亲和性以分散副本
可靠性检查清单
- 所有 Pod 上都设置了资源
requests和limits - 使用了正确的探针(startupProbe、livenessProbe、readinessProbe)
- 为 HPA 设置了
minReplicas: 2或更高 - PodDisruptionBudget 允许至少
minAvailable: 2或maxUnavailable: 1 - 配置了优雅关闭(
terminationGracePeriodSeconds≥ 30) - 使用了预停止钩子以优雅地排空连接
- 测试了回滚(
kubectl rollout undo) - 就绪探针不依赖外部服务(没有级联故障)
可观测性检查清单
- 应用程序以结构化格式(JSON)记录到 stdout/stderr
- Prometheus 指标端点和 ServiceMonitor 已配置(如果使用 Prometheus Operator)
- 分布式追踪(OpenTelemetry)已初始化
- 配置了自定义资源指标并用于 HPA(不仅仅是 CPU/内存)
- 设置了适当的日志级别,可在不重启的情况下更改
- Pod 注解中包含了相关元数据(版本、提交哈希)