SkyPilot Sky Serve 架构与实战:从单一 Endpoint 到跨云多副本弹性推理服务
【免费下载链接】skypilotThe AI Compute Platform for frontier teams. SkyPilot turns fragmented AI compute into one AI supercomputer, so frontier AI teams build custom intelligence faster.项目地址: https://gitcode.com/GitHub_Trending/sk/skypilot
Sky Serve 是 SkyPilot 内置的推理服务托管库(位于 sky/serve 目录),其目标非常直接:对外暴露一个稳定的 Endpoint,把进入的流量透明地分发到运行在不同资源、不同区域、甚至不同云厂商上的多个服务端点副本上。负载均衡(Load Balancing)、故障转移(Failover)与弹性伸缩(Autoscaling)全部由 Sky Serve 在后台自动处理,用户只需要提交一个声明式的 Service YAML。读完本文,你将掌握 Sky Serve 的四组件架构、Service YAML 的完整配置语义、请求级负载均衡与 QPS 驱动的自动扩缩容原理,并能用sky serve命令一键部署并运维一个多副本推理服务。
设计目标:一个 Endpoint,任意规模的服务集群
在 SkyPilot 的定位中,前端团队的计算往往横跨多个云厂商、多个区域,还可能同时使用按需实例与 Spot 实例。传统做法需要自己搭建网关、写健康检查、处理实例被抢占时的迁移——工作量大且极易出错。Sky Serve 把这些复杂性收敛为一个用户感知不到的"服务层":
暴露一个 Endpoint,将任何进入的流量分发到运行在不同资源、区域和云上的服务端点。
它透明地处理了三大核心问题:负载均衡(把请求均匀或按策略地分到健康副本)、故障转移(副本失败时自动把流量切到其他健康副本并重建新副本)、自动伸缩(根据流量或队列长度动态增减副本数量)。这意味着用户可以把 Spot 实例的抢占、某个区域容量不足、甚至整个云不可用都交给系统兜底,服务对外始终只有一个稳定的 URL。
四组件架构总览
Sky Serve 的核心架构由四个关键组件构成(架构图见 docs/source/images/sky-serve-architecture.png):
- Load Balancers(负载均衡器):接收所有进入的请求,按照所选策略把请求代理转发到健康副本。实现位于 sky/serve/load_balancer.py。
- Load Balancing Policies(负载均衡策略):定义请求如何在健康副本之间分布,例如轮询、最少负载等。实现位于 sky/serve/load_balancing_policies.py。
- Autoscalers(自动伸缩器):根据流量指标(QPS、队列长度等)动态决定副本的扩缩。实现位于 sky/serve/autoscalers.py。
- Replica Managers(副本管理器):负责副本的创建、销毁、健康探测与故障恢复。实现位于 sky/serve/replica_managers.py。
四个组件共同运行在一个名为Controller的常驻进程中(由sky serve up自动在云端拉起)。Controller 负责协调一切:它维护服务与副本的元数据数据库(sky/serve/serve_state.py 中的services、replicas、version_specs三张表),调度 Replica Manager 执行副本的sky.launch/sky down,并驱动 Autoscaler 周期性产出扩缩容决策。
负载均衡器如何工作:同步 + 代理 + 重试
load_balancer.py 中的SkyServeLoadBalancer是一个基于 FastAPI + Uvicorn 的异步代理进程:
- 每
LB_CONTROLLER_SYNC_INTERVAL_SECONDS = 20秒与 Controller 同步一次(_sync_with_controller),拉取当前健康副本列表,同时把这一周期内累计的请求时间戳上报给 Controller,供 Autoscaler 计算 QPS(constants.py)。 - 每个请求进入
_proxy_with_retries:先由策略从健康副本中选出一个 URL,再用httpx.AsyncClient以流式方式代理请求(_proxy_request_to),响应通过自定义的_CleanupStreamingResponse边收边转发。 - 由于支持 Spot 实例上的推理,副本可能在请求处理途中被抢占,因此代理内置了最多
LB_MAX_RETRY = 3次重试;无可用副本时返回 503,客户端中途断开返回 499,重试耗尽返回 500(load_balancer.py)。流式响应的默认超时为 120 秒,足够覆盖 Llama2-70B 这类大模型的一次生成。
Service YAML 配置全解析
Sky Serve 的配置入口是 Service YAML,核心是service:段。解析与校验逻辑全部集中在 sky/serve/service_spec.py 的SkyServiceSpec.from_yaml_config。以下以一个真实的 vLLM 服务为例(完整文件见 examples/serve/vllm.yaml):
service: readiness_probe: path: /v1/models initial_delay_seconds: 1200 # vLLM 安装需要 5-10 分钟 replicas: 2 resources: ports: 8081 accelerators: A100:1 setup: | conda activate chatbot ... run: | conda activate chatbot python3 -m fastchat.serve.openai_api_server --host ${HOST_IP} --port 8081readiness_probe:健康探测配置
readiness_probe既可以是字符串(等价于path),也可以是完整字典(service_spec.py):
| 配置项 | 含义 | 默认值 |
|---|---|---|
path | 就绪探测的 HTTP 路径,必须以/开头 | 必填 |
initial_delay_seconds | 服务启动后此期间内的探测失败被忽略(用于等待模型加载/依赖安装) | 1200 |
timeout_seconds | 单次就绪探测请求的超时时间 | 15 |
endpoint_probe_interval_seconds | 探测副本端点的间隔 | 10 |
post_data | 以 POST 方式探测时携带的 JSON 请求体 | None(默认 GET) |
headers | 探测时附加的自定义请求头 | None |
consecutive_failure_threshold_timeout | 连续失败达到该超时后判定副本失败 | None |
这些默认值均来自 sky/serve/constants.py。initial_delay_seconds默认 1200 秒是因为真实 LLM 推理服务的就绪探测往往要等模型权重加载与依赖安装,过早判定失败会导致副本被误杀。
副本策略:replica_policy / replicas
service:段中,副本数量既可以用简写replicas: 2(等价于固定 2 个副本),也可以用完整的replica_policy:(二者不能同时出现,见 service_spec.py):
service: replica_policy: min_replicas: 1 max_replicas: 4 target_qps_per_replica: 2.0 # 触发自动伸缩的每副本目标 QPS num_overprovision: 0 # 在目标副本数之上额外多起的副本数 upscale_delay_seconds: 300 # 扩容生效前的持续观察时间 downscale_delay_seconds: 1200 # 缩容生效前的持续观察时间 base_ondemand_fallback_replicas: 1 # 常驻的按需实例副本数 dynamic_ondemand_fallback: false # 抢占后是否动态补按需实例关键校验规则(service_spec.py):
- 设置
target_qps_per_replica时必须同时设置max_replicas; - 未设置
target_qps_per_replica时,min_replicas与max_replicas必须相等(固定副本数); max_replicas不得小于min_replicas。
load_balancing_policy 与负载均衡策略注册表
load_balancing_policy字段用于选择流量分发策略。load_balancing_policies.py 通过LoadBalancingPolicy.__init_subclass__机制把策略自动注册进全局LB_POLICIES注册表,未知策略名会在 YAML 解析阶段直接报错(service_spec.py)。当前可用的策略:
| 策略名 | 行为 | 默认 |
|---|---|---|
least_load | 最少负载:实时跟踪每个副本的在途请求数,优先选择负载最小的副本,并轮转打破平局 | ✅ 默认 |
round_robin | 轮询:副本列表变化时先随机打乱,避免第一个副本长期承接最多流量 | |
instance_aware_least_load | 实例感知最少负载:按副本 GPU 型号的target_qps_per_replica归一化负载后再选择 |
round_robin的实现尤其值得注意:set_ready_replicas在副本集合变化时先random.shuffle再重置指针(load_balancing_policies.py),这是为了防止 Autoscaler 反复扩缩容导致排在最前的副本总是承接最多请求。
实例感知策略:异构 GPU 集群的按能力调度
当target_qps_per_replica是字典(按 GPU 型号给出不同目标 QPS)时,必须配合load_balancing_policy: instance_aware_least_load使用,否则 YAML 校验失败(service_spec.py):
service: replica_policy: min_replicas: 1 max_replicas: 4 target_qps_per_replica: A100: 2.0 H100: 4.0 load_balancing_policy: instance_aware_least_loadInstanceAwareLeastLoadPolicy会把每个副本的当前负载除以该副本 GPU 型号对应的目标 QPS,得到"归一化负载"后再取最小者(load_balancing_policies.py),并对A100:1这类带数量后缀的型号做基名匹配(A100:1匹配A100)。匹配不到时使用默认值 1.0 作为兜底,避免除零。对应地,Autoscaler 也会切换为InstanceAwareRequestRateAutoscaler(autoscalers.py),扩容时按最快的 GPU 的 QPS 估算所需副本数,缩容时优先回收 QPS 能力最低的副本。
端口与 TLS
ports:副本对外暴露的端口,必须处于 1-65535 之间(service_spec.py),同时需要在resources.ports中声明。tls:提供keyfile与certfile后,负载均衡器将升级为 HTTPS 端点(load_balancer.py),证书通过TLSCredential.dump_uvicorn_kwargs()直接注入 Uvicorn。
自动伸缩:QPS 驱动与队列长度驱动
Autoscaler 位于 sky/serve/autoscalers.py,由Autoscaler.from_spec根据配置自动选择实现(autoscalers.py):
- 配置了
pool→QueueLengthAutoscaler(队列长度驱动); - 配置了
use_ondemand_fallback→FallbackRequestRateAutoscaler(QPS 驱动 + Spot/按需兜底); target_qps_per_replica为字典 →InstanceAwareRequestRateAutoscaler;- 其他 →
RequestRateAutoscaler(标准 QPS 驱动)。
决策模型与滞回(Hysteresis)
所有基于 QPS 的 Autoscaler 都继承自_AutoscalerWithHysteresis。核心思想是不因为单次波动就扩缩容,而是要求目标副本数在连续多个决策周期内持续偏离当前值才生效:
- 决策间隔默认 20 秒;当目标副本数为 0(
min_replicas: 0且无流量)时缩短到 5 秒,加快服务拉起(autoscalers.py)。 - 扩容阈值 =
upscale_delay_seconds / 20,默认 300 秒 → 需要连续 15 个周期(约 5 分钟)都要求扩容才真正扩容;缩容阈值默认 1200 秒 → 约 20 分钟。 - 有一个快速通道:当前目标副本数为 0 时立即采用新目标,避免冷启动延迟(autoscalers.py)。
RequestRateAutoscaler._calculate_target_num_replicas的算法非常直观:用最近 60 秒窗口内的请求时间戳数量除以窗口长度得到每秒请求数,再除以单副本目标 QPS 后向上取整,最后裁剪到[min_replicas, max_replicas]区间(autoscalers.py)。请求时间戳由负载均衡器每 20 秒上报一次,Autoscaler 用bisect维护滑动窗口。
Spot 与按需兜底
FallbackRequestRateAutoscaler在 QPS 伸缩之上叠加了 Spot 策略(autoscalers.py):
base_ondemand_fallback_replicas:始终保留 N 个按需实例,提供基本的可用性保证;dynamic_ondemand_fallback:任何 Spot 副本被抢占,就用按需实例动态补位(按num_ready_spot而非num_nonterminal_spot计算缺口,因为被抢占的 Spot 可能永远无法恢复)。
扩容时 Spot 与按需分别通过SPOT_OVERRIDE = {'use_spot': True}与ONDEMAND_OVERRIDE = {'use_spot': False}资源覆盖来下发(autoscalers.py),Replica Manager 在launch_cluster中把这些覆盖应用到每个副本的sky.Task资源上(replica_managers.py)。
副本的缩容优先级
需要缩容时,_select_nonterminal_replicas_to_scale_down按四重标准排序(autoscalers.py):
- 状态越早期越先缩(
PENDING → PROVISIONING → STARTING → NOT_READY → READY,见 serve_state.py); - 版本号小的(旧版本)先缩;
- 对 Pool 场景,运行作业数少的空闲 worker 先缩;
- 副本 ID 大的(后启动的)先保留,因为可能即将就绪。
副本生命周期与故障恢复
Replica Manager 通过 replica_managers.py 中的launch_cluster和terminate_cluster管理每个副本对应的独立 SkyPilot 集群,并为每个副本注入环境变量SKYPILOT_SERVE_REPLICA_ID(值取自 constants.py)。
副本状态机
每个副本在数据库中维护一个ReplicaStatus(serve_state.py):
- 非终态:
PENDING(排队等待启动)→PROVISIONING(VM 创建中)→STARTING(依赖安装/服务启动中)→READY(就绪探测通过)→NOT_READY(曾就绪但探测失败)→SHUTTING_DOWN(下线中); - 终态失败:
FAILED(用户 run/setup 失败)、FAILED_INITIAL_DELAY(超过初始延迟仍不健康)、FAILED_PROBING(健康检查失败)、FAILED_PROVISION(启动失败)、FAILED_CLEANUP(下线失败)、PREEMPTED(Spot 被抢占/Kubernetes Pod 被驱逐)。
服务的整体状态则汇总为ServiceStatus(如CONTROLLER_INIT、READY、FAILED、SHUTTING_DOWN等,serve_state.py)。
不可恢复故障的熔断
Autoscaler 在generate_scaling_decisions中会先检查最新版本是否有不可恢复故障(unrecoverable_failure):如果最新版本从未就绪、且副本进入终态失败(如用户代码 bug 导致应用在就绪前崩溃),则立即停止一切扩缩决策,避免无限"终止-重启"循环浪费资源(autoscalers.py)。判断逻辑见 replica_managers.py:只要服务曾经就绪过(first_ready_time >= 0),就认为用户代码无 bug,失败可自动恢复。
优雅下线与排空
缩容/更新下线副本时,terminate_cluster支持replica_drain_delay_seconds(默认 120 秒,见 replica_managers.py),即先让副本进入排空状态、处理完在途请求再真正执行sky down。sky down失败会触发最多 3 次重试并标记FAILED_CLEANUP,提示可能存在资源泄漏。
命令行实战:一键部署与运维
安装 SkyPilot 后,所有 Sky Serve 操作都通过sky serve子命令完成。
启动服务
# 部署一个名为 vllm 的服务 sky serve up -n vllm examples/serve/vllm.yaml启动后,Controller 会自动在云端创建(默认资源配置cpus: 4+, memory: 8+,见 constants.py),并在30001-30020端口范围内为负载均衡器分配端口(constants.py)。部署成功后控制台会打印服务 Endpoint 地址。默认的 Controller 自动停止策略为idle_minutes: 10, down: false(constants.py),可通过~/.sky/config.yaml中的serve.controller.autostop覆盖。
查看状态与测试
# 查看服务与副本状态 sky serve status vllm # 直接打印服务 Endpoint sky serve status --endpoint vllm # 用 curl 直接向 Endpoint 发请求验证 curl -L http://<endpoint>/v1/modelssky serve status会展示服务的ServiceStatus、每个副本的ReplicaStatus(带颜色区分)以及自动伸缩策略摘要(Fixed 2 replicas、Autoscaling from 1 to 4 replicas (target QPS per replica: 2.0)等文本由autoscaling_policy_str()生成,见 service_spec.py)。状态输出效果可参考 docs/source/images/sky-serve-status-vllm.png 与 docs/source/images/sky-serve-status-vicuna-ready.png。
查看日志
# 服务端日志(Controller 与负载均衡器) sky serve logs vllm --controller sky serve logs vllm --load-balancer # 按副本查看日志 sky serve logs vllm --replica 1更新与下线
# 用新 YAML 滚动更新服务(自动执行新一轮副本部署与流量切换) sky serve update vllm examples/serve/vllm.yaml # 下线服务并回收全部副本集群 sky serve down vllm更新采用版本化机制:每次更新产生一个新version,数据库version_specs表按(service_name, version)保存各版本规格(serve_state.py)。滚动更新期间新旧版本副本并存,Autoscaler 依据active_versions协调下线节奏(autoscalers.py)。
扩展阅读:在仓库中继续深挖
- 服务规范与全部配置字段:sky/serve/service_spec.py
- 负载均衡策略注册表与三种策略实现:sky/serve/load_balancing_policies.py
- 四种 Autoscaler 与滞回机制:sky/serve/autoscalers.py
- 副本状态机与故障恢复:sky/serve/replica_managers.py、sky/serve/serve_state.py
- 默认参数(超时、端口、决策间隔):sky/serve/constants.py
- 更多可直接运行的示例:vLLM 推理 examples/serve/vllm.yaml、Stable Diffusion examples/serve/stable_diffusion_service.yaml、多区域多副本的 Spot 策略示例 examples/serve/spot_policy
从架构上看,Sky Serve 把"多云多副本推理服务的运维"抽象成了三层:负载均衡器只负责流量分发与请求时间戳上报;Autoscaler 只负责根据指标产出扩缩容决策;Replica Manager 只负责把决策落实为一个个真实的 SkyPilot 集群生命周期。理解这条职责链,就能在遇到流量不均、伸缩迟缓或副本反复失败时,快速定位问题出在哪个组件。
【免费下载链接】skypilotThe AI Compute Platform for frontier teams. SkyPilot turns fragmented AI compute into one AI supercomputer, so frontier AI teams build custom intelligence faster.项目地址: https://gitcode.com/GitHub_Trending/sk/skypilot
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考