为什么 vpnc 的路由缓存刷新警告会在容器里出现
路由缓存刷新是一个已不再维护该缓存的旧内核留下的残留;只读错误来自容器的挂载方式,而不是隧道本身。
新的容器日志常常以一句关于 /proc/sys/net/ipv4/route/flush 的 Read-only file system 报错收尾,于是这行输出被拿来指责一个其实完全健康的隧道。它背后的写入瞄准的是一个内核条目,而自 IPv4 路由缓存被移除后,这个条目已不再有任何用处。在容器里 /proc/sys 以只读方式挂载,因此在任何值写入之前重定向就被拒绝,shell 会打印这条拒绝信息。只要旧语句还留在脚本里,正常工作的隧道也会一直打印这条警告,而 --privileged 从来不会让这次写入变得有意义。
脚本当初想刷新什么
较早的连接脚本以一次缓存清理收尾:向内核控制文件写入一个值,并要求 IPv4 协议栈丢弃它的路由缓存,好让新的隧道路由被选中,而不必等待过期条目自然老去。该文件位于 /proc/sys/net/ipv4/ 之下,这使得该操作成了一次 shell 重定向,而不是一条路由命令。内核 3.6 移除了那个缓存,也就移除了刷新的存在理由。当前数据路径中没有任何东西依赖这次写入。
条目本身作为一个遗留的只写文件保留了下来,所以仍在向它写入的脚本会走到一条真实路径并触发真实的权限检查。在主机上,写入返回成功,什么也不会改变。在容器里,同样的重定向撞上只读的 /proc/sys,以 EROFS 失败,shell 会把它打印成只读文件系统的消息。在路径确实不存在的内核上,措辞会变成 No such file or directory,结论并不改变。
两个错误说的并不是同一件事,这个区别值得分清楚。Read-only file system 表示内核到达了某个条目并拒绝了写入;No such file or directory 表示该条目已经不存在了。两条消息都没有涉及隧道设备、路由或名称解析,所以两者都不能成为增加权限的理由。
让这条消息看起来像原因的是顺序。连接脚本会先安装路由、改变接口状态并重写名称解析,然后才走到末尾的清理,于是最后打印的那一行就是人们引用的那一行,哪怕每个关键步骤都返回了成功。在打印这条警告的同时建立并承载流量的隧道,正好符合上面的机制。
在增加权限之前先检查挂载和路由
三项检查能把表面警告和真实故障分开:已安装的脚本是否仍带有这条语句、/proc/sys 是如何挂载的,以及 应用的流量实际去了哪里。请在与警告出现时相同的容器命名空间中或同一台主机上执行它们,因为在无关机器上检查回答的是另一个问题。
grep -n 'ipv4/route/flush' /usr/share/vpnc-scripts/vpnc-script
findmnt -no OPTIONS /proc/sys
ip -4 route get 10.20.5.10grep 的答案比看上去更弱,因为找到只能证明这条语句存在,而不能证明它在没有保护的情况下执行;维护良好的脚本可能把写入放在会跳过它的判断里。findmnt -no OPTIONS /proc/sys 直接确定挂载方式,而该输出里的 ro 意味着该点以下的所有写入都会被拒绝,无论脚本做什么。随后路由查询会显示应用所需的目的地是通过隧道接口解析,还是通过容器的默认网关解析。查询选中默认网关,意味着隧道已经起来,但没有任何流量被路由进去,这是路由问题,不是权限问题。
修复究竟从哪里来
维护中的修复在脚本里,而不在容器标志里。上游 vpnc-script 已经改变了处理该操作的方式,所以请把镜像里的副本与当前上游比较,并通过提供它的软件包或镜像构建来更新。运行中的容器里手工改过的文件无法审查,也活不过下一次构建。
--privileged 的文档说明它会授予全部能力以及广泛的设备访问权限,对一条不改变任何东西的消息来说这是很大的让步,所以如果确实缺少某项能力,应当直接点名它。更新之后,请重做路由查询,并让一个应用真正使用隧道,因为安静的日志和可用的连接是两个不同的结果。