文章

Kubernetes 工作负载的生产基线

资源请求、探针、中断预算、优雅关闭和 Pod 安全:生产环境的 Deployment 在第一次事故之前就应具备的配置。

示例均为通用的说明性内容,并非取自任何客户环境:请根据您自己的系统调整阈值和配置。

为什么需要基线

Kubernetes 几乎不需要任何配置就能运行几乎任何东西,而这正是问题所在:默认值是为上手而设计的,而不是为了稳定运行。下面这些配置在第一天加上成本很低,若到故障时才发现则代价高昂。它们适用于无状态 Deployment;有状态工作负载需要更多配置。

资源请求与限制

调度器根据 requests 放置 Pod,Horizontal Pod Autoscaler 也以 requests 为基准计算利用率。没有 requests 的 Pod 会被盲目调度,也无法按 CPU 自动扩缩容。

内存不可压缩:超出内存限制的容器会被终止。将内存限制设为等于或接近 request,避免 Pod 被调度到它无法保有的内存上。CPU 则不同——超过 CPU 限制的容器会被限流,表现为延迟升高。许多团队为对延迟敏感的服务设置 CPU requests 而不设 CPU 限制;无论怎么选,都要度量限流情况。

三种探针,三种职责

  • 就绪探针(readiness)决定 Pod 是否接收流量。小心共享依赖:如果所有 Pod 同时就绪失败,服务将没有任何可用端点。
  • 存活探针(liveness)决定是否重启容器。只检查进程本身:如果它检查数据库,数据库一出故障就会同时重启所有 Pod。
  • 启动探针(startup)在慢启动应用启动期间暂缓其他探针,而不是靠拉长存活探针的延迟。

应对节点排空和可用区故障

节点升级和自动扩缩容随时都在驱逐 Pod。PodDisruptionBudget 限制一次自愿中断最多能停掉多少副本,拓扑分布约束(topology spread constraints)把副本分散到不同可用区,这样单个可用区故障不会拖垮整个服务。

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api
spec:
  maxUnavailable: 1
  selector:
    matchLabels: { app: api }

优雅关闭

Pod 被删除时,它从服务端点中移除,与其容器的关闭(先执行 preStop 钩子,再发送 SIGTERM)是同时开始的。立即退出的应用会丢失仍被路由给它的请求。添加一个短暂的 preStop 等待让负载均衡器跟上,收到 SIGTERM 后先完成进行中的工作,并让 terminationGracePeriodSeconds 大于等待时间与排空时间之和。

安全上下文与镜像

以非 root 用户运行,移除所有 Linux capabilities,禁止权限提升,并以只读方式挂载根文件系统;restricted 级别的 Pod 安全标准会在命名空间级别强制执行其中大部分要求。通过 digest 而不是可变标签引用镜像,确保运行的正是测试过的那一个。

整合起来

一个应用了上述基线的 Deployment。数值仅为示意:请根据实测用量而不是这个示例来设定 requests。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels: { app: api }
  template:
    metadata:
      labels: { app: api }
    spec:
      terminationGracePeriodSeconds: 45
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels: { app: api }
      securityContext:
        runAsNonRoot: true
        seccompProfile: { type: RuntimeDefault }
      containers:
        - name: api
          image: registry.example.com/api@sha256:<digest>
          resources:
            requests: { cpu: 250m, memory: 256Mi }
            limits: { memory: 256Mi }
          readinessProbe:
            httpGet: { path: /ready, port: 8080 }
            periodSeconds: 5
          livenessProbe:
            httpGet: { path: /live, port: 8080 }
            periodSeconds: 10
            failureThreshold: 3
          startupProbe:
            httpGet: { path: /live, port: 8080 }
            periodSeconds: 5
            failureThreshold: 30
          lifecycle:
            preStop:
              sleep: { seconds: 5 }   # native sleep action, Kubernetes 1.30+
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities: { drop: ["ALL"] }
联系

想听听第二种意见?

发送一段对您系统的简短描述。资深工程师会在一个工作日内书面答复。

聊聊您的基础设施