ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Django的运维平台实战:资产管理、任务执行与发布流程

基于Django的运维平台实战:资产管理、任务执行与发布流程

简介:这份资源是一套基于Django框架开发的轻量级运维平台源码,面向具备Python基础、希望了解Web运维系统实现思路的开发者与运维人员,可用于学习主机管理、服务监控、日志查看、任务调度、权限控制、资源统计、脚本执行与故障告警等常见运维模块的落地方式。压缩包共164个文件,以39个py后端代码、38个js与23个css前端资源为主,另含11个html模板、18个png图片及字体图标文件,整体约3.63MB,采用前后端不分离架构,Django视图与模板紧密结合,目录结构清晰,便于按模块阅读。已有2718人学习下载,说明其作为入门级实战项目具有一定参考价值。解压后可直接查看模型定义、视图函数与静态资源组织方式,适合作为课程设计、毕业设计原型或小型运维工具的二次开发起点,帮助读者理解Python Web开发与日常运维场景的结合路径。

1. 运维平台为什么值得用 Django 重写一遍

很多团队最初的运维工具是一堆散落的 shell 脚本加几张 Excel 资产表,机器一多就开始翻车:谁改过配置、哪台机器上跑的是哪个版本、发布失败该回滚到哪一步,全靠人脑记。这套方案要解决的就是把「脚本 + 表格 + 口头交接」收敛成一个可登录、可审计、可扩展的 Web 运维平台,用 Python 和 Django 把资产管理、任务执行、发布流程、监控看板串成一条线。它适合两类人:正在做毕设或课程设计、需要一个结构完整且能讲清楚技术选型的同学;以及小团队里要交付一版内部运维工具、后续还得自己维护的工程师。DevOps 工具链里最不缺的就是单点脚本,缺的是一个能把权限、日志、执行结果统一收口的入口,这也是我选 Django 而不是 Flask 或 FastAPI 的核心原因——后台管理、ORM、权限体系、表单校验都是现成的,省下来的时间可以全砸在任务执行和流程编排上。

2. 需求拆解与技术选型:四个模块撑起一个平台

2.1 从真实痛点倒推功能边界

做运维平台最容易犯的错是一上来就想做「全能面板」,结果三个月过去连资产录入都没跑通。我一般会先把需求压到四条主线上,每条线只保留最小可用闭环:

模块核心能力最小闭环后续可扩展
资产管理主机、分组、标签、凭据手工录入 + 批量导入自动发现、CMDB 同步
任务执行批量命令、脚本分发、结果回传单机执行 + 日志落库并发调度、失败重试
发布流程应用、版本、环境、审批单应用单环境发布灰度、回滚编排
监控看板主机存活、任务成功率定时探测 + 图表展示告警规则、通知渠道

这张表的作用不是给老师看的,是给自己划边界。资产模块必须先于任务模块完成,因为任务执行的对象就是资产记录;发布流程又依赖任务执行的结果回传,否则审批通过了却不知道有没有发成功。依赖顺序理清楚,开发排期就不会出现「页面都画完了发现底层没数据」的尴尬。

技术选型上,Web 框架用 Django,理由前面说过,权限和 Admin 能直接复用;任务执行层不引入 Celery 这类重型组件,毕设和中小团队场景下用 Django 自带的线程池加 subprocess 就够,部署少一个中间件就少一个故障点;数据库用 MySQL 或 PostgreSQL 都行,资产和日志表结构不复杂,SQLite 在演示阶段也能跑,但并发写入日志时容易锁表,正式一点还是上 MySQL;前端不搞前后端分离,Django 模板加 Bootstrap 足够,省掉一套构建链路,答辩时也少解释一个技术栈。

2.2 数据模型设计:先把表结构钉死

运维平台的数据模型决定了后面所有功能好不好写。核心就四张表:主机、主机组、任务、任务执行记录。主机和主机组是多对多,一台机器可以属于多个业务分组;任务和执行记录是一对多,一次批量任务会产生多条执行明细。下面这段模型定义可以直接作为起点:

