乐趣区

关于kubernetes:网易数帆k8s集群对networkpolicy的实践felix

Networkpolicy 的含意与现状

networkpolicy 是 k8s 在很早就提出的一个抽象概念。它用一个对象来形容一类 pod 的网络出入站规定。对于 networkpolicy 的语义能够参考我之前的文章

networkpolicy 的作用对象是 pod,作用成果包含出站、入站,作用成果拓扑包含 IP 段、namespace、pod、端口、协定。

与以往 IaaS 服务场景下,针对虚拟机、网卡对象的平安组规定不同,networkpolicy 是 k8s 原语 。因而,在 k8s 场景下,进行网络安全规定的布局时, 用 networkpolicy 能做到更加的灵便和自动化。举个例子:

有一套工作负载 A 是做相似数据库代理一类的工作,它只容许代理服务 B 拜访,不容许其余业务拜访。

  • 在 k8s 场景下,如果不应用 networkpolicy,咱们须要布局好 A 类 pod 的部署节点,配置相应的 ACL 规定,将 B 类 pod 的 IP 予以放行,一旦 A / B 类 pod 做了扩缩容,可能要在重新配置一份甚至多份 ACL 规定。
  • 在 k8s 场景下,咱们会给 A 和 B 类别离配置 label,创立好 networkpolicy 后限度 A 只放行 B 类 pod,每当 A 或 B 扩缩容时,无需做任何额定操作。

网易数帆的 networkpolicy 反对

网易数帆的云计算业务的内部用户中,基于交付的 openshift-sdn 计划或 calico 计划,这些计划都能够原生地反对 networkpolicy(下文会介绍)。

但云计算业务的外部用户(应用 vpc 或 bgp CNI)大多是由 PE 布局治理 IP 白名单,限度某些网络拜访,除此之外没有做任何跨业务的网络限度(比如说:离线转码业务与领取业务是互不相干的,然而两种业务的 pod 彼此网络是可通信的)。因而始终没有 networkpolicy 的需要,而 vpc、bgp 等外部应用的 CNI 也还始终没有实现相干性能。

将来随着业务规模的扩充,相似的网络安全策略是必不可少的,因而咱们会在接下来逐渐将 networkpolicy enable。

业界的 networkpolicy 实现

以后社区对于 k8s 的 networkpolicy 的实现,不外乎三种计划:

计划 依赖 案例 反对的 CNI
基于 iptables+ipset 实现规定 容器流量须要通过宿主机的协定栈 calico felix calico、flannel、terway
基于 ovs 流表实现规定 应用 openvswitch openshift-sdn openshift-sdn
基于 ebpf hook 实现规定 须要较高版本内核 cilium cilium、flannel、terway

从下面的表格能够看出:

  1. 基于 ovs 流表实现的计划, 典型的就是 openshift-sdn,此前咱们分享过一篇 openshift-sdn 的详解, 介绍了外面对 ovs table 的设计,其中有一个专门的 table(tableid=21)就是用来实现 networkpolicy 的规定。该计划是间接内建于 openshift-sdn 我的项目,根本无奈移植。而 openshift-sdn 尽管代码开源,但设计上、代码逻辑上与 openshift 平台耦合还是比拟严密的。比如说:

    1. 公开的 openshift-sdn 部署计划须要依赖 openshift-network-operator
    2. openshift-sdn 代码中硬编码了要拜访的容器运行时为 crio,不反对 dockershim
  2. cilium 是最先应用 ebpf 技术实现网络数据面的 CNI,它力求实现大而全的容器网络,封装、加密、全面、高性能等特点包罗万象,它对于 networkpolicy 的反对也曾经非常欠缺。但 ebpf hook 的实现形式,依赖较高的内核版本,且在数据面排障时比拟吃力。ebpf 技术对于网络性能的晋升很大,将来势必会越来越风行,所以值得关注。
  3. 基于 iptables+ipset 技术实现的计划,其实在几年前就比拟成熟了 calico-felix、romana、kube-router 等开源的网络计划都是基于此实现了反对 networkpolicy。其中,felix 是 calico 网络计划中的一个组件,官网反对在 calico 中 enable networkpolicy,且可能与 flannel 配合应用。阿里云的 terway 便是间接套用 felix 实现了对 networkpolicy 的反对(最近还套用了 cilium)。这套计划要求容器流量要进过宿主机协定栈,否则包就不会进入内核的 netfilter 模块,iptables 规定就无奈失效。

