随着汤不热吧技术社区内容生态的持续扩张,我们面临着一个日益突出的工程挑战:定时爬虫、图片处理、AI 推理、邮件通知等大量批处理与事件驱动任务,长期占用常驻计算资源,导致集群资源利用率长期低于 30%。为了从根本上解决这一问题,我们在 Kubernetes 集群上构建了一套基于 Knative Serving 与 KEDA(Kubernetes Event-Driven Autoscaling)的 Serverless 函数计算平台,实现了从 0 到 N 的自动弹性伸缩、按需付费级别的资源利用率,以及事件驱动的任务编排。本文完整记录这一平台的架构设计、部署过程与生产化实践经验。

一、背景与动机
在引入 Serverless 平台之前,我们的任务调度体系存在三大痛点。第一,资源浪费严重:30 余个 CronJob 分散在 6 个节点上运行,每个任务的平均 CPU 利用率不足 15%,但必须常驻 Pod 以保证按时触发,仅此一项每月浪费约 240 核 CPU 的预留资源。第二,扩缩容迟缓:面对突发流量(如每日热门内容推送时图片缩略图生成请求暴增),基于 HPA 的扩容需要 60-90 秒才能完成 Pod 调度与冷启动,用户体验受损。第三,任务编排能力薄弱:复杂的多步骤任务(如「抓取 → 清洗 → 向量化 → 入库」)依赖分散的 CronJob 与人工编排,缺乏统一的事件链路与错误重试机制。
经过对 OpenFaaS、Knative、Fission 三种方案的深度调研与基准测试,我们最终选择 Knative Serving 作为核心运行时,配合 KEDA 实现事件驱动的自动伸缩。Knative 的优势在于原生集成 Istio 网络层、支持从零扩容(scale-to-zero)、提供 request-driven 的自动伸缩模型;而 KEDA 补充了基于外部事件源(Kafka、Redis、Prometheus 等)的伸缩能力,两者结合能够覆盖 request-driven 与 event-driven 两种工作负载模型。
二、整体架构设计

