检查系统服务脚本
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()