如何基于健康探针脚本(health_probe.sh)实现客户需要的兜底方案
收藏回复举报
如何基于健康探针脚本(health_probe.sh)实现客户需要的兜底方案
新人帖
发表于2026-01-12 18:03:30
0 查看

一、背景

健康探针脚本(health_probe.sh)适用于检测 PD混部场景,服务是否正常的示例脚本。如果服务化出现异常(静默故障如超时,非静默故障如coredump),可使用以下兜底方案进行止血。

接下来将分两部分阐述。

1、第一部分对健康探针脚本(health_probe.sh)进行代码的解释,熟悉健康探针脚本的原理。

2、第二部分是如何基于健康探针脚本(health_probe.sh)实现客户需要的兜底方案,因为客户的场景不是完成相同,因此,基于不同客户的兜底方案应有所不同(如,客户在跑兜底方案的同时需要新增日志,或客户想要对于故障的处理操作)。

二、代码解读

health_probe.sh 代码主要分三个部分:

1、使用健康探针示例(必选):

#######################################################################################
# Check /health/timed-3
#######################################################################################

# 读取PD混部服务化配置参数中的管理面端口,赋值到 management_port 变量
config_file="/home/HwHiAiUser/Ascend/mindie/latest/mindie-service/conf/config.json"
management_port=$(grep '"managementPort"' "$config_file" | sed 's/[^0-9]*//g')

# 调用PD混部健康探针接口:/health/timed-${TIMEOUT},详情请看使用规范:https://www.hiascend.com/document/detail/zh/mindie/21RC1/mindieservice/servicedev/mindie_service0102.html
# 并把结果写入到 response_file 中
# 0.0.0.0 需要修改为PD混部服务化配置参数中的管理面IP
# --silent 静默模式
# --write-out "HTTPSTATUS:%{http_code}",在响应输出最后追加一段自定义信息,这里是 HTTP 状态码。例如如果返回 200,则在输出的最后加上 HTTPSTATUS:200
# -m 3 设置 最大执行时间为 3 秒,超过 3 秒自动退出(防止请求卡死)
response_file=~/health_response
curl --silent --write-out "HTTPSTATUS:%{http_code}" -m 3 "http://0.0.0.0:$management_port/health/timed-3" > "$response_file" &

2、监控AICore示例(可选):

#######################################################################################
# Check npu-smi info
#######################################################################################

# 从 ~/device_info 文件读取 NPU 信息,假如没有该文件,请设置为卡号,如0、1
npu_id=$(awk 'NR==2 {print $1}' ~/device_info)
max_aicore_usage=0
# 设置采样次数
num_samples=4

for ((i=0; i<num_samples; i++)); do
    output=$(npu-smi info -t usages -i "$npu_id")
    aicore_usage=$(echo "$output" | grep 'Aicore Usage Rate(%)' | awk '{print $NF}' | tr -d '%')
    # 如果提取到的值不为空,并且比当前记录的最大值大,就更新最大值
    if [[ -n "$aicore_usage" && "$aicore_usage" -gt "$max_aicore_usage" ]]; then
        max_aicore_usage=$aicore_usage
    fi
    # 每次采样之间短暂休眠 0.1 秒,避免采样间隔过短
    if (( i < num_samples - 1 )); then
        sleep 0.1
    fi
done

# 根据最大值判断核心是否异常
# 这里设定阈值为 10%,小于 10% 认为异常
if (( max_aicore_usage < 10 )); then
    # echo "Core utilization abnormal: Max Aicore Usage Rate is less than 10."
    core_abnormal=true
else
    # echo "Core utilization is normal."
    core_abnormal=false
fi

3、基于以上监控结果返回示例:

#######################################################################################
# Final conclusion
#######################################################################################

# 等待前面后台运行的 curl 命令完成($! 代表最后一个后台任务的 PID)
wait $!

# 读取 curl 的完整输出内容,提取响应体和响应码
response=$(<"$response_file")
response_body=$(echo "$response" | sed -e 's/HTTPSTATUS\:.*//g')
response_code=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')

# 判断服务是否健康
# 条件1:HTTP 状态码不是 200
# 条件2:响应体不是 {"status":"healthy"}
if [[ "$response_code" -ne 200 ]] || [[ "$response_body" != '{"status":"healthy"}' ]]; then
    # echo "The service might be deadlocked."
    # 认为服务可能死锁/异常
    timed_out=true
else
    # echo "The service is healthy."
    # 服务正常
    timed_out=false
fi

