ErieCanal 和 Karmada:支持跨集群的服务治理

背景

Kubernetes 作为一项核心技术已成为现代应用程序架构的基础,越来越多的企业使用 Kubernetes 作为容器编排系统。随着对云计算接受程度不断提高、企业规模的持续增长,越来越多的企业开始考虑采用或者已经采用多云和混合云的架构。多云混合云策略的引入,相应地,Kubernetes 集群的数量也变得越来越多。

使用 Karmada 我们可以轻松完成应用的跨地域或者云提供商的部署,通过在多个 Kubernetes 集群中部署同一服务的多个实例,提升了系统的可用性和灵活性。但由于服务的彼此依赖,为了保证服务间的交互和功能完整性,要将这些相关的服务部署在同一个集群中。服务之间的强依赖和高耦合性就像一个错综复杂的藤蔓,使得系统的各个组件之间纠缠不清。在服务部署时往往需要进行全量部署,资源利用率低,运营成本增加。

这篇文档将演示如何使用 ErieCanal 完成 Karmada 多集群的互联互通,实现跨集群的服务治理。

ErieCanal 是一个 MCS(多集群服务 Multi-Cluster Service)实现,为 Kubernetes 集群提供 MCS、Ingress、Egress、GatewayAPI。

总体架构

在本示例中,我们借助 Karmada 实现资源的跨集群调度;借助 ErieCanal 实现服务的跨集群的注册发现。

  • 服务注册:通过创建 ServiceExport 资源来将 Service 声明为多集群服务,注册到 ErieCanal 的控制面,同时为该服务创建入口(Ingress)规则。
  • 服务发现:ErieCanal 的控制面,使用服务的信息及所在集群的信息创建 ServiceImport 资源,并将其同步到所有的成员集群。

ErieCanal 会运行在控制面集群和成员集群上,成员集群需要注册到控制面集群。ErieCanal 是独立的组件,无需运行在 Karmada 控制面上。ErieCanal 负责多集群服务的注册发现,Karmada 负责多集群的资源调度。

与 ErieCanal 集成支持跨集群的服务治理 - 图1

完成服务的跨集群注册发现之后,在处理应用的访问(curl -> httpbin)时要完成流量的自动调度,这里有两种方案:

  • 集成服务网格 FSM(Flomesh Service Mesh) 根据策略实现流量的调度,除了从 Kubernetes Service 获取服务的访问信息外,还会同多集群资源的 ServiceImport 中获取服务信息。
  • 使用 ErieCanal 中的组件 ErieCanalNet(该特性即将发布),通过 eBPF+sidecar(Node level)来实现流量的跨集群管理。

下面是演示的全部流程,大家也可以使用我们提供的 脚本 flomesh.sh 完成自动化地演示。使用脚本的前提,系统需要已经安装 Docker 和 kubectl,并至少 8G 内存。脚本的使用方式:

  • flomesh.sh - 不提供任何参数,脚本会创建 4 个集群、完成环境搭建(安装 Karmada、ErieCanal、FSM),并运行示例。
  • flomesh.sh -h - 查看参数的说明
  • flomesh.sh -i - 创建集群和完成搭建环境
  • flomesh.sh -d - 运行示例
  • flomesh.sh -r - 删除示例所在的命名空间
  • flomesh.sh -u - 销毁集群

以下是分步指南,与 flomesh.sh 脚本的执行步骤一致。

前提条件

  • 4 个集群:control-plane、clutser-1、cluster-2、cluster-3
  • Docker
  • k3d
  • helm
  • kubectl

环境设置

1. 安装 Karmada 控制面

参考 Karmada文档,安装 Karmada 的控制面。Karmada 初始化完成后,将 三个成员集群 clutser-1、cluster-2、cluster-3 注册到 Karmada 控制面,可 参考 Karmada 的集群注册指引

这里使用 push 模式,集群注册参考下面的命令(在控制面集群 control-plane 上执行 ):

  1. karmadactl --kubeconfig PATH_TO_KARMADA_CONFIG join CLUSTER_NAME --cluster-kubeconfig=PATH_CLSUTER_KUBECONFIG

