使用Shell脚本实现服务器巡检报告自动生成

描述

背景与适用场景

服务器巡检是运维工作中最基础也最容易被忽视的工作之一。巡检做得好,能在故障发生之前发现隐患;巡检做得敷衍,往往是"平时不巡检,出事干瞪眼"。但现实情况是:很多团队的巡检就是登录服务器敲几个命令,把结果截图往群里一丢就算交差了——既没有系统性的数据收集,也没有历史对比,更谈不上告警联动。

本篇的目标是用 Shell 脚本实现服务器巡检报告的自动生成,最终输出一个结构化的 Markdown 文件。这个方案的特点:

一键执行:一条命令收集一台或多台服务器的全部巡检数据

结构清晰:按系统、硬件、应用三层组织巡检内容

可存档、可对比:Markdown 格式便于存档,也可以用 Git 管理历史版本

可扩展:在统一框架下添加新的巡检项非常容易

无需额外依赖:纯 Shell + 系统自带工具,不依赖 Ansible、Salt 等重型工具

适用场景:

每日/每周例行巡检报告生成

交接时对服务器现状的摸底记录

故障排查前对系统健康状况的基线采集

迁移或升级前的现状确认

巡检报告框架设计

报告结构

一份完整的服务器巡检报告应该包含以下内容:

 

# 服务器巡检报告

## 基本信息
- 主机名、操作系统版本、内核版本
- 当前时间、运行时长、负载状态

## 系统资源
- CPU:型号、核心数、负载平均值
- 内存:总量、使用量、剩余量、使用率
- 磁盘:各分区使用率、IO 等待

## 网络状态
- 网卡配置、IP 地址、网关
- 端口监听情况
- 网络连接数统计

## 运行服务
- 关键服务运行状态(Nginx、MySQL、Redis 等)
- 开机自启配置

## 系统日志
- 近期的错误和警告日志(最近 24 小时内)

## 安全状态
- 用户账户(特权用户、非活跃账户)
- 登录记录(近期登录来源)
- SElinux / AppArmor 状态

## 应用层
- 数据库状态(连接数、版本)
- 中间件状态
- 业务进程状态

## 磁盘 inode 使用情况
- 文件系统 inode 使用率

## 历史性能(采集时刻)
- top5 进程(按 CPU、内存排序)
- 历史负载、内存趋势

## 巡检结论
- 发现的问题和建议

 

脚本目录结构

 

~/server-inspection/
├── inspect.sh              # 主脚本(部署到每台服务器执行)
├── collect_remote.sh        # 远程采集脚本(从一台机器批量采集多台)
├── report_template.md      # Markdown 报告模板
└── output/                  # 报告输出目录
    ├── 2024-01-15-web-01.md
    └── 2024-01-15-db-01.md

 

本地巡检脚本:inspect.sh

这是部署到每台服务器上执行的巡检脚本,负责收集本机数据并生成 Markdown 格式的巡检报告。

 

#!/bin/bash
# ~/server-inspection/inspect.sh
# 服务器巡检脚本 - 在每台服务器上执行,生成 Markdown 格式巡检报告
# 用法: ./inspect.sh [输出目录]

set -euo pipefail

# 输出目录,默认当前目录
OUTPUT_DIR="${1:-.}"
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
REPORT_DATE=$(date "+%Y-%m-%d")
SHORT_DATE=$(date "+%Y%m%d%H%M%S")

# 获取主机名和 IP
HOSTNAME=$(hostname)
PRIMARY_IP=$(hostname -I | awk '{print $1}')
OS_VERSION=$(cat /etc/redhat-release 2>/dev/null || cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)
KERNEL=$(uname -r)
UPTIME_DAYS=$(uptime -p 2>/dev/null || uptime)
LOAD_AVG=$(cat /proc/loadavg | awk '{print $1, $2, $3}')
BOOT_TIME=$(who -b | awk '{print $3, $4}')

# 颜色定义(用于终端输出,非必须)
RED='�33[0;31m'
GREEN='�33[0;32m'
YELLOW='�33[1;33m'
NC='�33[0m'

log_info() {
    echo -e "${GREEN}[INFO]${NC} $1"
}

log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# 确保输出目录存在
mkdir -p "$OUTPUT_DIR"

OUTPUT_FILE="${OUTPUT_DIR}/${REPORT_DATE}-${HOSTNAME}.md"

# ========== 开始生成报告 ==========

