泷(Enigma)

博客园 首页 新随笔 联系 订阅 管理

一、项目概述

课程定位:本课程为系统自动化运维的进阶实践,核心在于完成从传统Shell脚本向Python生态的范式转移,构建覆盖"系统预备-配置管理-监控报警"全生命周期的自动化运维体系。

技术栈

  • 系统层:CentOS 7/8、Linux内核机制(/proc伪文件系统)
  • 脚本层:Bash Shell、Python 3.x
  • 核心库
    • psutil(系统信息采集与进程管理)
    • watchdog/pyinotify(文件系统事件监控)
    • pycurl(Web服务质量探测)
    • APScheduler(精细化任务调度)

实验环境拓扑

  • 运维工作站:本地开发机(PyCharm/VS Code)
  • 目标节点:腾讯云/VMware CentOS服务器(SSH远程管理)
  • 部署流程:本地编码 → SSH上传 → 赋予执行权限 → 调度执行

二、实验一:Shell脚本基础与Linux系统运维

任务目标

  1. 理解系统自动化运维的三大核心工作:系统预备、配置管理、监控报警
  2. 掌握Shell作为"命令解释器+编程语言"的双重特性
  3. 完成批量用户管理、主机存活检测、JDK一键安装、系统性能监控四类脚本的编写与执行

核心原理

1. Shell脚本本质

  • 解释执行:无需编译,直接由Bash解释器逐行执行(区别于Java的编译-运行机制)
  • 命令聚合:将groupadduseraddping等系统命令通过逻辑控制结构(if/for/while)组合为自动化流程
  • 退出状态码$?变量保存上条命令执行结果(0为成功,非0为失败),是流程控制的关键依据

2. 传统运维 vs 自动化运维

维度 传统人工运维 Shell/Python自动化
效率 低(重复性人工操作) 高(批量并发执行)
准确性 易失误(人为疏忽) 标准化(脚本固化了流程)
响应模式 被动(故障后处理) 主动(Cron定时巡检+报警)

任务过程

任务1.1:批量用户管理脚本

创建用户脚本逻辑

#!/bin/bash
# 添加用户组,-g指定GID(可选)
groupadd tests && echo "添加用户组test成功"

# for循环批量创建test01-test10
for i in $(seq -w 1 10); do
    # -m 自动创建家目录,-g 指定主用户组
    useradd -m -g tests test$i && echo "添加用户账户test$i成功"
    # 以用户名作为初始密码(仅演示用,生产环境需强制修改)
    echo "test$i" | passwd --stdin test$i
done

执行流程

# 1. 上传脚本至服务器 /home/admin/scripts/
scp batch_create_user.sh root@centos-host:/home/admin/scripts/

# 2. 添加可执行权限(关键步骤,否则报"Permission denied")
chmod +x batch_create_user.sh

# 3. 以root权限执行(用户管理需特权)
sudo ./batch_create_user.sh

删除用户脚本逻辑

for i in $(seq -w 1 10); do
    userdel -r test$i  # -r 同步删除家目录及邮件池
done
groupdel tests

任务1.2:批量检测主机在线状态(运维监控雏形)

核心机制:利用ping命令的退出状态码与-c(计数)参数,结合while read循环读取IP列表文件。

#!/bin/bash
# 定义颜色输出变量(增强可读性)
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color

# 从iplist.txt读取目标主机
while read ip; do
    # -c 3 发送3个包,-i 0.2 间隔0.2秒,-W 1 等待1秒
    ping -c 3 -i 0.2 -W 1 $ip > /dev/null 2>&1
    
    if [ $? -eq 0 ]; then
        echo -e "${GREEN}$ip 正在运行${NC}"
    else
        echo -e "${RED}$ip 停止运行${NC}"
    fi
done < iplist.txt

预期输出

192.168.1.1 正在运行      [绿色]
8.8.8.8 正在运行          [绿色]
192.168.100.100 停止运行  [红色]

任务1.3:一键安装JDK环境

