数据包在 Kubernetes 中的一生(2)

原文:Life of a Packet in Kubernetes — Part 2

作者:Dinesh Kumar Ramasamy

如前文所述,CNI 插件是 Kubernetes 网络的重要组件。目前有很多第三方 CNI 插件,Calico 就是其中之一,因为它的易用性和网络能力,得到很多工程师的青睐。它支持很多不同的平台,例如 Kubernetes、OpenShift、Docker EE、OpenStack 以及裸金属服务。Calico Node 组件以 Docker 容器的形式运行在 Kubernetes 的所有 Master 和 Node 节点上。Calico-CNI 插件会直接集成到 Kubernetes 每个节点的 Kubelet 进程中,一旦发现了新建的 Pod,就会将其加入 Calico 网络。

下面的内容会涉及安装、Calico 模块(Felix、BIRD 以及 Confd)和路由模式,但是不会包含网络策略方面的内容。

CNI 的任务

  1. 创建 veth 对,并移入容器
  2. 鉴别正确的 POD CIDR
  3. 创建 CNI 配置文件
  4. IP 地址的分配和管理
  5. 在容器中加入缺省路由
  6. 把路由广播给所有 Peer 节点(不适用于 VxLan)
  7. 在主机上加入路由
  8. 实施网络策略

其实还有很多别的需求,但是上面几个点是最基础的。看看 Master 和 Worker 节点上的路由表。每个节点都有一个容器,容器有一个 IP 地址和缺省的容器路由。

1.png

上面的路由表说明,Pod 能够通过 3 层网络进行互通。什么模块负责添加路由,如何获取远端路由呢?为什么这里缺省网关是 169.254.1.1 呢?我们接下来会讨论这些问题。

Calico 的核心包括 Bird、Felix、ConfD、ETCD 以及 Kubernetes API Server。Calico 需要保存一些配置信息,例如 IP 池、端点信息、网络策略等,数据存储位置是可以配置的,本例中我们使用 Kubernetes 进行存储。

BIRD(BGP)

Bird 是一个 BGP 守护进程,运行在每个节点上,负责相互交换路由信息。通常的拓扑关系是节点之间构成的网格:

2.png

然而集群规模较大的时候,就会很麻烦了。可以使用 Route Reflector(部分 BGP 节点能够配置为 Route Reflector)来完成路由的传播工作,从而降低 BGP 连接数量。路由广播会发送给 Route Reflector,再由 Route Reflector 进行传播,更多信息可以参考 RFC4456

3

BIRD 实例负责向其它 BIRD 实例传递路由信息。缺省配置方式就是 BGP Mesh,适用于小规模部署。在大规模集群中,建议使用 Route Reflector 来克服这个缺点。可以使用多个 RR 来达成高可用目的,另外还可以使用外部 RR 来替代 BIRD。

ConfD

ConfD 是一个简单的配置管理工具,运行在 Calico Node 容器中。它会从 ETCD 中读取数据(Calico 的 BIRD 配置),并写入磁盘文件。它会循环读取网络和子网,并应用配置数据(CIDR 键),组装为 BIRD 能够使用的配置。这样不管网络如何变化,BIRD 都能够得到通知并在节点之间广播路由。

Felix

Calico Felix 守护进程在 Calico Node 容器中运行,完成如下功能:

  • 从 Kubernetes ETCD 中读取信息
  • 构建路由表
  • 配置 iptables 或者 IPVS

看看集群中所有的 Calico 模块:

4

是不是有点不同?veth 的一端是“悬空”的,没有连接。

数据包如何被路由到 Peer 节点的?

  1. Master 上的 Pod 尝试 Ping 10.0.2.11
  2. Pod 向网关发送一个 ARP 请求
  3. 从 ARP 响应中得到 MAC 地址
  4. 但是谁响应的 ARP 请求?

容器是怎样路由到一个不存在的 IP 的?容器的缺省路由指向了 169.254.1.1。容器的 eth0 需要访问这个地址,因此在使用缺省路由的时候会对这个 IP 进行 ARP 查询。

如果能捕获 ARP 响应信息,会发现 veth 另外一侧的(cali123) MAC 地址。所以到底是怎样响应一个没有 IP 接口的 ARP 请求的呢?答案是 proxy-arp,如果我们检查一下主机侧的 veth 接口,会看到启用了 proxy-arp

$ cat /proc/sys/net/ipv4/conf/cali123/proxy_arp
1

