跳转至

网卡静态 IP 固化 SOP(Ubuntu / netplan + NetworkManager)

主机:Ubuntu 192.168.31.x 网段 | 网卡:eno1 / eno2 / eno3 性质:C 类实操变更(改动系统网络配置,可能瞬断网络)| 日期:2026-09-14


一、背景与目标

背景

三个网卡原为 DHCP 动态获取(noprefixroute,租期 12 小时),地址可能随续租/重启变化。 代理服务 token_proxy 监听 0.0.0.0:8788,客户端 base_url 直接写死了本机 IP, 一旦 IP 漂移,计量链路立即失效(现象:客户端连接失败,logs/usage.jsonl 停止增长)。

目标网络规划

网卡 变更前(DHCP) 变更后(静态) 默认路由 DNS
eno1 192.168.31.39 192.168.31.249/24 ✅ 带(metric 100) 192.168.31.1, 223.5.5.5
eno2 192.168.31.40 192.168.31.250/24 ❌ 仅同网段可达 无
eno3 192.168.31.41 192.168.31.251/24 ❌ 仅同网段可达 无

网关统一 192.168.31.1。只由 eno1 承担默认路由,避免 4 条默认路由互抢(原状态即是 4 条并存)。

客户端统一使用 http://192.168.31.250:8788(eno2),与 eno1 的默认路由解耦。


二、前置条件

# 条件 校验命令
1 具备 root 权限 sudo -v
2 目标地址未被占用 ping -c1 -W1 192.168.31.249(249/250/251 均无响应)
3 已备份 /etc/netplan 脚本首次执行自动备份
4 已确认无需 Service 停机 本次涉及网络,代理进程无需停止(监听 0.0.0.0,换 IP 不影响监听)

三、环境事实(2026-09-14 探测)

项 值
网络管理 NetworkManager active;持久配置源是 netplan(/etc/NetworkManager/system-connections/ 为空,连接均为运行时生成)
现有 netplan 文件 01-network-manager-all.yaml(仅声明 renderer: NetworkManager)、50-cloud-init.yaml(仅 eno2: dhcp4: true)
cloud-init 已禁用(/etc/cloud/cloud-init.disabled 存在),不会在重启时重写 netplan
网卡命名 eno1=enp1s0f0、eno2=enp1s0f1、eno3=enp2s0f0

因此本方案不动 cloud-init、不动现有 netplan 文件,只新增更高优先级的 /etc/netplan/99-static-ips.yaml(netplan 按文件名字典序合并,99- 覆盖 50-)。


四、分步操作

脚本:set_static_ip.sh

分级执行顺序(重要)

最后处理 eno2:客户端/Agent 的计量链路当前指向 192.168.31.40(eno2), 改 eno2 会瞬断这条链路。故按 eno1 → eno3 → eno2 的顺序逐张固化,每步独立验证、独立可回滚。

cd /home/rain/Documents/services/token_proxy/net_static_ip

# 步骤 0:预演(不改任何系统状态)
sudo ./set_static_ip.sh --dry-run --ifaces "eno1"

# 步骤 1:固化 eno1(.39 -> .249,带默认路由)
sudo ./set_static_ip.sh --ifaces "eno1"
ip -4 addr show eno1 | grep 192.168.31.249
ip -4 route show default

# 步骤 2:固化 eno3(.41 -> .251)
sudo ./set_static_ip.sh --ifaces "eno1 eno3"

# 步骤 3:固化 eno2(.40 -> .250)—— 最后做,会中断指向 .40 的客户端链路
sudo ./set_static_ip.sh --ifaces "eno1 eno2 eno3"

--ifaces 每次给累积列表,脚本会用该列表整体重写 99-static-ips.yaml, 避免漏掉已固化的网卡。已达标网卡自动跳过(幂等)。

脚本行为

  1. 逐网卡做幂等检测,已达标则 [跳过];
  2. 首次执行自动备份 /etc/netplan → ../.bak/net_static_ip.<时间戳>/netplan/;
  3. 生成 /etc/netplan/99-static-ips.yaml(权限 600);
  4. netplan generate → netplan apply;
  5. 验证:每个目标网卡拿到目标地址 + 网关可达;
  6. 验证失败自动回滚(删除新文件并恢复备份),退出码 1。

五、验证

# 地址
ip -4 addr show eno1; ip -4 addr show eno2; ip -4 addr show eno3

# 默认路由应只剩 eno1 一条(metric 100)
ip -4 route show default

# DNS
resolvectl status | head -20

# 网关与外网连通
ping -c 2 192.168.31.1
python3 -c "import socket;s=socket.create_connection(('api.deepseek.com',443),timeout=8);print('外网连通',s.getpeername());s.close()"

# 代理在新地址可用(换 IP 不需要重启代理)
python3 -c "import urllib.request;print(urllib.request.urlopen('http://192.168.31.250:8788/healthz',timeout=5).read().decode())"

预期:三张网卡均为目标静态地址;默认路由仅 default via 192.168.31.1 dev eno1; healthz 返回 {"status": "ok", "routes": ["ds", "glm"]}。

幂等复跑

再次执行同一条命令应输出 [跳过] ... 无需变更,且不产生任何变更。


六、回滚

场景 操作
单步失败(脚本已自动回滚) 脚本退出码 1,查看输出确认已恢复
手动回滚到变更前 sudo ./set_static_ip.sh --rollback
更彻底回滚(含 cloud-init 目录) 见下方命令
BK=$(ls -d /home/rain/Documents/services/token_proxy/.bak/net_static_ip.* | sort | tail -1)

