用 Rust 和 AI 做自动化运维:巡检脚本加模型分析等于智能值班

📅 2026/7/22 1:35:06
用 Rust 和 AI 做自动化运维:巡检脚本加模型分析等于智能值班
用 Rust 和 AI 做自动化运维巡检脚本加模型分析等于智能值班一、值班的真实痛点 —— 不是累是重复说起值班很多人的第一反应是累。但说实话值班真正的问题不是体力上的累而是机械重复。每周做同样的事情检查磁盘使用率、检查服务健康状态、检查备份是否成功、检查证书是否快过期……这些检查有固定的流程和检查项但执行过程枯燥且容易遗漏。另一个痛点是经验难以沉淀。老运维看一眼磁盘增长曲线就知道这个趋势下去下周三会满新人可能需要翻历史数据才能得出同样的结论。这种直觉如果能用代码表达出来价值巨大。二、Rust 巡检脚本 —— 采集层的完整实现巡检的第一步是采集。我设计了一个可扩展的检查框架每个检查项是一个独立的 trait 实现方便新增和修改检查逻辑。use async_trait::async_trait; use serde::Serialize; use std::time::Duration; /// 单个巡检项的结果 #[derive(Debug, Clone, Serialize)] pub struct CheckResult { /// 检查项名称如 磁盘使用率、服务健康状态 pub name: String, /// 状态: ok / warning / critical pub status: String, /// 采集到的数值人类可读 pub value: String, /// 详细描述 pub detail: String, /// 采集时间戳 pub timestamp: chrono::DateTimechrono::Utc, } /// 巡检检查项 trait —— 新增检查项只需实现这个接口 #[async_trait] pub trait HealthCheck: Send Sync { /// 检查项名称用于报告展示 fn name(self) - str; /// 执行检查并返回结果 async fn execute(self) - CheckResult; /// 检查项优先级数字越小越优先展示 fn priority(self) - u32 { 100 } } /// 磁盘使用率检查 pub struct DiskUsageCheck { /// 告警阈值使用率超过此值触发 warning warn_threshold: f64, /// 严重告警阈值 critical_threshold: f64, } #[async_trait] impl HealthCheck for DiskUsageCheck { fn name(self) - str { 磁盘使用率 } async fn execute(self) - CheckResult { // 执行 df -h 获取磁盘使用情况 let output tokio::process::Command::new(df) .arg(-h) .arg(/) // 检查根分区 .output() .await; let detail match output { Ok(out) String::from_utf8_lossy(out.stdout).to_string(), Err(e) { return CheckResult { name: self.name().to_string(), status: critical.to_string(), value: N/A.to_string(), detail: format!(无法获取磁盘信息: {}, e), timestamp: chrono::Utc::now(), }; } }; // 解析 df 输出提取使用率百分比 let usage Self::parse_disk_usage(detail); let status if usage self.critical_threshold { critical } else if usage self.warn_threshold { warning } else { ok }; CheckResult { name: self.name().to_string(), status: status.to_string(), value: format!({:.1}%, usage), detail, timestamp: chrono::Utc::now(), } } } impl DiskUsageCheck { /// 解析 df -h 的输出提取使用率百分比 fn parse_disk_usage(output: str) - f64 { // df -h / 输出示例: // Filesystem Size Used Avail Use% Mounted on // /dev/sda1 50G 35G 13G 73% / for line in output.lines().skip(1) { let fields: Vecstr line.split_whitespace().collect(); if fields.len() 5 { if let Some(percent) fields[4].strip_suffix(%) { if let Ok(val) percent.parse::f64() { return val; } } } } 0.0 } } /// 服务健康检查 pub struct ServiceHealthCheck { /// 要检查的服务名列表 services: VecString, } #[async_trait] impl HealthCheck for ServiceHealthCheck { fn name(self) - str { 服务健康状态 } async fn execute(self) - CheckResult { let mut results Vec::new(); let mut all_ok true; for service in self.services { // systemctl is-active 检查服务状态 let output tokio::process::Command::new(systemctl) .arg(is-active) .arg(service) .output() .await; let status match output { Ok(out) { let stdout String::from_utf8_lossy(out.stdout); let active stdout.trim() active; if !active { all_ok false; } format!({}: {}, service, stdout.trim()) } Err(e) { all_ok false; format!({}: 检查失败 ({}), service, e) } }; results.push(status); } CheckResult { name: self.name().to_string(), status: if all_ok { ok.to_string() } else { warning.to_string() }, value: format!({}/{} 服务正常, results.iter().filter(|r| r.contains(active)).count(), self.services.len() ), detail: results.join(\n), timestamp: chrono::Utc::now(), } } } /// SSL 证书过期检查 pub struct CertExpiryCheck { /// 检查的域名列表 domains: VecString, /// 提前多少天告警 warn_days: i64, } #[async_trait] impl HealthCheck for CertExpiryCheck { fn name(self) - str { SSL 证书有效期 } async fn execute(self) - CheckResult { let mut results Vec::new(); let mut earliest_expiry i64::MAX; for domain in self.domains { // 使用 openssl s_client 获取证书信息 let output tokio::process::Command::new(sh) .arg(-c) .arg(format!( echo | openssl s_client -servername {} -connect {}:443 2/dev/null | openssl x509 -noout -enddate, domain, domain )) .output() .await; match output { Ok(out) { let stdout String::from_utf8_lossy(out.stdout); // 解析 notAfter 字段 if let Some(date_str) stdout.strip_prefix(notAfter) { let date_str date_str.trim(); results.push(format!({}: 有效期至 {}, domain, date_str)); // 计算剩余天数 if let Ok(expiry) chrono::NaiveDate::parse_from_str( date_str[..10], %b %d %Y ) { let remaining (expiry.and_hms_opt(0, 0, 0).unwrap() .and_utc() - chrono::Utc::now()) .num_days(); earliest_expiry earliest_expiry.min(remaining); } } } Err(e) { results.push(format!({}: 检查失败 ({}), domain, e)); } } } let status if earliest_expiry 7 { critical } else if earliest_expiry self.warn_days { warning } else { ok }; CheckResult { name: self.name().to_string(), status: status.to_string(), value: format!(最早到期: {} 天后, earliest_expiry), detail: results.join(\n), timestamp: chrono::Utc::now(), } } }框架设计的关键是HealthChecktrait——所有检查项实现同一个接口新增检查项只需 add 一行注册代码不改任何现有逻辑。这就是面向接口编程的优势也是 Rust trait 系统在实际项目中最实用的用法之一。三、巡检引擎 —— 编排所有检查项采集模块写好了需要一个引擎来编排执行、收集结果、汇总报告。use std::sync::Arc; /// 巡检引擎 —— 编排所有检查项的运行 pub struct PatrolEngine { /// 所有注册的检查项 checks: VecBoxdyn HealthCheck, } impl PatrolEngine { pub fn new() - Self { Self { checks: Vec::new() } } /// 注册一个检查项 pub fn register(mut self, check: Boxdyn HealthCheck) { self.checks.push(check); } /// 执行全部巡检并返回结果列表 pub async fn run_all(self) - VecCheckResult { // 并发执行所有检查项 let futures: Vec_ self.checks .iter() .map(|check| { let name check.name().to_string(); async move { let start std::time::Instant::now(); let result check.execute().await; let elapsed start.elapsed(); tracing::info!( 巡检项 [{}] 完成耗时 {:?}状态: {}, name, elapsed, result.status ); result } }) .collect(); let mut results futures::future::join_all(futures).await; // 按优先级排序 results.sort_by_key(|r| { self.checks .iter() .find(|c| c.name() r.name) .map(|c| c.priority()) .unwrap_or(100) }); results } /// 生成纯文本报告供 LLM 分析使用 pub fn generate_text_report(self, results: [CheckResult]) - String { let summary results .iter() .map(|r| format!( [{}] {}: {} — {}, r.status.to_uppercase(), r.name, r.value, r.detail )) .collect::Vec_() .join(\n); let ok_count results.iter().filter(|r| r.status ok).count(); let warn_count results.iter().filter(|r| r.status warning).count(); let crit_count results.iter().filter(|r| r.status critical).count(); format!( 系统巡检报告\n时间: {}\n\n正常: {} 项\n警告: {} 项\n严重: {} 项\n\n详细结果:\n{}, chrono::Utc::now().format(%Y-%m-%d %H:%M:%S), ok_count, warn_count, crit_count, summary ) } }四、AI 分析层 —— 让巡检报告变智能采集到的数据不能只是罗列要有分析。我接入了一个本地部署的 LLMqwen2.5-7b来分析巡检结果输出趋势预测和风险提示。/// AI 智能分析器 —— 让巡检不再只是罗列数据 pub struct AIPatrolAnalyzer { /// 本地 LLM 的 API 地址如 Ollama llm_url: String, /// 模型名称 model: String, } impl AIPatrolAnalyzer { /// 分析巡检报告并生成智能建议 pub async fn analyze(self, report: str) - ResultString, anyhow::Error { let prompt format!( r#你是一名资深运维工程师。请分析以下系统巡检报告并给出智能建议。 ## 巡检报告 {} ## 分析要求 1. **趋势判断**对比上次巡检如果有历史数据哪些指标有恶化趋势 2. **风险预警**未来 7 天内可能出现的问题有哪些 3. **优化建议**基于当前系统状态给出 3 条优先级最高的优化建议 4. **总结**用一句话总结当前系统健康状况 请用简洁专业的中文输出每个部分控制在 3 行以内。不要推测没有数据支撑的结论。 #, report ); let body serde_json::json!({ model: self.model, messages: [ { role: system, content: 你是运维专家请用中文输出分析结果简洁专业。 }, { role: user, content: prompt } ], temperature: 0.3, max_tokens: 800 }); let resp reqwest::Client::new() .post(self.llm_url) .json(body) .send() .await?; let json: serde_json::Value resp.json().await?; let analysis json[message][content] .as_str() .unwrap_or(分析失败) .to_string(); Ok(analysis) } } /// 完整的智能值班主流程 pub async fn run_ai_patrol() - Result(), anyhow::Error { // 1. 初始化巡检引擎并注册检查项 let mut engine PatrolEngine::new(); engine.register(Box::new(DiskUsageCheck { warn_threshold: 80.0, critical_threshold: 90.0, })); engine.register(Box::new(ServiceHealthCheck { services: vec![ nginx.to_string(), docker.to_string(), sshd.to_string(), ], })); engine.register(Box::new(CertExpiryCheck { domains: vec![ api.example.com.to_string(), admin.example.com.to_string(), ], warn_days: 30, })); // 2. 执行全部巡检 let results engine.run_all().await; // 3. 生成纯文本报告 let report engine.generate_text_report(results); // 4. AI 分析 let analyzer AIPatrolAnalyzer { llm_url: http://localhost:11434/api/chat.to_string(), model: qwen2.5:7b.to_string(), }; let ai_analysis analyzer.analyze(report).await?; // 5. 组装最终报告 let final_report format!( {}\n\n## AI 智能分析\n{}, report, ai_analysis ); println!({}, final_report); // 6. 推送到企业微信 / 钉钉 / 飞书 // send_to_wecom(final_report).await?; // 7. 如果有 critical 项自动创建工单 let has_critical results.iter().any(|r| r.status critical); if has_critical { println!(⚠️ 检测到严重问题建议立即处理); // create_jira_ticket(results).await?; } Ok(()) }temperature: 0.3在这里特别重要——对于运维分析来说我们要的是准确的判断不是文学创作。超过 0.5 的温度可能导致 LLM 虚构不存在的风险误导值班人员。实际项目里跑了一个月发现本地 LLM 的趋势预测对于磁盘增长这种线性问题很准但对于 CPU 毛刺这种随机波动经常过度预警——AI 看到一个大波动就预测下周会雪崩实际只是恰好有一批批处理任务。后来给 LLM 的 prompt 加了过去 30 天的均值方差作为背景数据虚警率从 60% 降到了 15%。多说一句本地的 qwen2.5:7b 做运维分析绰绰有余完全没必要上更大模型。省下来的 GPU 内存跑巡检都不卡。五、总结这套方案在我们组跑了三周最大的感受是——以前值班是被动执行每周一机械地跑完检查然后写总结现在是主动预警每天早上定时跑巡检AI 会主动指出磁盘增长趋势如果持续预计一周后达到 85%。从事后报告变成事前预测这才是智能运维的真正价值。