Proxy ARP 技术能用特定网络上的代理设备来响应针对本网络不存在的 IP 地址的 ARP 查询。这个代理知道流量的目标,会以自己的 MAC 地址进行响应。如此一来,流量就转给 Proxy,通常会被 Proxy 使用其它网络接口或者隧道路由到原定目标。这种以自己 MAC 地址响应其他 IP 地址的 ARP 请求,完成代理任务的行为有时也被称为发布。

仔细看看 Worker 节点:

5.png

数据包进入内核之后,会根据路由表进行路由。

入栈流量:首先进入Worker 节点内核。 内核把数据包发给 cali123

路由模式

Calico 支持三种路由模式,本节中会对几种模式的优劣和适用场景进行讨论。

  • IP-in-IP:缺省,有封装行为;
  • Direct/NoEncapMode:无封包(推荐);
  • VxLan:有封包(无 BGP)

IP-in-IP

这是一种简单的对 IP 包进行再封包的方式。传输中的数据包带有一个外层头部,其中描述了源主机和目的 IP,还有一个内层头部,包含源 Pod 和目标 IP。

目前 Azure 还不支持 IP-IP,因此这种环境中无法使用该模式,建议关掉 IP-IP 以提高性能。

NoEncapMode

这种模式下数据包是用 Pod 发出时的原始格式发出来的。因为没有封包和解包的开销,这种模式比较有性能优势。

AWS 中要使用这种模式需要关闭源 IP 校验。

VXLAN

Calico 3.7 以后的版本才支持 VXLAN 路由。

VXLAN 是 Virtual Extensible LAN 的缩写。VXLAN 是一种封包技术,二层数据帧被封装为 UDP 数据包。VXLAN 是一种网络虚拟化技术。当设备在软件定义的数据中心里进行通信时,会在这些设备之间建立 VXLAN 隧道。这些隧道能建立在屋里或虚拟交换机之上。这些交换端口被称为 VXLAN Tunnel Endpoints(VTEPs),负责 VXLAN 的封包和解包工作。不支持 VXLAN 的设备可以连接到 VTEP,由 VTEP 提供 VXLAN 的出入转换工作。

VXLAN 对于不支持 IP-in-IP 的网络非常有用,例如 Azure 或者其它不支持 BGP 的数据中心。

6

演示—— IPIP 和 UnEncapMode

在没安装 Calico 之前检查一下集群:

$ kubectl get nodes
NAME           STATUS     ROLES    AGE   VERSION
controlplane   NotReady   master   40s   v1.18.0
node01         NotReady   <none>   9s    v1.18.0

$ kubectl get pods --all-namespaces
NAMESPACE     NAME                                   READY   STATUS    RESTARTS   AGE
kube-system   coredns-66bff467f8-52tkd               0/1     Pending   0          32s
kube-system   coredns-66bff467f8-g5gjb               0/1     Pending   0          32s
kube-system   etcd-controlplane                      1/1     Running   0          34s
kube-system   kube-apiserver-controlplane            1/1     Running   0          34s
kube-system   kube-controller-manager-controlplane   1/1     Running   0          34s
kube-system   kube-proxy-b2j4x                       1/1     Running   0          13s
kube-system   kube-proxy-s46lv                       1/1     Running   0          32s
kube-system   kube-scheduler-controlplane            1/1     Running   0          33s

检查 CNI 的二进制文件和目录。其中没有任何配置文件或者 Calico 二进制,Calico 安装过程会用加载卷来填充其中的内容:

$ cd /etc/cni
-bash: cd: /etc/cni: No such file or directory
$ cd /opt/cni/bin
$ ls
bridge  dhcp  flannel  host-device  host-local  ipvlan  loopback  macvlan  portmap  ptp  sample  tuning  vlan

在 Master/Worker 节点上检查 ip route

$ ip route
default via 172.17.0.1 dev ens3
172.17.0.0/16 dev ens3 proto kernel scope link src 172.17.0.32
172.18.0.0/24 dev docker0 proto kernel scope link src 172.18.0.1 linkdown

在集群环境中下载并提交 calico.yaml

$ curl https://docs.projectcalico.org/manifests/calico.yaml -O
$ kubectl apply -f calico.yaml

看看其中的配置参数:

cni_network_config: |-
    {
      "name": "k8s-pod-network",
      "cniVersion": "0.3.1",
      "plugins": [
        {
          "type": "calico", >>> Calico's CNI plugin
          "log_level": "info",
          "log_file_path": "/var/log/calico/cni/cni.log",
          "datastore_type": "kubernetes",
          "nodename": "__KUBERNETES_NODE_NAME__",
          "mtu": __CNI_MTU__,
          "ipam": {
              "type": "calico-ipam" >>> Calico's IPAM instaed of default IPAM
          },
          "policy": {
              "type": "k8s"
          },
          "kubernetes": {
              "kubeconfig": "__KUBECONFIG_FILEPATH__"
          }
        },
        {
          "type": "portmap",
          "snat": true,
          "capabilities": {"portMappings": true}
        },
        {
          "type": "bandwidth",
          "capabilities": {"bandwidth": true}
        }
      ]
    }