# models.py from django.db import models class HostGroup(models.Model): name = models.CharField(max_length=64, unique=True) remark = models.CharField(max_length=255, blank=True) class Host(models.Model): ip = models.GenericIPAddressField(unique=True) hostname = models.CharField(max_length=128) port = models.IntegerField(default=22) username = models.CharField(max_length=64) # 凭据不存明文,实际项目里走加密字段或外部密钥管理 credential_ref = models.CharField(max_length=128) groups = models.ManyToManyField(HostGroup, blank=True) is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) class Task(models.Model): name = models.CharField(max_length=128) command = models.TextField() targets = models.ManyToManyField(Host) created_by = models.CharField(max_length=64) created_at = models.DateTimeField(auto_now_add=True) class TaskRecord(models.Model): task = models.ForeignKey(Task, on_delete=models.CASCADE, related_name='records') host = models.ForeignKey(Host, on_delete=models.CASCADE) status = models.CharField(max_length=16, default='pending') # pending/success/failed stdout = models.TextField(blank=True) stderr = models.TextField(blank=True) duration_ms = models.IntegerField(default=0) finished_at = models.DateTimeField(null=True, blank=True)

几个设计点值得展开。credential_ref存的是凭据引用而不是密码本身,这是血泪经验——早期图省事直接存明文,后面做导出功能时差点把密码带出去。TaskRecord单独建表而不是把结果塞进Task的 JSON 字段,是为了让「按主机查历史」「按状态筛失败记录」这类查询能走索引,日志量上来之后差别很明显。status用字符串而不是布尔值,是因为后面大概率要加timeout、canceled这些状态,布尔值改起来要动数据迁移。

2.3 权限与审计:别等出事才补

Django 自带的auth体系够用,但运维平台的角色划分要更细一点。通常的做法是建三个组:管理员、运维、只读。管理员能管资产和用户,运维能建任务和发布,只读只能看记录。用 Django 的Group加自定义权限就能实现,不需要引入第三方权限库。审计这块,所有写操作都通过信号或者中间件记一条操作日志,字段包括操作人、动作、对象、时间、来源 IP。这个表在答辩时是加分项,在实际使用中是后悔药——出了事故第一件事就是查谁在什么时候动了什么。

3. 任务执行引擎:从点击按钮到结果落库

3.1 执行链路拆解

任务执行是整个平台最容易翻车的部分,链路必须拆清楚:前端提交任务 → 后端校验目标主机和命令 → 创建Task和一批TaskRecord(状态 pending)→ 线程池并发执行 → 每台主机执行完更新对应记录 → 前端轮询或刷新看结果。这里的关键决策是「同步还是异步」。同步执行在主机少的时候没问题,但批量十台以上就会把请求卡住,浏览器超时。我一般用线程池做异步,任务提交后立即返回,前端拿任务 ID 去轮询状态。

# executor.py import subprocess, time from concurrent.futures import ThreadPoolExecutor from django.utils import timezone from .models import TaskRecord POOL = ThreadPoolExecutor(max_workers=8) def run_on_host(record: TaskRecord): start = time.time() try: # 演示用本地 subprocess,实际项目替换为 SSH 客户端调用 proc = subprocess.run( record.task.command, shell=True, capture_output=True, text=True, timeout=30 ) record.stdout = proc.stdout[-4000:] # 截断,避免日志表膨胀 record.stderr = proc.stderr[-4000:] record.status = 'success' if proc.returncode == 0 else 'failed' except subprocess.TimeoutExpired: record.status = 'failed' record.stderr = 'execution timeout' finally: record.duration_ms = int((time.time() - start) * 1000) record.finished_at = timezone.now() record.save(update_fields=['stdout', 'stderr', 'status', 'duration_ms', 'finished_at']) def dispatch(task): for record in task.records.filter(status='pending'): POOL.submit(run_on_host, record)

