集群
Backend API
集群管理路由来自 llm_d_lens/cluster/router.py、llm_d_lens/cluster/bootstrap/router.py 和 llm_d_lens/cluster/sdk_discovery.py。
列出集群
列出由 Lens 管理的集群,并可选择包含尚未完成的草稿记录。该路由还会通过探测每个集群的 Kubernetes API 可达性来计算 ready 标志。
- REST API
- Python API
GET /api/cluster/clusters
请求:无请求体。
includeDrafts(boolean,查询参数,默认false):是否包含草稿集群记录。
响应:ClustersResponse,每个可见集群对应一个 ClusterRecord。
hfTokenSecret 返回该集群 HF_TOKEN Secret 的命名空间和名称;集群没有该 Secret 时为 null。接口只返回位置信息,令牌值保存在目标集群上,不会由 API 返回。
{
"items": [
{
"id": "cluster-a",
"name": "Primary cluster",
"description": "Shared production GPU cluster",
"createdAt": "2026-09-20T10:15:00Z",
"ready": true,
"proxy": {
"mode": "custom",
"httpProxy": "http://proxy.example:3128",
"httpsProxy": "http://proxy.example:3128",
"noProxy": "127.0.0.1,localhost,.svc"
},
"llmDRef": "v0.3.0",
"llmDBenchmarkRef": "main",
"llmDRepoPath": "/cache/llm-d/v0.3.0",
"llmDBenchmarkRepoPath": "/cache/llm-d-benchmark/main",
"hfTokenSecret": {
"namespace": "llm-d-bench-storage-44395349",
"name": "hf-token-c758ced0"
},
"draft": false
}
]
}
import asyncio
from llm_d_lens.cluster import service
async def main() -> None:
clusters = service.list_clusters(include_drafts=True)
ready_flags = await asyncio.gather(*(service.cluster_is_ready(cluster) for cluster in clusters))
for cluster, ready in zip(clusters, ready_flags):
print(cluster.id, cluster.name, ready)
asyncio.run(main())
获取支持的 llm-d 版本
返回当前 Lens 版本所支持的 llm-d 组件版本(llm-d、llm-d-router、llm-d-benchmark、Inference Payload Processor、Gateway API / Gateway API Inference Extension CRD 以及网关供应商)。只读;集群向导和每条安装路径都使用这些值。
- REST API
- Python API
GET /api/v1/versions
响应:
{
"llm_d": "v0.10.0",
"min_k8s_version": "v1.32.0",
"llm_d_router": "v0.11.0",
"llm_d_lensmark": "v0.8.0",
"llm_d_inference_payload_processor": "v0.1.0",
"k8s_gateway_api": "v1.5.1",
"k8s_gateway_api_inference_extension": "v1.5.0",
"gateway_providers": {
"istio": "1.29.4",
"envoy_gateway": "v1.8.1",
"envoy_ai_gateway": "v0.7.0",
"agentgateway": "v1.5.0"
}
}
from llm_d_lens import versions
stack = versions.describe()
print(stack["llm_d"], stack["llm_d_router"], stack["llm_d_lensmark"])
print(stack["gateway_providers"]["istio"])
列出 Lens 主机地址
为集群创建/编辑表单中的"Lens 地址"下拉框及自动填充功能,返回本 Lens 主机的候选 IP 地址/主机名。端口是 Lens 自身的实时服务端口,会自动添加。
- REST API
- Python API
GET /api/cluster/lens-hosts
请求:无参数。
响应:
{ "items": ["10.112.228.229"] }
from llm_d_lens.utils.hostinfo import local_host_addresses
print(local_host_addresses())
注册集群
通过上传的 kubeconfig 文件注册集群。该路由接受 multipart 表单数据,存储集群记录,并返回新集群以及(如有)创建的 Lens 会话 id。
llm-d 组件版本由 Lens 技术栈配置文件固定(参见获取支持的 llm-d 版本);llmDRef、llmDBenchmarkRef、routerVersion、gieVersion 和 ippVersion 为兼容性保留字段,但会被忽略。
- REST API
- Python API
POST /api/cluster/clusters
请求:multipart 表单数据,不是 JSON。
name(string,表单字段):集群显示名称。description(string,表单字段,可选):自由格式描述。file(file,表单字段):UTF-8 编码的 kubeconfig 上传文件。proxy(string,表单字段,可选):与ProxyConfigDTO匹配的 JSON 字符串。llmDRef(string,表单字段,可选):已忽略 ——llm-d版本来自技术栈配置文件。llmDBenchmarkRef(string,表单字段,可选):已忽略 ——llm-d-benchmark版本来自技术栈配置文件。gatewayProvider(string,表单字段,可选):共享网关供应商(envoy-ai-gateway|istio|agentgateway|gke)。gatewayPublicUrl(string,表单字段,可选):客户端用来访问网关的外部可达主机/IP —— Lens 会自动添加http://加暴露端口加/v1。为空时将根据暴露端口和集群节点推导。gatewayPort(integer,表单字段,可选):共享网关暴露所使用的 NodePort(30000–32767)。gatewayAuthzHost(string,表单字段):该集群用来访问 Lens 的主机/IP(对于带有 Lens 地址钩子的供应商为必填:envoy-ai-gateway/istio/agentgateway)。可从GET /api/cluster/lens-hosts中选取;Lens 会自动添加自身的服务端口,为空时 Lens 会自动填入该主机地址。inotifyMaxUserInstances(integer,表单字段,默认8192):Lens 应用于每个节点的每用户 inotify 实例数上限(fs.inotify.max_user_instances)。在运行大量 Pod 的节点上,应将其调高到 kind 默认值 128 之上。draft(boolean,表单字段,默认false):是否以草稿形式创建。
示例字段取值:
{
"name": "Primary cluster",
"description": "Shared production GPU cluster",
"file": "<uploaded kubeconfig.yaml UTF-8 file>",
"proxy": "{"mode":"custom","httpProxy":"http://proxy.example:3128","httpsProxy":"http://proxy.example:3128","noProxy":"127.0.0.1,localhost,.svc"}",
"llmDRef": "v0.3.0",
"llmDBenchmarkRef": "main",
"draft": true
}
响应:CreateClusterResponse。
{
"cluster": {
"id": "cluster-a",
"name": "Primary cluster",
"description": "Shared production GPU cluster",
"createdAt": "2026-09-23T09:00:00Z",
"ready": false,
"proxy": {
"mode": "custom",
"httpProxy": "http://proxy.example:3128",
"httpsProxy": "http://proxy.example:3128",
"noProxy": "127.0.0.1,localhost,.svc"
},
"llmDRef": "v0.3.0",
"llmDBenchmarkRef": "main",
"draft": true
},
"sessionId": "f4c15c0a-8c1f-4eb7-b4d7-ef83f22c2e9b"
}
from llm_d_lens.cluster import service
kubeconfig_text = """
apiVersion: v1
kind: Config
clusters: []
contexts: []
current-context: ""
users: []
""".strip()
cluster, session = service.create_cluster(
"Primary cluster",
"Shared production GPU cluster",
kubeconfig_text,
proxy_mode="custom",
http_proxy="http://proxy.example:3128",
https_proxy="http://proxy.example:3128",
no_proxy="127.0.0.1,localhost,.svc",
llm_d_ref="v0.3.0",
llm_d_lensmark_ref="main",
draft=True,
)
print(cluster.id, session.id)
更新集群
修改集群的可编辑设置,包括名称、描述、代理设置和草稿状态。kubeconfig 本身不在此补丁契约范围内。版本字段(llmDRef、llmDBenchmarkRef、routerVersion、gieVersion、ippVersion)由服务端根据技术栈配置文件管理,输入时会被忽略。
Gateway 命名空间前缀会附加该 Lens 集群注册的稳定短 ID。注册 ID 为 1234abcd 时,
默认命名空间为 lens-gateway-1234abcd;gatewayNamespace: "custom" 会解析为
custom-1234abcd。再次传入带后缀的值不会重复添加。集群响应返回实际命名空间,
以前使用共享 lens-gateway 默认值的记录也遵循此规则。
service.update_cluster_settings(..., gateway_namespace="custom") 的行为相同。
- REST API
- Python API
PATCH /api/cluster/clusters/{cluster_id}
请求体(ClusterSettingsUpdateRequest):
name(string,可选)description(string,可选)proxy(ProxyConfigDTO,可选)llmDRef(string,可选)llmDBenchmarkRef(string,可选)gatewayProvider(string,可选)gatewayNamespace(string,可选):最多 63 个字符的命名空间前缀,会附加注册的稳定短 ID。gatewayPort(integer,可选)gatewayPublicUrl(string,可选):外部可达的网关主机/IP —— Lens 会自动添加暴露端口;为空时将自动推导。gatewayAuthzHost(string):该集群用来访问 Lens 的主机/IP(对于带有 Lens 地址钩子的供应商为必填;Lens 会自动添加自身服务端口;为空时会自动填入该主机地址)。inotifyMaxUserInstances(integer,可选):应用于每个节点的每用户 inotify 实例数上限(默认8192)。draft(boolean,可选)
{
"name": "Primary cluster",
"description": "Shared production GPU cluster",
"proxy": {
"mode": "custom",
"httpProxy": "http://proxy.example:3128",
"httpsProxy": "http://proxy.example:3128",
"noProxy": "127.0.0.1,localhost,.svc"
},
"llmDRef": "v0.3.0",
"llmDBenchmarkRef": "main",
"draft": false
}
响应:CreateClusterResponse,包含更新后的记录。
{
"cluster": {
"id": "cluster-a",
"name": "Primary cluster",
"description": "Shared production GPU cluster",
"createdAt": "2026-09-23T09:00:00Z",
"ready": true,
"proxy": {
"mode": "custom",
"httpProxy": "http://proxy.example:3128",
"httpsProxy": "http://proxy.example:3128",
"noProxy": "127.0.0.1,localhost,.svc"
},
"llmDRef": "v0.3.0",
"llmDBenchmarkRef": "main",
"draft": false
},
"sessionId": null
}
from llm_d_lens.cluster import service
cluster = service.update_cluster_settings(
"cluster-a",
name="Primary cluster",
description="Shared production GPU cluster",
proxy_mode="custom",
http_proxy="http://proxy.example:3128",
https_proxy="http://proxy.example:3128",
no_proxy="127.0.0.1,localhost,.svc",
llm_d_ref="v0.3.0",
llm_d_lensmark_ref="main",
gateway_authz_host="10.0.0.5",
draft=False,
)
print(cluster.id, cluster.name)
创建集群 HF_TOKEN secret
在目标集群上创建保存 Hugging Face 令牌的独立 Kubernetes Secret,并把它的坐标记录到 Lens 集群记录上,供部署和模型缓存下载默认使用。这是创建集群向导的模型缓存步骤。令牌值只写入集群;Lens 只保存命名空间和名称。Secret 已存在时返回 409 secret_already_exists,此时请使用下面的轮换接口替换令牌。
- REST API
- Python API
POST /api/cluster/clusters/{cluster_id}/hf-token-secrets
请求体(HfTokenSecretCreateRequest):
namespace(string,必填):创建 Secret 的命名空间;不存在时自动创建。name(string,必填):Secret 名称。token(string,必填):保存在HF_TOKEN键下的 Hugging Face 令牌。
{
"namespace": "llm-d-bench-storage-44395349",
"name": "hf-token-c758ced0",
"token": "hf_exampleTokenValue"
}
响应:HfTokenSecretRef。
{
"namespace": "llm-d-bench-storage-44395349",
"name": "hf-token-c758ced0"
}
import asyncio
from llm_d_lens.cluster import registry, service
cluster = registry.get_cluster("cluster-a")
ref = asyncio.run(
service.create_hf_token_secret(
cluster,
"llm-d-bench-storage-44395349",
"hf-token-c758ced0",
"hf_exampleTokenValue",
)
)
print(ref["namespace"], ref["name"])
轮换集群 HF_TOKEN secret
在编辑集群面板中替换集群 HF_TOKEN Secret 中保存的 Hugging Face 令牌。与创建接口不同,这里使用 apply,因此已存在的 Secret 会被覆盖而不是报错。namespace 和 name 默认使用集群上已记录的坐标(hfTokenSecret),所以轮换令牌只需提供令牌;提供不同坐标会把集群指向该 Secret,并在其不存在时创建它。
轮换只重写这个集群级 Secret。部署和模型缓存下载在运行时派生的命名空间内 llm-d-hf-token 副本会在下一次部署或下载时重新派生,因此已在运行的工作负载在重建之前仍使用旧令牌。
- REST API
- Python API
PUT /api/cluster/clusters/{cluster_id}/hf-token-secret
请求体(HfTokenSecretUpdateRequest):
namespace(string,可选):默认使用集群已记录的 Secret 命名空间。name(string,可选):默认使用集群已记录的 Secret 名称。token(string,必填):新的 Hugging Face 令牌;空值返回422 invalid_secret_token。
{
"token": "hf_rotatedTokenValue"
}
响应:HfTokenSecretRef,返回实际写入的坐标。
{
"namespace": "llm-d-bench-storage-44395349",
"name": "hf-token-c758ced0"
}
import asyncio
from llm_d_lens.cluster import registry, service
cluster = registry.get_cluster("cluster-a")
# 使用集群上已记录的坐标原地轮换。
ref = asyncio.run(service.update_hf_token_secret(cluster, "hf_rotatedTokenValue"))
print(ref["namespace"], ref["name"])
# 也可以把集群指向另一个 Secret。
ref = asyncio.run(
service.update_hf_token_secret(
cluster,
"hf_rotatedTokenValue",
namespace="llm-d-bench-storage-44395349",
name="hf-token-rotated",
)
)
启动集群软件下载
为集群下载由 Lens 技术栈配置文件固定的 llm-d 和 llm-d-benchmark 版本。处理函数会调度后台下载任务并立即返回当前状态快照;请求体会被忽略。
- REST API
- Python API
POST /api/cluster/clusters/{cluster_id}/software-downloads
请求体(SoftwareDownloadRequest):可选且会被忽略 —— 系统会下载配置文件中固定的版本。
{}
响应:SoftwareDownloadStatusResponse。
{
"llmD": {
"ref": "v0.2.0",
"state": "downloading",
"path": null,
"error": null
},
"llmDBenchmark": {
"ref": "main",
"state": "downloading",
"path": null,
"error": null
}
}
import asyncio
from llm_d_lens.cluster.router import _download_and_record
from llm_d_lens.cluster import repo_downloads
async def main() -> None:
await _download_and_record("cluster-a", "llm-d", "v0.2.0")
await _download_and_record("cluster-a", "llm-d-benchmark", "main")
print(repo_downloads.get_status("cluster-a", "llm-d"))
print(repo_downloads.get_status("cluster-a", "llm-d-benchmark"))
asyncio.run(main())
获取集群软件下载状态
返回集群固定的 llm-d 和 llm-d-benchmark 引用的当前内存态下载状态。使用此端点来轮询下载进度。
- REST API
- Python API
GET /api/cluster/clusters/{cluster_id}/software-downloads
请求:无请求体。
cluster_id(string,路径参数):目标集群 id。
响应:SoftwareDownloadStatusResponse。
{
"llmD": {
"ref": "v0.2.0",
"state": "ready",
"path": "/cache/llm-d/v0.2.0",
"error": null
},
"llmDBenchmark": {
"ref": "main",
"state": "ready",
"path": "/cache/llm-d-benchmark/main",
"error": null
}
}
from llm_d_lens.cluster import repo_downloads
llm_d = repo_downloads.get_status("cluster-a", "llm-d")
llm_d_lensmark = repo_downloads.get_status("cluster-a", "llm-d-benchmark")
print(llm_d)
print(llm_d_lensmark)
启动集群镜像预拉取
为所选加速器在每个集群节点上预拉取 Lens 固定版本的 llm-d 镜像。这些镜像包括 llm-d-router EPP 和 P/D sidecar,以及各加速器对应的模型服务镜像;版本来自 Lens 技术栈配置文件,而非请求内容。一个特权 DaemonSet 会在每个节点上运行主机的 crictl;可通过下方的 GET 端点轮询每个节点的就绪状态。
- REST API
- Python API
POST /api/cluster/clusters/{cluster_id}/images/prepull
请求体:
accelerators(string[]):需要拉取模型服务镜像的加速器 id(例如["nvidia","intel"])。
{
"accelerators": ["nvidia", "intel"]
}
响应:总体状态、ready/desired 节点数、镜像列表以及每个节点的就绪状态。
{
"state": "pulling",
"desired": 2,
"ready": 1,
"images": [
{ "name": "ghcr.io/llm-d/llm-d-router-endpoint-picker:v0.11.0", "ready": 2, "desired": 2, "state": "ready" },
{ "name": "ghcr.io/llm-d/llm-d-router-disagg-sidecar:v0.11.0", "ready": 2, "desired": 2, "state": "ready" },
{ "name": "ghcr.io/llm-d/llm-d-cuda:v0.9.0", "ready": 1, "desired": 2, "state": "pulling" }
],
"nodes": [{ "node": "worker-1", "phase": "Running", "ready": true, "restarts": 0 }]
}
当所有节点都已拉取该镜像时 images[].state 为 ready;拉取进行中为 pulling;若某节点拉取不断崩溃重试则为 failed。
from llm_d_lens.cluster import images
status = await images.start_image_prepull("cluster-a", ["nvidia", "intel"])
print(status["state"], status["ready"], status["desired"])
获取集群镜像预拉取状态
获取集群当前的镜像预拉取状态:总体状态、ready/desired 节点数以及每个拉取 Pod 所在节点的就绪状态。
- REST API
- Python API
GET /api/cluster/clusters/{cluster_id}/images/prepull
响应:与启动响应结构相同。
{
"state": "ready",
"desired": 2,
"ready": 2,
"images": [
{ "name": "ghcr.io/llm-d/llm-d-cuda:v0.9.0", "ready": 2, "desired": 2, "state": "ready" }
],
"nodes": [
{ "node": "worker-1", "phase": "Running", "ready": true, "restarts": 0 },
{ "node": "worker-2", "phase": "Running", "ready": true, "restarts": 0 }
]
}
from llm_d_lens.cluster import images
status = await images.image_prepull_status("cluster-a")
print(status["state"])
应用固定版本的 Gateway API / Gateway API Inference Extension CRD
应用由 Lens 技术栈配置文件固定版本的 Gateway API(gateway.networking.k8s.io)和 Gateway API Inference Extension(inference.networking.k8s.io)CRD 包。该操作是幂等的;集群创建过程中也会执行此操作。
- REST API
- Python API
POST /api/cluster/clusters/{cluster_id}/crds
响应:每个 CRD 包的执行结果。
{
"gateway_api": { "ok": true, "message": "" },
"gateway_api_inference_extension": { "ok": true, "message": "" }
}
from llm_d_lens.cluster import gateway_crds
result = await gateway_crds.apply_gateway_crds("cluster-a")
print(result["gateway_api"]["ok"])
获取 Gateway API / GIE CRD 状态
报告集群上是否存在固定版本的 Gateway API 和 Gateway API Inference Extension CRD(每个包的状态为 ready/missing)。
- REST API
- Python API
GET /api/cluster/clusters/{cluster_id}/crds
{
"gateway_api": "ready",
"gateway_api_inference_extension": "ready"
}
from llm_d_lens.cluster import gateway_crds
status = await gateway_crds.gateway_crds_status("cluster-a")
print(status["gateway_api"])
检查集群 Kubernetes 版本
探测集群 Kubernetes API 的可达性及其服务端版本,并与 Lens 技术栈配置文件固定的最低 Kubernetes 版本(min_k8s_version)进行比较。创建向导会在 kubeconfig 上传后调用此端点,当集群不可达或版本过低时阻止继续操作。
- REST API
- Python API
GET /api/cluster/clusters/{cluster_id}/kubernetes-version
{
"reachable": true,
"version": "1.34",
"min_k8s_version": "v1.32.0",
"supported": true
}
from llm_d_lens.hardware.version import server_kubernetes_version
from llm_d_lens.versions import k8s_version_supports
parsed = await server_kubernetes_version("cluster-a")
version = f"{parsed[0]}.{parsed[1]}" if parsed else None
print(version, bool(version) and k8s_version_supports(version))
删除集群
删除由 Lens 管理的集群记录,并清理相关状态。仅当需要将集群从 Lens 中彻底移除时才应使用此操作。
该接口会执行以下远端清理:
- 删除注册独立的 Gateway 命名空间(通常为
lens-gateway-<cluster-id>),以及其中的 Gateway、IPP、鉴权资源和路由。命名空间不存在视为成功,不删除旧共享命名空间、其他注册的命名空间或共享 Gateway 控制器。 - 删除
hfTokenSecret中记录的集群HF_TOKENSecret;当其所在命名空间没有理由在集群之后继续存在时,该命名空间也会一并删除。以下情况属于此类:Lens 自有的llm-d-bench-storage-{cluster_id}命名空间、Lens 为该 Secret 创建的任何命名空间(带有app.kubernetes.io/managed-by: llm-d-lens标签),以及删除该 Secret 后仅剩 Kubernetes 和集群插件注入到每个命名空间中的对象(default服务账号及其令牌 Secret、kube-root-ca.crt等 CA 证书包 ConfigMap)的命名空间。仍包含任何其他对象(包括自定义资源)的命名空间会被保留;default、kube-system、kube-public和kube-node-lease永远不会被删除。如果无法列出命名空间内容,则保留该命名空间。 - 对于草稿集群,仍挂在其上的每个存储卷都会连同其
PersistentVolumeClaim和PersistentVolume对象一起删除。底层 NFS 或主机路径存储上的模型文件会保留,因为PersistentVolume使用Retain回收策略。
Gateway 命名空间必须清理成功:失败或超时返回 502 cluster_gateway_cleanup_failed,
并保留集群记录和 kubeconfig 以便重试。HF-token 和草稿存储清理仍为尽力而为。
非草稿集群仍有可用部署或存储卷时返回 409,请先清理这些资源。
- REST API
- Python API
DELETE /api/cluster/clusters/{cluster_id}
请求:无请求体。
cluster_id(string,路径参数):目标集群 id。
响应:204 No Content。此端点不返回 JSON 响应体。
失败:502 cluster_gateway_cleanup_failed 表示独立 Gateway 命名空间无法删除;
409 cluster_has_available_deployments 或 cluster_has_storage_volumes
表示已完成注册的集群仍有对应资源。
必要清理失败时,service.delete_cluster() 抛出包含相同状态和错误码的 ClusterOverviewError。
import asyncio
from llm_d_lens.cluster import service
async def main() -> None:
await service.delete_cluster("cluster-a")
asyncio.run(main())
获取集群会话
获取或创建某个集群 id 对应的活动 Lens 会话元数据。当其他 API 接口需要会话 id 而非集群 id 时使用此端点。
- REST API
- Python API
GET /api/cluster/session
请求:无请求体。
clusterId(string,查询参数):目标集群 id。
响应:ClusterSessionResponse。
{
"cluster": {
"id": "cluster-a",
"name": "Primary cluster",
"description": "Shared production GPU cluster",
"createdAt": "2026-09-23T09:00:00Z",
"ready": true,
"proxy": {
"mode": "custom",
"httpProxy": "http://proxy.example:3128",
"httpsProxy": "http://proxy.example:3128",
"noProxy": "127.0.0.1,localhost,.svc"
},
"draft": false
},
"sessionId": "f4c15c0a-8c1f-4eb7-b4d7-ef83f22c2e9b"
}
from llm_d_lens.cluster import service
cluster, session = service.get_cluster_session("cluster-a")
print(cluster.id, session.id)
获取集群概览
返回一个集群的实时 Kubernetes 概览信息,包括节点、GPU、命名空间、缓存镜像以及组件健康状况摘要。该路由还会在返回实时快照的同时报告当前的集群记录。
- REST API
- Python API
GET /api/cluster/overview
请求:无请求体。
clusterId(string,查询参数):目标集群 id。
响应:OverviewResponse。
{
"cluster": {
"id": "cluster-a",
"name": "Primary cluster",
"description": "Shared production GPU cluster",
"createdAt": "2026-09-23T09:00:00Z",
"ready": true,
"proxy": {
"mode": "custom",
"httpProxy": "http://proxy.example:3128",
"httpsProxy": "http://proxy.example:3128",
"noProxy": "127.0.0.1,localhost,.svc"
},
"draft": false
},
"fetchedAt": "2026-09-23T09:18:00Z",
"kubernetes": {
"components": {
"intelDevicePlugin": {
"installed": true,
"ready": 1,
"desired": 1,
"status": "ready"
},
"monitoring": {
"installed": true,
"ready": 3,
"desired": 3,
"status": "ready"
}
},
"nodes": [
{
"name": "worker-1",
"version": "v1.31.0",
"osImage": "Ubuntu 22.04 LTS",
"ready": true,
"schedulingDisabled": false,
"cpu": 64.0,
"gpu": true,
"gpuCount": 4,
"gpuByProfile": {
"intel-xpu": 4
},
"vramBytes": 274877906944,
"role": "worker",
"cachedImages": [
"ghcr.io/llm-d/serving:latest"
]
}
],
"namespaces": [
{
"name": "default",
"deployments": [
{
"name": "example-deploy",
"desired": 1,
"ready": 1,
"available": 1
}
],
"ready": 1
}
],
"hardware": {
"nodes": 3,
"cpuCores": 192.0,
"memoryBytes": 824633720832,
"memoryUsagePercent": 25.0,
"memoryAvailableBytes": 618475290624,
"diskBytes": 7619773423616,
"diskUsedBytes": 4178392940544,
"diskAvailableBytes": 3441380483072,
"diskUsagePercent": 54.8,
"gpuNodes": 2,
"gpuCount": 8,
"totalGpuCount": 8,
"accelerators": [
{
"id": "intel-xpu",
"gpuCount": 8
}
],
"vramBytes": 549755813888,
"allocatedGpuCount": 4,
"availableGpuCount": 4,
"usableGpuCount": 8
}
}
}
hardware 在给出使用率的同时也给出剩余容量,调用方无需自己换算。
diskUsedBytes/diskAvailableBytes 直接来自各节点 kubelet 的 Summary API,
因此数值是精确的,并且在本集群自身的 Prometheus 不可用时依然可以上报。
memoryAvailableBytes 则由 memoryUsagePercent(Prometheus 上报的各节点
使用率的平均值)换算而来,因为内存没有字节级的数据源。这三个字段在其所依赖的
读数不可用时均为 null——null 表示未知,而不是剩余为零。
import asyncio
from llm_d_lens.cluster import registry, service
async def main() -> None:
cluster = registry.require_cluster("cluster-a")
overview = await service.build_overview(cluster)
print(overview)
asyncio.run(main())
读取规划发现资源
为一个活动的集群会话读取某个受许可列表限制的规划发现资源。此端点有意限制为固定的 kubectl 读取操作,当读取失败或返回不可用的 JSON 时会返回 {"result": null}。
- REST API
- Python API
GET /api/cluster/sessions/{session_id}/planning-discovery/{resource}
请求:无请求体。
session_id(string,路径参数):活动集群会话 id。resource(string,路径参数):version、nodes、deviceclasses、resourceslices或runtimeclasses之一。
响应:包含 result 键的包装对象。
{
"result": {
"items": []
}
}
import asyncio
import json
from llm_d_lens.cluster import sessions
from llm_d_lens.utils.kubernetes import run_kubectl
async def main() -> None:
session = sessions.require_active_session("f4c15c0a-8c1f-4eb7-b4d7-ef83f22c2e9b")
result = await run_kubectl(["get", "nodes", "-o", "json"], timeout=10, cluster_id=session.id)
payload = json.loads(result.stdout) if result.ok else None
print({"result": payload if isinstance(payload, dict) else None})
asyncio.run(main())
发现引导阶段的 SSH 主机密钥
在不进行身份验证或发送凭据的情况下,从每个目标读取其 SSH 主机密钥。主机规格和 IP 范围会在服务端展开,因此每个响应项都标识了一个确切的 host:port。在 UI 中确认之前,请将这些 SHA-256 指纹与可信来源进行比对。
- REST API
POST /api/cluster/bootstrap/host-keys
{
"nodes": [{ "host": "10.0.0.10-12", "port": 22 }]
}
每个响应项都包含 host、port、algorithm 和 fingerprint;无法访问的主机则会包含 error 字段。
{
"items": [
{ "host": "10.0.0.10", "port": 22, "algorithm": "ssh-ed25519", "fingerprint": "SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" },
{ "host": "10.0.0.11", "port": 22, "algorithm": "ssh-ed25519", "fingerprint": "SHA256:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" },
{ "host": "10.0.0.12", "port": 22, "error": "Connection timed out" }
]
}
运行引导阶段预检
针对一台或多台目标机器运行 Kubespray 引导阶段预检。响应会报告每个节点的状态,以及各项检查是通过、失败还是被跳过。
- REST API
- Python API
POST /api/cluster/bootstrap/preflight
请求体(BootstrapPreflightRequest):
nodes(BootstrapNodeInput[]):包含主机、SSH 及角色信息的多行数据。hostKeys(BootstrapHostKeyPin[]):每个展开后的host:port对应的确切已确认指纹。
{
"nodes": [
{
"host": "10.0.0.1",
"port": 22,
"roles": [
"control-plane"
],
"username": "root"
},
{
"host": "10.0.0.2",
"port": 22,
"roles": [
"worker"
],
"username": "root"
}
],
"hostKeys": [
{ "host": "10.0.0.1", "port": 22, "fingerprint": "SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" },
{ "host": "10.0.0.2", "port": 22, "fingerprint": "SHA256:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" }
]
}
响应:BootstrapPreflightResponse。
{
"items": [
{
"host": "10.0.0.1",
"roles": [
"control-plane"
],
"state": "passed",
"checks": [
{
"id": "ssh",
"label": "SSH connectivity",
"status": "passed",
"detail": "reachable"
},
{
"id": "sudo",
"label": "Passwordless sudo",
"status": "passed"
}
]
},
{
"host": "10.0.0.2",
"roles": [
"worker"
],
"state": "passed",
"checks": [
{
"id": "ssh",
"label": "SSH connectivity",
"status": "passed",
"detail": "reachable"
}
]
}
],
"allPassed": true
}
import asyncio
from llm_d_lens.cluster.bootstrap.dto import BootstrapHostKeyPin, BootstrapNodeInput
from llm_d_lens.cluster.bootstrap.service import run_preflight_check
async def main() -> None:
pins = [
BootstrapHostKeyPin(host="10.0.0.1", port=22, fingerprint="SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
BootstrapHostKeyPin(host="10.0.0.2", port=22, fingerprint="SHA256:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"),
]
nodes = await run_preflight_check([
BootstrapNodeInput(host="10.0.0.1", roles=["control-plane"]),
BootstrapNodeInput(host="10.0.0.2", roles=["worker"]),
], pins)
for node in nodes:
print(node.host, node.preflight_state)
asyncio.run(main())
启动引导作业
在预检成功后启动一个基于 Kubespray 的长时间运行集群引导作业。响应会立即返回引导作业 id,而实际的配置过程会在后台继续进行。
- REST API
- Python API
POST /api/cluster/bootstrap
请求体(BootstrapCreateRequest):
nodes(BootstrapNodeInput[]):要配置的节点。hostKeys(BootstrapHostKeyPin[]):每个展开后的host:port对应的确切已确认指纹。proxy(BootstrapProxyConfig,可选):目标节点的代理配置。
{
"nodes": [
{
"host": "10.0.0.1",
"port": 22,
"roles": [
"control-plane"
],
"username": "root"
},
{
"host": "10.0.0.2",
"port": 22,
"roles": [
"worker"
],
"username": "root"
}
],
"hostKeys": [
{ "host": "10.0.0.1", "port": 22, "fingerprint": "SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" },
{ "host": "10.0.0.2", "port": 22, "fingerprint": "SHA256:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" }
],
"proxy": {
"mode": "custom",
"httpProxy": "http://proxy.example:3128",
"httpsProxy": "http://proxy.example:3128",
"noProxy": "127.0.0.1,localhost,.svc"
}
}
响应:BootstrapCreateResponse。
{
"bootstrapId": "bootstrap-5b7fd3a81290"
}
import asyncio
from llm_d_lens.cluster.bootstrap.dto import BootstrapHostKeyPin, BootstrapNodeInput, BootstrapProxyConfig
from llm_d_lens.cluster.bootstrap import service
async def main() -> None:
pins = [
BootstrapHostKeyPin(host="10.0.0.1", port=22, fingerprint="SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
BootstrapHostKeyPin(host="10.0.0.2", port=22, fingerprint="SHA256:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"),
]
job = service.create_job(
[
BootstrapNodeInput(host="10.0.0.1", roles=["control-plane"]),
BootstrapNodeInput(host="10.0.0.2", roles=["worker"]),
],
pins,
proxy=BootstrapProxyConfig(
mode="custom",
httpProxy="http://proxy.example:3128",
httpsProxy="http://proxy.example:3128",
noProxy="127.0.0.1,localhost,.svc",
),
)
await service.run_bootstrap_job(job.id)
print(job.id)
asyncio.run(main())
获取引导状态
获取引导作业的当前状态。当作业首次达到 succeeded 状态时,该响应还可以携带一次性的 kubeconfig 交接数据。
- REST API
- Python API
GET /api/cluster/bootstrap/{bootstrap_id}
请求:无请求体。
bootstrap_id(string,路径参数):引导作业 id。
响应:BootstrapStatusResponse。
{
"id": "bootstrap-5b7fd3a81290",
"phase": "succeeded",
"logTail": "PLAY [all]
ok: [10-0-0-1]",
"error": null,
"kubeconfig": "apiVersion: v1
kind: Config
clusters: []
contexts: []
users: []
"
}
from llm_d_lens.cluster.bootstrap import service
job = service.get_job("bootstrap-5b7fd3a81290")
kubeconfig = service.consume_kubeconfig(job.id) if job.phase == "succeeded" else None
print(job.phase, kubeconfig)
取消引导作业
取消一个正在运行的引导作业,使其停止配置并不再继续。一旦收到取消请求,此端点会立即返回。
- REST API
- Python API
POST /api/cluster/bootstrap/{bootstrap_id}/cancel
请求:无请求体。
bootstrap_id(string,路径参数):引导作业 id。
响应:204 No Content。此端点不返回 JSON 响应体。
from llm_d_lens.cluster.bootstrap import service
service.cancel_job("bootstrap-5b7fd3a81290")
还有一些其他端点可用于网关端口可用性检查、HF token 密钥创建、模型密钥列表、节点 cordon/uncordon 以及旧版集群概览会话断开路由 —— 详见完整的 OpenAPI 参考文档。
完整的请求/响应模式请参见 FastAPI OpenAPI 模式(使用 npm run docs:api 重新生成)。