Cilium网络概述

Cilium是一个基于eBPF和XDP的高性能容器网络方案,与其他网络方案不同的是,Cilium着重强调了其在网络安全上的优势,可以透明的对Kubernetes等容器管理平台上的应用程序服务之间的网络连接进行安全防护。

Cilium在设计和实现上,基于Linux的一种新的内核技术eBPF,可以在Linux内部动态插入强大的安全性、可见性和网络控制逻辑,相应的安全策略可以在不修改应用程序代码或容器配置的情况下进行应用和更新。

Cilium在其官网上对产品的定位称为“eBPF-based Networking, Observability, and Security”,其特性主要包括以下三方面:

  1. 提供Kubernetes中基本的网络互连互通的能力,实现容器集群中包括Pod、Service等在内的基础网络连通功能;
  2. 依托eBPF,实现Kubernetes中网络的可观察性以及基本的网络隔离、故障排查等安全策略;
  3. 依托eBPF,突破传统主机防火墙仅支持L3、L4微隔离的限制,支持基于API的网络安全过滤能力。Cilium提供了一种简单而有效的方法来定义和执行基于容器/Pod身份(Identity Based)的网络层和应用层(比如HTTP/gRPC/Kafka等)安全策略。

架构与组件


Cilium官方给出了如下的架构图,Cilium位于容器编排系统和Linux Kernel之间,向上可以通过编排平台为容器进行网络以及相应的安全配置,向下可以通过在Linux内核挂载eBPF程序,来控制容器网络的转发行为以及安全策略执行。

在Cilium的架构中,除了Key-Value数据存储之外,主要组件包括Cilium Agent和Cilium Operator,还有一个客户端的命令行工具Cilium CLI。

Cilium Agent作为整个架构中最核心的组件,通过DaemonSet的方式,以特权容器的模式运行在集群的每个主机上。Cilium Agent作为用户空间守护程序,通过插件与容器运行时和容器编排系统进行交互,进而为本机上的容器进行网络以及安全的相关配置。同时提供了开放的API,供其他组件进行调用。

Cilium Agent在进行网络和安全的相关配置时,采用eBPF程序进行实现。Cilium Agent结合容器标识和相关的策略,生成eBPF程序,并将eBPF程序编译为字节码,将它们传递到Linux内核。

Cilium Operator主要负责管理集群中的任务,尽可能的保证以集群为单位,而不是单独的以节点为单位进行任务处理。主要包括通过etcd为节点之间同步资源信息、确保Pod的DNS可以被Cilium管理、集群NetworkPolicy的管理和更新等。

组网模式


Cilium提供多种组网模式,默认采用基于vxlan的overlay组网。除此之外,还包括:

  1. 通过BGP路由的方式,实现集群间Pod的组网和互联;
  2. 在AWS的ENI(Elastic Network Interfaces)模式下部署使用Cilium;
  3. Flannel和Cilium的集成部署;
  4. 采用基于ipvlan的组网,而不是默认的基于veth;
  5. Cluster Mesh组网,实现跨多个Kubernetes集群的网络连通和安全性

多种组网模式

本文将针对默认的基于vxlan的overlay组网,进行数据包路径分析。

Overlay组网数据包路径分析


Cilium的安装可参考《Kubernetes网络方案Cilium》一文,此处不再赘述

在基于vxlan的overlay组网情况下,主机上的网络发生了以下变化:在主机的root命名空间,新增了如下图所示的四个虚拟网络接口,其中cilium_vxlan主要是对数据包进行vxlan封装和解封装操作;cilium_netcilium_host是一对veth-pair,cilium_host作为该节点所管理的Cluster IP子网的网关,还有一个lxc_health,用于节点之间的健康检测。

在每个主机上,可以进入Cilium Agent,查看其隧道配置。比如进入主机easyk8s1上的Cilium Agent,运行cilium bpf tunnel list,可以看到,其为集群中的另一台主机easyk8s2上的虚拟网络10.244.1.0创建了一个隧道。同样在easyk8s1上也有一条这样的隧道配置。