接来下,需要向 Karmada 控制面注册 ErieCanal 的多集群 CRD。

  1. kubectl --kubeconfig PATH_TO_KARMADA_CONFIG apply -f https://raw.githubusercontent.com/flomesh-io/ErieCanal/main/charts/erie-canal/apis/flomesh.io_clusters.yaml
  2. kubectl --kubeconfig PATH_TO_KARMADA_CONFIG apply -f https://raw.githubusercontent.com/flomesh-io/ErieCanal/main/charts/erie-canal/apis/flomesh.io_mcs-api.yaml

在控制面集群 control-plane 使用 Karmada apiserver 的 config 可查看集群的注册信息。

  1. kubectl --kubeconfig PATH_TO_KARMADA_CONFIG get cluster
  2. NAME VERSION MODE READY AGE
  3. cluster-1 v1.23.8+k3s2 Push True 154m
  4. cluster-2 v1.23.8+k3s2 Push True 154m
  5. cluster-3 v1.23.8+k3s2 Push True 154m

2. 安装 ErieCanal

接下来,需要在 所有集群上 安装 ErieCanal。这里推荐使用 Helm 的方式来安装,可以参考 ErieCanal 的安装文档

  1. helm repo add ec https://ec.flomesh.io --force-update
  2. helm repo update
  3. EC_NAMESPACE=erie-canal
  4. EC_VERSION=0.1.3
  5. helm upgrade -i --namespace ${EC_NAMESPACE} --create-namespace --version=${EC_VERSION} --set ec.logLevel=5 ec ec/erie-canal

安装完成后,将 三个成员集群 注册到 ErieCanal 的控制面。命令(在控制面集群 control-plane 上执行)如下,其中

  • CLUSTER_NAME:成员集群名
  • HOST_IP:成员集群的入口 IP 地址
  • PORT:成员集群的入口端口
  • KUBECONFIG:成员集群的 KUBECONFIG 内容
  1. kubectl apply -f - <<EOF
  2. apiVersion: flomesh.io/v1alpha1
  3. kind: Cluster
  4. metadata:
  5. name: ${CLUSTER_NAME}
  6. spec:
  7. gatewayHost: ${HOST_IP}
  8. gatewayPort: ${PORT}
  9. kubeconfig: |+
  10. ${KUBECONFIG}
  11. EOF

注册完成后,可在控制面集群 control-plane 查看成员集群的注册信息:

  1. kubectl get cluster
  2. NAME REGION ZONE GROUP GATEWAY HOST GATEWAY PORT MANAGED MANAGED AGE AGE
  3. local default default default 80 159m
  4. cluster-1 default default default 10.0.2.4 80 True 159m 159m
  5. cluster-2 default default default 10.0.2.5 80 True 159m 159m
  6. cluster-3 default default default 10.0.2.6 80 True 159m 159m

3. 安装 FSM

这里我们选择集成服务网格来完成流量的跨集群调度,接下来就是在 三个成员集群 上安装服务网格 FSM。FSM 提供了 CLI 和 Helm 两种方式安装,这里我们选择 CLI 的方式。

先下载 FSM 的 CLI。

  1. system=$(uname -s | tr [:upper:] [:lower:])
  2. arch=$(dpkg --print-architecture)
  3. release=v1.0.0
  4. curl -L https://github.com/flomesh-io/fsm/releases/download/${release}/fsm-${release}-${system}-${arch}.tar.gz | tar -vxzf -
  5. ./${system}-${arch}/fsm version
  6. sudo cp ./${system}-${arch}/fsm /usr/local/bin/

然后就是安装 FSM。在支持多集群时,FSM 需要开启本地 DNS 代理,安装是需要提供集群 DNS 的地址。

  1. DNS_SVC_IP="$(kubectl get svc -n kube-system -l k8s-app=kube-dns -o jsonpath='{.items[0].spec.clusterIP}')"
  2. fsm install \
  3. --set=fsm.localDNSProxy.enable=true \
  4. --set=fsm.localDNSProxy.primaryUpstreamDNSServerIPAddr="${DNS_SVC_IP}" \
  5. --timeout 120s

