深入探索 Cilium 的工作机制
这篇之前写 Kubernetes 网络学习之 Cilium 与 eBPF 记录的内容,隔了几个月终于想起把笔记完成,作为探索 Cilium 工作原理的入门,也还是 Cilium 冰山一角,像是高级的网络策略、网络加密、BGP 网络、服务网格等方面并没有深入。如果阅读过程中有发现任何问题,也烦请纠正。
本文基于 Cilium v1.12 及 Kubernetes v1.25。
实验环境
我们使用 k8e 创建集群,因为 k8e 使用 Cilium 作为默认的 CNI 实现。在我的 homelab 上做个双节点(ubuntu-test1: 192.168.1.21
、ubuntu-test2: 192.168.1.22
)的集群。
Master 节点
curl -sfL https://getk8e.com/install.sh | API_SERVER_IP=192.168.1.21 K8E_TOKEN=ilovek8e INSTALL_K8E_EXEC="server --cluster-init --write-kubeconfig-mode 644 --write-kubeconfig ~/.kube/config" sh -
工作节点
curl -sfL https://getk8e.com/install.sh | K8E_TOKEN=ilovek8e K8E_URL=https://192.168.1.21:6443 sh -
部署示例应用,通过修改 nodeName 将两个 pod 分别调度两个节点上。
NODE1=ubuntu-test1
NODE2=ubuntu-test2
kubectl apply -n default -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
labels:
app: curl
name: curl
spec:
containers:
- image: curlimages/curl
name: curl
command: ["sleep", "365d"]
nodeName: $NODE1
---
apiVersion: v1
kind: Pod
metadata:
labels:
app: httpbin
name: httpbin
spec:
containers:
- image: kennethreitz/httpbin
name: httpbin
nodeName: $NODE2
EOF
Cilium 组件
Agent
cilium-agent
运行在集群的各个节点上(上图的 Cilium Daemon)。接收来自 Kubernetes 或者 Cilium API 的描述网络、服务负载均衡、网络策略以及可见性和监控需求的配置。
Agent 监控如 Kubernetes 等编排系统中容器或者负载的生命周期变化,管理着用来控制进出容器的网络访问的 eBPF 程序;对外(CNI Plugin、CLI)提供 API 来操作配置、Endpoint、Node、Policy、Service、IP 地址等资源。
API 包含如下几个模块:
- daemon
- endpoint
- ipam
- metrics
- policy
- prefilter
- recorder
- service
CLI
Cilium CLI 客户端,与 cilium-agent 一起安装。通过 Cilium REST API 与同节点上的本地 agent 进行交互。通过 CLI 可以检查本地 agent 的状态,也可以直接访问 eBPF map 的内容以便随时验证状态。
cilium
CLI for interacting with the local Cilium Agent
Usage:
cilium [command]
Available Commands:
bpf Direct access to local BPF maps
cleanup Remove system state installed by Cilium at runtime
completion Output shell completion code
config Cilium configuration options
debuginfo Request available debugging information from agent
encrypt Manage transparent encryption
endpoint Manage endpoints
fqdn Manage fqdn proxy
help Help about any command
identity Manage security identities
ip Manage IP addresses and associated information
kvstore Direct access to the kvstore
lrp Manage local redirect policies
map Access userspace cached content of BPF maps
metrics Access metric status
monitor Display BPF program events
node Manage cluster nodes
policy Manage security policies
prefilter Manage XDP CIDR filters
preflight cilium upgrade helper
recorder Introspect or mangle pcap recorder
service Manage services & loadbalancers
status Display status of daemon
version Print version information
Flags:
--config string config file (default is $HOME/.cilium.yaml)
-D, --debug Enable debug messages
-h, --help help for cilium
-H, --host string URI to server-side API
Use "cilium [command] --help" for more information about a command.
Operator
Cilium Operator 负责管理集群中的职责,逻辑上应该为整个集群处理一次,而不是为集群中的每个节点处理一次。Operator 不参与任何转发或者网络策略的决策。即使 Operator 短暂不可用,集群通常会继续运行,除了某些操作可能会失败。
Operator 允许多实例运行,但由 leader 实例完成特定操作,比如 Node CIDR 的分配。
CNI Plugin
Cilium 的 CNI 实现,当 pod 调度到某个节点后,该节点的容器运行时会调用 Cilium CNI 完成一系列的网络配置。CNI Plugin 会与 Agent 组件通过 REST API 进行交互,比如调用 API 分配 IP 地址、创建 Endpoint 等。
对这部分有兴趣的可以看下之前的文章 《Kubernetes 网络学习之 Cilium 与 eBPF》。
IPAM
Cilium 管理网络 Endpoint,Endpoint 使用的 IP 地址,由 IPAM 分配和管理。
支持 多种 IPAM 模型,默认使用 Cluster Scope cluster-pool
,通过 cilium-config
中的字段 ipam
来设置。
cluster-scope
IPAM 模型为各个集群 node 分配 PodCIDRs,然后在各个 node 上使用 host-scope 分配器分配 IP 地址。
这个 PodCIDRs 的分配与 Flannel 中的 PodCIDRs 分配 的不同:后者使用 v1.Node
上由 Kubernetes 分配的 podCIDR
,这个与 Cilium 的 Kubernetes Host Scope 类似;而 Cilium 的 cluster-scope
使用的 PodCIDRs 则是由 Cilium operator 来分配和管理的,operator 将分配的 PodCIDR 附加在 v2.CiliumNode
上。
Agent 启动后,从 v2.CiliumNode
上获取 podCIDRs
,而后在 CNI 插件调用时为 pod 分配 IP 地址。
eBPF
eBPF 是一种 Linux 内核字节码解释器,最初用于过滤网络数据包,例如 tcpdump 和套接字过滤器。此后,它扩展了额外的数据结构,如哈希表和数组,以及支持数据包处理、转发、封装等的其他操作。
Cilium 利用 eBPF 执行核心数据路径过滤、处理、监控和重定向,并需要任何 Linux 内核版本 4.8.0 或更高版本的 eBPF 功能。
Linux 内核一直是实现监控/可观测性、网络和安全功能的理想地方。不过很多情况下这并非易事,因为这些工作需要修改内核源码或加载内核模块,最终实现形式是在已有的层层抽象之上叠加新的抽象。eBPF 是一项革命性技术,它能在内核中运行沙箱程序(sandbox programs),而无需修改内核源码或者加载内核模块。
将 Linux 内核变成可编程之后,就能基于现有的(而非增加新的)抽象层来打造更加智能、功能更加丰富的基础设施软件,而不会增加系统的复杂度,也不会牺牲执行效率和安全性。
概念
标签 Label
标签是处理大量资源的通用、灵活且高度可扩展的方式,在 Kubernetes 中与 标签选择器 一起被广泛应用。简单来说,一个标签就是一个 key
和 value
的组合:key=value
(使用时也可不提供 value
)。
在 Cilium 中标签有些许不同:每个标签都有个前缀 [source]:
,这个 source
表示该标签是衍生自哪里。创建 Pod 时,衍生来的标签会带有 k8s:
前缀,如 k8s:io.kubernetes.pod.namespace=default
;而使用 docker run [...] -l foo=bar
运行的容器,则带有标签 container:foo=bar
。
除了刚才提到的 k8s:
和 container:
,还有两种 reserved:
和 unspec:
。前者表示是 Cilium 的保留标签,后者则表示没有指定来源。
端点 Endpoint
Cilium 通过为容器分配 IP 地址使其在网络上可用。多个容器可以共享同一个 IP 地址,就像一个 Kubernetes Pod 中可以有多个容器,这些容器之间共享网络命名空间,使用同一个 IP 地址。这些共享同一个地址的容器,Cilium 将其组合起来,成为 Endpoint(端点)。
注意,这个 Endpoint 与 Kubernetes 原生资源 v1.Endpoints
类似但却不同,Cilium 为其提供了 CRD CiliumEndpoint
。本文中提到 Endpoint 除非特别说明,都是指 CiliumEndpoint
。
kubectl get CiliumEndpoint -n default
NAME ENDPOINT ID IDENTITY ID INGRESS ENFORCEMENT EGRESS ENFORCEMENT VISIBILITY POLICY ENDPOINT STATE IPV4 IPV6
curl 2580 1979 <status disabled> <status disabled> <status disabled> ready 10.42.0.171
httpbin 205 20965 <status disabled> <status disabled> <status disabled> ready 10.42.1.205
可以看到我们部署的示例应用对应的 Endpoint 信息,进一步查看 Endpoint curl
的详细信息:
apiVersion: cilium.io/v2
kind: CiliumEndpoint
metadata:
creationTimestamp: "2023-05-20T05:15:24Z"
generation: 1
labels:
app: curl
name: curl
namespace: default
ownerReferences:
- apiVersion: v1
kind: Pod
name: curl
uid: d34675ea-8f4d-4bad-a1c4-02d183380846
resourceVersion: "1197"
uid: c4d7cdb5-c3a8-4ff8-bf03-d6623a0ec00a
status:
encryption: {}
external-identifiers:
container-id: 60dbc4edc0e5bf70b5290feea448559693b7fb51f7b1c3cf996b310c6cd8a576
k8s-namespace: default
k8s-pod-name: curl
pod-name: default/curl
id: 2580
identity:
id: 1979
labels:
- k8s:app=curl
- k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=default
- k8s:io.cilium.k8s.policy.cluster=default
- k8s:io.cilium.k8s.policy.serviceaccount=default
- k8s:io.kubernetes.pod.namespace=default
networking:
addressing:
- ipv4: 10.42.0.171
node: 192.168.1.21
policy:
egress:
enforcing: false
state: <status disabled>
ingress:
enforcing: false
state: <status disabled>
state: ready
visibility-policy-status: <status disabled>
Endpoint 大部分信息都集中在 status
部分。为了辨别 Endpoint,每个 Endpoint 都有一个在 Node 节点范围上唯一的 ID。 比如使用 Endpoint curl 的 ID 2580,在所在节点的 agent 中执行 cilium endpoint get 2580
同样可以得到与 status
部分相同的信息。
除此以外每个 Endpoint 还有一个 Identity(身份),身份是用于执行在 Endpoint 之间执行基本连接的信息,在集群范围内唯一。身份中包含了标签信息,这些标签信息是创建 Endpoint 时从 Pod 或者容器来的。
比如这里我们创建 Pod curl
,容器运行时将 Pod 相关的信息作为执行 CNI ADD
操作的参数 CNI_ARGS
传递给 CNI 插件(这里是 Cilium CNI,对这部分有兴趣的可以看下之前的文章 《认识一下容器网络接口 CNI》)。Cilium CNI 将这些信息以及其他内容作为创建 Endpoint 的依据,传递给 cilium-agent 来创建 Endpoint。这些信息最终作为 identity.labels
存在。
节点 Node
Cilium 中节点是集群的成员,每个节点上都必须运行 cilium-agent。与 Endpoint 一样,Cilium 也提供了 CRD CiliumNode
,后面文中提到的节点,如非特指均是 Cilium 的 CiliumNode
。
kubectl get CiliumNode
NAME AGE
ubuntu-test1 14m
ubuntu-test2 13m
工作机制
下面只是简单概括了 Cilium 在几个场景下的工作原理,忽略了大量的细节。
新节点加入
Cilium Agent 以 DaemonSet 的方式部署,运行在各个 Node 节点上。当有新的节点加入时,Operator 会监测到新的节点加入,为节点分配 Pod CIDR 并创建 CiliumNode
。
新的 Agent 实例调度到该节点上运行,接着为节点上的 CLI、CNI 等操作提供 API 的支持;加载基础 BPF 程序;初始化 BPF Map 等操作。
Pod 创建
当容器运行时通过 CNI 来创建 Pod 的网络命名空间,当执行到 CNI 插件时,CNI 插件会调用 Agent API 来分配 IP 地址、创建 CiliumEndpoint
,并在 Pod 的网络命名空间中加载必要的 BPF 程序。
网络策略
使用 Cilium 的 CiliumNetworkPolicy
可以灵活地定义应用的网络通信策略,例如允许或拒绝特定的流量流入和流出应用。以此保证只有经过授权的流量可以访问应用程序,提供细粒度的访问控制。
以 《使用 Cilium 增强 Kubernetes 网络安全》 中使用的策略为例。
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: "rule1"
spec:
description: "L7 policy to restrict access to specific HTTP call"
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 Agent 中运行着大量的 watcher,其中一个就是 CiliumNetworkPolicy
watcher。当策略创建或者更新时,Agent 会对策略进行转换并将规则存储到 BPF Map 中。在网络通信时,BPF 程序会对网络流量进行检查并决定应当允许或者拒绝访问。