☰
Nautobot 网络自动化实战:从表格台账到单一可信数据源
2026/11/2 5:00:17 网站建设 项目流程

1. 我为什么把网络台账从表格搬到了这个平台

第一次认真研究 Nautobot,起因很朴素:手上那套设备台账表格又在同步时打架了。三个人同时改,一份是上周的,一份是上周上加一个补丁的,还有一份是某次割接临时导出后忘了合并的。谁都说自己那份是最新的,结果排查一个 IP 冲突花了整整半天。Nautobot 就是在这种场景下进入视野的——它是一款开源、可自托管的网络自动化与运营管理平台,核心定位是"网络数据的单一可信来源"加"可执行的自动化中枢"。说人话,它把设备、接口、IP、VLAN、机架、电路这些散落在表格、文档、脑子里面的东西,收进一个带版本、带权限、带 API 的数据库里,然后在上面长出一层能跑脚本、能渲染配置、能对接外部系统的东西。

它能干的事大致分三块。第一块是建账:把网络资产的两百多个字段结构化,谁改的、什么时候改的、改成什么都能翻出来。第二块是取数:REST 和 GraphQL 两套接口,Ansible、Python 脚本、监控系统、CMDB 都能直接拉数据,不用再找人要 Excel。第三块是干活:内置的 Jobs 机制让你把 Python 脚本挂上去点按钮执行,配置模板能根据数据自动渲染出设备配置,插件还能把它扩展成合规检查、配置备份、工单联动的工作台。

适合谁来参考?如果手上有几十台以上网络设备、有跨团队共享台账的痛点、团队里至少有一两个人能写点 Python、又不想为商业方案掏授权费,那这套东西值得认真评估。如果只是十几台设备、一个人维护、从不做变更审计,那用表格或者轻量工具反而更省事——这话我放在前面,免得后面看完觉得被"安利"了。

1.1 从一堆表格说起:真实痛点到底在哪

网络台账用表格管,前半年通常很舒服,一年后开始变形。最常见的三个症状:字段靠约定不靠约束,同一个型号有人写"华为S5720"有人写"S5720-28X",做统计时得先做文本清洗;关联关系靠手工维护,设备换了接口,IP 表、拓扑图、监控配置三处都要跟着改,漏一处就埋雷;变更没有留痕,三个月后有人问"这个 VLAN 是谁加的",只能翻聊天记录。

Nautobot 对这些症状的解法不是"给你一个更好的表格",而是换了一套底层逻辑:数据以对象形式存储,设备是一个对象,接口是挂在设备下的对象,IP 地址是挂在接口上的对象,对象之间是数据库层面的外键关系,改一处,关联处自动跟着变。同时每次保存都会生成一条变更记录,谁在什么时间把status从 Active 改成了 Offline,一清二楚。这套东西不新鲜,本质是把 CMDB 的思路搬到了网络域,但它的价值在网络这种"设备多、关系密、变更频繁"的场景里被放大了。

还有一个容易被忽略的点:它让"数据"变成了"可编程的输入"。表格再漂亮,程序也不好读;而一个带 GraphQL 接口的数据源,意味着你的巡检脚本、配置生成器、报表工具全都可以直接消费同一份数据。这是从"人读的文档"到"机器读的数据源"的跨越,也是后面所有自动化的地基。

1.2 它到底解决了什么问题:三个层次的收益

第一层是一致性。所有系统都从同一个接口取数,监控里看到的设备名和配置里生成的设备名必然一致,因为它们是同一个字段。这一层收益最快显现,通常上线两三周就能感觉到"不用再对账了"。

第二层是可追溯。变更日志加上 Job 的执行记录,让"谁在什么时候干了什么"变成可查询的事实。做审计、做故障复盘的时候,这个价值远超预期。我见过不止一次,故障定位卡在"这个配置什么时候变的"上,有日志和没日志是两种排查效率。

第三层是自动化编排。当数据足够干净,配置渲染、批量巡检、合规校验这些事才谈得上"跑起来"。数据脏的时候写自动化脚本,等于给流沙地基建房子。

影响范围也要说清楚:这套东西落地,改变的往往不只是网络组的工作方式,还会牵动监控、安全、资产、运维值班几个团队。因为一旦它成为权威数据源,别人就会来对接,对接就意味着接口稳定性、字段语义、权限边界这些事要提前约定。所以真要上,别当成"装个软件",当成"立一条数据治理的规矩"更合适。