# 只回滚 netplan
sudo rm -f /etc/netplan/99-static-ips.yaml
sudo cp -a "${BK}/netplan/." /etc/netplan/
sudo netplan apply

# 如需连 cloud-init 目录一起恢复
sudo cp -a "${BK}/cloud.cfg.d/." /etc/cloud/cloud.cfg.d/

本次备份目录:.bak/net_static_ip.20260914-021434/(含 netplan/ 与 cloud.cfg.d/)。


七、风险与影响面

风险 等级 说明与规避
改 eno2 瞬断客户链路 高 客户端 base_url 现指向 192.168.31.40。放最后执行;改完把客户端改到 http://192.168.31.250:8788,或改用 http://127.0.0.1:8788(不依赖网卡)
默认路由配错导致出网异常 中 只让 eno1 带默认路由;脚本验证网关可达,失败自动回滚
IP 与 DHCP 池冲突 中 .249/.250/.251 已探测无占用,但若路由器 DHCP 池覆盖该区间,日后可能被分配出去,建议在路由器上排除这三个地址或做静态绑定
远程操作失联 中 若通过 SSH 连接,改当前入口网卡会断连;脚本失败自动回滚,但建议保留一个物理/带外入口
netplan 权限告警 低 01-network-manager-all.yaml 为 644,netplan 提示过于开放;不影响功能,本次不修改
多 DNS 源冲突 低 仅 eno1 配 DNS,eno2/eno3 不配

八、变更后需同步的位置

位置 原值 新值
Trae 客户端 -proxy 模型 base_url http://192.168.31.40:8788/ds /glm http://192.168.31.250:8788/ds /glm
token_proxy/docs/SOP.md 192.168.31.40 192.168.31.250

代理本身无需重启、无需改配置(监听 0.0.0.0,allow_cidr 已是 192.168.31.0/24)。


九、执行记录(2026-09-14 02:15)

按 eno1 → eno3 → eno2 顺序分级执行,每步独立验证、独立可回滚。

步骤 命令 结果
预演 --dry-run --ifaces "eno1" 打印拟写入内容,未改系统
步骤 1 --ifaces "eno1" eno1 192.168.31.39 → 192.168.31.249,网关可达,退出码 0
步骤 2 --ifaces "eno1 eno3" eno1 输出 [跳过](已达标);eno3 192.168.31.41 → 192.168.31.251,退出码 0
步骤 3 --ifaces "eno1 eno2 eno3" eno2 192.168.31.40 → 192.168.31.250,退出码 0,三网卡全部达标

默认路由收敛过程(符合"只 eno1 带默认路由"的目标):

  • 步骤 1 后:eno3 的 DHCP 默认路由一度升至 metric 100,与 eno1 静态路由并存(中间态);
  • 步骤 2 后:eno3 转为静态、其 DHCP 默认路由消失 → default via 192.168.31.1 dev eno1 proto static metric 100 + eno2 的 DHCP 路由;
  • 步骤 3 后:仅剩 eno1 的静态默认路由。

备份目录:.bak/net_static_ip.20260914-021434/(含 netplan/ 与 cloud.cfg.d/), 首次执行时自动创建,后续步骤复用,回滚即取此目录。

幂等验证:步骤 2 中 eno1 被自动识别为已达标并 [跳过],只变更了 eno3,符合幂等要求。

已知告警(不影响功能,本次未处理):netplan 执行时提示 Permissions for /etc/netplan/01-network-manager-all.yaml are too open ——该文件权限为 644,netplan 建议 600;属既有配置问题,与本次变更无关。

9.1 最终验证(2026-09-14 02:17)

验证项 结果
三网卡地址 eno1 192.168.31.249/24、eno2 192.168.31.250/24、eno3 192.168.31.251/24(均为 forever,不再是 DHCP)
默认路由 仅 default via 192.168.31.1 dev eno1 proto static metric 100
网关连通 ping 192.168.31.1 0% 丢包
DNS 解析 api.deepseek.com -> 117.163.60.130
外网连通 api.deepseek.com:443 可建立连接
代理可用性 192.168.31.249/250/251 三个地址的 /healthz 均返回 {"status":"ok",...}
代理进程 未重启,PID 仍为 23319(监听 0.0.0.0,换 IP 不影响)
幂等复跑 再次执行输出 4 行 [跳过],退出码 0,零变更
配置持久化 /etc/netplan/99-static-ips.yaml(600)已落盘,重启后仍生效

9.2 遗留风险:既有 SSH 入口仍绑在旧地址

变更后检查到 3 条已建立的 SSH 连接仍指向旧地址 192.168.31.40:22(对端 192.168.31.154):

ESTAB  0  0  192.168.31.40:22  192.168.31.154:61939

TCP 已建立连接在接口地址被移除后不会被内核强制断开,所以当前会话仍然可用; 但一旦这些连接断开(超时、网络抖动、重连),对端继续连 192.168.31.40 就会失败,因为该地址已不存在。

处置:把连接本机的入口(SSH 客户端 / Trae 远程连接配置)由 192.168.31.40 改为 192.168.31.250, 改完后再重连验证。在此之前不要主动断开当前会话。

另:192.168.31.40 已从接口移除,ss 中若看到残留的 TIME_WAIT/ESTAB 条目属正常,会自行消亡。


来源:/home/rain/Documents/services/token_proxy/net_static_ip/SOP_静态IP.md | 最后更新:2026-09-17 02:23(由 build/sync.py 同步,请勿在站点侧修改)