前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >容器网络基础(一)

容器网络基础(一)

原创
作者头像
智零办法就是多
发布2022-04-13 22:18:36
1.4K0
发布2022-04-13 22:18:36
举报
文章被收录于专栏:技术私享会技术私享会
  1. 容器网络基础

我们一定听过容器的基础原理,namespace做隔离,Cgroups做限制,rootfs做文件系统,容器本质上是linux的一个进程,那么为什么大多数场景下,容器不直接使用宿主机上的网络,而要是通过network namespace隔离出一组专属的网络空间呢?(容器的基础原理,可参考:https://coolshell.cn/articles/17010.html)

原因很简单,直接使用宿主机的网络,会带来资源冲突的问题,比如:容器与宿主机的端口冲突。大多数情况下,我们都希望容器能够使用自己network namespace里的网络栈,及拥有自己的IP地址加端口。

那么,此时,在宿主机上的容器网络就面临着需要解决以下几个问题:

  • 怎样虚隔离网络资源,以确保容器运行在自己专属的网络栈中?
  • 怎样确保不同的容器间能正常通信,却又不会相互影响?
  • 容器怎样访问外界的网络,如:internet?
  • 外界怎样访问容器上部署的服务?

好在,linux操作系统了提供了一些列工具,可以帮助我们完美的解决这些问题。接下来,我们在linux宿主机上使用系统自带的工具,来运行一个仅仅包括网络栈的容器进行验证。包括以下几个组件:

  • network namespace;
  • virtual Ethernet devices(veth);虚拟以太网设备
  • vertual network switch(bridge);虚拟网桥
  • IP routing and network address translation。

1.1 通过network namespace进行网络隔离

linux完成网络通信所需的网络栈包括了:网卡(Network interface),回环设备(Loopback Device),路由表(Routing Table)和iptables。对于一个进程来说,这些要素,构成了其网络通信的基础环境。

我们可以通过以下命令查看自己的,网络栈:

代码语言:javascript
复制
$ ifconfig
$ ip route
$ iptables --list-rules

为了便于对比root和我们即将要创建的容器network namespace,我们将现有iptables添加一条规则,以便于区分宿主机和容器的运行环境。此时,通过执行iptables --list-rules可以看到多了一条-N ROOT_NS.

现在,我们来创建一个network namaspace netns0,可以通过linux 的 ip 工具来实现。

代码语言:javascript
复制
$ sudo ip netns add netns0

那么,怎么使用刚刚我们创建的network namespace netns0呢? linux提供了相应的工具nsenter,顾名思义,nsenter提供了进入其他ns执行给定命令的权限。我们进入容器netns0的命名空间。然后查看基础网络栈信息。

代码语言:javascript
复制
$ sudo nsenter --net=/var/run/netns/netns0 
$ ifconfig -a
$ ip route
$ iptables --list-rules

可以看到,我们在netns0 namespace只能看到lo设备,不能看到eth0,并且iptables中,是看不到-N ROOT_NS,由此可知,网络已经与root进行了隔离。

下图,总结了容器network ns和宿主机 ns的区别。

1.2 通过veth连接容器和主机网络

我们已经创建了容器专属的network namespace netns0,此时的netns0是无法和主机进行通信的。幸运的是,linux提供了veth-pair来帮助我们。Veth Pair的特点是:它被创建出来后,总是以两张虚拟网卡(Veth Peer)的形式成对出现的。并且,从其中一个“网卡”发出的数据包,可以直接出现在与它对应的另一张“网卡”上,哪怕这两个“网卡”在不同的NS里

这使得Veth Pair常常被用作连接不同Network Namespace的“网线”。

通过以下命令,我们可以创建一对相互连接的veth pair:veth0和ceth0.

代码语言:javascript
复制
$ sudo ip link add veth0 type veth peer name ceth0
$ ip link

当veth0和ceth0创建出来后,全部归属在宿主机 network namespace。如果需要netns0和宿主机network namespace互通,需要将veth pair的一端绑定到netns0上,一端继续绑定在宿主机network namespace上。可通过如下命令实现:

代码语言:javascript
复制
$ sudo ip link set ceth0 netns netns0

一旦我们启动 “veth pair”,从一端“网卡”发出去的包会立刻出现在相对应的另一端”网卡“上,我们从宿主机namespace开始,设置root端IP地址为172.18.0.11/16。

代码语言:javascript
复制
$ sudo ip link set veth0 up
$ sudo ip addr add 172.18.0.11/16 dev veth0

然后,继续配置netns0. 启动lo设备

代码语言:javascript
复制
$ sudo nsenter --net=/var/run/netns/netns0
$ ip link set lo up
$ ip addr add 172.18.0.10/16 dev ceth0
$ ip link

我们来检查一下网络的联通性。在netns0内部,ping宿主机ip:172.18.0.11

退出netns0,在宿主机 ns内ping netns0 ip : 172.18.0.10

此时,我们在netns0中,访问任何其他的网络均不可达,比如,ping宿主机ip:10.0.0.15

问题很简单,因为我们没有访问其他地址的路由表,可通过查看netns0路由表来获知。

目前为止,我们已经知道了如何进行隔离、虚拟化以及连通linux的网络栈。

1.3 容器间的通信问题(bridge)

现在,我们再次新建一个”容器“,来解决容器间的通信问题。

代码语言:javascript
复制
# From root namespace 
$ 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的连通性,发现在nsnet1内部,无法ping 通宿主机网络IP:172.18.0.21. 在宿主机network namespace内,也无法ping通nsnet1的网络。并且检查nsnet1路由表,发现路由正确。

推出netns1,我们再次核查root 的路由表,可清晰的发现问题:原来是路有冲突,有2条匹配172.18.0.0/16网段的路由,并且第一条的路由优先级高于第二条。所以宿主机ns和netns1 ns无法连通。

那怎么解决该问题呢?如果选择另一个网段,那自然不会有任何冲突,但实际中,多个容器处于同一网段司空见惯,必须要解决其网络通信问题。好在,linux提供了网桥(bridge)帮我们解决该问题。

网桥:在linux中能够起到虚拟交换机的作用,工作在L2(数据链路层),主要功能是根据MAC地址学习来将数据包转发到网桥的不同端口上。

从前面的章节中可以得知,网桥:类似于交换机,实现L2层通信,Veth: 类似于网线,实现了点到点的通信。那么,怎么将容器连在交换机(网桥)上呢,实际中,当然要靠网线(veth pair),我们通过实验来验证。

我们先将前面的实验环境清空:

代码语言:javascript
复制
sudo ip netns delete netns0
sudo ip netns delete netns1

然后,我们快速创建2个容器,与之前的区别是,我们不为veth0和veth1分配任何IP地址。

代码语言:javascript
复制
$ 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

检查一下,确保宿主机上没有新增路由。

代码语言:javascript
复制
$ ip route

最后,开始创建并启动网桥br0。

代码语言:javascript
复制
$ sudo ip link add br0 type bridge
$ sudo ip link set br0 up

现在,将veth0和veth1”连“在网桥上。

代码语言:javascript
复制
$ sudo ip link set veth0 master br0
$ sudo ip link set veth1 master br0

再来检查一下两个容器间网络的连通性。可以发现,此时,两个容器间的网络已经可以互通。为什么不需要配置ip地址,只是”连“在交换机(网桥)上,两个容器也可以互通了呢?

回顾一下前面的知识,网桥类似一个交换机,是通过MAC地址来将数据包发送到不同的接口上。

1.4 容器访问外部网络

通过前面的实验,我们已经解决了容器间通过网桥bridge和veth pair进行互通的问题,此时,我们试着在容器内访问宿主机网络,会发现网络不可达。

原因很简单,查看容器netns0路由表可发现我们并没有前往宿主机网段10.0.0.0网段的路由。

同理,宿主机ns也无法和容器ns进行通信。

为了在宿主机和容器ns间实现连接,我们需要为网桥分配IP地址,作为容器netns0和netns1的网关。

代码语言:javascript
复制
$ ip addr add 172.18.0.1/16 dev br0

可以发现,完成添加IP地址后,IP route会自动出现新路由条目。

进入nsnet0和nsnet1添加默认路由做bridge网关后,再次进行root和容器ns间联通性测试。

此时,root和容器ns,以及容器之间已完成互通。

我们知道,内网和外网是无法直接互通的,因为容器在内网,外网并不知道容器的回程路由,只能知道信息来自宿主机的公网IP。所以,容器要访问外网,就需要进行NAT转换。好在,我们可以通过linux的iptables来实现NAT的转换。

linux操作系统,默认情况下,禁止了IP转发功能,可以通过如下命令打开。

代码语言:javascript
复制
$ sudo bash -c 'echo 1 > /proc/sys/net/ipv4/ip_forward'

然后,执行iptables指令,增加一条NAT转发规则。

代码语言:javascript
复制
$ sudo iptables -t nat -A POSTROUTING -s 172.18.0.0/16 ! -o br0 -j MASQUERADE

此时,进入容器内部,ping公网IP进行测试,发现容器已可访问公网。

1.5 外部访问容器服务

我们在容器启动一个简单的python3 http服务。

代码语言:javascript
复制
$ sudo nsenter --net=/var/run/netns/netns0
$ python3 -m http.server --bind 172.18.0.10 80

如果我们从主机的宿主机 ns发送http请求到netns0,会发现该应用服务正常工作。

如果我们从外部访问该http服务,我们用哪个IP地址呢?私有IP地址我们不能用,那么能用的只有宿主机的公网IP地址了。此时,我们可以测试一下访问宿主机IP地址时http服务是否正常。发现并不可达。

要解决这个问题,我们需要将容器的端口发布到宿主机的eth0网卡上,可以通过iptables工具来实现。

代码语言:javascript
复制
# External traffic 
$ sudo iptables -t nat -A PREROUTING -d 10.0.0.15 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.18.0.10:80  
# Local traffic (since it doesn't pass the PREROUTING chain) 
$ sudo iptables -t nat -A OUTPUT -d 10.0.0.15 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.18.0.10:80

然后,启动iptables intercepting traffic over bridged networks功能

代码语言:javascript
复制
sudo modprobe br_netfilter

再次进行测试。

1.6 小结与思考

我们通过一系列实验,对单机容器网络的基础原理进行了初步的探索,我们来回顾一下。

  1. 通过network namespace进行容器网络的隔离,这时,可以创建出孤立的容器,无法和外界通信。
  2. 容器想和外界通信,它发出的网络包必须从自己的ns中出来,通过宿主机和外界通信。其中,veth pair起到了网线的作用,可以连接宿主机和容器,并可以连接到网桥(交换机)上。
  3. 网桥,在linux系统中起到了交换机的作用,可以作为容器和外界通信的网关。容器使用的网桥叫docker0.
  4. 容器想要访问外界,还必须设置ip路由和转发规则。
  5. 容器对外提供服务,需要将端口发布到宿主机,并要进行合理的转发规则设置。

本章主要介绍的是容器bridge网络模式,实际上,容器的网络不是远远不止一种。容器还可以直接使用宿主机的网络,也可以使用overlay技术,具体可参考容器网络模式的介绍:https://docs.docker.com/network/

  1. 腾讯云容器网络介绍
  2. 腾讯云容器网络最佳实践

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.1 通过network namespace进行网络隔离
  • 1.2 通过veth连接容器和主机网络
  • 1.3 容器间的通信问题(bridge)
  • 1.4 容器访问外部网络
  • 1.5 外部访问容器服务
  • 1.6 小结与思考
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档