内容接上篇文章 ,我们已经手动创建了两个网络命名空间【也可称为容器】,并用一个网桥将它们连接起来了:
让容器可以 ping 通外网 IP
现在在容器内不能 ping 通我主机上的 eth0【IP是192.168.71.131/24】,也不能到达外网【以外网 IP 114.114.114.114 为例】:
root@ubuntu21:~
ping: connect: Network is unreachable
root@ubuntu21:~
ping: connect: Network is unreachable
root@ubuntu21:~
172.18.0.0/16 dev ceth0 proto kernel scope link src 172.18.0.10
这是因为容器内只有一条主机路由,没有默认路由。
从主机上也不能 ping 通容器:
root@ubuntu21:~
PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data.
^C
--- 172.18.0.10 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1022ms
容器和主机间不能互 ping 的原因在于网桥上没有配置 IP 地址,于是给网桥 br0 配上 IP 172.18.0.1/24:
ip addr add 172.18.0.1/16 dev br0
这样主机和容器间就能互 ping 成功了:
root@ubuntu21:~
PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data.
64 bytes from 172.18.0.10: icmp_seq=1 ttl=64 time=0.238 ms
64 bytes from 172.18.0.10: icmp_seq=2 ttl=64 time=0.087 ms
^C
--- 172.18.0.10 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1030ms
rtt min/avg/max/mdev = 0.087/0.162/0.238/0.075 ms
现在容器内可以 ping 通主机上的网桥,却还是不能 ping 通 eth0 :
root@ubuntu21:~
ping: connect: Network is unreachable
于是我们在容器内添加一条默认路由:
ip netns exec netns0 ip route add default via 172.18.0.1
这下就可以 ping 通 eth0 了:
root@ubuntu21:~
PING 192.168.71.131 (192.168.71.131) 56(84) bytes of data.
64 bytes from 192.168.71.131: icmp_seq=1 ttl=64 time=0.112 ms
64 bytes from 192.168.71.131: icmp_seq=2 ttl=64 time=0.120 ms
^C
--- 192.168.71.131 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1020ms
rtt min/avg/max/mdev = 0.112/0.116/0.120/0.004 ms
现在就是这样的情形:
但是容器内还是 ping 不通外网 IP:
root@ubuntu21:~
PING 114.114.114.114 (114.114.114.114) 56(84) bytes of data.
^C
--- 114.114.114.114 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1001ms
我们稍加分析一下就知道:ping 外网 IP 的时候,ICMP 报文源 IP 是内网 IP 172.18.0.10,这样 ICMP 响应是无法回来的,所以 ping 不通。
有没有办法将出去的报文 IP 修改为出接口上的 IP?有的,使用 iptables 伪装源 IP 就行:
iptables -t nat -A POSTROUTING -s 172.18.0.0/16 ! -o br0 -j MASQUERADE
这下就可以 ping 通外网了:
root@ubuntu21:~
PING 114.114.114.114 (114.114.114.114) 56(84) bytes of data.
64 bytes from 114.114.114.114: icmp_seq=1 ttl=127 time=39.4 ms
64 bytes from 114.114.114.114: icmp_seq=2 ttl=127 time=36.8 ms
^C
--- 114.114.114.114 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 36.807/38.117/39.428/1.310 ms
Docker
我的主机现在安装了 Docker ,查看下 iptables nat 表的规则:
root@ubuntu21:~
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.18.0.0/16 ! -o br0 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
看到这条规则了吗:
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
这是 Docker 安装时添加的规则,是不是和我们加的一模一样!
再查看下 iptables filter 表的规则:
root@ubuntu21:~
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-ISOLATION-STAGE-1
-N DOCKER-ISOLATION-STAGE-2
-N DOCKER-USER
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
因为容器到外部网络的流量要经过 FORWARD 链再走 POSTROUTING 链 ,而FORWARD 链默认策略是丢弃,所以 Docker 添加了这条规则:
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
将容器到外网的流量放行!
让外部网络访问容器内的服务
现在我们在容器内启动一个 http 服务,监听端口 5000:
root@ubuntu21:~
Serving HTTP on 0.0.0.0 port 5000 (http://0.0.0.0:5000/) ...
然后在主机上使用 eth0 的 IP 192.168.71.131 curl 一下【因为外部网络只知道 eth0 的 IP】:
root@ubuntu21:~
curl: (7) Failed to connect to 192.168.71.131 port 5000: Connection refused
提示 5000 端口在 eth0 上没有开放!我们需要将容器内的 5000 端口 publish 到主机上。
我们想到可以用 iptables 修改数据包目的 IP 为 172.18.0.10 ,这样数据包就会发送到容器内了。来验证一下:
root@ubuntu21:~
root@ubuntu21:~
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
...
OK 的。现在主机上可以访问这个服务了,但是从外部网络【另一台虚拟机上】却仍无法访问:
gns3@gns3vm:~$ curl 192.168.71.131:5000
curl: (7) Failed to connect to 192.168.71.131 port 5000: Connection refused
于是还要在 nat 表 PREROUTING 链再添加这条规则:
root@ubuntu21:~
这下就可以访问了:
gns3@gns3vm:~$ curl 192.168.71.131:5000
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
...
Docker
现在我们启动一个 Docker 容器,将主机内的 80 端口 映射到容器内的 80 端口:
docker run -d -p 80:80 docker/getting-started
看下 iptables nat 表规则:
root@ubuntu21:~
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 80 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.2:80
最后在 DOCKER 链增加了一条 DNAT 规则:
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.2:80
是不是和我们加的规则一模一样!
再查看 iptables filter 表:
root@ubuntu21:~
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-ISOLATION-STAGE-1
-N DOCKER-ISOLATION-STAGE-2
-N DOCKER-USER
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER -d 172.17.0.3/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
外部网络的流量进来在做 DNAT 之后要走 FORWARD 链,因为 FORWARD 链默认策略是丢弃,所以 Docker 在 DOCKER 链添加了如下的规则:
-A DOCKER -d 172.17.0.3/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT
放行外部网络访问容器的 TCP 80 端口流量!
|