Kubernetes Semaphore:模块化、无侵入的跨集群通信框架

原文:Kubernetes Semaphore: A modular and nonintrusive framework for cross cluster communication

作者:UW Labs

问题

我们有一个环境,其中包含分属三个不同供应商(AWS、GCP 和私有云)的三个集群,我们希望不同集群中运行的应用能够互相通信,以及:

  • 跨集群的 Pod 网络和加密能力;
  • 访问远端 Kubernetes Service 的能力;
  • 用策略来放行远端集群特定应用,使之能够访问本地端点。

我们有一个跨集群的三层网络,这样三个集群的节点就能互相访问了。每个集群都是在各个供应商子网中申请的节点:

  • AWS:10.66.21.0/24
  • GCP:10.22.20.0/24
  • 私有云:10.88.0.0/24

三个集群的 Pod 网络分配如下:

  • AWS:10.2.0.0/16
  • GCP:10.4.0.0/16
  • 私有云:10.6.0.0/16

依赖项

  • Calico CNI:在所有集群中使用 Calico CNI,方案中对 Calico 具有一定依赖;
  • CoreDNS:Semaphore-Service-Mirror 对此有依赖;
  • 跨集群的三层网络。

现有方案

我们对 Istio、Linkerd、Consul 进行了评估,还直接编写了自己的 Envoy Proxy 配置工具。这些方案都能提供上述大部分甚至全部的功能。但是我们认为他们的性价比并不完全适合我们的环境。不同集群之中运行的不同应用构成的网格结构并不总是让人有兴趣的,其中提供的大量功能也无法让所有人受益。我们希望避免使用 Sidecar Proxy,也回避随之而来的额外开销,并确保我们的程序保持尽可能的独立,不受跨集群通信解决方案的影响。

设计

上面既然提到要避开 Sidecar 代理,我们希望用一种对运维人员和用户都较为简单的方式来解决问题。

理想情况下,每个目标都应该以相互隔离的方法来达成——假设用户只是需要对 Pod 通信进行加密,那么只要进行单独的部署即可。对于新用户来说,只需要少量的配置就可以对方案进行试用,并且可以轻松回退。

方案

Kube-Semaphore 是一个轻量级框架,为不同 Kubernetes 集群之间的应用,提供了简单安全的通信能力,并且无需对应用和清单进行修改。

这不是一个服务网格方案,而是要为远端集群提供服务端点和防火墙规则。

方案由三个独立的工具组成

  • Semaphore-Wireguard:负责 Kubernetes 集群之间的流量加密;
  • Semaphore-Service-Mirror:负责在无需外部负载均衡器参与的情况下,将一个集群中的服务暴露到另一个集群之中;
  • Semaphore-Policy:负载在跨集群的 Pod 间通信里创建防火墙规则。

为了小型、轻量和尽可能的安全,这个组件是用 Go 编写的,并使用了 Kubernetes 和 Calico 的客户端。远端集群上的部署内容很小,本地控制器仅需要一系列的 Service Account,满足 Watch 资源的权限需求即可。

路由和加密

Semaphore-Wireguard 负责在不同集群的节点之间进行加密通信,并在本地主机上加入访问远端 Pod 子网的路由。每个集群的每个节点上都会运行一个 WireGuard 管理器,负责节点之间的点到点通信。它负责生成本地密钥并发现所有远端密钥和端点,并配置与所有远程节点的对等关系。此外,它还负责更新本地路由表,以便通过主机的 WireGuard 接口将所有流量导向远程 Pod 子网。因此,Pod 可以利用所有集群中的节点之间创建的 WireGuard 网状结构,触达远程集群上的 Pod。

WireGuard 和 Calico 的集群内通信管理协作,形成了所有集群所有节点之间的网状结构,WireGuard 网络承担了节点之间的通信。

Semaphore WireGuard 部署样例

下图表达了 WireGuard 形成的主机网格,其中的 merit 就是我们的私有集群:

semaphore-wireguard-full-mesh-example

Service

Semaphore-Service-Mirror 是一个控制器,负责在不同 Kubernetes 集群之间复制服务。此处的镜像服务代表的是一个本地服务,其端点处于远端集群。

镜像控制器会在本地集群创建服务,并用远程集群中 Pod 的地址来更新端点列表,最终形成一个 ClusterIP 类型的 Service。

控制器部署样例

例如 AWS 集群上有一个 Service 对象:

$ kubectl --context=aws --namespace=sys-log get service fluentd
NAME      TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)             AGE
fluentd   ClusterIP   10.3.88.18   <none>        8888/TCP,8889/TCP   164d

它包含的端点:

$ kubectl --context=aws --namespace=sys-log get endpoints fluentd
NAME      ENDPOINTS                                                  AGE
fluentd   10.2.3.19:8889,10.2.4.19:8889,10.2.7.18:8889 + 3 more...   164d

镜像控制器会在 semaphore-service-mirror 所在的命名空间 sys-semaphore 中创建对应的 Service 和 Endpoint:

$ kubectl --context=gcp --namespace=sys-semaphore get service | grep fluentd
aws-sys-log-73736d-fluentd   ClusterIP   10.5.184.192   <none>        8888/TCP,8889/TCP   25d

$ kubectl --context=gcp --namespace=sys-semaphore get endpoints | grep fluentd
aws-sys-log-73736d-fluentd   10.2.3.19:8889,10.2.4.19:8889,10.2.7.18:8889 + 3 more...   17d

我们会发现,这个 Service 指向是远程集群:

$ kubectl --context=gcp --namespace=sys-semaphore describe service aws-sys-log-73736d-fluentd | grep Endpoints
Endpoints:         10.2.3.19:8888,10.2.4.19:8888,10.2.7.18:8888
Endpoints:         10.2.3.19:8889,10.2.4.19:8889,10.2.7.18:8889