指标

基于上述现状,咱们心愿基于现有的开源实现计划,进行兼容性调研或革新,适配网易数帆的各种网络计划,如:

  • netease-vpc
  • netease-bgp
  • flannel

因为这些网络计划都满足 felix 的要求,同时 felix 有较为沉闷的社区和较多的适配案例,因而咱们决定基于 felix,实现一套即插即用的 networkpolicy addon。本文接下来将会着重介绍该计划的实现。

calico/felix 的设计实现

架构

calico 在部署架构上做了屡次演进,咱们以最新版本 v3.17.1 为准。calico 的残缺架构包含了若干组件:

  • calico/kube-controllers:calico 控制器,用于监听一些 k8s 资源的变更,从而进行相应的 calico 资源的变更。例如依据 networkpolicy 对象的变更,变更相应的 calicopolicy 对象
  • pod2daemon:一个 initcontainer,用于构建一个 Unix Domain Socket,来让 Felix 程序与 Dikastes(calico 中反对 istio 的一种 sidecar,详见 calico 的 istio 集成) 进行加密通信.
  • cni plugin / ipam plugin:规范的 CNI 插件,用于配置 / 解除网络;调配 / 回收网络配置
  • calico-node calico-node 其实是一个数据面工具总成,包含了:

    • felix:治理节点上的容器网卡、路由、ACL 规定;并上报节点状态
    • bird/bird6:用来建设 bgp 连贯,并依据 felix 配置的路由,在不同节点间散发
    • confd:依据以后集群数据生成本地 brid 的配置
  • calicoctl:calico 的 CLI 工具。
  • datastore plugin:即 calico 的数据库,能够是独立的 etcd,也能够以 crd 形式记录于所在集群的 k8s 中
  • typha:相似于数据库代理,能够尽量少防止有大量的连贯建设到 apiserver。实用于超过 100 个 node 的集群。

官网给出了 calico 整体的组件架构图:

原理

在网络连通性(Networking)方面:calico 的数据面是非常简单的三层路由转发。路由的学习和散发由 bgp 协定实现。如果 k8s 的上层是 VPC 之类的三层网络环境,则须要进行 overlay,calico 反对 ipip 封装实现 overlay。

在网络安全性方面:calico 思考到其 Networking 是依赖宿主机协定栈进行路由转发实现的,因而能够基于 iptables+ipset 进行流量标记、地址集布局、流量解决(放行或 DROP),并且基于这些操作能够实现:

  1. networkpolicy 的抽象概念
  2. calico 自定义的 networkpolicy,为了在 openstack 场景下利用而设计
  3. calico 自定义的 profile,已废除。

这里所有的 iptables 规定都作用在:

  1. pod 在宿主机 namespace 中的 veth 网卡(calico 中将之称为 workload)
  2. 宿主机 nodeIP 所在网卡(calico 中将之称为 host-endpoint,实际上这部分规定不属于 k8s 的 networkpolicy 领域)。