# 如果同时满足:服务异常 + NPU 核心利用率异常,则进行二次确认
if [[ "$timed_out" == true && "$core_abnormal" == true ]]; then
    # echo "Service may be deadlocked, check again..."
    # 重置 Aicore 利用率最大值
    max_aicore_usage=0
    # 增加采样次数到 6 次(相比之前的 4 次更严格)
    num_samples=6

    # 再次采样 Aicore 使用率
    for ((i=0; i<num_samples; i++)); do
        output=$(npu-smi info -t usages -i "$npu_id")

        aicore_usage=$(echo "$output" | grep 'Aicore Usage Rate(%)' | awk '{print $NF}' | tr -d '%')
        # echo $aicore_usage

        if [[ -n "$aicore_usage" && "$aicore_usage" -gt "$max_aicore_usage" ]]; then
            max_aicore_usage=$aicore_usage
        fi

        if (( i < num_samples - 1 )); then
            sleep 0.1
        fi
    done

    # echo "Maximum Aicore Usage Rate(%) recorded: $max_aicore_usage"
    # 再次判断 Aicore 利用率是否低于阈值(10%)
    if (( max_aicore_usage < 10 )); then
        # echo "Core utilization abnormal: Max Aicore Usage Rate is less than 10."
        core_abnormal=true
    else
        # echo "Core utilization is normal."
        core_abnormal=false
    fi

    # 如果经过二次确认仍然:服务异常 + 核心异常
    if [[ "$timed_out" == true && "$core_abnormal" == true ]]; then
        # 返回 501,表示可能死锁,需要进一步处理
        echo 501
    else
        # 返回 200,认为正常
        echo 200
    fi
else
    # 初步检查通过,直接认为服务正常
    echo 200
fi

三、兜底方案实现

首先前提条件,判断客户的服务有没有空转的可能,如果有可能,则不需要监控AICore。

通常兜底方案的实现为(以下为伪代码):

while true; do
    # 检查服务健康状态
    if check_service_health; then
        # 服务正常
    else
        # 服务异常,重新启动服务
        restart_service
    fi
    # 等待60秒后再次检查
    sleep 60
done

其中,检查服务健康状态(check_service_health)可以详细为(以下为可执行代码):

# 函数:check_service_health
# 功能:检查服务健康状态,支持最多三次重试
# 返回值:
#   0 -> 服务健康
#   1 -> 服务异常或重试多次仍未健康
check_service_health() {
    # 定义局部变量,避免污染全局命名空间
    local response      # 存放 curl 命令的完整输出(响应体 + HTTP 状态码)
    local http_code     # 存放解析出的 HTTP 状态码
    local content       # 存放解析出的响应体
    local retry_count=0 # 重试计数器
    local max_retries=3 # 最大重试次数

    # 循环重试机制:最多尝试 max_retries 次
    while [ $retry_count -lt $max_retries ]; do
        # 使用 curl 请求健康检查 URL
        # -s: 静默模式,不显示进度条或错误信息
        # -w "\n%{http_code}": 在输出末尾追加 HTTP 状态码,并换行
        # 2>/dev/null: 屏蔽标准错误输出
        response=$(curl -s -w "\n%{http_code}" "$HEALTH_CHECK_URL" 2>/dev/null)

        # 从 curl 输出中取最后一行,即 HTTP 状态码
        http_code=$(echo "$response" | tail -n1)

        # 去掉最后一行,得到响应体内容
        content=$(echo "$response" | sed '$d')

        # 判断服务是否健康
        # 条件1:HTTP 状态码必须为 200
        # 条件2:响应体内容必须是 '{"status":"healthy"}'
        if [ "$http_code" = "200" ] && [ "$content" = '{"status":"healthy"}' ]; then
            return 0  # 服务健康,函数返回成功
        fi

        # 如果未通过健康检查,则增加重试计数
        retry_count=$((retry_count + 1))

        # 等待 1 秒后再次重试
        sleep 1
    done

    # 如果超过最大重试次数仍未通过健康检查,返回失败
    return 1
}

其中,重新启动服务(restart_service)可以详细为(以下为可执行代码):

# 函数:restart_service
# 功能:终止当前服务相关进程并重新启动服务,同时更新时间戳
restart_service() {
    # 强制终止 mindie 进程
    pkill -9 mindie
    
    # 强制终止 python 进程
    pkill -9 python
    
    # 等待进程完全退出
    local wait_count=0
    local max_wait=10

    while [ $wait_count -lt $max_wait ]; do
        # 再次尝试终止进程
        pkill -9 mindie
        pkill -9 python

        # 检查进程是否仍存在
        if pgrep -x "mindie" > /dev/null || pgrep -f "python" > /dev/null; then
            sleep 1
            wait_count=$((wait_count + 1))
        else
            break
        fi
    done

    # 启动服务,此处启动服务为环境变量 + Daemon启动
    bash A3_single_machine.sh
    
    # 更新状态为 START 并记录当前时间戳
    current_status="$STATUS_START"
    start_time=$(date +%s)
}