控制器会监控远端资源,并根据事件进行更新,这样镜像服务就会及时地指向可用的端点。

最后对 CoreDNS 进行配置,就能够更好地进行解析了:

$ drill fluentd.sys-log.svc.cluster.aws
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 51067
;; flags: qr aa rd ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 
;; QUESTION SECTION:
;; fluentd.sys-log.svc.cluster.aws.     IN      A

;; ANSWER SECTION:
fluentd.sys-log.svc.cluster.aws.        5       IN      A       10.5.184.192

这样我们的 Pod 在无需关注镜像细节的情况下使用友好的服务名称了。

Policy

Semaphore-Policy 是一个用于创建防火墙策略的组件,用于限制来自远端集群的访问。这个组件会创建用于 Calico 网络策略的 IP 组,来定义允许发起的流量。控制器的唯一任务就是根据标签选择器来监控远端集群的 Pod,然后根据监控结果在本地创建 NetworkSets。接下来,可以使用简单的标签来描述 Calico Network Policy,方便地实现跨集群防火墙规则。

策略控制器部署样例

假设 GCP 集群中存在如下部署:

$ kubectl --context=gcp --namespace=sys-log get po -o wide -l policy.semaphore.uw.io/name=forwarder
NAME              READY   STATUS    RESTARTS   AGE     IP          NODE                                      NOMINATED NODE   READINESS GATES
forwarder-4jdm6   1/1     Running   0          3d20h   10.4.1.3    worker-k8s-exp-1-4l87.c.uw-dev.internal   <none>           <none>
forwarder-6ztl4   1/1     Running   0          3d20h   10.4.0.13   worker-k8s-exp-1-2868.c.uw-dev.internal   <none>           <none>
forwarder-klxdc   1/1     Running   0          4h27m   10.4.4.2    master-k8s-exp-1-j5f8.c.uw-dev.internal   <none>           <none>
forwarder-m9k27   1/1     Running   0          4h27m   10.4.5.2    master-k8s-exp-1-fc0b.c.uw-dev.internal   <none>           <none>
forwarder-n6nsn   1/1     Running   0          4h27m   10.4.3.3    master-k8s-exp-1-31rv.c.uw-dev.internal   <none>           <none>
forwarder-n8vnj   1/1     Running   0          3d20h   10.4.2.4    worker-k8s-exp-1-mdd7.c.uw-dev.internal   <none>           <none>

这是一个 sys-log 命名空间里名为 forwarder 的 DaemonSet。为了让控制器在远程集群中创建所需资源,需要给这个 Daemonset 中的 Pod 加入标签 policy.semaphore.uw.io/name=forwarder。加入标签之后,AWS 集群中的控制器就会创建对应的 GlobalNetworkSet

$ kubectl --context=aws describe GlobalNetworkSet gcp-sys-log-forwarder
Name:         gcp-sys-log-forwarder
Namespace:
Labels:       managed-by=semaphore-policy
              policy.semaphore.uw.io/cluster=gcp
              policy.semaphore.uw.io/name=forwarder
              policy.semaphore.uw.io/namespace=sys-log
Annotations:  projectcalico.org/metadata: {"uid":"c7569765-a47d-424c-9533-80e4a7c201d6","creationTimestamp":"2021-04-09T15:04:43Z"}
API Version:  crd.projectcalico.org/v1
Kind:         GlobalNetworkSet
Spec:
  Nets:
    10.4.5.2/32
    10.4.4.2/32
    10.4.1.3/32
    10.4.0.13/32
    10.4.3.3/32
    10.4.2.4/32
Events:  <none>

这个组合包含了远端对象的 IP 地址,可以用于 Calico 的网络策略,定义允许进入的流量:

apiVersion: crd.projectcalico.org/v1
kind: NetworkPolicy
metadata:
  name: allow-to-fluentd
spec:
  selector: app.kubernetes.io/name == 'fluentd'
  types:
    - Ingress
  ingress:
    - action: Allow
      protocol: TCP
      source:
        selector: >-
          policy.semaphore.uw.io/name == 'forwarder' &&
          policy.semaphore.uw.io/namespace == 'sys-log' &&
          policy.semaphore.uw.io/cluster == 'gcp'
        namespaceSelector: global()
      destination:
        ports:
          - 8889

上边的规则允许来自远端 forwarder 到本地 fluentd 的访问。

后记

这套东西对我们来说很有用,但是它的局限性也是显而易见的——如果恰巧适用于读者的环境和需求,那自然很好;如果不是,也希望读者在本文中得到一点启发。

没有 Calico 和 WireGuard,这个方案可能就难于落地了,方案的复杂部分主要是由这两个项目完成的,也正因为此,我们对我们的方案充满信心,这里对他们致以崇高敬意。

相关链接

  • Semaphore WireGuard:https://github.com/utilitywarehouse/semaphore-wireguard
  • Semaphore WireGuard 部署示例:https://github.com/utilitywarehouse/semaphore-wireguard/tree/main/deploy/example
  • Semaphore Service Mirror:https://github.com/utilitywarehouse/semaphore-service-mirror
  • Semaphore Service Mirror 部署示例:https://github.com/utilitywarehouse/semaphore-service-mirror/tree/master/deploy/example
  • Semaphore Policy:https://github.com/utilitywarehouse/semaphore-policy
  • Semaphore Policy 部署示例:https://github.com/utilitywarehouse/semaphore-policy/tree/main/deploy
Avatar
崔秀龙

简单,是大师的责任;我们凡夫俗子,能做到清楚就很不容易了。

comments powered by Disqus
上一页

相关