log_info "开始生成巡检报告: ${HOSTNAME}"
log_info "输出文件: ${OUTPUT_FILE}"

# 生成 Markdown 报告
cat > "$OUTPUT_FILE" << EOF
# 服务器巡检报告

**生成时间**: ${TIMESTAMP}
**主机名**: ${HOSTNAME}
**主 IP 地址**: ${PRIMARY_IP}
**操作系统**: ${OS_VERSION}
**内核版本**: ${KERNEL}
**运行时长**: ${UPTIME_DAYS}
**最近重启**: ${BOOT_TIME}
**负载平均值**: ${LOAD_AVG}

---

## 1. 系统基本信息

```bash
# 主机名
hostname: ${HOSTNAME}

# 操作系统版本
cat /etc/redhat-release:
$(cat /etc/redhat-release 2>/dev/null || echo "Non-RHEL system")

# 内核版本
uname -r: ${KERNEL}

# 系统位数
uname -m: $(uname -m)

# 最近重启时间
who -b: ${BOOT_TIME}
```

EOF

# ========== CPU 巡检 ==========
log_info "采集 CPU 信息..."

CPU_MODEL=$(cat /proc/cpuinfo | grep "model name" | head -1 | cut -d':' -f2 | sed 's/^ //')
CPU_CORES=$(nproc --all 2>/dev/null || echo "$(cat /proc/cpuinfo | grep processor | wc -l)")
CPU_PHYSICAL=$(cat /proc/cpuinfo | grep "physical id" | sort -u | wc -l)
CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{print "用户态: " $2 "%, 系统态: " $4 "%, 空闲: " $8 "%"}')
LOAD_AVG_15=$(cat /proc/loadavg | awk '{print $3}')

cat >> "$OUTPUT_FILE" << EOF
## 2. CPU 信息

**CPU 型号**: ${CPU_MODEL}
**物理核心数**: ${CPU_PHYSICAL}
**逻辑核心数**: ${CPU_CORES}
**当前使用率**: ${CPU_USAGE}
**15分钟负载**: ${LOAD_AVG_15}

```bash
# CPU 详细信息
lscpu:
$(lscpu 2>/dev/null | head -30)

# 实时 CPU 使用率
top -bn1 | head -10:
$(top -bn1 | head -10)
```

EOF

# ========== 内存巡检 ==========
log_info "采集内存信息..."

MEM_TOTAL=$(free -b | awk 'NR==2{printf "%.2f GB", $2/1024/1024/1024}')
MEM_USED=$(free -b | awk 'NR==2{printf "%.2f GB", $3/1024/1024/1024}')
MEM_FREE=$(free -b | awk 'NR==2{printf "%.2f GB", $4/1024/1024/1024}')
MEM_AVAILABLE=$(free -b | awk 'NR==2{printf "%.2f GB", $7/1024/1024/1024}')
MEM_USED_PCT=$(free | awk 'NR==2{printf "%.1f", $3/$2*100}')
SWAP_TOTAL=$(free -b | awk 'NR==3{printf "%.2f GB", $2/1024/1024/1024}')
SWAP_USED=$(free -b | awk 'NR==3{printf "%.2f GB", $3/1024/1024/1024}')
SWAP_USED_PCT=$(free | awk 'NR==3{printf "%.1f", $2==0?0:$3/$2*100}')

cat >> "$OUTPUT_FILE" << EOF
## 3. 内存信息

**物理内存总量**: ${MEM_TOTAL}
**已使用**: ${MEM_USED} (${MEM_USED_PCT}%)
**空闲**: ${MEM_FREE}
**可用(包含 cache/buffer)**: ${MEM_AVAILABLE}
**Swap 总量**: ${SWAP_TOTAL}
**Swap 已使用**: ${SWAP_USED} (${SWAP_USED_PCT}%)

> **巡检关注点**:如果 Swap 使用率持续高于 30%,说明物理内存可能不足。如果 Swap 已用为 0 但可用内存很少,说明 cache/buffer 被大量使用,这是 Linux 的正常行为,不需要恐慌。

```bash
# 内存详细使用情况
free -h:
$(free -h)

# 按进程内存使用排序(Top 10)
ps aux --sort=-%mem | head -11:
$(ps aux --sort=-%mem | head -11)
```

EOF

# ========== 磁盘巡检 ==========
log_info "采集磁盘信息..."

