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之间的相互通信。