网络命名空间
Linux 的网络协议栈是十分复杂的,为了支持独立的协议栈,相关的这些全局变挝都 必须被修改为协议栈私有 。 最好的办法就是让这些全局变量成为 一个 NetNamespace 变皂 的成员 , 然后为 协议栈 的函数调 用加入 一个 Namespace 参数 。 这就是 Linux 实现网络命名 空间的核心 。
在建立新的网络命名空间,并将某个进程关联到这个网络命名空间后,就出现了类似 于如图 7.1 所示的内核数据结构,所有网站栈变簸都被放入了网络命名空间的数据结构中 。 这个网络命名空间是其进程组私有的,和其他进程组不冲突 。
在新生成的私有命名空间 中只 有回环设备(名为 “lo” 且是停止状态),其他设备默 认都不存在,如果我们需要,则要一一手工建立 。 Docker 容器中的各类网络栈设备都是 Docker Daemon 在启动时自动创建和配置的。
前面提到,由千网络命名空间代表的是一个独立的协议栈,所以它们之间是相互隔离 的,彼此无法通信,在协议栈内部都看不 到对方 。 处于不同命名空间中的网络相互通信,甚至与外部的网络进行通信。应用 Veth 设备对即可
Veth 设备对
引入 Veth 设备对是为了在不同的网络命名空间之间通信,利用它可以直接将两个网 络命名空间连接起来 。 由于要连接两个网络命名空间,所以 Veth 设备都是成对出现的。
在 Docker 内部, Veth 设备对也是连通容器与宿主 机的主要网络设备,离开它是不行的 。
网桥
将这些网络连接起 来 并实现各网络中主机的相互通信呢,可以用网桥。
网桥是一个二层的虚拟网络设备,把若 干个网络接口”连接”起来,以使得网络接口之间的报文能够相互转发 。 网桥能够解析收 发的报文,读取目标 MAC 地址的信息,将其与自己记录的 MAC 表结合,来决策报文的 转发目标网络接口 。
为了实现这些功能,网桥会学习源 MAC 地址(二层网桥转发的依据 就是 MAC 地址 )。在转发报文时,网桥只需向特定的网口进行转发,来避免不必要的网络 交互。如果它遇到一个自己从未学习到 的地址,就无法知道这个报 文应该向哪个网络接口 转发,将报文广播给所有的网络接口(报文来源的网络接口除外)。
在实际的网络中,网络拓扑不可能永久不变。设备如果被移动到另 一 个端口上,却没 有发送任何数据,网桥设备就无法感知这个变化,网桥还是向原来的端口转发数据包,在 这种情况下数据会丢失。如果网桥收到了对应端口 MAC 地址回发的包,则重置超时时间,否则过了超时 时间,就认为设备已经不在那个端口上了,它会重新广播发送 。
Linux 网桥的实现
Linux 内核是通过一个虚拟网桥设备 (Net Device) 来实现桥接的 。 这个虚拟设备可以 绑定若干个以太网接口设备,从而将它们桥接起来 。
这种 Net Device 网桥 和普通的设备不同,最明显的 一 个特性是它还可以有一个 IP 地址 。
网桥设备 br0 绑定了 eth0 和 ethl。对于网络协议的上层来说,只看得到 br0 就行 。 因为桥接是在数据链路层实现的,上层不需要关心桥接的细节,所以协议 栈上层需要发送的报文被送到 br0, 网桥设备的处理代码判断报文应该被转发到 eth0 还是eth1,还是都可以反过来,从 cthO 或从 ethl 接收到的报文被提交给网桥的处理代码,在这里会判断报文应该被转发、丢弃还是被提交到协议栈上层 。
iptables 和 Netfilter
Linux 提供了 一 套机制来为用户实现自定义的数据包处理 。
在 Linux 网络协议栈中有 一 组回调函数挂接点,通过这些挂接点挂接的钩子函数可以 在 Linux 网络栈处理数据包的过程中对数据包进行一些操作,例如过滤 、 修改 、 丢弃等 。 该挂接点技术就叫作 Netfilter 和 iptables。
-
Netfilter 负责在内核中执行各种挂接的规则,运行在内核模式中;Netfilter is responsible for executing various hooked rules in the kernel, running in kernel mode.
-
而 iptables 是在用 户模式下运行的进程,负责协助和维护内核中 Netfilter 的各种规则表 。 iptables is a process running in user mode, responsible for assisting and maintaining various rule tables in the Netfilter of the kernel.
二 者相互配合来实 现整个 Linux 网络协议栈中灵活的数据包处理机制 。
docker网络实现
安装 Docker 时,它会自动创建三个网络,bridge(创建容器默认连接到此网络)、 none 、host
- host:容器将不会虚拟出自己的网卡,配置自己的 IP 等,而是使用宿主机的IP和端口。allows containers to use the IP address and ports of the host machine instead of creating their own network interfaces
- Container:创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围。
- None:该模式关闭了容器的网络功能。
- Bridge:此模式会为每一个容器分配、设置 IP 等,并将容器连接到一个 docker0 虚拟网桥,通过 docker0 网桥以及 Iptables nat 表配置与宿主机通信。assigns and configures IP addresses for each container and connects them to a virtual bridge called docker0. This allows communication between the containers and the host machine through the docker0 bridge and iptables NAT rules
在 bridge 模式下, Docker Daemon 首次启动时 会创建一个虚拟网桥,默认的名称是 docker0, 然后按照 RPC1918 的模型在私有网络 空间中给这个网桥分配一个子网。针对由 Docker创建的每一个容器,都会创建一个虚拟以太网设备 (Veth设备对),其中一端关联 到网桥上,另一端使用 Linux 的网络命名空间技术映射到容器内的 eth0设备,然后在网桥 的地址段内给 eth0接口分配一个 IP 地址 。In the bridge mode, when Docker Daemon is first started, it creates a virtual bridge with the default name docker0.it assigns a subnet to this bridge in the private network space. For each container created by Docker, a virtual Ethernet device (Veth device pair) is also created。One end of the device is connected to the bridge, while the other end is mapped to the eth0 device inside the container using Linux’s network namespace technology. Then, an IP address is assigned to the eth0 interface within the address range of the bridge.
启动后, Docker 还将 Veth 设备对的名称映射到 ethO 网络接口 。 ip3 就是主机的网卡 地址 。
其中 ipl 是网桥的 IP 地址, Docker Daemon 会在几个备选地址 段里给它选一个地址, 通常是以 172 开头的一个地址,这个地址和主机的 IP 地址是不重叠的 。 ip2 是 Docker 在 启动容器时在这个地址段选择的一个没有使用的 IP 地址,它被分配给容器,相应的 MAC 地址也根据这个 IP 地址,在 02:42:ac:11:00:00 和 02:42:ac:11:ff:ff的范围内生成,这样做 可以确保不会有 ARP 冲突 。
在一般情况下, ipl 、 ip2 和 ip3 是不同的 IP 段,所以在默认 不 做任何特殊配置的情况 下,在外部是看不到 ip1 和 ip2 的 。
k8s网络实现
同一个pod
同一个 Pod 内的容器 (Pod 内的容器是不会跨宿主机的)共享同一个网络命名空间,共享同一个 Linux 协议栈。In a Pod, the containers share the same network namespace and Linux protocol stack. This means that containers within the same Pod do not cross host boundaries and have their own isolated network environment.
- Pod 在节点上拥有独立的网络命名空间。当创建 Pod 时,container running time会给容器创建一个网络命名空间。When a Pod is created, the container running time will create a network namespace for each container.
- CNI 负责给 Pod 分配一个 IP 地址,两个容器之间共享端口。(CNI) is responsible for assigning an IP address to the Pod and enabling port sharing between the containers.
- 两个容器共享相同的网络命名空间,并在本地彼此可见。The two containers within the Pod share the same network namespace and can communicate with each other locally.
当你创建一个 Pod,Pod 被分配给一个节点后,CNI 将:
- 分配 IP 地址。Allocate an IP address.
- 将容器连接到网络。Connect the containers to the network.
如果 Pod 包含多个容器,那么这些容器都将被放在同一个命名空间中。节点上的每一个 Pod 都会有一个对应的 pause 容器。这个pause
容器负责创建和维持网络命名空间。Each Pod on a node has a corresponding pause container. This pause
container is responsible for creating and maintaining the network namespace.
如图 7.8 中的阴影部分所示,在 Node 上运行若 一 个 Pod 实例 。 在我们的例子中,容 器就是图 7.8 中的容器 1 和容器 2。容器 l 和 容器 2 共享一个网络的命名空司,共享一个 命名空间的结果就是它们好像在一台机器上运行,它们打开的端口不会有冲突,可以直接 使用 Linux 的本地 IPC 进行通信(例如消息队列或者管道)。其实,这和传统的一组普通 程序运行的环境是完全一样的,传统程序不需要针对网络做特别的修改就可以移植,它们 之间的相互访问只需使用 localhost 就可以 。
不同pod
Pod 到 Pod 的通信有两种可能的情况:
- Pod 流量的目的地是同一节点上的 Pod。
- Pod 流量的目的地是在不同节点上的 Pod。
整个工作流依赖于虚拟接口对和网桥,下面先来了解一下这部分的内容。
同一个node两个pod
因此,可以使用网桥连接两个接口,即 Pod 命名空间的veth
连接到同一节点上另一个 Pod 的veth
。网桥会汇聚位于根命名空间中的每一个虚拟接口。这个网桥允许虚拟 pair 之间的流量,也允许穿过公共根命名空间的流量。
可以看出, Podl 和 Pod2 都是通过 Veth 连接到同一个 dockerO 网桥的,它们的 IP 地址 IPl 、 IP2 都是从 dockerO 的网段上动态获取的,和网桥本身的 IP3 属于同 一 个网段 。Pod1 and Pod2 are both connected to the same docker0 bridge via Veth. Their IP addresses, IP1 and IP2, are dynamically obtained from the docker0 network range, and they belong to the same subnet as the IP3 of the bridge itself.
在 Podl 、 Pod2 的 Linux 协议栈上,默认路由都是 dockerO 的地址,也就是说所有非本地地址的网络数据,都会被默认发送到 dockerO 网桥上,由 dockerO 网桥直接中转 。
由于它们都关联在同 一 个 dockerO 网桥上,地址段相同,所以它们之间是能直接通信的 。they are both associated with the same docker0 bridge and have the same address range, they can communicate with each other directly.
- 由于访问目标不在同一个命名空间,Pod-A 将数据包发送到其默认接口 eth0。这个接口与 veth 对的一端绑定,作为隧道。这样,数据包会被转发到节点上的根命名空间。
- 以太网网桥作为一个虚拟交换机,需要目标 Pod-B 的 MAC 地址才能工作
- ARP 协议会解决这个问题。当帧到达网桥时,会向所有连接的设备发送 ARP 广播。网桥广播询问持有 Pod-B 的 IP 地址
- 此时会收到一个带有 Pod-B IP 的 MAC 地址应答,这条消息会被存储在桥接 ARP 缓存(查找表)中。
- IP 地址和 MAC 地址的映射关系存储之后,网桥就在表中查找,并将数据包转发到正确的端点。数据包到达根命名空间内 Pod-B 的 veth 之后,很快又到达 Pod-B 命名空间内的 eth0 接口。
不同Node
Pod 的地址是与 dockerO 在同一个网段的,我们知道 dockerO 网段与宿主机网卡是两个 完全不同的 IP 网段,并且不同 Node 之间的通信只能通过宿主机的物理网卡进行 , 因此要 想实现不同 Node上 Pod容器之间的通信,就必须想办法通过主机的这个 IP地址进行寻址 和通信 。The IP address of the Pod is in the same subnet as the docker0 bridge. We know that the docker0 subnet is completely different from the IP subnet of the host machine’s network interface. Communication between different nodes can only be achieved through the physical network interface of the host machine. Therefore, in order to enable communication between Pod containers on different nodes, we must find a way to address and communicate through the IP address of the host machine.
另一方面 , 这些动态分配且藏在 dockerO 后的“私有 “IP 地址也是可以找到的 。 Kubernetes 会记录所有正在运行的 Pod 的 IP 分配信息,并将这些信息保存在 etcd 中(作 为 Service 的 Endpoint)。这些私有 IP 信息对于 Pod 到 Pod 的通信也是十分重要的,因为 我们的网络模型要求 Pod 到 Pod 使用私有 IP 进行通信。所以首先要知道这些 IP 是什么 。
综上所述,要想支持不同 Node 上 Pod 之间的通信 , 就要满足两个条件
- 整 个 Kubernetes 集群中对 Pod 的 IP 分配进行规划,不能有冲突(Flannel管理资源池的分配)
- 找到一种办法,将 Pod 的 IP 和所在 Node 的 IP 关联起来,通过这个关联让 Pod 可以相互访问
Pod 中的数据在发出 时 ,需要有一个机 制 能够知道对方 Pod 的 IP 地址挂在哪个具体的 Node 上 。 也就是说 , 先要找到 Node 对应宿 主 机的 IP 地址,将数据 发送到这个宿主机的网卡,然后在宿主机上将相应的数据转发到具体的 dockerO 上 。 一 旦数据到达宿主机 Node, 那个 Node 内部的 dockerO 便知道如何将数据发送到 Pod 了
we need to first find the IP address of the Node corresponding to the Pod and send the data to the network interface of that Node. Then, on the Node, the data will be forwarded to the appropriate Docker container.
Once the data reaches the Node, the Docker container within that Node knows how to send the data to the specific Pod.
网络实现的不同
默认的 Docker 网络模型提供了一个 IP 地址段是 172.17.0.0/16 的 docker0 网桥 。 每个 容器都会在这个子网内获得 IP 地址,并且将 docker0 网桥的 IP 地址 ( 172.17.42.1) 作为 其默认网关 。 需要注意的是, Docker 宿主机外面的网络不需要知道任何关于这个 172.17.0.0/16 的信息或者知道如何连接到其内部,因为 Docker 的宿主机针对容器发出的数 据,在物理网卡地址后面都做了 IP 伪装 MASQUERADE (隐含 NAT)。也就是说,在网络 上看到的任何容器数据流都来源于该 Docker 节点的物理 IP 地址。这里所说的网络都指连 接这些主机的物理网络。这个模型便于使用,但是并不完美,需要依赖端口映射的机制 。
在 Kubernetes 的网络模型中,每台主机上的 docker0 网桥都是可以被路由到的 。 也就 是说,在部署了一个 Pod 时,在同一个集群中,各主机都可以访问其他主机上的 Pod IP, 并不需要在主机上做端口映射 。
这意味着,每一个新部署的容器都将使用这个 Node ( docker0 的网桥 IP) 作为它的默认网关。而这些 Node (类似路由器)都有其他 docker0的路由信息,这样它们就能够相互 连通了 。
k8s pod 网络
1 |
|
我们检查的第 1 个容器是运行了 k8s.gcr.io/pause: latest 镜像的容器,它使用了 Docker 默认的网 络模型 bridge; 而我们检查的第 2 个容器,也就是在 RC/Pod 中定义运行的 php-redis 容器, 使用了非默认的网络配笠和映射容器的模型,指定了映射目标容器为 k8s.gcr.io/pause:latest
service网络
在服务正确创建后,可以看到 Kubernetes集群已经为这个服务分配了一个虚拟 IP地址。这个 IP 地址是在 Kubernetes 的 Portal Network 中分配的。 而这个 Portal Network的地址范围是我们在 Kubmaster上启动 API服务进程时,使用–service-cluster-ip-range=xx 命令行参数指定 的
这个 IP 段可以是任何段,只要不和 docker0 或者物理网络的子网冲突就可以 。 选择任 意其他网段的原因是,这个网段将不会在物理网络和 dockerO 网 络上进行路由 。 这个 Portal Network 针对的是每 一 个 Node 都有局部的特殊性,实际上它存在的意义是让容器的流量都指 向默认网关(也就是dockerO网桥)。
目标为 Service IP 地址和端口的任何流呈都将被重定向到本地的 33761 端口(iptable作用)
这个端口连到哪里去了呢?这就到了 kube-proxy 发挥作用的地方了 。这个 kube-proxy 服务 给每一个新创建 的服务都关联了 一个随机的端口号 ,并且监听那个特定的端口,为服务创 建了相关的负载均衡对象。
可以知道,所有流量都被导入 kube-proxy 中了。我们现在需要它完成一些负载均衡工 作,Service 将会把客户端的请求负载分发到包含name=frontend 标签的所有 Pod 上
1 |
|
-
让我们在网络图上用实线标出第 1个窗口中网络抓包信息的含义(物理 网卡上 的网络流量),
-
并用虚线标出第 2 个窗口土质络抓 包信息的含义 ( docker0 网桥上的 网络流量)
虚线绕 过了 Node3 的 kube-proxy, 这么做是因为 Node3 上的 kube-proxy 没有参与这次网络交互。换句话说 , Nadel 的 kube-proxy 服务直娄和负载均衡 到的 Pod进行网络交互。
总而言之, Kubemetes 的 kube-proxy作为一个全功能的代理服务器管理了两个独立的 TCP 连接:一个是从容器到 kube-proxy: 另一个是从 kube-proxy 到负载均 衡的 目标 Pod。
CNI
CNI 定义了容器运行环境与网络插件之间的简单接口规范,通过 一个 JSON Schema 定义 CNI 插件提供的输入和输出参数 。一个容器可以通过绑定多个网络插件加人多个网络 中。
在 CNI 模型中只涉及两个概念:容器和网络。
- 容器 : 是拥有独立 Linux 网络命名空间的环境,例如使用 Docker 或 rkt 创建的容 器 。关 键之处是容器需要拥有自己的 Linux 网络命名空间,这是扛入网络的必要 条件 。
- 网络:表示可以互连的一组实体,这些实体拥有各自独立、唯一航 IP 地址,可以 是容摇、物理机或者其他网络设备(比如路由器)等 。 可以将容器添加到一个或 多个网络中,也可以从一个或多个网络中删除 。
Flannel 插件的原理
可以搭建 Kubernetes 依赖的底层网络 underlying network,是因为它能实现以下两点
- 协助 Kubernetes, 给每一个 Node上的 Docker容器都分配互不冲突的 IP地址。assigning unique IP addresses to each Docker container on every node.
- 在这些 IP地址之间建立一个覆盖网络 (OverlayNetwork),通过这个覆盖网络,将数据包原封不动地传递到目标容器 内 。Build an overlay network between these IP addresses, allowing data packets to be delivered unchanged to the target containers.
Flannel 首先创建了一个名为 flanne!O 的网 桥,而且这个网桥的一端连接dockerO 网桥,另一端连接一个叫作 flanneld 的服务进程 。creates a bridge called flannel0, with one end connected to the docker0 bridge and the other end connected to a service process called flanneld
flanneld 进程并不简单,它上连 etcd, 利用 etcd 来管理可分配的 IP 地址段资源 ,同时 监控 etcd 中每个 Pod 的实际地址,并在内存中建立了一个 Pod 节点路由表;connects to etcd, using etcd to manage the allocation of IP address ranges. It also monitors the actual addresses of each Pod in etcd and builds a Pod node routing table in memory.它下连 dockerO 和物理网络,使用内存中 的 Pod 节点路由表,将 dockerO 发给它的数据包包装起来,利用物理 网络的连接将数据包投递到目标 flanneld 上,从而完成 Pod 到 Pod 之间的直接地址通信 。It connects to both docker0 and the physical network, wrapping the data packets received from docker0 using the Pod node routing table in memory, and delivers the packets to the target flanneld through the physical network connection, thereby enabling direct address communication between Pods.
我们看一下 Flannel 是如何做到为不同 Node 上的 Pod 分配的 IP 不产生冲突的 。其实 想到 Flannel 使用了集中的 etcd 存储就很容易理解了 。 它每次分配的地址段都在同 一个公 共区域获取,这样大家自然能够相互协调,不产生冲突了。而且在 Flannel 分配好地址段 后,后面的事情是由 Docker 完成的, Flannel 通过修改 Docker 的启动参数将分配给它的地 址段传递进去:
–bip=1 72.17.18 .1/24 通过这些操作, Flannel 就控制了每个 Node 上的 dockerO 地址段的地址,也就保障了
所有 Pod 的 IP 地址都在同一个水平网络中且不产生冲突了 。
Calico
Calico 保证 所有容器之间的数据流最都是通过 IP 路由的方式完成互联互通的 。Calico 节点组网时可以 直接利用数据中心的网络结构 (L2 或者 L3),不需要额外的 NAT 、隧道或者 Overlay Network,
Calico ensures that all container-to-container traffic is interconnected through IP routing.Without the need for additional NAT, tunnels, or overlay networks
没有额外的封包解包,能够节约 CPU 运算,提高网络效 率。Calico 在小规模集群中可以直接互联,在大规模集群中可以通过 额外的 BGP route reflector来完成。No need encapsulation and decapsulation for packets, saving CPU processing power and improving network efficiency. In smaller clusters, Calico can directly interconnect, while in larger clusters, an additional BGP route reflector can be used to facilitate communication.
-
Felix: Calico Agent, 运行在每个 Node 上,负责为容器设登网络资源 (IP 地址、 路由规则 、 iptables 规则等),保证跨主机容器网络互通 。
-
etcd: Calico 使用的后端存储 。
-
BGPClient:负责把Felix在各Node上设置的路由信息通过BGP广播到Calico网络。
-
Route Reflector: 通过一个或者多个 BGP Route Reflector 完成大规模集群的分级路
由分发
-
CalicoCtl: Calico 命令行管理工具 。
部署Calico应用
- 修改 Kubemetes 服务的启动参数,并重启服务
- 设置 Master 上 kube-apiserver 服务的启动参数: –allow-privileged=true (因为 calico-node 需要以特权模式运行在各 Node 上)。
- 设置各 Node 上 kubelet 服务的启动参数 : –network-plugin=cni (使用 CNI 网络插 件)。
IPIP 是一种将各 Node 的路由之间做 一个 tunnel, 再把两个网 络连接起来的模式。BGP 模式则直接使用物理机作为虚拟路由器 (vRouter),不再创建额外的 tunnel
Network Policy
策略控制器需要实现一个 API Listener,监听用户设置的 NetworkPolicy 定义,并将网络访问规则通过各 Node 的 Agent 进行实际设 置 (Agent则需要通过 CNI 网络插件实现)。