背景
随着技术和架构的不断演进,有着多运行时的态势:现代应用程序的基础能力不断地以独立运行时的方式从应用程序分离出来。这其中就有分布式应用运行时和服务网格两种运行时,今天这篇文章就为大家介绍 Dapr 与 Flomesh 服务网格的集成进行跨集群的服务调用来实现“真正的”多集群互联互通。
多集群
Kubernetes 秉持着松耦合和可扩展的设计理念,带来了 Kubernetes 生态的蓬勃发展。但这些大部分先限制在单一集群内,然后由于种种原因和目的企业内部创建的集群越来越多,比如单集群故障、监管要求、异地多机房可用区容灾、出于敏捷、降本考虑的混合云、多云部署、单一集群的承载能力受限、多版本 Kubernetes 集群共存等。.
Dapr
Dapr[1] 是一个分布式应用工具包,通过提供简单而稳定的 API 实现应用程序和外围功能组件的解耦合,让开发人员可以聚焦在业务功能的研发。同时与外围组件的解耦,也使得应用程序更加的便携、更加云原生,企业可以轻松低成本地将应用迁移到不同的环境中。
Dapr 工具包提供了丰富的功能,如服务调用、弹性策略、状态存储、发布/订阅、绑定、分布式锁、名称解析等,但对于高级的服务治理功能如灰度、跨集群服务调用没有支持。
Flomesh 服务网格
微服务架构兴起之后,随着规模越来越大,服务治理的难度和碎片化显著提升,服务网格的出现使得这些问题迎刃而解。服务网格是一个处理服务间通讯的专用的基础设施层,通过它可以透明地添加可观测性、流量管理和安全性等功能,而无需将其添加到你的代码中。
Flomesh 服务网格使用可编程代理 Pipy[2] 为核心提供东西、南北向的流量管理。通过基于 L7 的流量管理能力,突破计算环境间的网络隔离,建议一个虚拟的平面网络,使不同计算环境中应用可以互相通信。可以想象,Flomesh 服务网格是覆盖多集群的“大网格”。
示例介绍
服务端 NodeApp 是个 Dapr 应用, 在 Dapr hello-kubernetes 示例[3] 中的 NodeApp 基础上做了修改,返回响应时会显示当前的集群名;客户端 curl 用于向 NodeApp 发送请求,但并没有声明为 Dapr 应用。
NodeApp 中有三个 endpoint:
•
GET /ports
返回当前应用可访问的端口•
POST /neworder
创建新的订单•
GET /order
查询订单
下面的演示中会从集群的创建开始,一步步介绍环境的配置、各个组件的安装和配置、应用的部署等等。
一键安装脚本
我们也准备脚本进行一键安装和快速的体验,免除环境和组件配置的繁琐。可以 访问 GitHub 获取脚本[4] 内容。
使用该脚本之前,需要确保系统装已经安装了 Docker
和 kubectl
。脚本运行时会进行检查,并安装 k3d
、helm
、jq
、pv
等工具。
•
flomesh.sh
- 不提供任何参数,脚本会创建 4 个集群、完成环境安装配置并运行演示•
flomesh.sh -h
- 打印帮助信息•
flomesh.sh -i
- 创建 4 个集群、完成环境的安装和配置•
flomesh.sh -d
- 运行演示•
flomesh.sh -r
- 清理演示相关的资源•
flomesh.sh -u
- 删除所有集群
执行下面的命令,即可完成环境安装配置和演示的运行。
curl -sL https://raw.githubusercontent.com/addozhang/flomesh-dapr-demo/main/flomesh.sh | bash -
逐步演示
前提条件
进行演示,我们需要如下的工具:
• Docker
• Kubectl
• K3d
• Helm
• kubectx
创建多集群
获取本机 IP 地址作为集群间的通信地址。
export HOST_IP=10.0.0.13
执行下面创建 4 个集群:control-plane
、cluster-1
、cluster-2
和 cluster-3
。
API_PORT=6444 #6444 6445 6446 6447PORT=80 #81 82 83for CLUSTER_NAME in control-plane cluster-1 cluster-2 cluster-3do k3d cluster create ${CLUSTER_NAME} \ --image docker.io/rancher/k3s:v1.23.8-k3s2 \ --api-port "${HOST_IP}:${API_PORT}" \ --port "${PORT}:80@server:0" \ --servers-memory 4g \ --k3s-arg "--disable=traefik@server:0" \ --network multi-clusters \ --timeout 120s \ --wait ((API_PORT=API_PORT+1)) ((PORT=PORT+1))done
安装 FSM
helm repo add fsm https://charts.flomesh.iohelm repo updateexport FSM_NAMESPACE=flomeshexport FSM_VERSION=0.2.1-alpha.3for CLUSTER_NAME in control-plane cluster-1 cluster-2 cluster-3do kubectx k3d-${CLUSTER_NAME} sleep 1 helm install --namespace ${FSM_NAMESPACE} --create-namespace --version=${FSM_VERSION} --set fsm.logLevel=5 fsm fsm/fsm sleep 1 kubectl wait --for=condition=ready pod --all -n $FSM_NAMESPACE --timeout=120sdone
将集群 cluster-1
、cluster-2
和 cluster-3
纳入集群 control-plane
的管理。
kubectx k3d-control-planesleep 1PORT=81for CLUSTER_NAME in cluster-1 cluster-2 cluster-3do kubectl apply -f - <<EOFapiVersion: flomesh.io/v1alpha1kind: Clustermetadata: name: ${CLUSTER_NAME}spec: gatewayHost: ${HOST_IP} gatewayPort: ${PORT} kubeconfig: |+`k3d kubeconfig get ${CLUSTER_NAME} | sed 's|^| |g' | sed "s|0.0.0.0|$HOST_IP|g"`EOF((PORT=PORT+1))done
安装 osm-edge
下载 CLI
system=$(uname -s | tr [:upper:] [:lower:])arch=$(dpkg --print-architecture)release=v1.3.1curl -L https://github.com/flomesh-io/osm-edge/releases/download/$release/osm-edge-$release-$system-$arch.tar.gz | tar -vxzf -./${system}-${arch}/osm versioncp ./${system}-${arch}/osm /usr/local/bin/
将服务网格 osm-edge 安装到集群 cluster-1
、cluster-2
和 cluster-3
。控制平面不处理应用流量,无需安装。
export OSM_NAMESPACE=osm-systemexport OSM_MESH_NAME=osmfor CLUSTER_NAME in cluster-1 cluster-2 cluster-3do kubectx k3d-${CLUSTER_NAME} DNS_SVC_IP="$(kubectl get svc -n kube-system -l k8s-app=kube-dns -o jsonpath='{.items[0].spec.clusterIP}')"osm install \ --mesh-name "$OSM_MESH_NAME" \ --osm-namespace "$OSM_NAMESPACE" \ --set=osm.certificateProvider.kind=tresor \ --set=osm.image.pullPolicy=Always \ --set=osm.sidecarLogLevel=error \ --set=osm.controllerLogLevel=warn \ --timeout=900s \ --set=osm.localDNSProxy.enable=true \ --set=osm.localDNSProxy.primaryUpstreamDNSServerIPAddr="${DNS_SVC_IP}"done
kubectl get svc -n defaultNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEkubernetes ClusterIP 10.43.0.1 <none> 443/TCP 43h
放行 pod 中访问 apiserver 的流量,不经过 sidecar。
for CLUSTER_NAME in cluster-1 cluster-2 cluster-3do kubectx k3d-${CLUSTER_NAME} kubectl patch meshconfig osm-mesh-config -n $OSM_NAMESPACE -p '{"spec":{"traffic":{"outboundIPRangeExclusionList":["10.43.0.1/32"]}}}' --type=mergedone
安装 Dapr
将 Dapr 安装到集群 cluster-1
、cluster-2
和 cluster-3
。
for CLUSTER_NAME in cluster-1 cluster-2 cluster-3do kubectx k3d-${CLUSTER_NAME} dapr init --kubernetes \ --enable-mtls=false \ --waitdone
查看组件运行状态。
dapr status -k NAME NAMESPACE HEALTHY STATUS REPLICAS VERSION AGE CREATED dapr-placement-server dapr-system True Running 1 1.9.6 2m 2023-02-09 10:36.51 dapr-operator dapr-system True Running 1 1.9.6 2m 2023-02-09 10:36.51 dapr-dashboard dapr-system True Running 1 0.11.0 2m 2023-02-09 10:36.51 dapr-sentry dapr-system True Running 1 1.9.6 2m 2023-02-09 10:36.51 dapr-sidecar-injector dapr-system True Running 1 1.9.6 2m 2023-02-09 10:36.51
查看组件 Service 及端口。
kubectl get svc -n dapr-systemNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEdapr-placement-server ClusterIP None <none> 50005/TCP,8201/TCP 5h50mdapr-sidecar-injector ClusterIP 10.43.12.213 <none> 443/TCP 5h50mdapr-webhook ClusterIP 10.43.103.31 <none> 443/TCP 5h50mdapr-dashboard ClusterIP 10.43.172.156 <none> 8080/TCP 5h50mdapr-api ClusterIP 10.43.126.14 <none> 80/TCP 5h50mdapr-sentry ClusterIP 10.43.41.10 <none> 80/TCP 5h50m
不拦截 dapr 组件和 redis 端口的流量。
for CLUSTER_NAME in cluster-1 cluster-2 cluster-3do kubectx k3d-${CLUSTER_NAME} kubectl patch meshconfig osm-mesh-config -n $OSM_NAMESPACE -p '{"spec":{"traffic":{"outboundPortExclusionList":[50005,8201,6379]}}}' --type=mergedone
部署 Redis
version: '3'services: redis: image: redis:latest container_name: redis ports: - 6379:6379 volumes: - ./data:/data command: redis-server --appendonly yes --requirepass changeme
创建示例命名空间
export NAMESPACE=dapr-testfor CLUSTER_NAME in cluster-1 cluster-2 cluster-3do kubectx k3d-${CLUSTER_NAME} kubectl create namespace $NAMESPACE osm namespace add $NAMESPACEdone
注册 Redis State Store 组件
export NAMESPACE=dapr-testfor CLUSTER_NAME in cluster-1 cluster-2 cluster-3do kubectx k3d-${CLUSTER_NAME} kubectl create secret generic redis -n $NAMESPACE --from-literal=redis-password=changeme kubectl apply -n $NAMESPACE -f - <<EOFapiVersion: dapr.io/v1alpha1kind: Componentmetadata: name: statestorespec: type: state.redis version: v1 metadata: - name: redisHost value: 10.0.0.13:6379 - name: redisPassword secretKeyRef: name: redis key: redis-passwordauth: secretStore: kubernetesEOFdone
部署示例应用
在集群 cluster-1
和 cluster-3
的 httpbin
命名空间(由网格管理,会注入 sidecar)下,部署 nodeapp
应用。
export NAMESPACE=dapr-testfor CLUSTER_NAME in cluster-1 cluster-2 cluster-3do kubectx k3d-${CLUSTER_NAME} kubectl apply -n $NAMESPACE -f - <<EOFkind: ServiceapiVersion: v1metadata: name: nodeapp labels: app: nodespec: selector: app: node ports: - protocol: TCP port: 3000 targetPort: 3000---apiVersion: apps/v1kind: Deploymentmetadata: name: nodeapp labels: app: nodespec: replicas: 1 selector: matchLabels: app: node template: metadata: labels: app: node annotations: dapr.io/enabled: "true" dapr.io/app-id: "nodeapp" dapr.io/app-port: "3000" dapr.io/enable-api-logging: "true" spec: containers: - name: node image: addozhang/dapr-nodeapp env: - name: APP_PORT value: "3000" - name: CLUSTER_NAME value: ${CLUSTER_NAME} ports: - containerPort: 3000 imagePullPolicy: AlwaysEOFdone
在集群 cluster-2
的命名空间 curl
下部署 curl
应用,这个命名空间是被网格管理的,注入的 sidecar 会完全流量的跨集群调度。
export NAMESPACE=curlkubectx k3d-cluster-2kubectl create namespace ${NAMESPACE}osm namespace add ${NAMESPACE}kubectl apply -n ${NAMESPACE} -f - <<EOFapiVersion: v1kind: ServiceAccountmetadata: name: curl---apiVersion: v1kind: Servicemetadata: name: curl labels: app: curl service: curlspec: ports: - name: http port: 80 selector: app: curl---apiVersion: apps/v1kind: Deploymentmetadata: name: curlspec: replicas: 1 selector: matchLabels: app: curl template: metadata: labels: app: curl annotations: dapr.io/enabled: "true" dapr.io/app-id: "curl" dapr.io/enable-api-logging: "true" dapr.io/log-level: "debug" spec: serviceAccountName: curl containers: - image: curlimages/curl imagePullPolicy: IfNotPresent name: curl command: ["sleep", "365d"]EOFsleep 3kubectl wait --for=condition=ready pod -n ${NAMESPACE} --all --timeout=60s
导出服务
export NAMESPACE=dapr-testfor CLUSTER_NAME in cluster-1 cluster-3do kubectx k3d-${CLUSTER_NAME} kubectl apply -n $NAMESPACE -f - <<EOFapiVersion: flomesh.io/v1alpha1kind: ServiceExportmetadata: name: nodeappspec: serviceAccountName: '*' pathRewrite: from: '^/nodeapp/?' to: '/' rules: - portNumber: 3000 path: '/nodeapp' pathType: PrefixEOFsleep 1done
导出后的服务,FSM 会自动为其创建 Ingress 规则,有了规则之后就可以通过 Ingress 来访问这些服务。
for CLUSTER_NAME_INDEX in 1 3do CLUSTER_NAME=cluster-${CLUSTER_NAME_INDEX} ((PORT=80+CLUSTER_NAME_INDEX)) kubectx k3d-${CLUSTER_NAME} echo "Getting service exported in cluster ${CLUSTER_NAME}" echo '-----------------------------------' kubectl get serviceexports.flomesh.io -A echo '-----------------------------------' curl -s "http://${HOST_IP}:${PORT}/ports" echo '-----------------------------------'done
测试
切换到集群 cluster-2
在 curl pod 中发起请求进行测试。
kubectx k3d-cluster-2curl_client="$(kubectl get pod -n curl -l app=curl -o jsonpath='{.items[0].metadata.name}')"
发送请求访问 nodeapp 时报错,这是因为在 cluster-2
中并未部署 nodeapp 应用。默认情况下,在不指定跨集群的流量策略时,只会尝试调用本地服务,不会将流量调度到其他的集群。
kubectl exec "${curl_client}" -n curl -c curl -- curl -s http://nodeapp.dapr-test:3000/portscommand terminated with exit code 7
设置流量策略
跨集群的流量策略支持三种:
•
Locality
:只使用本集群的服务,也是默认类型。这就是为什么我们不提供任何全局策略的时候,访问nodeapp
应用会失败,因为在集群cluster-2
中并没有该服务。•
FailOver
:当本集群访问失败时才会代理到其他集群,也就是常说的故障迁移,类似主备。•
ActiveActive
:正常情况下也会代理到其他集群,类似多活。
接下来我们创建并应用如下的 ActiveActive
策略,同样是在 cluster-2
集群中进行操作。
kubectl apply -n dapr-test -f - <<EOFapiVersion: flomesh.io/v1alpha1kind: GlobalTrafficPolicymetadata: name: nodeappspec: lbType: ActiveActive targets: - clusterKey: default/default/default/cluster-1 weight: 100 - clusterKey: default/default/default/cluster-3 weight: 100EOF
再次尝试发送请求,可以收到成功的响应。
kubectl exec "${curl_client}" -n curl -c curl -- curl -s http://nodeapp.dapr-test:3000/ports{"DAPR_HTTP_PORT":"3500","DAPR_GRPC_PORT":"50001"}, from cluster: cluster-3
这时请求 /neworder
尝试写入订单数据。
kubectl exec "${curl_client}" -n curl -c curl -- curl -si --request POST --data '{"data":{"orderId":"42"}}' --header Content-Type:application/json --header dapr-app-id:nodeapp http://nodeapp.dapr-test:3000/newordercreated order via cluster: cluster-1
多次请求 /order
,可以发现请求被转发到了不同的集群进行处理。
kubectl exec "${curl_client}" -n curl -c curl -- curl -s http://nodeapp.dapr-test:3000/order{"orderId":"42"}, from cluster: cluster-3kubectl exec "${curl_client}" -n curl -c curl -- curl -s http://nodeapp.dapr-test:3000/order{"orderId":"42"}, from cluster: cluster-1