次要包含如下几类规定:

  • iptables 的 INPUT 链规定中,会先跳入 cali-INPUT 链,在cali-INPUT 链中,会判断和解决两种方向的流量:

    • pod 拜访 node(cali-wl-to-host)实际上这个链中只走了 cali-from-wl-dispatch 链,如果是利用在 openstack 中,该链还会容许拜访 metaserver;如果应用 ipv6,该链中还会容许收回 icmpv6 的一系列包
    • 来自 node 的流量(cali-from-host-endpoint)
  • iptables 的 OUTPUT 链中,会首先跳入 cali-OUTPUT 链,在 cali-OUTPUT 链中,次要会解决:

    • 拜访 node 的流量(cali-to-host-endpoint)的流量
  • iptables 的 FORWARD 链中,会首先跳入 cali-FORWARD 链,在 cali-FORWARD 链中会解决如下几种流量:

    • 来自 node 转发的流量cali-from-hep-forward
    • 从 pod 中收回的流量cali-from-wl-dispatch
    • 达到 pod 的流量cali-to-wl-dispatch
    • 达到 node 的转发流量cali-to-hep-forward
    • 纯正的 IP 段到 IP 段的转发流量cali-cidr-block

k8s 的 networkpolicy 只须要关注上述流量中与 pod 相干的流量,因而只须要关怀:

  • cali-from-wl-dispatch
  • cali-to-wl-dispatch

这两个链的规定,对应到 pod 的 egress 和 ingress networkpolicy。

1. 除了 nat 表,在 raw 和 mangle 表中还有对 calico 关注的网卡上的收发包进行初始标记的规定,和最终的判断规定。2. 在 https://github.com/projectcalico/felix/blob/master/rules/static.go 中能够看到残缺的动态 iptables 表项的设计

接着,iptables 规定中还会在 cali-from-wl-dispatchcali-to-wl-dispatch两个链中依据收包 / 发包的网卡判断这是哪个 pod,走到该 pod 的 egress 或 ingress 链中。每个 pod 的链中则又设置了对应 networkpolicy 实例规定的链,以此递归调用。

这样,pod 的流量通过 INPUT/OUTPUT/FORWARD 等链后,递归地走了多个链,每个链都会 Drop 或者 Return,如果把链表走一遍下来始终 Return,会 Return 到 INPUT/OUTPUT/FORWARD,而后执行 ACCEPT,也就是说这个流量满足了某个 networkpolicy 的规定限度。如果过程中被 Drop 了,就示意受某些规定限度,这个链路不通。

咱们通过一个简略的例子来形容 iptables 这块的链路程序。

felix 实现 networkpolicy 的案例

假如有如下一个 networkpolicy:

  spec:
    egress:
    - {}
    ingress:
    - from:
      - podSelector:
          matchLabels:
            hyapp: client1
    - from:
      - ipBlock:
          cidr: 10.16.2.0/24
          except:
          - 10.16.2.122/32
      ports:
      - port: 3456
        protocol: TCP
    podSelector:
      matchLabels:
        hyapp: server
  • 他作用于有 hyapp=server 的 label 的 pod
  • 这类 pod 出方向不限度
  • 这类 pod 的入站规定中只容许如下几种流量:

    • 来自于有 hyapp=client1 的 label 的 pod
    • 10.16.2.0/24 网段中除了 10.16.2.122/32 以外的 IP 能够拜访该类 pod 的 3456 TCP 端口。

咱们应用 iptables -Liptables-save 命令来剖析机器上的 iptables 规定。

因为是入站规定,所以咱们能够察看 iptables 表中的 cali-to-wl-dispatch 链。另外,该 networkpolicy 的作用 pod 只有一个,它的 host 侧网卡是veth-13dd25c5cb。咱们能够看到如下的几条规定:

Chain cali-to-wl-dispatch (1 references)
target     prot opt source               destination
cali-to-wl-dispatch-0  all  --  anywhere             anywhere            [goto]  /* cali:Ok_j0t6AwtLyoFYU */
cali-tw-veth-13dd25c5cb  all  --  anywhere             anywhere            [goto]  /* cali:909gC5dwdBI3E96S */
DROP       all  --  anywhere             anywhere             /* cali:4M4uUxEEGrRKj1PR */ /* Unknown interface */