这段代码有几个参数需要按场景调。max_workers=8是并发上限,设太大容易把目标机器打满,设太小批量任务排队时间长,一般按目标主机数量的三分之一到二分之一来定。timeout=30是单机执行超时,长任务要单独放宽,但必须有上限,否则一个卡死的命令会占住线程。[-4000:]截断日志是防止某条命令输出几十兆把数据库撑爆,完整日志应该落文件而不是落库。update_fields指定更新字段能减少写放大,日志表写入频繁时这个细节有用。

3.2 结果回传与前端展示

执行结果落库后,前端用简单的轮询就能展示。任务详情页每三秒请求一次状态接口,返回各主机的执行状态和耗时,页面用表格渲染,失败的行标红。这里不需要 WebSocket,毕设场景下轮询足够,实现成本低,答辩时也好解释。状态接口要做分页,一次批量任务可能涉及上百台主机,全量返回会让页面卡顿。

提示:轮询接口要加缓存或者限流,否则多个页面同时开着会把数据库读压力放大,简单做法是接口层加 2 秒的结果缓存。

3.3 发布流程怎么接进来

发布流程本质上是「带审批的任务执行」。建一张Release表,字段包括应用名、版本号、目标环境、关联任务、审批状态、审批人。运维提交发布单后状态是待审批,管理员审批通过才触发任务执行,执行结果回写到发布单上。回滚就是把上一个成功版本的发布单重新执行一遍,不需要单独设计回滚逻辑。这个设计的好处是发布和任务共用一套执行引擎,代码量少,逻辑也统一。

4. 监控看板与资产联动:让数据自己说话

4.1 存活探测与定时任务

监控看板的第一版不需要接 Prometheus,用 Django 的 management command 加系统定时任务就能做主机存活探测。写一个probe_hosts命令,遍历活跃主机,对 IP 和端口做 TCP 连接测试,结果写进一张HostStatus表,记录时间、是否可达、响应耗时。系统 crontab 每分钟跑一次,看板页面按最近一次探测结果渲染在线率和耗时趋势。

# 每分钟探测一次,输出追加到日志便于排查 * * * * * cd /path/to/project && python manage.py probe_hosts >> /var/log/probe.log 2>&1

探测超时设 3 秒比较合适,太短会误判网络抖动,太长会让一轮探测拖过一分钟。探测结果表要定期清理,只保留最近七天的数据,否则表会无限增长。这个清理动作也写成 management command,每天凌晨跑一次。

4.2 看板指标怎么选

看板上的指标不在多,在于能回答「现在有没有问题」。我一般放四块:主机在线率、近 24 小时任务成功率、失败任务 Top 5 主机、最近发布记录。前两个是健康度,第三个是定位入口,第四个是变更上下文。图表用 ECharts 或者 Chart.js 都行,数据接口返回聚合后的 JSON,前端只负责渲染。聚合查询用 Django ORM 的annotate和Count就能完成,不需要手写 SQL。

4.3 资产与任务的联动细节

资产表里有个容易被忽略的字段是「标签」,比如env=prod、role=web。任务创建时支持按标签选目标,比一台台勾选高效得多。实现上就是Host.objects.filter(groups__name='web')这类查询,前端把标签做成多选。这个功能在演示时很出彩,因为它体现了「资产不是死数据,是执行的目标源」。另外,主机下线时不要物理删除,把is_active置 false,历史任务记录还能关联到它,否则外键一删,审计链路就断了。

5. 避坑与排查:那些演示时看不出来的问题

5.1 任务状态永远停在 pending

现象是提交任务后前端一直显示执行中,刷新也没用。原因通常是线程池提交失败或者执行函数抛了未捕获异常,记录没被更新。解决方法是给run_on_host整体包一层 try/except,任何异常都把记录置为 failed 并写入错误信息;同时在dispatch里检查POOL.submit的返回值,提交失败要立即标记。排查时直接看TaskRecord里finished_at为空的记录,基本能定位到卡住的那批。