# 检查根分区和重要分区使用率
DISK_ALERT="正常"
df -h | awk 'NR>1 {
    usage=$5;
    sub(/%/, "", usage);
    if (usage > 80) {
        print "WARN: "$6" 使用率 " usage "%";
    }
}' | while read line; do
    DISK_ALERT="异常"
done

cat >> "$OUTPUT_FILE" << EOF
## 4. 磁盘信息

**整体状态**: ${DISK_ALERT}

### 4.1 文件系统使用情况

```
$(df -h | head -30)
```

### 4.2 磁盘 IO 统计

```bash
# 设备吞吐和 IOPS
iostat -x 1 2 | tail -n +7:
$(iostat -x 1 2 2>/dev/null | tail -n +7 || echo "iostat not available (install sysstat)")

# 每秒 IO 操作数
iostat -d 1 2 | tail -n +4:
$(iostat -d 1 2 2>/dev/null | tail -n +4 || echo "iostat not available")
```

### 4.3 inode 使用情况

```
$(df -i | head -20)
```

> **巡检关注点**:
> - 文件系统使用率超过 80% 需要关注,超过 90% 需要立即处理
> - inode 使用率超过 80% 同样需要关注(小文件过多时可能出现)
> - 如果 /var/log 或 /data 接近满载,优先清理历史日志

EOF

# ========== 网络巡检 ==========
log_info "采集网络信息..."

LISTENING_PORTS=$(ss -tlnp 2>/dev/null | awk 'NR>1 {print $4}' | sort -u)
ESTABLISHED_COUNT=$(ss -s 2>/dev/null | grep ESTAB | awk '{print $4}')
TIME_WAIT_COUNT=$(ss -s 2>/dev/null | grep TIME-WAIT | awk '{print $4}')
CLOSE_WAIT_COUNT=$(ss -s 2>/dev/null | grep CLOSE-WAIT | awk '{print $4}')

cat >> "$OUTPUT_FILE" << EOF
## 5. 网络信息

### 5.1 网卡配置

```bash
# IP 地址配置
ip addr show:
$(ip addr show | grep -E '^[0-9]+:|inet ' | head -30)

# 网卡速率和连接状态
cat /proc/net/dev:
$(cat /proc/net/dev | head -10)
```

### 5.2 端口监听情况

```bash
# 监听端口汇总
ss -tlnp | sort -k1,1n:
$(ss -tlnp 2>/dev/null | head -30 || ss -tln | head -30)
```

### 5.3 网络连接统计

**当前状态**:
- ESTABLISHED: ${ESTABLISHED_COUNT}
- TIME-WAIT: ${TIME_WAIT_COUNT}
- CLOSE-WAIT: ${CLOSE_WAIT_COUNT}

> **巡检关注点**:
> - TIME-WAIT 过多(>10000)可能是短连接过多导致,可能需要调整 `tcp_tw_reuse` 内核参数
> - CLOSE-WAIT 存在说明对端已关闭但本地未关闭 socket,需要检查应用层是否有连接泄漏
> - 如果 CLOSE-WAIT 持续不为 0 且数量较大,需要排查

```bash
# 按状态统计连接数
ss -s:
$(ss -s 2>/dev/null || echo "ss not available")

# 外部连接情况(谁在连接本机)
ss -tnp | grep ESTAB | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -10:
$(ss -tnp 2>/dev/null | grep ESTAB | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -10 || echo "need root")
```

### 5.4 路由表

```
$(ip route show 2>/dev/null || route -n 2>/dev/null)
```

EOF

# ========== 运行服务巡检 ==========
log_info "采集服务状态..."

# 常用服务列表(根据实际情况调整)
SERVICES_TO_CHECK="nginx httpd mysqld redis docker sshd crond rsyslog firewalld ufw"

cat >> "$OUTPUT_FILE" << EOF
## 6. 运行服务状态

### 6.1 关键服务状态

| 服务名 | 状态 | 开机自启 | 内存占用 |
|--------|------|----------|----------|
EOF

for svc in $SERVICES_TO_CHECK; do
    if systemctl list-unit-files | grep -q "^$svc.service"; then
        STATUS=$(systemctl is-active $svc 2>/dev/null || echo "unknown")
        ENABLED=$(systemctl is-enabled $svc 2>/dev/null || echo "unknown")
        MEM=$(ps aux 2>/dev/null | grep -E "[/]$svc " | awk '{sum+=$6} END {printf "%.1f MB", sum/1024}')
        echo "| $svc | $STATUS | $ENABLED | $MEM |" >> "$OUTPUT_FILE"
    fi