1.3 适合谁、不适合谁:一份不太客气的评估清单

适合的情况:设备规模在几十台以上;有至少两人以上共同维护网络数据;存在跨系统取数需求(监控、自动化、报表);团队接受"改数据要走流程"这件事;有人能读懂 Python 报错。

不太适合的情况:设备规模很小且变动极少;团队里没人愿意维护一个服务;对"数据必须先录入才能用"这种前置成本非常抵触;需要的是实时拓扑发现而不是人工维护的台账。

还有一类是"可以先不上,但可以先试"的:把 Nautobot 当只读的数据源用,先做只读对接,让它从现有系统同步数据进来,跑一段时间看看数据质量,再决定要不要把它扶正成主数据源。这个路径风险低,是我比较推荐的起步方式。

2. 平台整体设计与选型思路拆解

理解一个平台,先看它的骨架。Nautobot 的技术栈很朴素:Python + Django + PostgreSQL + Redis,前端是 Django 模板加少量 JS。这个组合不炫技,但极其稳妥——Django 的 ORM 让数据建模变得像写类定义一样自然,PostgreSQL 的 JSONB 字段撑起了自定义字段的灵活性,Redis 同时承担缓存和任务队列。整套东西跑起来,一台 4 核 8G 的机器足够支撑几千台设备的规模。

这个选型的深层逻辑是可运维性优先于性能极限。它不追求每秒十万次查询,追求的是"一个懂 Python 的运维能看懂、能改、能扩展"。插件用 Python 写,Job 用 Python 写,连数据导入都可以用 Django shell 直接操作 ORM。这种"不设墙"的设计,对中小团队极其友好,因为你不需要专门养一个前端团队。

另一个关键设计是插件化。核心只保留最通用的数据模型(设备、机架、IP、VLAN、租户、集群),行业特化的东西全部推到插件层。这样做的好处是核心足够稳定,坏处是功能边界要靠插件补齐,装什么插件、插件之间是否冲突,需要点判断力。

2.1 数据模型:一切从"源真相"开始

Nautobot 的核心数据模型围绕几个层次展开,理解这个层次结构,后面建什么都不会乱:

层级主要对象说明
物理位置Location、Rack、RackGroup站点、机房、机架、机架组,支持多层嵌套
设备本体Manufacturer、DeviceType、Device、Module厂商、型号、实例、模块(子卡/光模块)
连接关系Interface、Cable、ConsolePort、PowerPort接口、线缆、 console 口、电源口
网络标识VLAN、VRF、Prefix、IPAddressVLAN、路由表、网段、IP
逻辑归属Tenant、TenantGroup租户,用来做业务归属划分
服务对象Cluster、VirtualMachine、Service虚拟化集群、虚机、服务

这套模型的精髓在于每一层都有明确的"父"。IP 一定挂在接口上,接口一定挂在设备上,设备一定在某个位置下。这种强约束带来的直接好处是:你没法录一个"孤儿 IP",也没法录一个不属于任何站点的设备。很多表格台账的脏数据,根源就是缺少这种约束。

还有一个概念要特别拎出来:Status 和 Role。Nautobot 把"状态"做成了一等公民,Active、Planned、Offline、Decommissioning 这些状态是独立对象,可以自定义、可以配颜色、可以控制哪些状态算"在用"。Role 则是设备的角色标签,比如接入、汇聚、核心、防火墙。这两个字段看着不起眼,做统计报表和筛选的时候是主力。

自定义字段(Custom Field)是另一条腿。核心模型不可能覆盖所有场景,比如"设备维保到期时间""机房进线运营商"这类字段,直接用自定义字段挂上去,支持文本、整数、布尔、日期、下拉几种类型。需要注意:自定义字段虽然方便,但不要滥用,超过二十个自定义字段之后,录入界面会变得很长,而且查询效率会下降,这时候该考虑写插件了。

2.2 为什么是 Django + PostgreSQL:一次务实的取舍

Django 带来的最大红利是ORM 即建模语言。定义一个设备型号,本质是定义一个 Python 类,字段类型、外键关系、默认值、校验规则全在里面。这意味着插件开发者的上手成本极低——会写 Django model 就会扩展 Nautobot。