留神,这里有一个 cali-to-wl-dispatch-0 的链,是用来做前缀映射的,该链的规定下蕴含所有cali-tw-veth-0 这个前缀的链:

Chain cali-to-wl-dispatch-0 (1 references)
target     prot opt source               destination
cali-tw-veth-086099497f  all  --  anywhere             anywhere            [goto]  /* cali:Vt4xxuTYlCRFq62M */
cali-tw-veth-0ddbc02656  all  --  anywhere             anywhere            [goto]  /* cali:7FDgBEq4y7PN7kMf */
DROP       all  --  anywhere             anywhere             /* cali:up42FFMQCctN8FcW */ /* Unknown interface */

这是 felix 设计上用于缩小 iptables 规定遍历次数的一个优化伎俩。

咱们通过 iptables-save |grep cali-to-wl-dispatch 命令,能够发现如下的规定:

cali-to-wl-dispatch -o veth-13dd25c5cb -m comment --comment "cali:909gC5dwdBI3E96S" -g cali-tw-veth-13dd25c5cb

意思就是:在 cali-to-wl-dispatch 链中,依据 pod 在 host 侧网卡的名字,会执行 cali-tw-veth-13dd25c5cb 链,咱们再看这条链:

   Chain cali-tw-veth-13dd25c5cb (1 references)
   target     prot opt source               destination
1  ACCEPT     all  --  anywhere             anywhere             /* cali:RvljGbJwZ8z9q-Ee */ ctstate RELATED,ESTABLISHED
2  DROP       all  --  anywhere             anywhere             /* cali:krH_zVU1BetG5Q5_ */ ctstate INVALID
3  MARK       all  --  anywhere             anywhere             /* cali:Zr20J0-I__oX_Y2w */ MARK and 0xfffeffff
4  MARK       all  --  anywhere             anywhere             /* cali:lxQlOdcUUS4hyf-h */ /* Start of policies */ MARK and 0xfffdffff
5  cali-pi-_QW8Cu1Tr3dYs2pTUY0-  all  --  anywhere             anywhere             /* cali:d2UTZGk8zG6ol0ME */ mark match 0x0/0x20000
6  RETURN     all  --  anywhere             anywhere             /* cali:zyuuqgEt28kbSlc_ */ /* Return if policy accepted */ mark match 0x10000/0x10000
7  DROP       all  --  anywhere             anywhere             /* cali:DTh9dO0o6NsmIQSx */ /* Drop if no policies passed packet */ mark match 0x0/0x20000
8  cali-pri-kns.default  all  --  anywhere             anywhere             /* cali:krKqEtFijSLu5oTz */
9  RETURN     all  --  anywhere             anywhere             /* cali:dgRtRf38hD2ZVmC7 */ /* Return if profile accepted */ mark match 0x10000/0x10000
10 cali-pri-ksa.default.default  all  --  anywhere             anywhere             /* cali:NxmrZYbhCNLKgL6O */
11 RETURN     all  --  anywhere             anywhere             /* cali:zDbjbrN6JPMZx9S1 */ /* Return if profile accepted */ mark match 0x10000/0x10000
12 DROP       all  --  anywhere             anywhere             /* cali:d-mHGbHkL0VRl6I6 */ /* Drop if no profiles matched */
  • 第 1、2 条:如果 ct 表中能检索到该连贯的状态,咱们间接依据状态来确定这个流量的解决形式,这样能够省略很大一部分工作。
  • 第 3 条:先对包进行标记(将第 17 地位 0),在本链的规定执行结束后,会判断标记是否 match(判断第 17 位是否有被置 1),不匹配(没有被置 1)就 DROP;
  • 第 4 条:如果该网卡对应的 pod 有相干的 networkpolicy,要再打一次 mark,与之前的 mark 做与计算后目前 mark 应该是 0xfffcffff(17、18 位为 0);
  • 第 5 条:如果包 mark match 0x0/0x20000(第 18 位为 0),执行 cali-pi-_QW8Cu1Tr3dYs2pTUY0- 链进入 networkpolicy 的判断。
  • 第 6、7 条:如果 networkpolicy 查看通过,会对包进行 mark 批改,所以查看是否 mark match 0x10000/0x10000, 匹配阐明通过,间接 RETURN,不再查看其余的规定;如果 mark 没有批改,与原先统一,视为没有任何一个 networkpolicy 容许该包通过,间接 DROP
  • 第 8、9、10、11 条:当没有任何相干的 networkpolicy 时(即第 4~7 条不存在)才会被执行,执行 calico 的 profile 策略,分成 namespace 维度和 serviceaccount 维度,如果在这两个策略里没有对包的 mark 做任何批改,就示意通过。这两个策略是 calico 的概念,且为了不与 networkpolicy 混同,曾经被弃用了。因而此处都是空的。
  • 第 12 条:如果包没有进入 上述两个 profile 链,DROP。