done

cat >> "$OUTPUT_FILE" << EOF

### 6.2 全部运行中服务

```bash
systemctl list-units --type=service --state=running --no-pager:
$(systemctl list-units --type=service --state=running --no-pager 2>/dev/null | head -40)
```

### 6.3 CPU/内存占用 Top 10 进程

```bash
# 按 CPU 使用排序
ps aux --sort=-%cpu | head -11:
$(ps aux --sort=-%cpu | head -11)

# 按内存使用排序
ps aux --sort=-%mem | head -11:
$(ps aux --sort=-%mem | head -11)
```

EOF

# ========== 系统日志巡检 ==========
log_info "采集系统日志..."

cat >> "$OUTPUT_FILE" << EOF
## 7. 系统日志(最近 24 小时)

### 7.1 错误级别日志(Error 及以上)

```
$(journalctl --since "24 hours ago" --priority=err --no-pager 2>/dev/null | tail -30 || echo "journalctl not available")
```

### 7.2 警告级别日志

```
$(journalctl --since "24 hours ago" --priority=warning --no-pager 2>/dev/null | tail -20 || echo "journalctl not available")
```

### 7.3 SSH 登录记录

```bash
# 成功登录记录
last -20 | grep -v reboot:
$(last -20 2>/dev/null | grep -v reboot | head -20)

# 失败登录记录
lastb -20 2>/dev/null | head -20 || echo "lastb not available or no failed logins"
```

EOF

# ========== 安全巡检 ==========
log_info "采集安全信息..."

cat >> "$OUTPUT_FILE" << EOF
## 8. 安全状态

### 8.1 特权用户(UID=0)

```bash
# 检查 UID 为 0 的非 root 用户
awk -F: '$3 == 0 {print $1}' /etc/passwd:
$(awk -F: '$3 == 0 {print $1}' /etc/passwd 2>/dev/null)
```

### 8.2 非活跃账户(90 天未登录)

```bash
# 检查非活跃账户
lastlog -b 90 2>/dev/null | grep "Never|pts|tty" | head -10 || echo "lastlog -b not available"
```

### 8.3 SElinux / AppArmor 状态

```bash
# CentOS/RHEL SElinux
getenforce 2>/dev/null || echo "SELinux not available"
sestatus 2>/dev/null | head -5 || echo "sestatus not available"

# Ubuntu AppArmor
aa-status 2>/dev/null || echo "AppArmor not available"
```

### 8.4 防火墙状态

```bash
# firewalld (CentOS/RHEL)
firewall-cmd --state 2>/dev/null || echo "firewalld not running"

# ufw (Ubuntu)
ufw status 2>/dev/null || echo "ufw not available"

# iptables rules summary
iptables -L -n --line-numbers 2>/dev/null | head -30 || echo "iptables not available"
```

EOF

# ========== 应用层巡检 ==========
log_info "采集应用状态..."

cat >> "$OUTPUT_FILE" << EOF
## 9. 应用层巡检

### 9.1 Nginx 状态

```bash
# Nginx 版本
nginx -v 2>&1

# Nginx 进程状态
ps aux | grep nginx | grep -v grep:

# Nginx 连接状态
ss -tnp | grep nginx || echo "no nginx connections"
```

EOF

# MySQL 状态
cat >> "$OUTPUT_FILE" << 'MYSQL_EOF'
### 9.2 MySQL 状态

