kubernetes 集群中 cilium 的实践及其网络通信解析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了kubernetes 集群中 cilium 的实践及其网络通信解析相关的知识,希望对你有一定的参考价值。

参考技术A

Cilium 是一个基于 eBPF 和 XDP 的高性能容器网络方案的开源项目,目标是为微服务环境提供网络、负载均衡、安全功能,主要定位是容器平台。

Why Cilium ?

现在应用程序服务的发展已从单体结构转变为微服务架构,微服务间的的通信通常使用轻量级的 http 协议。微服务应用往往是经常性更新变化的,在持续交付体系中为了应对负载的变化通常会横向扩缩容,应用容器实例也会随着应用的更新而被创建或销毁。

这种高频率的更新对微服务间的网络可靠连接带来了挑战:

Cilium 通过利用 BPF 具有能够透明的注入网络安全策略并实施的功能,区别于传统的 IP 地址标识的方式,Cilium 是基于 service / pod /container 标识来实现的,并且可以在应用层实现 L7 Policy 网络过滤。总之,Cilium 通过解藕 IP 地址,不仅可以在高频变化的微服务环境中应用简单的网络安全策略,还能在支持 L3/L4 基础上通过对 http 层进行操作来提供更强大的网络安全隔离。BPF 的使用使 Cilium 甚至可以在大规模环境中以高度可扩展的方式解决这些挑战问题。

Cilium 的主要功能特性

此处使用外部的 etcd 的部署方式,外部 etcd 安装 cilium 在较大的运行环境中能够提供更好的性能。

Requirements

安装 helm 3

挂载 BPF 文件系统

在 kubernetes 集群所有 node 上挂载 bpf 文件系统

kubernetes 配置

cilium 安装

当使用外部 etcd 作为 cilium 的 k-v 存储,etcd 的 IP 地址需要在 cilium 的 configmap 中配置。

使用 helm 安装 cilium

安装 cilium 连接测试用例

此用例将会部署一系列的 deployment,它们会使用多种路径来相互访问,连接路径包括带或者不带服务负载均衡和各种网络策略的组合。

部署的 podName 表示连接方式,readiness/liveness 探针则可指示连接是否成功。

安装 hubble https://github.com/cilium/hubble

hubble 是一个用于云原生工作负载的完全分布式网络和安全可视化平台。它建立在 Cilium 和 eBPF 的基础上,以完全透明的方式深入了解服务以及网络基础结构的通信和行为。

hubble 对前面安装的测试用例监控信息

cilium 在 kubernetes 集群中安装好后,此处我们来探究一下在不同 node 上 pod 间的 vxlan 通信方式。

cilium 安装完后,cilium agent 会在 node 上创建 cilium_net 与 cilium_host 一对 veth pair 及用于跨宿主机通信的 cilium_vxlan ,然后在 cilium_host 上配置其管理的 CIDR IP 作为网关。

如上图示中,通过抓包分析 Container A 与 Container B 之前的通信路径。

Container A ping Container B (以下称 Container A -> CA , Container B -> CB)

进入 Node01 上 CA 内 (10.244.1.154),ping CB ip 地址 10.224.6.11

进入 Node01 上 cilium agent 容器内,查看 CA 容器作为 cilium endpoint 的信息

CA 容器 ping 出的 icmp 包将会经过网卡 lxc8b528e748ff4 (lxcxxA) 路由到 cilium_host 网关,路由方式与传统的通过 Linux bridge 这样的二层设备转发不一样,cilium 在每个容器相关联的虚拟网卡上都附加了 bpf 程序,通过连接到 TC ( traffic control ) 入口钩子的 bpf 程序将所有网络流量路由到主机端虚拟设备上,由此 cilium 便可以监视和执行有关进出节点的所有流量的策略,例如 pod 内的 networkPolicy 、L7 policy、加密等规则。

CA 容器内的流量要跨宿主机节点路由到 CB 容器,则需要 cilium_vxlan VTEP 设备对流量包进行封装转发到 Node02 上。bpf 程序会查询 tunnel 规则并将流量发送给 cilium_vxlan ,在 cilium agent 容器内可查看到 bpf 的 tunnel 规则

在 Node01 上对 cilium_vxlan 抓包,可看到 CA 容器对 icmp 包经过了 cilium_vxlan

再对 Node01 上的 eth0 抓包,可看到 cilium_vxlan 已将 CA 的流量进行 vxlan 封包,src ip 改为本机 node ip 192.168.66.226, dst ip 改为 192.168.66.221

到 Node02 上对 eth0 抓包,可看到 CA 容器的流量包已到 Node02 上