PostgreSQL 的选择更关键。它的 JSONB 类型让自定义字段不需要改表结构,一个_custom_field_data字段就能装下所有动态属性,查询时还能用 GIN 索引加速。如果换成 MySQL,这套机制实现起来会别扭得多。另外 PostgreSQL 的全文检索、数组类型、CTE 在数据量大了之后都能派上用场。

Redis 在这套体系里承担两个角色:缓存和 Celery 的 broker。这个设计要注意一个坑——两个用途最好用不同的 database index,否则一次缓存清理可能把队列里没消费的任务一起干掉。这属于配置细节,但踩过一次会记很久。

至于为什么不用更"现代"的技术栈(比如 FastAPI + Vue 前后端分离),我的理解是:这类平台的核心用户是运维工程师,他们需要的是"看得懂源码、改得动逻辑",而不是"前端交互丝滑"。Django 的服务端渲染在这个场景下反而降低了维护成本——出问题的时候,报错栈是完整的 Python 调用链,不用跨语言排查。

2.3 插件化架构的扩展边界在哪

插件(在新版本里被称作 Apps)是这个平台的生命力所在。核心提供骨架,插件提供血肉。常见的插件方向包括:配置备份与差异对比、配置模板渲染与合规校验、设备生命周期管理、防火墙策略管理、IP 地址扫描与发现、工单系统对接。

插件的能力边界,主要看它能不能做三件事:注册新的数据模型、挂新的菜单和视图、注册新的 Job。这三件事覆盖了绝大多数扩展需求。但插件也有它的天花板:插件之间的依赖关系由开发者自己维护,装多了容易出现版本冲突;插件升级往往跟着核心版本走,核心大版本升级时插件没跟上,就会卡住。

我的经验是插件要少而精。必装的一般就两三个:配置管理方向一个、数据导入导出方向一个。剩下的按需再装。装之前先看它的活跃度和最近提交时间,一个一年没更新的插件,在新核心版本上大概率跑不起来。还有个小技巧:装插件前先在测试环境跑一次nautobot-server post_upgrade,能提前暴露迁移冲突。

2.4 部署形态选型:容器还是裸机

这个选择没有标准答案,取决于你团队的运维习惯。

容器方式的优势是一致性。镜像里 Python 版本、依赖包、系统库都是固定的,不会出现"我这能跑你那不能跑"。官方维护的 docker compose 编排把 Web、Worker、Redis、PostgreSQL 全打包了,起一套环境通常十几分钟。劣势是调试稍麻烦,出问题要进容器看日志,而且数据卷的备份策略要提前设计好。

裸机(或虚拟机)方式的优势是可控性和性能。所有路径都看得见,日志直接落盘,调优参数想改就改。劣势是依赖管理麻烦,Python 版本、系统库、PostgreSQL 版本都要自己对齐,升级的时候容易出岔子。

一个折中方案是:开发和验证用容器,生产用裸机加 systemd 管理。这样既享受了容器的快速迭代,又保留了生产的可控性。无论哪种方式,有几条底线必须守住:数据库必须单独备份;SECRET_KEY必须持久化且不进版本库;静态文件和媒体文件要独立挂载;Redis 要配持久化,否则重启后排队中的任务全丢。

3. 核心能力逐个拆解与实操要点

这一章拆四块最能体现平台价值的能力:数据建模落地、接口查询、Jobs 执行、配置渲染。这四块串起来,就是一条从"录数据"到"用数据"的完整链路。

3.1 数据建模落地:设备、接口、IP、VLAN 的正确录法

新手最容易犯的错是先录设备再补关系,结果录到一半发现型号没建、位置没建,来回返工。正确的顺序是自顶向下:

  1. 建 Location(站点/机房/机架位),按你的实际层级来,一般两到三层就够
  2. 建 Manufacturer 和 DeviceType,型号的接口模板在这里定义,一次定义终身复用
  3. 建 Rack,把机架挂到 Location 下
  4. 建 Device,选型号、选机架、填位置和状态
  5. 建 VLAN 和 Prefix,按站点或业务划分
  6. 建 IPAddress,挂到接口上,标记为主 IP 的通常是管理地址
  7. 建 Cable,把设备之间的物理连接录进去

第三步到第四步之间有个省力技巧:DeviceType 里预先定义接口模板。比如一台 48 口交换机,在型号里把 48 个千兆口 + 4 个万兆口一次性定义好,建实例的时候接口自动生成,不用一个个手敲。这个功能对批量录入的价值极高,一台设备能省五分钟,两百台就是十几个小时。