# Enable IPIP
- name: CALICO_IPV4POOL_IPIP
    value: "Always" >> Set this to 'Never' to disable IP-IP
# Enable or Disable VXLAN on the default IP pool.
- name: CALICO_IPV4POOL_VXLAN
    value: "Never"

安装完毕之后,检查 Pod 和节点状态。

$ kubectl get pods --all-namespaces
NAMESPACE     NAME                                       READY   STATUS              RESTARTS   AGE
kube-system   calico-kube-controllers-799fb94867-6qj77   0/1     ContainerCreating   0          21s
kube-system   calico-node-bzttq                          0/1     PodInitializing     0          21s
kube-system   calico-node-r6bwj                          0/1     PodInitializing     0          21s
kube-system   coredns-66bff467f8-52tkd                   0/1     Pending             0          7m5s
kube-system   coredns-66bff467f8-g5gjb                   0/1     ContainerCreating   0          7m5s
kube-system   etcd-controlplane                          1/1     Running             0          7m7s
kube-system   kube-apiserver-controlplane                1/1     Running             0          7m7s
kube-system   kube-controller-manager-controlplane       1/1     Running             0          7m7s
kube-system   kube-proxy-b2j4x                           1/1     Running             0          6m46s
kube-system   kube-proxy-s46lv                           1/1     Running             0          7m5s
kube-system   kube-scheduler-controlplane                1/1     Running             0          7m6s
$ kubectl get nodes
NAME           STATUS   ROLES    AGE     VERSION
controlplane   Ready    master   7m30s   v1.18.0
node01         Ready    <none>   6m59s   v1.18.0

Kubelet 需要 CNI 的配置文件来设置网络:

$ cd /etc/cni/net.d/
$ ls
10-calico.conflist  calico-kubeconfig
$
$
$ cat 10-calico.conflist
{
  "name": "k8s-pod-network",
  "cniVersion": "0.3.1",
  "plugins": [
    {
      "type": "calico",
      "log_level": "info",
      "log_file_path": "/var/log/calico/cni/cni.log",
      "datastore_type": "kubernetes",
      "nodename": "controlplane",
      "mtu": 1440,
      "ipam": {
          "type": "calico-ipam"
      },
      "policy": {
          "type": "k8s"
      },
      "kubernetes": {
          "kubeconfig": "/etc/cni/net.d/calico-kubeconfig"
      }
    },
    {
      "type": "portmap",
      "snat": true,
      "capabilities": {"portMappings": true}
    },
    {
      "type": "bandwidth",
      "capabilities": {"bandwidth": true}
    }
  ]
}

检查 CNI 的二进制文件:

$ ls
bandwidth  bridge  calico  calico-ipam dhcp  flannel  host-device  host-local  install  ipvlan  loopback  macvlan  portmap  ptp  sample  tuning  vlan

安装 calicoctl 来获取 Calico 的更多信息并能修改 Calico 配置:

$ cd /usr/local/bin/
$ curl -O -L  https://github.com/projectcalico/calicoctl/releases/download/v3.16.3/calicoctl
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   633  100   633    0     0   3087      0 --:--:-- --:--:-- --:--:--  3087
100 38.4M  100 38.4M    0     0  5072k      0  0:00:07  0:00:07 --:--:-- 4325k
$ chmod +x calicoctl
$ export DATASTORE_TYPE=kubernetes
$ export KUBECONFIG=~/.kube/config
# Check endpoints - it will be empty as we have't deployed any POD
$ calicoctl get workloadendpoints
WORKLOAD   NODE   NETWORKS   INTERFACE

检查 BGP Peer 的状态,会看到 Worker 节点是一个 Peer。

$ calicoctl node status
Calico process is running.
IPv4 BGP status
+--------------+-------------------+-------+----------+-------------+
| PEER ADDRESS |     PEER TYPE     | STATE |  SINCE   |    INFO     |
+--------------+-------------------+-------+----------+-------------+
| 172.17.0.40  | node-to-node mesh | up    | 00:24:04 | Established |
+--------------+-------------------+-------+----------+-------------+

