docker容器技术实现网络隔离-Network namespace

Network namespace是内核支持的一种网络虚拟方式,可以在一个操作系统中创建多个网络命名空间,每个网络空间都有一个独立的协议栈。

网络命名可以通过用户工具ip管理,ip 命令管理的功能很多, 和 network namespace 有关的操作都是在子命令 ip netns 下进行的,可以通过 ip netns help` 查看所有操作的帮助信息。

Usage: ip netns list               #查看网络命名空间
       ip netns add NAME           #创建网络命名空间
       ip netns set NAME NETNSID   
       ip [-all] netns delete [NAME] #删除网络命名空间
       ip netns identify [PID]
       ip netns pids NAME             #查看指定命名空间的进程ID
       ip [-all] netns exec [NAME] cmd ... #在指定网络命名空间执行命令
       ip netns monitor
       ip netns list-id

创建网络命名空间

ip netns add net1  #创建网络命名空间net1

ip netns list      #查看网络命名空间

ls /var/run/netns/ #查看网络命名空间的链接文件

ip netns 命令创建的 network namespace ,会在 /var/run/netns/ 目录下创建一个net1的关联文件,在这个新创建的网络命名空间会有独立的网卡,arp表,路由表、iptables规则等网络相关属性。通过如下命令可以查看。

ip netns exec net1 ip addr #查看网卡信息

ip netns exec net1 arp -a  #查看arp表信息

ip netns exec net1 ip route #查看路由信息

network namespace 之间通信

有了不同 network namespace 之后,也就有了网络的隔离,要把两个网络连接起来,linux 提供了 veth pair 。可以把 veth pair 当做是双向的 pipe(管道),从一个方向发送的网络数据,可以直接被另外一端接收到;或者也可以想象成两个 namespace 直接通过一个特殊的虚拟网卡连接起来,可以直接通信。

ip netns add net2 #创建另外一个网络命名空间net2

ip netns list #查看创建的网络命名空间,此时存在net1和net2两个网络命名空间

ip link add type veth #创建虚拟网卡对

ip link set veth1 netns net1 #将虚拟网卡对一端添加到net1网络命名空间

ip link set veth0 netns net2 #将虚拟网卡对的另外一端添加到net2网络命名空间

ip netns exec net1 ip addr  #查看虚拟网卡对在网络命名空间的信息
ip netns exec net2 ip addr   #查看虚拟网卡对在网络命名空间的信息

#配置两个虚拟网卡在各自命名空间的地址信息
ip netns exec net1 ip link set veth1 up
ip netns exec net1 ip addr add 10.0.0.1/24 dev veth1
ip netns exec net2 ip link set veth0 up
ip netns exec net2 ip addr add 10.0.0.2/24 dev veth1

#验证测试
ip netns exec net1 ping 10.0.0.2
ip netns exec net2 ping 10.0.0.1

这样就可以实现两个网络命名空间的通讯了

不过,通常情况下,相同宿主机之间的docker容器可以通过网桥互联

#创建网桥
brctl addbr netbr

ip link set netbr up

#创建虚拟网卡对,执行两次,一端接入网桥,一端接入对应的网络命令空间
ip link add type veth

#虚拟网卡对接入网络命名空间
ip link set veth0 netns net1
ip link set veth3 netns net2

ip netns exec net1 ip link set lo up
ip netns exec net1 ip link set veth0 up

ip netns exec net2 ip link set lo up
ip netns exec net2 ip link set veth3 up

#虚拟网卡对接入网桥
brctl addif netbr veth1
brctl addif netbr veth2
ip netns exec net1 ip addr add 10.0.0.1/24 dev veth0
ip netns exec net2 ip addr add 10.0.0.2/24 dev veth3

#验证联通性
ip netns exec net1 ping 10.0.0.2 

veth-pair 是什么

顾名思义,veth-pair 就是一对的虚拟设备接口,和 tap/tun 设备不同的是,它都是成对出现的。一端连着协议栈,一端彼此相连着。如下图所示:

正因为有这个特性,它常常充当着一个桥梁,连接着各种虚拟网络设备,典型的例子像“两个 namespace 之间的连接”,“Bridge、OVS 之间的连接”,“Docker 容器之间的连接” 等等,以此构建出非常复杂的虚拟网络结构,比如 OpenStack Neutron。

veth-pair 的连通性

我们看下整个通信流程就明白了。

  1. 首先 ping 程序构造 ICMP echo request,通过 socket 发给协议栈。
  2. 由于 ping 指定了走 veth0 口,如果是第一次,则需要发 ARP 请求,否则协议栈直接将数据包交给 veth0。
  3. 由于 veth0 连着 veth1,所以 ICMP request 直接发给 veth1。
  4. veth1 收到请求后,交给另一端的协议栈。
  5. 协议栈看本地有 10.1.1.3 这个 IP,于是构造 ICMP reply 包,查看路由表,发现回给 10.1.1.0 网段的数据包应该走 localback 口,于是将 reply 包交给 lo 口(会优先查看路由表的 0 号表,ip route show table 0 查看)。
  6. lo 收到协议栈的 reply 包后,啥都没干,转手又回给协议栈。
  7. 协议栈收到 reply 包之后,发现有 socket 在等待包,于是将包给 socket。
  8. 等待在用户态的 ping 程序发现 socket 返回,于是就收到 ICMP 的 reply 包。

整个过程如下图所示:

03 两个 namespace 之间的连通性#

namespace 是 Linux 2.6.x 内核版本之后支持的特性,主要用于资源的隔离。有了 namespace,一个 Linux 系统就可以抽象出多个网络子系统,各子系统间都有自己的网络设备,协议栈等,彼此之间互不影响。

如果各个 namespace 之间需要通信,怎么办呢,答案就是用 veth-pair 来做桥梁。

根据连接的方式和规模,可以分为“直接相连”,“通过 Bridge 相连” 和 “通过 OVS 相连”。

3.1 直接相连#

直接相连是最简单的方式,如下图,一对 veth-pair 直接将两个 namespace 连接在一起。

# 创建 namespace
ip netns a ns1
ip netns a ns2

# 创建一对 veth-pair veth0 veth1
ip l a veth0 type veth peer name veth1

# 将 veth0 veth1 分别加入两个 ns
ip l s veth0 netns ns1
ip l s veth1 netns ns2

# 给两个 veth0 veth1 配上 IP 并启用
ip netns exec ns1 ip a a 10.1.1.2/24 dev veth0
ip netns exec ns1 ip l s veth0 up
ip netns exec ns2 ip a a 10.1.1.3/24 dev veth1
ip netns exec ns2 ip l s veth1 up

# 从 veth0 ping veth1
[root@localhost ~]# ip netns exec ns1 ping 10.1.1.3
PING 10.1.1.3 (10.1.1.3) 56(84) bytes of data.
64 bytes from 10.1.1.3: icmp_seq=1 ttl=64 time=0.073 ms
64 bytes from 10.1.1.3: icmp_seq=2 ttl=64 time=0.068 ms

--- 10.1.1.3 ping statistics ---
15 packets transmitted, 15 received, 0% packet loss, time 14000ms
rtt min/avg/max/mdev = 0.068/0.084/0.201/0.032 ms

3.2 通过 Bridge 相连#

Linux Bridge 相当于一台交换机,可以中转两个 namespace 的流量,我们看看 veth-pair 在其中扮演什么角色。

如下图,两对 veth-pair 分别将两个 namespace 连到 Bridge 上。

# 首先创建 bridge br0
ip l a br0 type bridge
ip l s br0 up 

# 然后创建两对 veth-pair
ip l a veth0 type veth peer name br-veth0
ip l a veth1 type veth peer name br-veth1

# 分别将两对 veth-pair 加入两个 ns 和 br0
ip l s veth0 netns ns1
ip l s br-veth0 master br0
ip l s br-veth0 up

ip l s veth1 netns ns2
ip l s br-veth1 master br0
ip l s br-veth1 up

# 给两个 ns 中的 veth 配置 IP 并启用
ip netns exec ns1 ip a a 10.1.1.2/24 dev veth0
ip netns exec ns1 ip l s veth0 up

ip netns exec ns2 ip a a 10.1.1.3/24 dev veth1
ip netns exec ns2 ip l s veth1 up

# veth0 ping veth1
[root@localhost ~]# ip netns exec ns1 ping 10.1.1.3
PING 10.1.1.3 (10.1.1.3) 56(84) bytes of data.
64 bytes from 10.1.1.3: icmp_seq=1 ttl=64 time=0.060 ms
64 bytes from 10.1.1.3: icmp_seq=2 ttl=64 time=0.105 ms

--- 10.1.1.3 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.060/0.082/0.105/0.024 ms

3.3 通过 OVS 相连#

OVS 是第三方开源的 Bridge,功能比 Linux Bridge 要更强大,对于同样的实验,我们用 OVS 来看看是什么效果。

如下图所示

# 用 ovs 提供的命令创建一个 ovs bridge
ovs-vsctl add-br ovs-br

# 创建两对 veth-pair
ip l a veth0 type veth peer name ovs-veth0
ip l a veth1 type veth peer name ovs-veth1

# 将 veth-pair 两端分别加入到 ns 和 ovs bridge 中
ip l s veth0 netns ns1
ovs-vsctl add-port ovs-br ovs-veth0
ip l s ovs-veth0 up

ip l s veth1 netns ns2
ovs-vsctl add-port ovs-br ovs-veth1
ip l s ovs-veth1 up

# 给 ns 中的 veth 配置 IP 并启用
ip netns exec ns1 ip a a 10.1.1.2/24 dev veth0
ip netns exec ns1 ip l s veth0 up

ip netns exec ns2 ip a a 10.1.1.3/24 dev veth1
ip netns exec ns2 ip l s veth1 up

# veth0 ping veth1
[root@localhost ~]# ip netns exec ns1 ping 10.1.1.3
PING 10.1.1.3 (10.1.1.3) 56(84) bytes of data.
64 bytes from 10.1.1.3: icmp_seq=1 ttl=64 time=0.311 ms
64 bytes from 10.1.1.3: icmp_seq=2 ttl=64 time=0.087 ms
^C
--- 10.1.1.3 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.087/0.199/0.311/0.112 ms

k8s的flannel

注意图中的docker0在使用k8s组网中应该是cni0

在这里插入图片描述

 

 Flannel通过给每台宿主机分配一个子网的方式为容器提供虚拟网络,它基于Linux TUN/TAP,使用UDP封装IP包来创建overlay网络,并借助etcd维护网络的分配情况。
Flannel为每一个主机的Docker daemon分配一个IP段,通过etcd维护一个跨主机的路由表,容器之间IP是可以互相连通的,当两个跨主机的容器要通信的时候。会在主机上修改数据包的header,修改目的地址和源地址,经过路由表发送到目标主机后解包。封包的方式,可以支持udp、vxlan、host-gw等,但是如果一个容器要暴露服务,还是需要映射IP到主机侧

在这里插入图片描述

 

tap/tun 是什么#

tap/tun 是 Linux 内核 2.4.x 版本之后实现的虚拟网络设备,不同于物理网卡靠硬件网路板卡实现,tap/tun 虚拟网卡完全由软件来实现,功能和硬件实现完全没有差别,它们都属于网络设备,都可以配置 IP,都归 Linux 网络设备管理模块统一管理。

作为网络设备,tap/tun 也需要配套相应的驱动程序才能工作。tap/tun 驱动程序包括两个部分,一个是字符设备驱动,一个是网卡驱动。这两部分驱动程序分工不太一样,字符驱动负责数据包在内核空间和用户空间的传送,网卡驱动负责数据包在 TCP/IP 网络协议栈上的传输和处理。

用户空间与内核空间的数据传输#

在 Linux 中,用户空间和内核空间的数据传输有多种方式,字符设备就是其中的一种。tap/tun 通过驱动程序和一个与之关联的字符设备,来实现用户空间和内核空间的通信接口。

在 Linux 内核 2.6.x 之后的版本中,tap/tun 对应的字符设备文件分别为:

  • tap:/dev/tap0
  • tun:/dev/net/tun

设备文件即充当了用户空间和内核空间通信的接口。当应用程序打开设备文件时,驱动程序就会创建并注册相应的虚拟设备接口,一般以 tunX 或 tapX 命名。当应用程序关闭文件时,驱动也会自动删除 tunX 和 tapX 设备,还会删除已经建立起来的路由等信息。

tap/tun 设备文件就像一个管道,一端连接着用户空间,一端连接着内核空间。当用户程序向文件 /dev/net/tun 或 /dev/tap0 写数据时,内核就可以从对应的 tunX 或 tapX 接口读到数据,反之,内核可以通过相反的方式向用户程序发送数据。

tap/tun 和网络协议栈的数据传输#

tap/tun 通过实现相应的网卡驱动程序来和网络协议栈通信。一般的流程和物理网卡和协议栈的交互流程是一样的,不同的是物理网卡一端是连接物理网络,而 tap/tun 虚拟网卡一般连接到用户空间。

如下图的示意图,我们有两个应用程序 A、B,物理网卡 eth0 和虚拟网卡 tun0 分别配置 IP:10.1.1.11 和 192.168.1.11,程序 A 希望构造数据包发往 192.168.1.0/24 网段的主机 192.168.1.1

 

基于上图,我们看看数据包的流程:

  1. 应用程序 A 构造数据包,目的 IP 是 192.168.1.1,通过 socket A 将这个数据包发给协议栈。
  2. 协议栈根据数据包的目的 IP 地址,匹配路由规则,发现要从 tun0 出去。
  3. tun0 发现自己的另一端被应用程序 B 打开了,于是将数据发给程序 B.
  4. 程序 B 收到数据后,做一些跟业务相关的操作,然后构造一个新的数据包,源 IP 是 eth0 的 IP,目的 IP 是 10.1.1.0/24 的网关 10.1.1.1,封装原来的数据的数据包,重新发给协议栈。
  5. 协议栈再根据本地路由,将这个数据包从 eth0 发出。

后续步骤,当 10.1.1.1 收到数据包后,会进行解封装,读取里面的原始数据包,继而转发给本地的主机 192.168.1.1。当接收回包时,也遵循同样的流程。

在这个流程中,应用程序 B 的作用其实是利用 tun0 对数据包做了一层隧道封装。其实 tun 设备的最大用途就是用于隧道通信的。

tap/tun 的区别#

看到这里,你可能还不大明白 tap/tun 的区别。
tap 和 tun 虽然都是虚拟网络设备,但它们的工作层次还不太一样。

  • tap 是一个二层设备(或者以太网设备),只能处理二层的以太网帧;
  • tun 是一个点对点的三层设备(或网络层设备),只能处理三层的 IP 数据包。

tap/tun 的应用#

从上面的数据流程中可以看到,tun 设备充当了一层隧道,所以,tap/tun 最常见的应用也就是用于隧道通信,比如 VPN,包括 tunnel 和应用层的 IPsec 等,其中比较有名的两个开源项目是 openvpn 和 VTun

总结#

veth-pair 在虚拟网络中充当着桥梁的角色,连接多种网络设备构成复杂的网络。

veth-pair 的三个经典实验,直接相连、通过 Bridge 相连和通过 OVS 相连。

tap/tun 对应的字符设备,用户空间与内核空间的数据传输

tun/tap 虚拟网卡,对应于物理网卡,如 eth0。

tun/tap 驱动包括字符设备驱动和网卡驱动。

tun/tap 常用于隧道通信。

参考#

Linux Switching – Interconnecting Namespaces – Open Cloud Blog

linux - Linux虚拟网络设备之veth_个人文章 - SegmentFault 思否

Linux 虚拟网络设备 veth-pair 详解,看这一篇就够了 - bakari - 博客园

 [原创] 详解云计算网络底层技术——虚拟网络设备 tap/tun 原理解析 - bakari - 博客园

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
默认情况下,只有 root 用户才能使用 `--privileged` 参数启动 Docker 容器,因为该参数会让容器拥有访问宿主机系统资源的特权。如果使用普通用户启动容器,会提示权限不足的错误。 不过,您可以通过配置 Docker 守护进程来允许普通用户启动特权容器。具体来说,您需要将普通用户添加到 `docker` 用户组中,并将 Docker 守护进程配置为允许该用户组的成员使用特权容器。 具体操作步骤如下: 1. 将普通用户添加到 `docker` 用户组中: ``` sudo usermod -aG docker your_username ``` 其中,`your_username` 是您要添加到 `docker` 用户组中的用户名。 2. 编辑 Docker 配置文件 `/etc/docker/daemon.json`,配置容器运行时的默认参数: ``` { "default-runtime": "nvidia", "runtimes": { "nvidia": { "path": "nvidia-container-runtime", "runtimeArgs": [] } }, "runcmd": [ "/usr/sbin/sysctl -w kernel.perf_event_paranoid=-1" ], "max-concurrent-downloads": 10, "max-concurrent-uploads": 10, "iptables": true, "ipv6": true, "userns-remap": "default", "experimental": true, "features": { "buildkit": true }, "group": "docker" } ``` 其中,`group` 参数设置为 `docker`,表示允许属于 `docker` 用户组的成员使用 Docker。 3. 重新启动 Docker 守护进程: ``` sudo systemctl daemon-reload sudo systemctl restart docker ``` 4. 使用普通用户身份启动特权容器: ``` docker run --privileged -it bcc-image ``` 现在,普通用户已经可以使用特权容器了。需要注意的是,使用特权容器会增加安全风险,需要谨慎使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

忍冬行者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值