执行命令可常看集群中安装的服务网格版本等信息。

  1. fsm version
  2. CLI Version: version.Info{Version:"v1.0.0", GitCommit:"9966a2b031c862b54b4b007eae35ee16afa31a80", BuildDate:"2023-05-29-12:10"}
  3. MESH NAME MESH NAMESPACE VERSION GIT COMMIT BUILD DATE
  4. fsm fsm-system v1.0.0 9966a2b031c862b54b4b007eae35ee16afa31a80 2023-05-29-12:11

部署示例应用

应用的部署调度,通过 Karmada 控制面来完成,执行 kubectl 命令时需要显示地指定 Karmada 控制面 apiserver 的配置。

  1. alias kmd="kubectl --kubeconfig /etc/karmada/karmada-apiserver.config"

服务端

创建命名空间 httpbin。为了纳入服务网格的管理,创建时,我们增加了 label flomesh.io/monitored-by: fsm 以及 annotation flomesh.io/monitored-by: fsm

  1. kmd apply -f - <<EOF
  2. apiVersion: v1
  3. kind: Namespace
  4. metadata:
  5. name: httpbin
  6. labels:
  7. flomesh.io/monitored-by: fsm
  8. annotations:
  9. flomesh.io/sidecar-injection: enabled
  10. EOF

创建 httpbin Deployment 和 Service。

  1. kmd apply -n httpbin -f - <<EOF
  2. apiVersion: apps/v1
  3. kind: Deployment
  4. metadata:
  5. name: httpbin
  6. labels:
  7. app: pipy
  8. spec:
  9. replicas: 2
  10. selector:
  11. matchLabels:
  12. app: pipy
  13. template:
  14. metadata:
  15. labels:
  16. app: pipy
  17. spec:
  18. containers:
  19. - name: pipy
  20. image: flomesh/pipy:latest
  21. ports:
  22. - containerPort: 8080
  23. command:
  24. - pipy
  25. - -e
  26. - |
  27. pipy()
  28. .listen(8080)
  29. .serveHTTP(() => new Message(os.env['HOSTNAME'] +'\n'))
  30. ---
  31. apiVersion: v1
  32. kind: Service
  33. metadata:
  34. name: httpbin
  35. spec:
  36. ports:
  37. - port: 8080
  38. targetPort: 8080
  39. protocol: TCP
  40. selector:
  41. app: pipy
  42. EOF

在 Karmada 控制面创建资源后,还需要创建 PropagationPolicy 策略来对资源进行分发,我们将 DeploymentService 分发到成员集群 cluster-1cluster-3

  1. $kmd apply -n httpbin -f - <<EOF
  2. apiVersion: policy.karmada.io/v1alpha1
  3. kind: PropagationPolicy
  4. metadata:
  5. name: httpbin
  6. spec:
  7. resourceSelectors:
  8. - apiVersion: apps/v1
  9. kind: Deployment
  10. name: httpbin
  11. - apiVersion: v1
  12. kind: Service
  13. name: httpbin
  14. placement:
  15. clusterAffinity:
  16. clusterNames:
  17. - cluster-1
  18. - cluster-3
  19. EOF

应用部署完之后,还需要完成服务的多集群注册。如开头所说,我们要为服务创建 ServiceExport 资源来完成服务的注册。同样地,在 Karmada 控制面创建资源时,也需要为其创建分发策略。

  1. kmd apply -n httpbin -f - <<EOF
  2. apiVersion: flomesh.io/v1alpha1
  3. kind: ServiceExport
  4. metadata:
  5. name: httpbin
  6. spec:
  7. serviceAccountName: "*"
  8. rules:
  9. - portNumber: 8080
  10. path: "/httpbin"
  11. pathType: Prefix
  12. ---
  13. apiVersion: policy.karmada.io/v1alpha1
  14. kind: PropagationPolicy
  15. metadata:
  16. name: httpbin
  17. spec:
  18. resourceSelectors:
  19. - apiVersion: flomesh.io/v1alpha1
  20. kind: ServiceExport
  21. name: httpbin
  22. placement:
  23. clusterAffinity:
  24. clusterNames:
  25. - cluster-1
  26. - cluster-2
  27. - cluster-3
  28. EOF