对 cilium_vxlan 抓包即可看到 CA 容器过来对流量包已被解封

至此,CA 容器对流量已到达 cilium 创建的虚拟网卡。

我们知道 Linux 内核本质上是事件驱动的(the Linux Kernel is fundamentally event-driven), cilium 创建的虚拟网卡接收到流量包将会触发连接到 TC ( traffic control ) ingress 钩子的 bpf 程序,对流量包进行相关策略对处理。

查看 cilium 官方给出的 ingress/egress datapath,可大致验证上述 cilium 的网络通信路径。

首先是 egress datapath ,图中橙黄色标签为 cilium component,有 cilium 在宿主机上创建的 bpf 程序(对应着红色标签的 kernel bpf 钩子),若使用 L7 Policy 则还有 cilium 创建的 iptables 规则。流量从某个容器 endpoint 通过容器上的 veth pair 网卡 lxcxxx 出发,即会触发 bpf_sockops.c / bpf_redir.c bpf 程序,若使用了 L7 Policy 则进入用户空间进行 L7 层的数据处理,若没有使能 L7 Policy 则将触发 TC egress 钩子,bpf_lxc 对数据进行处理(若使能 L3 加密,则触发其他 bpf 钩子),数据最终被路由到 cilium_hos t 网关处,再根据 overlay 模式(vxlan 等)将数据发送出去。

在 cilium ingress datapath 中,数据流量进入主机网络设备上,cilium 可根据相关配置,对数据流量进行预处理(prefilter/L3 加解密/ 负载均衡/ L7 Policy 处理)或直接路由到 cilium_host 触发相应到 bpf 程序,再到最终的 endpoint 处。

使用 Cilium 增强 Kubernetes 网络安全

TL;DR

在本篇,我们分别使用了 Kubernetes 原生的网络策略和 Cilium 的网络策略实现了 Pod 网络层面的隔离。不同的是,前者只提供了基于 L3/4 的网络策略;后者支持 L3/4、L7 的网络策略。

通过网络策略来提升网络安全,可以极大降低了实现和维护的成本,同时对系统几乎没有影响。

尤其是基于 eBPF 技术的 Cilium,解决了内核扩展性不足的问题,从内核层面为工作负载提供安全可靠、可观测的网络连接。

背景

为什么说 Kubernetes 网络存在安全隐患?集群中的 Pod 默认是未隔离的,也就是 Pod 之间的网络是互通的,可以互相通信的。