综上,您已经可以基于健康探针脚本(health_probe.sh)实现客户需要的兜底方案

也可以参考在其他局点已经使用上的脚本,里面丰富了日志记录和服务化状态

#!/bin/bash

# 日志文件
LOG_FILE="monitor.log"

# 状态定义
STATUS_INIT="INIT"
STATUS_NORMAL="NORMAL"
STATUS_START="START"

# 服务健康检查URL
HEALTH_CHECK_URL="http://175.100.2.4:1026/health/timed"

# 初始状态
current_status="$STATUS_INIT"
start_time=0

# 记录日志函数
log_message() {
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] $1" >> "$LOG_FILE"
}

# 检查服务状态函数
check_service_health() {
    local response
    local http_code
    local retry_count=0
    local max_retries=3
    
    # 重试机制
    while [ $retry_count -lt $max_retries ]; do
        response=$(curl -s -w "\n%{http_code}" "$HEALTH_CHECK_URL" 2>/dev/null)
        http_code=$(echo "$response" | tail -n1)
        content=$(echo "$response" | sed '$d')
        
        # 检查HTTP状态码和响应内容
        if [ "$http_code" = "200" ] && [ "$content" = '{"status":"healthy"}' ]; then
            return 0
        fi
        
        retry_count=$((retry_count + 1))
        sleep 1
    done
    
    return 1
}

# 服务重启函数
restart_service() {
    log_message "开始重启服务..."
    
    # 终止mindie进程
    pkill -9 mindie
    log_message "已发送终止mindie进程信号"
    
    # 终止python进程
    pkill -9 python
    log_message "已发送终止python进程信号"
    
    # 等待进程完全退出
    local wait_count=0
    local max_wait=10
    
    while [ $wait_count -lt $max_wait ]; do
        # 检查进程是否还存在
        pkill -9 mindie
        pkill -9 python
        if pgrep -x "mindie" > /dev/null || pgrep -f "python" > /dev/null; then
            sleep 1
            wait_count=$((wait_count + 1))
        else
            break
        fi
    done
    
    # 如果进程仍然存在,强制报告
    if pgrep -x "mindie" > /dev/null; then
        log_message "警告: mindie进程仍然存在"
    fi
    
    if pgrep -f "python" > /dev/null; then
        log_message "警告: python进程仍然存在"
    fi
    
    # 启动服务
    log_message "正在启动服务..."
    bash A2_single_machine.sh
    log_message "服务启动命令已执行"
    
    # 更新状态为START并记录时间
    current_status="$STATUS_START"
    start_time=$(date +%s)
    log_message "状态变更为: $STATUS_START"
}

# 主循环
log_message "监控脚本启动,初始状态: $STATUS_INIT"

while true; do
    # 检查服务健康状态
    if check_service_health; then
        # 服务正常
        case "$current_status" in
            "$STATUS_INIT")
                log_message "服务健康,状态从 $STATUS_INIT 变更为 $STATUS_NORMAL"
                current_status="$STATUS_NORMAL"
                ;;
            "$STATUS_START")
                log_message "服务健康,状态从 $STATUS_START 变更为 $STATUS_NORMAL"
                current_status="$STATUS_NORMAL"
                ;;
            "$STATUS_NORMAL")
                # 保持正常状态,无需操作
                ;;
        esac
    else
        # 服务异常
        case "$current_status" in
            "$STATUS_INIT")
                log_message "服务异常,状态从 $STATUS_INIT 变更为 $STATUS_START"
                current_status="$STATUS_START"
                start_time=$(date +%s)
                ;;
            "$STATUS_NORMAL")
                log_message "检测到服务异常,状态从 $STATUS_NORMAL 变更为 $STATUS_START"
                current_status="$STATUS_START"
                start_time=$(date +%s)
                restart_service
                ;;
            "$STATUS_START")
                current_time=$(date +%s)
                elapsed_time=$((current_time - start_time))
                
                # 检查是否超过15分钟(900秒)
                if [ $elapsed_time -ge 900 ]; then
                    log_message "服务异常已持续15分钟,重新启动服务..."
                    restart_service
                else
                    log_message "服务仍然异常,已持续 ${elapsed_time} 秒"
                fi
                ;;
        esac
    fi
    
    # 等待5秒后再次检查
    sleep 5
done

我要发帖子