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等外部流量設施。
圖中有三種顏色的線,橙色是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}
。
- v1返回
-
環境變量
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和hello2 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.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%。
示例(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
為入口網關配置入口流量示意如下。名稱為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實例下的VM網格化信息:
aliyun servicemesh GetVmAppMeshInfo --ServiceMeshId $MESH_ID
自動生成的WorkloadEntry默認是沒有version標籤的,為了展示流量轉移,我們需要分別為3個WorkloadEntry增加與上述DestinationRule中配置相應的標籤信息。
登錄ASM管控臺,在控制平面中找到指定的WorkloadEntry並進行編輯。
示意如下:
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之間的相互通信。