開發與維運

非容器應用與K8s工作負載的服務網格化實踐-2 基於ASM的Workload Entry實踐

Istio從1.6版本開始在流量管理中引入了新的資源類型Workload Entry,用以將虛擬機(VM)或者裸金屬(bare metal)進行抽象,使其在網格化後作為與Kubernetes中的POD同等重要的負載,具備流量管理、安全管理、可視化等能力。通過WorkloadEntry可以簡化虛擬機/裸金屬的網格化配置過程。

1 VM與POD同等負載

首先,我們通過一個示例來展示如何使用Workload Entry實現VM與POD同等負載。示例的拓撲如下圖所示,核心目標是將ack中的hello2 pod和ecs中的hello2 app視為同一個服務(hello2 service)下的兩種負載。簡單起見,本例沒有配置流量轉移,因此實驗預期的結果是:從hello1發出的請求,交替發向hello2 pod和hello2 app。本例的流量始於hello1 POD內部,因此無需為hello1配置service,同理沒有gateway等外部流量設施。

2-1-workload-demo.png

圖中有三種顏色的線,橙色是ASM istioD和ASM sidecar之間的xDS通信(用途包括同步流量配置給各POD中的envoy等),黑色是物理鏈路,綠色虛線是邏輯鏈路。物理鏈路的視角是POD和VM,對應的邏輯視角是serviceentry、service和workloadentry。

示例(http_workload_demo)包含如下元素:

  • hello1 deployment(鏡像http_springboot_v1)
  • hello2 deployment(鏡像http_springboot_v2)
  • hello2 docker container(鏡像http_springboot_v1)
  • hello2 service
  • hello2 serviceentry
  • hello2 workloadentry

實驗特製鏡像

鏡像http_springboot-{version}是一個基於springboot開發的http服務(源代碼在這裡):

  • version不同返回的結果信息不同。這樣設計的目的是在流量轉移實驗中展示不同路由的請求結果

    • v1返回Hello {input}
    • v2返回Bonjour {input}
    • v3返回Hola {input}
  • 環境變量HTTP_HELLO_BACKEND用於告訴當前服務是否有下游服務。這樣設計的目的是在不借助其他組件的情況下,極簡地展示鏈路信息;另外可以靈活地無限擴展鏈路長度,以演示和驗證各種流量管理場景下的解決方案。

    • 如果沒有下游,直接返回類似這樣的信息:Hello eric(192.168.0.170)
    • 如果存在下游,則請求下游並將結果合併,返回信息類似這樣:Hello eric(172.18.0.216)<-...<-Bonjour eric(172.18.1.97)

1.1 Setup

hello1 deployment示意如下,env定義了下游服務hello2-svc.pod-vm-hello.svc.cluster.local

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: pod-vm-hello
  name: hello1-deploy
...
    spec:
      containers:
        - name: hello-v1-deploy
          image: registry.cn-beijing.aliyuncs.com/asm_repo/http_springboot_v1:1.0.1
          env:
            - name: HTTP_HELLO_BACKEND
              value: "hello2-svc.pod-vm-hello.svc.cluster.local"
          ports:
            - containerPort: 8001

hello2 deploymenthello2 service示意如下,其中鏡像使用了v2版本,以便區分vm中的hello2 app的返回結果。

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: pod-vm-hello
  name: hello2-deploy-v2
...
    spec:
      containers:
        - name: hello-v1-deploy
          image: registry.cn-beijing.aliyuncs.com/asm_repo/http_springboot_v2:1.0.1
          ports:
            - containerPort: 8001
---
apiVersion: v1
kind: Service
metadata:
  namespace: pod-vm-hello
  name: hello2-svc
  labels:
    app: hello2-svc
spec:
  ports:
    - port: 8001
      name: http
  selector:
    app: hello2-deploy

hello2 serviceentry和hello2 workloadentry示意如下,ServiceEntry通過workloadSelector中定義的app: hello2-deploy找到相應的pod和workload entry。WorkloadEntry與VM(ecs實例)一一對應,address中定義了ecs的ip。

apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: mesh-expansion-hello2-svc
  namespace: pod-vm-hello
spec:
  hosts:
  - hello2-svc.pod-vm-hello.svc.cluster.local
  location: MESH_INTERNAL
  ports:
  - name: http
    number: 8001
    protocol: HTTP
  resolution: STATIC
  workloadSelector:
    labels:
      app: hello2-deploy
---
apiVersion: networking.istio.io/v1alpha3
kind: WorkloadEntry
metadata:
  name: vm1
  namespace: pod-vm-hello
spec:
  address: 192.168.0.170
  labels:
    app: hello2-deploy
    class: vm
    version: v1