另一个技巧是导入用 CSV 而不是手工点。平台自带导入功能,支持 CSV 和 JSON,导入前先下载模板看一眼字段名,字段名必须完全匹配。导入顺序同样要遵守上面的依赖关系,父对象不存在会直接报错。CSV 文件建议用 UTF-8 编码保存,带 BOM 的文件在某些版本上会读出乱码——这个坑很经典,中文环境下尤其容易遇到。

注意:批量导入前一定要在测试环境先跑一遍。导入是不可逆的批量写操作,导错了要一条条删,比录入还费时间。

3.2 GraphQL 与 REST:什么场景用哪个

平台同时提供 REST 和 GraphQL 两套接口,这不是重复建设,是两种不同的取数哲学。

REST 适合"我知道我要什么,一次拿一类"。比如拉全部设备列表,GET /api/dcim/devices/?limit=0,返回一个扁平数组,分页、过滤、排序参数都很直观。写脚本的时候用 REST 最省心,因为结构固定,一眼能看懂。

GraphQL 适合"我要一个关联视图,一次拿一串"。比如"给我这台设备的名字、型号、主 IP、所有接口名",用 REST 得请求三次再在代码里拼,用 GraphQL 一次就够:

{ devices(name: "core-sw-01") { name status { name } device_type { manufacturer { name } model } location { name } primary_ip4 { address } interfaces { name type enabled ip_addresses { address } } } }

这段查询的价值在于减少往返次数。网络自动化的典型场景就是"拿一批设备的关联数据",用 REST 很容易写成 N+1 查询,两百台设备发上千个请求,慢得让人怀疑人生。GraphQL 一次请求拿完,性能差距是数量级的。

但 GraphQL 也有它的坑。第一,嵌套层级不能太深,一次查三层以上关联,数据库那边的 JOIN 会很重,容易超时;第二,没有天然的限流,一条复杂查询可能拖垮数据库,生产环境建议在反向代理层做查询深度和复杂度的限制;第三,权限控制的粒度要提前设计,GraphQL 是按字段返回的,权限没配好可能把不该看的字段暴露出去。

我的实际用法是:脚本化、单对象的操作用 REST;报表、批量视图、需要多表关联的用 GraphQL。两条路都通,选顺手的就行。

3.3 Jobs:把脚本变成可追溯的作业

Jobs 是这套平台最有"运维味"的功能。你可以把一段 Python 脚本扔进去,它会自动生成一个带表单的页面,点一下就跑,跑完有日志、有状态、有执行人、有耗时。这事听着简单,但它解决了一个老大难问题:脚本执行的黑盒问题。

以前写个批量改描述的小脚本,谁跑的、什么时候跑的、改了哪些设备、跑失败了几台,全靠人记。Jobs 把这些全变成结构化记录,失败了还能重跑。

一个最小可用的 Job 长这样:

from nautobot.extras.jobs import Job, StringVar from nautobot.dcim.models import Device class DeviceInterfaceReport(Job): """按设备名统计接口清单""" device_name = StringVar(description="设备名称,需与平台内名称完全一致") class Meta: name = "设备接口巡检" description = "输入设备名,输出该设备全部接口的类型与启用状态" def run(self, data, commit): name = data["device_name"] try: device = Device.objects.get(name=name) except Device.DoesNotExist: self.log_failure(f"设备 {name} 不存在,检查名称是否精确匹配") return count = 0 for intf in device.interfaces.all(): self.log_info(f"{intf.name} | {intf.type} | enabled={intf.enabled}") count += 1 self.log_success(f"{name} 共 {count} 个接口")

几个实操要点:

commit参数是安全阀。默认commit=False时 Job 应该是只读的,涉及写操作要显式检查if not commit: self.log_warning("干跑模式,不写库"); return。这个约定非常重要,它让"先看结果再决定要不要真执行"成为可能。

日志级别要对。log_info打过程,log_success打结果,log_failure打失败,log_warning打提醒。一批设备里部分失败,用log_failure会把整个 Job 标记成失败,但实际可能是预期内的;这种场景用log_warning更合适。

Job 的执行依赖 Worker。Web 进程只负责提交任务,真正跑的是 Celery worker。如果点了没反应、状态一直停在 Pending,九成是 worker 没起或队列名配错了。这个排查我会放在后面细讲。