接着看 networkpolicy 的链cali-pi-_QW8Cu1Tr3dYs2pTUY0-,只有在这个链里执行 Return 前有将包打上 mark 使其 match 0x10000/0x10000,就示意匹配了某个 networkpolicy 规定,包容许放行:

Chain cali-pi-_QW8Cu1Tr3dYs2pTUY0- (1 references)
target     prot opt source               destination
MARK       all  --  anywhere             anywhere             /* cali:fdm8p72wShIcZesY */ match-set cali40s:9WLohU2k-3hMTr5j-HlIcA0 src MARK or 0x10000
RETURN     all  --  anywhere             anywhere             /* cali:63L9N_r1RGeYN8er */ mark match 0x10000/0x10000
MARK       all  --  anywhere             anywhere             /* cali:xLfB_tIU4esDK000 */ MARK xset 0x40000/0xc0000
MARK       all  --  10.16.2.122          anywhere             /* cali:lUSV425ikXY6zWDE */ MARK and 0xfffbffff
MARK       tcp  --  10.16.2.0/24         anywhere             /* cali:8-qnPNq_KdC2jrNT */ multiport dports 3456 mark match 0x40000/0x40000 MARK or 0x10000
RETURN     all  --  anywhere             anywhere             /* cali:dr-rzJrx0I6Vqfkl */ mark match 0x10000/0x10000
  • 第 1、2 条:如果 src ip match ipset:cali40s:9WLohU2k-3hMTr5j-HlIcA0 , 将包 mark or 0x10000,并查看是否 match,match 就 RETUR。咱们能够在机器上执行 ipset list cali40s:9WLohU2k-3hMTr5j-HlIcA0 ,能够看到这个 ipset 里蕴含的就是 networkpolicy 中指明的、带有hyapp=client1 这个 label 的两个 pod 的 ip。
  • 第 3、4、5、6 条则是针对 networkpolicy 中的第二局部规定,先对包设置正向标记,而后将要隔离的 src IP/IP 段进行判断并做反向标记,接着判断 src 段是否在准入范畴,如果在,并且目标端口匹配,并且标记为正向,就再对包进行 MARK or 0x10000,这样,最终判断 match 了就会 Return。
  • 实际上咱们能够看到,这里就算不 match,这个链执行完了也还是会 RETURN 的,所以这个链执行的后果是通过 mark 返回给上一级的,这就是为什么调用该链的上一级,会在调用结束后要判断 mark 并确认是否 ACCEPT。

至此,一个残缺的 networkpolicy 的实现链路就实现了。

egress 规定与上述 ingress 规定相似。能够参考下图:

通用的 felix 插件设计

如果你看了上文 calico/felix 的设计实现,你就会发现原理其实非常简单,这个设计齐全能够利用到任何一个“基于三层路由转发”的网络计划中。但理论利用过程中咱们还是遇到了一些问题。

问题 1:networkpolicy-only

