生产级部署指南
在生产环境运行 TensorFusion:控制面高可用、外置指标存储、告警、固定版本,以及可灰度、可回滚的迁移
默认的 helm install 只有 1 个 controller 副本、单机 GreptimeDB 和 1 个 Alertmanager 副本,适合试用,不适合生产。本文按实施顺序列出上生产前要做的调整:
- controller 和 Alertmanager 改为多副本。
- 指标存储换成高可用的 GreptimeDB。
- 把告警接到值班渠道。
- 固定各组件版本。
- 小步迁移工作负载,并保留回滚手段。
- 检查调度、组件更新和超卖策略。
前置条件
- 一个带 GPU 节点的 Kubernetes 集群,并能用
kubectl和helm访问。参见 在 Kubernetes 安装。 - 至少两个可以运行 controller 的节点,让副本分散开。
- 集群有默认 StorageClass,供 Alertmanager 存储卷使用。
- 一个 chart 之外的 GreptimeDB 实例,或者准备在第 2 步部署。
下文命令假定 release 名称和命名空间都是 tensor-fusion-sys。把你的覆盖项写进一个文件(本文称为 my-values.yaml)并纳入版本管理。所有配置项的含义见 Helm 安装 Values 配置。
第 1 步:控制面高可用
以 chart 自带的生产叠加文件为基础,再叠加你自己的文件:
helm upgrade --install tensor-fusion-sys tensor-fusion \
--repo https://download.tensor-fusion.ai \
--namespace tensor-fusion-sys --create-namespace \
-f https://download.tensor-fusion.ai/values-production.yaml \
-f my-values.yaml如果集群已接入 TensorFusion 控制台,把控制台安装命令里的 agent.agentId 和 agent.enrollToken 保留在 my-values.yaml 中。
values-production.yaml 做了这些改动:
| 组件 | 默认值 | 生产叠加文件 |
|---|---|---|
| controller 副本数 | 1 | 2 |
| controller 资源 | requests 50m CPU / 256Mi,limits 2 CPU / 2Gi | requests 1 CPU / 1Gi,limits 4 CPU / 4Gi |
| Alertmanager 副本数 | 1 | 3 |
| Alertmanager 存储 | emptyDir | 每个副本一个 5Gi 的 PersistentVolume |
| 内置 GreptimeDB | 安装 | 不安装(greptime.installStandalone: false) |
叠加文件只是去掉了内置 GreptimeDB,并没有配置替代实例。完成第 2 步之前,这次安装是不完整的。
controller 默认开启选主(启动命令中的 -leader-elect),同一时刻只有一个副本工作,它故障后由另一个接管。默认的 Pod 反亲和是 preferred。如果要保证副本一定不在同一节点,改成 required:
# my-values.yaml
controller:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchExpressions:
- key: tensor-fusion.ai/component
operator: In
values:
- operator第 2 步:使用高可用的 GreptimeDB
TensorFusion 把指标和系统日志存在 GreptimeDB 里,告警规则、自动伸缩和控制台的监控面板都从这里读数据。内置实例是单副本加一个 20Gi 存储卷,生产环境请二选一:
- Greptime Cloud:托管实例,运维工作量小。
- GreptimeDB Operator:在自己的 Kubernetes 里部署集群版,至少 3 个 datanode。
使用 Greptime Cloud,或其他需要用户名密码、走 HTTPS 的实例:
# my-values.yaml
greptime:
installStandalone: false
isCloud: true
host: <instance>.<region>.aws.greptime.cloud
port: 5001
db: <database>
user: <user>
password: <password>使用不带认证的自建实例:
# my-values.yaml
greptime:
installStandalone: false
isCloud: false
host: <greptimedb-frontend-service>.<namespace>.svc.cluster.local
port: 4002
db: publicisCloud: false 时,controller 通过 port 指定的 MySQL 端口查询,指标则写入 http://<host>:4000 的 public 库。请确认 Service 同时暴露了这两个端口。
greptime.password 最终会存进 Kubernetes Secret,但它同样明文出现在 values 文件里。建议从密钥管理系统取出后用 --set greptime.password=... 传入,不要提交到代码仓库。
查看监控面板
接入控制台的集群,在 TensorFusion 控制台里打开对应集群,就能看到 GPU、节点和工作负载的面板。数据仍然留在你的 GreptimeDB 中,控制台通过集群里的 agent 查询。
没有接入控制台时,把 Grafana 连到 GreptimeDB,基于 tf_* 表自建面板。表结构见监控指标定义。
第 3 步:配置告警
controller 针对 GreptimeDB 评估告警规则,把触发的告警发给 Alertmanager。发给哪个 Alertmanager、再转发到哪里,由你决定。
方式 A:使用 chart 自带的 Alertmanager
默认已启用,补上接收器和路由即可:
# my-values.yaml
alert:
alertManagerConfig:
global: {}
receivers:
- name: default-receiver
webhook_configs:
- url: https://<your-alert-webhook>
route:
receiver: default-receiver
group_wait: 10s
group_interval: 5m
repeat_interval: 3h内容遵循 Alertmanager 配置格式。默认配置里只有一个没有目标的接收器,所以在你添加之前,不会有任何通知发出集群。
方式 B:使用已有的 Alertmanager
关闭 chart 自带的 Alertmanager,让 controller 指向你的实例。地址必须带协议。Helm 会整体替换 command 列表,所以其余参数要原样保留:
# my-values.yaml
alert:
enabled: false
controller:
command:
- /manager
- -metrics-bind-address
- :9000
- -leader-elect
- -enable-auto-scale
- -enable-alert
- -enable-auto-expander
- -alert-manager-addr
- http://<alertmanager-service>.<namespace>.svc.cluster.local:9093接收器和路由在你的 Alertmanager 里配置,方法与其他告警相同。
新增或修改告警规则
chart 内置 15 条规则,覆盖 worker 被限流、分配失败、GPU 显存或算力打满、GPU 过热、节点和池的可用容量过低、GPU 空闲等情况,位于 dynamicConfig.alertRules。
新增规则时,先把 chart values.yaml 中默认的 alertRules 列表完整复制到你的文件里,再追加自己的规则。Helm 覆盖列表是整体替换,文件里只写新规则会把内置规则全部删掉。
# my-values.yaml
dynamicConfig:
alertRules:
# ... 从 values.yaml 复制的全部内置规则 ...
- name: GPUTemperatureWarning
query: |
SELECT node, pool, uuid, avg(temperature) AS avg_temperature
FROM tf_gpu_usage
WHERE temperature > {{ .Threshold }} AND {{ .Conditions }}
GROUP BY node, pool, uuid
threshold: 80
evaluationInterval: 30s
consecutiveCount: 3
severity: P2
summary: "GPU temperature above {{ .Threshold }}°C on {{ .node }} {{ .uuid }}"
description: "GPU {{ .uuid }} on node {{ .node }} in pool {{ .pool }} averages {{ .avg_temperature }}°C"
alertTargetInstance: "{{ .uuid }}"
runbookURL: "https://<your-runbook-url>"规则修改后 controller 会自动重新加载,不用重启。各字段含义见 dynamicConfig.alertRules。
改完后触发一条测试告警(例如加一条阈值必然被超过的规则),确认通知能送达你的渠道。
第 4 步:固定组件版本
chart 1.8.8 已经固定了大部分镜像版本,但仍有几个使用浮动 tag。建议把所有 tag 都显式写出来,避免 Pod 重启后拉到不同的版本:
| 组件 | values 键 | chart 默认值 |
|---|---|---|
| controller | controller.image.tag | 空,取 chart 的 appVersion(2.17.3) |
| hypervisor | cluster.hypervisorImage(完整镜像地址) | tensorfusion/tensor-fusion-hypervisor:2.15.0 |
| accelerator 库和 limiter 库 | providerConfigs.nvidia.images.middleware | tensorfusion/vgpu-provider-nvidia:1.3.9 |
| client | providerConfigs.nvidia.images.remoteClient | tensorfusion/tensor-fusion-client:v2.25.0 |
| worker | providerConfigs.nvidia.images.remoteWorker | tensorfusion/tensor-fusion-worker:v2.25.0 |
| vector | controller.vectorAgentImage | docker.io/timberio/vector:latest-alpine(浮动) |
| 内置 GreptimeDB | greptime.image.tag | latest(浮动) |
controller 和 hypervisor 镜像以同一个版本号一起发布,改其中一个时要核对另一个。可用的 tag 见 Docker Hub。
chart 本身也要固定:在 helm upgrade 命令里加上 --version <chart 版本>。
第 5 步:渐进式迁移工作负载
单个工作负载分批切换
给工作负载加上注解 tensor-fusion.ai/enabled-replicas,只让其中一部分副本使用 TensorFusion,其余副本保持原样,继续使用 nvidia.com/gpu。
apiVersion: apps/v1
kind: Deployment
# ...
spec:
replicas: 10
template:
metadata:
labels:
tensor-fusion.ai/enabled: "true"
annotations:
# 10 个副本中 3 个使用 TensorFusion,其余 7 个仍使用 nvidia.com/gpu
tensor-fusion.ai/enabled-replicas: "3"验证没问题后逐步调大这个数字。需要全部副本都使用 TensorFusion 时,删掉这个注解。
与 NVIDIA device plugin 共存
如果集群里还有 Pod 通过 NVIDIA device plugin 或 GPU Operator 申请 nvidia.com/gpu,请开启渐进式迁移,避免两套分配器把同一张 GPU 分给不同的 Pod:
# my-values.yaml
controller:
nvidiaOperatorProgressiveMigration: true开启后:
- 申请原生 GPU 资源的 Pod 改由
tensor-fusion-scheduler调度,并且只会落到 TensorFusion 尚未分配任何 GPU 的节点上。 - 已有 TensorFusion 分配的节点会被打上
PreferNoSchedule污点,节点重新空闲后自动移除。 - TensorFusion 不会分配已被 device plugin 分出去的 GPU。
修改这个开关后需要重建 hypervisor Pod,新配置才会生效。
正常情况下,Pod webhook 只处理带 tensor-fusion.ai/enabled: "true" 标签的 Pod。开启渐进式迁移后,集群里所有 Pod 的创建请求都会经过它,而默认的 failurePolicy 是 Fail。开启前请确保 controller 至少有 2 个副本。
回滚
要让工作负载退回原生 GPU 用法,把标签改为 "false",或者把启用副本数改为 "0",然后等 Pod 重建:
apiVersion: apps/v1
kind: Deployment
# ...
spec:
template:
metadata:
labels:
tensor-fusion.ai/enabled: "false"
annotations:
tensor-fusion.ai/enabled-replicas: "0"
spec:
containers:
- name: python
image: ...
resources:
limits:
# 迁移期间保留这一项,回滚时就不用再改别的地方
nvidia.com/gpu: 1 // [!code highlight]完整的迁移流程见迁移现有应用。
第 6 步:检查调度器配置
TensorFusion Pod 由 tensor-fusion-scheduler 调度。它是运行在 controller 进程里的 kube-scheduler,多了两个插件。配置来自 Helm 的 schedulerConfig,保存在 ConfigMap tensor-fusion-sys-config 的 scheduler-config.yaml 键中。
默认配置与原生 kube-scheduler 有两点不同:
- 优先把节点装满。
NodeResourcesFit使用RequestedToCapacityRatio策略,优先选择已有负载的节点;默认放置策略是NodeCompactGPULowLoad。空节点因此保持空闲,可以被回收。 - GPU 的权重高于 CPU 和内存。
nvidia.com/gpu的权重是 5,CPU 和内存是 1。
最常需要调整的插件参数:
schedulerConfig:
profiles:
- schedulerName: tensor-fusion-scheduler
# ...
pluginConfig:
- name: GPUResourcesFit
args:
maxWorkerPerNode: 256
vramWeight: 0.7
tflopsWeight: 0.3
- name: GPUNetworkTopologyAware
args:
mode: soft
topologySource: auto
maxAllowedTier: 1
unknownTopologyPolicy: treat-as-worst
preferLeastDamage: trueschedulerConfig 是一个整体对象,修改前先从 values.yaml 把默认内容完整复制出来。改完后重启 controller Deployment。各字段含义见 schedulerConfig。
验证调度结果
controller 提供模拟调度接口,不用真的创建 Pod:
kubectl port-forward deployment/tensor-fusion-sys-controller 8080:8080 -n tensor-fusion-sys
# 模拟调度一个 Pod,请求体是 YAML 格式的 Pod 清单
curl -X POST http://localhost:8080/api/simulate-schedule \
-H "Content-Type: application/yaml" \
-d 'apiVersion: v1
kind: Pod
metadata:
name: test-pod
namespace: default
labels:
tensor-fusion.ai/enabled: "true"
annotations:
tensor-fusion.ai/tflops-request: "100"
tensor-fusion.ai/vram-request: "16Gi"
tensor-fusion.ai/tflops-limit: "100"
tensor-fusion.ai/vram-limit: "16Gi"
spec:
schedulerName: tensor-fusion-scheduler
containers:
- name: test-container
image: nvidia/cuda'
# 导出分配器的内存状态
curl http://localhost:8080/api/allocation返回结果里有选中的节点和 GPU;调度失败时,会列出各候选在哪个过滤阶段被排除。
第 7 步:控制组件的更新方式
修改 GPU 池里 hypervisor、worker 或 client 的配置(例如换镜像)后,controller 可以自动滚动更新。是否更新、更新多快,由池上的 nodePoolRollingUpdatePolicy 决定:
| 字段 | 作用 | CRD 默认值 | chart 默认池 |
|---|---|---|---|
autoUpdateHypervisor | hypervisor 配置变化后重建 hypervisor Pod | false | true |
autoUpdateWorker | worker 配置变化后重建 worker Pod | false | false |
autoUpdateClient | 删除仍在使用旧 client 的业务 Pod,由其所属控制器按新配置重建 | false | false |
batchPercentage | 每批更新的 Pod 比例(百分比) | 100 | 20 |
batchInterval | 两批之间的等待时间 | 1s | 1s |
开关为 false 时,controller 只记录“配置未同步”,不动正在运行的 Pod。这些 Pod 下次重建时才会用上新配置。
生产环境建议 hypervisor 小批量更新,并留出观察时间。worker 和 client 保持关闭:更新它们会重启业务,应当放在你自己的发布流程里做。
apiVersion: tensor-fusion.ai/v1
kind: TensorFusionCluster
# ...
spec:
gpuPools:
- name: shared
isDefault: true
specTemplate:
nodeManagerConfig:
nodePoolRollingUpdatePolicy:
autoUpdateHypervisor: true
autoUpdateWorker: false
autoUpdateClient: false
batchPercentage: 20
batchInterval: 5mCRD 中还有 maintenanceWindow 和 maxDuration 两个字段,但当前版本的 controller 并不处理它们。请把“修改配置”这个动作本身安排在维护窗口内。
每次更新都先在非生产集群验证。参见组件更新。
第 8 步:采用保守的容量策略
超卖
默认池允许调度器分配物理 TFLOPS 的 20 倍(tflopsOversellRatio: 2000),并把主机内存的 50%、主机磁盘的 60% 计入额外 VRAM。这适合突发型的开发类负载。对延迟敏感的生产负载,建议调低:
# my-values.yaml
cluster:
pool:
capacityConfig:
oversubscription:
# 可调度 TFLOPS = 物理 TFLOPS 的 3 倍
tflopsOversellRatio: 300 // [!code highlight]
# 由主机内存承载的额外 VRAM,占主机内存的百分比
vramExpandToHostMem: 40 // [!code highlight]
# 由主机磁盘承载的额外 VRAM,占主机磁盘的百分比
vramExpandToHostDisk: 20VRAM 扩展会占用 GPU 节点的内存和磁盘,规划节点规格时要留出余量;也可以把两个百分比都设为 0,让 VRAM 严格等于物理显存。
请求与限额
不能被限流的工作负载,请求和限额之间的差距要小:
apiVersion: apps/v1
kind: Deployment
# ...
spec:
template:
metadata:
labels:
tensor-fusion.ai/enabled: "true"
annotations:
# 算力最多突发到请求值的 2 倍
tensor-fusion.ai/tflops-request: "100"
tensor-fusion.ai/tflops-limit: "200"
# VRAM 不允许突发
tensor-fusion.ai/vram-request: "3Gi"
tensor-fusion.ai/vram-limit: "3Gi"
spec:
containers:
- name: my-workload
resources:
requests:
memory: 1Gi
cpu: 1000m
limits:
memory: 4Gi
cpu: 4000m请求与限额相等的工作负载,默认 QoS 为 high。参见 QoS 级别与计价。
其他建议
- 节点自动伸缩。 GPU 节点需要自动增减的集群,在池上设置最小、预热和最大容量,既能应对突发流量,又能封住成本上限。参见云厂商集成(BYOC)。
- 日志级别。 默认池给 hypervisor 设置了
TF_LOG_LEVEL=info,生产环境保持这个级别。debug 级别的日志既刷屏又有额外开销。 - 远程 vGPU 的网络。 远程模式下,GPU 调用要在 client Pod 和 worker Pod 之间走网络。相关节点之间应使用低延迟、高带宽的网络。
- webhook 失败策略。 保持
controller.admissionWebhooks.failurePolicy: Fail。设为Ignore时,webhook 不可用期间创建的 TensorFusion Pod 会在没有分配 GPU 的情况下启动。
验证部署
# 2 个 controller Pod、3 个 Alertmanager Pod,全部 Running
kubectl get pods -n tensor-fusion-sys -o wide
# 集群和池都处于 Running
kubectl get tensorfusioncluster,gpupool
# GPU 节点和 GPU 已被发现
kubectl get gpunode,gpu
# controller 已连上 GreptimeDB,日志没有报错
kubectl logs -n tensor-fusion-sys deploy/tensor-fusion-sys-controller -c controller --tail=100最后删掉当前工作的 controller Pod,确认它重启期间新建的 TensorFusion Pod 仍能正常注入和调度。
下一步
- Helm 安装 Values 配置:chart 的全部配置项和默认值。
- 云厂商集成(BYOC):自动供给 GPU 节点。
- QoS 级别与计价:优先级与内部成本分摊。
- 问题处理手册
TensorFusion 文档