大任务要分片。一个 Job 遍历五千台设备、每台发一次 SSH,跑几个小时,中途断一次全白干。正确做法是用run里再拆子任务,或者干脆拆成多个 Job 按站点跑。

3.4 配置上下文与模板渲染:从数据到配置

把数据变成配置,是这个平台最实用的闭环。核心机制叫配置上下文(Config Context),本质是给对象挂一段结构化数据,支持按位置、角色、设备、租户多个维度做匹配,权重高的覆盖权重低的。

举个实际例子。你想给所有"北京机房的核心交换机"下发统一的 NTP 和 SNMP 配置,可以建一个 Config Context:

{ "ntp_servers": ["10.0.0.1", "10.0.0.2"], "snmp": { "location": "IDC-A", "version": "v2c" }, "syslog": ["10.0.0.10"] }

挂载规则设成"Location 属于 IDC-A 且 Role 是核心",保存后所有匹配的设备都能读到这段数据。设备本身不用改任何字段,规则一变,全量跟着变。这就是"数据驱动配置"的核心——配置的差异用规则表达,而不是用复制粘贴表达。

模板渲染则是把数据套进 Jinja2 模板:

hostname {{ obj.name }} ! {% for server in obj.get_config_context().ntp_servers %} ntp server {{ server }} {% endfor %} ! {% for intf in obj.interfaces.all() %} interface {{ intf.name }} description {{ intf.description | default('') }} {% if intf.enabled %} no shutdown{% else %} shutdown{% endif %} {% endfor %}

模板里能拿到obj(设备对象)以及通过get_config_context()取到的上下文字典,接口、IP、VLAN 全都能遍历。这套东西跑通之后,新增一台设备只需要录数据,配置自动生成,人工只做审核和下发。

需要提醒的是模板的边界。模板只做"数据到文本"的映射,不要在里面写复杂的业务判断,比如"如果这个接口对端是防火墙就加某段配置"——这种逻辑应该提前算好写进上下文,让模板保持简单。模板一旦复杂到需要单元测试,就该换方案了。

3.5 Git 数据源与插件生态:让数据和代码一起管

平台支持把数据源指向 Git 仓库,Job、配置上下文、导出模板、甚至部分对象数据都可以从 Git 同步。这个能力的意义在于把配置管理纳入版本控制。模板改动走 PR、走评审、走合并,改坏了能回滚,这比在网页上直接编辑靠谱得多。

Git 数据源配置起来不复杂,填仓库地址、分支、凭证(通常是只读的部署密钥),同步后会扫描目录结构识别出 Job 和模板。要注意两点:一是同步是定时任务,不是实时的,改完代码要等下一次同步或手动触发;二是仓库结构要遵守约定,Job 必须放在指定的目录路径下,路径不对扫不出来,这个报错信息比较隐晦,容易卡住。

插件生态方面,选择时会踩的坑主要是版本匹配。插件的pyproject.toml里会声明兼容的核心版本范围,装之前先看一眼。装完必须跑数据库迁移,否则页面会报字段不存在的错。还有个小细节:插件的静态文件需要重新collectstatic,不做的结果是新插件的页面样式全丢,看起来像坏了一样,其实只是缺资源。

4. 从零搭一套可用环境:完整实操过程

这一章给两条路:容器快起和裸机部署。选哪条看你当下是想"先看看长什么样"还是"直接上生产"。

4.1 环境准备与依赖清单

先把依赖列清楚,避免边装边找。

组件版本要求说明
Python3.9 及以上建议 3.11,太新的版本可能插件不兼容
PostgreSQL12 及以上建议 14/15,字符集用 UTF8
Redis6 及以上建议 7,缓存和队列分库
系统库libpq、libxml2、libxslt、openssl编译依赖,缺了会报错
内存4G 起生产建议 8G 以上
磁盘40G 起主要被日志和备份占用

Python 版本这块要多说一句:不要盲目追新。某些系统库和插件在最新 Python 上还没适配,装上之后会在 import 阶段报错,排查起来很费劲。选一个主流版本(比如 3.11)并固定下来,比"用最新的"稳妥。

数据库初始化要做两件事:建库、建用户并授权。字符集必须是 UTF8,编码不对的话中文设备名会变问号,而且这个错误在后期才发现,修起来很麻烦。

