Dapr 集成 Flomesh 實現跨集羣服務調用

背景

隨着技術和架構的不斷演進,有着多運行時的態勢:現代應用程序的基礎能力不斷地以獨立運行時的方式從應用程序分離出來。這其中就有分佈式應用運行時和服務網格兩種運行時,今天這篇文章就爲大家介紹 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:

下面的演示中會從集羣的創建開始,一步步介紹環境的配置、各個組件的安裝和配置、應用的部署等等。

一鍵安裝腳本

我們也準備腳本進行一鍵安裝和快速的體驗,免除環境和組件配置的繁瑣。可以 訪問 GitHub 獲取腳本 [4] 內容。

使用該腳本之前,需要確保系統裝已經安裝了 Docker 和 kubectl。腳本運行時會進行檢查,並安裝 k3dhelmjqpv 等工具。

執行下面的命令,即可完成環境安裝配置和演示的運行。

curl -sL https://raw.githubusercontent.com/addozhang/flomesh-dapr-demo/main/flomesh.sh | bash -

逐步演示

前提條件

進行演示,我們需要如下的工具:

創建多集羣

獲取本機 IP 地址作爲集羣間的通信地址。

export HOST_IP=10.0.0.13

執行下面創建 4 個集羣:control-planecluster-1cluster-2 和 cluster-3

API_PORT=6444 #6444 6445 6446 6447
PORT=80 #81 82 83
for CLUSTER_NAME in control-plane cluster-1 cluster-2 cluster-3
do
  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.io
helm repo update
export FSM_NAMESPACE=flomesh
export FSM_VERSION=0.2.1-alpha.3
for CLUSTER_NAME in control-plane cluster-1 cluster-2 cluster-3
do 
  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=120s
done

將集羣 cluster-1cluster-2 和 cluster-3 納入集羣 control-plane 的管理。

kubectx k3d-control-plane
sleep 1
PORT=81
for CLUSTER_NAME in cluster-1 cluster-2 cluster-3
do
  kubectl apply -f - <<EOF
apiVersion: flomesh.io/v1alpha1
kind: Cluster
metadata:
  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.1
curl -L https://github.com/flomesh-io/osm-edge/releases/download/$release/osm-edge-$release-$system-$arch.tar.gz | tar -vxzf -
./${system}-${arch}/osm version
cp ./${system}-${arch}/osm /usr/local/bin/

將服務網格 osm-edge 安裝到集羣 cluster-1cluster-2 和 cluster-3。控制平面不處理應用流量,無需安裝。

export OSM_NAMESPACE=osm-system
export OSM_MESH_NAME=osm
for CLUSTER_NAME in cluster-1 cluster-2 cluster-3
do
  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 default
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.43.0.1    <none>        443/TCP   43h

放行 pod 中訪問 apiserver 的流量,不經過 sidecar。

for CLUSTER_NAME in cluster-1 cluster-2 cluster-3
do
  kubectx k3d-${CLUSTER_NAME}
  kubectl patch meshconfig osm-mesh-config -n $OSM_NAMESPACE -p '{"spec":{"traffic":{"outboundIPRangeExclusionList":["10.43.0.1/32"]}}}'  --type=merge
done

安裝 Dapr

將 Dapr 安裝到集羣 cluster-1cluster-2 和 cluster-3

for CLUSTER_NAME in cluster-1 cluster-2 cluster-3
do
  kubectx k3d-${CLUSTER_NAME}
  dapr init --kubernetes \
  --enable-mtls=false \
  --wait
done

查看組件運行狀態。

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-system
NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)              AGE
dapr-placement-server   ClusterIP   None            <none>        50005/TCP,8201/TCP   5h50m
dapr-sidecar-injector   ClusterIP   10.43.12.213    <none>        443/TCP              5h50m
dapr-webhook            ClusterIP   10.43.103.31    <none>        443/TCP              5h50m
dapr-dashboard          ClusterIP   10.43.172.156   <none>        8080/TCP             5h50m
dapr-api                ClusterIP   10.43.126.14    <none>        80/TCP               5h50m
dapr-sentry             ClusterIP   10.43.41.10     <none>        80/TCP               5h50m

不攔截 dapr 組件和 redis 端口的流量。

for CLUSTER_NAME in cluster-1 cluster-2 cluster-3
do
  kubectx k3d-${CLUSTER_NAME}
  kubectl patch meshconfig osm-mesh-config -n $OSM_NAMESPACE -p '{"spec":{"traffic":{"outboundPortExclusionList":[50005,8201,6379]}}}'  --type=merge
