网络运维干久了会发现一个挺别扭的事实:真正拖慢效率的往往不是你脚本写得好不好,而是手上那本网络资产的账本准不准。设备表在 Excel 里一版,监控系统里一版,配置备份里又一版,三份数据打架的时候,任何自动化都是在沙地上盖楼。Nautobot 就是冲这个问题来的——一套开源的网络自动化与运营管理平台,内核是网络源记录(Source of Truth),外面套着 IP 地址管理、机架空间管理、配置上下文、任务调度和 API 网关。我最初接触它是想找一个能免授权费、又能自己改代码的资产管理底座,实测下来,社区版的功能完整度已经能撑起中小规模数据中心的日常运维,代价是你得自己把它跑稳。这篇内容写给三类人:手里有几十到上千台网络设备、想把资产台账变成唯一可信源的运维;想给团队搭一套自动化基础设施、但预算批不下来的工程师;以及刚接手一套历史遗留网络、连设备型号都对不上的新人。下面我按自己的落地顺序,把这套平台拆开讲透。
1. 先搞清楚 Nautobot 的定位:它管什么、不管什么
1.1 网络运维的三个阶段与真正的分水岭
我习惯把网络运维的演进拆成三段。第一段是手工阶段,配置靠命令粘贴,资产靠 Excel,出故障靠翻聊天记录找上次是谁改的。第二段是脚本阶段,有人开始写批量登录脚本,把命令打包成模板,效率提升明显,但脚本本身没法回答"这台设备现在应该是什么样"这个问题。第三段才叫数据驱动,设备、接口、IP、VLAN、机架位置、租户归属全部落进结构化数据库,配置由数据渲染生成,变更走审批和任务队列,事后能追溯。
分水岭不在第二段到第三段之间,而在第一段到第二段之间就已经埋下了——大部分团队写脚本的时候不维护数据模型,导致脚本越写越脆弱。Nautobot 干的事情,本质是把第三段需要的那层"结构化底座"先给你搭好,让你写脚本这件事有据可依。它不是一个配置下发工具,也不是一个监控系统,它更像是一本带 API 的、强制你按模型填写的网络字典。这个定位想不清楚,很容易买椟还珠:把它当成 CMDB 用,觉得录入太麻烦;把它当成自动化引擎用,又觉得它不会自己连设备。
实际用下来,正确的预期是——Nautobot 负责回答"有什么、在哪、属于谁、该配成什么样",具体"怎么连上去改"交给 Ansible、Napalm、Nornir 或者它自带的 Job 去调用这些工具。这个分工非常关键,理解了它,后面所有的设计取舍都顺了。
1.2 能力边界清单:适合谁、不适合谁
判断一套工具值不值得投入,我一般先列它的硬能力和硬缺口。Nautobot 社区版能做的事,我按重要性排了个序:
| 能力域 | 具体内容 | 日常价值 |
|---|---|---|
| 设备与机架管理 | 设备、设备类型、厂商、角色、平台、机架、位置层级 | 资产账本,拓扑还原 |
| IP 地址管理 | 聚合段、前缀、IP、VRF、VLAN、VLAN 组 | 地址规划与回收 |
| 电路与提供商 | 电路、提供商、电路类型、带宽 | 专线台账 |
| 虚拟化资源 | 集群、虚拟机、集群类型 | 虚机与宿主的归属关系 |
| 配置上下文 | 按设备/角色/平台/位置匹配的层级化配置数据 | 变量集中管理 |
| 任务与作业 | 用 Python 写的 Job,可在 Web 上手动触发或定时跑 | 巡检、报表、下发 |
| 开放接口 | REST API、GraphQL、Webhook、导出模板 | 与外部系统集成 |
| 扩展机制 | 自定义字段、计算字段、关系、插件(App) | 适配自家特殊属性 |
缺口也很明确:它不生成配置(要靠模板引擎或你自己写 Job),不做设备配置备份(虽然可以写 Job 做),不做实时监控和告警(那是另一套体系的事),不带现成的合规引擎(有需求要自己搭)。社区版功能完整、不需要授权费,但你得自己承担部署、升级、备份、性能调优的所有工作量——这笔人力账在立项时一定要算进去,别只看"免费"两个字。
适合的场景:设备规模在几十台以上、有明确的自动化诉求、团队里至少有一个愿意碰 Python 和 Linux 的人。如果只有五台交换机、三年没变过配置,说实话没必要上这套东西,一张维护良好的表格更划算。
1.3 和周边工具的分工关系
我见过最常见的一种失败,是把所有事情都塞进一个平台。Nautobot 在整个链条里的位置应该是"数据中枢 + 触发入口",其他角色这样分:
- 采集层:定期从设备抓取当前配置和接口状态,回写到平台。可以用 Netmiko、Scrapli 或者 Nornir 写一个 Job 挂在定时任务上,也可以在外部跑完通过 REST 回灌。回写要注意区分"期望状态"和"观测状态",别让采集数据覆盖了人工录入的规划数据。
- 渲染层:拿配置上下文和设备属性做模板渲染,Jinja2 是最省事的方案。渲染结果先进候选区,人工比对无误再下发。
- 执行层:真正的登录、推送、回滚由外部引擎负责。Nautobot 通过 Webhook 在数据变更时触发外部流水线,或者反过来由外部系统通过 API 拉取目标设备清单。
- 校验层:配置合规、命名规范、IP 冲突检测,都可以写成 Job 定时跑,结果落在自定义字段或者导出成报表。
这个分工的好处是可替换性——哪一层不爽就换哪一层,数据不会跟着一起死。我经历过一次执行引擎换代,因为数据上下文都在平台上,换了工具之后两天就切换完了,这在"脚本里既有数据又有逻辑"的老架构下不可想象。
2. 架构拆解:为什么它用 Django + PostgreSQL + 消息队列这套组合
2.1 五层结构一览
把容器都拉起来之后,我看到的是这样一套结构,理解它对后面排障帮助极大:
第一层是数据持久层,PostgreSQL 存全部结构化数据,Redis 存缓存和任务队列的消息中转。第二层是应用层,一个基于 Django 的 Web 服务,负责页面渲染、REST API、GraphQL 查询和权限校验。第三层是任务层,独立的 worker 进程消费队列里的 Job,另有一个 beat 进程负责定时触发。第四层是接入层,一般是 Nginx 反向代理,扛 TLS 和静态文件。第五层是扩展层,Git 仓库可以作为配置上下文、导出模板和 Job 代码的数据源,插件以 Python 包的形式装进应用层。
这套结构的核心逻辑是"读写分离到进程级别"——Web 进程只做轻量的读写和触发,任何可能跑几分钟的事情(批量导入、配置渲染、设备巡检)都扔进队列交给 worker。这个设计决定了你在容量规划时必须把 Web 和 worker 分开算,而不是简单给一个容器加 CPU。
还有一个容易被忽略的细节:任务队列的实现在版本演进中变过,早期用的是一个轻量队列库,后来切换到了更成熟的 Celery,并拆成 worker 和 beat 两个角色。如果你照着老教程部署,很可能会发现新版本里根本找不到那个服务名,这一步是整个部署里最容易卡住的地方。
2.2 数据模型是核心资产
平台的价值八成在数据模型上。我把核心模型按依赖顺序整理成了一张表,录入数据时按这个顺序走不会卡住:
| 层级 | 模型 | 关键字段 | 依赖 |
|---|---|---|---|
| 一 | 厂商 Manufacturer | 名称、别名 | 无 |
| 一 | 位置类型 LocationType | 名称、层级嵌套关系 | 无 |
| 二 | 设备类型 DeviceType | 型号、厂商、高度(U)、前面板端口 | 厂商 |
| 二 | 位置 Location | 名称、类型、父位置、状态 | 位置类型 |
| 二 | 设备角色 DeviceRole | 名称、颜色 | 无 |
| 二 | 平台 Platform | 名称、厂商、驱动标识 | 厂商 |
| 三 | 机架 Rack | 名称、位置、高度、编号方向 | 位置 |
| 三 | 设备 Device | 名称、类型、角色、平台、位置、机架位 | 上表全部 |
| 四 | 接口 Interface | 名称、类型、速率、模式、所属设备 | 设备 |
| 四 | VLAN / VRF | VID、名称、租户 | 无 |
| 五 | 前缀 Prefix / IP 地址 | 网段、掩码、VRF、状态 | VRF、接口 |
| 五 | 配置上下文 ConfigContext | 数据(JSON)、权重、匹配条件 | 角色/平台/位置 |
有个版本差异必须提前说清楚:较新的版本把区域、站点、机房、机架这一串层级统一抽象成了"位置类型 + 位置"的可自定义层级,机架和设备最终都挂到某个位置上。这个改动对老数据迁移影响最大,如果你是从旧版本升上来,务必在测试环境把完整迁移跑一遍再动生产库。我在一个约三百台设备的环境里做过一次,光位置映射就核对了大半天——因为历史数据里区域命名不统一,"华东"和"华东区"被当成两个不同的位置,迁移后设备挂错地方,后面基于位置的配置上下文全部失效。
2.3 为什么"以数据为中心"比"以脚本为中心"划算
这条我可以用一个具体例子说明。假设要给全网设备配 NTP 服务器,不同机房用不同的上游。脚本时代你会写一个变量文件,里面按机房分段,每次新增机房手动加一段。这套东西能跑,但半年后你离职了,接手的人要花两小时搞清楚这个变量文件的组织逻辑。
换成数据驱动之后,NTP 上游写在配置上下文里,匹配条件是"位置类型=机房",权重按层级区分:全局默认权重 1000,机房级权重 200,设备级权重 10。设备查配置时,平台自动合并所有匹配的上下文,权重高的覆盖权重低的。新增机房只需要加一条位置和一条上下文,脚本一行不改。
两者的差别不在于谁更"高级",而在于变更成本落在谁身上。脚本模式把成本压在写代码的人身上,数据模式把成本前置到建模阶段,之后每次变更都是填表格。对于网络这种"设备多、差异多、变更频繁"的场景,数据模式的摊薄效应非常明显。这也是我最终选择这条路而不是继续维护一堆变量文件的原因。
2.4 扩展点:App、Job、自定义字段、配置上下文
这套平台的扩展能力按侵入性从低到高排,我给个选型建议:
- 自定义字段:给现成模型加属性,比如给设备加"维保到期日""资产编号""责任人"。零代码,页面上直接配。缺点是只能在已有模型上加,不能加新表。
- 计算字段:用 Jinja2 表达式从其他字段算出一个值,比如把接口名拼成"设备名-接口名"。适合做展示用的派生字段,注意它不落库,不能用来做查询过滤。
- 配置上下文:上面说过了,是管理变量的主力,强烈建议所有模板变量都从这里出,不要硬编码。
- 关系:两个模型之间加自定义的多对多或多对一关联,比如"这台设备关联哪些业务系统"。
- Job:写 Python 落地业务逻辑,巡检、报表、渲染、回写都靠它。这是最灵活的扩展点,也是团队技术能力的体现。
- App(插件):整包安装,可以加新模型、新页面、新 API。只有在前几招都不够用时才动,因为它会跟着主版本升级一起折腾。
我个人的原则是:能用自定义字段解决的不写 Job,能用 Job 解决的不写 App。每上一层,升级时的迁移成本就翻一倍。见过有团队把所有东西塞进插件,结果平台升级一次,插件全挂,业务停摆两天。
3. 从零部署:容器化落地的完整步骤与参数取舍
3.1 部署方式怎么选
官方给了两条路:容器编排和传统安装。我的建议很直接——除非你有非常强的合规要求必须装在物理机上,否则一律走容器。
容器方案的优势是组件版本锁定在一个编排文件里,数据库、缓存、应用、worker 全在一份配置里定义好,迁移和重建的成本极低。传统安装要自己搞 Python 虚拟环境、系统依赖、systemd 服务、静态文件收集、定时任务,一步一步都是坑,而且换个机器就得重来一遍。
代价也要说清楚:容器方案下所有状态都得显式挂卷,数据库数据目录、上传文件目录、以及自带的 Git 仓库克隆目录,三个卷缺一个就会在重启后丢数据。我第一次部署时就漏了最后一个,结果配置上下文全部消失,排查了半天才发现是容器重建把克隆的仓库清空了。
3.2 关键环境变量与容量估算
编排文件里最需要动的地方是环境变量,下面这些是必填或者强烈建议改的:
services: nautobot: image: nautobot/nautobot:latest environment: NAUTOBOT_ALLOWED_HOSTS: "nb.example.internal 10.0.0.20" NAUTOBOT_SECRET_KEY: "替换成足够长的随机串" NAUTOBOT_DB_HOST: "postgres" NAUTOBOT_DB_NAME: "nautobot" NAUTOBOT_DB_USER: "nautobot" NAUTOBOT_DB_PASSWORD: "改掉默认值" NAUTOBOT_REDIS_HOST: "redis" NAUTOBOT_REDIS_PORT: "6379" NAUTOBOT_REDIS_PASSWORD: "改掉默认值" NAUTOBOT_SUPERUSER_NAME: "admin" NAUTOBOT_SUPERUSER_EMAIL: "admin@example.internal" NAUTOBOT_SUPERUSER_PASSWORD: "首次登录后立刻改" NAUTOBOT_TIME_ZONE: "Asia/Shanghai" NAUTOBOT_MAX_PAGE_SIZE: "1000" volumes: - nautobot_media:/opt/nautobot/media - nautobot_git:/opt/nautobot/git几个参数值得单独解释。NAUTOBOT_ALLOWED_HOSTS是访问白名单,写成实际域名和 IP,用空格分隔,不配的话页面会直接给你 400,这个坑几乎人人踩一次。NAUTOBOT_SECRET_KEY用于会话签名和加密,务必用随机长串,泄漏等于允许伪造会话。NAUTOBOT_MAX_PAGE_SIZE控制列表页单页上限,默认值偏保守,设备多了之后翻页翻到怀疑人生,但调太大又会让首次渲染变慢,一千是个比较平衡的值。时区务必设成Asia/Shanghai,否则定时任务的时间点全按 UTC 走,你以为早上八点跑,实际是下午四点。
容量规划我给一张经验表,这套数据是几个不同规模环境实测汇总的,可以直接拿来当采购依据:
| 规模档位 | 设备数 | 接口数 | IP 数 | PostgreSQL | 应用容器 | Worker 并发 |
|---|---|---|---|---|---|---|
| 小型 | < 200 | < 3000 | < 5000 | 2 核 / 4 GB | 2 核 / 2 GB | 2 |
| 中型 | 200–1500 | 3000–30000 | 5000–80000 | 4 核 / 16 GB | 4 核 / 4 GB | 4–8 |
| 大型 | > 1500 | > 30000 | > 80000 | 8 核 / 32 GB 起 | 4 核 / 8 GB | 8–16 |
数据库那几个参数按比例算:共享缓冲区取物理内存的 25%,缓存估算值取 50% 到 75%,单个查询可用内存取"总内存 ÷ (最大连接数 × 2)"。中型的 16 GB 机器,共享缓冲区给 4 GB,缓存估算给 10 GB,最大连接数设 100,单查询内存给 80 MB 左右。这些值不需要精确到小数点,方向对了性能就差不了。
3.3 起服务、建管理员、校验
编排文件写好后,起服务的顺序有讲究:先起数据库和缓存,等健康检查过了再起应用,最后起 worker 和定时任务。
docker compose up -d postgres redis docker compose ps # 确认两个都是 healthy docker compose up -d nautobot docker compose logs -f nautobot # 等到出现启动完成的日志 docker compose up -d nautobot-worker nautobot-beat第一次启动时,容器入口脚本会自动跑数据库迁移、收集静态文件、按环境变量创建超级用户,不需要你手动执行。如果日志里出现迁移报错,九成是数据库连不上或者权限不够。
起来之后做三件事校准:打开首页登录,确认样式正常(样式丢失说明静态文件没收集成功);进系统信息页看一眼版本号和已安装的 App;手动创建一个测试设备,保存后刷新,确认数据落库。这三步过了,基础部署就算完成。
3.4 反向代理与 HTTPS 的两个坑
生产环境前面一定要有反向代理,理由有三个:TLS 卸载、静态文件加速、请求体大小不受应用层限制。
配 Nginx 时有两个坑我踩过。第一个是上传大小,批量导入 CSV 或者上传镜像文件时,默认的 1 MB 限制会让你得到一个不好排查的 413,得把client_max_body_size调到 50 MB 以上。第二个是超时,跑长 Job 的时候页面上会看到一个网关超时,不是任务失败,是代理等不及了。把proxy_read_timeout和proxy_send_timeout都设到 300 秒以上,同时给 worker 的日志留好排查路径。
还有个小细节:配置反向代理之后,应用需要知道真实的外部地址和协议,否则生成的回调 URL 全是内网 IP。把代理传来的协议头透传过去,同时在允许主机里加上外部域名,这两步不能漏。
4. 数据建模与批量导入:把账本先做准
4.1 建模型的顺序(有依赖关系)
数据录入最大的坑是顺序错了。平台有外键约束,设备类型必须在厂商之后建,设备必须在设备类型和位置之后建,接口必须在设备之后建,IP 必须在接口或者前缀之后建。按第 2 章那张表的顺序走,不会卡。
我建议的顺序是这样的:厂商 → 位置类型 → 位置(区域、机房、机架位)→ 设备角色 → 平台 → 设备类型 → 机架 → 设备 → 接口 → VLAN 和 VRF → 前缀 → IP 地址 → 电路 → 租户。
其中"租户"这个模型容易被忽略,但它对多业务线的组织特别有用。给每个业务线上打一个租户标签,设备、前缀、电路、虚机都可以关联租户,后面做分摊统计和权限隔离都靠它。我在一个多业务线的环境里把租户体系建好之后,业务方来问"我这条线占了多少地址资源",一条 API 查询就能出结果。
4.2 CSV 批量导入的实操与坑
几十台设备手点还能忍,上百台就必须批量导入了。平台提供 CSV 导入功能,流程是上传文件、映射字段、预检、提交。
预检那一步千万别跳过,它会把每一行的错误列出来,比提交后回滚再改要高效得多。我整理了几个高频错误:
| 报错现象 | 根本原因 | 处理方式 |
|---|---|---|
| 外键不存在 | 关联对象还没建,或者名字差一个空格 | 先建被依赖对象,检查尾随空格 |
| 唯一约束冲突 | 同名设备已存在 | 用更新模式而不是新建模式 |
| 枚举值非法 | 状态字段填了自定义值 | 用平台预置的状态值 |
| 日期格式错 | 用了本地格式 | 统一成 ISO 格式 |
| 中文乱码 | 文件编码不是 UTF-8 | 另存为 UTF-8 无 BOM |
还有一个隐蔽的坑:设备名称在模型里通常要求全局唯一,但现实中很多团队的命名是"机房缩写-角色-序号",看着唯一其实不然。导入前先做一次去重检查,把重名设备挑出来改规范,否则后面所有基于设备名的关联操作都会出问题。
导入完成之后,务必抽查。我的做法是随机抽 10% 的设备,点进去看设备类型、位置、机架位、接口数量是否对得上,再和 Excel 原始台账核对一遍总数。这一步花二十分钟,能省掉后面几天的排查。
4.3 配置上下文怎么写才好维护
配置上下文是这套平台最实用的功能,也是最容易写烂的功能。写烂的典型症状是:一百多条上下文,内容互相覆盖,谁也说不清某台设备最终用的是哪个值。
我的写法有三条规则。第一,权重分层不留空档:全局默认用 1000,位置级用 500,角色级用 200,设备级用 10。这样层级关系一眼可见,新加一类条件时也知道该给多少。第二,数据扁平化:JSON 里父子层级别超过三层,深层嵌套在模板里取值很痛苦。第三,命名带前缀:所有键统一加业务前缀,比如ntp_、snmp_、bgp_,避免和别的上下文撞名。
给个实际用的例子:
{ "ntp_servers": ["10.0.0.11", "10.0.0.12"], "snmp_location": "SH-A01", "snmp_community_ref": "snmp_ro_v2", "bgp_local_as": 65001, "bgp_peers": [ {"ip": "10.1.0.1", "remote_as": 65002, "description": "上行核心"} ], "mgmt_vlan": 100 }匹配条件那边,能用角色和平台就别用具体设备,用手工指定的设备级上下文,条数一多就失控。见过一个环境里有三百多条设备级上下文,管理员离职后没人敢动,最后只能全部作废重建。
注意:配置上下文里绝对不要放明文密码。平台有专门的密钥管理模块,密码类的值存密钥,上下文里只存引用标识,模板渲染时再去解析真实值。
5. 自动化闭环:从数据到配置生成与下发
5.1 Job 编写与调试
Job 是这个平台里最像"编程"的部分。一个 Job 就是一个 Python 类,继承基类,定义输入变量,实现执行逻辑,日志自动进任务结果页。
写一个设备清单报表的骨架大概长这样:
from nautobot.apps.jobs import Job, ObjectVar, BooleanVar, register_jobs from nautobot.dcim.models import Device, DeviceRole class DeviceInventoryReport(Job): """按角色导出设备清单与主管理地址""" role = ObjectVar(model=DeviceRole, required=False, description="不选则统计全部") only_active = BooleanVar(default=True, description="只统计在用设备") class Meta: name = "设备清单报表" description = "输出设备名、位置、主 IP,并生成每台设备的文本文件" has_sensitive_variables = False def run(self, role, only_active): qs = Device.objects.all() if role: qs = qs.filter(role=role) if only_active: qs = qs.filter(status="active") total = qs.count() self.logger.info(f"命中设备 {total} 台") for dev in qs.iterator(chunk_size=200): ip = dev.primary_ip4.address if dev.primary_ip4 else "未分配" loc = dev.location.name if dev.location else "-" self.logger.info(f"{dev.name}\t{loc}\t{ip}") self.create_file( f"{dev.name}.txt", f"name={dev.name}\nlocation={loc}\nmgmt_ip={ip}\n", ) return f"完成,共处理 {total} 台设备" register_jobs(DeviceInventoryReport)几个实操要点。iterator(chunk_size=...)而不是普通遍历,设备上千台时内存占用差一个数量级。create_file生成的文件会打包在任务结果里供下载,适合做一次性交付物,但别在里面循环写几万个文件,会把磁盘撑爆。返回值会显示在任务详情页,这是给人看的结果摘要,写清楚完成了什么。
调试阶段直接在页面上手动跑,日志实时输出。上线之后再挂到定时任务上,按 cron 表达式配置。有个细节:定时任务的时区跟主配置走,改了主时区记得重建 beat 容器,否则老的时间表还在按旧时区算。
5.2 REST API 与 GraphQL 该怎么选
两种接口都在,选哪个取决于查询形态。
REST 适合"已知资源、批量取列表"的场景,路径清晰,分页成熟,脚本里最好用:
TOKEN="把在用户设置里生成的令牌放这里" curl -s \ -H "Authorization: Token ${TOKEN}" \ -H "Accept: application/json" \ "https://nb.example.internal/api/dcim/devices/?role=core-switch&limit=200" \ | jq '.results[] | {name, location, primary_ip4}'注意认证头的格式是Token前缀,不是常见的Bearer,这个细节错了会一直拿到 401。
GraphQL 适合"一次查询要跨多张表、字段还挑着要"的场景,一个请求能拿回设备、位置、接口、IP 的组合树,特别省往返:
{ devices(role: "core-switch") { name status location { name } primary_ip4 { address } interfaces { name type description } } }代价是查询复杂度不受你控制,深嵌套加上大列表容易把服务端拖慢。生产环境建议在反向代理层面对查询深度和超时做限制,别让一个手抖的查询把整个 Web 服务拖死。我在测试环境用过一个五层嵌套的查询,日志里直接看到数据库查询时间飙到十几秒。
5.3 渲染配置与合规校验
数据齐了之后,配发生成就是模板的事。用 Jinja2 写模板,数据从设备对象和配置上下文里取,渲染出来的结果先写进候选文件,不直接下发。
合规校验我一般做成两个 Job。一个是命名规范检查:设备名、接口描述、VLAN 名称是否匹配正则,不匹配的写进自定义字段标记出来。另一个是一致性检查:设备上配置的 IP 是否在平台登记过,平台登记但设备上没配的 IP 是否属于配置漂移。第二个检查要求你先有采集数据,通常做法是定期 SSH 抓配置,解析出关键字段回写到设备的自定义字段上。
这两个 Job 的价值在于把"人工比对"变成"定时跑批"。跑出来的结果导出成 CSV 发给相关人,比在群里吼"谁的接口没写描述"有效得多。
5.4 和外部执行引擎联动
最后一公里交给谁,取决于团队现状。有 Ansible 积累的,就把平台当动态清单源,通过 API 拉设备列表和变量,生成 inventory,剩下的事情沿用原有流水线。没有积累的,可以在 Job 里直接调用设备连接库,但这要求 Job 能访问设备的 SSH 端口,网络分区要提前打通。
无论哪条路,都建议加一层变更闸门:Job 只负责生成和预检,真正的推送由外部流水线在人工确认后执行,执行结果通过 API 回写平台。这样平台上既有期望状态也有实际状态,出问题能对上账。跳过这一层直接自动推送,第一次误操作就能让你记住什么叫敬畏生产环境。
6. 常见问题排查实录与速查表
6.1 部署与访问类
部署阶段的问题高度集中,我把遇到过的整理成速查表:
| 现象 | 最可能的原因 | 排查与处理 |
|---|---|---|
| 页面返回 400 | 允许主机列表没配或没含当前域名 | 补全环境变量后重建应用容器 |
| 页面样式全丢 | 静态文件没收集,或代理路径不对 | 手动执行静态收集,检查代理配置 |
| 502 网关错误 | 应用容器没起来或还在初始化 | 看容器日志,等迁移和收集跑完 |
| 登录后立刻掉线 | 会话密钥每次重启都变 | 把密钥固定到配置或密钥文件里 |
| 中文显示为方块 | 容器内字体缺失 | 装基础字体包,或换用系统字体 |
| 时间对不上 | 时区没设或设错了 | 改时区后重建应用和 beat |
第一个 400 我要多说一句,因为它的报错信息极其简略,新手很容易怀疑是数据库问题。实际上九成就是允许主机没配对。判断方法很简单:用容器内部 IP 直接访问能通,用域名访问 400,那就是这个问题。
6.2 任务与队列类
任务类问题的表现是"点了没反应"或者"一直排队"。核心排查顺序是:看任务状态 → 看 worker 日志 → 看队列积压。
- 任务一直排队:worker 容器没起来,或者 worker 连的队列和应用不在同一个实例。检查两边的缓存连接配置是否一致,这是最常见的原因。
- 任务跑一半失败:看日志里的异常栈。如果是数据库连接超时,多半是长事务加上连接池耗尽;如果是权限问题,检查 Job 执行时的用户上下文。
- 文件生成失败:磁盘满了。任务生成的临时文件不会自动清理,跑一段时间记得清理媒体目录。
- 定时任务不触发:beat 容器挂了,或者时间表里有非法表达式。beat 的日志会明确提示解析失败。
单 worker 并发数不要盲目调大。IO 密集型的任务(比如 SSH 采集)可以给到 CPU 核数的两倍,CPU 密集型的任务给到核数就够了。超过这个量级,收益递减还会互相拖慢。
6.3 数据与性能类
平台用久了会变慢,慢的地方有规律。
列表页加载慢:通常是单页数量太大,加上关联字段的预加载没做好。调小分页上限,或者用导出功能代替页面浏览。
API 查询超时:一是没加过滤条件全表拉,二是嵌套关联太深。接口层面强制要求带过滤条件或者分页参数,能从源头解决大半问题。
数据库膨胀:变更历史表会一直增长,这个表在排查问题时很有价值,但也确实占空间。定期归档超过半年的记录,或者只保留关键模型的变更历史,能明显缓解。
批量操作卡死:一次提交几千条记录会锁表。分批提交,每批两三百条,中间留一点间隔。
注意:手工改数据库是最后手段。平台有数据校验和缓存,直接改库很容易出现页面显示和数据库不一致的情况,改完记得清缓存并重启应用。
6.4 升级与备份
升级这件事,我的做法是把它当成一次小型项目来管。
备份永远是第一步,而且必须验证可恢复。两个东西要备:数据库全量转储,以及媒体和 Git 仓库目录。只备数据库不备文件目录,恢复后配置上下文和上传的模板全没了,这个亏我吃过一次。
# 数据库全量转储(在数据库容器内执行) pg_dump -U nautobot -Fc nautobot > /backup/nautobot_$(date +%Y%m%d).dump # 文件卷打包 docker run --rm -v nautobot_media:/data -v /backup:/backup alpine \ tar czf /backup/media_$(date +%Y%m%d).tar.gz -C /data .升级顺序:先在测试环境用生产数据的副本完整跑一遍,确认迁移无报错、关键页面能打开、Job 能正常跑;再挑业务低峰期升级生产;升完立刻抽查设备列表、IP 列表、配置上下文、任务队列四个位置。
跨大版本升级要额外看变更说明,尤其是模型结构变动的那几版。位置模型重构就是典型例子,老数据迁移时如果存在命名冲突或者层级断裂,迁移脚本会报错中断。建议在测试环境里先把明显的历史脏数据清理掉,再执行迁移。
7. 落地节奏与团队协作的个人经验
7.1 分三期推进,不要一次做完
我参与过三次这类平台的落地,最顺利的一次是分了三期。
第一期只做资产台账:把设备、机架位置、接口、IP 段录准,打通和监控系统的对账。这一期不给团队任何"自动化"的承诺,目标就是让所有人查资料时第一个想到打开这个平台。周期大概四到六周,成果好衡量——查一台设备的物理位置从翻三个表变成点两下。
第二期做配置上下文和模板渲染:先把配置生成出来,但不自动下发,人工比对后手工粘贴。这一期是建立信任的关键,让团队看到渲染结果和老配置一致,才会愿意进入下一期。这一期最容易出现的阻力是"模板不如我手写灵活",解决办法是把差异点做成变量,而不是说服别人接受你的写法。
第三期接入执行流水线:推送、回滚、合规校验全上。到这一步,前面两期的数据质量直接决定成败。数据不准的地方,自动化会以每天几十次的频率提醒你。
跳期的代价很大。我见过一个团队第一期还没把数据录准,直接上自动下发,第一次跑就改了四十多台设备的配置,回滚花了一整天。
7.2 数据治理的三条硬规矩
工具只是载体,能不能用起来看规矩。我总结下来这三条最有效:
第一条,平台是唯一事实源。任何设备信息变更,先改平台再改设备,不接受"我先在设备上改完再补录"。这条不立住,三个月后数据必然和现实脱节。
第二条,录入必须填全关键字段。设备的位置、角色、平台、管理 IP 这四个字段设为必填,缺一个就不允许保存。设置必填这件事一开始会有阻力,但它是数据质量的最低保障。
第三条,定期对账。每月跑一次采集 Job,把设备上的实际配置和平台记录做比对,差异列成清单。有差异不一定是数据错了,也可能是有人绕过流程改了设备——不管哪种情况,都需要有人处理。
最后分享一个我觉得挺实用的小技巧:给每台设备的自定义字段加一个"最后核对日期",每次对账后批量更新。半年后打开列表按这个字段排序,哪些设备一年没人管过一目了然。这个字段不解决任何技术问题,但它能让人对数据的状态有感知,比任何制度条文都管用。
这套平台我用了挺长时间,最深的体会是它逼着团队把"网络长什么样"这件事说清楚。这个过程中暴露出来的命名混乱、地址规划随意、责任人缺失,其实都是原本就存在的问题,只是以前被脚本和表格掩盖着。把这些理顺之后,自动化反而是水到渠成的那一步,不需要多高的技术能力,需要的是耐心和一点较真。