然后创建busybox1和busybox2两个Pod运行于easyk81,busybox3和busybox4两个Pod运行于easyk8s2。其与主机的root命名空间,通过veth-pair连接,如下图所示。

进入任意Pod,可以发现,Cilium已经为其分配了IP地址,并且设置了默认的路由,默认路由指向了本机的cilium_host。初始状态Pod内的ARP表为空。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@easyk8s1 ~]# kubectl  exec -ti busybox1 -- ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
15: eth0@if16: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 0a:c5:89:f9:04:84 brd ff:ff:ff:ff:ff:ff
inet 10.244.2.96/32 scope global eth0
valid_lft forever preferred_lft forever
[root@easyk8s1 ~]# kubectl exec -ti busybox1 -- route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.244.2.35 0.0.0.0 UG 0 0 0 eth0
10.244.2.35 0.0.0.0 255.255.255.255 UH 0 0 0 eth0
[root@easyk8s1 ~]# kubectl exec -ti busybox1 -- arp
[root@easyk8s1 ~]#

同节点Pod通信

以busybox1 ping busybox2为例

busybox1的IP地址为10.244.2.96/32,busybox2的IP地址为10.244.2.21/32,掩码均为32位。当busybox1准备发送ping包时,数据包逐层封装,网络层(3层)源IP为10.244.2.96/32,目的IP为10.244.2.21/32,其通过路由表(注:每个Node上所有的容器路由表一样)查询到下一跳为10.244.2.35。数据链路层(2层)源MAC地址为0a:c5:89:f9:04:84,目的MAC地址因初始状态下Pod内的ARP表为空,busybox1需发送ARP请求目的MAC地址。

1
2
3
4
5
6
[root@easyk8s1 ~]# kubectl  exec -ti busybox1 -- route -n 
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.244.2.35 0.0.0.0 UG 0 0 0 eth0
10.244.2.35 0.0.0.0 255.255.255.255 UH 0 0 0 eth0
[root@easyk8s1 ~]# kubectl exec -ti busybox1 -- arp

busybox1发送ARP请求目的MAC地址,busybox1的eth0网卡是一对veth-pair设备,另一端ifindex为16的lxca60338c921d9在Node节点easyk8s1上。

1
2
3
4
5
6
7
8
9
10
11
12
[root@easyk8s1 ~]# kubectl  exec -ti busybox1 -- ip a
15: eth0@if16: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 0a:c5:89:f9:04:84 brd ff:ff:ff:ff:ff:ff
inet 10.244.2.96/32 scope global eth0
valid_lft forever preferred_lft forever

[root@easyk8s1 ~]# ip addr show
...
16: lxca60338c921d9@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether e2:d8:51:8b:44:d4 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::e0d8:51ff:fe8b:44d4/64 scope link
valid_lft forever preferred_lft forever

lxca60338c921d9网卡收到ARP请求包,内核处理ARP请求,发现地址就在本机,但是地址所属网卡cilium_host为NOARP状态,根据Cilium Agent挂载的eBPF程序实现,将使用接收ARP请求包的lxca60338c921d9网卡的MAC地址进行响应。(分别在网卡cilium_hostlxca60338c921d9抓取MAC报文,后者收到报文。报文中MAC回包的mac地址是lxca60338c921d9的MAC地址e2:d8:51:8b:44:d4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@easyk8s1 ~]# ip addr show
...
6: cilium_net@cilium_host: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether d6:99:91:04:02:a5 brd ff:ff:ff:ff:ff:ff
inet6 fe80::d499:91ff:fe04:2a5/64 scope link
valid_lft forever preferred_lft forever
7: cilium_host@cilium_net: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 6e:5e:1e:d7:c2:07 brd ff:ff:ff:ff:ff:ff
inet 10.244.2.35/32 scope link cilium_host
valid_lft forever preferred_lft forever
inet6 fe80::6c5e:1eff:fed7:c207/64 scope link
valid_lft forever preferred_lft forever