ecs中的hello2 app使用如下命令啟動。啟用--network host的目的是在hello2返回信息時使用ecs的ip。也可以不啟用,這時返回的是docker container的ip,以便通過端口映射實現,在一個ecs示例中啟動多個http_springboot實例。

docker run \
--rm \
--network host \
--name http_v1 \
registry.cn-beijing.aliyuncs.com/asm_repo/http_springboot_v1:1.0.1

完整的setup腳本參見setup-pod-vm.sh,不再冗述。示例實驗環境搭建好後,我們進入驗證環節。

1.2 流量驗證

本例的流量始於hello1 POD內部,因此我們的驗證環境是POD$hello1_pod中的hello-v1-deploy容器。

驗證過程如下方腳本所示:

  • 獲取並進入驗證環境所在的容器
  • 在這個容器中分別向自身和hello2-svc.pod-vm-hello.svc.cluster.local發起請求
alias k="kubectl --kubeconfig $USER_CONFIG"

hello1_pod=$(k get pod -l app=hello1-deploy -n pod-vm-hello -o jsonpath={.items..metadata.name})

for i in {1..5}; do
  echo ">>> test hello1 -> hello2"
  k exec "$hello1_pod" -c hello-v1-deploy -n pod-vm-hello -- curl -s localhost:8001/hello/eric
  echo
  echo ">>> test hello2 directly"
  k exec "$hello1_pod" -c hello-v1-deploy -n pod-vm-hello -- curl -s hello2-svc.pod-vm-hello.svc.cluster.local:8001/hello/eric
  echo
done

我們期待的結果是,流量交替發向hello2 pod和hello2 app:

▶ sh sh/pod-vm-test.sh

>>> test hello1 -> hello2
Hello eric(172.18.0.216)<-Hello eric(192.168.0.170)
>>> test hello2 directly
Hello eric(192.168.0.170)

>>> test hello1 -> hello2
Hello eric(172.18.0.216)<-Bonjour eric(172.18.1.97)
>>> test hello2 directly
Bonjour eric(172.18.1.97)
...

到此,示例的驗證完畢。通過這個示例,我們看到WorkloadEntry的引入,使vm中的非kubernetes容器應用可以非常容易地加入服務網格中,與POD成為同級別的負載。

在此基礎上,我們很容易想到可以藉助服務網格的流量轉移,將ecs上的應用無損地遷移到POD中。接下來,我們繼續基於這個示例來演示流量的切換。

2 從VM遷移到POD

首先我們在示例驗證結果的基礎上,刪除hello2 pod,讓從hello1發出的流量全部進入ecs中的hello2 app。這是模擬遺留應用網格化的初始狀態。如下方左圖所示。

接下來,我們把hello2 pod加入網格,即前序示例的狀態。這是模擬遺留應用網格化的中間過程

最後,我們刪除hello2 workloadentry,此時從hello1發出的流量將全部進入ack中的hello2 pod。這是模擬遺留應用完成網格化的終態。如下方右圖所示。

2-2-workload-demo.png

2.1 流量驗證

驗證過程如下方腳本所示:

alias k="kubectl --kubeconfig $USER_CONFIG"
alias m="kubectl --kubeconfig $MESH_CONFIG"

verify_in_loop(){
  for i in {1..5}; do
  echo ">>> test hello1 -> hello2"
  k exec "$hello1_pod" -c hello-v1-deploy -n pod-vm-hello -- curl -s localhost:8001/hello/eric
  echo
  echo ">>> test hello2 directly"
  k exec "$hello1_pod" -c hello-v1-deploy -n pod-vm-hello -- curl -s hello2-svc.pod-vm-hello.svc.cluster.local:8001/hello/eric
  echo
done
}

hello1_pod=$(k get pod -l app=hello1-deploy -n pod-vm-hello -o jsonpath={.items..metadata.name})

echo "1 delete pod and test serviceentry only routes to workloadentry:"
k delete -f yaml/hello2-deploy-v2.yaml
verify_in_loop

echo "2 add pod and test serviceentry routes to pod and workloadentry:"
k apply -f yaml/hello2-deploy-v2.yaml
verify_in_loop

echo "3 delete workloadentry and test serviceentry only routes to pod:"
m delete workloadentry vm1 -n pod-vm-hello
verify_in_loop

驗證結果如下所示:

  • 第一步刪除pod後,流量全部進入ecs中的hello2 app
  • 第二步加入pod後,流量交替發送到ack中的hello2 pod和ecs中的hello2 app
  • 第三步刪除workloadentry後,流量全部進入ack中的hello2 pod
1 delete pod and test serviceentry only routes to workloadentry:d_demo/sh/migrate-test.sh
deployment.apps "hello2-deploy-v2" deleted

>>> test hello1 -> hello2
Hello eric(172.18.0.216)<-Hello eric(192.168.0.170)
>>> test hello2 directly
Hello eric(192.168.0.170)

