CNI 插件 flannel-vxlan 使用实操

这里主要研究 flannel-vxlan ,flannel-vxlan 是 overlay 类型的网络。本次实操无需安装 Kubernetes,直接调用插件来配置容器网络。

CNI 插件的本质工作有两个:

  • 工作一:配置容器网络命名空间的网络
  • 工作二:路由各个网络命名空间的网络流量

工作一需要实现 CNI 相关的接口,从而可以通过外部对插件进行调用。工作二常常需要额外的守护进程来完成,在 Kubernetes 中一般以 DaemonSet 的形式部署,同时以 Kubernetes 的 etcd 作为数据存储的后端。

环境介绍

  • 节点0,10.211.55.2(用于部署 etcd)
  • 节点1,10.211.55.32
  • 节点2,10.211.55.57

事前准备

在节点0上部署 etcd 为网络插件提供存储服务,这里使用 Docker 部署,暴露端口为 2379,相关官网教程见 https://etcd.io/docs/v3.4/op-guide/container/ ,启动参数见 https://etcd.io/docs/v3.4/op-guide/configuration/

1
docker run -p 2379:2379 quay.io/coreos/etcd:v3.3.1 /usr/local/bin/etcd --advertise-client-urls http://10.211.55.2:2379 --listen-client-urls http://0.0.0.0:2379

安装 etcdctl 工具,相关教程见 https://gist.github.com/sanjid133/fffaae1c7deb7c3d6c5f6bae549d6380,当然,也可以使用 docker exec 登录到 etcd 容器中使用它自带的 etcdctl。

安装 cni 基础网络插件至 /opt/cni/bin 下,插件仓库在 https://github.com/containernetworking/plugins 上,直接下载编译好的 release https://github.com/containernetworking/plugins/releases 然后解压到 /opt/cni/bin 下即可。

直接使用 https://www.cni.dev/docs/cnitool/ 或者自己编写如下程序调用 CNI 插件,这里采用第二种方式,该程序最终会被编译为 cnidemo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package main

import (
"context"
"flag"
"fmt"
"log"
"os"
"os/signal"
"strconv"

gocni "github.com/containerd/go-cni"
)

var (
idF = flag.Int("id", 2, "")
confFF = flag.String("conf", "/etc/cni/net.d/11-flannel.conf", "")
)

func init() {
flag.Parse()
}

func main() {
id := *idF
netns := fmt.Sprintf("/var/run/netns/ns-%d", id)

l, err := gocni.New(
gocni.WithMinNetworkCount(2),
gocni.WithPluginDir([]string{"/opt/cni/bin"}),
gocni.WithInterfacePrefix("eth"))
if err != nil {
log.Fatalf("failed to initialize cni library: %v", err)
}

if err := l.Load(gocni.WithLoNetwork, gocni.WithConfListFile(*confFF)); err != nil {
log.Fatalf("failed to load cni configuration: %v", err)
}

ctx := context.Background()
defer func() {
if err := l.Remove(ctx, strconv.Itoa(id), netns); err != nil {
log.Fatalf("failed to teardown network: %v", err)
}
}()

result, err := l.Setup(ctx, strconv.Itoa(id), netns)
if err != nil {
log.Fatalf("failed to setup network for namespace: %v", err)
}

for key, iff := range result.Interfaces {
if len(iff.IPConfigs) > 0 {
IP := iff.IPConfigs[0].IP.String()
fmt.Printf("IP of the interface %s:%s\n", key, IP)
}
}

ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt)
<-ch
}

flannel-vxlan

flannel 网络插件分为两个部分,第一部分为 https://github.com/containernetworking/plugins 自带的 flannel ,用于完成工作一,通过调用 bridge 插件完成对容器命名空间网络的配置,而 https://github.com/flannel-io/flannel 用于完成工作二,使用 vxlan 实现容器跨节点通信。

手动部署的官网文件见 https://github.com/flannel-io/flannel/blob/master/Documentation/running.md

首先,利用 etcdctl 向 etcd 中添加相关的网络配置,注意这里使用的 etcd api 版本为 v2。

1
etcdctl --endpoints=http://10.211.55.2:2379 set /coreos.com/network/config '{ "Network": "10.5.0.0/16", "Backend": {"Type": "vxlan"}}'

在节点1和节点2上下载 https://github.com/flannel-io/flannel/releases 二进制程序并启动。

1
./flanneld-amd64 -etcd-endpoints http://10.211.55.2:2379

在节点1和节点2的 /etc/cni/net.d/ 下编写 flannel 配置文件 11-flannel.conf。此处的配置信息参考 https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml 中 ConfigMap 的 cni-conf.json 部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"name":"cbr0",
"cniVersion":"0.3.1",
"plugins":[
{
"type":"flannel",
"delegate":{
"hairpinMode":true,
"isDefaultGateway":true
}
},
{
"type":"portmap",
"capabilities":{
"portMappings":true
}
}]
}

在 节点1 和 节点2 上分别创建网络命名空间 ns-2,ns-3。

1
ip netns add ns-x

使用自己编写的 cnidemo 调用 flannel 插件来配置 ns-2,ns-3。

1
2
3
4
5
# 节点1
./cnidemo -id 2 -conf /etc/cni/net.d/11-flannel.conf

# 节点2
./cnidemo -id 3 -conf /etc/cni/net.d/11-flannel.conf

程序会输出容器网络命名空间的 ip ,我这里是 10.5.67.4 和 10.5.7.2。可以在 ns-2 中启动一个 http server,在 ns-3 中测试是否可以访问。如果可以成功访问,则表示 flannel 网络插件正常运行了。

1
2
3
4
5
# 节点1
ip netns exec ns-2 python3 -m http.server 8080

# 节点2
ip netns exec ns-3 curl http://10.5.67.4:8080

分析

flannel 利用 etcd 来对数据进行存取,利用 etcdctl 插件它在 etcd 中存储的数据。

1
etcdctl --endpoints=http://10.211.55.2:2379 ls /coreos.com/network

输出如下:

1
2
/coreos.com/network/config
/coreos.com/network/subnets

第一个就是之前配置的内容,这里查看第二个 key 的数据:

1
etcdctl --endpoints=http://10.211.55.2:2379 ls /coreos.com/network/subnets

输出如下:

1
2
/coreos.com/network/subnets/10.5.67.0-24
/coreos.com/network/subnets/10.5.7.0-24

可以看出,这是当前两个节点在容器网络中的子网信息,挑选第一个进行查看:

1
etcdctl --endpoints=http://10.211.55.2:2379 get /coreos.com/network/subnets/10.5.67.0-24

输出如下:

1
{"PublicIP":"10.211.55.32","BackendType":"vxlan","BackendData":{"VtepMAC":"92:3a:42:4a:9a:a9"}}

由于 vxlan 构建的是一个二层 overlay 网络,所以这里 BackendData 数据记录的是 flannel 守护进程创建的网卡 flannel.1 的物理 MAC 地址。这个 flannel.1 是 VTEP(VXLAN Tunnel Endpoints),输入命令查看其相关信息:

1
ip -d link show flannel.1

输出如下:

1
2
3
flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
link/ether 92:3a:42:4a:9a:a9 brd ff:ff:ff:ff:ff:ff promiscuity 0
vxlan id 1 local 10.211.55.32 dev enp0s5 srcport 0 0 dstport 8472 nolearning ageing 300 udpcsum addrgenmode none

在另一个节点节点2上查看转发表:

1
bridge fdb show dev flannel.1

输出如下:

1
92:3a:42:4a:9a:a9 dst 10.211.55.32 self permanent