简介:这是一套面向计算机专业毕业设计场景的自动化运维平台源码,基于Python与Django开发,适合需要完成Web运维系统课题的学生,也适合希望熟悉Django项目实战的开发者。平台围绕服务器监控、日志分析、任务调度、配置管理、权限控制与RESTful API等功能模块展开,可帮助管理员集中处理常见运维任务。压缩包共1132个文件,包含191个Python文件、223个HTML模板、184个JS脚本、144个CSS样式及较多PNG等静态资源,整体13.78MB,目录结构完整,其中还包含环境配置、启动脚本和文档说明,便于本地运行与部署。目前已有311人学习下载。通过这套代码,读者可梳理Django的MVT架构、ORM数据交互、定时任务调度逻辑及前后端配合方式,同时获得一套可复用的运维平台基础框架,便于二次开发或作为毕业设计答辩材料。
1. 为什么说Python+Django撑起自动化运维是最稳妥的组合:先把这个zip解压后的价值看明白
运维最烦的不是故障处理,而是每天在重复的登录、查日志、改配置里耗掉大半天。当你拿到一个「基于Python+Django的自动化运维平台.zip」,解压后通常会看到manage.py、requirements.txt、按业务拆开的app目录——这不是玩具演示,而是一套把资产、任务、告警、权限收拢到浏览器里的正经系统。Django自带的后台管理、用户认证和ORM,恰好是运维平台最需要又最不想从零写的东西;自动化运维平台的核心诉求是「少人工」,而Django的admin、auth、migrate这些能力本身就是少人工的活例子。这篇笔记适合刚转自动化的运维、想用Python做内部系统的开发,也适合想评估Django值不值得当运维开发主框架的人。
2. 从.zip到能访问的Web平台:环境搭建、依赖安装与数据库初始化完整步骤
2.1 先确认Python和虚拟环境:版本不对后面全是坑
很多zip传到你手上时,写它的人用的是什么Python版本,你并不知道。最稳妥的做法是先确认本机环境。在终端里敲:
python --version python3 --version如果你在Windows上装了Python但从官网下载后没勾选「Add Python to PATH」,那么python命令可能直接报错,这时候要用py -V或者去pycharm里看解释器路径。在Linux上则会遇到python指向Python 2、python3才是Python 3的老问题,所以后面所有命令里我都会写成python还是python3,你按本机实际来。
确认版本之后,强烈建议建一个虚拟环境,不要直接往系统Python里装依赖。这是Python项目最常见也最值得养成的习惯:项目A要Django 3.2,项目B要Django 4.2,混在一个环境里就是翻车现场。
python -m venv venv # Windows 激活 venv\Scripts\activate # Linux / macOS 激活 source venv/bin/activate激活后命令行前面会出现(venv),后面pip装什么都只影响这个虚拟环境。pycharm或vscode配python环境时,也直接把解释器指到venv/bin/python或venv\Scripts\python.exe,这样编辑器里的代码补全和调试走的都是同一份依赖。
2.2 解压与依赖安装:requirements.txt是第一个黑匣子
把zip解压后,先看根目录。一个典型的Django项目里一定有这几样:manage.py(所有命令的入口)、requirements.txt(依赖清单)、一个与项目同名的配置包(里面是settings.py)、以及按功能拆分的app目录。我第一次拿到这类zip时,第一反应不是急着跑,而是先打开requirements.txt看它锁了哪些库。
通常你会在里面看到这些:
| 依赖 | 在运维平台里干什么 |
|---|---|
| Django | Web框架本身,路由、ORM、admin、auth |
| djangorestframework | 提供API接口,给前端或外部系统调用 |
| celery | 异步任务队列,跑耗时命令和定时巡检 |
| redis | Celery的broker和结果后端 |
| paramiko | 用SSH登录服务器并执行命令 |
| requests | 调外部接口,比如钉钉、企业微信通知 |
| channels | 如果需要WebSocket实时推送,会看到它 |
安装命令很简单,但有个容易卡住的问题:直接pip install -r requirements.txt可能会因为网络问题装到一半失败。国内环境建议先配置镜像源,或者单独装某个装不上的库:
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple如果某个库老是编译报错,比如paramiko的依赖cryptography在Windows上装不上,去官网下一个对应Python版本的wheel再pip install,比硬改代码省事得多。装完执行pip list,对照requirements.txt逐项核对,别让依赖问题混到后面排查流程里。
注意:requirements.txt只写了直接依赖,没有版本锁的库装出来可能是新版。Django项目对版本很敏感,如果一个zip里Django 3.2对应的代码用到url()函数,而你装了Django 4.x,migrate阶段就会报错。碰到这种情况先看代码里是否用了旧API,再决定降版本还是改代码。
2.3 数据库迁移与超级用户:让Django自己建出全套表结构
依赖装完,接下来做的事是让Django把数据库表建出来。Django的ORM把模型定义在models.py里,migrate命令根据模型自动建表、改表。初始化入口是settings.py里的INSTALLED_APPS——里面自带admin、auth、sessions这些内置应用,首次migrate会把它们的表一起建好。
python manage.py migrate python manage.py createsuperuser python manage.py checkmigrate会输出一串「Applying admin.0001_initial... OK」,看到OK就是成功。默认数据库是SQLite,文件叫db.sqlite3,好处是零配置、解压即用;缺点是并发写入能力弱,适合开发和几十人的内部平台。如果你要换成MySQL或PostgreSQL,得先改settings.py里的DATABASES配置,装对应的数据库驱动,然后在建表前把数据库手动创建好,之后再执行上面的命令。
createsuperuser会依次问你用户名、邮箱、密码。这个账号进的是Django自带的admin后台,运维平台最常见的权限管理入口就在这里。manage.py check是个经常被人忽略的命令,它在不跑服务的情况下检查项目配置有没有明显错误,我习惯每次改完settings.py都先跑一下,比直接启动再报错省时间多了。
2.4 启动与登录验证:runserver起来后先测这三件事
一切就绪后,启动开发服务器:
python manage.py runserver 0.0.0.0:80000.0.0.0表示监听所有网卡,这样局域网里其他机器也能通过http://你的IP:8000访问。如果只想本机调试,用127.0.0.1:8000更安全。
起来后浏览器访问http://127.0.0.1:8000/admin/,用刚才的超级用户登录。前几分钟别急着点功能,先测三件事:
第一,admin页面能否正常加分页显示,说明静态文件和服务没白屏。第二,点进一个资产列表或任务列表,随便点一个查看详情,确认数据库里的数据能被读出来。第三,盯着runserver的日志看有没有红色报错,尤其是404和500。本地跑不起来的项目,直接部署到生产环境只会更惨。
如果登录后跳转卡住,或者静态资源加载不出来,先看这两处:settings.py里DEBUG = True时Django自己处理静态文件;关闭DEBUG后就必须靠Nginx或whitenoise来管静态文件。开发阶段保持DEBUG=True,登录页能正常显示一般就没问题。
3. 拆解Django运维平台的核心模块:资产管理、任务执行与权限控制的代码路径
3.1 后台自带的管理能力:为什么先白拿一套Admin
跑通后,很多人第一反应是「这界面真朴素」。但朴素不代表功能弱。Django自带的admin后台,本质上是一套不需要你写页面就能增删改查的管理系统。运维平台最需要的资产管理、账号维护、任务记录查询,直接注册进admin就能用。
最常见的做法是在每个app的admin.py里注册模型:
# assets/admin.py from django.contrib import admin from .models import Asset, Task @admin.register(Asset) class AssetAdmin(admin.ModelAdmin): list_display = ("hostname", "ip", "status", "updated_at") search_fields = ("hostname", "ip") admin.site.register(Task)list_display决定列表页显示哪些列,search_fields提供右上角的搜索框。对Asset这种数量可能上千的模型,这两个参数几乎是必须的,不然找一台机器要靠翻页,体验很差。
对自动化运维平台来说,admin的价值不只是「能用」,它还是开发的脚手架:你的自定义页面还没写好的时候,先用admin把数据维护起来,业务数据先流动起来,再慢慢补正式界面。这也是Django做内部系统最大的优势——先白拿一套完整后台,而不是从空页面开始画。
3.2 资产与任务的ORM模型:表结构里藏着平台的数据流
拆解这类平台,最先看的是models.py。运维平台最核心的两张表,一张记录「我能连哪些机器」,一张记录「我在机器上跑了什么」。这两张表通常是这样的关系:
# assets/models.py from django.db import models class Asset(models.Model): hostname = models.CharField(max_length=64, unique=True, verbose_name="主机名") ip = models.GenericIPAddressField(verbose_name="IP地址") ssh_port = models.IntegerField(default=22, verbose_name="SSH端口") account = models.CharField(max_length=32, default="root", verbose_name="登录账号") status = models.CharField(max_length=16, choices=( ("online", "在线"), ("offline", "离线")), default="online") created_at = models.DateTimeField(auto_now_add=True) class Task(models.Model): name = models.CharField(max_length=128, verbose_name="任务名") asset = models.ForeignKey(Asset, on_delete=models.CASCADE, related_name="tasks", verbose_name="目标资产") command = models.TextField(verbose_name="命令内容") status = models.CharField(max_length=16, choices=( ("pending", "待执行"), ("running", "执行中"), ("success", "成功"), ("failed", "失败")), default="pending") output = models.TextField(blank=True, verbose_name="执行输出") created_at = models.DateTimeField(auto_now_add=True)ForeignKey把任务挂到资产上,on_delete=models.CASCADE表示资产被删,它名下的任务记录也被删。这里要慎重:资产误删连带任务记录全没,如果有审计需求,应该把on_delete改成PROTECT,或者干脆留一个is_deleted软删除字段。
这两张表已经足够说明平台的数据流:先录入资产,再创建任务,任务选择目标资产,执行后把结果写回output和status。你在界面上看到的资产列表和任务列表,底层就是这两个模型的查询。理解了这个数据流,后面加任何功能都不会跑偏。
3.3 任务执行的两种路线:subprocess直跑与Celery异步的取舍
自动化运维平台的核心功能是执行任务。最常见的实现有两种,区别很大。
第一种,直接在Django视图里用subprocess跑命令:
# tasks/services.py import subprocess def run_command(cmd, timeout=30): try: result = subprocess.run( cmd, shell=True, capture_output=True, text=True, timeout=timeout ) return result.returncode, result.stdout, result.stderr except subprocess.TimeoutExpired: return 124, "", "command timed out"这样写简单直接,短命令没问题,但有两个隐患。一是shell=True有命令注入风险:如果cmd是从页面输入的,用户拼一个; rm -rf xx进去,后果自负。二是在本机执行命令,而不是登录到资产服务器上执行,很多场景不适用。更合理的做法是用paramiko的SSHClient去目标机器上执行,或者用Fabric/Ansible这类封装好的库。
第二种是把任务丢给Celery异步执行。Django请求-响应模型里,视图函数必须等任务跑完再返回,而一条命令可能跑几分钟,用户会一直转圈。先配好Redis,再定义任务:
# tasks/tasks.py from celery import shared_task from .services import run_command @shared_task def run_task(task_id): task = Task.objects.get(id=task_id) task.status = "running" task.save() code, stdout, stderr = run_command(task.command, timeout=60) task.output = stdout + stderr task.status = "success" if code == 0 else "failed" task.save()视图里run_task.delay(task_id)就返回了,剩下的在Celery worker里慢慢跑。这个改动让页面不再卡死,也为批量执行、定时巡检留了口子。代价是多维护一个worker进程和一个Redis实例。对自动化运维平台,我认为这个代价必须付,subprocess直跑只适合内部验证。
3.4 权限与审计:Django Auth之外还要补什么
Django自带一套完整的用户体系:User模型、登录会话、组、权限。运维平台默认就该用这套,而不是自己建user表再踩一遍认证坑。
登录逻辑最常见的写法:
# users/views.py from django.contrib.auth import login, authenticate def user_login(request): if request.method == "POST": username = request.POST.get("username") password = request.POST.get("password") user = authenticate(request, username=username, password=password) if user: login(request, user) # 登录成功后,session_id 会写入 cookie return redirect("/admin/") return render(request, "users/login.html")login调用后,Django会把session_id种到浏览器cookie里,请求头带上这个cookie,后端就能识别是谁。如果你做的是前后端分离,前端要保存一个token,常见做法是用djangorestframework自带的TokenAuthentication,登录接口返回一个token字符串,前端每次请求在Header里带Authorization: Token xxxx。这与cookie设置token是两套体系,别混着用。
权限之外,运维平台必须补审计。谁在什么时间对哪台机器执行了什么命令,这条记录比命令本身的输出更重要。做法很简单:加一个OperationLog模型,在任务执行入口记录request.user、任务ID、时间戳。没有审计的运维平台,出了事查不到人,比没有平台还麻烦。
4. 改成你自己的运维平台:新增资产字段、部署通知与WebSocket实时推送的落地改法
4.1 用startapp扩展新模块:给平台加一块「发布记录」的完整流程
拿到平台之后,很快会遇到第一个需求:「给我加一个发布记录,记录每次上线了什么版本。」 这在Django里是一个标准的app扩展流程:
python manage.py startapp publish命令会生成publish目录,里面有models.py、views.py、admin.py等文件。但注意,生成app后Django还不知道它的存在,必须手动在settings.py的INSTALLED_APPS里加一行:
# settings.py INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", # ... "assets", "tasks", "publish", # 新增 ]然后写模型、做迁移:
python manage.py makemigrations publish python manage.py migratemakemigrations根据models.py里的变化生成迁移文件,migrate把变更落到数据库。这个两步走是Django的逻辑:先记录「要改什么」,再执行「怎么改」。如果你看到同事只在models.py里加字段而不跑migrate,数据库里永远不会有这个字段,页面一访问就报「no such column」。
之后在publish/admin.py里注册,admin后台就出现「发布记录」菜单了。整个流程走一遍,你会理解Django内部为什么把「改表结构」做得这么重——它拿一套带历史的迁移记录,把数据库结构变更纳入了版本管理,生产环境升级时按迁移文件顺序执行,不会因为谁手工改了一列就崩掉。
4.2 通知接入:把执行结果推给钉钉/邮件的最小代码
任务执行完,用户不可能一直盯着列表刷新。最常见的自动化工况是:任务失败时把结果推送出来。钉钉群机器人是最省事的通道,只需要一个webhook地址和一个requests.post:
# common/notify.py import requests def send_dingtalk(webhook_url, content): payload = { "msgtype": "text", "text": {"content": content}, } try: resp = requests.post(webhook_url, json=payload, timeout=5) return resp.status_code == 200 except requests.exceptions.RequestException: return False使用它时,在Celery任务里失败时调用:
from common.notify import send_dingtalk if task.status == "failed": send_dingtalk( "https://oapi.dingtalk.com/robot/send?access_token=xxx", f"任务 {task.name} 执行失败,资产 {task.asset.hostname}" )注意webhook地址里含有token,千万别写进git仓库,建议放到settings.py里的环境变量或者本地配置文件中。timeout=5是必要的,通知通道挂了不能反过来拖垮任务执行流程。邮件同理,用Django内置的django.core.mail.send_mail,配置好EMAIL_HOST、EMAIL_HOST_USER、EMAIL_HOST_PASSWORD后调用即可。这一步的收益立竿见影:任务失败后不用盯屏,群消息会炸出来。
4.3 WebSocket实时推送:后台有数据前端自动弹的任务状态
很多运维平台做到后面,会希望前端页面不要靠手动刷新就能看到任务状态变化。Django默认的HTTP是请求-响应模式,服务端没法主动往浏览器推数据。标准的补法是引入Channels,让Django具备WebSocket能力。
首先把channels加进requirements.txt并安装,然后在asgi.py里配置:
# ops_platform/asgi.py import os from channels.routing import ProtocolTypeRouter, URLRouter from django.core.asgi import get_asgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ops_platform.settings") application = ProtocolTypeRouter({ "http": get_asgi_application(), "websocket": URLRouter([]), # 后面挂路由 })再加一个消费者,把任务状态推送出去:
# tasks/consumers.py import json from channels.generic.websocket import AsyncWebsocketConsumer class TaskConsumer(AsyncWebsocketConsumer): async def connect(self): self.group_name = "task_status" await self.channel_layer.group_add(self.group_name, self.channel_name) await self.accept() async def task_update(self, event): await self.send(text_data=json.dumps({ "task_id": event["task_id"], "status": event["status"], }))然后在Celery任务状态变更时调用channel_layer.group_send,所有连到task_status的前端页面就会收到消息。前端用浏览器原生WebSocket就可以:
const ws = new WebSocket("ws://127.0.0.1:8000/ws/tasks/"); ws.onmessage = (event) => { const data = JSON.parse(event.data); updateTaskStatus(data.task_id, data.status); };这个改动的核心思路是:把状态更新从「前端问服务端要」改成「服务端主动推给前端」,响应速度从轮询间隔变成毫秒级。代价是生产环境要加一个支持WebSocket的服务器,比如Daphne或Uvicorn,Gunicorn本身不处理WebSocket。本地开发测试时,runserver会自动带上Channels的开发server,所以开发阶段感觉不到这个差别,上线前一定要把部署命令换成支持WebSocket的ASGI server。
4.4 调整settings.py的七处必改配置
不管拿到的zip长什么样,settings.py始终是第一个需要通读的文件。我按经验列一份必改清单:
| 配置项 | 必改原因 | 本地建议值 |
|---|---|---|
| ALLOWED_HOSTS | 不配置,局域网访问直接DisallowedHost | ["*"]开发;生产写域名 |
| DEBUG | 生产环境True会暴露完整报错栈 | 开发True,部署后False |
| TIME_ZONE | 影响任务记录和定时任务的时间 | Asia/Shanghai |
| LANGUAGE_CODE | admin界面语言 | zh-hans |
| DATABASES | SQLite换MySQL/PostgreSQL的入口 | 默认SQLite先跑通 |
| STATIC_ROOT | 关DEBUG后静态文件收集目录 | 部署时collectstatic用 |
| INSTALLED_APPS | 加不加新app、第三方组件都在这里 | 按业务追加 |
还有一处容易被忽略:如果用了Celery,CELERY_TIMEZONE要单独设,Django改了TIME_ZONE不会自动同步到Celery。定时任务如果时间不对,十有八九是这里没设成Asia/Shanghai。每次改settings.py,保存后跑一遍python manage.py check,比等到启动时被报错教育老实得多。
5. 上线部署避坑:5个让Django运维平台翻车的常见问题与排查方案
5.1 开发服务器能跑,局域网访问不了:ALLOWED_HOSTS与端口之谜
现象:本地runserver 127.0.0.1:8000一切正常,换成局域网IPhttp://192.168.1.20:8000访问,浏览器显示DisallowedHost,或干脆连接超时。
原因:Django的安全机制要求请求Header里的Host必须在ALLOWED_HOSTS里,默认只有localhost和127.0.0.1,你拿局域网IP访问自然被拒;如果是连接超时,那就是监听地址或防火墙挡住了。
解决:先把settings.py里ALLOWED_HOSTS加上本机局域网IP或直接["*"];再把runserver启动参数改成0.0.0.0:8000,只监听127.0.0.1的话外部网络根本连不进来。最后确认服务器防火墙放行了8000端口。按这个顺序排查,95%的局域网访问问题都在这三处。
5.2 migrate总是报错:迁移历史与脏库的教训
现象:执行python manage.py migrate时报InconsistentMigrationHistory,或者提示某张表已存在。
原因:最常见是数据库目录里已经有一个旧版db.sqlite3,里面残留了旧结构的表;或者项目交付时把别人的迁移文件带了过来,而数据库里没有对应的迁移历史记录。Django的migrate按迁移文件逐一执行,发现表存在但迁移记录里没有这一条,就会报不一致。
解决:开发环境,最省事的方案是把db.sqlite3改名备份后重新migrate——这是后悔药,但前提是你确认没有需要保留的数据。生产环境千万别直接删库,先python manage.py showmigrations看哪些标记为[X]、哪些没有,把缺失或多余的迁移文件整理干净,再执行migrate --fake或手工补表。我在生产上这么操作时,每次都先备份数据库文件再动手,备份文件名带上日期,至少能回到改之前的状态。
5.3 任务执行页面转圈卡死:subprocess没有超时把请求线程堵住
现象:点「执行任务」后,浏览器一直转圈,等了几分钟也没返回,再点其他页面也卡住。
原因:如果任务执行用的是一开始那种subprocess.run,且没有timeout参数,一条卡死的命令就会一直占用视图线程。Django开发服务器默认是单进程多线程,其中一个线程被占死,其他请求排队,表现就是整个平台假死。
解决:给subprocess加硬性超时——timeout=30,加异常处理,超时就往任务表里写失败状态;再进一步把任务执行扔给Celery,视图只提交任务就返回。这是从根上解决问题:任何运维命令都不该占用Web请求线程。上线后如果还有人用subprocess直跑长任务,我建议直接code review拦下来。
5.4 Celery任务迟迟不执行:时区、队列与worker没启动的三重排查
现象:页面提交任务后状态一直是pending,数据库里能看到任务,但Celery就是不消费。
原因:第一反应不是代码问题,而是Celery worker根本没启动。很多人只启动了runserver,却忘了执行celery -A ops_platform worker --loglevel=info。第二个常见原因是CELERY_TIMEZONE没设成Asia/Shanghai,定时任务按UTC算,时间总差8小时。第三个是Redis没启动,worker启动时报connection refused,任务根本进不了队列。
解决:先确认Redis在跑,再启动worker,然后看worker日志有没有「Received task」字样。定时任务如果依赖celery beat,那要单独启动调度器,worker和beat是两个进程。排查顺序我建议是:进程在不在 -> 队列通不通 -> 时区对不对。走一遍,九成问题都在这三关上。
5.5 POST请求被403拦下:CSRF与Token设置的两个连招
现象:前端表单提交POST请求,返回403 CSRF verification failed;用POSTMAN调接口也一样。
原因:Django默认开启CSRF中间件,要求所有POST、PUT、DELETE请求都携带CSRF token,否则拒绝。浏览器里模板表单如果忘了写{% csrf_token %}就会踩这个;前后端分离的场景,前端没从cookie里取csrftoken塞进请求头,也会被拦。
解决:模板渲染的表单,form里加{% csrf_token %}一行即可。前后端分离,在JavaScript里读cookie里的csrftoken,放进请求头X-CSRFToken,同时保证CSRF_COOKIE_NAME和前端读取的名字一致。如果用的是djangorestframework,通常配合TokenAuthentication:登录换token,之后的请求头带Authorization: Token xxx,绕过CSRF校验。这里要记住,cookie设置token和CSRF token是两个不同的东西,一个管身份,一个管防伪造,别混用。
6. 让平台真正扛起日常:正确性验证、巡检一致性比对与一组收尾技巧
把平台部署上线只是开始,真正让它扛起日常,要验证两件事:平台看到的和服务器真实情况一不一致,以及核心任务执行结果是否可复现。
验证方法很简单。先在平台上对一台机器执行hostname && date,然后自己SSH上去跑同样的命令,对比输出是否一致。这个动作能同时验证paramiko连接、命令执行、结果回写这三段链路。我还会定期抽查任务表里的output字段,看有没有被截断或只能拿到半截内容——这是paramiko读取超时最典型的症状。
巡检一致性比对是运维平台的灵魂:平台说这个资产在线,真的在线吗?写一个独立于Django的脚本,把平台数据库里的资产IP导出来,批量ping或SSH握手,再和platform里的状态字段做diff,不一致的列成清单。这个脚本不值得天天跑,一周一次就够了,但每次跑都能揪出几个僵尸资产。我在生产上还养成了一个习惯:每次改完settings.py,先manage.py check,再跑一遍上述对比脚本,双重确认后再让平台接真实任务。
生产部署不要用runserver。常见做法是gunicorn ops_platform.wsgi:application -w 4 -b 0.0.0.0:8000,如果用了WebSocket就换成Daphne。再前面挂Nginx处理静态文件和反向代理。数据库记得定时备份,SQLite直接备份db.sqlite3文件,PostgreSQL用pg_dump,备份日期留7天的轮转,这是遇到误删后唯一的后悔药。
回看整个平台,Django这套组合在中小团队里的性价比确实很高:一个后端开发加一个运维,就能把资产管理、任务执行、通知、审计串起来。成本不在写代码,而在把运维规范拆成Django的模型和流程。希望这篇笔记能帮你在部署和改造的路上少踩几个坑。
本文还有配套的精品资源,点击获取