咱们晓得,较新版本的 felix 都是集成到 calico-node 组件中运行。calico-node 默认状况下会实现容器网络和 networkpolicy 两块工作,如何部署一个只负责实现 networkpolicy 规定的 calico-node 呢?

能够参考 calico 官网提供的 canal 计划是如何适配 flannel 的。从 canal 的部署模板中咱们能够根本确认,只有部署好 kube-controllers,pod2daemon,calico-node 并且通过环境变量管制 calico-nodeCALICO_NETWORKING环境变量为"false"(禁止配置容器网络)即可。

问题 2: 网卡名映射

咱们尝试在轻舟 k8s 集群(应用网易云 VPC 作为容器网络)中尝试以这样的形式部署一套 calico 套件,部署后,咱们会发现 calico-node 的日志里定期报错,提醒:找不到 cali**** 的网卡,无奈配置 iptables 规定 。有用过 calico 的同学应该看得明确,cali 是 calico 计划在宿主机侧生成的网卡名前缀。而咱们基于网易云 VPC 设计的容器网络计划,会以 veth- 为容器 hostveth 前缀。如果 calico 是基于前缀来找到容器网卡的,那么是否有参数能够指定前缀呢?

官网的 felix 配置文档中提到:能够应用 InterfacePrefix 参数或 FELIX_INTERFACEPREFIX 环境变量,决定 felix 要检索的 host 侧网卡前缀。一开始看到这个阐明令人欣喜万分。然而当咱们理论配置了之后,会发现,calico-node 还是会报错,提醒: 找不到 veth-***** 的网卡,这个网卡名超过了 linux 内核的常数限度(15 个字符)

咱们依照日志里打印的网卡名去找,的确找不到这个网卡,看来必须要搞清楚 calico 是如何给 host 侧的网卡进行命名的。

calico 为 pod 的 veth 命名的规定实现在 libcalico-go 我的项目中,存在如下的一个接口

type WorkloadEndpointConverter interface {VethNameForWorkload(namespace, podName string) string
    PodToWorkloadEndpoints(pod *kapiv1.Pod) ([]*model.KVPair, error)
}

这个接口用用来实现 pod 映射到 workload 的,同时还能依据 pod 的信息,推导 pod 的 hostveth 网卡名是啥,该接口只有一种实现:defaultWorkloadEndpointConverter, 其中 VethNameForWorkload 的实现如下:

// VethNameForWorkload returns a deterministic veth name
// for the given Kubernetes workload (WEP) name and namespace.
func (wc defaultWorkloadEndpointConverter) VethNameForWorkload(namespace, podname string) string {
    // A SHA1 is always 20 bytes long, and so is sufficient for generating the
    // veth name and mac addr.
    h := sha1.New()
    h.Write([]byte(fmt.Sprintf("%s.%s", namespace, podname)))
    prefix := os.Getenv("FELIX_INTERFACEPREFIX")
    if prefix == "" {
        // Prefix is not set. Default to "cali"
        prefix = "cali"
    } else {
        // Prefix is set - use the first value in the list.
        splits := strings.Split(prefix, ",")
        prefix = splits[0]
    }
    log.WithField("prefix", prefix).Debugf("Using prefix to create a WorkloadEndpoint veth name")
    return fmt.Sprintf("%s%s", prefix, hex.EncodeToString(h.Sum(nil))[:11])
}

能够看到,calico 依据 pod 的 namespace 和 name 进行 hash,而后依据 FELIX_INTERFACEPREFIX 环境变量的值决定网卡名前缀,将前缀与 hash 的前 11 个字符拼凑起来。libcalico-go是所有 calico 组件的 lib 库,也就是说,不论是 calico-cni 去创立 veth,还是 felix 去依据 pod 查找对应的网卡,都是基于这个逻辑去匹配的。

显然这个代码破绽很大!calico 没有对前缀做长度查看,这里要填充 hash 的前 11 位,齐全是因为默认的前缀是四个字符的cali

