HOOOS

多节点 Slurm 集群中,如何用 Ansible 优雅地批量维护与巡检 GPU MPS 状态?

0 3 算力运维老兵 SlurmAnsibleNVIDIA MPS
Apple

在大型 GPU 算力集群中,为了提升中小显存占用任务的吞吐量,NVIDIA MPS(Multi-Process Service,多进程服务) 是一个几乎必选的方案。配合 Slurm 的 gres/mps 机制,多任务可以物理共享单张 GPU,并实现算力和显存的硬隔离。

然而,在多节点、高并发的生产环境下,MPS 的维护往往是个灾难:

  • 用户作业异常退出导致 MPS 守护进程(nvidia-cuda-mps-control)残留或僵死。
  • /tmp/nvidia-mps 下的 IPC 套接字文件权限被污染,导致后续作业无法连接。
  • Slurm 登记的 MPS 资源与节点物理运行的 MPS 状态出现漂移,导致调度器“空放”任务或报 Resource temporarily unavailable

本文将分享如何通过 Ansible 编排,实现多节点 Slurm 集群下 GPU MPS 的批量一键部署、状态复位以及自动化健康巡检。


一、 核心架构:Slurm 与 MPS 的生命周期管理

在开始写 Ansible Playbook 之前,必须理清 Slurm 调度下 MPS 的运行逻辑。

通常有两种管理模式:

  1. Slurm 托管模式(推荐):在 slurm.conf 中配置 GresTypes=gpu,mps,并在 gres.conf 中定义 MPS 资源。Slurm 在拉起作业时,通过 PrologSlurmctldtask/cgroup 插件动态启动和限制 MPS。
  2. 系统级常驻模式:在每个 GPU 节点上,通过 Systemd 静态拉起一个全局的 nvidia-cuda-mps-control 守护进程,供所有作业共享。

本文方案主要针对第二种常驻模式,以及第一种模式下需要对底层节点进行大面积体检和残留清理的场景。


二、 Ansible 批量维护:MPS 状态一键复位与清理

当节点出现 MPS 僵死或者权限混乱时,我们需要一个“干净”的复位流程。一个合格的复位动作不能简单地 kill -9,必须遵循 NVIDIA 规范,优雅地退出演算,清理残留,再重新初始化。

1. 基础环境定义 (group_vars/gpu_nodes.yml)

首先定义基础环境变量,确保所有节点的 IPC 路径和日志路径一致:

---
mps_control_bin: "/usr/bin/nvidia-cuda-mps-control"
mps_ipc_dir: "/tmp/nvidia-mps"
mps_log_dir: "/var/log/nvidia-mps"
# 限制单个 MPS 客户端的最大可用算力百分比(可选,默认不限制)
mps_active_thread_percentage: 100 

2. 复位与拉起 Playbook (reset_mps.yml)

这个 Playbook 实现了以下逻辑:

  1. 优雅停止现有的 MPS 控制进程。
  2. 强行清理可能残留的僵尸进程(如 nvidia-cuda-mps-server)。
  3. 清理并重新创建合规权限的 /tmp 共享目录。
  4. 启动 MPS 守护进程。
---
- name: Batch Maintain and Reset GPU MPS Status
  hosts: gpu_nodes
  become: yes
  gather_facts: yes
  tasks:

    - name: 1. Ensure NVIDIA kernel modules are loaded
      ansible.builtin.shell: lsmod | grep nvidia
      register: nvidia_driver_loaded
      failed_when: nvidia_driver_loaded.rc != 0
      changed_when: false

    - name: 2. Stop running MPS control daemon gracefully
      ansible.builtin.shell: |
        echo "quit" | {{ mps_control_bin }}
      failed_when: false
      changed_when: true

    - name: 3. Force kill any zombie MPS server processes
      ansible.builtin.shell: |
        killall -9 nvidia-cuda-mps-control nvidia-cuda-mps-server
      failed_when: false
      register: kill_zombies
      changed_when: "'killed' in kill_zombies.stdout"

    - name: 4. Clean up corrupted IPC directory and log directory
      ansible.builtin.file:
        path: "{{ item }}"
        state: absent
      loop:
        - "{{ mps_ipc_dir }}"
        - "{{ mps_log_dir }}"

    - name: 5. Recreate IPC and Log directories with correct permissions
      ansible.builtin.file:
        path: "{{ item }}"
        state: directory
        owner: root
        group: root
        mode: '0777' # 必须是0777,允许不同系统用户提交的作业往此写入IPC套接字
      loop:
        - "{{ mps_ipc_dir }}"
        - "{{ mps_log_dir }}"

    - name: 6. Start nvidia-cuda-mps-control in background
      ansible.builtin.shell: |
        export CUDA_VISIBLE_DEVICES=0,1,2,3 # 根据节点实际显卡数量调整,或通过fact动态获取
        export CUDA_MPS_PIPE_DIRECTORY={{ mps_ipc_dir }}
        export CUDA_MPS_LOG_DIRECTORY={{ mps_log_dir }}
        {{ mps_control_bin }} -d
      environment:
        CUDA_VISIBLE_DEVICES: "{{ ansible_env.CUDA_VISIBLE_DEVICES | default('0') }}"
      async: 10
      poll: 0
      changed_when: true

    - name: 7. Verify MPS control daemon is active
      ansible.builtin.wait_for:
        path: "{{ mps_ipc_dir }}/control"
        state: present
        timeout: 5