>>> test hello1 -> hello2
Hello eric(172.18.0.216)<-Hello eric(192.168.0.170)
>>> test hello2 directly
Hello eric(192.168.0.170)
...

2 add pod and test serviceentry routes to pod and workloadentry:
deployment.apps/hello2-deploy-v2 created

>>> test hello1 -> hello2
Hello eric(172.18.0.216)<-Hello eric(192.168.0.170)
>>> test hello2 directly
Hello eric(192.168.0.170)

>>> test hello1 -> hello2
Hello eric(172.18.0.216)<-Bonjour eric(172.18.1.98)
>>> test hello2 directly
Bonjour eric(172.18.1.98)
...

3 delete workloadentry and test serviceentry only routes to pod:
workloadentry.networking.istio.io "vm1" deleted

>>> test hello1 -> hello2
Hello eric(172.18.0.216)<-Bonjour eric(172.18.1.98)
>>> test hello2 directly
Bonjour eric(172.18.1.98)

>>> test hello1 -> hello2
Hello eric(172.18.0.216)<-Bonjour eric(172.18.1.98)
>>> test hello2 directly
Bonjour eric(172.18.1.98)
...

到此,從VM遷移到POD驗證完畢。因為本例的核心是展示從VM遷移到POD的可能性,只演示了遷移過程的3個邊界狀態,沒有展示通過virtualservice進行平滑切流,從而實現流量無損的遷移。接下來的流量管理示例,virtualservice將作為一號位閃亮登場。

3 VM流量管理

通過前兩節的展示我們看到:使用``Workload Entry可以方便地將非容器應用加入服務網格,並與POD同等待遇;此外,可以最終使用POD替代全部非容器應用。但是,遷移與否以及遷移的節奏依賴於諸多因素。在沒有啟程遷移時,依然可以通過Workload Entry讓非容器應用享受到一定的網格化能力。接下來的示例將展示流量遷移。

示例的拓撲如下圖所示,核心目標是將ecs中的hello2 app的多個實例視為同一個服務下的負載,通過virtualservice配置實現不同實例的流量配比。這個實驗將完整地展示入口網關和流量轉移,流量從本地向公網發起,經入口網關到hello1,再由hello1向ecs中的三個hellp2 app發起請求,流量配比為:30%:60%:10%。

2-3-workload-traffic-demo.png

示例(http_workload_traffic_demo)包含如下元素:

  • hello1 deployment(鏡像http_springboot_v1)
  • hello2 docker containers(鏡像http_springboot_v1/http_springboot_v2/http_springboot_v3)
  • 入口網關:istio-ingressgateway
  • 入口流量配置:gateway/virtualservice
  • hello1流量配置:hello1 service/hello1 virtualservice/hello1 destinationrule
  • hello2流量配置:hello2 service/hello2 virtualservice/hello2 destinationrule
  • hello2 serviceentry/hello2 workloadentry

3.1 入口流量配置

首先登錄ASM管控臺,配置入口網關服務,增加8002端口如下所示:

apiVersion: istio.alibabacloud.com/v1beta1
kind: IstioGateway
metadata:
  name: ingressgateway
  namespace: istio-system
spec:
  ports:
  ...
    - name: http-workload
      port: 8002
      targetPort: 8002

2-4-istio-ingressgateway.png

為入口網關配置入口流量示意如下。名稱為external-hello-gateway的Gateway將spec.selector.istio定義為入口網關的名稱ingressgateway,名稱為gateway-vs的VirtualService將spec.gateways定義為external-hello-gateway。將請求到8002端口的全部流量轉移到hello1-svc

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  namespace: external-hello
  name: external-hello-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 8002
        name: http
        protocol: HTTP
      hosts:
        - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  namespace: external-hello
  name: gateway-vs
spec:
  hosts:
    - "*"
  gateways:
    - external-hello-gateway
  http:
    - match:
        - port: 8002
      route:
        - destination:
            host: hello1-svc

hello1-svc將流量轉移到8001,本例中的負載(POD/VM)均通過8001對外提供服務。

spec:
  ports:
    - name: http
      port: 8002
      targetPort: 8001
      protocol: TCP

3.2 hello2流量轉移配置

接下來,我們重點關注hello2的流量配置。在hello2 destinationrule中定義了hello2的3個subsets,每個subset通過labels與對應的負載關聯。在hello2 virtualservice中定義了請求路徑規則:路徑前綴為/hello的請求,會按照3:6:1的比例轉移到v1/v2/v3對應的負載端點上。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  namespace: external-hello
  name: hello2-dr
spec:
  host: hello2-svc
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2
    - name: v3
      labels:
        version: v3
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  namespace: external-hello
  name: hello2-vs