sudo -u postgres psql CREATE DATABASE nautobot WITH ENCODING 'UTF8' LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8' TEMPLATE=template0; CREATE USER nautobot WITH PASSWORD '换成强密码'; GRANT ALL PRIVILEGES ON DATABASE nautobot TO nautobot; \q

4.2 容器方式:十几分钟看到界面

容器方式的步骤很固定:

# 从代码托管平台拉取官方编排文件 git clone <编排仓库地址> nautobot-docker cd nautobot-docker # 复制环境变量模板并修改 cp environments/creds.env.example environments/creds.env vi environments/creds.env # 启动 docker compose --profile=local up -d

creds.env里必须改的几项:NAUTOBOT_SECRET_KEY(生成一串 50 位以上的随机字符串)、NAUTOBOT_DB_PASSWORD、NAUTOBOT_REDIS_PASSWORD、NAUTOBOT_ALLOWED_HOSTS(填你的域名或 IP)、NAUTOBOT_SUPERUSER_PASSWORD。

生成SECRET_KEY的命令:

python3 -c "import secrets; print(secrets.token_urlsafe(50))"

启动之后,docker compose logs -f nautobot盯着看,出现Listening at: http://0.0.0.0:8080就说明 Web 起来了。首次启动会自动跑迁移和创建超级用户,这一步会花两三分钟,别急着刷页面。

有个细节:官方编排里有local和dev两种 profile,前者用本地数据库容器,后者适合挂载源码调试。生产用的话要另外配外部数据库,编排文件里的默认数据库容器不适合承载生产数据,因为容器重建的时候数据卷的管理容易出岔子。

4.3 裸机部署的完整命令序列

裸机部署步骤多,按顺序来:

# 1. 创建运行用户和目录 sudo useradd -r -s /bin/bash nautobot sudo mkdir -p /opt/nautobot /etc/nautobot /var/log/nautobot sudo chown -R nautobot:nautobot /opt/nautobot /etc/nautobot /var/log/nautobot # 2. 创建虚拟环境并安装 sudo -u nautobot python3 -m venv /opt/nautobot/venv sudo -u nautobot /opt/nautobot/venv/bin/pip install --upgrade pip sudo -u nautobot /opt/nautobot/venv/bin/pip install nautobot # 3. 生成配置文件 sudo -u nautobot /opt/nautobot/venv/bin/nautobot-server init # 4. 编辑配置 sudo vi /etc/nautobot/nautobot_config.py

配置文件里至少要改这几处:

ALLOWED_HOSTS = ["nautobot.example.internal", "10.0.0.20"] DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": "nautobot", "USER": "nautobot", "PASSWORD": "换成强密码", "HOST": "127.0.0.1", "PORT": "5432", "CONN_MAX_AGE": 300, } } REDIS_HOST = "127.0.0.1" REDIS_PORT = 6379 REDIS_PASSWORD = "换成强密码" # 缓存和队列分库,避免互相影响 CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/0", } } CELERY_BROKER_URL = f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/1" CELERY_RESULT_BACKEND = f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/2" TIME_ZONE = "Asia/Shanghai" SECRET_KEY = "从配置文件里读,不要硬编码进版本库"

CONN_MAX_AGE设成 300 秒是个实用参数,避免每个请求都重建数据库连接。TIME_ZONE设错会导致日志时间和 Job 执行时间对不上,排查问题时会误导判断。

接着跑初始化和迁移:

sudo -u nautobot /opt/nautobot/venv/bin/nautobot-server migrate sudo -u nautobot /opt/nautobot/venv/bin/nautobot-server collectstatic --no-input sudo -u nautobot /opt/nautobot/venv/bin/nautobot-server createsuperuser

最后是三个常驻服务:Web(用 gunicorn 或 uwsgi)、Worker、Scheduler。用 systemd 管理最省心,每个服务一个 unit 文件,配好Restart=always和日志输出。Worker 的启动参数要指定队列和并发数,机器核数少的话并发设 4 就够,设太高反而因为内存竞争变慢。

注意:Worker 和 Scheduler 必须是两个独立进程。Scheduler 只负责往队列里塞定时任务,不消费任务;如果只起了 Worker 没起 Scheduler,所有定时任务都不会触发,而且页面上看不出来异常。

4.4 数据导入与初始化:先把骨架搭起来

环境起来之后,第一件事不是急着录设备,而是把"骨架数据"建好:Location、Manufacturer、DeviceType、Role、Status、Tag。