5.2 日志表把数据库撑爆

现象是平台跑两周后查询变慢,磁盘告警。原因是每条执行记录都存了完整 stdout/stderr,批量任务一多,单表轻松上百万行。解决办法是双管齐下:写入时截断到固定长度,完整日志落文件并在记录里存文件路径;同时加定期归档任务,把三个月前的记录迁到历史表或者直接清理。这个坑我在第一个版本就踩过,后来把日志字段改成TextField加截断,查询速度立刻回来了。

5.3 权限配了但接口没校验

现象是只读用户通过直接调接口能触发任务执行。原因是视图函数只在前端隐藏了按钮,后端没加permission_required或者自定义校验。解决办法是所有写操作视图统一加装饰器,并且在测试用例里专门写一条「只读用户调用写接口应返回 403」的断言。这类问题在答辩时被问到会很尴尬,提前用测试覆盖掉最省心。

5.4 批量导入资产时 IP 重复报错中断

现象是导入 CSV 时遇到一条重复 IP 整个事务回滚,前面导入的全没了。原因是用了create而不是update_or_create,且没做逐行异常处理。解决办法是导入逻辑改成逐行处理,重复的走更新,格式错误的记录收集起来最后统一提示,不要因为一行脏数据废掉整批。

5.5 定时探测把目标机器当攻击源

现象是探测频率调高后,目标机器的安全策略把平台服务器拉黑。原因是每分钟对同一 IP 发起连接,频率过高。解决办法是探测间隔不低于一分钟,同一轮探测内串行执行并加短延迟,重要主机可以单独配置探测频率。这个细节在方案文档里写一句,能体现对生产环境的敬畏。

6. 验收与答辩:怎么证明这套平台真的能用

验收这件事,我一般分三层来做,层层递进,答辩时按这个顺序讲,评委跟得上,自己也不慌。

第一层是功能验收,对着需求表逐条走。资产模块要演示新增、批量导入、按标签筛选;任务模块要演示单机执行、批量执行、失败记录查看;发布模块要演示提交、审批、执行、回滚完整链路;看板要演示在线率和成功率随数据变化。每条验收都准备一条具体的测试数据,比如「三台模拟主机、一个故意失败的脚本」,现场跑一遍比截图有说服力。

第二层是异常验收,专门演示出问题时的表现。停掉一台模拟主机看探测是否标红,提交一条超时命令看状态是否变 failed,用只读账号调写接口看是否返回 403。这一层最能体现方案的完整度,因为大部分课程设计只演示正常流程,异常路径一問就露馅。

第三层是数据验收,检查审计日志和任务记录是否完整。随便挑一条历史任务,能查到谁建的、什么时候执行的、每台主机的输出是什么、耗时多少。这一步证明平台不是「演示玩具」,而是有可追溯性的工具。

答辩陈述控制在八分钟以内,结构建议是:两分钟讲痛点和模块划分,三分钟演示主链路,两分钟讲一个踩坑和解决过程,一分钟讲后续扩展方向。踩坑那段是加分项,讲清楚「日志表膨胀怎么发现、怎么定位、怎么改」,比堆功能列表更能说明你真的动手做过。

验收项通过标准演示方式
资产导入100 条 CSV 导入成功,重复 IP 走更新现场导入并查列表
批量任务10 台目标全部返回结果,失败可见跑一条混合成功失败的命令
发布回滚回滚后版本号回到上一版走完整发布再回滚
权限隔离只读账号写接口返回 403切换账号调接口
审计追溯任意任务可查到操作人和明细随机点一条历史记录

从那以后我每次做这类平台,都会在开发前先把验收表写出来,功能按表开发,答辩按表讲,中间少了很多「做完了发现没东西可演示」的返工。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进