客户端

接下来就是客户端,也是同样操作,先创建命名空间 curl

  1. kmd apply -f - <<EOF
  2. apiVersion: v1
  3. kind: Namespace
  4. metadata:
  5. name: curl
  6. labels:
  7. flomesh.io/monitored-by: fsm
  8. annotations:
  9. flomesh.io/sidecar-injection: enabled
  10. EOF

部署应用 curl。

  1. kmd apply -n curl -f - <<EOF
  2. apiVersion: apps/v1
  3. kind: Deployment
  4. metadata:
  5. labels:
  6. app: curl
  7. name: curl
  8. spec:
  9. replicas: 1
  10. selector:
  11. matchLabels:
  12. app: curl
  13. strategy: {}
  14. template:
  15. metadata:
  16. creationTimestamp: null
  17. labels:
  18. app: curl
  19. spec:
  20. containers:
  21. - image: curlimages/curl
  22. name: curl
  23. command:
  24. - sleep
  25. - 365d
  26. ---
  27. apiVersion: v1
  28. kind: Service
  29. metadata:
  30. name: curl
  31. labels:
  32. app: curl
  33. service: curl
  34. spec:
  35. ports:
  36. - name: http
  37. port: 80
  38. selector:
  39. app: curl
  40. EOF

同样需要为应用创建分发策略,我们通过策略将其调度到成员集群 cluster-2 上。

  1. kmd apply -n curl -f - <<EOF
  2. apiVersion: policy.karmada.io/v1alpha1
  3. kind: PropagationPolicy
  4. metadata:
  5. name: curl
  6. spec:
  7. resourceSelectors:
  8. - apiVersion: apps/v1
  9. kind: Deployment
  10. name: curl
  11. - apiVersion: v1
  12. kind: Service
  13. name: curl
  14. placement:
  15. clusterAffinity:
  16. clusterNames:
  17. - cluster-2
  18. EOF

测试

我们在成员集群 cluster-2 上进行测试。

  1. curl_client=`kubectl get po -n curl -l app=curl -o jsonpath='{.items[0].metadata.name}'`
  2. kubectl exec $curl_client -n curl -c curl -- curl -s http://httpbin.httpbin:8080

此时我们会看到返回 Not found,这是因为默认情况下不会使用其他群里的服务来响应请求。创建资源 GlobalTrafficPolicy 来对调度策略进行配置。同样地,也需要为其创建分发策略。

  1. kmd apply -n httpbin -f - <<EOF
  2. apiVersion: flomesh.io/v1alpha1
  3. kind: GlobalTrafficPolicy
  4. metadata:
  5. name: httpbin
  6. spec:
  7. lbType: ActiveActive
  8. targets:
  9. - clusterKey: default/default/default/cluster-1
  10. - clusterKey: default/default/default/cluster-3
  11. ---
  12. apiVersion: policy.karmada.io/v1alpha1
  13. kind: PropagationPolicy
  14. metadata:
  15. name: httpbin
  16. spec:
  17. resourceSelectors:
  18. - apiVersion: flomesh.io/v1alpha1
  19. kind: GlobalTrafficPolicy
  20. name: httpbin
  21. placement:
  22. clusterAffinity:
  23. clusterNames:
  24. - cluster-2
  25. EOF

此时再发送测试请求,就可以收到其他集群 httpbin 的响应了。

  1. kubectl -n curl -c curl -- curl -s http://httpbin.httpbin:8080/
  2. httpbin-c45b78fd-4v2vq
  3. kubectl -n curl -c curl -- curl -s http://httpbin.httpbin:8080/
  4. httpbin-c45b78fd-6822z