最省力的路径是用 Django shell 批量创建:

# nautobot-server nbshell from nautobot.dcim.models import Manufacturer, DeviceType, Location, LocationType from nautobot.extras.models import Status, Role # 站点类型 lt, _ = LocationType.objects.get_or_create(name="Data Center") # 站点 loc, _ = Location.objects.get_or_create(name="IDC-A", location_type=lt, status=Status.objects.get(name="Active")) # 厂商 mfr, _ = Manufacturer.objects.get_or_create(name="Generic") # 型号 dt, _ = DeviceType.objects.get_or_create(manufacturer=mfr, model="SW-48G", defaults={"u_height": 1})

这段代码的价值在于可重复执行。get_or_create保证跑十次结果一样,不会产生重复数据。做初始化脚本的时候,这个模式应该贯穿始终。

建完之后再导入设备清单。先导出一个 CSV 模板,填好数据,用界面的导入功能上传。导入时有个技巧:先导 10 条验证字段映射,成功了再导全量。全量导入出错的时候,回滚很麻烦,因为部分记录已经落库了。

4.5 跑通第一个 Job 与第一个 API 查询

环境通了之后,用两步验证整条链路是活的。

第一步,写个最简单的 Job 文件放到JOBS_ROOT目录下,然后在界面里点执行。执行成功会在 Job Result 里看到日志输出。如果状态一直是 Pending,去查 Worker 日志。

第二步,用 curl 测接口:

curl -s -H "Authorization: Token <你的API_TOKEN>" \ -H "Accept: application/json" \ "http://10.0.0.20/api/dcim/devices/?limit=5&status=active" | head -50

API Token 在用户个人设置里生成,注意Token 是有权限范围的,用普通用户生成的 Token 只能读到该用户有权限的数据,测试的时候别用管理员 Token 掩盖了权限配置问题。

两步都通了,说明 Web、数据库、Redis、Worker 这条链路完整。这时候再往上叠插件、叠模板,出问题也容易定位——因为地基已经验证过了。

5. 常见问题与排查技巧实录

这一章按"症状—原因—处理"的方式整理,都是我实际遇到过或帮别人排查过的类型。

5.1 启动与页面类问题

症状大概率原因处理方式
页面能开但样式全丢静态文件没收集或路径没配重跑collectstatic,检查STATIC_ROOT和反向代理配置
502 Bad GatewayWeb 进程没起或端口不对查 systemd 状态和 gunicorn 日志
登录后跳回登录页SECRET_KEY每次重启都变把SECRET_KEY固定写进配置文件
提交表单报 CSRF 错误ALLOWED_HOSTS缺当前访问域名补上域名或 IP
中文显示成问号数据库字符集不是 UTF8建库时指定ENCODING 'UTF8',已建的要重建

关于静态文件,还有个细节容易忽略:升级之后必须重跑collectstatic --no-input。新版本的静态资源文件名带哈希,不收集的话浏览器加载的是旧文件,页面会出现各种诡异错位。这个问题的迷惑性在于日志里完全没有报错。

5.2 作业与队列类问题

症状一:Job 点了没反应,状态卡在 Pending。

排查顺序是这样的:先看 Worker 进程是否存在(ps aux | grep celery),再看 Worker 的启动参数里的队列名和 Web 端配置的CELERY_TASK_DEFAULT_QUEUE是否一致,最后看 Redis 是否连得上。这三步能覆盖九成情况。

还有一个隐蔽原因:Job 文件有语法错误导致注册失败。这种情况下 Job 在列表里能看到,但点执行的时候找不到对应的任务,报错信息不明显。处理方式是看 Worker 启动日志,注册失败的 Job 会在启动时打一条警告。

症状二:Job 跑到一半超时。

默认的任务超时时间可能不够用。有两个方向:调整CELERY_TASK_SOFT_TIME_LIMIT,或者把大任务拆小。我更推荐后者,因为超时设置调大之后,一旦任务真的卡死,会占着 Worker 不放,把后续任务全堵住。

症状三:Job 里读不到配置上下文。

get_config_context()返回空对象,通常是匹配规则没命中。检查三个地方:上下文的权重是不是被更高权重的覆盖了;规则的匹配条件是不是用了has_key之类容易写错的条件;对象本身是不是缺少必要的关联(比如没设 Location,按 Location 匹配的规则自然不生效)。