done

部署 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-test
for CLUSTER_NAME in cluster-1 cluster-2 cluster-3
do
  kubectx k3d-${CLUSTER_NAME}
  kubectl create namespace $NAMESPACE
  osm namespace add $NAMESPACE
done

註冊 Redis State Store 組件

export NAMESPACE=dapr-test
for CLUSTER_NAME in cluster-1 cluster-2 cluster-3
do
  kubectx k3d-${CLUSTER_NAME}
  kubectl create secret generic redis -n $NAMESPACE --from-literal=redis-password=changeme
  kubectl apply -n $NAMESPACE -f - <<EOF
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: 10.0.0.13:6379
  - name: redisPassword
    secretKeyRef:
      name: redis
      key: redis-password
auth:
  secretStore: kubernetes
EOF
done

部署示例應用

在集羣 cluster-1 和 cluster-3 的 httpbin 命名空間(由網格管理,會注入 sidecar)下,部署 nodeapp 應用。

export NAMESPACE=dapr-test
for CLUSTER_NAME in cluster-1 cluster-2  cluster-3
do
  kubectx k3d-${CLUSTER_NAME}
  kubectl apply -n $NAMESPACE -f - <<EOF
kind: Service
apiVersion: v1
metadata:
  name: nodeapp
  labels:
    app: node
spec:
  selector:
    app: node
  ports:
  - protocol: TCP
    port: 3000
    targetPort: 3000

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nodeapp
  labels:
    app: node
spec:
  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: Always
EOF
done

在集羣 cluster-2 的命名空間 curl 下部署 curl 應用,這個命名空間是被網格管理的,注入的 sidecar 會完全流量的跨集羣調度。

export NAMESPACE=curl
kubectx k3d-cluster-2
kubectl create namespace ${NAMESPACE}
osm namespace add ${NAMESPACE}
kubectl apply -n ${NAMESPACE} -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: curl
---
apiVersion: v1
kind: Service
metadata:
  name: curl
  labels:
    app: curl
    service: curl
spec:
  ports:
    - name: http
      port: 80
  selector:
    app: curl
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: curl
spec:
  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"]
EOF

sleep 3
kubectl wait --for=condition=ready pod -n ${NAMESPACE} --all --timeout=60s

導出服務

export NAMESPACE=dapr-test
for CLUSTER_NAME in cluster-1 cluster-3
do
  kubectx k3d-${CLUSTER_NAME}
  kubectl apply -n $NAMESPACE -f - <<EOF
apiVersion: flomesh.io/v1alpha1
kind: ServiceExport
metadata:
  name: nodeapp
spec:
  serviceAccountName: '*'
  pathRewrite:
    from: '^/nodeapp/?'
    to: '/'
  rules:
    - portNumber: 3000
      path: '/nodeapp'
      pathType: Prefix
EOF
sleep 1
done

導出後的服務,FSM 會自動爲其創建 Ingress 規則,有了規則之後就可以通過 Ingress 來訪問這些服務。

for CLUSTER_NAME_INDEX in 1 3
do
  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-2
curl_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/ports
command terminated with exit code 7

設置流量策略

跨集羣的流量策略支持三種:

接下來我們創建並應用如下的 ActiveActive 策略,同樣是在 cluster-2 集羣中進行操作。

kubectl apply -n dapr-test -f - <<EOF
apiVersion: flomesh.io/v1alpha1
kind: GlobalTrafficPolicy
metadata:
  name: nodeapp
spec:
  lbType: ActiveActive
  targets:
    - clusterKey: default/default/default/cluster-1
      weight: 100
    - clusterKey: default/default/default/cluster-3
      weight: 100
EOF

再次嘗試發送請求,可以收到成功的響應。

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/neworder
created 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-3
kubectl exec "${curl_client}" -n curl -c curl -- curl -s http://nodeapp.dapr-test:3000/order
{"orderId":"42"}, from cluster: cluster-1

引用鏈接

[1] Dapr: https://dapr.io
[2] Pipy: https://github.com/flomesh-io/pipy
[3] Dapr hello-kubernetes 示例: https://github.com/dapr/quickstarts/tree/master/tutorials/hello-kubernetes
[4] 訪問 GitHub 獲取腳本: https://github.com/addozhang/flomesh-dapr-demo

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Y-MewxHVMULKDi4_cbl6Yw