问题十分明确了,要想在本人的网络计划下无痛享受 felix,就得本人实现一个 WorkloadEndpointConverter 接口,并编译出定制化的 calico-node 镜像。

案例 1: canal 如何接入

从 canal 的部署模板中咱们就能够看得出来,canal 计划中应用的 CNI plugin 实际上也是 calico,只不过 calico 只负责创立 veth 对,配置 IP 和路由等工作,veth 的命名交给 calico 来做,天然就依照 calico 的官网配置来命名了,理论应用过程中就能够看到,canal 计划下容器在宿主机上的 veth 名称也是 cali 前缀。

案例 2: 阿里云 terway 如何接入

阿里云的 ACK 应用其自研的 terway 来作为容器网络计划。terway 中反对两种容器网卡虚拟化计划:

  • veth
  • ipvlan L2

veth 计划下会应用 felix 来实现 networkpolicy,而 ipvlan 下则应用 cilium。咱们此处次要关注 veth 计划。

veth 计划下 felix 是如何应用的呢?terway 在部署时,间接基于社区 v3.5.8 版本的 felix 代码进行编译(编译前还往代码中退出了一个 terway 自定义的 patch),将编译进去的 felix 二进制文件丢到 terway 的 docker 镜像中,daemonset 里启动三个 terway 镜像容器,别离用于装置 cni 插件;运行 agent;运行 felix。

terway 是如何实现兼容 felix 的呢?上文提到的网卡名的问题,它如何解决呢?

通过浏览 terway 的源码,咱们发现 terway 做得比拟暴力——间接复用了 calico 代码中的网卡命名形式,对 host 侧的 veth 进行命名,网卡前缀为硬编码的cali

案例 3: 网易云 k8s 如何接入

网易云的场景中,host 侧网卡命名是以某个前缀加上 pod 的 sandbox 容器 id 来命名的(起因见下文)。因而咱们即使把前缀改成 cali 或者其余长度 4 以内的字符串,felix 也无奈基于 calico 的那套逻辑找到网卡。

因而咱们改写了该接口。实现了一个 sandboxWorkloadEndpointConverter,将VethNameForWorkload 做了另一种实现:

  1. 依据 felix 参数感知自定义的网卡名前缀,这里为了防止 prefix 太长,导致网卡名抵触,对 prefix 长度进行限度,倡议不超过 5 个字符,至多给后缀保留 10 个字符(咱们已经在线上环境呈现过同一个节点的两个 podsandbox 容器 id 前 9 位完全相同的状况)
  2. 依据 pod 信息获取到他对应的 sandbox 容器 ID,取其 15-len(prefix) 位作为后缀。
  3. 通过前缀与 sandboxID 后缀形成 workload 的网卡名。

将来咱们会尝试对这部分代码做更通用化的革新,反对多种前缀,并反对主动选用网卡命名办法。

为什么咱们要以 podsandbox 容器 id 来命名网卡?

因为理论应用过程中咱们发现,kubelet 对于 sandbox 容器的解决并不一定是有序的,可能呈现如下场景:

  1. 为 poda 创立出 sandbox1,调用 CNI ADD 失败,但 veth 曾经创立;
  2. 为 poda 创立出 sandbox2,调用 CNI ADD 胜利,间接应用了上一次创立的 veth;
  3. 删除此前曾经失败的 sandbox1,调用 CNI DEL。将第 2 步创立的 veth 删除,导致 poda 的网络异样

因而,如果 kubelet 调用 CNI 是以 sandbox 为粒度,那么咱们创立的资源就理当也以 sandbox 为粒度。

编译与构建

目前咱们将通用的 felix 基于 calico/node 的 v3.17 分支构建,并将它援用的libcalico-gofork 到网易云的 github organization 我的项目:163yun/libcalico-go,并建设分支 tag:v1.7.2-nks.1

这样,咱们能够间接拉取社区代码,进行编译:

