Kubernetes混部部署TensorFlow训练任务:TFJob调度与监控实践

发布时间:2026/9/15 15:27:04
Kubernetes混部部署TensorFlow训练任务:TFJob调度与监控实践
前阵子我接到一个需求团队里有一批TensorFlow机器学习训练任务原本单独跑在一台GPU服务器上维护成本高资源利用率也差而且和线上服务各自持有机器高峰不够用、低谷白白浪费。后来我们决定把这些离线训练任务挪进kubernetes集群和在线服务做混部同时把资源监控补全。折腾完方案最终落在Kubeflow的tf-operator Prometheus Grafana这条链路上。这篇把整个落地过程完整写出来包括混部调度策略、TFJob编写、监控组件搭建以及我踩过的那些坑。这里涉及一个老生常谈又非常关键的点在k8s上跑TensorFlow训练任务不是随便创建几个Deployment就能完事的。分布式训练有参数服务器、多worker协调、任务失败重试这些需求手动用Deployment加Service去编排维护起来非常痛苦。tf-operator通过TFJob这个自定义资源把整个训练任务的编排逻辑都封装好了提交一个YAML就能跑起分布式训练。监控部分Prometheus负责采集数据Grafana负责展示两者结合基本是k8s可观测性方案里的标配。这篇内容适合正在做离线任务上k8s、需要跑TensorFlow训练又想把资源监控补齐的运维和开发同学。不涉及特别高深的理论以能复用的实操为主每个环节我都尽量解释清楚为什么这么做。1. 项目背景与整体方案拆解1.1 为什么要做离线任务混部部署先说说业务背景。我们这边有两类任务一类是线上推理服务时延要求高流量有明显的峰谷变化另一类是离线训练任务比如模型周期性重训、数据预处理特点是耗时大、对时延不敏感、可以被打断重跑。过去这两类任务各自占一批机器线上服务在低谷期大量CPU和内存闲置离线任务又在高峰期排队等资源。这个浪费其实很惊人运维同学翻监控一看集群平均利用率还不到20%。混部混合部署的核心思路就是把在线服务和离线任务放到同一个k8s集群中让离线任务去“吃”在线服务用不完的碎片资源。理想状态下离线任务不影响在线服务的SLO在线服务的资源波谷又能被离线任务填上整体集群利用率能明显提升。实现手段主要包括用namespace做逻辑隔离、用ResourceQuota限制总用量、用PriorityClass区分优先级、用affinity/anti-affinity控制调度位置。这些都是k8s原生能力不需要额外引入组件我在4.4节会展开讲。这里有一个很重要的预期管理问题。混部不是把两个任务随便丢进一个集群就完事如果离线任务没有做好资源限制它可能会疯狂抢占CPU和内存把在线服务拖垮。所以混部的前提是“可控”每个任务都有明确的资源请求和上限优先级有明确的层级调度规则有明确的目标。这个设计思路直接影响后面TFJob的写法。1.2 为什么选Kubeflowtf-operator这条路线在动手之前我们对比过几种跑TensorFlow训练任务的方式。最原始的做法是直接在裸机上跑配合screen或supervisor守护进程扩展性基本为零后来有人用docker compose在单机管理容器化是做到了但跨节点的分布式网络还是要自己写编排逻辑再后来把训练任务做成普通的k8s Deploymentk8s能帮忙保证Pod数量但PS和Worker是一套有状态且有依赖关系的组件光靠Deployment加Service处理起来很别扭。Kubeflow提供了一整套机器学习平台包括Notebook、Pipeline、Katib、KServe以及负责训练编排的tf-operator。tf-operator的核心是TFJob这个CRD。你只需要声明需要多少个PS、多少个Worker、用什么镜像和资源限制tf-operator会自动完成Pod创建、Service关联、失败重试、状态更新。这样一来分布式训练任务的管理方式就从“人肉进程编排”变成了“声明式的k8s原生资源”和Deployment管理无状态应用的理念保持一致。我整理过一张对比表方便大家看选型逻辑部署方式扩展性分布式编排成本失败恢复适用场景裸机脚本差极高手动单机小任务docker compose单机高手动本地调试k8s Deployment好较高自动只保证副本数无状态服务TFJobtf-operator好低自动感知训练任务状态分布式训练1.3 从提交训练任务到看见监控数据整条链路是什么样的整个落地流程可以用一条链路概括k8s集群准备 - 部署tf-operator - 提交TFJob - TensorFlow训练容器启动 - 通过Prometheus采集节点、容器、GPU、训练指标 - 在Grafana中展示。部署的时候我建议把环节拆开做先跑通训练再补监控每一步都验证清楚再进入下一步否则问题叠加在一起排错会非常痛苦。具体顺序我是这样安排的第一准备k8s集群包括GPU节点驱动和runtime第二单独部署tf-operator确认TFJob CRD和controller正常第三写一个最简单的TFJob跑通训练流程确认日志和状态流转符合预期第四部署kube-prometheus-stack让节点和Pod的基础监控数据先动起来第五补充GPU指标和训练任务自定义指标最后在Grafana上配置面板和告警。后面章节就按这个顺序展开。提示新手特别容易在一开始就想把Kubeflow完整平台、监控告警、面板全部装上结果既不知道哪里出了问题也不知道该看哪个日志。我的经验是先通后精先让一条链路跑起来再逐步加东西。2. 环境准备k8s集群规划和基础概念2.1 硬件与集群拓扑怎么规划这里说的规划不是让你照搬而是给一个参考基线。集群规模取决于训练任务大小如果只跑单机多卡的模型三台GPU节点就够如果要跑大型分布式训练PS和Worker加起来可能需要几十个Pod节点数就要相应扩大。我们这边的环境是三个master节点加六个worker节点其中四个worker带GPU。master节点建议至少4C8G系统盘用SSD因为etcd的IO性能直接影响集群稳定性。GPU节点的要求是NVIDIA驱动版本要匹配CUDA同时安装nvidia-container-toolkit否则k8s调度器无法感知GPU资源。GPU节点准备好后建议先给节点打标签和污点。打标签是为了让训练任务通过nodeSelector精准调度kubectl label node node-gpu-01 node-typegpu打污点是为了确保普通在线服务不会被调度到GPU节点上只有声明了toleration的训练Pod才能使用GPU资源kubectl taint nodes node-gpu-01 gpureserved:NoSchedule这样做的好处是GPU节点变成离线训练任务的“专属资源池”同时又保留了在线服务在极端情况下抢占GPU节点的可能性。这个设计在后面4.4节还会用到。存储方面也要提前想清楚。训练数据、checkpoint、模型输出一般不建议放在Pod本地盘上因为Pod重建后数据就丢了。我们用的是共享存储把训练数据通过PVC挂载到训练Pod里。如果条件不允许至少也要把模型保存目录挂到持久化存储上否则Worker训练到一半被重新调度已保存的模型就没了。2.2 先讲清楚k8s和docker的分工我在梳理这个项目时发现很多同学对k8s和docker的关系比较模糊群里几乎每隔几天就会有人问“k8s和docker到底有什么区别”。简单说docker解决的是“单机容器怎么运行”的问题k8s解决的是“一批容器怎么跨机器调度、服务发现、故障恢复”的问题。你可以把docker里的镜像、容器理解成一间拎包入住的公寓而k8s是帮你管理整栋楼的物业决定每个公寓什么时候入住、住在哪个房间、房间坏了怎么处理。实操中最大的感受是kubectl和docker命令操作对象不一样。docker ps对应的是容器kubectl get pods对应的是Pod。Pod是k8s的最小调度单元一个Pod里面可以跑一个或多个容器这些容器共享网络命名空间和存储卷。在k8s里你不直接操作容器而是通过Deployment、TFJob这类资源描述目标状态由控制器来创建和管理Pod。理解了这一点后面看TFJob的YAML就不会发懵。还有一点值得注意k8s的容器运行时不只有docker现在很多集群已经在使用containerd。这意味着你平时可能在服务器上找不到docker命令但kubectl依然能正常工作。排查问题时不要只看docker相关状态而要用kubectl和crictl这套工具链。2.3 基础工具和集群验证进入实操之前先把要用的工具装好kubectl客户端版本和集群版本不要差太多、helm后面装监控会用到、kustomize安装tf-operator会用到、kubectx/kubens多集群切换方便。这些工具装完我们先用两条命令确认集群健康kubectl version --short kubectl get nodes确认所有节点是Ready状态再确认核心组件正常kubectl get pods -A | grep -E coredns|kube-proxy|metrics-server如果有GPU节点还要确认GPU资源被k8s正确识别kubectl describe node node-gpu-01 | grep -i capacity如果输出里没有nvidia.com/gpu: 4说明nvidia-device-plugin没有部署或者驱动有问题。这个问题不解决后面TFJob即使写了GPU limitPod也只能一直Pending。看到nvidia.com/gpu出现后GPU资源这一层的准备就结束了。3. 部署Kubeflow和tf-operator3.1 只装tf-operator还是装完整KubeflowKubeflow是一个偏重量级的平台通常包含Dashboard、Notebook Controller、Pipeline、Katib、KServe等十几个组件。如果你的目标是快速在k8s上跑TensorFlow训练任务我建议只部署tf-operator而不是安装整套Kubeflow。只部署tf-operator能省掉大量组件带来的存储、网络和权限副作用问题定位也简单。我们的最终方案就是基于独立部署的tf-operator。如果你所在团队已经有完整的Kubeflow平台那可以跳过这节的安装步骤直接使用平台自带的TFJob功能。判断方法很简单执行kubectl api-resources | grep tfjob如果能看到tfjobs.kubeflow.org说明TFJob已经可用可以直接跳到第4节。如果没有任何输出说明平台里没有启用tf-operator按照下面步骤手动部署就行。3.2 TFJob这个CRD到底长什么样TFJob本质上是一个自定义资源CRD。k8s允许你定义自己的资源类型tf-operator在集群里注册了一个叫TFJob的资源并实现了一个控制器来监听它。当你提交一个TFJob对象控制器会负责创建对应的Pod和Service并维护TFJob的状态。一个TFJob YAML最核心的部分是spec.tfReplicaSpecs里面可以定义PS、Worker、Chief、Evaluator这几种角色。每种角色是一个ReplicaSpec里面用replicas指定副本数量用template描述Pod模板。runPolicy则控制任务运行策略比如cleanPodPolicy表示任务结束后是否清理PodttlSecondsAfterFinished表示任务完成后多少秒自动删除。字段不算多但每个角色的职责不同。为了更清楚我把角色和典型使用场景整理成一张表角色职责什么时候用Worker执行训练循环计算梯度必备PS保存和更新模型参数模型参数大用参数服务器架构时使用Chief作为worker首领负责保存模型、处理评估分布式同步训练时使用Evaluator在训练过程中跑验证集评估需要独立评估时使用理解了角色写YAML的时候就不会乱。这里要注意TFJob的Pod模板和普通Pod写法基本一致支持requests、limits、环境变量、挂载卷也支持nodeSelector、tolerations、priorityClassName这些调度字段。后面混部的关键配置就是靠这些字段完成的。3.3 tf-operator安装实操独立安装tf-operator的流程很直接。从GitHub拉取最新的tf-operator仓库用kustomize构建并应用git clone https://github.com/kubeflow/tf-operator.git cd tf-operator kubectl apply -k manifests/overlays/standalone这个命令会创建kubeflow命名空间以及tf-operator需要的ServiceAccount、ClusterRole、ClusterRoleBinding、CRD和Controller Deployment。如果你的环境无法访问GitHub需要提前把镜像和manifests同步到内网再在内网执行同样的apply。安装完成后确认关键对象是否就绪kubectl get pods -n kubeflow | grep tf-operator kubectl get crd | grep tfjob第一条能看到tf-operator-controller-manager的Pod处于Running第二条能看到tfjobs.kubeflow.org这个CRD。两个都正常说明核心控制器已经就绪。注意老版本tf-operator的API版本是kubeflow.org/v1beta1新版本是kubeflow.org/v1。网上很多教程还停留在旧API版本直接套用新安装的tf-operator时会报no matches for kind TFJob之类的错误。写YAML前先确认API版本以官方仓库当前状态为准。3.4 验证安装结果和前置注意事项安装完成的验证我会额外做三件事。第一查看tf-operator控制器日志确认它没有因为RBAC权限不足报大量Error。第二随手创建一个小型TFJob比如replicas都设为1的CPU版本确认Pod能被创建出来。第三确认tf-operator的webhook没有被其他组件拦截。如果你在集群里装了Istio或者自定义的准入控制器它们可能会拦截TFJob的创建请求导致任务创建后一直不出现Pod。还有一个很容易踩的坑tf-operator默认的命名空间是kubeflow但你可以把TFJob部署到其他命名空间。如果部署到非kubeflow命名空间一定要确认该命名空间存在并且tf-operator有权限在那里创建Pod。否则你会发现TFJob显示Created但Pod完全没有被创建这个时候要先看tf-operator日志日志里通常会明确报权限错误。kubectl logs -n kubeflow deploy/tf-operator-controller-manager -f从这步开始建议训练任务的命名空间不要换来换去固定用kubeflow减少权限和网络层面的干扰。4. 提交TensorFlow训练任务并调优调度4.1 编写一个可运行的TFJob YAML跑通整个流程的关键是先写一个最小的TFJob。下面这个例子用CPU训练一个MNIST分类模型只是为了验证链路所以资源请求写得比较保守apiVersion: kubeflow.org/v1 kind: TFJob metadata: name: tf-mnist-cpu namespace: kubeflow spec: runPolicy: cleanPodPolicy: Running ttlSecondsAfterFinished: 1800 tfReplicaSpecs: Worker: replicas: 1 template: spec: containers: - name: tensorflow image: tensorflow/tensorflow:2.9.1 command: - python - -c - | import tensorflow as tf mnist tf.keras.datasets.mnist (x_train, y_train), (x_test, y_test) mnist.load_data() model tf.keras.models.Sequential([ tf.keras.layers.Flatten(input_shape(28, 28)), tf.keras.layers.Dense(128, activationrelu), tf.keras.layers.Dropout(0.2), tf.keras.layers.Dense(10, activationsoftmax) ]) model.compile(optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy]) model.fit(x_train, y_train, epochs3) resources: requests: cpu: 1 memory: 2Gi limits: cpu: 2 memory: 4Gi在实际项目中你不会把训练代码直接写进YAML而是会把代码打进自定义镜像或者通过ConfigMap挂载到容器里。直接内嵌代码的好处是演示时不用额外构建镜像。用这个YAML可以快速验证tf-operator链路是否正常。如果你的任务需要GPU只需把镜像换成tensorflow/tensorflow:2.9.1-gpu并在resources.limits里加一行nvidia.com/gpu: 1。4.2 TFJob角色拆解PS、Chief、Worker、Evaluator前面已经提过角色这里仔细讲一下它们在分布式训练里到底扮演什么角色。Worker是真正干活的人负责计算梯度、执行训练循环PSParameter Server在参数服务器架构下负责保存和更新模型参数可以理解为“参数仓库”Chief是worker团队里的“班长”在分布式训练中负责保存模型、处理checkpoint和评估Evaluator则是独立的评估角色在训练过程中周期性地跑验证集。并不是每个训练任务都需要所有角色如果模型参数不大完全可以不用PS如果使用同步训练可以指定Chief角色。这些角色之间的通信tf-operator会自动处理。它会创建一组Service命名规则是{TFJob名称}-{角色}-{序号}同时往每个Pod注入TF_CONFIG环境变量。你的TensorFlow训练代码只要读取TF_CONFIG就能自动组建分布式集群。比如用tf.distribute.experimental.MultiWorkerMirroredStrategy或ParameterServerStrategy时代码会在启动时通过TF_CONFIG获取其他worker的地址。这个环节最常见的坑是自定义训练脚本没处理TF_CONFIG或者处理方式不对后面7.1节会再提。4.3 提交任务、观察日志与清理任务提交任务的方式和普通k8s资源没有区别。把上面的YAML保存为tf-mnist-cpu.yaml后执行kubectl apply -f tf-mnist-cpu.yaml。紧接着用kubectl get tfjob和kubectl get pods两个命令确认TFJob与Pod都已经创建出来。这里推荐用label选择器过滤Pod标签是training.kubeflow.org/job-name能直接筛出属于当前TFJob的所有Podkubectl get tfjob -n kubeflow kubectl get pods -n kubeflow -l training.kubeflow.org/job-nametf-mnist-cpu日志和普通Pod一样kubectl logs -n kubeflow tf-mnist-cpu-worker-0 -f如果想看任务的整体状态直接看TFJob对象kubectl get tfjob tf-mnist-cpu -n kubeflow -o yaml | grep -A20 status训练完成或需要清理时kubectl delete tfjob tf-mnist-cpu -n kubeflowcleanPodPolicy可以控制运行期间和结束后Pod的保留策略。我习惯设置为Running这样训练完成后被删除的是已经结束的旧Pod运行中的Pod不会被误杀如果任务失败后还想保留现场排查可以把值设置成None。ttlSecondsAfterFinished可以用来控制任务成功或失败后多久自动清理对长时间运行很多任务的环境特别有用可以防止历史TFJob越积越多。4.4 混部场景下的资源隔离与调度策略前面说的混部在这里就是关键实施点。混部不只是把任务放一个集群里更要做优先级和配额控制。我给离线训练任务设计了四层保护。第一层通过ResourceQuota限制命名空间总资源。比如kubeflow命名空间最多使用200个CPU、600Gi内存防止离线任务无节制挤占在线服务资源。第二层通过PriorityClass设置优先级。在线服务使用高优先级离线训练任务使用低优先级低优先级Pod在节点资源紧张时会被驱逐或抢占保障在线服务的稳定性。第三层通过nodeSelector和affinity把训练任务调度到GPU节点或特定节点组。第四层通过resources.requests和limits精确限制每个训练Pod的资源上限防止某个Pod把节点的CPU和内存打满。一个简单的PriorityClass示例apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: offline-low value: 1000 globalDefault: false description: Offline training tasks然后在TFJob的Pod模板里加priorityClassName: offline-low tolerations: - key: gpu operator: Equal value: reserved effect: NoSchedule这样训练任务只能使用被打上GPU污点的节点并且优先级低于在线服务。实际观察下来在线服务的P99时延几乎不受影响集群整体利用率从20%提升到了50%以上。需要说明的是混部的收益和业务类型强相关不要指望复制一套配置就能得到同样结果但优先级、配额、亲和性这套组合拳是通用的。5. Prometheus监控资源使用5.1 Prometheus部署方式对比与选择Prometheus的部署方式我列过三个选项。最轻量的是用单个二进制的Docker容器跑适合临时验证稍微复杂一点的是用官方chart或镜像自定义配置最省心的是kube-prometheus-stack因为它把Prometheus、Alertmanager、Grafana、node-exporter、kube-state-metrics打包在一起开箱即用。我推荐直接上kube-prometheus-stack用helm安装helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack -n monitoring --create-namespace安装完成后monitoring命名空间下会出现prometheus、alertmanager、grafana、node-exporter等Pod。相比自己拼装这一步能节省大量时间。需要注意的是kube-prometheus-stack的版本和k8s版本有兼容矩阵安装前最好查一下chart对应的k8s版本要求。如果版本不匹配可能出现crash或者指标采集异常。5.2 采集节点、容器和Pod指标Prometheus能采集哪些指标主要取决于Exporter。node-exporter采集主机指标比如CPU、内存、磁盘、网络kubelet内置的cAdvisor采集容器指标比如container_cpu_usage_seconds_total、container_memory_working_set_byteskube-state-metrics采集k8s对象的状态指标比如Pod数量、Deployment副本数、节点状态等。kube-prometheus-stack默认会在每个节点部署node-exporter并启动对kubelet和kube-state-metrics的采集所以装完之后不需要额外配置集群层面的数据就已经在流了。想验证数据有没有进来可以先port-forward Prometheus然后在Graph页面执行一条PromQLkubectl port-forward -n monitoring service/prometheus-operated 9090:9090浏览器打开http://localhost:9090输入查询语句能看到返回的时序数据说明Pod采集链路没问题rate(container_cpu_usage_seconds_total{namespacekubeflow}[5m])Prometheus的默认采集配置通常已经包含了kubernetes-pods的job会自动发现带有prometheus.io/scrape: true注解的Pod。这一点在后面训练任务自定义指标采集时非常关键。5.3 采集GPU指标和训练任务自定义指标GPU指标是训练任务监控里最刚需的部分。方法是用DCGM Exporter它在每个GPU节点上以DaemonSet方式运行把GPU利用率、显存使用、温度、功耗等指标暴露给Prometheus。kube-prometheus-stack没有自带DCGM Exporter需要额外部署。部署方式通常是用NVIDIA官方提供的helm chart或直接apply DaemonSet YAML然后通过ServiceMonitor接入Prometheus。接入后GPU利用率指标长这样DCGM_FI_DEV_GPU_UTIL。训练任务自身的指标比如当前loss、accuracy、epochPrometheus默认是采集不到的需要在训练代码里自己暴露。最简单的办法是使用prometheus_client这个Python库在训练循环里更新Gauge指标并开启一个HTTP端口from prometheus_client import start_http_server, Gauge import random, time loss_gauge Gauge(training_loss, Current training loss) start_http_server(8080) for epoch in range(10): loss random.random() * 10 loss_gauge.set(loss) time.sleep(5)然后给TFJob的Pod加annotation让Prometheus自动发现并抓取metadata: annotations: prometheus.io/scrape: true prometheus.io/port: 8080如果你使用的是ServiceMonitor也可以直接为训练任务创建一个ServiceMonitor对象通过label selector选择对应的Service。两种方式我都用过annotation方式更适合临时任务ServiceMonitor适合固定命名空间下的长期任务。在生产环境我更推荐ServiceMonitor因为它的配置更结构化而且能在多个环境间复用。5.4 告警规则配置与PromQL实战数据能查到之后下一步就是告警。告警规则可以分为三层节点层、Pod层、业务层。节点层可以关注节点CPU使用率超过85%、磁盘空间不足、GPU温度过高Pod层可以关注容器频繁重启、内存使用超限、任务Pod长时间Pending业务层可以关注训练loss异常、训练进度停滞、训练任务失败。在kube-prometheus-stack中告警规则通常通过PrometheusRule这个CRD来管理。举个例子如果GPU利用率持续10分钟超过95%说明显存或算力不足需要关注apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: name: gpu-high-utilization namespace: monitoring spec: groups: - name: gpu.rules rules: - alert: GPUHighUtilization expr: DCGM_FI_DEV_GPU_UTIL 95 for: 10m labels: severity: warning annotations: summary: GPU utilization too high description: GPU {{ $labels.gpu }} utilization is above 95% for 10 minutes.告警触发后Alertmanager会把消息推送到钉钉、企业微信或者邮件。配置告警规则前最好先在Prometheus的Graph页面把每一条PromQL跑一遍确认表达式返回的数据是预期内容。我自己就吃过亏表达式写错导致告警乱发最后被群里的消息持续轰炸。6. Grafana可视化大盘与使用技巧6.1 部署Grafana并接入Prometheus数据源kube-prometheus-stack安装时已经附带Grafana。登录账号和密码默认情况需要看values.yaml或通过secret获取。为了安全建议第一次登录后立刻修改密码或者在helm install时通过values自定义grafana: adminPassword: your-strong-passwordGrafana的接入步骤如下登录后打开Configuration - Data Sources - Add data source选择Prometheus在URL栏填Prometheus服务的地址。kube-prometheus-stack的Prometheus服务名是prometheus-operated命名空间是monitoring所以地址写成http://prometheus-operated:9090。如果填错面板上所有数据都会空白。6.2 快速搭建集群和训练任务监控面板新手没必要从零开始画面板Grafana社区有很多现成模板。在Dashboards - Import界面输入面板ID就能直接导入。我常用的几个节点与集群总览可以用面板ID为315的Kubernetes cluster monitoringnode-exporter的主机监控可以用8919或者16098GPU监控建议搜索“DCGM”关键字选择NVIDIA官方提供的模板通常会包含利用率、显存、温度、功耗等图表。如果现有模板的变量逻辑和你的环境不一致导入后可能没有数据。这时候需要检查模板里的数据源和job名称。比如模板里查询的job叫node-exporter而你的环境中job名可能是kube-prometheus-stack-node-exporter需要点开对应Panel在Query里用Edit模式改成你环境里的实际job名。如果只是看Kubeflow训练任务资源使用我会单独建一个Panel用namespace变量筛选kubeflow命名空间下的CPU和内存使用曲线。6.3 Grafana高效使用小技巧分享几个我在实际使用中觉得特别有用的技巧。第一用变量动态切换环境。在Dashboard Settings - Variables里添加一个变量label_values(container_cpu_usage_seconds_total, namespace)面板里的所有查询都通过$namespace引用这个变量一个面板就能看所有命名空间的数据。第二学会复制面板。看到别人的Panel布局不错可以直接在Panel标题下拉菜单里选择Copy粘贴到自己的Dashboard里再修改查询。这样调整布局很快不用每次都从空白Panel开始。第三注意时间区间的选择。训练任务通常是短时任务如果选择30天区间很多细粒度指标被降采样后会失真我一般用最近15分钟到1小时来观察训练过程的波动。7. 常见问题与排查实录7.1 高频问题速查表问题现象可能原因解决方案TFJob创建后Pod一直没出现CRD没安装命名空间不存在tf-operator没权限检查kubectl get crd、kubectl get pods -n kubeflow查看tf-operator日志TFJob状态一直Createdcontroller异常查看tf-operator日志确认RBAC权限Pod一直PendingGPU资源不足节点taint未容忍资源request超过集群容量kubectl describe pod查看事件补充toleration或扩容节点镜像拉取失败镜像仓库地址不可达私有仓库secret未配置配置imagePullSecrets提前完成镜像同步Worker和PS之间互相连不上TF_CONFIG缺失或错误Service未创建端口配置不一致确认tf-operator生成的Service存在检查TF_CONFIG环境变量Pod反复重启训练脚本异常OOMKilled启动命令路径错误查看Pod日志调整资源limitsPrometheus面板无数据数据源URL错误采集配置没生效时间范围不对检查Prometheus Graph页面是否能看到指标确认面板查询条件Grafana登录不出来admin密码错误secret被修改重置admin密码通过secret查看初始凭据7.2 排查流程与个人经验遇到问题不要急着看面板先按“目标状态 - 当前状态 - 日志 - 事件”的顺序排查。比如TFJob没跑到Succeeded先看TFJob状态kubectl get tfjob再看Pod状态kubectl get pods再看Pod详情kubectl describe pod最后看日志kubectl logs。如果发现状态总是Running但训练没有推进很可能是训练代码本身有问题需要进到Pod里手动执行一下训练脚本。我自己积累的几个小经验。一tf-operator控制器的日志很有价值凡是Pod创建异常、webhook拦截、RBAC权限问题都能在这里看到一定要养成看这个日志的习惯。二GPU相关的报错很多和驱动、device-plugin有关可以先在主机的nvidia-smi确认GPU正常再检查Pod的Resources字段里是否有nvidia.com/gpu。三混部场景中如果发现在线服务时延飘高优先看节点的资源水位尤其是CPU steal和内存回收事件热迁移或驱逐日志也能帮助定位。四配置监控时不要一次性把所有指标都接进来先接CPU和内存再逐步加GPU和自定义指标这样数据链路出了问题很容易定位。这个方案上线后我们把大部分离线训练任务都迁移到了k8s上通过tf-operator统一调度配合Prometheus和Grafana资源使用情况一目了然。整条链路里我最大的体会是先让训练任务能稳定跑起来再把监控补齐最后才去谈优化调度策略。文章里给的参数和配置建议大家结合自己的集群环境做调整尤其是GPU型号、k8s版本和镜像地址直接照搬容易踩坑。