天天看點

非容器應用與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等外部流量設施。

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

圖中有三種顔色的線,橙色是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 deployment

示意如下,其中鏡像使用了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。這是模拟遺留應用完成網格化的終态。如下方右圖所示。

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

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%。

非容器應用與K8s工作負載的服務網格化實踐-2 基于ASM的Workload Entry實踐
示例(http_workload_traffic_demo)
  • 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           
非容器應用與K8s工作負載的服務網格化實踐-2 基于ASM的Workload Entry實踐

為入口網關配置入口流量示意如下。名稱為

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中配置相應的标簽資訊。

登入

,在控制平面中找到指定的WorkloadEntry并進行編輯。

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

示意如下:

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配置方式一緻的

DestinationRule

VirtualService

,即可實作服務網格對VM上應用流量轉移的配置管理。

4 尚未網格化

本篇的示例展示了使用

WorkloadEntry

将虛拟機/裸金屬上的應用加入服務網格的過程,目的是以

WorkloadEntry

為主視角,展示

WorkloadEntry

的作用和使用方法。

WorkloadEntry

的引入解決了從網格内的POD向VM中的應用請求的流量管理。但是反方向的請求單靠

WorkloadEntry

是不能解決的,因為VM中的應用無法找到網格内的POD。到目前為止,我們的VM還沒有真正意義地實作網格化,隻有完全實作網格化,VM内才能為應用提供sidecar,進而通過POD對應的service,将VM應用的請求路由到POD。

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