在多家团队共用一套Knative集群的日常场景中,一个非常现实的问题摆在我们面前:某个团队的应用突然被大量调用,容器实例疯狂扩容,结果整个集群的CPU和内存资源被瞬间吃光,其他团队的正常服务也跟着一起罢工。这种"一个应用拖垮全家"的情况,在共享集群中几乎是每个运维团队都经历过的噩梦。要解决这个问题,核心思路其实很简单——给每个团队划一块"田",田的大小有限,种多少庄稼、用多少水肥都有明确上限,谁也不能越界。

一、问题的根源:为什么共享集群容易出乱子

在多团队共享Knative集群的架构中,资源争抢之所以频繁发生,根本原因在于Knative的设计哲学是"按需弹性伸缩"。当某个Service的流量暴涨时,Knative会自动创建更多的Pod来应对,但如果没有限制,这些Pod可以一直扩下去,直到集群没有剩余资源为止。

举一个真实场景:假设集群中有三个团队A、B、C,各自部署了若干Knative Service。团队A的某个营销活动导致其Service的QPS从10飙升到5000,Knative迅速扩容了上百个Pod,每个Pod占用500m CPU和1Gi内存,瞬间消耗了集群大部分资源。此时团队B和C的Service因为无法获得资源而进入Pending状态,用户请求全部超时。

要解决这类问题,我们需要在Knative层面引入资源治理机制。Knative本身基于Kubernetes,因此可以直接利用Kubernetes的ResourceQuota和LimitRange资源对象来管理命名空间级别的资源使用。

二、理解两个核心概念

2.1 ResourceQuota:命名空间的"资源总账本"

ResourceQuota的作用是给整个命名空间设置资源使用的上限。它就像给一个团队发了一张"信用卡",额度固定,不能透支。一旦命名空间内的资源总量触及上限,新的Pod就无法被创建,Knative的扩容也会受到影响。

举个例子,如果给命名空间team-a设置了CPU配额为8、内存配额为32Gi,那么这个命名空间下所有Pod的CPU请求总和不能超过8核,内存请求总和不能超过32Gi。

2.2 LimitRange:每个容器的"默认规矩"

LimitRange的作用更细粒度。它做两件事:一是给没有设置资源请求的Pod设置默认值,二是给每个Pod设置资源上限。如果说ResourceQuota是管"总量"的,那LimitRange就是管"单个Pod能占多少"的。

比如我们可以设置LimitRange,让每个容器默认获得100m CPU和128Mi内存,同时最多只能使用500m CPU和512Mi内存。这样即使某个团队没有主动配置资源,也不会出现一个Pod就把整个节点吃光的情况。

三、Knative集群中的资源管理特殊之处

Knative与传统Kubernetes有一个关键区别:Knative Service通过Controller动态创建和删除Revision,每个Revision背后是一组Pod。这意味着如果我们对命名空间施加了ResourceQuota限制,当某个Revision需要扩容时,如果配额已经用完,扩容就会失败。

更具体地说,Knative的自动伸缩是通过修改Deployment的replicas来实现的。当Knative Serving Controller检测到流量增加时,它会把Deployment的副本数调大。如果命名空间的CPU配额已经被占满,新创建的Pod会因为资源不足而无法调度,导致Service的扩容中断。

因此,在设计ResourceQuota时,我们需要充分考虑Knative的弹性伸缩需求,不能把配额设得太紧,否则会影响服务的正常扩缩能力。

四、实践:命名空间级别的ResourceQuota设计

4.1 技术栈说明

本节及后续所有示例统一使用以下技术栈:Kubernetes v1.28+ 结合 Knative Serving v1.12+,通过kubectl直接部署配置,配合Prometheus进行监控告警。

4.2 创建团队命名空间的ResourceQuota

以下示例展示了如何为三个不同团队分别设置差异化的资源配额:

---
# 文件:resource-quota.yaml
# 功能:为多团队共享Knative集群设置命名空间级别的资源配额
# 技术栈:Kubernetes v1.28+ / Knative Serving v1.12+

apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a-quota
  namespace: team-a
  labels:
    team: team-a
    managed-by: platform-team
spec:
  # CPU配额:总共8核,考虑到Knative弹性伸缩的突发需求,留有一定余量
  requests.cpu: "8"
  limits.cpu: "16"
  # 内存配额:总共32Gi,根据业务类型调整
  requests.memory: "32Gi"
  limits.memory: "64Gi"
  # Pod数量限制:Knative每个Revision至少1个Pod,加上滚动更新,限制合理数量
  pods: "50"
  # 配置Map和Secret数量限制,防止资源对象爆炸
  configmaps: "50"
  secrets: "50"
  # 容器数量限制
  count/deployments.apps: "20"
  count/statefulsets.apps: "10"
---
apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-b-quota
  namespace: team-b
  labels:
    team: team-b
    managed-by: platform-team
spec:
  # 团队B的业务比较稳定,不需要太高的弹性
  requests.cpu: "4"
  limits.cpu: "8"
  requests.memory: "16Gi"
  limits.memory: "32Gi"
  pods: "30"
  configmaps: "30"
  secrets: "30"
  count/deployments.apps: "15"
  count/statefulsets.apps: "5"
---
apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-c-quota
  namespace: team-c
  labels:
    team: team-c
    managed-by: platform-team
spec:
  # 团队C是实验性业务,资源给得最紧,防止误操作影响全局
  requests.cpu: "2"
  limits.cpu: "4"
  requests.memory: "8Gi"
  limits.memory: "16Gi"
  pods: "15"
  configmaps: "20"
  secrets: "20"
  count/deployments.apps: "10"
  count/statefulsets.apps: "5"

4.3 配额设计中的Knative特殊考量

在Knative场景中,我们需要额外考虑一个重要因素:每个Knative Service会创建多个Revision,每个Revision对应一个Deployment。当Service更新时,新旧Revision会同时存在一段时间。

假设一个Knative Service当前运行10个Pod,更新后新Revision也需要扩容到10个Pod,在过渡期间,总共需要20个Pod的资源。因此,ResourceQuota中的pods限制需要留出至少1.5倍到2倍的余量。

以下是计算Pod配额余量的参考脚本:

#!/bin/bash
# 脚本:calculate-pod-quota.sh
# 功能:根据团队业务规模计算合理的Pod配额
# 使用方法:bash calculate-pod-quota.sh <团队名称> <Service数量> <每个Service最大副本数>
# 技术栈:Kubernetes v1.28+ / Knative Serving v1.12+

TEAM="$1"
SERVICE_COUNT="$2"
MAX_REPLICAS_PER_SERVICE="$3"

# 计算当前最大Pod需求
CURRENT_MAX=$((SERVICE_COUNT * MAX_REPLICAS_PER_SERVICE))

# 考虑滚动更新和过渡期,乘以1.5的系数
TRANSITION_FACTOR=1.5
TRANSITION_MAX=$(echo "$CURRENT_MAX * $TRANSITION_FACTOR" | bc)

# 再加上一些缓冲(10%)
BUFFER_FACTOR=1.1
FINAL_QUOTA=$(echo "$TRANSITION_MAX * $BUFFER_FACTOR" | bc | xargs printf "%.0f\n")

echo "=========================================="
echo "团队: $TEAM"
echo "Service数量: $SERVICE_COUNT"
echo "每个Service最大副本数: $MAX_REPLICAS_PER_SERVICE"
echo "当前最大Pod需求: $CURRENT_MAX"
echo "考虑滚动更新后: $TRANSITION_MAX"
echo "最终建议Pod配额: $FINAL_QUOTA"
echo "=========================================="

五、实践:LimitRange的精细化配置

5.1 设置容器资源默认值和上限

LimitRange确保每个容器都有合理的资源边界。在Knative集群中,由于服务可能是无状态的(比如API网关),也可能是有状态的(比如消息处理),我们可以为不同类型的容器设置不同的资源策略。