```bash
# MySQL 版本
mysql --version 2>/dev/null || echo "mysql client not available"

# MySQL 进程
ps aux | grep mysql | grep -v grep:

# MySQL 连接数
mysql -e "SHOW STATUS LIKE 'Threads_connected';" 2>/dev/null
mysql -e "SHOW STATUS LIKE 'Max_used_connections';" 2>/dev/null
mysql -e "SHOW PROCESSLIST;" 2>/dev/null | head -20

 

MYSQL_EOF

Redis 状态

cat >> "$OUTPUT_FILE" << 'REDIS_EOF'

9.3 Redis 状态

 

# Redis 进程
ps aux | grep redis | grep -v grep:

# Redis 连接状态
redis-cli ping 2>/dev/null || echo "redis-cli not available or redis not running"
redis-cli info replication 2>/dev/null | grep -E "role|connected_slaves" || echo "replication info not available"
redis-cli info memory 2>/dev/null | grep -E "used_memory_human|maxmemory_human" || echo "memory info not available"

 

REDIS_EOF

Docker 状态

cat >> "$OUTPUT_FILE" << 'DOCKER_EOF'

9.4 Docker 状态

 

# Docker 版本
docker --version 2>/dev/null || echo "docker not available"

# Docker 服务状态
systemctl is-active docker 2>/dev/null || echo "docker service not running"

# 运行中的容器
docker ps 2>/dev/null || echo "docker not available"

# 容器资源使用
docker stats --no-stream 2>/dev/null | head -10 || echo "no containers or docker not available"

# 镜像数量和占用空间
docker images --format "table {{.Repository}}	{{.Tag}}	{{.Size}}" 2>/dev/null | head -20 || echo "no docker images"

 

DOCKER_EOF

========== 综合结论 ==========

log_info "生成巡检结论..."

动态判断告警项

ALERTS=""

检查磁盘告警

DISK_WARNINGS=$(df -h | awk 'NR>1 { usage=$5; sub(/%/, "", usage); if (usage > 80) { printf "- 严重: %s 使用率 %s%% ", $6, usage } else if (usage > 70) { printf "- 警告: %s 使用率 %s%% ", DISK_WARNINGS" ] && ALERTS="{DISK_WARNINGS} "

检查内存告警

MEM_PCT=MEM_PCT" -gt 90 ]; then ALERTS="${ALERTS} ### 内存告警 - 严重: 内存使用率 MEM_PCT" -gt 80 ]; then ALERTS="${ALERTS} ### 内存告警 - 警告: 内存使用率 ${MEM_PCT}% " fi

检查负载告警

LOAD_15=$(cat /proc/loadavg | awk 'print (nproc 2>/dev/null| echo 1) LOAD_INT=${LOAD_15%.*2>/dev/null || LOAD_INT=0 if [ "CORES" ]; then ALERTS="${ALERTS} ### 负载告警 - 警告: 15分钟负载 {CORES} " fi

检查可疑登录

FAILED_LOGIN=FAILED_LOGIN" -gt 10 ]; then ALERTS="${ALERTS} ### 安全告警 - 警告: 近 20 条失败登录尝试,请检查是否有暴力破解 " fi

如果没有告警

[ -z "$ALERTS" ] && ALERTS=" 无严重告警。 "

cat >> "$OUTPUT_FILE" << EOF

10. 巡检结论

${ALERTS}

10.1 巡检摘要

检查项 状态 说明
CPU LOAD_INT" -ge "$CORES" ] && echo " 异常"  
内存 MEM_PCT" -gt 80 ] && echo " 异常"  
磁盘 $(df -h awk 'NR>1 {usage=$5; sub(/%/, "", usage); if (usage>80) status=" 异常"} END {print status ? status : " 正常"}')
网络 TIME_WAIT_COUNT" -gt 10000 ] && echo " 异常"  
服务 $(systemctl is-active nginx mysqld redis firewalld 2>/dev/null grep -q "failed" && echo " 异常"
日志 $(journalctl --since "24 hours ago" --priority=err --no-pager 2>/dev/null tail -1

10.2 下次巡检建议

EOF

添加动态建议

echo "" >> "OUTPUT_FILE" fi if [ "OUTPUT_FILE" fi if systemctl is-active firewalld 2>/dev/null | grep -q inactive; then echo "- 防火墙处于关闭状态,建议评估是否需要开启" >> "$OUTPUT_FILE" fi

cat >> "$OUTPUT_FILE" << EOF

本报告由 inspect.sh 脚本自动生成,生成时间:${TIMESTAMP}EOF

log_info "巡检报告已生成: ${OUTPUT_FILE}"

输出文件大小

ls -lh "$OUTPUT_FILE"

如果有告警,打印到终端

if [ -n "{ALERTS}" != " 无严重告警。 " ]; then log_warn "发现以下告警项:" echo "${ALERTS}" fi

 

设置执行权限:

```bash
chmod +x ~/server-inspection/inspect.sh

 

远程批量采集脚本:collect_remote.sh

在控制节点上运行这个脚本,可以批量从多台服务器采集巡检报告:

 

#!/bin/bash
# ~/server-inspection/collect_remote.sh
# 批量从多台服务器采集巡检报告
# 用法: ./collect_remote.sh servers.txt

