Knative Serving:ABP 应用的 scale-to-zero 与并发模型
Knative Serving:ABP 应用的 scale-to-zero 与并发模型 🚀
📚 目录
- Knative Serving:ABP 应用的 scale-to-zero 与并发模型 🚀
- 0. TL;DR 🧾
- 1) 数据面 & 控制面 🧭
- 2) 与 ABP(.NET)集成点 🧩
- 3) 并发模型与扩缩判定 📈
- 4) 冷启动与预热 ❄️🔥
- 5) Knative Service 清单(ABP 订单服务)🧪
- 6) 灰度与按路径路由 )🛣️
- 7) 观测与告警 🔭
- 8) 压测方法(k6 / hey)🧪⚙️
- 9) 发布与回滚剧本 🧯
- 10) ABP/.NET 落地代码片段(健康探针 / Kestrel / 限流)🧱
- 11) 默认值与调参备忘 🧠
- 12) FAQ ❓
0. TL;DR 🧾
- 选择 KPA(Knative Pod Autoscaler)按 并发/RPS 伸缩,原生支持 scale-to-zero(全局开关);HPA 仅支持 CPU/内存,不支持 归零。
- Activator 在 0→1 或 突发超过 TBC(Target Burst Capacity,默认 200) 时入链缓冲并触发扩容;queue-proxy 在每个 Pod 前实施 硬并发(
containerConcurrency
)与排队并导出指标(仅当 硬并发>1 才会导出revision_queue_depth
)。 - 冷启动治理:
initial-scale
(修订创建预热)+activation-scale
(from-zero 预热)+scale-down-delay
(延迟降容)+stable-window
(默认 60s)组合拳。 - 路由/灰度:Serving 原生 按修订百分比;按路径灰度 交给 Ingress/Gateway(Istio/Kong/Contour…)实现。
1) 数据面 & 控制面 🧭
2) 与 ABP(.NET)集成点 🧩
- 健康探针:区分
/healthz/ready
(就绪)与/healthz/live
(存活),建议增加startupProbe
(JIT/预热慢时更稳)。 - Kestrel/HTTP2 并发:
Http2.MaxStreamsPerConnection
默认 100;与containerConcurrency
共同收敛端到端并发,避免线程耗尽。 - 多租户 & 限流:.NET 8
RateLimiter
做租户/接口层限速/限并,叠加 queue-proxy 形成“双层背压”。
3) 并发模型与扩缩判定 📈
关键参数
- 软目标:
autoscaling.knative.dev/metric=concurrency|rps
+.../target
。 - 硬上限:
spec.template.spec.containerConcurrency
(>0 启用排队;=0 不限)。 - TBC:
autoscaling.knative.dev/target-burst-capacity
控制 Activator on-path(默认 200;0
=仅 from-zero 入链,-1
=总在链上)。 - 惊慌窗口:
panic-window-percentage
×window
(默认 10% × 60s ≈ 6s)。
4) 冷启动与预热 ❄️🔥
只在 KPA 下可用;切到 HPA(
class=hpa.*
)后最低副本≥1,无法归零。initial-scale
与activation-scale
的生效时机不同:前者→“修订创建”、后者→“从 0 唤醒”。
5) Knative Service 清单(ABP 订单服务)🧪
目标:软并发 5、硬并发 20、初始 1、上限 50;允许归零;显式 queue-proxy 资源;探针分离并加
startupProbe
;gRPC/h2c 用端口名h2c
(不要用 networking 注解切 h2c)。
apiVersion: serving.knative.dev/v1
kind: Service
metadata:name: abp-ordersannotations:serving.knative.dev/rollout-duration: "5m" # 时间渐进切换;与自动伸缩无耦合
spec:template:metadata:annotations:# ---- Autoscaling (KPA) ----autoscaling.knative.dev/metric: "concurrency"autoscaling.knative.dev/target: "5"autoscaling.knative.dev/min-scale: "0"autoscaling.knative.dev/max-scale: "50"autoscaling.knative.dev/initial-scale: "1" # 仅“修订创建”时生效autoscaling.knative.dev/activation-scale: "2" # 仅“从0唤醒”时生效autoscaling.knative.dev/window: "60s"autoscaling.knative.dev/scale-down-delay: "30s"autoscaling.knative.dev/target-burst-capacity: "200" # 默认 200# ---- queue-proxy 侧车资源(官方注解键名)----queue.sidecar.serving.knative.dev/cpu-resource-request: "50m"queue.sidecar.serving.knative.dev/cpu-resource-limit: "500m"queue.sidecar.serving.knative.dev/memory-resource-request: "64Mi"queue.sidecar.serving.knative.dev/memory-resource-limit: "256Mi"# 可选:queue.sidecar.serving.knative.dev/ephemeral-storage-resource-{request|limit}spec:containerConcurrency: 20containers:- image: ghcr.io/yourorg/abp-orders:1.2.3# 若为 gRPC/h2c,请把端口名设置为 h2cports:- name: h2ccontainerPort: 8080env:- name: ASPNETCORE_URLSvalue: http://0.0.0.0:8080resources:requests: { cpu: "200m", memory: "256Mi" }limits: { cpu: "1", memory: "512Mi" }readinessProbe:httpGet: { path: /healthz/ready, port: 8080 }periodSeconds: 5livenessProbe:httpGet: { path: /healthz/live, port: 8080 }periodSeconds: 10# 更稳的启动探针,避免JIT/预热导致反复重启startupProbe:httpGet: { path: /healthz/live, port: 8080 }failureThreshold: 30periodSeconds: 5
6) 灰度与按路径路由 )🛣️
- 按修订百分比:Serving 原生支持;可加
rollout-duration
做时间渐进。 - 按路径灰度:在 Ingress/Gateway(如 Istio
VirtualService
、Kong 路由)实现 path/header 区分,把不同路由打到不同 Service,各自再按修订百分比分流。
7) 观测与告警 🔭
核心指标拓扑:
PromQL 示例(使用 container_name
选择 Activator,避免依赖 job 名):
# Activator 在途比例(近 5 分钟)
sum(rate(request_count{container_name="activator", revision_name="abp-orders"}[5m]))
/
sum(rate(revision_request_count{revision_name="abp-orders"}[5m]))# p95(queue-proxy 总时延)
histogram_quantile(0.95,sum by (le, revision_name) (rate(revision_request_latencies_bucket{revision_name="abp-orders"}[5m]))
)# 队列深度(仅当 containerConcurrency>1)
max by (revision_name) (revision_queue_depth{revision_name="abp-orders"})
💡 若启用 Service Mesh mTLS,需按发行版指引允许 Prometheus 抓取 Serving 组件指标(Activator/queue-proxy),否则面板会“空白”。
8) 压测方法(k6 / hey)🧪⚙️
目标场景
- 0→突发:空闲 2 分钟后 200 RPS 持续 5 分钟;
- 长尾低频:每分钟 3–5 个请求;
- 窄峰宽谷:10 分钟峰 + 20–30 分钟低频。
k6(RPS 精确控制):
// k6-burst.js
import http from 'k6/http'; import { sleep } from 'k6';
export const options = {scenarios: {burst_from_zero: { executor: 'constant-arrival-rate', rate: 200, timeUnit: '1s',duration: '5m', preAllocatedVUs: 50, maxVUs: 200 },long_tail: { executor: 'ramping-arrival-rate', startRate: 3, timeUnit: '1m',stages: [{ duration: '20m', target: 5 }] }}
};
export default () => { http.get(`${__ENV.URL}/api/orders/ping`); sleep(1); };
hey(-q
为“每 worker 的 QPS”,总 RPS ≈ q * c
):
# 约 200 RPS、持续 5 分钟:200 并发 * 1 qps/worker ≈ 200 rps
hey -z 5m -c 200 -q 1 -m GET "${URL}/api/orders/ping"
# 约 400 RPS:-q 2
评测输出建议:绘制 initial-scale=0/1/2
× containerConcurrency=10/20/50
的 p95 / 冷启动命中率 / 成本 三曲线,挑选满足 SLO 的最低成本组合。
9) 发布与回滚剧本 🧯
- 发布前检查:
enable-scale-to-zero=true
、stable-window=60s
、target-burst-capacity=200
。 - 灰度策略:按修订百分比 1%→10%→25%→50%→100%,或用
serving.knative.dev/rollout-duration
做时间型平滑切换。 - 快速回滚:把
traffic
切回稳定修订;必要时临时min-scale=1
/提升activation-scale
抑制冷启动,再定位根因。
10) ABP/.NET 落地代码片段(健康探针 / Kestrel / 限流)🧱
健康探针(Program.cs)
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;builder.Services.AddHealthChecks().AddCheck("db", () => HealthCheckResult.Healthy(), tags: new[] { "ready" });var app = builder.Build();app.MapHealthChecks("/healthz/ready",new HealthCheckOptions { Predicate = r => r.Tags.Contains("ready") });
app.MapHealthChecks("/healthz/live",new HealthCheckOptions { Predicate = _ => false });app.Run();
Kestrel/HTTP2 并发
builder.WebHost.ConfigureKestrel(o =>
{o.Limits.MaxConcurrentConnections = 1024;o.Limits.Http2.MaxStreamsPerConnection = 100; // 默认 100,可按需调
});
租户限流(.NET 8 RateLimiter)
using System.Threading.RateLimiting;builder.Services.AddRateLimiter(o =>
{o.AddPolicy("per-tenant", ctx =>RateLimitPartition.GetTokenBucketLimiter(ctx.Request.Headers["X-TenantId"].ToString() ?? "anon",_ => new TokenBucketRateLimiterOptions {TokenLimit = 50, TokensPerPeriod = 50,ReplenishmentPeriod = TimeSpan.FromSeconds(1),QueueLimit = 200, QueueProcessingOrder = QueueProcessingOrder.OldestFirst,AutoReplenishment = true}));
});
var app = builder.Build();
app.UseRateLimiter();
11) 默认值与调参备忘 🧠
container-concurrency-target-default = 100
requests-per-second-target-default = 200
target-burst-capacity = 200
stable-window = 60s
,panic-window-percentage = 10%
(惊慌窗口≈6s)activation-scale
:仅 from-zero 生效;initial-scale
:仅 修订创建时生效revision_queue_depth
:仅当containerConcurrency > 1
才会导出
12) FAQ ❓
- 切到 HPA 为啥不能归零? → HPA 最低副本≥1;只有 KPA 支持 scale-to-zero。
containerConcurrency
vstarget
? → 前者是 硬上限(触发排队),后者是 软目标(指导扩缩)。- Activator 是否影响时延? → 看 Activator
request_count/latencies
与服务侧 p95 的联动;必要时调高activation-scale
/TBC
。 revision_queue_depth
没数据? → 设置containerConcurrency > 1
才会导出该指标。- gRPC/h2c 怎么启用? → 将容器端口 命名为
h2c
(或配相应 appProtocol);不要用 networking 注解去切 h2c。