---
# 文件:limit-range.yaml
# 功能:为命名空间设置容器级别的资源默认值和上限
# 技术栈:Kubernetes v1.28+ / Knative Serving v1.12+
# 说明:每个团队的LimitRange可以有不同的配置,下面以team-a为例

apiVersion: v1
kind: LimitRange
metadata:
  name: team-a-limits
  namespace: team-a
  labels:
    team: team-a
    managed-by: platform-team
spec:
  # 容器级别的资源限制
  limits:
  - type: Container
    default:
      cpu: "100m"          # 默认请求100毫核CPU
      memory: "128Mi"      # 默认请求128Mi内存
    defaultRequest:
      cpu: "100m"          # 默认请求值(实际调度用)
      memory: "128Mi"
    max:
      cpu: "2000m"         # 单个容器最大使用2核CPU
      memory: "4Gi"        # 单个容器最大使用4Gi内存
    min:
      cpu: "50m"           # 单个容器最小使用50毫核CPU
      memory: "64Mi"       # 单个容器最小使用64Mi内存
  # Pod级别的资源限制
  - type: Pod
    max:
      cpu: "4000m"         # 单个Pod最大使用4核CPU
      memory: "8Gi"        # 单个Pod最大使用8Gi内存

5.2 为什么LimitRange对Knative特别重要

在Knative中,开发者经常通过Service的resources字段来指定容器资源,但如果忘记配置,Pod就会使用默认值。如果没有设置LimitRange,这些Pod会继承Kubernetes的默认值(通常非常宽松),可能导致单个Pod占用大量节点资源。

Knative Serving的默认Service YAML中通常包含这样的资源配置:

# Knative Service默认的资源配置片段
# 展示Knative Service如何声明容器资源
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: my-app
  namespace: team-a
spec:
  template:
    spec:
      containers:
      - image: my-registry/my-app:v1
        resources:
          requests:
            cpu: "100m"
            memory: "256Mi"
          limits:
            cpu: "500m"
            memory: "512Mi"

当多个Service同时扩容时,如果没有LimitRange兜底,一个配置不当的Service可能让单个Pod占用超过预期的资源。LimitRange的max配置就是最后一道防线,确保即使开发者配置了很高的limits,也不会超过设定的上限。

六、Knative Service与资源配额的联动机制

6.1 配额耗尽时的行为表现

当命名空间的ResourceQuota即将耗尽时,Knative的行为会出现以下变化。以下脚本模拟了这个过程:

#!/bin/bash
# 脚本:simulate-quota-exhaustion.sh
# 功能:模拟配额耗尽场景,观察Knative Service的行为
# 技术栈:Kubernetes v1.28+ / Knative Serving v1.12+
# 使用方法:bash simulate-quota-exhaustion.sh <命名空间>

NAMESPACE="$1"

echo "=========================================="
echo "模拟配额耗尽场景"
echo "命名空间: $NAMESPACE"
echo "=========================================="

# 查看当前资源使用情况
echo "当前ResourceQuota使用情况:"
kubectl get resourcequota -n "$NAMESPACE" -o yaml
echo ""

# 查看当前Pod状态
echo "当前Pod状态:"
kubectl get pods -n "$NAMESPACE" -o wide
echo ""

# 尝试创建一个新的Pod来模拟Knative扩容
echo "尝试创建新Pod(模拟Knative扩容):"
kubectl run test-pod -n "$NAMESPACE" --image=nginx --requests="cpu=500m,memory=256Mi"
echo ""

# 观察Pod状态
echo "新Pod状态(等待调度):"
sleep 5
kubectl get pod test-pod -n "$NAMESPACE" -o jsonpath='{.status.phase}' 2>/dev/null || echo "Pod创建失败(配额不足)"
echo ""

# 清理测试Pod
kubectl delete pod test-pod -n "$NAMESPACE" --ignore-not-found=true
echo "测试完成"

6.2 配置建议:为Knative预留扩容空间

为了避免配额耗尽导致Service无法扩容,建议按照以下方式分配配额:

