docker网络问题

场景

处理了一个现场问题,现场部署了多个agent但是就一个agent离线。

agent是部署在单独的服务器A上,且使用docker部署,该agent需要与另外一台服务器B通信,当前的状态即是无法通信。

agent->docker->服务器A->….->服务器B

思路

先搞清楚请求要从agent到服务器B是个什么过程,找出其中的关键环节,逐个排查。

容器B中的某个服务程序(比如web服务器)需要向服务器C发送数据。它会调用socket接口,传入目标地址等信息来生成数据包。
数据包被传递给容器B的网络堆栈。该堆栈会为数据包添加上以太网头(包含源MAC、目标MAC等),然后发送到容器的虚拟接口veth pair。
veth pair会将数据包传递给Docker主机的网络命名空间。这里包含一个Bridge接口docker0。
docker0收到数据包后,查找路由表,确定应该通过主机的物理接口eth0发送。
数据包被传递至主机的网络协议栈,这里会为数据包添加物理层头信息(比如Ethernet II头)。
添加完头信息的数据包通过主机的物理接口eth0发送出去。
数据包到达主机所在物理网络的交换机,交换机根据MAC地址转发到路由器。
路由器剥离二层头信息,查找三层信息,确定应向ISP上的默认网关发送。
在各个ISP路由器之间,重复着提取IP头,查询路由表,转发的过程。
最终数据包到达服务器C,服务器C的网络栈处理数据包,将其传递给目标服务。

在Docker容器的网络中,还存在一个默认网关,它是容器访问外部网络的桥梁。

当容器B的网络栈处理数据包时,会查看目标IP是否在容器内网段:

如果目标IP在容器内网段,则直接通过veth pair发送给docker0。
如果目标IP不在容器内网段,则需要先发送给容器的默认网关。
容器的默认网关其实是一个在主机网络命名空间内的虚拟接口,IP地址属于docker0子网,比如172.17.0.1。

所以数据包的流程可以补充为:

容器B网络栈 -> 容器默认网关(172.17.0.1) -> docker0 -> eth0

默认网关负责帮助容器正确地将数据包发送到外部网络,从而实现网络访问。

可能的问题点

  1. 容器内默认路由未正确设置
    容器需要有默认网关来路由外部网络的访问。如果默认路由未设置或设置错误,可能导致容器内无法访问外部网络。

  2. docker0桥接问题
    docker0是连接容器和主机网络的桥梁。如果docker0配置错误,无法正确转发容器的数据包,会造成网络访问故障。

  3. 容器访问外部网络规则问题
    可能由于iptables等防火墙规则错误配置,导致容器无法访问外部特定网络或端口。

  4. 容器网络命名空间隔离问题

排查

查了下agent所在的服务器与主业务是能telnet通的,但是进入agent容器内部则不行,不管是ping还是curl都不行。

首先怀疑容器的网络配置可能存在问题。检查容器的网络设置,先后检查了iptables、DNS、检查默认路由都没有异常。又因为容器外能访问,且该agent与其它2、3个agent属于同一个子网下,所以也侧面证明了,服务器的网络配置确实没问题。

那接下来基本确定是docker网络配置的问题了。

基于nsenter,验证网络问题,发现确实存在网络问题,ping docker默认网关不通。

现场反馈更新过agent的网络地址,随即检查daemon.json 、docker-compose.yml文件

发现docker-compose.yml内定义的subnet为16为子网掩码,而daemon.json为24位子网掩码,

因为16比24的IP范围大,这种情况下,容器获得的IP地址可能不在docker-compose网络的子网范围内,从而无法连接到该网络,造成网络通信失败。

解决方案:

将docker-compose.yml和docker daemon中的子网掩码设置为相同。例如都使用24位子网掩码。