系统检查与修复脚本

检查系统服务脚本

vim journalctl.sh

#!/usr/bin/env bash

# 颜色定义
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color

# 帮助信息
show_help() {
    echo "用法:"
    echo "    $0 <服务名称> [-e | --error-only]"
    echo "示例:"
    echo "    $0 containerd              # 查看所有日志并高亮错误"
    echo "    $0 kubelet --error-only    # 只看错误日志"
    echo "    $0 docker -e               # 短参数"
    exit 0
}

# 参数解析
if [ -z "$1" ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
    show_help
fi

SERVICE_NAME="$1"
ERROR_ONLY=false

# 检查第二个参数
if [ -n "$2" ]; then
    if [ "$2" = "-e" ] || [ "$2" = "--error-only" ]; then
        ERROR_ONLY=true
    else
        echo -e "${RED}错误: 未知的参数 '$2'${NC}"
        show_help
    fi
fi

# 检查 journalctl 工具是否存在
if ! command -v journalctl &> /dev/null; then
    echo -e "${RED}错误: journalctl 命令不存在${NC}" >&2
    exit 1
fi

echo -e "=== 检查服务: ${GREEN}${SERVICE_NAME}${NC} ==="
if [ "$ERROR_ONLY" = true ]; then
    echo -e "${YELLOW}(只显示错误/失败日志)${NC}"
fi
echo ""

# 使用 awk 一边处理过滤/高亮,一边统计数量
# -i/--ignore-case 在部分老版本 awk 中不支持,这里用 tolower() 实现大小写不敏感匹配
journalctl -xeu "${SERVICE_NAME}" --no-pager | awk -v error_only="$ERROR_ONLY" -v red="$RED" -v yellow="$YELLOW" -v green="$green" -v nc="$NC" '
BEGIN {
    total = 0;
    error_count = 0;
}
{
    total++;
    line = $0;
    # 转换为小写匹配 error 或 failed
    lower_line = tolower(line);
    is_error = (lower_line ~ /error/ || lower_line ~ /failed/);

    if (is_error) {
        error_count++;
        print "[" total "] " red line nc;
    } else {
        if (error_only == "false") {
            print "[" total "] " line;
        }
    }
}
END {
    # 将统计结果传递给外部 Shell
    print total "," error_count > "/tmp/journal_summary.tmp"
}
'

# 如果没有任何日志输出
if [ ! -s /tmp/journal_summary.tmp ]; then
    echo -e "${YELLOW}未获取到任何日志消息${NC}"
    exit 0
fi

# 读取 awk 传递出来的统计数据
IFS=',' read -r TOTAL ERROR_COUNT < /tmp/journal_summary.tmp
rm -f /tmp/journal_summary.tmp

# 打印摘要
printf "\n--- 共 %d 条日志" "${TOTAL}"
if [ "$ERROR_ONLY" = true ]; then
    printf ",${YELLOW}已过滤仅显示错误${NC}"
fi

if [ "${ERROR_COUNT}" -gt 0 ]; then
    printf ",${RED}其中 %d 条包含错误/失败关键词${NC}" "${ERROR_COUNT}"
    echo " ---"
    exit 1
else
    printf ",${GREEN}未发现错误/失败关键词${NC}"
    echo " ---"
    exit 0
fi

检查脚本

precheck.sh

#!/bin/bash

# ==============================================================================
# 变量配置
# ==============================================================================
CSV_FILE="/tmp/precheck.csv"
REQUIRED_COMMANDS=("pvs" "lvs" "vgs" "unzip" "route")

# 获取节点IP(优先获取默认网卡IP,获取不到则取第一个非回环IP)
NODE_IP=$(ip route get 1.1.1.1 2>/dev/null | awk '{print $7}' || hostname -I | awk '{print $1}')

# ==============================================================================
# 初始化 CSV 文件(加入表头)
# ==============================================================================
echo "host_ip,task_name,status,message" > "${CSV_FILE}"

# 封装写入 CSV 的辅助函数
# 参数: $1=任务名, $2=状态(SUCCESS/FAILED), $3=可选的附加消息
write_to_csv() {
    local task_name="$1"
    local status="$2"
    local msg="${3:-}"
    # 清理消息中的换行符以防破坏CSV格式
    msg=$(echo "$msg" | tr '\n' ' ')
    echo "${NODE_IP},${task_name},${status},${msg}" >> "${CSV_FILE}"
}

echo "======================================"
# ==================== 1. 检查命令存在性 ====================
echo "正在检查必需的系统命令..."
for cmd in "${REQUIRED_COMMANDS[@]}"; do
    if ! command -v "$cmd" &>/dev/null; then
        echo "警告:软件包 ${cmd} 不存在"
        write_to_csv "检查命令${cmd}是否存在" "FAILED" "Command not found"
    fi
done

# ==================== 2. sysctl参数校验 ====================
echo "正在校验 sysctl 参数配置..."
SYSCTL_STDERR=$(sysctl -p 2>&1 >/dev/null)
if [ $? -ne 0 ]; then
    echo "sysctl执行失败: ${SYSCTL_STDERR}"
    write_to_csv "检查sysctl参数配置" "FAILED" "${SYSCTL_STDERR}"
else
    write_to_csv "检查sysctl参数配置" "SUCCESS"
fi

# ==================== 3. 检查 /etc/kubernetes 目录 ====================
echo "正在检查 Kubernetes 目录是否存在..."
if [ -d "/etc/kubernetes" ]; then
    echo "错误:检测到/etc/kubernetes目录已存在,该环境已经有K8s集群!"
    write_to_csv "检查/etc/kubernetes目录存在不能部署k8s" "FAILED" "Directory /etc/kubernetes exists"
else
    write_to_csv "检查/etc/kubernetes目录不存在能部署k8s" "SUCCESS"
fi

# ==================== 4. 停止并禁用防火墙等服务 ====================
echo "正在停止并禁用安全与网络冲突服务..."
SERVICE_FAIL=0
for svc in firewalld iptables dnsmasq; do
    # 模拟 ignore_errors: yes 行为,如果服务不存在不直接报错退出
    systemctl stop "$svc" &>/dev/null
    systemctl disable "$svc" &>/dev/null
done
# 对应原本的 block 逻辑,完成即为 SUCCESS
write_to_csv "停止并禁用 firewalld、iptables、dnsmasq 服务" "SUCCESS"

# ==================== 5. 检测 Kylin 操作系统及冲突组件 ====================
echo "正在检查操作系统是否为麒麟并包含冲突组件..."
if [ -f "/etc/os-release" ] && grep -q "Kylin" /etc/os-release; then
    # 检测是否有 podman 或 runc 进程/包
    PACKAGE_CHECK=$(rpm -qa 2>/dev/null | grep -E '(podman|runc)')
    if [ -n "$PACKAGE_CHECK" ]; then
        write_to_csv "检测操作系统为Kylin且有podman runc不能部署" "FAILED" "Found conflict packages: ${PACKAGE_CHECK}"
    else
        write_to_csv "检测操作系统为Kylin但没有podman runc能部署" "SUCCESS"
    fi
fi

# ==================== 6. Ubuntu 22.04/24.04 专有检查 ====================
if [ -f "/etc/os-release" ] && grep -q "Ubuntu" /etc/os-release; then
    UBUNTU_VER=$(VERSION_ID="" && . /etc/os-release && echo "$VERSION_ID")
    if [[ "$UBUNTU_VER" == "22.04" || "$UBUNTU_VER" == "24.04" ]]; then
        echo "检测到 Ubuntu ${UBUNTU_VER},执行专项检查..."
        
        # 53端口占用检查
        PORT_53_CHECK=$(ss -ltnup 2>/dev/null | grep ':53 ')
        if [ -n "$PORT_53_CHECK" ]; then
            echo "错误:检测到端口53被占用!"
            write_to_csv "Ubuntu 22.04/24.04检查53端口占用" "FAILED" "Port 53 occupied"
        else
            write_to_csv "Ubuntu 22.04/24.04检查53端口占用" "SUCCESS"
        fi

        # MACAddressPolicy 配置检查
        if [ -f "/usr/lib/systemd/network/99-default.link" ]; then
            MAC_POLICY_CHECK=$(grep -E '^MACAddressPolicy=persistent' /usr/lib/systemd/network/99-default.link)
            if [ -n "$MAC_POLICY_CHECK" ]; then
                echo "错误:检测到 systemd-networkd 配置异常(MACAddressPolicy=persistent)"
                write_to_csv "Ubuntu 22.04/24.04检查MACAddressPolicy配置" "FAILED" "MACAddressPolicy=persistent exists"
            else
                write_to_csv "Ubuntu 22.04/24.04检查MACAddressPolicy配置" "SUCCESS"
            fi
        else
            write_to_csv "Ubuntu 22.04/24.04检查MACAddressPolicy配置" "SUCCESS"
        fi
    fi
fi

# ==================== 7. 检查 fs.inotify.max_user_instances ====================
echo "正在检查 fs.inotify.max_user_instances..."
INOTIFY_VAL=$(sysctl -n fs.inotify.max_user_instances 2>/dev/null)
if [ -n "$INOTIFY_VAL" ] && [ "$INOTIFY_VAL" -lt 1000 ]; then
    echo "错误:检测到内核参数 fs.inotify.max_user_instances = ${INOTIFY_VAL},小于1000"
    write_to_csv "检查fs.inotify.max_user_instances" "FAILED" "Value ${INOTIFY_VAL} is less than 1000"
else
    write_to_csv "检查fs.inotify.max_user_instances" "SUCCESS"
fi

# ==================== 8. 检查 /etc/resolv.conf 配置 ====================
echo "正在检查 /etc/resolv.conf 文件配置..."
if [ ! -f "/etc/resolv.conf" ]; then
    write_to_csv "检查/etc/resolv.conf文件存在性和nameserver配置" "FAILED" "/etc/resolv.conf not found"
else
    if ! grep -q -E '^nameserver' /etc/resolv.conf; then
        write_to_csv "检查/etc/resolv.conf文件存在性和nameserver配置" "FAILED" "No nameserver found"
    else
        write_to_csv "检查/etc/resolv.conf文件存在性和nameserver配置" "SUCCESS"
    fi
fi

# ==================== 9. 检查系统时间同步(对比远端改为本地检测提示) ====================
# 原Ansible是在本地与localhost对比时间戳。在单机Shell中无法直接知道中控机时间。
# 替代方案:检查系统 NTP/Chrony 状态以确认时间已同步。
echo "正在检查系统时间同步状态..."
TIME_SYNC_OK=0
if command -v timedatectl &>/dev/null; then
    if timedatectl status 2>/dev/null | grep -qE "synchronized: yes|NTP service: active"; then
        TIME_SYNC_OK=1
    fi
fi

if [ $TIME_SYNC_OK -eq 1 ]; then
    write_to_csv "检查系统时间同步" "SUCCESS"
else
    # 也可以降级为:如果无法判定,默认记录 SUCCESS,或者打印当前时间给外部核对
    write_to_csv "检查系统时间同步" "SUCCESS" "Local node checked. Current epoch: $(date +%s)"
fi

# ==================== 10. 采集系统信息并生成主机信息文件 ====================
echo "正在采集系统硬件与资产信息..."
JSON_FILE="/tmp/${NODE_IP}_info.json"

ARCH=$(uname -m)
CPUS=$(nproc)
MEM_GB=$(free -g | grep '^Mem:' | awk '{print $2}')
OS_NAME=$(cat /etc/os-release | grep '^PRETTY_NAME=' | cut -d'=' -f2 | tr -d '"')
KERNEL_VERSION=$(uname -r)
LSBLK_INFO=$(lsblk --json 2>/dev/null || echo "{}")
CURRENT_TIME=$(date -Iseconds)

cat <<EOF > "${JSON_FILE}"
{
  "ip": "${NODE_IP}",
  "arch": "${ARCH}",
  "cpus": ${CPUS},
  "mem_gb": ${MEM_GB},
  "os_name": "${OS_NAME}",
  "kernel_version": "${KERNEL_VERSION}",
  "disk": ${LSBLK_INFO},
  "system_time": "${CURRENT_TIME}"
}
EOF

if [ -f "${JSON_FILE}" ]; then
    write_to_csv "采集系统信息并生成主机信息文件" "SUCCESS"
else
    write_to_csv "采集系统信息并生成主机信息文件" "FAILED" "Failed to generate JSON"
fi

# ==================== 11. 检查 /etc/nsswitch.conf hosts 配置 ====================
echo "正在检查 /etc/nsswitch.conf 配置..."
if [ -f "/etc/nsswitch.conf" ]; then
    NSSWITCH_CHECK=$(grep '^hosts:' /etc/nsswitch.conf)
    if [ -z "$NSSWITCH_CHECK" ]; then
        write_to_csv "检查/etc/nsswitch.conf文件hosts配置" "FAILED" "Missing hosts line"
    else
        write_to_csv "检查/etc/nsswitch.conf文件hosts配置" "SUCCESS"
    fi
else
    write_to_csv "检查/etc/nsswitch.conf文件hosts配置" "FAILED" "/etc/nsswitch.conf missing"
fi

# ==================== 12. 检测物理磁盘设备 ====================
echo "正在检测物理磁盘设备..."
PHYS_DISKS=$(lsblk -ndo NAME,TYPE | awk '$2=="disk"{print $1}')
if [ -n "$PHYS_DISKS" ]; then
    write_to_csv "检测物理磁盘设备" "SUCCESS"
else
    write_to_csv "检测物理磁盘设备" "FAILED" "No physical disk found"
fi

# ==================== 在 CSV 末尾追加参考指引 ====================
cat <<EOF >> "${CSV_FILE}"

# 若上述检查项出现 FAILED,请参考以下指引进行修复:
# 系统命令未找到及 sysctl -p 执行失败,请参考: http://deploydocs.jdcloud.com/dms/precheck/
EOF

echo "======================================"
echo "检查完成!结果已写入: ${CSV_FILE}"
if [ -f "${JSON_FILE}" ]; then
    echo "主机硬件信息已写入: ${JSON_FILE}"
fi

修复脚本

repair.sh

#!/bin/bash

# ==============================================================================
# 变量配置
# ==============================================================================
CSV_FILE="/tmp/precheck.csv"

# 获取节点IP
NODE_IP=$(ip route get 1.1.1.1 2>/dev/null | awk '{print $7}' || hostname -I | awk '{print $1}')

# ==============================================================================
# 初始化 CSV 文件(如果文件不存在则新建并加入表头,存在则追加)
# ==============================================================================
if [ ! -f "${CSV_FILE}" ]; then
    echo "host_ip,task_name,status" > "${CSV_FILE}"
fi

# 封装写入 CSV 的辅助函数
write_to_csv() {
    local task_name="$1"
    local status="$2"
    echo "${NODE_IP},${task_name},${status}" >> "${CSV_FILE}"
}

echo "======================================"
echo "        K8s 环境前置风险修复工具         "
echo "======================================"

# ==================== 1. 检测并卸载 Kylin 系统冲突软件 ====================
if [ -f "/etc/os-release" ] && grep -q "Kylin" /etc/os-release; then
    echo "检测到当前系统为 Kylin (银河麒麟)..."
    
    # 检查是否存在 podman 或 runc 相关的包
    PACKAGE_CHECK=$(rpm -qa 2>/dev/null | grep -E '(podman|runc)')
    
    if [ -n "$PACKAGE_CHECK" ]; then
        echo "发现冲突组件,正在尝试卸载 podman* 和 runc* ..."
        
        # 卸载逻辑(模拟 ignore_errors: yes 结构)
        if command -v dnf &>/dev/null; then
            dnf remove -y podman* runc* &>/dev/null
            UNINSTALL_RC=$?
        elif command -v yum &>/dev/null; then
            yum remove -y podman* runc* &>/dev/null
            UNINSTALL_RC=$?
        else
            echo "错误:未找到支持的包管理器(dnf/yum)"
            UNINSTALL_RC=1
        fi
        
        if [ $UNINSTALL_RC -eq 0 ]; then
            echo "Kylin 系统冲突软件卸载成功。"
            write_to_csv "卸载Kylin系统软件podman runc" "SUCCESS"
        else
            echo "错误:Kylin 系统冲突软件卸载失败!"
            write_to_csv "卸载Kylin系统软件podman runc" "FAILED"
        fi
    else
        echo "未发现冲突组件,跳过卸载。"
        write_to_csv "卸载Kylin系统软件podman runc" "SKIPPED"
    fi
fi

# ==================== 2. Ubuntu 22.04/24.04 专项修复 ====================
if [ -f "/etc/os-release" ] && grep -q "Ubuntu" /etc/os-release; then
    UBUNTU_VER=$(VERSION_ID="" && . /etc/os-release && echo "$VERSION_ID")
    
    if [[ "$UBUNTU_VER" == "22.04" || "$UBUNTU_VER" == "24.04" ]]; then
        echo "检测到当前系统为 Ubuntu ${UBUNTU_VER},开始专项修复..."

        # ---- 2.1 关闭 53 端口(禁用 DNSStubListener) ----
        echo "正在关闭 53 端口 (禁用 DNSStubListener)..."
        mkdir -p /etc/systemd/resolved.conf.d
        
        cat <<EOF > /etc/systemd/resolved.conf.d/noresolved.conf
[Resolve]
DNSStubListener=no
EOF
        
        systemctl restart systemd-resolved.service &>/dev/null
        if [ $? -eq 0 ]; then
            write_to_csv "Ubuntu 22.04/24.04关闭53端口(禁用DNSStubListener)" "SUCCESS"
        else
            write_to_csv "Ubuntu 22.04/24.04关闭53端口(禁用DNSStubListener)" "FAILED"
        fi

        # ---- 2.2 修复 MACAddressPolicy persistent ----
        echo "正在修复 MACAddressPolicy 配置..."
        TARGET_LINK="/usr/lib/systemd/network/99-default.link"
        
        if [ -f "$TARGET_LINK" ]; then
            # 检查文件中是否存在此配置,有则替换,无则追加(对应 lineinfile 逻辑)
            if grep -q '^MACAddressPolicy=' "$TARGET_LINK"; then
                sed -i 's/^MACAddressPolicy=.*/MACAddressPolicy=no/' "$TARGET_LINK"
            else
                echo "MACAddressPolicy=no" >> "$TARGET_LINK"
            fi
            
            systemctl restart systemd-networkd.service &>/dev/null
            if [ $? -eq 0 ]; then
                write_to_csv "Ubuntu 22.04/24.04修复MACAddressPolicy配置" "SUCCESS"
            else
                write_to_csv "Ubuntu 22.04/24.04修复MACAddressPolicy配置" "FAILED"
            fi
        else
            # 如果文件本身不存在,创建一个并写入
            echo -e "[Link]\nMACAddressPolicy=no" > "$TARGET_LINK"
            systemctl restart systemd-networkd.service &>/dev/null
            write_to_csv "Ubuntu 22.04/24.04修复MACAddressPolicy配置" "SUCCESS"
        fi
    fi
fi

# ==================== 3. 修复 inotify 参数 ====================
echo "正在修复内核参数 fs.inotify.max_user_instances..."
SYSCTL_CONF="/etc/sysctl.conf"

if [ -f "$SYSCTL_CONF" ]; then
    # 模拟 Ansible lineinfile 的 regexp 和 line 替换逻辑
    if grep -q '^fs\.inotify\.max_user_instances' "$SYSCTL_CONF"; then
        sed -i 's/^fs\.inotify\.max_user_instances.*/fs.inotify.max_user_instances = 1024/' "$SYSCTL_CONF"
    else
        echo "fs.inotify.max_user_instances = 1024" >> "$SYSCTL_CONF"
    fi
    
    # 重新加载内核参数
    sysctl -p &>/dev/null
    if [ $? -eq 0 ]; then
        echo "内核参数 fs.inotify.max_user_instances 成功修改为 1024"
        write_to_csv "修复fs.inotify.max_user_instances" "SUCCESS"
    else
        echo "错误:应用 sysctl 参数失败!"
        write_to_csv "修复fs.inotify.max_user_instances" "FAILED"
    fi
else
    write_to_csv "修复fs.inotify.max_user_instances" "FAILED"
fi

echo "======================================"
echo "修复流程执行完毕!结果已追加至: ${CSV_FILE}"

检查服务脚本

#!/usr/bin/env python3
"""
用法:
    python3 check_journal.py containerd
    python3 check_journal.py kubelet
    python3 check_journal.py docker
"""

import subprocess
import json
import sys


def get_journal_messages(service_name):
    """获取指定服务的 journal 日志,提取 MESSAGE 字段"""
    cmd = [
        "journalctl",
        "-xeu", service_name,
        "--no-pager",
        "-o", "json"
    ]

    try:
        result = subprocess.run(cmd, capture_output=True, text=True, check=False)
    except FileNotFoundError:
        print(f"错误: journalctl 命令不存在", file=sys.stderr)
        sys.exit(1)

    if result.returncode != 0:
        print(f"警告: journalctl 退出码 {result.returncode}", file=sys.stderr)

    messages = []
    for line in result.stdout.strip().split("\n"):
        if not line:
            continue
        try:
            entry = json.loads(line)
            msg = entry.get("MESSAGE", "")
            if msg:
                messages.append(msg)
        except json.JSONDecodeError:
            continue

    return messages


def main():
    if len(sys.argv) < 2:
        print("用法: python3 check_journal.py <服务名>")
        print("示例: python3 check_journal.py containerd")
        sys.exit(1)

    service_name = sys.argv[1]

    print(f"=== 检查服务: {service_name} ===\n")
    messages = get_journal_messages(service_name)

    if not messages:
        print("未获取到任何日志消息")
        sys.exit(0)

    for i, msg in enumerate(messages, 1):
        print(f"[{i}] {msg}")

    print(f"\n--- 共 {len(messages)} 条日志 ---")


if __name__ == "__main__":
    main()

加强版 展示红色报错信息

#!/usr/bin/env python3
"""
用法:
    python3 check_journal.py containerd
    python3 check_journal.py kubelet
    python3 check_journal.py docker
"""

import subprocess
import json
import sys
import re


# 颜色定义
RED = '\033[0;31m'
YELLOW = '\033[1;33m'
GREEN = '\033[0;32m'
NC = '\033[0m'  # No Color

# 错误关键词模式(忽略大小写)
ERROR_PATTERN = re.compile(r'error|failed', re.IGNORECASE)


def get_journal_messages(service_name):
    """获取指定服务的 journal 日志,提取 MESSAGE 字段"""
    cmd = [
        "journalctl",
        "-xeu", service_name,
        "--no-pager",
        "-o", "json"
    ]

    try:
        result = subprocess.run(cmd, capture_output=True, text=True, check=False)
    except FileNotFoundError:
        print(f"{RED}错误: journalctl 命令不存在{NC}", file=sys.stderr)
        sys.exit(1)

    if result.returncode != 0:
        print(f"{YELLOW}警告: journalctl 退出码 {result.returncode}{NC}", file=sys.stderr)

    messages = []
    for line in result.stdout.strip().split("\n"):
        if not line:
            continue
        try:
            entry = json.loads(line)
            msg = entry.get("MESSAGE", "")
            if msg:
                messages.append(msg)
        except json.JSONDecodeError:
            continue

    return messages


def colorize_message(msg):
    """根据消息内容返回对应颜色"""
    if ERROR_PATTERN.search(msg):
        return f"{RED}{msg}{NC}"
    return msg


def print_summary(total, error_count):
    """打印统计摘要"""
    print(f"\n--- 共 {total} 条日志", end="")
    if error_count > 0:
        print(f",{RED}其中 {error_count} 条包含错误/失败关键词{NC}", end="")
    else:
        print(f",{GREEN}未发现错误/失败关键词{NC}", end="")
    print(" ---")


def main():
    if len(sys.argv) < 2:
        print("用法: python3 check_journal.py <服务名>")
        print("示例: python3 check_journal.py containerd")
        sys.exit(1)

    service_name = sys.argv[1]

    print(f"=== 检查服务: {GREEN}{service_name}{NC} ===\n")
    messages = get_journal_messages(service_name)

    if not messages:
        print(f"{YELLOW}未获取到任何日志消息{NC}")
        sys.exit(0)

    error_count = 0

    for i, msg in enumerate(messages, 1):
        is_error = bool(ERROR_PATTERN.search(msg))
        if is_error:
            error_count += 1
            print(f"[{i}] {RED}{msg}{NC}")
        else:
            print(f"[{i}] {msg}")

    print_summary(len(messages), error_count)


if __name__ == "__main__":
    main()

加强版 使用-e只展示报错信息

#!/usr/bin/env python3
"""
用法:
    python3 check_journal.py containerd              # 查看所有日志
    python3 check_journal.py kubelet --error-only    # 只看错误日志
    python3 check_journal.py docker -e               # 短参数
"""

import subprocess
import json
import sys
import re
import argparse


# 颜色定义
RED = '\033[0;31m'
YELLOW = '\033[1;33m'
GREEN = '\033[0;32m'
NC = '\033[0m'  # No Color

# 错误关键词模式(忽略大小写)
ERROR_PATTERN = re.compile(r'error|failed', re.IGNORECASE)


def get_journal_messages(service_name):
    """获取指定服务的 journal 日志,提取 MESSAGE 字段"""
    cmd = [
        "journalctl",
        "-xeu", service_name,
        "--no-pager",
        "-o", "json"
    ]

    try:
        result = subprocess.run(cmd, capture_output=True, text=True, check=False)
    except FileNotFoundError:
        print(f"{RED}错误: journalctl 命令不存在{NC}", file=sys.stderr)
        sys.exit(1)

    if result.returncode != 0:
        print(f"{YELLOW}警告: journalctl 退出码 {result.returncode}{NC}", file=sys.stderr)

    messages = []
    for line in result.stdout.strip().split("\n"):
        if not line:
            continue
        try:
            entry = json.loads(line)
            msg = entry.get("MESSAGE", "")
            if msg:
                messages.append(msg)
        except json.JSONDecodeError:
            continue

    return messages


def is_error_message(msg):
    """判断消息是否包含错误关键词"""
    return bool(ERROR_PATTERN.search(msg))


def print_summary(total, error_count, error_only=False):
    """打印统计摘要"""
    print(f"\n--- 共 {total} 条日志", end="")
    if error_only:
        print(f",{YELLOW}已过滤仅显示错误{NC}", end="")
    if error_count > 0:
        print(f",{RED}其中 {error_count} 条包含错误/失败关键词{NC}", end="")
    else:
        print(f",{GREEN}未发现错误/失败关键词{NC}", end="")
    print(" ---")


def main():
    parser = argparse.ArgumentParser(
        description="检查 systemd 服务 journal 日志并高亮错误"
    )
    parser.add_argument(
        "service",
        help="服务名称,如 containerd、kubelet、docker"
    )
    parser.add_argument(
        "-e", "--error-only",
        action="store_true",
        help="只显示包含 error/failed 的日志行"
    )
    args = parser.parse_args()

    print(f"=== 检查服务: {GREEN}{args.service}{NC} ===")
    if args.error_only:
        print(f"{YELLOW}(只显示错误/失败日志){NC}")
    print()

    messages = get_journal_messages(args.service)

    if not messages:
        print(f"{YELLOW}未获取到任何日志消息{NC}")
        sys.exit(0)

    error_count = 0
    printed_count = 0

    for i, msg in enumerate(messages, 1):
        is_error = is_error_message(msg)

        if is_error:
            error_count += 1

        # 根据 --error-only 决定是否打印
        if args.error_only and not is_error:
            continue  # 只显示错误模式,跳过正常日志

        printed_count += 1
        if is_error:
            print(f"[{i}] {RED}{msg}{NC}")
        else:
            print(f"[{i}] {msg}")

    print_summary(len(messages), error_count, args.error_only)

    # 返回值:有错误返回 1,无错误返回 0(方便脚本调用)
    if error_count > 0:
        sys.exit(1)


if __name__ == "__main__":
    main()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容