最近观看了《Container Networking From Scratch》这个视频,看完之后,需要记录一下。
Q: Kubernetes对于网络的要求?
A: kubernetes的官方描述:
Kubernetes imposes the following fundamental requirements on any networking implementation (barring any intentional network segmentation policies):
- pods on a node can communicate with all pods on all nodes without NAT
- agents on a node (e.g. system daemons, kubelet) can communicate with all pods on that node
Note: For those platforms that support Pods running in the host network (e.g. Linux):
- pods in the host network of a node can communicate with all pods on all nodes without NAT
简单来说,k8s对于网络的要求是:
- 在没有进行NAT的情况下(without NAT),一个节点(node)上的pods可以同所有节点上的所有pods进行通信
- 节点上的代理(agents),比如kubelet和system daemons,能够同该节点上的所有pods进行通信
- 对于支持Pods运行在主机网络上的平台,比如linux,一个位于主机网络上的节点(node)的pods能够在不使用NAT的情况下同所有节点上的所有pods进行通信
这里,我有两个疑问:
- 什么叫without NAT?NAT就是Network Address translation,简单来说就是网络包在从内网发往外网的时候,将网络包的source ip或者destination ip转换为另一个IP地址。其原因为内部网络的IP地址仅为内部使用,外部无法以该IP定位主机,因此网络包从内部网络经某个设备出去的时候,该网络包的source ip会被translate为该设备的IP地址,以接收返回的网络包。同理,在网络包进来的时候,destination ip也会被变换为网络内部的节点的ip地址。
那么without NAT的意思呢?我理解的就是当网络包从一个pod发送到另一个pod的时候,源IP或者目的IP都是对应的pod的地址,而不是其他地址。即没有经过NAT转换,不能出现源IP不是发送包的pod的ip二是另外一个ip。 - 什么叫“支持pod运行在主机网络上的平台,比如linux”?这里理解的就是pods之间可以通过pod所在的主机网络进行通信。
视频里面还有一条:
- the ip that a container sees itself as is the same IP that others see it
就是说一个容器自己的ip同其他容器看到的值是一样的。
视频里面从什么都没有到完成一个overlay网络,走了四部:
- Step 1: Single network namespace.
- Step 2: Single node, 2 network namespaces.
- Step 3: Multiple nodes, same L2 network.
- Step 4: Multiple nodes, overlay network.
具体情况,下面记录。
基础知识
- 容器就是通过一系列的linux 隔离技术,各种namespaces形成的一个isolated process,也就是容器是linux上的一个进程,该进行同其他进程进行了资源隔离。
- 从网络的角度上来看,一个容器就是一个独立的同其他容器隔离的network namespace(网络命名空间)
- 主机是一个网络命名空间。
- 每个独立的网络命名空间,具有独立的ethernet、iptable、route等
- veth pair。veth即virtual ethernet,虚拟的ethernet。veth pair,即veth对,就是两个虚拟ethernet,他们组成一个pair(对)。我们可以认为veth pair的两个virtual ethernet,就是一根线的两端,请求从一端进入就会从另外一端出去,换句话说就是发往pair中一个ethernet的网络包同时也会被另一个ethernet接收到。
第一步,Single network namespace
先看图:
step1.png
解释一下:
- node,表示节点,即最大的框
- con,表示container,即容器,大框里面的小框
- veth1和veth2是一对veth pair,veth2在容器中,veth1在节点的root namespace里
- enp0s8是node的ethernet
- 容器里面的route表明所有的流量默认走veth2
- 节点上的route表示到172.16.0.1的流量走veth1
该图表示了流量是如何从节点通过route流向veth1,再到容器中的。同时也表明了流量如何从容器,通过容器内的route,veth2流向节点的。
接下来看看代码:
#!/bin/bash -e
. env.sh
echo "Creating the namespace"
sudo ip netns add $CON
echo "Creating the veth pair"
sudo ip link add veth1 type veth peer name veth2
echo "Adding one end of the veth pair to the namespace"
sudo ip link set veth2 netns $CON
echo "Configuring the interface in the network namespace with an IP address"
sudo ip netns exec $CON ip addr add $IP dev veth2
echo "Enabling the interface inside the network namespace"
sudo ip netns exec $CON ip link set dev veth2 up
echo "Enabling the interface on the node"
sudo ip link set dev veth1 up
echo "Setting the loopback interface in the network namespace"
sudo ip netns exec $CON ip link set lo up
echo "Setting the routes on the node"
sudo ip route add $IP/32 dev veth1
echo "Setting the default route in the network namespaces"
sudo ip netns exec $CON ip route add default via $IP dev veth2
大概流程为:
- 创建netns,即network namespace
- 创建veth1和veth2,并将其作为一个pair
- 将veth2放入刚刚创建的netns
- 设置veth2的ip地址
- 启动veth1和veth2
- 启动netns里面的loopback interface
- 设置veth1的route(路由),将流量导向veth1
- 设置netns中的route(路由)
第二步,Single node, 2 network namespaces.
先看图:
step2.jpg
解释一下:
- 容器1和容器2里面的veth通过veth pair,连接到节点上的网桥br0上
- 网桥br0连接了veth10和veth20,也就间接连通了容器1和容器2
- 通过主机的路由,将所有172.16.0.0/24的流量导向网桥br0
- 设置容器内部路由,将所有流量导向veth
看看代码:
#!/bin/bash -e
. env.sh
echo "Creating the namespaces"
sudo ip netns add $CON1
sudo ip netns add $CON2
echo "Creating the veth pairs"
sudo ip link add veth10 type veth peer name veth11
sudo ip link add veth20 type veth peer name veth21
echo "Adding the veth pairs to the namespaces"
sudo ip link set veth11 netns $CON1
sudo ip link set veth21 netns $CON2
echo "Configuring the interfaces in the network namespaces with IP address"
sudo ip netns exec $CON1 ip addr add $IP1/24 dev veth11
sudo ip netns exec $CON2 ip addr add $IP2/24 dev veth21
echo "Enabling the interfaces inside the network namespaces"
sudo ip netns exec $CON1 ip link set dev veth11 up
sudo ip netns exec $CON2 ip link set dev veth21 up
echo "Creating the bridge"
sudo ip link add name br0 type bridge
echo "Adding the network namespaces interfaces to the bridge"
sudo ip link set dev veth10 master br0
sudo ip link set dev veth20 master br0
echo "Assigning the IP address to the bridge"
sudo ip addr add $BRIDGE_IP/24 dev br0
echo "Enabling the bridge"
sudo ip link set dev br0 up
echo "Enabling the interfaces connected to the bridge"
sudo ip link set dev veth10 up
sudo ip link set dev veth20 up
echo "Setting the loopback interfaces in the network namespaces"
sudo ip netns exec $CON1 ip link set lo up
sudo ip netns exec $CON2 ip link set lo up
echo "Setting the default route in the network namespaces"
sudo ip netns exec $CON1 ip route add default via $BRIDGE_IP dev veth11
sudo ip netns exec $CON2 ip route add default via $BRIDGE_IP dev veth21
相对上一步来说:
- 创建了两个network namespace
- 创建网桥
- 将主机network namespace中的veth加入到网桥上
- 设置网桥IP地址
- 设置主机路由规则,将对应的流量导向网桥
第三步,Multiple nodes, same L2 network.
先看图:
step3.jpg
解释一下:
- 两台主机通过swith进行了连接
- 主机里面依然同上一步一样,只是在主机路由中,增加了到其他node的路由规则,比如左边节点上的172.16.1.0/24 via 10.0.0.20 enp0s8表明,所有到右边节点的请求都会通过enp0s8发出
看看代码:
#!/bin/bash -e
. env.sh
echo "Creating the namespaces"
sudo ip netns add $CON1
sudo ip netns add $CON2
echo "Creating the veth pairs"
sudo ip link add veth10 type veth peer name veth11
sudo ip link add veth20 type veth peer name veth21
echo "Adding the veth pairs to the namespaces"
sudo ip link set veth11 netns $CON1
sudo ip link set veth21 netns $CON2
echo "Configuring the interfaces in the network namespaces with IP address"
sudo ip netns exec $CON1 ip addr add $IP1/24 dev veth11
sudo ip netns exec $CON2 ip addr add $IP2/24 dev veth21
echo "Enabling the interfaces inside the network namespaces"
sudo ip netns exec $CON1 ip link set dev veth11 up
sudo ip netns exec $CON2 ip link set dev veth21 up
echo "Creating the bridge"
sudo ip link add name br0 type bridge
echo "Adding the network namespaces interfaces to the bridge"
sudo ip link set dev veth10 master br0
sudo ip link set dev veth20 master br0
echo "Assigning the IP address to the bridge"
sudo ip addr add $BRIDGE_IP/24 dev br0
echo "Enabling the bridge"
sudo ip link set dev br0 up
echo "Enabling the interfaces connected to the bridge"
sudo ip link set dev veth10 up
sudo ip link set dev veth20 up
echo "Setting the loopback interfaces in the network namespaces"
sudo ip netns exec $CON1 ip link set lo up
sudo ip netns exec $CON2 ip link set lo up
echo "Setting the default route in the network namespaces"
sudo ip netns exec $CON1 ip route add default via $BRIDGE_IP dev veth11
sudo ip netns exec $CON2 ip route add default via $BRIDGE_IP dev veth21
# ------------------- Step 3 Specific Setup --------------------- #
echo "Setting the route on the node to reach the network namespaces on the other node"
sudo ip route add $TO_BRIDGE_SUBNET via $TO_NODE_IP dev enp0s8
echo "Enables IP forwarding on the node"
sudo sysctl -w net.ipv4.ip_forward=1
同上一步不同的是:
- 开启了每个节点上的IP转发
- 设置了到其他节点的路由规则
sudo ip route add $TO_BRIDGE_SUBNET via $TO_NODE_IP dev enp0s8
第四步,Multiple nodes, overlay network.
先看图:
step4.jpg
解释一下:
- 每个节点增加了tun0这样一个ethernet
- 将到其他节点的流量导入到tun0,而不是上一步中的enp0s8
- 每个节点上会运行一个进程,该进程负载对到达tun0的网络包进行处理,比如通过vxlan协议将网络包发往另一个节点上的某个端口,该节点上的对应进程会监听该端口,处理接收到的vxlan包,然后根据路由规则(比如172.16.1.0/24 br0)将网络包拆解之后发往网桥,再由网桥转发到对应的veth,最终到达容器内部
- overlay网络的建立,其关键就是每个节点上有一个进程,由该进程进行封包和转发。其目的地是其他节点上的同样进程监听的端口。
看看代码:
#!/bin/bash -e
. env.sh
echo "Creating the namespaces"
sudo ip netns add $CON1
sudo ip netns add $CON2
echo "Creating the veth pairs"
sudo ip link add veth10 type veth peer name veth11
sudo ip link add veth20 type veth peer name veth21
echo "Adding the veth pairs to the namespaces"
sudo ip link set veth11 netns $CON1
sudo ip link set veth21 netns $CON2
echo "Configuring the interfaces in the network namespaces with IP address"
sudo ip netns exec $CON1 ip addr add $IP1/24 dev veth11
sudo ip netns exec $CON2 ip addr add $IP2/24 dev veth21
echo "Enabling the interfaces inside the network namespaces"
sudo ip netns exec $CON1 ip link set dev veth11 up
sudo ip netns exec $CON2 ip link set dev veth21 up
echo "Creating the bridge"
sudo ip link add name br0 type bridge
echo "Adding the network namespaces interfaces to the bridge"
sudo ip link set dev veth10 master br0
sudo ip link set dev veth20 master br0
echo "Assigning the IP address to the bridge"
sudo ip addr add $BRIDGE_IP/24 dev br0
echo "Enabling the bridge"
sudo ip link set dev br0 up
echo "Enabling the interfaces connected to the bridge"
sudo ip link set dev veth10 up
sudo ip link set dev veth20 up
echo "Setting the loopback interfaces in the network namespaces"
sudo ip netns exec $CON1 ip link set lo up
sudo ip netns exec $CON2 ip link set lo up
echo "Setting the default route in the network namespaces"
sudo ip netns exec $CON1 ip route add default via $BRIDGE_IP dev veth11
sudo ip netns exec $CON2 ip route add default via $BRIDGE_IP dev veth21
echo "Enables IP forwarding on the node"
sudo sysctl -w net.ipv4.ip_forward=1
# ------------------- Step 4 Specific Setup --------------------- #
echo "Starts the UDP tunnel in the background"
sudo socat TUN:$TUNNEL_IP/16,iff-up UDP:$TO_NODE_IP:9000,bind=$NODE_IP:9000 &
echo "Setting the MTU on the tun interface"
sudo ip link set dev tun0 mtu 1492
echo "Disables reverse path filtering"
sudo bash -c 'echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter'
sudo bash -c 'echo 0 > /proc/sys/net/ipv4/conf/enp0s8/rp_filter'
sudo bash -c 'echo 0 > /proc/sys/net/ipv4/conf/br0/rp_filter'
sudo bash -c 'echo 0 > /proc/sys/net/ipv4/conf/tun0/rp_filter'
同上一步不同的地方在于:
- 使用socat创建了UDP隧道用于跨节点的流量。该命名将tunnel_ip/16的流量发往节点地址的9000端口
- 禁用了reverse path filtering
- 设置了mtu
参考资料:
[1] https://www.youtube.com/watch?v=6v_BDHIgOY8
[2] https://github.com/kristenjacobs/container-networking
[3] https://blog.scottlowe.org/2013/09/04/introducing-linux-network-namespaces/
网友评论