set -euo pipefail

if [ -z "${1:-}" ]; then
    echo "用法: $0 <服务器列表文件>"
    echo "服务器列表文件格式(每行一个,格式: 主机名或IP 用户 SSH端口(可选)):"
    echo "  192.168.1.11 root"
    echo "  nginx-02 root 2222"
    echo "  db-01"
    exit 1
fi

SERVER_LIST="$1"
OUTPUT_DIR="./remote-reports-$(date +%Y%m%d)"
SSH_USER="${SSH_USER:-root}"
SSH_KEY="${SSH_KEY:-$HOME/.ssh/id_ed25519}"
SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10 -i $SSH_KEY"

# 巡检脚本内容(通过 stdin 传给远程服务器)
INSPECT_SCRIPT=$(cat << 'SCRIPT_EOF'
set -euo pipefail

OUTPUT_DIR="${1:-.}"
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
REPORT_DATE=$(date "+%Y-%m-%d")
HOSTNAME=$(hostname)
PRIMARY_IP=$(hostname -I | awk '{print $1}')
OS_VERSION=$(cat /etc/redhat-release 2>/dev/null || cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)

mkdir -p "$OUTPUT_DIR"
OUTPUT_FILE="${OUTPUT_DIR}/${REPORT_DATE}-${HOSTNAME}.md"

exec > "$OUTPUT_FILE"

# ... 这里是完整巡检逻辑(与 inspect.sh 相同的核心逻辑)
# 为了简洁,这里用 heredoc 引用 inspect.sh 的内容
SCRIPT_EOF
)

mkdir -p "$OUTPUT_DIR"

echo "开始批量采集,目标目录: $OUTPUT_DIR"
echo "=========================================="

TOTAL=$(grep -v "^#" "$SERVER_LIST" | grep -v "^$" | wc -l)
CURRENT=0