5.3 性能与数据一致性问题

GraphQL 查询慢是反馈最多的一类。原因通常是嵌套太深导致 JOIN 爆炸,或者没有走索引。处理思路:先用EXPLAIN ANALYZE看执行计划,找出慢在哪张表;再考虑给常用过滤字段加索引;最后才考虑拆查询。

有一个具体案例值得说:查设备列表时带上了interfaces { ip_addresses { ... } },单台设备没感觉,一次查五百台就超时。改成先查设备基础信息,再分批查接口,总耗时反而降到原来的十分之一。这就是典型的"看起来少发请求等于快,实际上不一定"。

数据一致性方面,最常见的坑是并发修改覆盖。两个人在页面上同时编辑同一台设备,后保存的会覆盖前面的。平台本身有冲突检测,但只在部分模型上生效。涉及关键字段的时候,建议用 API 做更新,配合?last_updated=做乐观锁校验,能避免大部分覆盖问题。

还有一类是删除的连锁反应。删一个 Location 会连带删掉下面的机架和设备,这个操作的确认弹窗有时候会被误点。我的建议是:关键对象删除前先改 Status 为 Decommissioning,观察一段时间确认没人用了再删。这个习惯救过我至少两次。

5.4 版本升级的坑与应对

升级这件事,我的态度是能小步走就不要跨大步。跨大版本升级的时候,数据库迁移脚本的路径会变得很长,中间任何一步失败都可能卡住,而且回滚成本很高。

升级前的标准动作:

  1. 完整备份数据库(pg_dump,验证备份文件能恢复到测试库)
  2. 记录当前所有插件的版本
  3. 查插件是否兼容目标版本——这一步最容易被跳过,也最容易翻车
  4. 在测试环境完整跑一遍升级流程
  5. 升级后立即验证三个点:登录、Job 执行、API 查询

升级过程中最常见的报错是迁移冲突,表现为某个表已存在或某个字段不存在。原因通常是之前有一次升级中途失败,数据库处于半迁移状态。处理方式是查django_migrations表,找到状态不一致的记录,手工修正后再重跑。这个操作有风险,动手前务必再备份一次。

还有一个容易忽略的点:升级后要清理浏览器缓存。前端资源有哈希,但如果你用了反向代理缓存,可能拿到旧版本页面,出现"升级了但界面没变"的错觉。

6. 落地过程中的几条经验

写到这儿,技术细节差不多了,最后说几条偏"方法论"的东西,都是踩过之后才想明白的。

先定命名规范,再录数据。设备名、接口名、VLAN 名的命名规则定得越早越好,因为一旦录了几百条再改,成本极高。命名规则不用复杂,能保证唯一性和可读性就够,比如"站点-角色-序号",idc-a-core-01这种。这件事看着琐碎,但它决定了后面所有自动化的可维护性。

不要一次性把所有系统都接到它上面。我见过最典型的失败案例,是上线第一周就把监控、配置备份、工单全切过来,结果数据还没清洗干净,下游全在报错,最后被迫回退。稳妥的节奏是:先只读对接一个系统,跑一个月,数据质量稳定了再扩。

给变更留一条"人工留痕"的路。数据库里的变更日志能记录字段级的变化,但记录不了"为什么改"。建议在关键对象上开一个自定义字段叫"变更单号"或者用 Tag 标记变更批次,出了问题能顺着号找到当时的沟通记录。这个小习惯在故障复盘时价值极大。

关于"免费"这件事要说清楚。平台本身开源免费,没有授权费,但自托管的成本是实打实的:服务器、备份存储、人力维护、升级验证的时间。粗算下来,一套支撑几百台设备的环境,每年投入的人力大概在 0.1 到 0.2 个人力之间。评估的时候把这部分算进去,比只算"省了多少授权费"要实在得多。

最后分享一个小技巧:如果你只是想先看看这东西长什么样,别折腾生产环境,找台闲置机器起个容器版本,把手上现成的设备清单导一批进去,跑一个 Job 试试。半天时间就能判断它适不适合你的场景——比看十篇介绍文章都快。我自己就是这么开始的,从"试试看"到"真香",中间大概隔了两周,转折点是第一次用 GraphQL 把两百台设备的接口信息一次性拉下来做报表,那种"数据终于能用了"的感觉,是这个平台最实在的价值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询