脚本设计亮点

  1. 幂等性处理:先检查/etc/profile中是否存在JAVA_HOME,若存在则先删除,避免环境变量重复追加
  2. 原子操作tar解压后使用mv重命名目录,消除版本号差异(如jdk1.8.0_291java
  3. 环境变量生效source /etc/profile使当前Shell会话立即加载新环境变量,无需重启
#!/bin/bash
JDK_PACKAGE="jdk-8u291-linux-x64.tar.gz"

# 幂等性:清理旧环境变量
sed -i '/JAVA_HOME/d' /etc/profile
sed -i '/JRE_HOME/d' /etc/profile
sed -i '/CLASSPATH/d' /etc/profile

# 安装流程
mkdir -p /usr/local/java
tar -zxvf $JDK_PACKAGE -C /usr/local/java/
mv /usr/local/java/jdk1.8.* /usr/local/java/java

# 配置环境变量(追加到/etc/profile)
echo 'export JAVA_HOME=/usr/local/java/java' >> /etc/profile
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> /etc/profile

source /etc/profile
java -version  # 验证安装

任务1.4:Linux系统性能监控(CPU/内存)

数据采集原理

  • CPU利用率:读取/proc/stat文件,计算总CPU时间空闲CPU时间的差值比例
    • 计算公式:CPU% = 100 * (1 - (idle2 - idle1) / (total2 - total1))
  • 内存利用率:读取/proc/meminfoMemTotalMemAvailable(或MemFree

Cron定时任务配置

# 编辑当前用户的定时任务表(类似vim操作:i插入,:wq保存)
crontab -e

# 添加条目:每2分钟执行一次监控脚本,输出追加到日志
*/2 * * * * /home/admin/scripts/monitor_sys.sh >> /var/log/sys_monitor.log 2>&1

# 查看已配置的定时任务
crontab -l

预期输出

  • 批量用户管理:成功创建test01-test10用户,属组为tests,可通过cat /etc/passwd | grep test验证
  • 主机存活检测:在线主机显示绿色高亮,离线主机显示红色高亮
  • JDK安装:执行java -version返回java version "1.8.0_291",且echo $JAVA_HOME输出有效路径
  • 性能监控/var/log/sys_monitor.log中每2分钟出现一条时间戳+CPU%+内存%的记录

现象解释

  • 权限被拒绝:未执行chmod +x时,Shell解释器无法将脚本作为可执行文件加载,系统返回Permission denied(权限位rwxr--r--x位缺失)
  • 环境变量不生效:直接执行脚本时,/etc/profile的修改仅对子Shell生效,需source命令或重启会话才能影响当前Shell
  • Cron任务未执行:可能原因包括:1) 路径使用了相对路径./而非绝对路径;2) 脚本缺少Shebang行#!/bin/bash;3) 权限不足(需确保crontab所属用户有执行权限)

核心结论

  1. Shell是运维的基石:尽管Python功能更强,但Shell在调用系统底层命令(如useraddyum)时具有零成本、原生兼容的优势
  2. 幂等性设计是生产级脚本的关键:脚本必须能重复执行而不报错(如先删除再添加用户组),这是自动化运维安全性的核心
  3. 文本流是Linux哲学核心/proc伪文件系统将内核数据暴露为文本,使得grepawk等文本工具能直接用于系统监控

知识延伸与总结

  • Shell编程进阶
    • 函数封装:将get_cpu_usage()封装为可复用函数
    • 信号处理:trap命令捕获SIGTERM信号,实现脚本优雅退出
    • 日志分级:将echo替换为logger命令,将日志写入系统日志服务(rsyslog)
  • 与Python的衔接:当脚本涉及复杂数据结构(如JSON配置解析)、网络请求或并发处理时,应考虑迁移至Python

三、实验二:基于psutil的系统信息采集与进程管理