这里就会有问题,比如由于数据敏感服务 B 只允许特定的服务 A 才能访问,而服务 C 无法访问 B。要禁止服务 C 对服务 B 的访问,可以有几种方案:

  • 在 SDK 中提供通用的解决方案,实现白名单的功能。首先请求要带有来源的标识,然后服务端可以接收规则设置放行特定标识的请求,拒绝其他的请求。
  • 云原生的解决方案,使用服务网格的 RBAC、mTLS 功能。RBAC 实现原理与应用层的 SDK 方案类似,但是属于基础设施层的抽象通用方案;mTLS 则会更加复杂一些,在连接握手阶段进行身份验证,涉及证书的签发、验证等操作。
  • 以上两种方案各有利弊:

  • SDK 的方案实现简单,但是规模较大的系统会面临升级推广困难、多语言支持成本高等问题。
  • 服务网格的方案是基础设施层的通用方案,天生支持多语言。但是对于未落地网格的用户来说,架构变化大,成本高。如果单纯为了解决安全问题,使用网格方案性价比又很低,且不说现有网格实现等落地难度大及后期的使用维护成本高。
  • 继续向基础设施下层找方案,从网络层入手。Kubernetes 提供了的网络策略 *NetworkPolicy*[1],则可以实现“网络层面的隔离”。

    示例应用

    在进一步演示 NetworkPolicy 的方案之前,先介绍用于演示的示例应用。我们使用 Cilium 在互动教程 Cilium getting started[2] 中使用的“星球大战”场景。

    这里有三个应用,星战迷估计不会陌生:

  • 死星 deathstar:在 80 端口提供 web 服务,有 2 个 副本,通过 Kubernetes Service 的负载均衡为帝国战机对外提供”登陆“服务。
  • 钛战机 tiefighter:执行登陆请求。
  • X翼战机 xwing:执行登陆请求。
  • 如图所示,我们使用了 Label 对三个应用进行了标识:org 和 class。在执行网络策略时,我们会使用这两个标签识别负载。

    # app.yaml
    ---
    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: 2
      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
    Kubernetes 网络策略

    可以通过官方文档[3]获取更多详细信息,这里我们直接放出配置:

    # native/networkpolicy.yaml
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: policy
      namespace: default
    spec:
      podSelector:
        matchLabels:
          org: empire
          class: deathstar
      policyTypes:
      - Ingress
      ingress:
      - from:
        - podSelector:
            matchLabels:
              org: empire
        ports:
        - protocol: TCP
          port: 80
  • podSelector :表示要应用网络策略的工作负载均衡,通过 label 选择到了 deathstar 的 2 个 Pod。
  • policyTypes :表示流量的类型,可以是 Ingress 或 Egress 或两者兼具。这里使用 Ingress,表示对选择的 deathstar Pod 的入站流量执行规则。
  • ingress.from:表示流量的来源工作负载,也是使用 podSelector 和 Label 进行选择,这里选中了 org=empire 也就是所有“帝国的战机”。
  • ingress.ports:表示流量的进入端口,这里列出了 deathstar 的服务端口。
  • 接下来,我们测试下。

    测试

    先准备环境,我们使用 K3s[4] 作为 Kubernetes 环境。但由于 K3s 默认的 CNI 插件 Flannel 不支持网络策略,我们需要换个插件,这里选择 Calico[5],即 K3s + Calico 的方案。

    先创建一个单节点的集群:

    curl -sfL https://get.k3s.io | K3S_KUBECONFIG_MODE="644" INSTALL_K3S_EXEC="--flannel-backend=none --cluster-cidr=10.42.0.0/16 --disable-network-policy --disable=traefik" sh -

    此时,所有的 Pod 都处于 Pending 状态,因为还需要安装 Calico:

    kubectl apply -f https://projectcalico.docs.tigera.io/manifests/calico.yaml

    待 Calico 成功运行后,所有的 Pod 也会成功运行。

    接下来就是部署应用:

    kubectl apply -f app.yaml

    执行策略前,执行下面的命令看看“战机能否登陆死星”:

    kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
    Ship landed

    kubectl exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
    Ship landed

    从结果来看,两种 ”战机“(Pod 负载)都可以访问 deathstar 服务。

    此时执行网络策略:

    kubectl apply -f native/networkpolicy.yaml

    再次尝试”登陆“,xwing 的登陆请求会停在那(需要使用 ctrl+c 退出,或者请求时加上 --connect-timeout 2)。

    思考

    使用 Kubernetes 网络策略实现了我们想要的,从网络层面为服务增加了白名单的功能,这种方案没有改造成本,对系统也几乎无影响。

    Cilium 还没出场就结束了?我们继续看:

    有时我们的服务会对外暴露一些管理端点,由系统调用执行一些管理上的操作,比如热更新、重启等。这些端点是不允许普通服务来调用,否则会造成严重的后果。

    比如示例中,tiefighter 访问了 deathstar 的管理端点 /exhaust-port

    kubectl exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port
    Panic: deathstar exploded

    goroutine 1 [running]:
    main.HandleGarbage(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)
            /code/src/github.com/empire/deathstar/
            temp/main.go:9 +0x64
    main.main()
            /code/src/github.com/empire/deathstar/
            temp/main.go:5 +0x85

    出现了 Panic 错误,检查 Pod 你会发现 dealthstar 挂了。

    Kubernetes 的网络策略仅能工作在 L3/4 层,对 L7 层就无能为力了。

    还是要请出 Cilium。

    Cilium 网络策略

    由于 Cilium 涉及了 Linux 内核、网络等众多知识点,要讲清实现原理篇幅极大。故这里仅摘取了官网的介绍,后期希望有时间再写一篇关于实现的。

    Cilium 简介

    Cilium[6] 是一个开源软件,用于提供、保护和观察容器工作负载(云原生)之间的网络连接,由革命性的内核技术 eBPF[7] 推动。

    eBPF 是什么?

    Linux 内核一直是实现监控/可观测性、网络和安全功能的理想地方。不过很多情况下这并非易事,因为这些工作需要修改内核源码或加载内核模块, 最终实现形式是在已有的层层抽象之上叠加新的抽象。eBPF 是一项革命性技术,它能在内核中运行沙箱程序(sandbox programs), 而无需修改内核源码或者加载内核模块。

    将 Linux 内核变成可编程之后,就能基于现有的(而非增加新的)抽象层来打造更加智能、 功能更加丰富的基础设施软件,而不会增加系统的复杂度,也不会牺牲执行效率和安全性。

    我们来看下 Cilium 的网络策略:

    # cilium/networkpolicy-L4.yaml
    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

    与 Kubernetes 的原生网络策略差异不大,参考前面的介绍也都看懂,我们直接进入测试。

    测试

    由于 Cilium 本身就实现了 CNI,所以之前的集群就不能用了,先卸载集群:

    k3s-uninstall.sh
    # !!!切记要清理之前的 cni 插件
    sudo rm -rf /etc/cni/net.d

    还是使用同样的命令创建单节点的集群:

    curl -sfL https://get.k3s.io | K3S_KUBECONFIG_MODE="644" INSTALL_K3S_EXEC="--flannel-backend=none --cluster-cidr=10.42.0.0/16 --disable-network-policy --disable=traefik" sh -

    #
     cilium 会使用该变量
    export KUBECONFIG=/etc/rancher/k3s/k3s.yaml

    接下来安装 Cilium CLI:

    curl -L --remote-name-all https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz,.sha256sum
    sha256sum --check cilium-linux-amd64.tar.gz.sha256sum
    sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin
    rm cilium-linux-amd64.tar.gz,.sha256sum

    cilium version
    cilium-cli: v0.10.2 compiled with go1.17.6 on linux/amd64
    cilium image (default): v1.11.1
    cilium image (stable): v1.11.1
    cilium image (running): unknown. Unable to obtain cilium version, no cilium pods found in namespace "kube-system"

    安装 Cilium 到集群:

    cilium install

    待 Cilium 成功运行:

    cilium status
        /¯¯\\
     /¯¯\\__/¯¯\\    Cilium:         OK
     \\__/¯¯\\__/    Operator:       OK
     /¯¯\\__/¯¯\\    Hubble:         disabled
     \\__/¯¯\\__/    ClusterMesh:    disabled
        \\__/

    Deployment        cilium-operator    Desired: 1, Ready: 1/1, Available: 1/1
    DaemonSet         cilium             Desired: 1, Ready: 1/1, Available: 1/1
    Containers:       cilium             Running: 1
                      cilium-operator    Running: 1
    Cluster Pods:     3/3 managed by Cilium
    Image versions    cilium-operator    quay.io/cilium/operator-generic:v1.11.1@sha256:977240a4783c7be821e215ead515da3093a10f4a7baea9f803511a2c2b44a235: 1
                      cilium             quay.io/cilium/cilium:v1.11.1@sha256:251ff274acf22fd2067b29a31e9fda94253d2961c061577203621583d7e85bd2: 1

    部署应用:

    kubectl apply -f app.yaml

    待应用启动后测试服务调用:

    kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
    Ship landed
    kubectl exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
    Ship landed

    执行 L4 网络策略:

    kubectl apply -f cilium/networkpolicy-L4.yaml

    再次尝试“登陆”死星,xwing 战机同样无法登陆,说明 L4 层的规则生效。

    我们再尝试 L7 层的规则:

    # cilium/networkpolicy-L7.yaml
    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"

    执行规则:

    kubectl apply -f cilium/networkpolicy-L7.yaml

    这回,使用 tiefighter 调用死星的管理接口:

    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

    这回返回了 Access denied,说明 L7 层的规则生效了。

    参考资料
    [1] 

    网络策略 NetworkPolicyhttps://kubernetes.io/zh/docs/concepts/services-networking/network-policies/

    [2] 

    Cilium getting started: https://play.instruqt.com/isovalent/tracks/cilium-getting-started

    [3] 

    官方文档: https://kubernetes.io/zh/docs/concepts/services-networking/network-policies/

    [4] 

    K3s: https://k3s.io

    [5] 

    Calico: https://www.tigera.io/project-calico/

    [6] 

    Cilium: https://cilium.io

    [7] 

    eBPF: https://ebpf.io

    文章转载自云原生指北点击这里阅读原文了解更多

    CNCF概况(幻灯片)

    扫描二维码联系我们!



    CNCF (Cloud Native Computing Foundation)成立于2015年12月,隶属于Linux  Foundation,是非营利性组织。 

    CNCF云原生计算基金会)致力于培育和维护一个厂商中立的开源生态系统,来推广云原生技术。我们通过将最前沿的模式民主化,让这些创新为大众所用。请长按以下二维码进行关注。

    以上是关于kubernetes 集群中 cilium 的实践及其网络通信解析的主要内容,如果未能解决你的问题,请参考以下文章

    关于centos8+kubeadm1.20.5+cilium+hubble的安装过程中cilium的配置问题--特别强调

    使用 Cilium 增强 Kubernetes 网络安全

    使用 Cilium 增强 Kubernetes 网络安全

    使用CalicoFlannelWeave和Cilium的终极指南

    Docker&Kubernetes ❀ Kubernetes集群实践与部署笔记知识点梳理

    Docker&Kubernetes ❀ Kubernetes集群实践与部署笔记知识点梳理