怎么从传统的Linux网络视角理解容器网络?《二》

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器镜像服务 ACR,镜像仓库100个 不限时长
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: 因为问题较多,关注我查看第一篇内容即可

使用虚拟网络switch(网桥)连接容器

容器化思想的驱动力是高效的资源共享。所以,一台机器上只运行一个容器并不常见。相反,最终目标是尽可能地在共享的环境上运行更多的隔离进程。因此,如果按照上述veth方案,在同一台主机上放置多个容器的话会发生什么呢?让我们尝试添加第二个容器。

# 从 root 命名空间   
    $ sudo ip netns add netns1   
    $ sudo ip link add veth1 type veth peer name ceth1   
    $ sudo ip link set ceth1 netns netns1   
    $ sudo ip link set veth1 up   
    $ sudo ip addr add 172.18.0.21/16 dev veth1   
    $ sudo nsenter --net=/var/run/netns/netns1   
    $ ip link set lo up   
    $ ip link set ceth1 up   
    $ ip addr add 172.18.0.20/16 dev ceth1

检查连通性:

# 从netns1无法连通root 命名空间!   
    $ ping -c 2 172.18.0.21   
    PING 172.18.0.21 (172.18.0.21) 56(84) bytes of data.   
    From 172.18.0.20 icmp_seq=1 Destination Host Unreachable   
    From 172.18.0.20 icmp_seq=2 Destination Host Unreachable   
    --- 172.18.0.21 ping statistics ---   
    2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 55ms pipe 2   
    # 但是路由是存在的!   
    $ ip route   
    172.18.0.0/16 dev ceth1 proto kernel scope link src 172.18.0.20   
    # 离开 `netns1`   
    $ exit    
    # 从 root 命名空间无法连通`netns1`   
    $ ping -c 2 172.18.0.20   
    PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data.   
    From 172.18.0.11 icmp_seq=1 Destination Host Unreachable   
    From 172.18.0.11 icmp_seq=2 Destination Host Unreachable   
--- 172.18.0.20 ping statistics ---   
2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 23ms pipe 2   
    # 从netns0可以连通 `veth1`   
    $ sudo nsenter --net=/var/run/netns/netns0   
    $ ping -c 2 172.18.0.21   
    PING 172.18.0.21 (172.18.0.21) 56(84) bytes of data.   
    64 bytes from 172.18.0.21: icmp_seq=1 ttl=64 time=0.037 ms   
    64 bytes from 172.18.0.21: icmp_seq=2 ttl=64 time=0.046 ms   
    --- 172.18.0.21 ping statistics ---   
    2 packets transmitted, 2 received, 0% packet loss, time 33ms   
    rtt min/avg/max/mdev = 0.037/0.041/0.046/0.007 ms   
    # 但是仍然无法连通netns1   
    $ ping -c 2 172.18.0.20   
    PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data.   
    From 172.18.0.10 icmp_seq=1 Destination Host Unreachable   
    From 172.18.0.10 icmp_seq=2 Destination Host Unreachable   
    --- 172.18.0.20 ping statistics ---   
    2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 63ms pipe 2

晕!有地方出错了……netns1有问题。它无法连接到root,并且从root命名空间里也无法访问到它。但是,因为两个容器都在相同的IP网段172.18.0.0/16里,从netns0容器可以访问到主机的veth1。

这里花了些时间来找到原因,不过很明显遇到的是路由问题。先查一下root命名空间的路由表:

$ ip route   
    # ... 忽略无关行... #   
    172.18.0.0/16 dev veth0 proto kernel scope link src 172.18.0.11   
    172.18.0.0/16 dev veth1 proto kernel scope link src 172.18.0.21

在添加了第二个veth对之后,root的网络栈知道了新路由172.18.0.0/16 dev veth1 proto kernel scope link src 172.18.0.21,但是之前已经存在该网络的路由了。当第二个容器尝试ping veth1时,选中的是第一个路由规则,这导致网络无法连通。如果我们删除第一个路由sudo ip route delete 172.18.0.0/16 dev veth0 proto kernel scope link src 172.18.0.11,然后重新检查连通性,应该就没有问题了。netns1可以连通,但是netns0就不行了。


如果我们为netns1选择其他的网段,应该就都可以连通。但是,多个容器在同一个IP网段上应该是合理的使用场景。因此,我们需要调整veth方案。

别忘了还有Linux网桥——另一种虚拟化网络技术!Linux网桥作用类似于网络switch。它会在连接到其上的接口间转发网络包。并且因为它是switch,它是在L2层完成这些转发的。