while IFS= read -r line; do
    [ -z "$line" ] && continue
    [[ "$line" =~ ^# ]] && continue

    CURRENT=$((CURRENT + 1))
    # 支持格式: IP PORT USER 或 IP USER 或 IP
    set -- $line
    SERVER=$1
    USER="${2:-$SSH_USER}"
    PORT="${3:-22}"

    echo "[$CURRENT/$TOTAL] 正在采集: $SERVER (user=$USER, port=$PORT)"

    # 把 inspect.sh 脚本内容通过 ssh 传给远程服务器执行
    # 这里用 ssh 执行本地脚本: ssh user@host "bash -s" < script.sh
    ssh $SSH_OPTS -p "$PORT" "${USER}@${SERVER}" 
        "bash -s" -- "${OUTPUT_DIR}" < <(cat ~/server-inspection/inspect.sh) 
        >> "${OUTPUT_DIR}/collect.log" 2>&1 &

    echo "  -> 已后台启动,将在后台运行"
done < "$SERVER_LIST"

echo ""
echo "=========================================="
echo "所有采集任务已后台启动"
echo "等待任务完成..."

# 等待所有后台任务完成(最多等 10 分钟)
wait_timeout=600
elapsed=0
while pgrep -f "ssh.*inspect" > /dev/null 2>&1; do
    sleep 5
    elapsed=$((elapsed + 5))
    if [ $elapsed -ge $wait_timeout ]; then
        echo "等待超时,停止等待"
        break
    fi
    echo "  已等待 ${elapsed}s,仍有任务在运行..."
done

echo ""
echo "=========================================="
echo "采集完成!报告保存在: $OUTPUT_DIR"
echo ""
echo "生成的报告文件:"
ls -lh "$OUTPUT_DIR"/*.md 2>/dev/null || echo "  没有找到报告文件"
chmod +x ~/server-inspection/collect_remote.sh

 

服务器列表文件示例 servers.txt:

 

# 服务器巡检列表
# 格式: IP 或 主机名 用户 SSH端口(可选)

# Web 服务器组
192.168.1.11 root 22
192.168.1.12 root 22

# 数据库服务器组
192.168.1.21 root 22
192.168.1.22 root 22

# Nginx 服务器
192.168.1.10 ops 2222

 

定时任务:自动巡检

把巡检脚本加入 crontab,实现自动定时执行:

 

# 编辑 crontab
crontab -e

# 每天早上 8 点执行巡检
0 8 * * * /bin/bash /root/server-inspection/inspect.sh /root/server-inspection/reports >> /root/server-inspection/reports/cron.log 2>&1

# 每周一早上 8 点执行巡检(详细版)
0 8 * * 1 /bin/bash /root/server-inspection/inspect.sh /root/server-inspection/reports-weekly >> /root/server-inspection/reports-weekly/cron.log 2>&1

 

报告示例输出

执行后的 Markdown 报告大致如下(截取关键部分):

 

# 服务器巡检报告

**生成时间**: 2024-01-15 0800
**主机名**: nginx-01
**主 IP 地址**: 192.168.1.11
**操作系统**: CentOS Linux 7.9.2009
**内核版本**: 3.10.0-1160.el7.x86_64
**运行时长**: up 45 days
**最近重启**: 2023-12-01 06:30
**负载平均值**: 0.15 0.12 0.10

---

## 2. CPU 信息

**CPU 型号**: Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz
**物理核心数**: 2
**逻辑核心数**: 4
**当前使用率**: 用户态: 2.3%, 系统态: 0.8%, 空闲: 96.9%
**15分钟负载**: 0.12

---

## 4. 磁盘信息

| 文件系统 | 大小 | 已用 | 可用 | 使用率 | 挂载点 |
|---------|------|------|------|--------|--------|
| /dev/sda1 | 50G | 35G | 15G | 70% | / |
| /dev/sda2 | 200G | 160G | 40G | 80% | /data |

> **巡检关注点**:/data 使用率 80%,建议清理历史数据或计划扩容

---

## 10. 巡检结论

### 磁盘告警
- **警告**: /data 使用率 80%

### 巡检摘要

| 检查项 | 状态 | 说明 |
|--------|------|------|
| CPU |  正常 | 负载: 0.12, 核心数: 4 |
| 内存 |  正常 | 使用率: 62% |
| 磁盘 |  异常 | /data 使用率 80% |

 

常见问题排查

问题一:脚本执行报错 "command not found"

原因:部分系统默认没有装某个命令

解决方案:在脚本里加判断,缺失时给出友好提示

 

for cmd in df free ss awk sed; do
    if ! command -v $cmd &> /dev/null; then
        echo "WARN: $cmd not found, some features may not work"
    fi
done

 

问题二:中文显示乱码

原因:服务器 locale 设置不支持中文

解决方案:设置环境变量

 

export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8

 

或者在脚本输出时只使用 ASCII 字符。

问题三:远程执行时 heredoc 格式丢失

原因:SSH 传输时某些特殊字符被转义

解决方案:使用 base64 编码传输脚本

 

# 控制节点上
SCRIPT_B64=$(base64 -w0 ~/server-inspection/inspect.sh)
ssh $SSH_OPTS user@host "echo '$SCRIPT_B64' | base64 -d > /tmp/inspect.sh && bash /tmp/inspect.sh"

 

风险提醒

脚本执行时机:巡检脚本本身对服务器压力极小,但 iostat、ps aux --sort 等命令会瞬时增加少量系统开销。建议不要在业务高峰期(比如大促前夕)执行巡检。

报告存放安全:巡检报告包含系统详细信息(IP、端口、服务版本等),本质上是一个"完整的系统画像",敏感环境切勿将报告放在公开可访问的目录。

日志泄露:报告中包含近期登录记录、错误日志等,可能包含业务敏感信息,需要注意报告的存储和分发范围。

SSH 免密安全:如果使用 SSH 公钥执行远程采集,确保控制节点的私钥权限正确(600),避免被滥用。

总结

本文实现了一套轻量级的服务器巡检自动化方案,核心就两个脚本:

inspect.sh:在每台服务器上执行,采集本机数据,输出 Markdown 报告

collect_remote.sh:在控制节点执行,批量从多台服务器采集报告

用定时任务驱动,就是一套无人值守的巡检系统。整个方案不依赖 Ansible 等重型工具,纯 Shell + 系统自带工具,实现简单、维护成本低。

这套方案的扩展方向:

接入告警:把脚本里的 ALERTS 输出对接钉钉/企业微信/邮件告警

历史对比:用 Git 管理报告目录,diff 两次报告看变化

数据入库:把关键指标(磁盘使用率、内存使用率)提取出来存入时序数据库

可视化:用 Grafana 展示巡检数据趋势

合规报告:针对等保等合规要求,添加特定的检查项

从今天起,巡检不再是"登录服务器敲几个命令截图丢群里",而是一条命令搞定、一份结构化报告存档。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分