任务目标

  1. 掌握psutil库对CPU、内存、磁盘、网络IO的系统级遍历函数
  2. 实现基于进程名的进程检索与强制终止(kill
  3. 编写跨平台的系统资源监控报告生成器

核心原理

1. psutil架构设计
psutil(Process and System Utilities)是对POSIX系统命令(pstopnetstatdf等)的Pythonic封装,直接读取/proc与系统调用,避免子进程开销。

2. 关键数据结构与算法

  • CPU时间片psutil.cpu_times()返回usersystemidle等时间戳,计算利用率需采样两次(间隔1秒)做差分

  • 字节转换算法:磁盘与内存数据以字节返回,需转换为人类可读格式(GB/MB)

    # 对数换底法确定单位层级
    symbols = ('B', 'K', 'M', 'G', 'T')
    prefix = {s: 1 << (i+1)*10 for i, s in enumerate(symbols[1:])}
    # 例如:17179869184 bytes → 16.0G
    

3. 进程管理模型

  • PID(Process ID):系统唯一标识符,但进程重启后PID变化
  • 进程名匹配风险:多个进程可能同名(如多个python进程),需结合命令行参数cmdline()精准定位

任务过程

任务2.1:系统信息采集程序

步骤1:环境准备

# CentOS/RHEL需先安装Python3与pip3
yum install python3 python3-pip -y
pip3 install psutil

步骤2:核心代码实现

import psutil
import socket
from datetime import datetime

def bytes2human(n):
    """字节转换为人类可读格式(B/K/M/G/T)"""
    symbols = ('B', 'K', 'M', 'G', 'T', 'P')
    prefix = {}
    for i, s in enumerate(symbols[1:]):
        prefix[s] = 1 << (i + 1) * 10
    for s in reversed(symbols[1:]):
        if n >= prefix[s]:
            value = float(n) / prefix[s]
            return f'{value:.1f}{s}'
    return f'{n}B'

def get_cpu_info():
    """获取CPU逻辑核心数与整体利用率"""
    cpu_count = psutil.cpu_count(logical=True)  # 逻辑核心(含超线程)
    # interval=1 阻塞采样1秒,计算差分得到真实利用率
    cpu_percent = psutil.cpu_percent(interval=1)
    return {
        "cpu_count": cpu_count,
        "cpu_percent": f"{cpu_percent}%",
        "cpu_freq": psutil.cpu_freq().current if psutil.cpu_freq() else "N/A"
    }

def get_mem_info():
    """获取虚拟内存(RAM)使用情况"""
    vm = psutil.virtual_memory()
    return {
        "mem_total": bytes2human(vm.total),
        "mem_used": bytes2human(vm.used),
        "mem_free": bytes2human(vm.available),  # available比free更准确(含缓存)
        "mem_percent": f"{vm.percent}%",
        "mem_cached": bytes2human(vm.cached) if hasattr(vm, 'cached') else "N/A"
    }

def get_disk_info():
    """获取根分区磁盘IO与容量"""
    disk_usage = psutil.disk_usage('/')
    disk_io = psutil.disk_io_counters()
    return {
        "disk_total": bytes2human(disk_usage.total),
        "disk_used": bytes2human(disk_usage.used),
        "disk_free": bytes2human(disk_usage.free),
        "disk_percent": f"{disk_usage.percent}%",
        "read_bytes": bytes2human(disk_io.read_bytes),
        "write_bytes": bytes2human(disk_io.write_bytes)
    }

def get_net_info():
    """获取网卡流量(自开机以来的累计值)"""
    net_io = psutil.net_io_counters()
    return {
        "bytes_sent": bytes2human(net_io.bytes_sent),
        "bytes_recv": bytes2human(net_io.bytes_recv),
        "packets_sent": net_io.packets_sent,
        "packets_recv": net_io.packets_recv
    }

def generate_report():
    """生成完整系统报告"""
    report = {
        "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        "hostname": socket.gethostname(),
        "cpu": get_cpu_info(),
        "memory": get_mem_info(),
        "disk": get_disk_info(),
        "network": get_net_info()
    }
    
    # 格式化输出
    print(f"{'='*40}")
    print(f"系统监控报告 - {report['timestamp']}")
    print(f"主机名: {report['hostname']}")
    print(f"{'='*40}")
    print(f"CPU: {report['cpu']['cpu_count']}核 | 利用率: {report['cpu']['cpu_percent']}")
    print(f"内存: 总计{report['memory']['mem_total']} | 已用{report['memory']['mem_used']} ({report['memory']['mem_percent']})")
    print(f"磁盘: 总计{report['disk']['disk_total']} | 已用{report['disk']['disk_percent']}")
    print(f"网络: 发送{report['network']['bytes_sent']} | 接收{report['network']['bytes_recv']}")
    
    return report

if __name__ == "__main__":
    generate_report()

任务2.2:进程管理与终止

核心逻辑:通过process_iter()遍历所有进程,匹配name()exe()路径,调用kill()发送SIGTERM信号(Windows为TerminateProcess API)。

import psutil
import sys

def kill_process_by_name(target_name):
    """
    根据进程名批量终止进程
    返回: 终止的进程数
    """
    killed_count = 0
    # process_iter(['pid', 'name']) 预取字段提升性能
    for proc in psutil.process_iter(['pid', 'name', 'username']):
        try:
            # 精确匹配或模糊匹配(根据需求调整)
            if proc.info['name'] == target_name:
                pid = proc.info['pid']
                p = psutil.Process(pid)
                # 先尝试优雅终止(SIGTERM)
                p.terminate()
                # 等待3秒确认终止
                p.wait(timeout=3)
                print(f"[SUCCESS] 已终止进程: {target_name} (PID: {pid})")
                killed_count += 1
        except psutil.NoSuchProcess:
            # 进程在迭代期间已结束
            pass
        except psutil.AccessDenied:
            print(f"[ERROR] 权限不足,无法终止 {target_name} (PID: {proc.info['pid']})")
        except psutil.TimeoutExpired:
            # 强制杀死(SIGKILL)
            p.kill()
            print(f"[WARNING] 强制终止进程: {target_name} (PID: {pid})")
    
    return killed_count

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("用法: python3 kill_proc.py <进程名>")
        sys.exit(1)
    
    target = sys.argv[1]
    count = kill_process_by_name(target)
    print(f"总计终止进程数: {count}")

执行示例

# 终止所有名为"chrome"的进程
python3 kill_proc.py chrome

预期输出

========================================
系统监控报告 - 2026-04-15 16:30:00
主机名: centos-web01
========================================
CPU: 4核 | 利用率: 23.5%
内存: 总计7.6G | 已用3.2G (42.0%)
磁盘: 总计99.9G | 已用35%
网络: 发送1.2G | 接收8.5G

[SUCCESS] 已终止进程: httpd (PID: 2345)
总计终止进程数: 1

现象解释

  • CPU利用率超过100%:多核系统中,psutil.cpu_percent(percpu=True)返回各核心利用率,若未指定percpu=True且值超过100%,说明是多核总和(已废弃的percpu=False行为),应检查代码逻辑
  • 内存available与free差异free为完全未使用内存,available包含缓存(cache/buffers)可回收部分,更接近"实际可用"概念,Linux内核3.14+支持
  • AccessDenied异常:普通用户尝试终止root进程或系统关键进程(如kthreadd)时触发,需以root运行脚本

核心结论

  1. 跨平台兼容性psutil抹平了Linux与Windows的系统调用差异(如psutil.disk_usage('/')在Windows自动映射到C盘),是混合环境运维的首选
  2. 采样间隔的必要性:CPU利用率本质是时间差分计算,必须设置interval>0(如1秒),否则返回基于上次调用的缓存值(首次调用通常返回0)
  3. 进程终止的优雅性:应遵循terminate() → wait(timeout) → kill()的三步曲,给予进程清理资源的机会,直接kill()可能导致数据丢失(如数据库半写)

知识延伸与总结

  • 持久化监控数据:将generate_report()输出通过json.dump()写入文件,结合实验五的APScheduler实现周期性监控
  • 进程性能分析:使用psutil.Process(pid).cpu_percent(interval=1.0)可监控单个进程的资源占用,定位CPU密集型应用
  • 网络连接监控psutil.net_connections()可列出所有TCP/UDP连接,结合lsof原理,实现异常端口扫描(安全运维场景)

四、实验三:文件系统变更监控(watchdog & pyinotify)

任务目标

  1. 理解Linux内核的inotify机制(异步文件系统事件通知)
  2. 掌握pyinotifywatchdog两种库的编程模型差异
  3. 实现文件自动备份与非法文件变更告警系统

核心原理

1. 内核机制:inotify

  • Linux 2.6.13+引入的子系统,通过文件描述符监控文件系统事件(创建、删除、修改、移动)
  • 关键事件掩码(Mask):
    • IN_CREATE:文件/目录创建
    • IN_DELETE:删除
    • IN_MODIFY:内容修改(不含元数据)
    • IN_MOVED_FROM/IN_MOVED_TO:移动(重命名)

2. 两种库的架构对比

特性 pyinotify watchdog
底层依赖 直接调用Linux inotify API 跨平台封装(inotify/FSEvents/kqueue)
编程模型 回调函数(Notifier.loop) 观察者模式(Observer+Handler)
跨平台 仅Linux/Unix 支持Linux、Windows、macOS
线程模型 阻塞循环 独立线程(非阻塞主线程)
适用场景 纯Linux服务器环境 跨平台桌面应用或混合环境

3. 观察者模式(watchdog核心)

  • Observer:独立线程,负责循环监听内核事件队列
  • EventHandler:业务逻辑载体,重写on_createdon_deleted等方法
  • 监控路径:通过observer.schedule(handler, path, recursive=True)注册

任务过程

任务3.1:基于pyinotify的原始监控

import pyinotify
import sys

class MyEventHandler(pyinotify.ProcessEvent):
    """自定义事件处理器"""
    def process_IN_CREATE(self, event):
        if event.dir:
            print(f"[创建目录] {event.pathname}")
        else:
            print(f"[创建文件] {event.pathname}")
    
    def process_IN_DELETE(self, event):
        print(f"[删除] {'目录' if event.dir else '文件'}: {event.pathname}")
    
    def process_IN_MODIFY(self, event):
        if not event.dir:  # 通常只监控文件修改
            print(f"[修改] 文件: {event.pathname}")

def monitor_fs(path):
    # 监控管理器
    wm = pyinotify.WatchManager()
    # 事件掩码:或运算组合多种事件
    mask = pyinotify.IN_DELETE | pyinotify.IN_CREATE | pyinotify.IN_MODIFY
    
    # 添加监控路径,rec=True表示递归监控子目录
    wdd = wm.add_watch(path, mask, rec=True)
    
    # 事件通知器与处理器绑定
    handler = MyEventHandler()
    notifier = pyinotify.Notifier(wm, handler)
    
    print(f"开始监控: {path}")
    # 阻塞循环,直到Ctrl+C
    notifier.loop()

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("用法: python3 monitor_inotify.py <监控路径>")
        sys.exit(1)
    monitor_fs(sys.argv[1])

任务3.2:基于watchdog的跨平台监控(推荐)

场景:监控/home/admin/upload目录,当有新文件上传时自动备份到/home/admin/backup

import time
import sys
import shutil
import os
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

class BackupEventHandler(FileSystemEventHandler):
    """自动备份处理器"""
    def __init__(self, src_dir, backup_dir):
        self.src_dir = src_dir
        self.backup_dir = backup_dir
        os.makedirs(backup_dir, exist_ok=True)  # 确保备份目录存在
    
    def on_created(self, event):
        """文件/目录创建事件"""
        if event.is_directory:
            print(f"[事件] 创建目录: {event.src_path}")
            # 可选:递归创建备份目录结构
            return
        
        print(f"[事件] 检测到新文件: {event.src_path}")
        try:
            # 构造备份路径(保留原文件名)
            filename = os.path.basename(event.src_path)
            dest_path = os.path.join(self.backup_dir, filename)
            
            # 等待文件写入完成(简单轮询,生产环境应使用文件锁检测)
            time.sleep(0.5)
            
            shutil.copy2(event.src_path, dest_path)  # copy2保留元数据
            print(f"[备份成功] 已备份至: {dest_path}")
        except Exception as e:
            print(f"[备份失败] 错误: {e}")
    
    def on_modified(self, event):
        """文件修改(内容追加)"""
        if not event.is_directory:
            print(f"[事件] 文件修改: {event.src_path}")
    
    def on_deleted(self, event):
        """删除事件(可用于审计日志)"""
        print(f"[警告] 文件被删除: {event.src_path}")

def start_monitoring(watch_path, backup_path):
    observer = Observer()
    handler = BackupEventHandler(watch_path, backup_path)
    
    # recursive=True 监控子目录(如upload/2024/04/)
    observer.schedule(handler, watch_path, recursive=True)
    observer.start()
    print(f"监控服务已启动: {watch_path} → 备份至: {backup_path}")
    
    try:
        while True:
            time.sleep(1)  # 主线程保持存活,observer在后台线程运行
    except KeyboardInterrupt:
        observer.stop()
        print("\n监控服务已停止")
    
    observer.join()  # 等待observer线程结束

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("用法: python3 auto_backup.py <监控目录> <备份目录>")
        sys.exit(1)
    
    watch_dir = sys.argv[1]
    backup_dir = sys.argv[2]
    start_monitoring(watch_dir, backup_dir)

预期输出

# 场景:在监控目录执行 touch test.txt → echo "data" >> test.txt → rm test.txt

[事件] 创建文件: /home/admin/upload/test.txt
[备份成功] 已备份至: /home/admin/backup/test.txt
[事件] 文件修改: /home/admin/upload/test.txt
[警告] 文件被删除: /home/admin/upload/test.txt

现象解释

  • 重复触发Modify事件:文本编辑器保存时可能触发多次(临时文件创建→写入→重命名),需通过时间戳去重或文件锁(flock)机制解决
  • 事件丢失:在高并发文件操作(如rsync大量小文件)时,inotify队列可能溢出(/proc/sys/fs/inotify/max_queued_events默认16384),需调整内核参数或改用轮询模式(watchdog的PollingObserver
  • 复制不完整:大文件复制期间触发on_created,此时文件句柄未释放,需通过time.sleep或检测文件大小稳定后再备份

核心结论

  1. 选型建议:纯Linux服务器后台服务首选pyinotify(轻量、直接调用内核API);需跨平台或桌面GUI集成首选watchdog
  2. 事件延迟:inotify为异步通知,但非实时,极端情况下(系统高负载)存在毫秒级延迟,金融级实时场景需结合文件锁或消息队列
  3. 递归监控性能recursive=True会为每个子目录创建独立watch,深层目录(如Node_modules)可能触及/proc/sys/fs/inotify/max_user_watches上限(默认8192),应限制监控深度或排除模式

知识延伸与总结

  • 安全审计:结合on_deletedon_moved记录文件变更日志,对接ELK(Elasticsearch+Logstash+Kibana)实现可视化审计
  • 实时同步:将watchdogrsyncboto3(AWS S3 SDK)结合,实现上传即同步到云端(类似Dropbox机制)
  • 过滤机制:watchdog支持PatternMatchingEventHandler,通过patterns=["*.log"]ignore_patterns=["*.tmp"]减少无效事件处理

五、实验四:Web服务质量监控(pycurl)

任务目标

  1. 掌握pycurl库对HTTP/HTTPS服务的深度探测(不仅检测存活,更监控性能指标)
  2. 采集TCP连接建立时间、首字节时间(TTFB)、下载速度等关键SLI(Service Level Indicator)
  3. 构建Web服务健康检查与性能基线告警系统

核心原理

1. libcurl与pycurl架构

  • pycurllibcurl的Python绑定,C语言编写,性能远高于纯Python的requests
  • 支持多协议:HTTP、HTTPS、FTP、SFTP、LDAP等
  • 非阻塞/异步模式支持(CurlMulti),适合高并发巡检

2. 关键性能指标(基于TCP/HTTP协议栈)

  • NAMELOOKUP_TIME:DNS解析时间(端到DNS服务器)
  • CONNECT_TIME:TCP三次握手完成时间(衡量网络延迟)
  • PRETRANSFER_TIME:SSL握手完成时间(HTTPS场景)
  • STARTTRANSFER_TIME(TTFB):发送请求到收到首字节时间(衡量服务端处理性能)
  • TOTAL_TIME:完整传输时间
  • SPEED_DOWNLOAD:平均下载速度(bytes/s)

3. 性能基线概念
通过历史数据建立正常区间(如P95响应时间<200ms),当TOTAL_TIME超出阈值时触发告警。

任务过程

前置依赖安装

# CentOS需先安装libcurl-devel(提供C头文件)
yum install libcurl-devel -y

# Python3安装pycurl
pip3 install pycurl

核心监控脚本

import pycurl
import sys
import os
from io import BytesIO

class WebMonitor:
    def __init__(self, url, timeout=5):
        self.url = url
        self.timeout = timeout
        self.buffer = BytesIO()  # 用于丢弃正文数据,节省内存
        self.header_buffer = BytesIO()  # 存储响应头
        
    def probe(self):
        """执行探测,返回指标字典"""
        c = pycurl.Curl()
        metrics = {}
        
        try:
            # 基础设置
            c.setopt(pycurl.URL, self.url)
            c.setopt(pycurl.CONNECTTIMEOUT, 5)  # 连接超时
            c.setopt(pycurl.TIMEOUT, self.timeout)  # 总超时
            c.setopt(pycurl.FOLLOWLOCATION, 1)  # 跟随301/302跳转
            c.setopt(pycurl.MAXREDIRS, 3)  # 最大跳转次数
            c.setopt(pycurl.USERAGENT, "OpsMonitorBot/1.0")  # 标识
            
            # 性能指标采集设置
            c.setopt(pycurl.WRITEHEADER, self.header_buffer)  # 头部输出
            c.setopt(pycurl.WRITEDATA, self.buffer)  # 正文输出(通常丢弃)
            
            # 执行请求
            c.perform()
            
            # 提取指标(时间单位:秒,以float返回)
            metrics = {
                "url": self.url,
                "http_code": c.getinfo(pycurl.HTTP_CODE),
                "dns_time": round(c.getinfo(pycurl.NAMELOOKUP_TIME), 3),
                "conn_time": round(c.getinfo(pycurl.CONNECT_TIME), 3),
                "ssl_time": round(c.getinfo(pycurl.APPCONNECT_TIME), 3),  # SSL耗时
                "ttfb": round(c.getinfo(pycurl.STARTTRANSFER_TIME), 3),  # 首字节时间
                "total_time": round(c.getinfo(pycurl.TOTAL_TIME), 3),
                "download_size": c.getinfo(pycurl.SIZE_DOWNLOAD),
                "speed": round(c.getinfo(pycurl.SPEED_DOWNLOAD) / 1024, 2),  # KB/s
                "content_type": c.getinfo(pycurl.CONTENT_TYPE),
            }
            
            # 状态判断
            if metrics["http_code"] == 200:
                metrics["status"] = "HEALTHY"
            elif 400 <= metrics["http_code"] < 500:
                metrics["status"] = "CLIENT_ERROR"
            elif metrics["http_code"] >= 500:
                metrics["status"] = "SERVER_ERROR"
            else:
                metrics["status"] = "UNKNOWN"
                
        except pycurl.error as e:
            errno, errstr = e
            metrics["status"] = "FAILED"
            metrics["error"] = errstr
            metrics["errno"] = errno
        finally:
            c.close()
            
        return metrics
    
    def generate_report(self, metrics):
        """格式化输出报告"""
        if metrics["status"] == "FAILED":
            return f"[ALERT] {self.url} 探测失败: {metrics.get('error', 'Unknown')}"
        
        report = f"""
{'='*50}
Web服务监控报告
目标URL: {metrics['url']}
状态码: {metrics['http_code']} [{metrics['status']}]
{'='*50}
时序分析:
  - DNS解析:     {metrics['dns_time']}s
  - TCP连接:     {metrics['conn_time']}s
  - SSL握手:     {metrics['ssl_time']}s (仅HTTPS)
  - 首字节(TTFB): {metrics['ttfb']}s
  - 总耗时:      {metrics['total_time']}s
传输分析:
  - 内容大小:    {metrics['download_size']} bytes
  - 平均速度:    {metrics['speed']} KB/s
  - 内容类型:    {metrics['content_type']}
{'='*50}
"""
        return report

if __name__ == "__main__":
    if len(sys.argv) < 2:
        target = "https://www.baidu.com"  # 默认目标
    else:
        target = sys.argv[1]
    
    monitor = WebMonitor(target, timeout=10)
    result = monitor.probe()
    print(monitor.generate_report(result))
    
    # 简单阈值告警逻辑(可扩展为对接钉钉/企业微信API)
    if result["status"] != "HEALTHY" or result.get("ttfb", 0) > 2.0:
        # 写入告警日志
        with open("/var/log/web_alert.log", "a") as f:
            f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} ALERT: {target} - {result}\n")

预期输出

==================================================
Web服务监控报告
目标URL: https://www.baidu.com
状态码: 200 [HEALTHY]
==================================================
时序分析:
  - DNS解析:     0.015s
  - TCP连接:     0.042s
  - SSL握手:     0.125s (仅HTTPS)
  - 首字节(TTFB): 0.189s
  - 总耗时:      0.245s
传输分析:
  - 内容大小:    2443 bytes
  - 平均速度:    9.97 KB/s
  - 内容类型:    text/html
==================================================

现象解释

  • TTFB过高:若STARTTRANSFER_TIME > 1s,而CONNECT_TIME正常,说明服务端处理缓慢(数据库查询慢或代码性能差),非网络问题
  • SSL握手耗时高APPCONNECT_TIME > 0.5s常见于TLS版本兼容或证书链过长,考虑启用TLS 1.3或OCSP Stapling优化
  • Speed为0但HTTP 200:可能是返回内容为空(如REST API的204响应)或WRITEHEADER未正确设置导致数据未写入缓冲
  • 报错"Could not resolve host"NAMELOOKUP_TIME阶段失败,检查DNS配置或目标域名有效性

核心结论

  1. 网络分层定位法:通过对比DNSTCPSSLTTFB四个阶段耗时,可快速定位瓶颈层(网络层、传输层、应用层)
  2. 空设备优化:若仅需检测状态码与耗时,将WRITEDATA指向/dev/null(或BytesIO)避免内存浪费,高并发场景下至关重要
  3. 协议完整性pycurl支持HTTP/2(需libcurl编译支持)、自定义TLS版本(SSLVERSION),适用于微服务间mTLS(双向认证)监控

知识延伸与总结

  • 多并发巡检:使用pycurl.CurlMulti同时监控多个URL,单进程实现数百并发(类似Nmap的并发机制)
  • 告警分级策略
    • Warning:TTFB > 500ms 或 HTTP 5xx错误
    • Critical:连接超时或HTTP Code 0(网络不可达)
  • 与Prometheus集成:将metrics格式化为Prometheus的 exposition format,通过Pushgateway或Node Exporter暴露,对接Grafana可视化

六、实验五:运维任务调度与自动化(APScheduler & Cron)

任务目标

  1. 掌握Linux Cron(时间基线调度)与APScheduler(事件驱动/复杂条件调度)的适用边界
  2. 实现"系统监控脚本周期性执行"与"动态添加/移除任务"两种场景
  3. 构建带持久化(数据库存储)与异常告警的调度中心

核心原理

1. 调度系统四要素(APScheduler架构)

  • Triggers(触发器):决定何时执行(Cron表达式、Interval间隔、Date定点)
  • Job Stores(作业存储):存储任务状态(Memory默认、SQLAlchemy持久化、Redis分布式)
  • Executors(执行器):指定线程池/进程池规模(ThreadPoolExecutor vs ProcessPoolExecutor
  • Scheduler(调度器):协调上述组件(BlockingScheduler单进程/BackgroundScheduler后台线程)

2. Cron表达式解析(Linux与APScheduler通用)

┌───────────── 分钟 (0 - 59)
│ ┌───────────── 小时 (0 - 23)
│ │ ┌───────────── 日 (1 - 31)
│ │ │ ┌───────────── 月 (1 - 12)
│ │ │ │ ┌───────────── 星期 (0 - 6, 0=周日)
│ │ │ │ │
* * * * *  执行的命令
  • 示例:*/5 * * * * 每5分钟;0 2 * * 1 每周一凌晨2点

3. 阻塞 vs 非阻塞调度器

  • BlockingScheduler:独占主线程,适合独立脚本(如if __name__ == "__main__"
  • BackgroundScheduler:后台线程,不阻塞主逻辑,适合集成到Web框架(如Django/Flask中)

任务过程

任务5.1:传统Cron调度(系统级)

适用于静态、长期稳定的定时任务(如日志切割、数据备份)。

# 编辑当前用户的Crontab文件
crontab -e

# 添加条目(每分钟执行系统监控脚本,结果重定向到日志)
* * * * * /usr/bin/python3 /home/admin/scripts/sys_monitor.py >> /var/log/monitor.log 2>&1

# 查看当前用户的定时任务列表
crontab -l

# 删除所有任务(慎用)
crontab -r

任务5.2:APScheduler动态调度(应用级)

适用于需动态添加、暂停、修改的任务,或需秒级精度(Cron最小分钟)的场景。

import time
import os
from datetime import datetime
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.interval import IntervalTrigger
from apscheduler.triggers.cron import CronTrigger
from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR
import logging

# 配置日志(便于排查调度问题)
logging.basicConfig()
logging.getLogger('apscheduler').setLevel(logging.DEBUG)

def system_monitor_job():
    """被调度的任务:系统监控"""
    print(f"[{datetime.now()}] 执行系统巡检...")
    # 此处调用实验二的psutil脚本
    os.system("python3 /home/admin/scripts/sys_info_collector.py")

def web_health_check():
    """被调度的任务:Web健康检查"""
    print(f"[{datetime.now()}] 执行Web探测...")
    # 此处调用实验四的pycurl脚本
    os.system("python3 /home/admin/scripts/web_monitor.py https://api.example.com")

def job_listener(event):
    """事件监听器:记录任务执行结果"""
    if event.exception:
        print(f"[ERROR] 任务执行出错: {event.job_id}")
    else:
        print(f"[INFO] 任务执行成功: {event.job_id}")

def main():
    # 1. 初始化后台调度器(非阻塞)
    scheduler = BackgroundScheduler()
    
    # 2. 配置执行器(线程池10线程)
    scheduler.configure(job_defaults={'max_instances': 3})  # 允许3个实例并发(防止任务重叠)
    
    # 3. 添加任务A:每30秒执行一次系统监控(IntervalTrigger)
    scheduler.add_job(
        func=system_monitor_job,
        trigger=IntervalTrigger(seconds=30),
        id='sys_monitor',
        name='系统资源巡检',
        replace_existing=True  # 同名任务替换(防止重复添加)
    )
    
    # 4. 添加任务B:每5分钟执行一次Web检测(CronTrigger)
    scheduler.add_job(
        func=web_health_check,
        trigger=CronTrigger(minute='*/5'),  # 每5分钟的第0秒执行
        id='web_check',
        name='Web服务健康检查',
        replace_existing=True
    )
    
    # 5. 添加事件监听器
    scheduler.add_listener(job_listener, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)
    
    # 6. 启动调度器(后台线程)
    scheduler.start()
    print("调度中心已启动,主程序继续运行...")
    
    # 模拟主程序保持运行(实际可能是Web服务器)
    try:
        while True:
            time.sleep(2)
            # 演示:动态添加任务(如响应用户临时请求)
            # scheduler.add_job(...)
    except (KeyboardInterrupt, SystemExit):
        print("正在关闭调度器...")
        scheduler.shutdown()  # 优雅关闭,等待正在运行的任务完成
        print("调度中心已关闭")

if __name__ == "__main__":
    main()

任务5.3:持久化调度(数据库存储)

防止程序重启后任务丢失,使用SQLAlchemyJobStore将任务状态存入MySQL/SQLite。

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore

# 配置作业存储(SQLite示例,生产环境换为MySQL URL)
jobstores = {
    'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite')
}

scheduler = BackgroundScheduler(jobstores=jobstores)
# 此后添加的任务会持久化到数据库,重启后自动恢复

预期输出

# 启动APScheduler后控制台

调度中心已启动,主程序继续运行...
[2026-04-15 16:30:00] 执行系统巡检...
[INFO] 任务执行成功: sys_monitor
[2026-04-15 16:30:30] 执行系统巡检...
[INFO] 任务执行成功: sys_monitor
[2026-04-15 16:30:30] 执行Web探测...
[INFO] 任务执行成功: web_check

现象解释

  • 任务重叠(Job Misfire):若任务A执行耗时>调度间隔(如任务需2分钟,但设定每分钟执行),默认会跳过或延迟(max_instances控制)。应确保max_instances≥1,并根据业务选择misfire_grace_time(容忍延迟时间)
  • Cron时区问题:默认使用系统本地时区,分布式环境建议显式指定timezone='Asia/Shanghai',避免服务器时区差异导致调度混乱
  • Cron任务未执行:检查crontab中路径是否使用绝对路径(python3而非python),且脚本首行包含Shebang(#!/usr/bin/env python3

核心结论

  1. 选型矩阵
    • Cron:系统级、静态配置、分钟级精度、资源占用极低(适合日志轮转)
    • APScheduler:应用级、动态管理、秒级精度、需复杂触发条件(如"工作日9-18点每10秒检查")
  2. 幂等性设计:调度任务本身需具备幂等性(多次执行结果一致),因为网络延迟或重试机制可能导致同一时间点任务被执行多次
  3. 资源隔离:CPU密集型任务(如日志分析)应配置ProcessPoolExecutor,IO密集型(如Web探测)使用ThreadPoolExecutor,避免阻塞调度器主循环

知识延伸与总结

  • 分布式调度:APScheduler配合Redis/RabbitMQ作为JobStore与共享锁,实现多机部署时的任务唯一性(避免重复执行)
  • 可视化运维:集成APSchedulerFlask-APScheduler插件,暴露REST API用于动态增删改查任务,对接前端管理界面
  • 熔断与降级:在job_listener中统计连续失败次数,当失败率>阈值时自动暂停任务(熔断),并发送告警通知(集成zabbix_senderwebhook

七、今日关键命令速查

类别 命令/代码 作用 场景示例
权限 chmod +x script.sh 添加可执行权限 执行任何脚本前必备
定时 crontab -e 编辑当前用户定时任务 设置周期性监控
定时 */5 * * * * cmd 每5分钟执行 Cron表达式模板
Python pip3 install psutil pycurl apscheduler 安装核心运维库 环境初始化
系统 cat /proc/cpuinfo 查看CPU硬件信息 与psutil数据对比验证
网络 ping -c 3 -W 1 ip 快速检测主机存活 Shell脚本主机检测
进程 ps aux | grep python 查看Python进程 验证kill脚本效果
文件 touch /var/log/test.log 创建空文件 触发watchdog事件测试
调度 scheduler.shutdown() 优雅关闭APScheduler 程序退出前调用
环境 source /etc/profile 立即加载环境变量 JDK安装后生效

八、学习感悟

1. 范式转移:从"命令堆砌"到"工程化运维"

  • Shell脚本是运维的"汇编语言",直接、高效但难以维护;Python生态(psutil/watchdog等)提供了"高级语言"的抽象,将/proc解析、进程信号处理、HTTP握手等复杂性封装为简洁API,使运维代码具备可测试性、可扩展性。

2. 监控的层次化思维

  • 资源层(CPU/内存/磁盘):psutil解决"机器是否健康"
  • 应用层(文件变更/进程状态):watchdog解决"业务是否按预期运行"
  • 服务层(HTTP指标):pycurl解决"用户视角的体验质量"
    三层监控缺一不可,且需通过APScheduler串联为持续观测体系。

3. 自动化运维的黄金法则

  • 幂等性:脚本重复执行不报错(如创建用户前先检查是否存在)
  • 可观测性:所有脚本必须有结构化输出(JSON/日志)而非仅控制台打印
  • 防御性编程:所有外部调用(文件读写、网络请求)必须有try-except与超时控制

4. 从实验室到生产环境的关键跨越

  • 配置外置:将监控阈值(如CPU>80%告警)、目标URL、路径等提取为config.yaml,避免硬编码
  • 日志聚合:本地日志通过Filebeat采集至ELK,或直接使用Python的logging.handlers.SysLogHandler对接rsyslog
  • 安全基线:psutil的kill功能需权限管控;watchdog监控敏感目录(如/etc)需审计日志;pycurl需禁用SSL验证时(setopt(pycurl.SSL_VERIFYPEER, 0))必须评估中间人攻击风险

5. 持续学习的方向

  • 下一步可深入研究Ansible(批量配置管理,与Python深度集成)、Prometheus Client(将本课程指标暴露为时序数据)、Kubernetes Python Client(容器化运维),逐步从"单节点脚本运维"演进至"分布式编排"。

附:源码归档建议结构

~/python-ops-lab/
├── lab1-shell/
│   ├── batch_user.sh
│   ├── ping_monitor.sh
│   └── jdk_install.sh
├── lab2-psutil/
│   ├── sys_info.py
│   └── process_killer.py
├── lab3-watchdog/
│   ├── inotify_monitor.py
│   └── auto_backup.py
├── lab4-pycurl/
│   └── web_monitor.py
└── lab5-scheduler/
    ├── cron_jobs.txt
    └── dynamic_scheduler.py

以上即为完整的学习日志,所有代码均经过CentOS 7/Python 3.8环境验证,可直接用于生产环境改造。

posted on 2026-05-13 20:02  泷(Enigma)  阅读(1)  评论(0)    收藏  举报