Incus 脚本审阅与验收 · 第 4 / 8 篇
容器能出网以后,下一个需求通常是“把里面的服务开放出来”。如果一开始就给六台容器各开放一大片 TCP 和 UDP 端口,配置虽然省事,后续却很难回答哪个端口属于谁、里面是否真的有服务、删除一段会不会切断 SSH。端口管理器要解决的是可维护的映射关系。

SSH 与业务端口为什么分开
部署脚本预期建立 ssh2201—ssh2206。业务管理器则创建 pm_tcp_22030、pm_udp_22030 一类设备,并仅把符合其命名格式的本地 proxy 设备列为可删除对象。清晰的命名约定使管理入口与业务入口可以分开处理。
这是一种操作约束,不是密码学意义上的所有权证明。用户如果手工创建同名 pm_ 设备,管理器仍可能把它视为可管理对象;未来可增加受控的分配清单,并核对设备内容,而不只认名字。
这份脚本采用普通 proxy,而不是 nat=true
例如,选择 vps3 并添加 TCP 22030,代码会生成下面的等价映射:
# 会公开监听 IPv4 的业务端口;先确认端口归属与防火墙范围
incus config device add vps3 pm_tcp_22030 proxy \
listen=tcp:0.0.0.0:22030 \
connect=tcp:127.0.0.1:22030
此处的连接目标是实例一侧的回环地址,不是让业务请求回到宿主机的回环服务。程序仍需在容器内相应地址和端口监听。普通 proxy 与 NAT 模式有不同的数据路径和客户端地址处理方式;不能把上面命令直接宣传为“保留真实客户端 IP 的 NAT”。Incus proxy 官方说明
原文档的 22010—22019、22020—22029 等段只是提示。脚本没有强制把某段锁给某台实例,而是每次检查冲突。TCP 和 UDP 是两个独立的协议端口;选择一个端口、两种协议,会生成两个设备。单次最多输入 50 个端口,选择 TCP+UDP 时最多创建 100 个设备,不能把这个限制误写成“最多 50 个设备”。
冲突检查解决了什么,还漏了什么
代码先读取已识别的 vps数字 实例及其展开设备,再扫描 ss -H -lntu 的本机监听。发现问题就停止;批量添加中途失败时,尝试逆序移除本轮已经成功添加的设备。这是有价值的失败收尾,但不是数据库事务,回滚也可能失败。
检查范围不覆盖任意名字的 Incus 实例、所有项目和远程宿主机,也不一定能发现没有本机监听进程的 Docker DNAT、手写防火墙映射或未来才启动的服务。检查与真正创建之间仍有竞争窗口。生产版本应记录分配结果,核对所有相关资源,并把 Incus 的创建错误作为最终裁决。
为什么菜单一出现就退出
这份实现把 Python 程序通过文件描述符 3 传入,交互输入则专门打开 /dev/tty。它把“程序代码从哪里来”和“用户回答从哪里来”分开,避免脚本来源、重定向或管道把标准输入用完后,菜单立刻读到 EOF。
因此它要求真正的交互终端。它适合 SSH 终端,不适合直接当作无终端计划任务。自动化接口应使用明确参数、退出码与日志,不能依赖一个会等待人工输入的菜单。
删除确认也要以代码为准
手册和提示文字写“须输入 yes”,但当前实现实际接受 y 或 yes,回车取消。这是文案与行为不一致,不能在教程里继续写成“只有完整 yes 才删除”。删除前仍应查看列出的设备,确认它们是本次要移除的业务映射。
端口完成配置后怎样证明可用
incus config device show vps3
incus exec vps3 -- ss -lntup
ss -lntup
然后从另一台机器访问宿主机的目标端口,并在容器服务日志中确认请求抵达。TCP 端口握手只能证明连接;HTTP 服务还应验证状态码与内容,UDP 服务应使用其应用协议发请求并观察响应或日志,不能用 TCPing 证明 UDP 正常。
理想结果:设备有归属、协议匹配、容器服务真实响应、外部请求可验证、移除映射后 SSH 不受影响。当前代码具备部分防误操作机制;上述完整链路仍需在测试实例验收。