整个 Serverless 平台分为四层:
- 入口层:基于 Istio Ingress Gateway 的统一流量入口,支持按域名路由到不同 Knative Service,同时集成 TLS 终结与限流。
- 调度层:Knative Serving Controller + Autoscaler(KPA)负责 Pod 的创建、缩容到零与按并发自动伸缩;KEDA 作为外部事件源伸缩器,对 Kafka topic 消费延迟、Redis 队列长度等指标做出扩容决策。
- 运行时层:每个 Knative Service 对应一个函数,使用 Queue-Proxy Sidecar 拦截请求并上报并发指标。函数镜像基于 distroless 镜像构建,冷启动时间控制在 800ms 以内。
- 事件层:基于 Knative Eventing 构建事件总线,支持 Kafka Source、Redis Source、CronJob Source 等事件源,通过 Broker → Trigger 模式实现解耦的事件路由。
三、Knative Serving 部署详解
3.1 前置依赖与命名空间
Knative Serving 依赖 Istio 作为网络层。我们使用 Istio 1.22 的 minimal profile,仅安装 Ingress Gateway 与必要的 CRD:
1
2
3
4
5
6
7
8
9
10
11
12 # 安装 Istio minimal profile
istioctl install --set profile=minimal \
--set values.gateways.istio-ingressgateway.enabled=true
# 安装 Knative Serving CRD
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.14.0/serving-crds.yaml
# 安装 Knative Serving core
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.14.0/serving-core.yaml
# 安装 Knative Istio Controller
kubectl apply -f https://github.com/knative/net-istio/releases/download/knative-v1.14.0/net-istio.yaml
安装完成后,配置 Knative 的自定义域名与网络参数。我们将默认的
1 | .svc.cluster.local |
后缀替换为社区自有域名,并通过 DNS 通配解析指向 Istio Ingress Gateway:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 # knative-serving ConfigMap 关键配置
apiVersion: v1
kind: ConfigMap
metadata:
name: config-domain
namespace: knative-serving
data:
fn.tbr8.org: ""
---
apiVersion: v1
kind: ConfigMap
metadata:
name: config-network
namespace: knative-serving
data:
ingress.class: "istio.ingress.networking.knative.dev"
system-internal-tls.enabled: "true"
3.2 自动伸缩参数调优
Knative 的自动伸缩行为由 ConfigMap
1 | config-autoscaler |
控制。针对我们的工作负载特征(以图片处理和 AI 推理为主,单请求处理时间 200ms-5s),关键参数调优如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 apiVersion: v1
kind: ConfigMap
metadata:
name: config-autoscaler
namespace: knative-serving
data:
# 每个实例的目标并发数,图片处理类任务设为 10
container-concurrency-target-default: "10"
# 并发突增时的快速扩容窗口
panic-window-percentage: "10.0"
# 稳定窗口用于缩容决策,设为 60 秒平滑缩容
scale-to-zero-grace-period: "60s"
# 允许缩容到零
enable-scale-to-zero: "true"
# 冷启动后的最小保留时间,避免频繁抖动
scale-to-zero-pod-retention-period: "120s"
# 最大扩容速率,每轮最多扩容 4 倍当前实例数
max-scale-up-rate: "4.0"
# 缩容速率限制
max-scale-down-rate: "2.0"
这里有一个重要经验:
1 | container-concurrency-target-default |
的设置直接影响冷启动频率与资源利用率。对于 CPU 密集型的图片处理任务,我们将其设为 10(而非默认的 100),因为单 Pod 处理 10 个并发请求时 CPU 已接近满载;对于 I/O 密集型的 Webhook 转发任务,则设为 50。这种按 Service 级别的覆盖配置通过 Annotation 实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: image-processor
annotations:
autoscaling.knative.dev/class: "kpa.autoscaling.knative.dev"
autoscaling.knative.dev/target: "10"
autoscaling.knative.dev/min-scale: "0"
autoscaling.knative.dev/max-scale: "50"
spec:
template:
spec:
containerConcurrency: 10
timeoutSeconds: 300
containers:
- image: registry.tbr8.org/fn/image-processor:v2.1
resources:
limits:
cpu: "2"
memory: "2Gi"
requests:
cpu: "500m"
memory: "512Mi"
四、KEDA 事件驱动伸缩集成
Knative KPA 的局限在于它只能基于 HTTP 并发数伸缩,无法直接感知 Kafka 队列积压、Redis Stream 长度等外部事件指标。KEDA 填补了这一空白。它的工作原理是:为每个 Deployment 注入一个 External Scaler,周期性轮询外部指标源,当指标超过阈值时通过 HPA 机制触发扩容。
4.1 KEDA 安装与 Kafka Scaler 配置
1
2
3 # 安装 KEDA 2.14
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda --namespace keda-system --create-namespace --version 2.14.0
以我们的内容抓取流水线为例:爬虫结果写入 Kafka topic
1 | crawl-results |
,下游的清洗函数需要根据积压消息数动态扩容。ScaledObject 配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: content-cleaner-scaler
namespace: serverless
spec:
scaleTargetRef:
name: content-cleaner
minReplicaCount: 0
maxReplicaCount: 30
pollingInterval: 15
cooldownPeriod: 120
triggers:
- type: kafka
metadata:
bootstrapServers: kafka.tbr8.org:9092
consumerGroup: content-cleaner-group
topic: crawl-results
lagThreshold: "50"
offsetResetPolicy: latest
partitionLimitation: "0,1,2,3,4,5"
该配置的含义是:当 Kafka 消费组的积压消息超过 50 条时触发扩容,每 15 秒轮询一次。最小副本数为 0(空闲时完全缩容),最大 30 副本。冷却期 120 秒防止频繁抖动。
4.2 多触发器组合
某些函数需要同时响应多个事件源。例如我们的 AI 自动标签函数,既需要处理 Kafka 中的新文章事件,也需要在 CPU 利用率过高时自动扩容以避免积压。KEDA 支持在同一 ScaledObject 中配置多个 trigger,取所有触发器中最大的扩容建议:
1
2
3
4
5
6
7
8
9
10
11 triggers:
- type: kafka
metadata:
topic: new-articles
lagThreshold: "20"
- type: prometheus
metadata:
serverAddress: http://prometheus.monitoring:9090
metricName: cpu_usage_avg
threshold: "80"
query: avg(rate(container_cpu_usage_seconds_total{pod=~"ai-tagger-.*"}[1m])) * 100
五、Knative Eventing 事件总线