[root@easyk8s1 ~]# tcpdump -nnn -vvv -i lxca60338c921d9 arp
tcpdump: listening on lxca60338c921d9, link-type EN10MB (Ethernet), capture size 262144 bytes
15:29:03.640538 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 10.244.2.35 tell 10.244.2.96, length 28
15:29:03.640654 ARP, Ethernet (len 6), IPv4 (len 4), Reply 10.244.2.35 is-at e2:d8:51:8b:44:d4, length 28

busybox1收到ARP应答后,获取到目的MAC地址并缓存,然后组装ICMP REQUEST包并发送

1
2
[root@easyk8s1 ~]# kubectl  exec -ti busybox1 -- ip neigh
10.244.2.35 dev eth0 lladdr e2:d8:51:8b:44:d4 used 0/0/0 probes 1 STALE

lxca60338c921d9网卡收到ICMP REQUEST报文,解封装,发现二层目的MAC地址是自身,继续解分装,发现三层目的IP地址是10.244.2.21/32,查询路由表(所有主机的路由表podsubnet网段内的路由都指向cilium_host)。cilium_host是eBPF埋钩子的地方,eBPF进行ingress方向的转发逻辑(包括prefilter、解密、L3/L4负载均衡、L7 Policy等)。进入对应Node节点的Cilium Agent通过cilium bpf endpoint list可以查询其转发规则。根据规则重新对数据包进行封装,发送给busybox2容器的veth-pair接口lxc021c0c8b5d94

1
2
3
4
5
6
7
8
[root@easyk8s1 ~]# kubectl exec -ti cilium-wtdbp -n kube-system -- cilium bpf endpoint list
IP ADDRESS LOCAL ENDPOINT INFO
10.244.2.21:0 id=6 flags=0x0000 ifindex=18 mac=2E:5A:9A:9E:01:00 nodemac=36:33:68:68:1F:DD
10.244.2.96:0 id=890 flags=0x0000 ifindex=16 mac=0A:C5:89:F9:04:84 nodemac=E2:D8:51:8B:44:D4
10.211.55.10:0 (localhost)
10.211.55.7:0 (localhost)
10.244.2.35:0 (localhost)
10.244.2.172:0 id=109 flags=0x0000 ifindex=10 mac=22:4F:87:F3:0C:94 nodemac=C6:F5:CD:16:6D:E2

busybox2收到ICMP REQUEST包并处理,回应ICMP REPLY包,回包过程与上面过程相反,此处不再赘述

跨节点Pod通信

以busybox1 ping busybox3为例

busybox1的IP地址为10.244.2.96/32,busybox3的IP地址为10.244.1.152/32,掩码均为32位。当busybox1准备发送ping包时,数据包逐层封装,网络层(3层)源IP为10.244.2.96/32,目的IP为10.244.1.152/32,其通过路由表(注:每个Node上所有的容器路由表一样)查询到下一跳为10.244.2.35。数据链路层(2层)源MAC地址为0a:c5:89:f9:04:84,目的MAC地址因初始状态下Pod内的ARP表为空,busybox1需发送ARP请求目的MAC地址。

1
2
3
4
5
6
[root@easyk8s1 ~]# kubectl  exec -ti busybox1 -- route -n 
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.244.2.35 0.0.0.0 UG 0 0 0 eth0
10.244.2.35 0.0.0.0 255.255.255.255 UH 0 0 0 eth0
[root@easyk8s1 ~]# kubectl exec -ti busybox1 -- arp

busybox1发送ARP请求请求目的MAC地址,busybox1的eth0网卡是一对veth-pair设备,另一端ifindex为16的lxca60338c921d9在Node节点easyk8s1上。

1
2
3
4
5
6
7
8
9
10
11
12
[root@easyk8s1 ~]# kubectl  exec -ti busybox1 -- ip a
15: eth0@if16: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 0a:c5:89:f9:04:84 brd ff:ff:ff:ff:ff:ff
inet 10.244.2.96/32 scope global eth0
valid_lft forever preferred_lft forever

