查看容器内的tcp连接

当需要查看tcp连接时,通常使用netstat或ss命令查看,但是查看docker容器的tcp连接存在两个问题

  • docker容器中无netstat或ss命令
  • node节点上无法查看容器中的连接

查看docker容器中网络连接

通过容器中的proc文件查看

  • 查看proc文件内容
cat /proc/net/tcp
  • 内容分析,主要关注的点是local_address、rem_address
 sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
   0: 00000000:0050 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 47626 1 ffff8ad7fae25f00 100 0 0 10 0
   1: 00000000:01BB 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 47627 1 ffff8ad7fae25740 100 0 0 10 0
   2: 020011AC:01BB 8297417C:CE2C 01 00000000:00000000 00:00000000 00000000   101        0 12071491 1 ffff8ad7fa78d740 20 4 23 10 -1
   3: 020011AC:01BB 8297417C:CE2B 01 00000000:00000000 00:00000000 00000000   101        0 12071492 1 ffff8ad7fa78e6c0 20 4 30 10 -1

ip及端口格式020011AC:01BB为16进制,可通过工具解析为10进制,如地址解析工具进制转换工具

通过namespace查看

  • 查找docker容器进程号
docker inspect -f {{.State.Pid}} nginx
  • 进入某个进程的network namespace
nsenter -n -t 2593
  • 执行netstat
[root@kevin ns]# netstat -t
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 kevin:35086             172.17.0.4:opentable    TIME_WAIT
tcp        0      0 kevin:https             124.64.18.179:41661     ESTABLISHED
tcp        0      0 kevin:https             124.65.151.130:61026    ESTABLISHED
tcp        0      0 kevin:https             124.65.151.130:61025    ESTABLISHED
tcp        0      0 kevin:https             124.65.151.130:61024    ESTABLISHED
tcp        0      0 kevin:https             124.65.151.130:59837    ESTABLISHED
tcp        0      0 kevin:https             124.65.151.130:60000    ESTABLISHED

故 netstat 默认情况下只能查看当前namespace下的连接,如果查看其它namespace的连接,需要先进入其它namespace

查看docker nginx进程对应的tcp连接

在节点上执行下述命令

cat /proc/2593/net/tcp

docker-proxy

查看节点上http或https对应的端口监听程序

当在节点上执行netstat -ltp时,可以发现存在进程docker-proxy-current监听着8080、443端口

[root@centos75_100-58 ~]# netstat -ltp|grep http
tcp6       0      0 [::]:http               [::]:*                  LISTEN      42138/docker-proxy-
tcp6       0      0 [::]:https              [::]:*                  LISTEN      42124/docker-proxy-

查看进程详细信息

[root@centos75_100-58 ~]# ps -ef|grep 42138 |grep -v grep
root     42138 41940  0  2019 ?        00:00:05 /usr/libexec/docker/docker-proxy-current -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip 172.17.66.3 -container-port 80
[root@centos75_100-58 ~]# ps -ef|grep 42124|grep -v grep
root     42124 41940  0  2019 ?        00:00:05 /usr/libexec/docker/docker-proxy-current -proto tcp -host-ip 0.0.0.0 -host-port 443 -container-ip 172.17.66.3 -container-port 443

docker-proxy 通过-host-ip指定了docker-proxy在主机上监听的网络接口,通过-host-port指定了监听的端口号;通过-container-ip和-container-port 指定了docker-proxy链接到容器内部的容器ip和端口号

为什么查询不到外部请求到 docker-proxy的tcp连接

Docker 1.7 版本起,Docker 提供了一个配置项: -userland-proxy,以让 Docker 用户决定是否启用 docker-proxy,默认为 true,即启用 docker-proxy。
现在的 Docker 环境默认的是: -userland-proxy=true。iptables 和 docker-proxy 都会起作用。

当-userland-proxy=true时,每设置一对端口映射就会启动一个 docker-proxy 进程。

但是,当我们通过iptables-save -t nat查看iptables nat列表时,会发现所有到docker容器映射端口的请求都会被nat到docker0

-A DOCKER ! -i docker0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.2:80

所以请求不会到达docker-proxy进程,故不会出现到该进程的tcp连接,所以在一定情况下,可以将docker-proxy关闭。

关闭docker-proxy的问题:

1、ipv6场景

docker启动时刻可以通过ipv6参数开启docker ipv6支持功能。开启后所有docker容器都在ipv6下工作。但是此时docker在ipv6上的工作并为完善,docker并未在ipv6table上为容器添加相应的DNAT规则。如果此时关闭docker-proxy,那么容器外部无法访问到容器内部网络。在不借助任何外部手段的情况下(可以使用一个叫ipv6nat工具实现ip6table nat规则的自动添加,但即便如此ipv6场景下,docker-proxy依然有存在意义详见后文场景3),所以此场景下docker-proxy需要开启。

2、在老内核下(2.6.x),容器内部通过hairpin 方式访问自己暴露的服务

在第一章的例子中,如果需要在容器内部访问自己暴露的服务,那么就出现了hairpin DNAT访问方式:

#docker exec -it zxy-nginx /bin/bash
root@8173f601424 #curl http://192.168.126.222:8080
<html>
<head>
<title>Welcome to nginx!</title>
</head>
<body bgcolor=“white“ text=“black”>
<center><h1>Welcome to nginx!</h1></center>
</body>
</html>

可以看到在容器zxy-nginx内通过主机ip+容器映射主机端口方式一样可以访问到zxy-nginx容器自己暴露的nginx服务。
这就是hairpin DNAT模式。但是关闭docker-proxy时刻,数据包进过docker0上的prerouting链时被表3-2的DNAT命中,数据包dst-ip被转换为172.17.0.4,dst-port被转换为80,在docker0的forwarding动作中,判定此包需要送回zxy-nginx在docker0上的网络接口veth17f3d1上。默认情况下,内核bridge不允许将包发送回到源接口的;只有在内核配置了hairpin mode enable时刻,才允许此类操作。在docker处理流程中,如果用户关闭了docker-proxy,那么docker会开启内核的hairpin mode(在centos 7x上通过echo “1”>/sys/class/net/docker0/brif/vethxxx/hairpin_mode开启hairpin模式)。但是在老内核2.6.x上,没有办法启用hairpin mode。所以此时无法借助iptables nat实现容器内部网络可达。此刻就必须使用docker proxy了。关于hairpin模式的解释请参考:https://wiki.mikrotik.com/wiki/Hairpin_NAT

3、在内核无法开启route_localnet的情况下

正常情况下,内核不会对地址为localnet(127.0.0.0/8)的地址做forwarding,因为这部分地址被认为为martian 。但是在内核中可以通过配置

echo “1”>/proc/sys/net/ipv4/conf/$BridgeName/route_localnet 

开启。docker在启动阶段会配置此配置项。但是对于低版本内核无此参数或对于ipv6地址场景(ipv6内核无此配置项,无此功能)内核依然不会对localhost(ipv6下地址为[::1])进行forwarding;所以在此部分场景下,如果需要在主机上,使用localhost:Port 与容器通讯依然需要依赖于docker-proxy