Eventing 是 Knative 的另一个核心组件,提供声明式的事件路由能力。我们使用 Broker-Trigger 模型:Broker 作为事件接收与过滤的中间层,Trigger 定义从 Broker 订阅特定类型事件的规则,并将事件投递到目标 Knative Service。
以下是一个完整的事件链路配置示例——新文章发布后触发向量化、标签生成、索引更新三个下游函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 apiVersion: eventing.knative.dev/v1
kind: Broker
metadata:
name: article-broker
namespace: serverless
---
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: vectorize-trigger
spec:
broker: article-broker
filter:
attributes:
type: article.published
subscriber:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: vectorize-fn
---
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: tag-trigger
spec:
broker: article-broker
filter:
attributes:
type: article.published
subscriber:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: ai-tagger-fn
事件发布方只需向 Broker 的 HTTP 端点发送遵循 CloudEvents 规范的事件,无需感知下游消费者:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 import json, requests
from cloudevents.http import CloudEvent, to_binary
event = CloudEvent({
"source": "cms-publish-service",
"type": "article.published",
"id": f"article-{article_id}",
}, json.dumps({"article_id": article_id, "url": url}))
# 发送到 Broker
requests.post(
"http://article-broker-broker.serverless.svc.cluster.local",
headers=to_binary(event)["headers"],
data=to_binary(event)["data"],
)
六、冷启动优化实践
Scale-to-zero 的代价是冷启动延迟。未经优化的函数首次请求延迟可达 3-5 秒,这对于面向用户的同步请求是不可接受的。我们从四个维度进行了优化:
6.1 镜像瘦身
将基础镜像从
1 | python:3.12 |
(900MB+)切换为
1 | gcr.io/distroless/python3-debian12 |
(约 120MB),并使用多阶段构建将依赖层与代码层分离。构建时利用 BuildKit 的
1 | --mount=type=cache |
缓存 pip 下载,使 CI 构建时间从 8 分钟降至 2 分钟:
1
2
3
4
5
6
7
8
9 FROM python:3.12-slim AS builder
RUN pip install --prefix=/install --no-cache-dir \
fastapi uvicorn pillow
FROM gcr.io/distroless/python3-debian12:nonroot
COPY --from=builder /install /usr/local/lib/python3.12/site-packages
COPY app.py /app/app.py
WORKDIR /app
CMD ["-m", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]
6.2 预热池与低延迟模式
对延迟敏感的函数,我们启用了 Knative 的
1 | min-scale: 2 |
保持常驻实例,同时配置 Pod 的
1 | startupProbe |
确保就绪后才接收流量。对于关键路径函数,进一步启用
1 | progressive-deadline |
特性,允许 Autoscaler 在扩容过程中并行启动多个实例而非串行。
6.3 Queue-Proxy 资源调优
每个 Knative Pod 都注入一个 Queue-Proxy Sidecar,负责接收请求并报告并发指标。默认配置下它占用 25m CPU / 20Mi 内存。在高并发场景下,Queue-Proxy 本身可能成为瓶颈。我们将其 CPU Limit 提升至 100m 并启用 HTTP/2:
1
2
3
4
5 # config-deployment ConfigMap
queue-sidecar-cpu-request: "50m"
queue-sidecar-cpu-limit: "200m"
queue-sidecar-memory-request: "40Mi"
queue-sidecar-memory-limit: "100Mi"
七、生产化监控与告警
Serverless 平台的监控需要关注与传统部署不同的指标维度。我们在 Prometheus 中配置了以下关键指标看板:
| 指标 | PromQL | 告警阈值 |
|---|---|---|
| 冷启动延迟 P99 | histogram_quantile(0.99, knative_serving_request_latencies{response_code_class!=”5xx”}) | > 2s |
| KPA 扩容决策频率 | rate(autoscaler_decisions_total[5m]) | > 10/min |
| 缩容到零次数 | increase(kpa_scale_to_zero_total[1h]) | > 20/h(可能抖动) |
| Kafka 积压 | kafka_consumergroup_lag | > 1000 |
| 函数错误率 | rate(knative_serving_request_count{response_code_class=”5xx”}[5m]) / rate(knative_serving_request_count[5m]) | > 5% |
基于这些指标,我们设置了三级告警:P0(冷启动超 5 秒或错误率超 10%)触发 PagerDuty 电话告警;P1(扩缩容抖动或 Kafka 积压超 5000)触发企业微信机器人通知;P2(冷启动超 2 秒)记录到每日运维报告供人工审查。
八、上线效果与数据
平台上线运行 45 天后,我们统计了以下关键指标的变化:
- 集群 CPU 利用率从 28% 提升至 71%,峰值利用率从 45% 提升至 89%——scale-to-zero 释放了大量预留资源。
- 月度计算成本从 $4,200 降至 $1,850,降幅 56%。主要来源是空闲时段(凌晨 2-7 点)的自动缩容到零,节省了约 220 核 CPU 的常驻开销。
- 冷启动 P99 延迟从初始的 4.2 秒优化至 780ms,满足面向用户的同步请求需求。
- 突发流量处理能力:在内容推送高峰期,图片处理函数从 0 实例扩容到 45 个实例仅需 12 秒,远优于此前 HPA 方案的 90 秒。
- 任务编排复杂度:原先依赖 12 个 CronJob 与 6 个消息消费者人工协调的流水线,现统一收敛为 3 个 Broker 上的事件链路,配置即代码、Git 版本管理。
九、踩过的坑与经验教训
在部署过程中,我们遇到了若干值得记录的问题:
坑一:Istio 与 Knative 版本兼容性。初期使用 Istio 1.20 + Knative 1.13 出现 Sidecar 注入后请求超时问题,根因是 Istio 默认的
1 | PeerAuthentication |
STRICT 模式与 Knative Queue-Proxy 的 mTLS 配置冲突。解决方案是在
1 | knative-serving |
namespace 设置 PERMISSIVE 模式,或升级到 Knative 1.14+ 内置 system-internal-tls。
坑二:Scale-to-zero 导致数据库连接耗尽。当大量函数同时从零扩容时,每个新 Pod 都会建立到 MySQL 的连接池,瞬间产生数百个连接冲击数据库。我们通过引入 PgBouncer 连接池中间件,并限制每个函数 Pod 的连接池大小为 5,配合 MySQL
1 | max_connections=500 |
彻底解决了这一问题。
坑三:KEDA 与 KPA 冲突。同时使用 Knative KPA 和 KEDA ScaledObject 管理同一工作负载会导致控制器互相覆盖缩容决策。正确做法是:HTTP 请求驱动的函数仅用 KPA;事件驱动的函数用 Deployment + KEDA ScaledObject,不创建 Knative Service,避免两套伸缩控制器打架。
十、后续规划
Serverless 平台的下一阶段规划包括三个方向:第一,引入 Knative Functions(kn func CLI)提供更友好的开发者体验,让非运维人员也能通过简单命令部署函数;第二,探索 GPU 函数计算,为 AI 推理类任务提供 scale-to-zero 的 GPU 实例,结合 NVIDIA Triton 的模型仓库热加载能力降低冷启动代价;第三,建设函数依赖追踪与分布式链路追踪体系,将 OpenTelemetry SDK 注入 Queue-Proxy 与函数运行时,打通从 Broker 事件入口到函数执行全链路的 Trace 可见性。
汤不热吧技术社区将继续以工程博客的形式公开我们的基础设施演进历程,欢迎开发者社区关注与交流。如有关于 Knative、KEDA 或 Serverless 架构的问题,可在本文评论区留言,社区技术团队会定期回复。
汤不热吧