一、项目概述
课程定位:本课程为系统自动化运维的进阶实践,核心在于完成从传统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系统运维
任务目标
- 理解系统自动化运维的三大核心工作:系统预备、配置管理、监控报警
- 掌握Shell作为"命令解释器+编程语言"的双重特性
- 完成批量用户管理、主机存活检测、JDK一键安装、系统性能监控四类脚本的编写与执行
核心原理
1. Shell脚本本质
- 解释执行:无需编译,直接由Bash解释器逐行执行(区别于Java的编译-运行机制)
- 命令聚合:将
groupadd、useradd、ping等系统命令通过逻辑控制结构(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环境
脚本设计亮点:
- 幂等性处理:先检查
/etc/profile中是否存在JAVA_HOME,若存在则先删除,避免环境变量重复追加 - 原子操作:
tar解压后使用mv重命名目录,消除版本号差异(如jdk1.8.0_291→java) - 环境变量生效:
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/meminfo的MemTotal与MemAvailable(或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所属用户有执行权限)
核心结论
- Shell是运维的基石:尽管Python功能更强,但Shell在调用系统底层命令(如
useradd、yum)时具有零成本、原生兼容的优势 - 幂等性设计是生产级脚本的关键:脚本必须能重复执行而不报错(如先删除再添加用户组),这是自动化运维安全性的核心
- 文本流是Linux哲学核心:
/proc伪文件系统将内核数据暴露为文本,使得grep、awk等文本工具能直接用于系统监控
知识延伸与总结
- Shell编程进阶:
- 函数封装:将
get_cpu_usage()封装为可复用函数 - 信号处理:
trap命令捕获SIGTERM信号,实现脚本优雅退出 - 日志分级:将
echo替换为logger命令,将日志写入系统日志服务(rsyslog)
- 函数封装:将
- 与Python的衔接:当脚本涉及复杂数据结构(如JSON配置解析)、网络请求或并发处理时,应考虑迁移至Python
三、实验二:基于psutil的系统信息采集与进程管理
任务目标
- 掌握
psutil库对CPU、内存、磁盘、网络IO的系统级遍历函数 - 实现基于进程名的进程检索与强制终止(
kill) - 编写跨平台的系统资源监控报告生成器
核心原理
1. psutil架构设计
psutil(Process and System Utilities)是对POSIX系统命令(ps、top、netstat、df等)的Pythonic封装,直接读取/proc与系统调用,避免子进程开销。
2. 关键数据结构与算法
-
CPU时间片:
psutil.cpu_times()返回user、system、idle等时间戳,计算利用率需采样两次(间隔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运行脚本
核心结论
- 跨平台兼容性:
psutil抹平了Linux与Windows的系统调用差异(如psutil.disk_usage('/')在Windows自动映射到C盘),是混合环境运维的首选 - 采样间隔的必要性:CPU利用率本质是时间差分计算,必须设置
interval>0(如1秒),否则返回基于上次调用的缓存值(首次调用通常返回0) - 进程终止的优雅性:应遵循
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)
任务目标
- 理解Linux内核的inotify机制(异步文件系统事件通知)
- 掌握
pyinotify与watchdog两种库的编程模型差异 - 实现文件自动备份与非法文件变更告警系统
核心原理
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_created、on_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或检测文件大小稳定后再备份
核心结论
- 选型建议:纯Linux服务器后台服务首选
pyinotify(轻量、直接调用内核API);需跨平台或桌面GUI集成首选watchdog - 事件延迟:inotify为异步通知,但非实时,极端情况下(系统高负载)存在毫秒级延迟,金融级实时场景需结合文件锁或消息队列
- 递归监控性能:
recursive=True会为每个子目录创建独立watch,深层目录(如Node_modules)可能触及/proc/sys/fs/inotify/max_user_watches上限(默认8192),应限制监控深度或排除模式
知识延伸与总结
- 安全审计:结合
on_deleted与on_moved记录文件变更日志,对接ELK(Elasticsearch+Logstash+Kibana)实现可视化审计 - 实时同步:将
watchdog与rsync或boto3(AWS S3 SDK)结合,实现上传即同步到云端(类似Dropbox机制) - 过滤机制:watchdog支持
PatternMatchingEventHandler,通过patterns=["*.log"]或ignore_patterns=["*.tmp"]减少无效事件处理
五、实验四:Web服务质量监控(pycurl)
任务目标
- 掌握
pycurl库对HTTP/HTTPS服务的深度探测(不仅检测存活,更监控性能指标) - 采集TCP连接建立时间、首字节时间(TTFB)、下载速度等关键SLI(Service Level Indicator)
- 构建Web服务健康检查与性能基线告警系统
核心原理
1. libcurl与pycurl架构
pycurl是libcurl的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配置或目标域名有效性
核心结论
- 网络分层定位法:通过对比
DNS、TCP、SSL、TTFB四个阶段耗时,可快速定位瓶颈层(网络层、传输层、应用层) - 空设备优化:若仅需检测状态码与耗时,将
WRITEDATA指向/dev/null(或BytesIO)避免内存浪费,高并发场景下至关重要 - 协议完整性:
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)
任务目标
- 掌握Linux Cron(时间基线调度)与APScheduler(事件驱动/复杂条件调度)的适用边界
- 实现"系统监控脚本周期性执行"与"动态添加/移除任务"两种场景
- 构建带持久化(数据库存储)与异常告警的调度中心
核心原理
1. 调度系统四要素(APScheduler架构)
- Triggers(触发器):决定何时执行(Cron表达式、Interval间隔、Date定点)
- Job Stores(作业存储):存储任务状态(Memory默认、SQLAlchemy持久化、Redis分布式)
- Executors(执行器):指定线程池/进程池规模(
ThreadPoolExecutorvsProcessPoolExecutor) - 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)
核心结论
- 选型矩阵:
- Cron:系统级、静态配置、分钟级精度、资源占用极低(适合日志轮转)
- APScheduler:应用级、动态管理、秒级精度、需复杂触发条件(如"工作日9-18点每10秒检查")
- 幂等性设计:调度任务本身需具备幂等性(多次执行结果一致),因为网络延迟或重试机制可能导致同一时间点任务被执行多次
- 资源隔离:CPU密集型任务(如日志分析)应配置
ProcessPoolExecutor,IO密集型(如Web探测)使用ThreadPoolExecutor,避免阻塞调度器主循环
知识延伸与总结
- 分布式调度:APScheduler配合Redis/RabbitMQ作为JobStore与共享锁,实现多机部署时的任务唯一性(避免重复执行)
- 可视化运维:集成
APScheduler的Flask-APScheduler插件,暴露REST API用于动态增删改查任务,对接前端管理界面 - 熔断与降级:在
job_listener中统计连续失败次数,当失败率>阈值时自动暂停任务(熔断),并发送告警通知(集成zabbix_sender或webhook)
七、今日关键命令速查
| 类别 | 命令/代码 | 作用 | 场景示例 |
|---|---|---|---|
| 权限 | 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环境验证,可直接用于生产环境改造。
浙公网安备 33010602011771号