创建一个两副本 Pod,并设置 tolerations,使之可以运行在 Master 节点:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: busybox-deployment
spec:
  selector:
    matchLabels:
      app: busybox
  replicas: 2
  template:
    metadata:
      labels:
        app: busybox
    spec:
      tolerations:
      - key: "node-role.kubernetes.io/master"
        operator: "Exists"
        effect: "NoSchedule"
      containers:
      - name: busybox
        image: busybox
        command: ["sleep"]
        args: ["10000"]

获取 Pod 和端点状态:

$ kubectl get pods -o wide
NAME                                 READY   STATUS    RESTARTS   AGE   IP                NODE           NOMINATED NODE   READINESS GATES
busybox-deployment-8c7dc8548-btnkv   1/1     Running   0          6s    192.168.196.131   node01         <none>           <none>
busybox-deployment-8c7dc8548-x6ljh   1/1     Running   0          6s    192.168.49.66     controlplane   <none>           <none>
$ calicoctl get workloadendpoints
WORKLOAD                             NODE           NETWORKS             INTERFACE
busybox-deployment-8c7dc8548-btnkv   node01         192.168.196.131/32   calib673e730d42
busybox-deployment-8c7dc8548-x6ljh   controlplane   192.168.49.66/32     cali9861acf9f07

获取 Pod 所在主机上的 VETH 信息:

$ ifconfig cali9861acf9f07
cali9861acf9f07: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1440
        inet6 fe80::ecee:eeff:feee:eeee  prefixlen 64  scopeid 0x20<link>
        ether ee:ee:ee:ee:ee:ee  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 5  bytes 446 (446.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

获取 Pod 网络界面的信息:

$ kubectl exec busybox-deployment-8c7dc8548-x6ljh -- ifconfig
eth0      Link encap:Ethernet  HWaddr 92:7E:C4:15:B9:82
          inet addr:192.168.49.66  Bcast:192.168.49.66  Mask:255.255.255.255
          UP BROADCAST RUNNING MULTICAST  MTU:1440  Metric:1
          RX packets:5 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:446 (446.0 B)  TX bytes:0 (0.0 B)
lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
$ kubectl exec busybox-deployment-8c7dc8548-x6ljh -- ip route
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
$ kubectl exec busybox-deployment-8c7dc8548-x6ljh -- arp

获取主节点路由:

$ ip route
default via 172.17.0.1 dev ens3
172.17.0.0/16 dev ens3 proto kernel scope link src 172.17.0.32
172.18.0.0/24 dev docker0 proto kernel scope link src 172.18.0.1 linkdown
blackhole 192.168.49.64/26 proto bird
192.168.49.65 dev calic22dbe57533 scope link
192.168.49.66 dev cali9861acf9f07 scope link
192.168.196.128/26 via 172.17.0.40 dev tunl0 proto bird onlink

尝试 Ping Worker 节点来触发 ARP:

$ kubectl exec busybox-deployment-8c7dc8548-x6ljh -- ping 192.168.196.131 -c 1
PING 192.168.196.131 (192.168.196.131): 56 data bytes
64 bytes from 192.168.196.131: seq=0 ttl=62 time=0.823 ms
$ kubectl exec busybox-deployment-8c7dc8548-x6ljh -- arp
? (169.254.1.1) at ee:ee:ee:ee:ee:ee [ether]  on eth0

注意上面的 MAC 地址。发出流量时,内核根据 IP 路由将数据包写入 tunl0,Proxy ARP 的配置:

$ cat /proc/sys/net/ipv4/conf/cali9861acf9f07/proxy_arp
1

目标节点如何处理数据包

node01 $ ip route
default via 172.17.0.1 dev ens3
172.17.0.0/16 dev ens3 proto kernel scope link src 172.17.0.40
172.18.0.0/24 dev docker0 proto kernel scope link src 172.18.0.1 linkdown
192.168.49.64/26 via 172.17.0.32 dev tunl0 proto bird onlink
blackhole 192.168.196.128/26 proto bird
192.168.196.129 dev calid4f00d97cb5 scope link
192.168.196.130 dev cali257578b48b6 scope link
192.168.196.131 dev calib673e730d42 scope link

接收到数据包之后,内核会根据路由表将数据包发给对应的 veth

如果抓包的话会看出 IP-IP 协议。据我所知,Azure 不支持 IP-IP,也就是说我们无法在这种环境里使用 IP-IP。关闭 IP-IP 能获得更高性能,下面一节尝试一下。

禁用 IP-IP

更新 ippool.yaml 设置 IPIP 为 Never,然后用 calicoctl 应用配置:

$ calicoctl get ippool default-ipv4-ippool -o yaml > ippool.yaml
$ vi ippool.yaml
...
$ calicoctl apply -f ippool.yaml
Successfully applied 1 'IPPool' resource(s)

再次检查 ip route

$ ip route
default via 172.17.0.1 dev ens3
172.17.0.0/16 dev ens3 proto kernel scope link src 172.17.0.32
172.18.0.0/24 dev docker0 proto kernel scope link src 172.18.0.1 linkdown
blackhole 192.168.49.64/26 proto bird
192.168.49.65 dev calic22dbe57533 scope link
192.168.49.66 dev cali9861acf9f07 scope link
192.168.196.128/26 via 172.17.0.40 dev ens3 proto bird

设备不再是 tunl0,而是变成 Master 节点的管理界面(ens3)。

Ping 一下 Worker 节点,验证工作情况,此时不再使用 IPIP 协议:

$ kubectl exec busybox-deployment-8c7dc8548-x6ljh -- ping 192.168.196.131 -c 1
PING 192.168.196.131 (192.168.196.131): 56 data bytes
64 bytes from 192.168.196.131: seq=0 ttl=62 time=0.653 ms
--- 192.168.196.131 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.653/0.653/0.653 ms

注意在 AWS 环境中使用这种模式需要禁用源 IP 检查。

演示 VXLAN

重新进行集群初始化,并下载 calico.yaml 文件,进行如下变更:

livenessProbereadinessProbe 中删除 bird

livenessProbe:
            exec:
              command:
              - /bin/calico-node
              - -felix-live
              - -bird-live >> Remove this
            periodSeconds: 10
            initialDelaySeconds: 10
            failureThreshold: 6
          readinessProbe:
            exec:
              command:
              - /bin/calico-node
              - -felix-ready
              - -bird-ready >> Remove this

calico_backend 修改为 vxlan,不再需要 BGP:

kind: ConfigMap
apiVersion: v1
metadata:
  name: calico-config
  namespace: kube-system
data:
  # Typha is disabled.
  typha_service_name: "none"
  # Configure the backend to use.
  calico_backend: "vxlan"

禁用 IPIP:

# Enable IPIP
- name: CALICO_IPV4POOL_IPIP
    value: "Never" >> Set this to 'Never' to disable IP-IP
# Enable or Disable VXLAN on the default IP pool.
- name: CALICO_IPV4POOL_VXLAN
    value: "Never"

应用这个 YAML:

$ ip route
default via 172.17.0.1 dev ens3
172.17.0.0/16 dev ens3 proto kernel scope link src 172.17.0.15
172.18.0.0/24 dev docker0 proto kernel scope link src 172.18.0.1 linkdown
192.168.49.65 dev calif5cc38277c7 scope link
192.168.49.66 dev cali840c047460a scope link
192.168.196.128/26 via 192.168.196.128 dev vxlan.calico onlink
vxlan.calico: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1440
        inet 192.168.196.128  netmask 255.255.255.255  broadcast 192.168.196.128
        inet6 fe80::64aa:99ff:fe2f:dc24  prefixlen 64  scopeid 0x20<link>
        ether 66:aa:99:2f:dc:24  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 11 overruns 0  carrier 0  collisions 0

获取 Pod 状态:

$ kubectl get pods -o wide
NAME                                 READY   STATUS    RESTARTS   AGE   IP                NODE           NOMINATED NODE   READINESS GATES
busybox-deployment-8c7dc8548-8bxnw   1/1     Running   0          11s   192.168.49.67     controlplane   <none>           <none>
busybox-deployment-8c7dc8548-kmxst   1/1     Running   0          11s   192.168.196.130   node01         <none>           <none>

查看 ip route

$ kubectl exec busybox-deployment-8c7dc8548-8bxnw -- ip route
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link

执行 Ping,触发 ARP 查询:

$ kubectl exec busybox-deployment-8c7dc8548-8bxnw -- arp
master $ kubectl exec busybox-deployment-8c7dc8548-8bxnw -- ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=116 time=3.786 ms
^C
$ kubectl exec busybox-deployment-8c7dc8548-8bxnw -- arp
? (169.254.1.1) at ee:ee:ee:ee:ee:ee [ether]  on eth0

概念和前一种模式相似,区别在于数据包抵达 vxland 的时候,会把节点 IP 以及 MAC 地址封装并发送。另外 vxland 的 UDP 端口是 4789。这里会从 etcd 获取可用节点以及节点支持的 IP 范围,从而让 vxlan-calico 据此构建数据包。

VxLan 模式需要更多系统开销

7

声明

本文未提供任何技术建议和推荐,文中所述皆为个人观点,不代表我所供职的企业。

Avatar
崔秀龙

简单,是大师的责任;我们凡夫俗子,能做到清楚就很不容易了。

comments powered by Disqus
下一页
上一页