cd $GOPATH/src
mkdir -p github.com/projectcalico
cd github.com/projectcalico
git clone https://github.com/projectcalico/node
cd node

# 批改依赖包,改为援用咱们批改过后的 libcalico-go
go mod edit -replace=github.com/projectcalico/libcalico-go=github.com/163yun/libcalico-go@v1.7.2-nks.1
# 编译出 calico/node 的 docker  image
make calico/node

docker tag calico/node:latest-amd64 $EXPECTED_IMAGE_PATH:$EXPECTED_IMAGE_TAG

编译构建过程中可能呈现一些网络起因导致编译阻塞:

  • 编译过程中会在容器里进行 go build,为了不便执行 go module,所以倡议在 Makefile、以及执行 make 时下载的长期版本Makefile.common.v*** 文件中的局部地位注入环境变量:GOPROXY=https://goproxy.cn
  • 编译结束后构建 docker image 时,会在根底镜像中下载安装多个依赖工具,可能呈现 yum 源无奈解析等问题,倡议在 Makefile 文件中调用 docker build 的语句里追加参数--network=host

测试方法

首先咱们要筹备好网易轻舟 k8s 集群,并应用 vpc 或 bgp 网络计划,并额定部署 felix 套件(参考上图)。

咱们应用 sonobuoy 工具进行测试工作,该工具也能够素来进行 k8s 集群的 conformance 认证。下载该工具的二进制文件,而后执行:

sonobuoy  run --e2e-focus="\[Feature:NetworkPolicy\]" --e2e-skip="" --image-pull-policy IfNotPresent 

即可在以后集群里进行 networkpolicy 相干的 e2e 测试(并且测试过程创立的 pod 的 imagePullPolicy 都是IfNotPresent)。

执行命令后能够查看集群中的 pods,会看到 sonobuoy 的 pod 以及它创立进去的一些 e2e 相干的 pod,如果有 pod 阻塞于 ImagePullBackoff,能够尝试在 pod 所在节点上拉取备用镜像并批改成所需镜像:

docker pull hub.c.163.com/combk8s/conformance:v1.19.3
docker tag hub.c.163.com/combk8s/conformance:v1.19.3 k8s.gcr.io/conformance:v1.19.3
docker pull hub.c.163.com/combk8s/e2e-test-images/agnhost:2.20
docker tag hub.c.163.com/combk8s/e2e-test-images/agnhost:2.20 k8s.gcr.io/e2e-test-images/agnhost:2.20

测试结束后,实践上所有 e2e 前缀的 pod 都会被删除,此时执行 sonobuoy retrieve 命令,会在当前目录生成一个 tar.gz 文件,解压该文件,并读取 plugins/e2e/results/global/e2e.log,就能够看到整个 e2e 测试的执行后果。也能够通过plugins/e2e/sonobuoy_results.yaml 文件查看,但这个文件内容包含了未执行的用例,可读性可能不太好。

简要的 e2e 测试后果如下:

root@pubt2-nks-for-dev6:/home/hzhuangyang1/plugins/e2e/results/global# tail -n 10 e2e.log

JUnit report was created: /tmp/results/junit_01.xml
{"msg":"Test Suite completed","total":29,"completed":29,"skipped":5204,"failed":0}

Ran 29 of 5233 Specs in 4847.335 seconds
SUCCESS! -- 29 Passed | 0 Failed | 0 Pending | 5204 Skipped
PASS

Ginkgo ran 1 suite in 1h20m48.75547671s
Test Suite Passed

总结

本文介绍了 networkpolicy 的特点和劣势,并剖析了当下支流的 networkpolicy 实现计划——calico-felix,摸索了 calico-felix 通用化革新的计划和落地。随着 calico-felix 的引入,用户能同时享受到 vpc 的易扩展性和 networkpolicy 的灵活性。

将来咱们还将着力引入 cilium 实现高内核版本下、基于 ebpf 实现的 networkpolicy,并实现 networkpolicy 拓扑可视化。

退出移动版