非容器应用与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 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。这是模拟遗留应用完成网格化的终态。如下方右图所示。

非容器应用与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)包含如下元素:

  • 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

非容器应用与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中配置相应的标签信息。

登录ASM管控台,在控制平面中找到指定的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配置方式一致的DestinationRuleVirtualService,即可实现服务网格对VM上应用流量转移的配置管理。

4 尚未网格化

本篇的示例展示了使用WorkloadEntry将虚拟机/裸金属上的应用加入服务网格的过程,目的是以WorkloadEntry为主视角,展示WorkloadEntry的作用和使用方法。

WorkloadEntry的引入解决了从网格内的POD向VM中的应用请求的流量管理。但是反方向的请求单靠WorkloadEntry是不能解决的,因为VM中的应用无法找到网格内的POD。到目前为止,我们的VM还没有真正意义地实现网格化,只有完全实现网格化,VM内才能为应用提供sidecar,进而通过POD对应的service,将VM应用的请求路由到POD。

接下来的两篇将分别以http/grpc协议来展示真正意义的网格化VM与POD之间的相互通信。

上一篇:基于WASM的无侵入式全链路A/B Test实践


下一篇:《互联网时代的软件革命-SaaS架构设计》封面草稿