三、 Ansible 批量健康巡检:主动发现故障节点

运维的最高境界是“主动发现”。我们需要一个巡检 Playbook,每天定时运行,过滤出那些“表面上在线,但 MPS 已经坏掉”的亚健康节点,并自动向 Slurm 发送 drain 信号,避免坏节点继续接单。

巡检指标设计:

  1. 进程存在性nvidia-cuda-mps-control 是否在运行?
  2. GPU 状态响应:通过 nvidia-smi 检查 GPU 驱模块是否异常(如掉卡/D-State)。
  3. MPS 服务响应:向控制套接字发送 get_default_active_thread_percentage 指令,看是否能正常返回。
  4. 共享目录权限/tmp/nvidia-mps 是否被某个用户的作业改写成了私有所有者(导致其他用户报错)。

巡检 Playbook (check_mps_health.yml)

---
- name: GPU MPS Multi-Node Health Audit
  hosts: gpu_nodes
  become: yes
  gather_facts: false
  tasks:

    - name: Audit 1 - Check MPS control process
      ansible.builtin.command: pgrep nvidia-cuda-mps
      register: mps_process_check
      ignore_errors: yes

    - name: Audit 2 - Query MPS control daemon response
      ansible.builtin.shell: |
        export CUDA_MPS_PIPE_DIRECTORY={{ mps_ipc_dir }}
        echo "get_default_active_thread_percentage" | {{ mps_control_bin }}
      register: mps_cmd_check
      ignore_errors: yes
      changed_when: false

    - name: Audit 3 - Check IPC socket path permissions
      ansible.builtin.stat:
        path: "{{ mps_ipc_dir }}"
      register: mps_dir_stat

    - name: Audit 4 - Check if Slurm daemon is synchronized
      ansible.builtin.shell: scontrol show node {{ inventory_hostname }} | grep -E "Gres|State"
      delegate_to: "{{ groups['slurm_master'][0] }}"
      register: slurm_node_state
      ignore_errors: yes
      changed_when: false

    - name: Aggregate Health Report and Handle Failures
      block:
        - name: Identify bad nodes
          ansible.builtin.set_fact:
            mps_unhealthy: >-
              {{
                mps_process_check.rc != 0 or
                '100' not in mps_cmd_check.stdout or
                mps_dir_stat.stat.mode != '0777'
              }}

        - name: Print warning for unhealthy node
          ansible.builtin.debug:
            msg: >
              [WARNING] Node {{ inventory_hostname }} is UNHEALTHY!
              Process: {{ 'ALIVE' if mps_process_check.rc == 0 else 'DEAD' }},
              Response: {{ mps_cmd_check.stdout | default('NO_RESPONSE') }},
              Dir Mode: {{ mps_dir_stat.stat.mode }}
          when: mps_unhealthy

        - name: Automatically DRAIN unhealthy node in Slurm
          delegate_to: "{{ groups['slurm_master'][0] }}"
          ansible.builtin.command: >
            scontrol update NodeName={{ inventory_hostname }} 
            State=DRAIN Reason="MPS Health Check Failed via Ansible"
          when: mps_unhealthy and trigger_drain | default(false) | bool

四、 进阶:如何规避 MPS 常见的“坑”

在用 Ansible 落地上述方案时,根据生产实践,有几个必须要踩的坑和避坑指南:

1. 动态 CUDA_VISIBLE_DEVICES 绑定

在多卡节点上,如果有些卡需要跑 MPS,有些卡需要跑独占模式(Exclusive),绝对不能粗暴地在全局拉起整个 MPS。

  • 最佳实践:在 Ansible 中,通过调用 nvidia-smi --query-gpu=index,uuid --format=csv 动态读取节点上的 GPU 拓扑。
  • 在 Ansible 模板中,针对每张需要启动 MPS 的 GPU,单独指定不同的 CUDA_MPS_PIPE_DIRECTORY=/tmp/nvidia-mps_${GPU_INDEX},实现物理隔离下的 MPS 管理。

2. 避免 /tmp 目录被 systemd-tmpfiles 清理

CentOS/Ubuntu 默认会定期清理 /tmp 下超过一定时间未访问的文件。如果你的 nvidia-mps 套接字长时间没有新任务连接,可能会被系统误删,导致后续作业提交直接挂掉。

  • 解决方法:通过 Ansible 部署一个 /etc/tmpfiles.d/nvidia-mps.conf 配置文件,内容如下:
    x /tmp/nvidia-mps
    x /tmp/nvidia-mps/*
    
    这会告诉系统:永远不要清理这个目录。

3. Slurm Epilog 中的自动收尾

除了定期用 Ansible 巡检,最好在 Slurm 的 Epilog 脚本中加入一行轻量级清理逻辑。当用户的最后一个 MPS 作业退出时,使用 Ansible 下发的管理脚本自动检测,若无活动连接,则执行 echo quit。这能极大减轻日常维护的压力。


五、 总结

利用 Ansible 对 Slurm GPU 集群进行批量 MPS 运维,其核心在于**“规范化路径”“状态自愈”**。通过上文提供的复位与健康巡检 Playbook,你可以将日常排查异构算力节点故障的时间缩短到分钟级,甚至通过结合 Slurm 的 scontrol 命令实现故障节点的“无感下线与自动修复”,保障大模型训练/推理集群的高可用性。

点评评价

captcha
健康