Kubernetes混部部署TensorFlow训练任务:TFJob调度与监控实践
2026/9/15 15:26:59 网站建设 项目流程

前阵子我接到一个需求:团队里有一批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 为什么选Kubeflow+tf-operator这条路线

在动手之前,我们对比过几种跑TensorFlow训练任务的方式。最原始的做法是直接在裸机上跑,配合screen或supervisor守护进程,扩展性基本为零;后来有人用docker compose在单机管理,容器化是做到了,但跨节点的分布式网络还是要自己写编排逻辑;再后来把训练任务做成普通的k8s Deployment,k8s能帮忙保证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较高自动(只保证副本数)无状态服务
TFJob(tf-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-type=gpu

打污点是为了确保普通在线服务不会被调度到GPU节点上,只有声明了toleration的训练Pod才能使用GPU资源:

kubectl taint nodes node-gpu-01 gpu=reserved: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 limit,Pod也只能一直Pending。看到nvidia.com/gpu出现后,GPU资源这一层的准备就结束了。

3. 部署Kubeflow和tf-operator

3.1 只装tf-operator,还是装完整Kubeflow

Kubeflow是一个偏重量级的平台,通常包含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表示任务结束后是否清理Pod,ttlSecondsAfterFinished表示任务完成后多少秒自动删除。

字段不算多,但每个角色的职责不同。为了更清楚,我把角色和典型使用场景整理成一张表:

角色职责什么时候用
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, activation='relu'), tf.keras.layers.Dropout(0.2), tf.keras.layers.Dense(10, activation='softmax') ]) model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) model.fit(x_train, y_train, epochs=3) 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是真正干活的人,负责计算梯度、执行训练循环;PS(Parameter 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 tfjobkubectl get pods两个命令确认TFJob与Pod都已经创建出来。这里推荐用label选择器过滤Pod,标签是training.kubeflow.org/job-name,能直接筛出属于当前TFJob的所有Pod:

kubectl get tfjob -n kubeflow kubectl get pods -n kubeflow -l training.kubeflow.org/job-name=tf-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 kubeflow

cleanPodPolicy可以控制运行期间和结束后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_totalcontainer_memory_working_set_bytes;kube-state-metrics采集k8s对象的状态指标,比如Pod数量、Deployment副本数、节点状态等。kube-prometheus-stack默认会在每个节点部署node-exporter,并启动对kubelet和kube-state-metrics的采集,所以装完之后不需要额外配置,集群层面的数据就已经在流了。

想验证数据有没有进来,可以先port-forward Prometheus,然后在Graph页面执行一条PromQL:

kubectl port-forward -n monitoring service/prometheus-operated 9090:9090

浏览器打开http://localhost:9090,输入查询语句,能看到返回的时序数据,说明Pod采集链路没问题:

rate(container_cpu_usage_seconds_total{namespace="kubeflow"}[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、epoch,Prometheus默认是采集不到的,需要在训练代码里自己暴露。最简单的办法是使用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-password

Grafana的接入步骤如下:登录后,打开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 monitoring;node-exporter的主机监控可以用8919或者16098;GPU监控建议搜索“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日志,调整资源limits
Prometheus面板无数据数据源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版本和镜像地址,直接照搬容易踩坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询