ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于Python+Django的自动化运维平台实战:从脚本到可审计系统

基于Python+Django的自动化运维平台实战:从脚本到可审计系统 简介这是一套面向计算机专业毕业设计场景的自动化运维平台源码基于Python与Django构建适合正在准备毕设、需要完整Web项目参考的学生以及想入门运维自动化的开发者。平台围绕服务器监控、日志分析、任务调度、配置管理、权限控制与RESTful API等模块展开将Python的paramiko、psutil等运维能力与Django的MVT架构结合形成可运行的集中管理工具。压缩包共1132个文件约13.78MB其中191个py文件承载后端逻辑223个html与144个css、184个js构成前端界面另有png、ttf、woff等静态资源及sh、yml、conf等部署配置目录结构完整。目前已有312人学习下载。读者可据此理解Django项目分层组织方式参考监控指标采集、定时任务与权限划分的实现思路并借助现有代码快速搭建自己的毕设原型或二次开发基础。1. 从一台跳板机到一套平台自动化运维到底在自动什么很多团队所谓的“自动化运维”本质是几台跳板机上散落着几十个 shell 脚本谁离职谁带走一批出故障时没人敢动。基于 Python Django 的自动化运维平台要解决的就是把这些脚本、定时任务、主机资产、执行记录收进一个 Web 系统里让运维动作可编排、可审计、可回滚。它适合两类人一是手里已经有一堆重复操作批量发版、日志清理、服务重启的中小团队运维二是想用 Django 把 Python 运维脚本产品化的后端开发。这一章先把边界划清楚平台不是万能的它替代的是“人肉登录机器敲命令”不是替代监控和 CI。选 Django 而不是 Flask/FastAPI核心原因是它自带 ORM、Admin、认证和权限体系运维平台最重的资产管理和任务记录这两块Django 能省掉大量脚手架代码。2. 平台骨架怎么搭Django 项目结构与运维域模型2.1 为什么运维平台的数据模型比普通 Web 应用更关键普通 Web 应用的核心是用户和内容运维平台的核心是“主机—任务—执行结果”这条链路。模型设计错了后面任务编排会越写越乱。我一般会先落四张核心表主机资产表Host、任务模板表TaskTemplate、执行记录表TaskExecution、执行明细表TaskDetail。主机表存 IP、SSH 端口、认证方式、所属业务组任务模板存脚本内容或命令、参数定义、超时时间执行记录存一次批量任务的总体状态执行明细存每台机器的 stdout、stderr、退出码。这样设计的好处是一次批量执行对应一条 TaskExecution 和 N 条 TaskDetail查询“某台机器最近失败过哪些任务”只需要 join 一次。# ops/models.py from django.db import models class Host(models.Model): ip models.GenericIPAddressField(uniqueTrue) port models.IntegerField(default22) username models.CharField(max_length64) auth_type models.CharField(max_length16, choices[(key, 密钥), (pwd, 密码)]) credential models.TextField() # 实际项目应加密存储 group models.CharField(max_length64, db_indexTrue) is_active models.BooleanField(defaultTrue) class Meta: indexes [models.Index(fields[group, is_active])] class TaskTemplate(models.Model): name models.CharField(max_length128, uniqueTrue) content models.TextField() # shell 或 python 脚本 timeout models.IntegerField(default300) created_at models.DateTimeField(auto_now_addTrue) class TaskExecution(models.Model): template models.ForeignKey(TaskTemplate, on_deletemodels.CASCADE) status models.CharField(max_length16, defaultpending) created_at models.DateTimeField(auto_now_addTrue) class TaskDetail(models.Model): execution models.ForeignKey(TaskExecution, related_namedetails, on_deletemodels.CASCADE) host models.ForeignKey(Host, on_deletemodels.CASCADE) exit_code models.IntegerField(nullTrue) stdout models.TextField(blankTrue) stderr models.TextField(blankTrue)逻辑说明Host 的 credential 字段在真实项目里必须用 Fernet 或 KMS 加密直接明文存是血泪教训。TaskDetail 用 related_namedetails前端查一次执行的所有机器结果就是 execution.details.all()不用手写 join。参数说明timeout 默认 300 秒批量任务里单机超时应该小于整体超时否则一台卡死会拖垮整个批次。group 加索引是因为按业务组筛选主机是最高频查询。2.2 用 Django 创建 app 并跑通最小可运行骨架热词里“django创建app”是新手第一个卡点。运维平台建议按域拆 app不要全塞在一个 app 里。常见做法是拆成 host、task、account 三个 apphost 管资产task 管任务和执行account 管用户和权限。# 创建项目和 app django-admin startproject opsplatform cd opsplatform python manage.py startapp host python manage.py startapp task python manage.py startapp account # settings.py 里注册 INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, host, task, account, ] # 生成迁移并建表 python manage.py makemigrations python manage.py migrate python manage.py createsuperuser逻辑说明startapp 之后必须把 app 名写进 INSTALLED_APPS否则 makemigrations 不会扫描到模型。参数说明createsuperuser 创建的账号用于登录 Django Admin运维平台初期可以直接用 Admin 管理主机资产省掉自研 CRUD 页面的时间。注意数据库默认是 SQLite生产环境要换成 PostgreSQL因为批量任务写入并发高SQLite 的写锁会成为瓶颈。2.3 主机资产批量导入与连接测试资产录入如果靠手工一条条填平台还没上线运维就先疯了。常见做法是提供一个 CSV 导入接口导入后异步做一次 SSH 连通性测试把不可达的主机标记出来。# host/services.py import paramiko from concurrent.futures import ThreadPoolExecutor from .models import Host def check_host(host: Host) - bool: client paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: if host.auth_type key: key paramiko.RSAKey.from_private_key_file(host.credential) client.connect(host.ip, porthost.port, usernamehost.username, pkeykey, timeout5) else: client.connect(host.ip, porthost.port, usernamehost.username, passwordhost.credential, timeout5) return True except Exception: return False finally: client.close() def batch_check(host_ids): hosts Host.objects.filter(id__inhost_ids) with ThreadPoolExecutor(max_workers20) as pool: results list(pool.map(check_host, hosts)) for host, ok in zip(hosts, results): host.is_active ok Host.objects.bulk_update(hosts, [is_active])逻辑说明用线程池并发探测20 个 worker 是经验值再高容易触发目标机器的 SSH 连接数限制。参数说明timeout5 秒内网可以设 3 秒跨机房设 8 秒。bulk_update 一次性写回避免逐条 save 产生大量 SQL。注意AutoAddPolicy 会自动接受未知主机指纹生产环境应改为 WarningPolicy 并维护 known_hosts否则有中间人风险。3. 任务执行引擎从单机命令到批量并发3.1 为什么不能用 Django 请求线程直接跑批量任务新手最容易翻车的地方是在 view 里直接 for 循环 SSH 执行请求超时了任务还在跑用户刷新页面又触发一次。正确做法是 view 只负责创建 TaskExecution 记录并投递到队列由独立的 worker 消费。常见方案是 Celery Redis轻量场景也可以用 Django 的 management command 配合数据库轮询。# task/tasks.py from celery import shared_task from .models import TaskExecution, TaskDetail from host.models import Host import paramiko shared_task def run_task(execution_id): execution TaskExecution.objects.get(idexecution_id) execution.status running execution.save(update_fields[status]) hosts Host.objects.filter(is_activeTrue) for host in hosts: detail TaskDetail.objects.create(executionexecution, hosthost) client paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: client.connect(host.ip, porthost.port, usernamehost.username, passwordhost.credential, timeout5) stdin, stdout, stderr client.exec_command( execution.template.content, timeoutexecution.template.timeout) detail.exit_code stdout.channel.recv_exit_status() detail.stdout stdout.read().decode(utf-8, errorsignore) detail.stderr stderr.read().decode(utf-8, errorsignore) except Exception as e: detail.exit_code -1 detail.stderr str(e) finally: client.close() detail.save() execution.status finished execution.save(update_fields[status])逻辑说明exec_command 返回的三个通道必须都读只读 stdout 不读 stderr 在输出量大时会阻塞。参数说明recv_exit_status 会阻塞直到命令结束配合 timeout 参数控制单机最长执行时间。注意这个版本是串行执行生产环境要改成 ThreadPoolExecutor 或 Celery 的 group但并发数要控制否则一次对 500 台机器发命令网络设备先扛不住。3.2 任务模板的参数化与安全边界硬编码命令的模板没有复用价值。常见做法是在模板里用 {{var}} 占位执行时传入参数字典做替换。但这里有个安全红线参数必须做白名单校验否则就是命令注入。# task/services.py import re PARAM_PATTERN re.compile(r\{\{(\w)\}\}) def render_template(content: str, params: dict) - str: def replace(match): key match.group(1) if key not in params: raise ValueError(f缺少参数: {key}) value str(params[key]) # 只允许字母数字、点、斜杠、横线、下划线 if not re.match(r^[\w./-]$, value): raise ValueError(f参数 {key} 含非法字符) return value return PARAM_PATTERN.sub(replace, content)逻辑说明用正则替换而不是 str.format因为 format 会执行属性访问存在注入风险。参数说明白名单正则 ^[\w./-]$ 覆盖了路径、版本号、服务名等常见参数如果业务需要空格或引号要单独评估。注意这个校验放在服务端前端校验只是体验优化不能作为安全依据。3.3 执行结果的存储与查询优化批量任务跑完TaskDetail 表会迅速膨胀。一台机器一天跑 10 个任务100 台机器一个月就是 3 万条明细。常见做法是 stdout/stderr 超过一定长度就截断存储完整日志落文件或对象存储数据库只存路径和摘要。MAX_LOG 8192 def save_detail(detail, stdout, stderr): detail.stdout stdout[:MAX_LOG] detail.stderr stderr[:MAX_LOG] if len(stdout) MAX_LOG: detail.stdout \n...[已截断] detail.save()逻辑说明截断阈值 8KB 是经验值足够定位大多数报错。参数说明如果业务需要完整日志把完整内容写到 /var/log/ops/{execution_id}/{host_ip}.log数据库存路径。注意TaskDetail 表要按 created_at 建索引并且定期归档否则半年后查询会明显变慢。4. 避坑与排查那些让平台上线即翻车的细节4.1 现象批量任务卡在 running 状态不结束原因worker 进程被 OOM kill 或者 Celery 任务没有设置超时某台机器 SSH 连接挂起导致整个任务阻塞。解决给 Celery 任务加 soft_time_limit 和 time_limitSSH 连接设置 timeout 和 banner_timeout并且用 try/finally 保证异常时也更新 execution 状态。4.2 现象同一任务被重复执行两次原因前端提交按钮没有防抖或者 Celery 的 acks_late 配置导致任务重投。解决在 TaskExecution 创建时用数据库唯一约束或 Redis 锁做幂等前端按钮点击后立即 disable。acks_late 要配合任务幂等设计使用不能盲目开。4.3 现象中文输出乱码原因目标机器 locale 不是 UTF-8stdout.read() 按默认编码解码失败。解决decode 时显式指定 errorsignore或者在 exec_command 前加 export LANGen_US.UTF-8。更稳妥的做法是统一用 bytes 存储展示层再解码。4.4 现象主机密钥认证失败但密码认证正常原因paramiko 读取的私钥格式不对比如 OpenSSH 新格式需要额外转换或者私钥有 passphrase 没传。解决用 paramiko.RSAKey.from_private_key_file 时捕获 PasswordRequiredException提示用户私钥需要去密码新格式私钥先用 ssh-keygen -p -m PEM 转换。4.5 现象Django Admin 加载主机列表越来越慢原因Host 表数据量大Admin 默认的 count 查询和每行外键查询拖慢页面。解决在 ModelAdmin 里设置 list_select_related、list_per_page50关闭 show_full_result_count必要时用 raw_id_fields 替代下拉框。5. 进阶技巧把执行记录变成可检索的运维知识库平台跑起来之后最有价值的不是执行本身而是积累下来的失败记录。我习惯在 TaskDetail 上加一个 failure_signature 字段把 stderr 做归一化去掉 IP、时间戳、PID后取 hash相同签名归为一类。这样跑一个月后你能直接看到“Top 10 失败原因”而不是一条条翻日志。import hashlib import re def normalize(stderr: str) - str: s re.sub(r\d\.\d\.\d\.\d, IP, stderr) s re.sub(r\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}, TIME, s) s re.sub(r\b\d\b, N, s) return s.strip() def signature(stderr: str) - str: return hashlib.md5(normalize(stderr).encode()).hexdigest()[:16]逻辑说明归一化把变量替换成占位符让同类错误落到同一个签名。参数说明hash 取前 16 位足够区分存 char(16) 即可。注意这个函数要在保存 TaskDetail 时调用历史数据可以写个 management command 回填。另一个技巧是给任务模板加“最近成功率”统计用 Django 的 annotate 一次查出展示在模板列表页。运维看到某个模板成功率从 99% 掉到 80%就知道该去查原因了。from django.db.models import Count, Q templates TaskTemplate.objects.annotate( totalCount(taskexecution__details), failedCount(taskexecution__details, filterQ(taskexecution__details__exit_code__gt0)) ) for t in templates: rate (t.total - t.failed) / t.total if t.total else 0 print(t.name, f{rate:.1%})逻辑说明用条件聚合一次查询算出总数和失败数避免 N1。参数说明exit_code__gt0 表示非零退出码算失败-1 表示连接异常也算失败。注意数据量大时这个查询会慢可以做成定时任务写入统计表页面直接读结果。我自己的习惯是平台上线第一周不接生产批量操作只做只读的资产巡检和日志采集让团队先信任执行结果的准确性。等失败签名库积累到几十条再逐步开放写操作。这个节奏比一上来就全量接管稳得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表