让外部世界可以访问容器(端口发布)
大家都知道可以将容器端口发布给一些(或者所有)主机的接口。但是端口发布到底是什么意思呢?
假设容器内运行着服务器:
$ sudo nsenter --net=/var/run/netns/netns0 $ python3 -m http.server --bind 172.18.0.10 5000
如果我们试着从主机上发送一个HTTP请求到这个服务器,一切都工作得很好(root命名空间和所有容器接口之间有链接,当然可以连接成功):
# 从 root 命名空间 $ curl 172.18.0.10:5000 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> # ... 忽略无关行 ...
但是,如果要从外部访问这个服务器,应该使用哪个IP呢?我们知道的唯一IP是主机的外部接口地址eth0:
$ curl 10.0.2.15:5000 curl: (7) Failed to connect to 10.0.2.15 port 5000: Connection refused
因此,我们需要找到方法,能够将到达主机eth05000端口的所有包转发到目的地172.18.0.10:5000。又是iptables来帮忙!
# 外部流量 sudo iptables -t nat -A PREROUTING -d 10.0.2.15 -p tcp -m tcp --dport 5000 -j DNAT --to-destination 172.18.0.10:5000 # 本地流量 (因为它没有通过 PREROUTING chain) sudo iptables -t nat -A OUTPUT -d 10.0.2.15 -p tcp -m tcp --dport 5000 -j DNAT --to-destination 172.18.0.10:5000
另外,需要让iptables能够在桥接网络上截获流量:
sudo modprobe br_netfilter
测试:
curl 10.0.2.15:5000 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> # ... 忽略无关行 ...
理解Docker网络驱动
我们可以怎么使用这些知识呢?比如,可以试着理解Docker网络模式。
从--network host模式开始。试着比较一下命令ip link和sudo docker run -it --rm --network host alpine ip link的输出。它们几乎一样!在host模式下,Docker简单地没有使用网络命名空间隔离,容器就在root网络命名空间里工作,并且和主机共享网络栈。
下一个模式是--network none。sudo docker run -it --rm --network host alpine ip link的输出只有一个loopback网络接口。这和之前创建的网络命名空间,没有添加veth设备前很相似。
最后是--network bridge(默认)模式。这正是我们前文尝试创建的模式。大家可以试试ip 和iptables命令,分别从主机和容器的角度观察一下网络栈。
rootless容器和网络
podman容器管理器的一个很好的特性是关注于rootless容器。但是,你可能注意到,本文使用了很多sudo命令。说明,没有root权限无法配置网络。Podman在root网络上的方案和Docker非常相似。但是在rootless容器上,Podman使用了slirp4netns项目:
从Linux 3.8开始,非特权用户可以创建user_namespaces(7)的同时创建network_namespaces(7)。但是,非特权网络命名空间并不是很有用,因为在主机和网络命名空间之间创建veth(4)仍然需要root权限。
slirp4netns可以用完全非特权的方式将网络命名空间连接到Internet上,通过网络命名空间里的一个TAP设备连接到用户态的TCP/IP栈(slirp)。
rootless网络是很有限的:“从技术上说,容器本身没有IP地址,因为没有root权限,无法实现网络设备的关联。另外,从rootless容器ping是不会工作的,因为它缺少CAP_NET_RAW安全能力,而这是ping命令必需的。”但是它仍然比完全没有连接要好。
结论
本文介绍的组织容器网络的方案仅仅是可能方案的一种(可能是最为广泛使用的一种)。还有很多别的方式,由官方或者第三方插件实现,但是所有这些方案都严重依赖于Linux网络虚拟化技术。因此,容器化可以认为是一种虚拟化技术。