一、背景 一般需要对外提供服务的Docker容器,我们在启动时后使用-p命令将对外访问端口暴露给外部,例如启动Docker Registry,我们将5000端口映射出来供外部访问:
但最近碰到一个非常奇怪的情况:研发组里一个CentOS 7测试环境里部署有Docker Registry,并对外暴露了端口。启动容器后一段时间内都是可以正常工作的,但在不定时间间隔后,外部主机就会出现无法从仓库中拉取镜像的情况,提示TimeOut:
然而在Docker宿主机上访问仓库则可以正常访问:
至于这个问题,只有手动重启出问题的Docker daemon服务后,外部才可以重新访问,但只要再过一段时间又会出现这样的问题。 二、问题排查 碰到这个问题我第一反应就是问组里的人,是不是有人重启过CentOS 7 自己的firewallD了。 因为这台服务器是我配置的,防火墙虽然开着但我已经开启端口访问了,所以肯定不是因为防火墙阻断连接的缘故。但由于这篇文章是篇踩坑排查文档,所以还是把这种情况写出来了 情况一:开着防火墙但没有开放端口 CentOS 7自带并启用了防火墙FirewallD,我们可以通过下面的命令检查FirewallD的状态:
如果输出的是“not running”则FirewallD没有在运行,且所有的防护策略都没有启动,那么可以排除防火墙阻断连接的情况了。 如果输出的是“running”,表示当前FirewallD正在运行,需要再输入下面的命令查看现在开放了哪些端口和服务:
可以看到当前防火墙只开放了80/tcp端口、ssh服务(22/tcp)和dhcpv6-client服务,并没有打开Docker容器映射的5000/tcp端口。 解决方案有两种: 1.关闭FirewallD服务: 如果您不需要防火墙,那直接关掉FirewallD服务就好了
2.添加策略对外打开指定的端口: 比如我们现在要打开对外5000/tcp端口,可以使用下面的命令:
如果只是临时打开端口,去掉第一行命令中的“--permanent”参数,那么当再次重启FirewallD服务时,本策略将失效。 情况二:人为重启CentOS 7的FirewallD服务 FirewallD是CentOS系统在7版本引入的新组件,简单的说就是iptables的包装,用于简化防火墙相关的设置。 然而FirewallD和Docker相处的并不是特别好,当FirewallD启动(或重新启动)时,会从iptables中删除DOCKER链,造成Docker不能正常工作:
在CentOS 7中,如果设置使用systemd开机自启动Docker服务是不会有问题的,因为Docker在systemd配置文件中明确注明了“After= firewalld.service”,以保证Docker daemon 在FirewallD启动后再启动。
(Docker:惹不起我还躲不起吗) 但每当用户手动重启过FirewallD服务之后,FirewallD服务会将Docker daemon写入iptables的DOCKER链删除,所以需要手动重新启动一次Docker daemon服务,让Docker daemon服务重建DOCKER链。 不过问了组里另外两个研发,都说没有动过。查看了shell的history也没找到对应的记录。 这就很奇怪了。不过经过一段时间的蹲点排查之后,我终于发现了一个新的原因: 情况三:没有启用IP_FORWARD 因为一直没法定位出问题的所在,所以我们研发组都是发现不能正常访问仓库时,手动登陆宿主机重启Docker daemon服务。 在有一次登录到宿主服务器上准备重启Docker daemon服务前,我突然想起之前在用Docker的时候还碰到过另一个问题:如果宿主机没有启用IP_FORWARD功能,那Docker容器在启动时会输出一条警告消息:
并且将不能在启动的容器中访问外部网络,容器对外暴露的端口外部也不能正常访问:
会不会是因为宿主机的IP_FORWARD功能没有启用所以才引起的这个故障呢?
果然,输出表示当前系统的IP_FORWARD功能处于停用状态! 可是问题来了,当时启动容器的时候都是好的啊,什么都没有输出,怎么用着用着IP_FORWARD功能就被禁用了呢? 等等,Docker daemon服务在启动的时候会自动设置iptables设置,难不成它还会检查IP_FORWARD设置,并帮我临时启用吗? 带着这个假设,我手动重启了一下Docker daemon服务:
果然,Docker daemon服务在启动过程中会检查系统的IP_FORWARD配置项,如果当前系统的IP_FORWARD功能处于停用状态,会帮我们临时启用IP_FORWARD功能,然而临时启用的IP_FORWARD功能会因为其他各种各样的原因失效… 虽然具体造成本次故障的原因现在还没有确凿的证据定位出,但我现在严重怀疑是因为重启网络服务造成的。因为出问题的服务器宿主机上运行着我们研发组正在开发的Web项目,其中有一个功能是修改网卡IP地址,这个功能在修改完网卡IP后,会自动调用下面的命令重启网络服务:
而重启网络服务正会使Docker daemon服务自动设置的临时启用IP_FORWARD配置失效:
另外因为是程序直接调用命令,所以不会在history命令中留下痕迹。 至于修复方案倒非常简单,只要一行命令就可以了:
执行完成后,重启服务器或使用下面的命令从文件中加载配置:
就可以了。 三、小结 Docker daemon服务在启动的时候会帮帮我们调整很多的配置项,比如这次出事儿的IP_FORWARD配置。 Docker daemon启用IP_FORWARD功能是因为Docker容器默认的网络模式(bridge/网桥模式)会给每个容器分配一个私有IP,如果容器需要和外部通信,就需要使用到NAT。NAT需要IP_FORWARD功能支持,否则无法使用。这也解释了为什么会出现在IP_FORWARD功能停用的情况下,使用bridge模式的容器内外均无法访问的情况。 只是在Linux下,出于安全考虑,默认是停用IP_FORWARD功能的,Docker daemon服务在启动时会检查IP_FORWARD功能是否已经启用,如果没有启用的话,Docker daemon会悄无声息的临时启用此功能,然而临时启用的IP_FORWARD功能并不能持久化,会因为其他命令的干扰导致失效。 不过这次的事情告诉了我一个小道理:当出现问题的时候,不要慌,要结合经验大胆的做出假设并验证,治标治本。 (责任编辑:IT) |