---
# 文件:knative-aware-quota.yaml
# 功能:考虑Knative弹性伸缩的ResourceQuota配置
# 技术栈:Kubernetes v1.28+ / Knative Serving v1.12+
# 核心思路:limits比requests大很多,给Knative弹性伸缩留出空间

apiVersion: v1
kind: ResourceQuota
metadata:
  name: knative-team-a-quota
  namespace: team-a
spec:
  # requests设置得较紧,确保基础资源不被滥用
  # 按正常业务量的2倍设置
  requests.cpu: "4"
  requests.memory: "16Gi"
  
  # limits设置得宽松,确保Knative扩容时有空间
  # 按正常业务量的4倍设置
  limits.cpu: "16"
  limits.memory: "64Gi"
  
  # Knative Revision数量的软限制
  # 每个Service最多保留5个Revision
  # 通过限制Deployment数量间接实现
  count/deployments.apps: "30"

七、监控与告警:让资源使用透明化

7.1 通过kubectl查看资源使用情况

编写一个脚本定期检查各命名空间的资源使用情况,是最直观的方式:

#!/bin/bash
# 脚本:check-resource-usage.sh
# 功能:定期检查各命名空间的资源使用情况
# 技术栈:Kubernetes v1.28+ / Knative Serving v1.12+

echo "=========================================="
echo "Knative集群资源使用报告"
echo "生成时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "=========================================="

NAMESPACES=("team-a" "team-b" "team-c")

for ns in "${NAMESPACES[@]}"; do
  echo ""
  echo "---------- 命名空间: $ns ----------"
  
  # 查看ResourceQuota使用详情
  echo "ResourceQuota使用情况:"
  kubectl get resourcequota -n "$ns" 2>/dev/null
  echo ""
  
  # 查看实际Pod资源消耗(需要metrics-server)
  echo "当前Pod实际资源消耗:"
  kubectl top pods -n "$ns" 2>/dev/null || echo "(metrics-server未安装,跳过)"
  echo ""
  
  # 查看Knative Service状态
  echo "Knative Service状态:"
  kubectl get ksvc -n "$ns" 2>/dev/null || echo "(无Knative Service)"
  echo ""
  
  # 检查是否有Pending状态的Pod
  PENDING_COUNT=$(kubectl get pods -n "$ns" --field-selector=status.phase=Pending 2>/dev/null | wc -l)
  if [ "$PENDING_COUNT" -gt 1 ]; then
    echo "⚠️  警告:有$((PENDING_COUNT - 1))个Pod处于Pending状态!"
    kubectl get pods -n "$ns" --field-selector=status.phase=Pending
  fi
done

echo ""
echo "=========================================="

7.2 基于Prometheus的告警规则

如果集群中部署了Prometheus和Alertmanager,可以配置以下告警规则:

# 文件:resource-quota-alerts.yaml
# 功能:针对资源配额使用率设置告警
# 技术栈:Kubernetes v1.28+ / Prometheus + Alertmanager / Knative Serving v1.12+

groups:
- name: knative-resource-quota-alerts
  rules:
  # 当CPU配额使用超过80%时告警
  - alert: ResourceQuotaCPUHighUsage
    expr: |
      sum(kube_resourcequota_used{resource="requests.cpu"}) 
      / sum(kube_resourcequota_spec_hard{resource="requests.cpu"}) 
      > 0.8
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "命名空间 {{ $labels.namespace }} CPU配额使用率超过80%"
      description: "当前使用率: {{ $value | humanizePercentage }},请检查是否有异常扩容"

  # 当内存配额使用超过85%时告警
  - alert: ResourceQuotaMemoryHighUsage
    expr: |
      sum(kube_resourcequota_used{resource="requests.memory"}) 
      / sum(kube_resourcequota_spec_hard{resource="requests.memory"}) 
      > 0.85
    for: 3m
    labels:
      severity: critical
    annotations:
      summary: "命名空间 {{ $labels.namespace }} 内存配额使用率超过85%"
      description: "当前使用率: {{ $value | humanizePercentage }},新Pod可能无法调度"

  # 当Pod配额即将耗尽时告警
  - alert: PodQuotaNearExhaustion
    expr: |
      sum(kube_resourcequota_used{resource="pods"}) 
      / sum(kube_resourcequota_spec_hard{resource="pods"}) 
      > 0.75
    for: 10m
    labels:
      severity: warning
    annotations:
      summary: "命名空间 {{ $labels.namespace }} Pod数量配额即将耗尽"
      description: "当前Pod数: {{ $value | humanizePercentage }},建议检查是否需要扩容配额"

  # Knative Service扩容失败告警
  - alert: KnativeServiceScalingFailure
    expr: |
      knative_revision_desired_replicas != knative_revision_actual_replicas
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "Knative Service扩容失败"
      description: "Revision {{ $labels.revision }} 期望副本数与实际不符,可能因资源配额不足"

