网卡静态 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-)。
四、分步操作¶
分级执行顺序(重要)¶
最后处理 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, 避免漏掉已固化的网卡。已达标网卡自动跳过(幂等)。
脚本行为¶
- 逐网卡做幂等检测,已达标则
[跳过]; - 首次执行自动备份
/etc/netplan→../.bak/net_static_ip.<时间戳>/netplan/; - 生成
/etc/netplan/99-static-ips.yaml(权限600); netplan generate→netplan apply;- 验证:每个目标网卡拿到目标地址 + 网关可达;
- 验证失败自动回滚(删除新文件并恢复备份),退出码 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同步,请勿在站点侧修改)