试试这个工具。但是首先,需要清除已有设置,因为之前的一些配置现在不再需要了。删除网络命名空间:

$ sudo ip netns delete netns0   
$ sudo ip netns delete netns1   
$ sudo ip link delete veth0   
$ sudo ip link delete ceth0   
$ sudo ip link delete veth1   
$ sudo ip link delete ceth1

快速重建两个容器。注意,我们没有给新的veth0和veth1设备分配任何IP地址:

$ sudo ip netns add netns0   
$ sudo ip link add veth0 type veth peer name ceth0   
$ sudo ip link set veth0 up   
$ sudo ip link set ceth0 netns netns0   
$ sudo nsenter --net=/var/run/netns/netns0   
$ ip link set lo up   
$ ip link set ceth0 up   
$ ip addr add 172.18.0.10/16 dev ceth0   
$ exit   
$ sudo ip netns add netns1   
$ sudo ip link add veth1 type veth peer name ceth1   
$ sudo ip link set veth1 up   
$ sudo ip link set ceth1 netns netns1   
$ sudo nsenter --net=/var/run/netns/netns1   
$ ip link set lo up   
$ ip link set ceth1 up   
$ ip addr add 172.18.0.20/16 dev ceth1   
$ exit

确保主机上没有新的路由:

$ ip route   
default via 10.0.2.2 dev eth0 proto dhcp metric 100   
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100

最后创建网桥接口:

$ sudo ip link add br0 type bridge   
$ sudo ip link set br0 up

将veth0和veth1接到网桥上:

$ sudo ip link set veth0 master br0   
$ sudo ip link set veth1 master br0


检查容器间的连通性:

$ sudo nsenter --net=/var/run/netns/netns0   
$ ping -c 2 172.18.0.20   
PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data.   
64 bytes from 172.18.0.20: icmp_seq=1 ttl=64 time=0.259 ms   
64 bytes from 172.18.0.20: icmp_seq=2 ttl=64 time=0.051 ms   
--- 172.18.0.20 ping statistics ---   
2 packets transmitted, 2 received, 0% packet loss, time 2ms   
rtt min/avg/max/mdev = 0.051/0.155/0.259/0.104 ms
$ sudo nsenter --net=/var/run/netns/netns1   
$ ping -c 2 172.18.0.10   
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.037 ms   
64 bytes from 172.18.0.10: icmp_seq=2 ttl=64 time=0.089 ms   
--- 172.18.0.10 ping statistics ---   
2 packets transmitted, 2 received, 0% packet loss, time 36ms   
rtt min/avg/max/mdev = 0.037/0.063/0.089/0.026 ms

太好了!工作得很好。用这种新方案,我们根本不需要配置veth0和veth1。只需要在ceth0和ceth1端点分配两个IP地址。但是因为它们都连接在相同的Ethernet上(记住,它们连接到虚拟switch上),之间在L2层是连通的:

$ sudo nsenter --net=/var/run/netns/netns0   
$ ip neigh   
172.18.0.20 dev ceth0 lladdr 6e:9c:ae:02:60:de STALE   
$ exit   
$ sudo nsenter --net=/var/run/netns/netns1   
$ ip neigh   
172.18.0.10 dev ceth1 lladdr 66:f3:8c:75:09:29 STALE   
$ exit

太好了,我们学习了如何将容器变成友邻,让它们互不干扰,但是又可以连通。

连接外部世界(IP路由和地址伪装)

容器间可以通信。但是它们能和主机,比如root命名空间,通信吗?

$ sudo nsenter --net=/var/run/netns/netns0   
$ ping 10.0.2.15 # eth0 address   
connect: Network is unreachable

这里很明显,netns0没有路由:

$ ip route   
172.18.0.0/16 dev ceth0 proto kernel scope link src 172.18.0.10

root命名空间不能和容器通信:

# 首先使用 exit 离开netns0:   
    $ ping -c 2 172.18.0.10   
    PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data.   
    From 213.51.1.123 icmp_seq=1 Destination Net Unreachable   
    From 213.51.1.123 icmp_seq=2 Destination Net Unreachable   
    --- 172.18.0.10 ping statistics ---   
    2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 3ms   
$ ping -c 2 172.18.0.20   
PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data.   
From 213.51.1.123 icmp_seq=1 Destination Net Unreachable   
From 213.51.1.123 icmp_seq=2 Destination Net Unreachable   
--- 172.18.0.20 ping statistics ---   
2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 3ms

要建立root和容器命名空间的连通性,我们需要给网桥网络接口分配IP地址:

$ sudo ip addr add 172.18.0.1/16 dev br0

一旦给网桥网络接口分配了IP地址,在主机的路由表里就会多一条路由:

$ ip route   
    # ...忽略无关行 ...   
    172.18.0.0/16 dev br0 proto kernel scope link src 172.18.0.1   
$ ping -c 2 172.18.0.10   
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.036 ms   
64 bytes from 172.18.0.10: icmp_seq=2 ttl=64 time=0.049 ms   
--- 172.18.0.10 ping statistics ---   
2 packets transmitted, 2 received, 0% packet loss, time 11ms   
rtt min/avg/max/mdev = 0.036/0.042/0.049/0.009 ms   
$ ping -c 2 172.18.0.20   
PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data.   
64 bytes from 172.18.0.20: icmp_seq=1 ttl=64 time=0.059 ms   
64 bytes from 172.18.0.20: icmp_seq=2 ttl=64 time=0.056 ms   
--- 172.18.0.20 ping statistics ---   
2 packets transmitted, 2 received, 0% packet loss, time 4ms   
rtt min/avg/max/mdev = 0.056/0.057/0.059/0.007 ms

容器可能也可以ping网桥接口,但是它们还是无法连接到主机的eth0。需要为容器添加默认的路由:

$ sudo nsenter --net=/var/run/netns/netns0   
$ ip route add default via 172.18.0.1   
$ ping -c 2 10.0.2.15   
PING 10.0.2.15 (10.0.2.15) 56(84) bytes of data.   
64 bytes from 10.0.2.15: icmp_seq=1 ttl=64 time=0.036 ms   
64 bytes from 10.0.2.15: icmp_seq=2 ttl=64 time=0.053 ms   
--- 10.0.2.15 ping statistics ---   
2 packets transmitted, 2 received, 0% packet loss, time 14ms   
rtt min/avg/max/mdev = 0.036/0.044/0.053/0.010 ms   
    # 为\`netns1\`也做上述配置

这个改动基本上把主机变成了路由,并且网桥接口变成了容器间的默认网关。


很好,我们将容器连接到root命名空间上。现在,继续尝试将它们连接到外部世界。Linux上默认disable了网络包转发(比如,路由功能)。我们需要先启用这个功能:

# 在 root 命名空间   
    sudo bash -c 'echo 1 > /proc/sys/net/ipv4/ip_forward'

再次检查连通性:

$ sudo nsenter --net=/var/run/netns/netns0   
$ ping 8.8.8.8   
    # hung住了...

还是不工作。哪里弄错了呢?如果容器可以向外部发包,那么目标服务器无法将包发回容器,因为容器的IP地址是私有的,那个特定IP的路由规则只有本地网络知道。并且有很多容器共享的是完全相同的私有IP地址172.18.0.10。这个问题的解决方法称为网络地址翻译(NAT)。

在到达外部网络之前,容器发出的包会将源IP地址替换为主机的外部网络地址。主机还会跟踪所有已有的映射,会在将包转发回容器之前恢复之前被替换的IP地址。听上去很复杂,但是有一个好消息!iptables模块让我们只需要一条命令就可以完成这一切:

$ sudo iptables -t nat -A POSTROUTING -s 172.18.0.0/16 ! -o br0 -j MASQUERADE

命令非常简单。在nat表里添加了一条POSTROUTING chain的新路由,会替换伪装所有源于172.18.0.0/16网络的包,但是不通过网桥接口。

检查连通性:

$ sudo nsenter --net=/var/run/netns/netns0   
$ ping -c 2 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.   
64 bytes from 8.8.8.8: icmp_seq=1 ttl=61 time=43.2 ms   
64 bytes from 8.8.8.8: icmp_seq=2 ttl=61 time=36.8 ms   
--- 8.8.8.8 ping statistics ---   
2 packets transmitted, 2 received, 0% packet loss, time 2ms   
rtt min/avg/max/mdev = 36.815/40.008/43.202/3.199 ms

要知道这里我们用的默认策略——允许所有流量,这在真实的环境里是非常危险的。主机的默认iptables策略是ACCEPT:

sudo iptables -S   
-P INPUT ACCEPT   
-P FORWARD ACCEPT   
-P OUTPUT ACCEPT

Docker默认限制所有流量,随后仅仅为已知的路径启用路由。

如下是在CentOS 8机器上,单个容器暴露了端口5005时,由Docker daemon生成的规则:

$ sudo iptables -t filter --list-rules   
-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.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 5000 -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   
$ sudo iptables -t nat --list-rules   
-P PREROUTING ACCEPT   
-P INPUT ACCEPT   
-P POSTROUTING ACCEPT   
-P OUTPUT ACCEPT   
-N DOCKER   
-A PREROUTING -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 5000 -j MASQUERADE  
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER   
-A DOCKER -i docker0 -j RETURN   
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 5005 -j DNAT --to-destination 172.17.0.2:5000   
$ sudo iptables -t mangle --list-rules   
-P PREROUTING ACCEPT   
-P INPUT ACCEPT   
-P FORWARD ACCEPT   
-P OUTPUT ACCEPT  
 -P POSTROUTING ACCEPT   
$ sudo iptables -t raw --list-rules   
-P PREROUTING ACCEPT   
-P OUTPUT ACCEPT
目录
相关文章
|
22天前
|
监控 Ubuntu Unix
Linux |Nethogs 监控网络使用情况
Linux |Nethogs 监控网络使用情况
46 9
Linux |Nethogs 监控网络使用情况
|
1天前
|
网络协议 Linux 网络安全
遇到Docker容器网络隔断?揭秘六种超级实用解决方案,轻松让Docker容器畅游互联网!
【8月更文挑战第18天】Docker容器内网络不通是开发者常遇问题,可能因网络配置错、Docker服务异常或防火墙阻碍等原因引起。本文提供六种解决策略:确认Docker服务运行状态、重启Docker服务、检查与自定义Docker网络设置、验证宿主机网络连接、临时禁用宿主机IPv6及检查防火墙规则。通过这些步骤,多数网络问题可得以解决,确保容器正常联网。
7 1
|
5天前
|
弹性计算 Prometheus 监控
如何基于容器网络流量指标进行弹性伸缩
【8月更文挑战第13天】基于容器网络流量指标进行弹性伸缩可动态调整资源,提升系统性能与利用率。首先选监控工具如Prometheus,收集并分析网络流量数据。接着定义监控指标及阈值,如入站与出站流量。最后配置如Kubernetes的HPA实现自动化伸缩,并通过测试不断优化策略,确保系统稳定高效运行。
|
6天前
|
缓存 安全 Linux
本地YUM源大揭秘:搭建您自己的Linux软件宝库,从此告别网络依赖!一文掌握服务器自给自足的终极技能!
【8月更文挑战第13天】在Linux中,YUM是一款强大的软件包管理工具,可自动处理依赖关系。为适应离线或特定安全需求,本指南教你搭建本地YUM源。首先创建存放软件包的`localrepo`目录,复制`.rpm`文件至其中。接着,安装并运用`createrepo`生成仓库元数据。随后配置新的`.repo`文件指向该目录,并禁用GPG检查。最后,清理并重建YUM缓存,即可启用本地YUM源进行软件搜索与安装,适用于网络受限环境。
24 3
|
15天前
|
Linux 程序员 测试技术
详解Linux中的容器技术
【8月更文挑战第4天】容器技术依赖两大核心:namespace(命名空间)实现逻辑隔离,如IP地址与用户空间的不同视图;cgroup(控制组)则确保资源如CPU和内存的配额使用。
|
13天前
|
SQL 安全 算法
网络安全与信息安全:漏洞、加密技术与安全意识的综合视角
【8月更文挑战第6天】在数字化时代,网络安全和信息安全成为维护个人隐私和企业资产的关键防线。本文将深入探讨网络安全漏洞的成因与影响,分析加密技术的工作原理及其在数据保护中的作用,并强调提升安全意识的必要性。通过综合这三个维度,我们旨在为读者提供一个全面的网络安全和信息安全知识框架,帮助他们在日益复杂的网络环境中保持警惕,采取有效措施保护自身和组织的安全。
|
12天前
|
Linux 调度 Docker
容器网络概述
【8月更文挑战第7天】容器就是 Container,而 Container 的另一个意思是集装箱。其实容器的思想就是要变成软件交付的集装箱。集装箱的特点,一是打包,二是标准。
|
24天前
|
关系型数据库 MySQL 数据库
容器网络管理
容器网络管理
25 2
|
4天前
|
Docker 容器
Docker - 网络模式与容器网络互连
Docker的网络模式包括桥接模式、主机模式和覆盖网络模式,以及如何通过Docker的网络操作命令实现容器网络互连。
9 0
|
7天前
|
Kubernetes 网络协议 Linux
容器跨主机通信:Flannel网络实现机制分析(二)
容器跨主机通信:Flannel网络实现机制分析(二)
18 0