[root@easyk8s1 ~]# ip addr show
...
16: lxca60338c921d9@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether e2:d8:51:8b:44:d4 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::e0d8:51ff:fe8b:44d4/64 scope link
valid_lft forever preferred_lft forever

lxca60338c921d9网卡收到ARP请求包,内核处理ARP请求,发现地址就在本机,但是地址所属网卡cilium_host为NOARP状态,根据Cilium Agent挂载的eBPF程序实现,将使用接收ARP请求包的lxca60338c921d9网卡的MAC地址进行响应。(分别在网卡cilium_hostlxca60338c921d9抓取MAC报文,后者收到报文。报文中MAC回包的mac地址是lxca60338c921d9的MAC地址e2:d8:51:8b:44:d4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@easyk8s1 ~]# ip addr show
...
6: cilium_net@cilium_host: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether d6:99:91:04:02:a5 brd ff:ff:ff:ff:ff:ff
inet6 fe80::d499:91ff:fe04:2a5/64 scope link
valid_lft forever preferred_lft forever
7: cilium_host@cilium_net: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 6e:5e:1e:d7:c2:07 brd ff:ff:ff:ff:ff:ff
inet 10.244.2.35/32 scope link cilium_host
valid_lft forever preferred_lft forever
inet6 fe80::6c5e:1eff:fed7:c207/64 scope link
valid_lft forever preferred_lft forever

[root@easyk8s1 ~]# tcpdump -nnn -vvv -i lxca60338c921d9 arp
tcpdump: listening on lxca60338c921d9, link-type EN10MB (Ethernet), capture size 262144 bytes
16:10:07.896625 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 10.244.2.35 tell 10.244.2.96, length 28
16:10:07.896682 ARP, Ethernet (len 6), IPv4 (len 4), Reply 10.244.2.35 is-at e2:d8:51:8b:44:d4, length 28

busybox1收到ARP应答后,获取到目的MAC地址并缓存,然后组装ICMP REQUEST包并发送

1
2
[root@easyk8s1 ~]# kubectl  exec -ti busybox1 -- ip neigh
10.244.2.35 dev eth0 lladdr e2:d8:51:8b:44:d4 used 0/0/0 probes 1 STALE

lxca60338c921d9网卡收到ICMP REQUEST报文,解封装,发现二层目的MAC地址是自身,继续解分装,发现三层目的IP地址是10.244.1.152/32,查询路由表(所有主机的路由表podsubnet网段内的路由都指向cilium_host)。cilium_host是eBPF埋钩子的地方,包括eBPF的Pod Level Policy、L7 Policy、加密等规则。本例中eBPF将查询tunnel转发规则,可进入easyk8s1节点的Cilium Agent通过cilium bpf tunnel list可以查询其隧道转发规则,根据隧道转发规则去往10.244.1.0/24网段的流量将发送给10.211.55.8,也就是easyk8s2节点。于是将ICMP REQUEST包重新封装发送给easyk8s1的cilium_vxlan网卡

1
2
3
[root@easyk8s1 ~]# kubectl exec -ti cilium-wtdbp -n kube-system -- cilium bpf tunnel list
TUNNEL VALUE
10.244.1.0:0 10.211.55.8:0

cilium_vxlan网卡会进行VXLAN封装(Cilium默认使用UDP 8472端口),通过underlay层路由,由物理网卡eth0发送给10.211.55.8,即easyk8s2节点

easyk8s2节点的eth0网卡收到报文以后,发现是VXLAN报文,便发给cilium_vxlan进行解封装,并根据内部目的地址路由

路由同样指向easyk8s2节点的cilium_host网卡,eBPF进行ingress方向的转发逻辑(包括prefilter、解密、L3/L4负载均衡、L7 Policy等)。进入easyk8s2节点的Cilium Agent通过cilium bpf endpoint list可以查询其转发规则。根据规则将数据包发送给busybox3容器的veth-pair接口lxcdf85c99387b7