八、应用场景分析

8.1 多租户SaaS平台

一家公司运营多个客户的产品线,每个客户团队独立开发和维护自己的服务。通过命名空间隔离和ResourceQuota限制,确保某个客户的业务高峰不会影响其他客户的服务稳定性。比如电商团队在大促期间流量暴涨,其配额上限确保了即使疯狂扩容,也不会抢走支付团队的资源。

8.2 内部平台共享集群

公司内部有多个业务线,包括基础架构团队、数据平台团队、AI平台团队等。不同团队的资源需求差异很大,数据平台的离线任务可能需要大量CPU,而AI平台的模型推理可能需要大量内存。通过为不同团队设置差异化的配额和LimitRange,既满足各自的业务需求,又保证集群整体的稳定性。

8.3 开发与测试环境

开发测试环境中的服务变更频繁,经常出现因误操作导致的资源浪费。通过严格的LimitRange配置,确保即使开发者忘记设置资源限制,单个Pod也不会占用过多资源,从而降低误操作的负面影响。

8.4 关键考量因素

在使用ResourceQuota和LimitRange管理Knative集群时,需要综合考虑以下因素:

第一,弹性伸缩余量。Knative的核心能力是自动弹性伸缩,配额设计必须给扩容留出空间,否则扩容失败会直接影响用户体验。

第二,滚动更新过渡期。Knative Service在更新时会创建新Revision并逐步将流量从旧Revision切换过去,过渡期内新旧Pod同时存在,需要额外的资源余量。

第三,冷启动成本。Knative服务从0到1的冷启动需要时间,如果配额卡得太紧,扩容速度会变慢,影响响应时间。

第四,公平性原则。配额分配应该基于各团队的实际业务需求和历史资源消耗数据,避免某些团队因配额不足而影响正常业务。

九、技术优缺点分析

9.1 优点

第一,原生支持。ResourceQuota和LimitRange是Kubernetes的一等公民资源对象,不需要额外的Operator或Agent,运维成本低。

第二,隔离性好。配额在命名空间级别生效,天然适配Knative的多命名空间部署模式,每个团队可以独立管理自己的资源边界。

第三,配置简单。通过YAML文件即可完成配置,不需要编写复杂的策略代码,降低了平台团队的管理负担。

第四,可观测性强。配额使用情况可以通过kubectl直接查看,也可以集成Prometheus进行监控告警,资源消耗的透明度很高。

9.2 缺点

第一,粒度有限。ResourceQuota只支持命名空间级别的总量控制,无法做到针对单个Service或Pod组的细粒度配额。如果一个命名空间内有多个Service,无法为每个Service单独设置配额上限。

第二,不区分优先级。ResourceQuota不区分资源的优先级,无法实现"高优先级Service抢占低优先级Service的资源"这类调度策略。

第三,与Knative的交互需要额外关注。Knative的弹性伸缩是自动的,配额限制可能导致扩容失败,但这种失败对开发者来说可能不够明显,需要额外的监控手段来发现。

