Cilium 如何处理 L7 流量

Cilium Agent 中运行着大量的 watcher,其中一个就是 CiliumNetworkPolicy watcher。当策略创建或者更新时,Agent 会对策略进行转换并将规则存储到 BPF Map 中。在网络通信时,BPF 程序会对网络流量进行检查并决定应当允许或者拒绝访问。

Cilium Agent 中运行着大量的 watcher,其中一个就是 CiliumNetworkPolicy watcher。当策略创建或者更新时,Agent 会对策略进行转换并将规则存储到 BPF Map 中。在网络通信时,BPF 程序会对网络流量进行检查并决定应当允许或者拒绝访问。

还记得在使用 Cilium 增强 Kubernetes 网络安全[1]示例中,我们通过设置网络策略限制钛战机tiefighter访问死星deathstar的/v1/exhaust-port端点,但放行着陆请求/v1/request-landing。在提起 Cilium 时,都说其是使用 eBPF 技术推动的用于提供、保护和观察容器工作负载之间的网络连接的开源软件。eBPF 可以处理 L3/4 的数据包,但是对复杂的 L7 的协议处理的成本比较高,并且无法应对 L7 协议策略的灵活性。Cilium 引入Envoy Proxy[2](Cilium 定制的发行版)作为 L7 代理,来处理该场景。

那 Cilium 是如何处理 L7 流量的呢?今天就让我们一探究竟。

注,这篇的内容是基于目前最新的 Cilium 1.13.3 和 proxy 1.23.9,不同版本间会有差异。

在开始之前先搭建先前的“星球大战”环境,或者你也可以直接跳到Debug 阶段[3]。

环境搭建

集群

export INSTALL_K3S_VERSION=v1.27.1+k3s1
curl -sfL https://get.k3s.io | sh -s - --disable traefik --disable local-storage --disable metrics-server --disable servicelb --flannel-backend=none --write-kubeconfig-mode 644 --write-kubeconfig ~/.kube/config

安装 Cilium

CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/master/stable.txt)
CLI_ARCH=amd64
if [ "$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-${CLI_ARCH}.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-${CLI_ARCH}.tar.gz /usr/local/bin
rm cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
cilium install

安装示例应用

kubectl apply -n default -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: deathstar
  labels:
    app.kubernetes.io/name: deathstar
spec:
  type: ClusterIP
  ports:
  - port: 80
  selector:
    org: empire
    class: deathstar
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deathstar
  labels:
    app.kubernetes.io/name: deathstar
spec:
  replicas: 1
  selector:
    matchLabels:
      org: empire
      class: deathstar
  template:
    metadata:
      labels:
        org: empire
        class: deathstar
        app.kubernetes.io/name: deathstar
    spec:
      containers:
      - name: deathstar
        image: docker.io/cilium/starwars
---
apiVersion: v1
kind: Pod
metadata:
  name: tiefighter
  labels:
    org: empire
    class: tiefighter
    app.kubernetes.io/name: tiefighter
spec:
  containers:
  - name: spaceship
    image: docker.io/tgraf/netperf
---
apiVersion: v1
kind: Pod
metadata:
  name: xwing
  labels:
    app.kubernetes.io/name: xwing
    org: alliance
    class: xwing
spec:
  containers:
  - name: spaceship
    image: docker.io/tgraf/netperf
EOF

设置策略

kubectl apply -n default -f - <<EOF
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"
EOF

测试

kubectl exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port
#Access denied
kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
#Ship landed

查看 pod 信息。

kubectl get po -o wide -n default
NAME                         READY   STATUS    RESTARTS   AGE     IP           NODE          NOMINATED NODE   READINESS GATES
deathstar-7848d6c4d5-58jc8   1/1     Running   0          6h57m   10.0.0.111   ubuntu-dev3   <none>           <none>
xwing                        1/1     Running   0          6h57m   10.0.0.209   ubuntu-dev3   <none>           <none>
tiefighter                   1/1     Running   0          6h57m   10.0.0.123   ubuntu-dev3   <none>           <none>

后面 debug 的操作我们会直接在 cilium 的 agent pod 进行。

agent=$(kubectl get po -l app.kubernetes.io/name=cilium-agent -n kube-system -o jsonpath='{.items[0].metadata.name}')

Debug

先贴上总结的图。

图片

怎么下手呢?

在深入探索 Cilium 的工作机制[4]时,我们对 Cilium 的网络策略处理机制一笔带过:

Cilium Agent 中运行着大量的 watcher,其中一个就是CiliumNetworkPolicywatcher。当策略创建或者更新时,Agent 会对策略进行转换并将规则存储到 BPF Map 中。在网络通信时,BPF 程序会对网络流量进行检查并决定应当允许或者拒绝访问。

实际上这里的处理比较复杂,我们从 watcher 的初始化入手。

  • #enableK8sWatchers[5]开启一些列的 watcher
  • #ciliumNetworkPoliciesInit[6]开启CiliumNetworkPolicywatcher
  • #PolicyAdd[8]将规则写入Daemon[9]的策略仓库中,实际发PolicyAddEvent到repository-change-queue队列中。
  • #policyAdd[10]对规则进行预处理,并收集与规则相关的 endpoint(需要重新生成 endpoint 的数据,如加载 BPF 程序、更新 map 等),推送PolicyReactionEvent事件
  • EndpointRegenerationEvent#Handle[12]事件的处理过程
  • Endpoint.regenerateBPF[14]重新加载 datapath BPF 程序,刷新 Map。
  • Endpoint.regenerate[13]
  • PolicyReactionEvent.Handle[11]事件处理的过程,依次处理所有策略相关的 endpoint,最后有发出EndpointRegenerationEvent事件
  • #addCiliumNetworkPolicyV2[7]添加CiliumNetworkPolicy的处理

至此我们 apply 的网络策略被写入到 map 中。

接下来看下 ebpf 程序有任何使用该策略。

eBPF

还记得在Kubernetes 网络学习之 Cilium 与 eBPF[15]中我们分析容器发出的数据包,被LXC BPF Ingress程序处理。这里不再赘述,处理流程可以看那篇文章。

我们先查看死星的 endpoint id 和 identity 分别为863和2033。

kubectl get ciliumendpoint -n default
NAME                         ENDPOINT ID   IDENTITY ID   INGRESS ENFORCEMENT   EGRESS ENFORCEMENT   VISIBILITY POLICY   ENDPOINT STATE   IPV4         IPV6
tiefighter                   2216          29439         <status disabled>     <status disabled>    <status disabled>   ready            10.0.0.123
deathstar-7848d6c4d5-58jc8   863           2033          <status disabled>     <status disabled>    <status disabled>   ready            10.0.0.111
xwing                        775           5513          <status disabled>     <status disabled>    <status disabled>   ready            10.0.0.209

使用 endpoint id 通过通过命令查看为死星配置的网络策略,可以看到其中的两条 ingress 的策略,其代理端口19313,这个端口就是 Cilium 中 L7 代理的监听端口。

kubectl exec $agent -n kube-system -c cilium-agent -- cilium bpf policy get 863
POLICY   DIRECTION   LABELS (source:key[=value])                                              PORT/PROTO   PROXY PORT   BYTES   PACKETS
Allow    Ingress     reserved:host                                                            ANY          NONE         0       0
                     reserved:kube-apiserver
Allow    Ingress     k8s:app.kubernetes.io/name=deathstar                                     80/TCP       19313        0       0
                     k8s:class=deathstar
                     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
                     k8s:org=empire
Allow    Ingress     k8s:app.kubernetes.io/name=tiefighter                                    80/TCP       19313        0       0
                     k8s:class=tiefighter
                     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
                     k8s:org=empire
Allow    Egress      reserved:unknown                                                         ANY          NONE         0       0

BPF 程序处理流量在检查策略时bpf_lxc.c#L1842[16],检查配置的策略带有代理端口执行POLICY_ACT_PROXY_REDIRECT将流量重定向给代理(端口19313,地址为主机地址)。

Cilium Proxy

Cilium agent 提供了 xds server 实现,通过 Unix Domain Socket/var/run/cilium/xds.sock与 proxy 进行通信,下发配置。

我们参考cilium-bugtool的dump 源码[17],dump 代理的配置。

kubectl exec $agent -n kube-system -c cilium-agent -- curl -s --unix-socket /var/run/cilium/envoy-admin.sock http://admin/config_dump?include_eds

从配置 config.json 中可以看到 Cilium 在 envoy proxy 中实现了如下三个不同类型的过滤器(Filter):

  • listener filter
  • filter
  • http filter

监听器过滤器

监听器过滤器(Listener Filter)`cilium.BpfMetadata`[18]会从几个数据源中准备元数据:策略、监听器设置、请求方的标识等。数据源包括 xds 配置、BPF mapcilium_ipcache、cilium_ct4_global(ct:connection tracking。当然还包括 ct6 相关的 map)。

从数据源中获取的数据保存在 socket option 中(proxy 源码bpf_metadata.cc#L364[19]),作为上下文元数据的在其他的过滤器中使用。

元数据数据源

xds filter 配置,这里提供了 bpf map 的根目录/sys/fs/bpf,以及is_ingress: true表示当前 filter 是在入口监听器上(ingress listener):

{
 "name": "cilium.bpf_metadata",
 "typed_config": {
  "@type": "type.googleapis.com/cilium.BpfMetadata",
  "bpf_root": "/sys/fs/bpf",
  "is_ingress": true
 }

xds network policy 配置(截取了 proxy 的部分配置),从配置中可以找到 endpoint 的 IP 和 id,以及前面我们设置的规则[20]:

{
 "@type": "type.googleapis.com/cilium.NetworkPoliciesConfigDump",
 "networkpolicies": [
  {
   "endpoint_ips": [
    "10.0.0.111"
   ],
   "endpoint_id": "863",
   "ingress_per_port_policies": [
    {
     "port": 80,
     "rules": [
      {
       "http_rules": {
        "http_rules": [
         {
          "headers": [
           {
            "name": ":method",
            "safe_regex_match": {
             "google_re2": {},
             "regex": "POST"
            }
           },
           {
            "name": ":path",
            "safe_regex_match": {
             "google_re2": {},
             "regex": "/v1/request-landing"
            }
           }
          ]
         }
        ]
       }
      }
     ]
    }
   ],
   "egress_per_port_policies": [
    {}
   ],
   "conntrack_map_name": "global"
  },
  ...
}

Mapcilium_ipcache,可以通过连接信息中的 IP 地址获取身份标识,如死星的`identity`[21]为2033(见 proxy 源码bpf_metadata.cc#L165[22]):

kubectl exec $agent -n kube-system -c cilium-agent -- cilium bpf ipcache list
IP PREFIX/ADDRESS   IDENTITY
10.0.0.67/32        identity=1 encryptkey=0 tunnelendpoint=0.0.0.0 nodeid=0
10.0.0.111/32       identity=2033 encryptkey=0 tunnelendpoint=0.0.0.0 nodeid=0
10.0.0.123/32       identity=29439 encryptkey=0 tunnelendpoint=0.0.0.0 nodeid=0
10.0.0.243/32       identity=4 encryptkey=0 tunnelendpoint=0.0.0.0 nodeid=0
10.0.0.160/32       identity=19608 encryptkey=0 tunnelendpoint=0.0.0.0 nodeid=0
10.0.0.209/32       identity=5513 encryptkey=0 tunnelendpoint=0.0.0.0 nodeid=0
192.168.1.13/32     identity=1 encryptkey=0 tunnelendpoint=0.0.0.0 nodeid=0
0.0.0.0/0           identity=2 encryptkey=0 tunnelendpoint=0.0.0.0 nodeid=0

Mapcilium_ct4_global,从连接跟踪(connection tracking)中获取请求方的identity(SourceSecurityID 29439,钛战机的标识):

cilium bpf ct list global
TCP OUT 10.0.0.123:48954 -> 10.0.0.111:80 expires=58774 RxPackets=4 RxBytes=435 RxFlagsSeen=0x1b LastRxReport=58764 TxPackets=6 TxBytes=522 TxFlagsSeen=0x1b LastTxReport=58764 Flags=0x0013 [ RxClosing TxClosing SeenNonSyn ] RevNAT=4 SourceSecurityID=29439 IfIndex=0
TCP IN 10.0.0.67:33988 -> 10.0.0.111:80 expires=58776 RxPackets=6 RxBytes=659 RxFlagsSeen=0x1b LastRxReport=58766 TxPackets=4 TxBytes=386 TxFlagsSeen=0x1b LastTxReport=58766 Flags=0x0013 [ RxClosing TxClosing SeenNonSyn ] RevNAT=0 SourceSecurityID=29439 IfIndex=0
TCP IN 10.0.0.123:48954 -> 10.0.0.111:80 expires=80364 RxPackets=6 RxBytes=522 RxFlagsSeen=0x1b LastRxReport=58764 TxPackets=0 TxBytes=0 TxFlagsSeen=0x00 LastTxReport=0 Flags=0x0051 [ RxClosing SeenNonSyn ProxyRedirect ] RevNAT=0 SourceSecurityID=29439 IfIndex=0

过滤器

过滤器(Filter)`cilium.NetworkFilter`[23]工作在 L4,用于处理已建立的链接,应用端口级的策略,即 L4 策略。

从上下文元数据中保存的 endpoint 相关的策略中查找与目标端口相关的策略,检查请求方证书中的 sni 和请求方的身份标识 identity 是否在白名单中,见 proxy 源码network_filter.cc#L169[24]。

假如策略上设置了 L7 的协议,会使用 Golang 编写的解析器对 L7 的数据进行解析。

在本示例中并未使用 L4 的策略。

HTTP 过滤器

HTTP 过滤器(HTTP Filter)`cilium.L7Policy`[25]是本文的重点,但相对其他两个过滤器来说逻辑就简单多了。

"http_filters": [
 {
  "name": "cilium.l7policy",
  "typed_config": {
   "@type": "type.googleapis.com/cilium.L7Policy",
   "access_log_path": "/var/run/cilium/access_log.sock"
  }
 }

在过滤器对 HTTP 请求头进行解码时(见 proxy 源码l7policy.cc#L97[26]),依然是从上下文元数据中获取策略等内容。拿到策略后,与请求方(对于这里 ingress 的场景检查请求方,如果是 egress 的场景,检查上游的标识)的标识、请求头的信息进行比对,决定放行还是拒绝请求。

总结

整篇看下来,Cilium 在处理 L7 流量上的实现还是比较复杂的,牵扯多个组件协同。eBPF 在 L3/L4 流量处理上有着优异的性能优势,但是对 L7 流量处理仍然无法脱离 sidecar 代理(不论 sidecar 是 per pod 还是 per node)。而 L7 流量处理也恰恰有着非常多的使用场景,不仅仅是 HTTP 协议。

参考资料

[1]使用 Cilium 增强 Kubernetes 网络安全:https://atbug.com/enhance-kubernetes-network-security-with-cilium/

[2]Envoy Proxy:https://github.com/cilium/proxy

[3]Debug 阶段:#debug

[4]深入探索 Cilium 的工作机制:https://atbug.com/deep-dive-into-cilium/#网络策略

[5]#enableK8sWatchers:https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/pkg/k8s/watchers/watcher.go#L525

[6]#ciliumNetworkPoliciesInit:https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/pkg/k8s/watchers/cilium_network_policy.go#L85

[7]#addCiliumNetworkPolicyV2:https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/pkg/k8s/watchers/cilium_network_policy.go#L159

[8]#PolicyAdd:https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/daemon/cmd/policy.go#L224

[9]Daemon:https://atbug.com/deep-dive-into-cilium/#agent

[10]#policyAdd:https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/daemon/cmd/policy.go#L249

[11]PolicyReactionEvent.Handle:https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/daemon/cmd/policy.go#L454

[12]EndpointRegenerationEvent#Handle:https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/pkg/endpoint/events.go#L27

[13]Endpoint.regenerate:https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/pkg/endpoint/policy.go#L286

[14]Endpoint.regenerateBPF:https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/pkg/endpoint/bpf.go#L584

[15]Kubernetes 网络学习之 Cilium 与 eBPF:https://atbug.com/learn-cilium-and-ebpf/#第-2-步pod1-lxc-bpf-ingress

[16]bpf_lxc.c#L1842:https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/bpf/bpf_lxc.c#L1842

[17]dump 源码:https://github.com/cilium/cilium/blob/f9bdd00c4910bfe3bac3b208fdfbb9452487e776/bugtool/cmd/root.go#L505

[18]cilium.BpfMetadata:https://github.com/cilium/proxy/blob/v1.23/cilium/bpf_metadata.cc

[19]bpf_metadata.cc#L364:https://github.com/cilium/proxy/blob/v1.23/cilium/bpf_metadata.cc#L364

[20]规则:#设置策略

[21]identity:https://atbug.com/deep-dive-into-cilium/#端点-endpoint

[22]bpf_metadata.cc#L165:https://github.com/cilium/proxy/blob/v1.23/cilium/bpf_metadata.cc#L165

[23]cilium.NetworkFilter:https://github.com/cilium/proxy/blob/v1.23/cilium/network_filter.cc

[24]network_filter.cc#L169:https://github.com/cilium/proxy/blob/v1.23/cilium/network_filter.cc#L169

[25]cilium.L7Policy:https://github.com/cilium/proxy/blob/v1.23/cilium/l7policy.cc

[26]l7policy.cc#L97:https://github.com/cilium/proxy/blob/v1.23/cilium/l7policy.cc#L97

©本文为清一色官方代发,观点仅代表作者本人,与清一色无关。清一色对文中陈述、观点判断保持中立,不对所包含内容的准确性、可靠性或完整性提供任何明示或暗示的保证。本文不作为投资理财建议,请读者仅作参考,并请自行承担全部责任。文中部分文字/图片/视频/音频等来源于网络,如侵犯到著作权人的权利,请与我们联系(微信/QQ:1074760229)。转载请注明出处:清一色财经

(0)
打赏 微信扫码打赏 微信扫码打赏 支付宝扫码打赏 支付宝扫码打赏
清一色的头像清一色管理团队
上一篇 2023年6月14日 00:02
下一篇 2023年6月14日 00:03

相关推荐

发表评论

登录后才能评论

联系我们

在线咨询:1643011589-QQbutton

手机:13798586780

QQ/微信:1074760229

QQ群:551893940

工作时间:工作日9:00-18:00,节假日休息

关注微信