1
2
3
4
5
6
7
[root@easyk8s1 ~]# kubectl exec -ti cilium-l2wpw -n kube-system -- cilium bpf endpoint list            
IP ADDRESS LOCAL ENDPOINT INFO
10.244.1.2:0 (localhost)
10.211.55.8:0 (localhost)
10.244.1.253:0 id=959 flags=0x0000 ifindex=10 mac=EA:1F:11:A8:CF:0D nodemac=DE:44:26:5F:50:CC
10.244.1.223:0 id=1419 flags=0x0000 ifindex=22 mac=C2:11:E1:7D:27:30 nodemac=92:C7:C1:10:8D:7C
10.244.1.152:0 id=3207 flags=0x0000 ifindex=20 mac=62:1B:E4:C9:4D:44 nodemac=EA:60:B5:7D:42:32

busybox3收到ICMP REQUEST包并处理,回应ICMP REPLY包,回包过程与上面过程相反,此处不再赘述

微隔离


默认情况下,Cilium与其他网络插件一样,提供了整个集群网络的完全互联互通,用户需要根据自己的应用服务情况设定相应的安全隔离策略。如下图所示,每当用户新创建一个Pod,或者新增加一条安全策略,Cilium Agent会在主机对应的虚拟网卡驱动加载相应的eBPF程序,实现网络连通以及根据安全策略对数据包进行过滤。比如,可以通过采用下面的NetworkPolicy实现一个基本的L3/L4层网络安全策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
description: "L3-L4 policy to restrict deathstar access to empire ships only"
metadata:
name: "rule1"
spec:
endpointSelector:
matchLabels:
org: empire
class: deathstar
ingress:
- fromEndpoints:
- matchLabels:
org: empire
toPorts:
- ports:
- port: "80"
protocol: TCP

然而,在微服务架构中,一个基于微服务的应用程序通常被分割成一些独立的服务,这些服务通过API(使用HTTP、gRPC、Kafka等轻量级协议)实现彼此的通信。因此,仅实现在L3/L4层的网络安全策略,缺乏对于微服务层的可见性以及对API的细粒度隔离访问控制,在微服务架构中是不够的。

我们可以看如下这个例子,Job Postings这个服务暴露了其服务的健康检查、以及一些增、删、改、查的API。Gordon作为一个求职者,需要访问Job Postings提供的Jobs相关信息。按照传统的L3/L4层的隔离方法,可以通过iptables -s 10.1.1.1 -p tcp –dport 80 -j ACCEPT,允许Gordon来访问Job Postings在80端口提供的HTTP服务。但是这样的网络规则,导致Gordon同样可以访问包括发布信息、修改信息、甚至是删除信息等其他接口。这样的情况肯定是我们的服务设计者所不希望发生的,同时也存在着严重的安全隐患。

因此,实现微服务间的L7层隔离,实现其对应的API级别的访问控制,是微服务网络微隔离的一个重要部分。Cilium在为Docker和Kubernetes等基于Linux的容器框架提供了支持API层面的网络安全过滤能力。通过使用eBPF,Cilium提供了一种简单而有效的方法来定义和执行基于容器/pod身份的网络层和应用层安全策略。我们可以通过采用下面的NetworkPolicy实现一个L7层网络安全策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
description: "L7 policy to restrict access to specific HTTP call"
metadata:
name: "rule1"
spec:
endpointSelector:
matchLabels:
org: empire
class: deathstar
ingress:
- fromEndpoints:
- matchLabels:
org: empire
toPorts:
- ports:
- port: "80"
protocol: TCP
rules:
http:
- method: "POST"
path: "/v1/request-landing"

Cilium还提供了一种基于Proxy的实现方式,可以更方便的对L7协议进行扩展。如下图所示,Cilium Agent采用eBPF实现对数据包的重定向,将需要进行过滤的数据包首先转发至Proxy代理,Proxy代理根据其相应的过滤规则,对收到的数据包进行过滤,然后再将其发回至数据包的原始路径,而Proxy代理进行过滤的规则,则通过Cilium Agent进行下发和管理。

当需要扩展协议时,只需要在Proxy代理中,增加对新协议的处理解析逻辑以及规则处置逻辑,即可实现相应的过滤能力。

参考文档