DOCKER 容器访问不通问题定位

DOCKER 容器访问不通问题定位

文章目录

现象

最近有一台设备上部署的容器服务无法从宿主机之外的节点进行访问。

分析

要定位该问题首先要确认以下几个事情:
1. 服务是否正常启动
2. 确认容器的网络模式
3. 容器如何与外面的节点通讯
4. 数据包在设备上实际流转

定位过程

确认服务是否正常

  1. 查看容器运行是否正常:
1
2CONTAINER ID        IMAGE                                                                          COMMAND                  CREATED             STATUS              PORTS                    NAMES
3cfde73945bf6        ***:1.0.7   "/bin/sh -c /opt/boo…"   27 hours ago        Up 27 hours         0.0.0.0:7788->7788/tcp   ***
4 
  1. 查看服务运行是否正常:
1[root@my-ti-johnkl06 ~]# curl 172.17.0.2:7788
2hello world 

由以上可以得出 容器运行正常,服务运行正常并且在宿主机上可以访问。

确认容器的网络模式

通过命令 docker inspect <container_id> -f "{{json .NetworkSettings.Networks }}" 来查看容器的网络模式。

1[root@my-ti-johnkl06 ~]#  docker inspect cfde73945bf6  -f "{{json .NetworkSettings.Networks }}"
2{"bridge":{"IPAMConfig":null,"Links":null,"Aliases":null,"NetworkID":"c52c24b417d9787fd1bf01d409dda7ecef2f519553f719eabe062a0a8132c327","EndpointID":"6e65d42c44e553c953ca763ad5c1e4046374993bdf263358e10d45bdef891d8d","Gateway":"172.17.0.1","IPAddress":"172.17.0.2","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"02:42:ac:11:00:02","DriverOpts":null}}

由上可以得出 容器网络模式是 bridge。

容器如何与外面的节点通讯

容器如何与外面的节点通讯

这里我们主要分析 bridge 网络模式, 容器启动时默认使用虚接口 veth 连接到宿主机 docker0 网桥, 容器和 docker0 组成了一个子网,docker0 上的 IP 就是这个子网的网关 IP。, 所以外部访问容器走的路线: eth0 -> docker0 -> veth_host -> veth_container。

确认数据包如何流转

通过抓包确定数据包是否到达相应的接口:

确认数据包如何流转

从抓包来看报文到达了 ppp1, 但是没有到达docker0, 那么 ppp1 如何发送到docker0呢,答案就是通过iptables规则 , 接下来我们来看一下iptables 是否配置正确。

iptables

检查 iptables 配置, 这里隐藏了部分敏感信息,具体配置如下:

 1# Generated by iptables-save v1.4.21 on Thu Jan 13 11:03:16 2022
 2*raw
 3:PREROUTING ACCEPT [482398345:242296465803]
 4:OUTPUT ACCEPT [335960682:958525745066]
 5-A PREROUTING -p tcp -m tcp --dport 7788 -j TRACE
 6-A OUTPUT -p tcp -m tcp --sport 7788 -j TRACE
 7-A OUTPUT -p tcp -m tcp --dport 7788 -j TRACE
 8COMMIT
 9# Completed on Thu Jan 13 11:03:16 2022
10# Generated by iptables-save v1.4.21 on Thu Jan 13 11:03:16 2022
11*nat
12:PREROUTING ACCEPT [37750:4171203]
13:INPUT ACCEPT [1448717:86057410]
14:OUTPUT ACCEPT [737914:44585663]
15:POSTROUTING ACCEPT [18628:1809317]
16:DOCKER - [0:0]
17-A PREROUTING ! -i p1p1 -p tcp -m multiport --dports 80,443,2238,7788,7799,8947:8949,9173,8400,19999 -j ACCEPT
18-A PREROUTING ! -i p1p1 -p udp -m multiport --dports 53,50000:50010,51280:51319 -j ACCEPT
19-A PREROUTING ! -i p1p1 -j DNAT --to-destination 192.168.200.2
20-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
21-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
22-A POSTROUTING ! -o p1p1 -j MASQUERADE
23-A POSTROUTING -s 192.168.200.0/30 -d 192.168.200.0/30 -o p1p1 -j MASQUERADE
24-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 7788 -j MASQUERADE
25-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 7799 -j MASQUERADE
26-A DOCKER -p tcp -m tcp --dport 7788 -j DNAT --to-destination 172.17.0.2:7788
27-A DOCKER -p tcp -m tcp --dport 7799 -j DNAT --to-destination 172.17.0.2:7799
28-A DOCKER -i docker0 -j RETURN
29COMMIT
30# Completed on Thu Jan 13 11:03:16 2022
31# Generated by iptables-save v1.4.21 on Thu Jan 13 11:03:16 2022
32*mangle
33:PREROUTING ACCEPT [651360808:317546315397]
34:INPUT ACCEPT [644642546:312913563883]
35:FORWARD ACCEPT [6717763:4632715906]
36:OUTPUT ACCEPT [456834563:1310363483378]
37:POSTROUTING ACCEPT [463560653:1314996798828]
38COMMIT
39# Completed on Thu Jan 13 11:03:16 2022
40# Generated by iptables-save v1.4.21 on Thu Jan 13 11:03:16 2022
41*filter
42:INPUT ACCEPT [454683740:230909596704]
43:FORWARD ACCEPT [5433044:3649513313]
44:OUTPUT ACCEPT [320137097:908885273741]
45:DOCKER - [0:0]
46:DOCKER-ISOLATION-STAGE-1 - [0:0]
47:DOCKER-ISOLATION-STAGE-2 - [0:0]
48:DOCKER-USER - [0:0]
49-A INPUT -p tcp -m tcp --dport 7788 -j ACCEPT
50-A INPUT -p tcp -m tcp --dport 7788 -j ACCEPT
51-A FORWARD -j DOCKER-USER
52-A FORWARD -j DOCKER-ISOLATION-STAGE-1
53-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
54-A FORWARD -o docker0 -j DOCKER
55-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
56-A FORWARD -i docker0 -o docker0 -j ACCEPT
57-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 7788 -j ACCEPT
58-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 7799 -j ACCEPT
59-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
60-A DOCKER-ISOLATION-STAGE-1 -j RETURN
61-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
62-A DOCKER-ISOLATION-STAGE-2 -j RETURN
63-A DOCKER-USER -j RETURN
64COMMIT
65# Completed on Thu Jan 13 11:03:16 2022

合作商为了对端口流量做限制,修改了iptables, 这里如果对iptables 比较了解的话应该可以看出问题所在, 当时查问题时只关注了 DNAT 配置是否有问题,并没有关注是否能执行到,所以当时没有看出问题所在.

调试 iptables

  1. 添加日志规则
1iptables -t raw -A PREROUTING -p tcp  --dport 7788     -j TRACE
2iptables -t raw -A OUTPUT -p tcp --dport 7788  -j TRACE
  1. 加载内核模块
    iptables的调试日志输出依赖于内核模块,这些内核模块并不是开机就加载的,因此我们需要手动加载,centos6 与 centos7 方式有所不同:
    centos6 按照下面的方式修改:
    1# modprobe ipt_LOG
    2# sysctl net.netfilter.nf_log.2=ipt_LOG
    3# sysctl net.netfilter.nf_log.2
    4net.netfilter.nf_log.2 = ipt_LOG 
    

centos7 按照下面的方式修改:

1# modprobe nf_log_ipv4
2# sysctl net.netfilter.nf_log.2=nf_log_ipv4
3# sysctl net.netfilter.nf_log.2
4net.netfilter.nf_log.2 = ipt_LOG  
  1. 打开日志并输出到 /var/log/iptables.log
    在 /etc/rsyslog.conf 中开启内核日志
1kern.*         /var/log/iptables.log

重启日志服务

1sudo systemctl restart rsyslog.service
  1. 查看日志 由于带有公网地址, 这里将SRC 改为1.1.1.1 , DST 改为2.2.2.2:
1Jan 12 16:51:14 my-ti-johnkl06 kernel: TRACE: raw:PREROUTING:policy:2 IN=ppp0 OUT= MAC= SRC=1.1.1.1 DST=2.2.2.2 LEN=64 TOS=0x00 PREC=0x00 TTL=41 ID=0 DF PROTO=TCP SPT=19327 DPT=7788 SEQ=3930627540 ACK=0 WINDOW=65535 RES=0x00 SYN URGP=0 OPT (0204056A010303060101080AC2E362FB0000000004020000)
2Jan 12 16:51:14 my-ti-johnkl06 kernel: TRACE: mangle:PREROUTING:policy:1 IN=ppp0 OUT= MAC= SRC=1.1.1.1 DST=2.2.2.2 LEN=64 TOS=0x00 PREC=0x00 TTL=41 ID=0 DF PROTO=TCP SPT=19327 DPT=7788 SEQ=3930627540 ACK=0 WINDOW=65535 RES=0x00 SYN URGP=0 OPT (0204056A010303060101080AC2E362FB0000000004020000)
3Jan 12 16:51:14 my-ti-johnkl06 kernel: TRACE: nat:PREROUTING:rule:1 IN=ppp0 OUT= MAC= SRC=1.1.1.1 DST=2.2.2.2 LEN=64 TOS=0x00 PREC=0x00 TTL=41 ID=0 DF PROTO=TCP SPT=19327 DPT=7788 SEQ=3930627540 ACK=0 WINDOW=65535 RES=0x00 SYN URGP=0 OPT (0204056A010303060101080AC2E362FB0000000004020000)
4Jan 12 16:51:14 my-ti-johnkl06 kernel: TRACE: mangle:INPUT:policy:1 IN=ppp0 OUT= MAC= SRC=1.1.1.1 DST=2.2.2.2 LEN=64 TOS=0x00 PREC=0x00 TTL=41 ID=0 DF PROTO=TCP SPT=19327 DPT=7788 SEQ=3930627540 ACK=0 WINDOW=65535 RES=0x00 SYN URGP=0 OPT (0204056A010303060101080AC2E362FB0000000004020000)
5Jan 12 16:51:14 my-ti-johnkl06 kernel: TRACE: filter:INPUT:policy:1 IN=ppp0 OUT= MAC= SRC=1.1.1.1 DST=2.2.2.2 LEN=64 TOS=0x00 PREC=0x00 TTL=41 ID=0 DF PROTO=TCP SPT=19327 DPT=7788 SEQ=3930627540 ACK=0 WINDOW=65535 RES=0x00 SYN URGP=0 OPT (0204056A010303060101080AC2E362FB0000000004020000)

从日志可以看出收到的报文直接上送到本机协议栈(INPUT),并没有进行转发,所以问题应该出现在 iptables PREROUTING , 应该在 PREROUTEING 将容器的报文转发到docker0 。 我们再来看一下 PREROUTEING的规则:

1-A PREROUTING ! -i p1p1 -p tcp -m multiport --dports 80,443,2238,7788,7799,8947:8949,9173,8400,19999 -j ACCEPT
2-A PREROUTING ! -i p1p1 -p udp -m multiport --dports 53,50000:50010,51280:51319 -j ACCEPT
3-A PREROUTING ! -i p1p1 -j DNAT --to-destination 192.168.200.2

数据包在匹配到第一条规则,就不再匹配, 所以PREROUTING 并没有进行DNAT处理, 我们需要添加一个策略将数据包优先进行 chain docker 处理,

解决方案

添加如下规则:

1# 当收到到达本机的报文时跳转到 DOCKER chain
2-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER

验证

再次验证

1➜  ~ curl  外网IP:7788
2hello world