一、背景
健康探针脚本(health_probe.sh)适用于检测 PD混部场景,服务是否正常的示例脚本。如果服务化出现异常(静默故障如超时,非静默故障如coredump),可使用以下兜底方案进行止血。
接下来将分两部分阐述。
1、第一部分对健康探针脚本(health_probe.sh)进行代码的解释,熟悉健康探针脚本的原理。
2、第二部分是如何基于健康探针脚本(health_probe.sh)实现客户需要的兜底方案,因为客户的场景不是完成相同,因此,基于不同客户的兜底方案应有所不同(如,客户在跑兜底方案的同时需要新增日志,或客户想要对于故障的处理操作)。
二、代码解读
health_probe.sh 代码主要分三个部分:
1、使用健康探针示例(必选):
2、监控AICore示例(可选):
3、基于以上监控结果返回示例:
三、兜底方案实现
首先前提条件,判断客户的服务有没有空转的可能,如果有可能,则不需要监控AICore。
通常兜底方案的实现为(以下为伪代码):
其中,检查服务健康状态(check_service_health)可以详细为(以下为可执行代码):
其中,重新启动服务(restart_service)可以详细为(以下为可执行代码):
综上,您已经可以基于健康探针脚本(health_probe.sh)实现客户需要的兜底方案
也可以参考在其他局点已经使用上的脚本,里面丰富了日志记录和服务化状态
一、背景
健康探针脚本(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 fi3、基于以上监控结果返回示例:
####################################################################################### # 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