第四,无法防止突发流量导致的短时资源争抢。ResourceQuota限制的是总量,但在突发流量场景下,多个团队可能同时需要资源,此时配额无法协调资源的分配优先级。

十、注意事项

10.1 配额设置不要过于保守

很多运维团队在设置ResourceQuota时倾向于"宁紧不松",但这在Knative场景中是危险的。Knative的设计初衷就是应对突发流量,如果配额设置过紧,Service无法正常扩容,反而失去了使用Knative的意义。建议按照"正常业务量乘以2"来设置requests配额,按照"正常业务量乘以4"来设置limits配额。

10.2 LimitRange的max值需要与实际业务匹配

LimitRange的max值设置需要仔细考虑。如果设置得太小,正常的业务Pod无法正常运行;如果设置得太大,就失去了保护作用。建议先通过压测数据了解业务的最大资源消耗,再在此基础上乘以1.2到1.5的系数作为max值。

10.3 定期审查配额使用情况

配额不是一劳永逸的配置。随着业务的发展,各团队的资源需求会变化。建议每月或每季度进行一次配额审查,根据实际的资源消耗数据调整配额设置。可以编写自动化脚本定期导出各命名空间的资源使用报告,帮助团队做出调整决策。

10.4 与PriorityClass配合使用

对于关键业务Service,可以配合PriorityClass使用,确保在资源争抢时关键Service能够优先获得资源。

---
# 文件:priority-class-and-quota.yaml
# 功能:配合PriorityClass确保关键Service的资源优先级
# 技术栈:Kubernetes v1.28+ / Knative Serving v1.12+

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
  value: 1000000
  globalDefault: false
  description: "用于关键业务Service的高优先级"
---
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: critical-api
  namespace: team-a
  labels:
    app: critical-api
spec:
  template:
    spec:
      priorityClassName: high-priority  # 指定高优先级
      containers:
      - image: my-registry/critical-api:v1
        resources:
          requests:
            cpu: "200m"
            memory: "512Mi"
          limits:
            cpu: "1000m"
            memory: "1Gi"

10.5 注意Knative Revision的清理策略

Knative Service每次更新都会创建新的Revision,旧的Revision在流量切走后通常还会保留一段时间。如果Revision积累过多,会占用配额。建议配置Knative Service的Revision保留策略:

---
# 文件:revision-retention.yaml
# 功能:配置Knative Service的Revision保留策略
# 技术栈:Kubernetes v1.28+ / Knative Serving v1.12+

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: my-app
  namespace: team-a
spec:
  revisionTemplate:
    metadata:
      name: my-app-
      labels:
        app: my-app
    spec:
      containers:
      - image: my-registry/my-app:v1
        resources:
          requests:
            cpu: "100m"
            memory: "256Mi"
          limits:
            cpu: "500m"
            memory: "512Mi"
  # 通过Knative配置来限制Revision保留数量
  # 在knative-serving-configmap中配置 revision-history-limit: "3"

十一、文章总结

在多团队共享的Knative集群中,资源治理是一个既需要技术深度又需要业务理解的工作。ResourceQuota和LimitRange作为Kubernetes原生的资源管理工具,为命名空间级别的资源管控提供了坚实的基础。但要做好这件事,不能只是简单地设置几个数字就完事,需要深入理解Knative的弹性伸缩机制、滚动更新流程,以及各团队的真实业务需求。

核心思路可以总结为三步:第一步,根据各团队的业务特点分配合理的ResourceQuota,给Knative的弹性伸缩留出足够的空间;第二步,通过LimitRange为每个容器设置合理的默认值和上限,防止单个Pod的资源滥用;第三步,建立完善的监控告警体系,实时掌握各命名空间的资源使用情况,及时调整配额配置。

资源治理不是一次性的工作,而是一个持续优化的过程。随着业务的发展和集群的演进,配额配置也需要不断调整。建议平台团队建立配额管理的标准化流程,包括初始配额分配、定期审查、按需调整等环节,确保共享集群在保障稳定性的同时,也能充分支持各团队的创新需求。