spec:
  hosts:
    - hello2-svc
  http:
    - name: http-hello-route
      match:
        - uri:
            prefix: /hello
      route:
        - destination:
            host: hello2-svc
            subset: v1
          weight: 30
        - destination:
            host: hello2-svc
            subset: v2
          weight: 60
        - destination:
            host: hello2-svc
            subset: v3
          weight: 10

我們通過執行aliyun cli命令,為ASM實例增加hello2 service/hello2 serviceentry/hello2 workloadentry。命令如下:

aliyun servicemesh AddVmAppToMesh \
  --ServiceMeshId $MESH_ID \
  --Namespace external-hello \
  --ServiceName hello2-svc \
  --Ips "$VM_PRI_1","$VM_PRI_2","$VM_PRI_3" \
  --Ports http:8001 \
  --Labels app=hello-workload
  • servicemesh:是ASM集成到aliyun cli的子命令
  • ServiceMeshId參數用來指定ASM實例ID
  • Namespace參數用來指定命名空間
  • ServiceName參數用來指定服務名稱
  • Ips參數用來指定VM的IP,本例使用3個ecs節點
  • Ports參數用來指定服務端口,使用冒號分割,冒號前為協議名稱,冒號後為服務端口號
  • Labels參數用來指定WorkloadEntry的labels和ServiceEntry的spec.workloadSelector.labels,這個參數值是兩者關聯的橋樑。

詳見服務網格 ASM官方文檔

創建完畢後可以通過如下命令查詢ASM實例下的VM網格化信息:

aliyun servicemesh GetVmAppMeshInfo --ServiceMeshId $MESH_ID

自動生成的WorkloadEntry默認是沒有version標籤的,為了展示流量轉移,我們需要分別為3個WorkloadEntry增加與上述DestinationRule中配置相應的標籤信息。

登錄ASM管控臺,在控制平面中找到指定的WorkloadEntry並進行編輯。

2-5-asm-workloadentry.png

示意如下:

spec:
  address: 192.168.0.170
  labels:
    app: hello-workload
    version: v1

最後,我們在3個ecs節點上分別啟動hello2.示意如下:

# vm/ssh1.sh
docker run \
--rm \
--network host \
--name http_v1 \
registry.cn-beijing.aliyuncs.com/asm_repo/http_springboot_v1:1.0.1
# vm/ssh2.sh
docker run \
--rm \
--network host \
--name http_v2 \
registry.cn-beijing.aliyuncs.com/asm_repo/http_springboot_v2:1.0.1
# vm/ssh3.sh
docker run \
--rm \
--network host \
--name http_v3 \
registry.cn-beijing.aliyuncs.com/asm_repo/http_springboot_v3:1.0.1

3.3 流量驗證

實驗環境搭建完畢後,通過驗證腳本test_traffic_shift.sh進行驗證:

  • 獲取入口網關IP
  • 循環調用8002端口並根據響應信息統計路由
IP=$(k -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

for i in {1..100}; do
  resp=$(curl -s "$IP":8002/hello/eric)
  echo "$resp" >>test_traffic_shift_result
done

echo "expected 30%(Hello eric)-60%(Bonjour eric)-10%(Hola eric):"
sort test_traffic_shift_result | grep -v "^[[:space:]]*$"| uniq -c | sort -nrk1

驗證結果如下:

Test route n a loop
expected 30%(Hello eric)-60%(Bonjour eric)-10%(Hola eric):
  61 Hello eric(172.18.1.99)<-Bonjour eric(192.168.0.171)
  31 Hello eric(172.18.1.99)<-Hello eric(192.168.0.170)
   8 Hello eric(172.18.1.99)<-Hola eric(192.168.0.172)

到此,VM流量管理驗證完畢。我們看到,通過配置WorkloadEntry將VM加入到網格中,然後再為其增加與POD配置方式一致的DestinationRuleVirtualService,即可實現服務網格對VM上應用流量轉移的配置管理。

4 尚未網格化

本篇的示例展示了使用WorkloadEntry將虛擬機/裸金屬上的應用加入服務網格的過程,目的是以WorkloadEntry為主視角,展示WorkloadEntry的作用和使用方法。

WorkloadEntry的引入解決了從網格內的POD向VM中的應用請求的流量管理。但是反方向的請求單靠WorkloadEntry是不能解決的,因為VM中的應用無法找到網格內的POD。到目前為止,我們的VM還沒有真正意義地實現網格化,只有完全實現網格化,VM內才能為應用提供sidecar,進而通過POD對應的service,將VM應用的請求路由到POD。

接下來的兩篇將分別以http/grpc協議來展示真正意義的網格化VM與POD之間的相互通信。

Leave a Reply

Your email address will not be published. Required fields are marked *