深入探索 Cilium 的工作机制

深入探索 Cilium 的工作机制

这篇之前写 Kubernetes 网络学习之 Cilium 与 eBPF 记录的内容,隔了几个月终于想起把笔记完成,作为探索 Cilium 工作原理的入门,也还是 Cilium 冰山一角,像是高级的网络策略、网络加密、BGP 网络、服务网格等方面并没有深入。如果阅读过程中有发现任何问题,也烦请纠正。

本文基于 Cilium v1.12 及 Kubernetes v1.25。

实验环境

我们使用 k8e 创建集群,因为 k8e 使用 Cilium 作为默认的 CNI 实现。在我的 homelab 上做个双节点(ubuntu-test1: 192.168.1.21ubuntu-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 PluginCLI)提供 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 中与 标签选择器 一起被广泛应用。简单来说,一个标签就是一个 keyvalue 的组合: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 程序会对网络流量进行检查并决定应当允许或者拒绝访问。

(转载本站文章请注明作者和出处乱世浮生,请勿用于任何商业用途)

comments powered by Disqus