将Dubbo应用部署到服务网格中

简介: 本文主要就Dubbo应用如何接入服务网格、获得各项云原生能力进行了探讨,并提出了最佳实践以及过渡两种实践场景。我们首先推荐您使用Dubbo社区提供的最佳实践场景来接入服务网格,在必要时可以通过过渡方案来向最佳实践方案逐步实现过渡。

服务网格是云原生时代下新一代的应用感知的云原生基础设施,能够为负责的应用提供标准、通用、无侵入的流量管理、可观测、安全等能力。Dubbo是一款易用高性能的微服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提供了 Java、Golang 等多语言 SDK 实现。

通过接入服务网格,Dubbo应用将能够无缝获得其带来的多项云原生能力,包括应用mTLS通信加密、统一的云原生可观测能力(Prometheus、日志、网格拓扑)、无侵入流量管理能力等。本文以阿里云服务网格ASM为基础,主要介绍使用Dubbo框架开发的微服务应用可以用怎样的姿势接入服务网格,实现微服务体系针对云原生时代基础设施的无缝接入。

1. 前提条件

1.1 服务网格环境准备

首先在阿里云上准备ACK集群以及ASM实例,作为应用的部署环境

1.2 Dubbo应用前提

如果可能,强烈建议您的应用升级到使用Dubbo3,并通过Triple协议进行通信。Dubbo3针对云原生部署方式进行了更完善的适配,并提供了Triple协议作为通信规范。Triple 协议完全兼容 gRPC 协议,支持 Request-Response、Streaming 流式等通信模型,可同时运行在 HTTP/1 和 HTTP/2 之上。通过使用Triple协议,Dubbo应用间通信的七层信息可被服务网格识别,带来更加完善的云原生能力。

在使用Dubbo2以及dubbo协议通信的情况下,服务网格只能拦截TCP4层流量,能够使用的服务网格能力极其有限。

2. 最佳实践场景:使用社区Dubbo Mesh接入服务网格ASM

Dubbo社区已经提供了接入服务网格的标准方案,一般情况下,都建议您参照此场景来实现您的Dubbo应用、通过云原生的方式实现服务相互调用,以无缝接入服务网格。

主要参考:https://github.com/apache/dubbo-samples/tree/master/3-extensions/registry/dubbo-samples-mesh-k8s

2. 步骤1:将Dubbo应用部署到ACK集群

使用ACK集群提供的kubeconfig连接到集群,并执行以下指令部署应用

kubectl apply -f- <<EOF
apiVersion: v1
kind: Service
metadata:
  name: dubbo-samples-mesh-provider
  namespace: dubbo-demo
spec:
  type: ClusterIP
  sessionAffinity: None
  selector:
    app: dubbo-samples-mesh-provider
  ports:
    - name: grpc-tri
      port: 50052
      targetPort: 50052
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dubbo-samples-mesh-provider-v1
  namespace: dubbo-demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: dubbo-samples-mesh-provider
      version: v1
  template:
    metadata:
      labels:
        app: dubbo-samples-mesh-provider
        version: v1
      annotations:
        # Prevent istio rewrite http probe
        sidecar.istio.io/rewriteAppHTTPProbers: "false"
    spec:
      containers:
        - name: server
          image: apache/dubbo-demo:dubbo-samples-mesh-provider-v1_0.0.1
          imagePullPolicy: Always
          ports:
            - name: grpc-tri
              containerPort: 50052
              protocol: TCP
            - name: http-health
              containerPort: 22222
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /live
              port: http-health
            initialDelaySeconds: 10
            periodSeconds: 5
            timeoutSeconds: 1
          readinessProbe:
            httpGet:
              path: /ready
              port: http-health
            initialDelaySeconds: 5
            periodSeconds: 5
            timeoutSeconds: 2
          startupProbe:
            httpGet:
              path: /startup
              port: http-health
            failureThreshold: 30
            initialDelaySeconds: 10
            periodSeconds: 5
            timeoutSeconds: 2
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dubbo-samples-mesh-provider-v2
  namespace: dubbo-demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: dubbo-samples-mesh-provider
      version: v2
  template:
    metadata:
      labels:
        app: dubbo-samples-mesh-provider
        version: v2
      annotations:
        # Prevent istio rewrite http probe
        sidecar.istio.io/rewriteAppHTTPProbers: "false"
    spec:
      containers:
        - name: server
          image: apache/dubbo-demo:dubbo-samples-mesh-provider-v2_0.0.1
          imagePullPolicy: Always
          ports:
            - name: grpc-tri
              containerPort: 50052
              protocol: TCP
            - name: http-health
              containerPort: 22222
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /live
              port: http-health
            initialDelaySeconds: 10
            periodSeconds: 5
            timeoutSeconds: 1
          readinessProbe:
            httpGet:
              path: /ready
              port: http-health
            initialDelaySeconds: 5
            periodSeconds: 5
            timeoutSeconds: 2
          startupProbe:
            httpGet:
              path: /startup
              port: http-health
            failureThreshold: 30
            initialDelaySeconds: 10
            periodSeconds: 5
            timeoutSeconds: 2
---
apiVersion: v1
kind: Service
metadata:
  name: dubbo-samples-mesh-consumer
  namespace: dubbo-demo
spec:
  type: ClusterIP
  sessionAffinity: None
  selector:
    app: dubbo-samples-mesh-consumer
  ports:
    - name: grpc-dubbo
      protocol: TCP
      port: 50052
      targetPort: 50052
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dubbo-samples-mesh-consumer
  namespace: dubbo-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: dubbo-samples-mesh-consumer
      version: v1
  template:
    metadata:
      labels:
        app: dubbo-samples-mesh-consumer
        version: v1
      annotations:
        # Prevent istio rewrite http probe
        sidecar.istio.io/rewriteAppHTTPProbers: "false"
    spec:
      containers:
        - name: server
          image: apache/dubbo-demo:dubbo-samples-mesh-consumer_0.0.1
          imagePullPolicy: Always
          ports:
            - name: grpc-tri
              containerPort: 50052
              protocol: TCP
            - name: http-health
              containerPort: 22222
              protocol: TCP
          env:
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            # This environment variable does not need to be configured by default. When the domain name suffix used inside k8s is artificially changed, it is only necessary to configure this
            #- name: CLUSTER_DOMAIN
            #  value: cluster.local
          livenessProbe:
            httpGet:
              path: /live
              port: http-health
            initialDelaySeconds: 5
            periodSeconds: 5
          readinessProbe:
            httpGet:
              path: /ready
              port: http-health
            initialDelaySeconds: 5
            periodSeconds: 5
          startupProbe:
            httpGet:
              path: /startup
              port: http-health
            failureThreshold: 30
            initialDelaySeconds: 5
            periodSeconds: 5
            timeoutSeconds: 2
EOF

所部署的应用包含一个consumer和一个provider、由consumer向provider发起调用。

服务代码可以直接查看上文参考中的Dubbo官方仓库,根据官方描述,Dubbo应用的配置中包含几个要点 :

  1. 保持 K8S Service 与 Dubbo application name 一致(在 dubbo.properties中定义)。
  2. 不使用nacos、zookeeper等注册中心,将dubbo.registry.address填写为N/A(在 dubbo.properties中定义)。
  3. Dubbo Consumer 服务声明中指定了消费的 Provider 服务(应用)名 @DubboReference(version = "1.0.0", providedBy = "dubbo-samples-mesh-provider", lazy = true)

有关该示例的配置文件,可详细参考dubbo.properties

2.2 步骤2:检查consumer、provider已经通过服务网格正常通信

继续通过kubectl查看consumer端 istio-proxy日志,可以看到发送给provider的请求已经在istio-proxy中留下访问日志。

kubectl -n dubbo-demo logs deploy/dubbo-samples-mesh-consumer -c istio-proxy|grep outbound
...
{"authority_for":"dubbo-samples-mesh-provider.dubbo-demo.svc.cluster.local:50052","bytes_received":"140","bytes_sent":"161","downstream_local_address":"192.168.4.76:50052","downstream_remote_address":"10.0.239.171:56276","duration":"15","istio_policy_status":"-","method":"POST","path":"/org.apache.dubbo.samples.Greeter/greetStream","protocol":"HTTP/2","request_id":"b613c0f5-1834-45d8-9857-8547d8899a02","requested_server_name":"-","response_code":"200","response_flags":"-","route_name":"default","start_time":"2024-07-15T06:56:01.457Z","trace_id":"-","upstream_cluster":"outbound|50052||dubbo-samples-mesh-provider.dubbo-demo.svc.cluster.local","upstream_host":"10.0.239.196:50052","upstream_local_address":"10.0.239.171:41202","upstream_response_time":"6","upstream_service_time":"6","upstream_transport_failure_reason":"-","user_agent":"-","x_forwarded_for":"-"}

注意到访问日志中upstream_cluster字段已经是outbound|50052||dubbo-samples-mesh-provider.dubbo-demo.svc.cluster.local,证明provider已经被服务网格正确注册、且请求已经被服务网格拦截并进行路由。

2.3 步骤3:部署虚拟服务、目标规则,查看服务网格路由管理

使用kubectl连接到ASM实例(参考通过控制面kubectl访问Istio资源),并通过kubectl执行以下指令。

kubectl apply -f- <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: dubbo-samples-mesh-provider
  namespace: dubbo-demo
spec:
  hosts:
    - dubbo-samples-mesh-provider.dubbo-demo.svc.cluster.local
  http:
    - route:
        - destination:
            host: dubbo-samples-mesh-provider.dubbo-demo.svc.cluster.local
            subset: v1
            port:
              # Specifies the port on the host being addressed. If the service exposes only one port, you don't need to choose the port explicitly
              number: 50052
          weight: 80
        - destination:
            host: dubbo-samples-mesh-provider.dubbo-demo.svc.cluster.local
            subset: v2
            port:
              # Specifies the port on the host being addressed. If the service exposes only one port, you don't need to choose the port explicitly
              number: 50052
          weight: 20
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: dubbo-samples-mesh-provider
  namespace: dubbo-demo
spec:
  host: dubbo-samples-mesh-provider.dubbo-demo.svc.cluster.local
  trafficPolicy:
    loadBalancer:
      # Envoy load balancing strategy
      simple: ROUND_ROBIN
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2
EOF

上述的目标规则通过pod标签将provider划分为v1、v2两个版本,虚拟服务中则指定了发往v1、v2版本的流量比例约为80:20。

此时重新部署consumer(可以删除原先的consumer pod、等待新pod拉起),并在一段时间后查看新的consumer日志,可以观察到发往provider的流量比例已经发生变化。

kubectl -n dubbo-demo logs deploy/dubbo-samples-mesh-consumer
...
==================== dubbo unary invoke 0 end ====================
[15/07/24 07:06:54:054 UTC] main  INFO action.GreetingServiceConsumer: consumer Unary reply <-message: "hello,service mesh, response from provider-v2: 10.0.239.196:50052, client: 10.0.239.196, local: dubbo-samples-mesh-provider, remote: null, isProviderSide: true"
==================== dubbo unary invoke 1 end ====================
[15/07/24 07:06:59:059 UTC] main  INFO action.GreetingServiceConsumer: consumer Unary reply <-message: "hello,service mesh, response from provider-v1: 10.0.239.189:50052, client: 10.0.239.189, local: dubbo-samples-mesh-provider, remote: null, isProviderSide: true"
==================== dubbo unary invoke 2 end ====================
[15/07/24 07:07:04:004 UTC] main  INFO action.GreetingServiceConsumer: consumer Unary reply <-message: "hello,service mesh, response from provider-v1: 10.0.239.189:50052, client: 10.0.239.189, local: dubbo-samples-mesh-provider, remote: null, isProviderSide: true"
==================== dubbo unary invoke 3 end ====================
[15/07/24 07:07:09:009 UTC] main  INFO action.GreetingServiceConsumer: consumer Unary reply <-message: "hello,service mesh, response from provider-v1: 10.0.239.175:50052, client: 10.0.239.175, local: dubbo-samples-mesh-provider, remote: null, isProviderSide: true"
==================== dubbo unary invoke 4 end ====================
[15/07/24 07:07:14:014 UTC] main  INFO action.GreetingServiceConsumer: consumer Unary reply <-message: "hello,service mesh, response from provider-v1: 10.0.239.175:50052, client: 10.0.239.175, local: dubbo-samples-mesh-provider, remote: null, isProviderSide: true"
==================== dubbo unary invoke 5 end ====================
[15/07/24 07:07:19:019 UTC] main  INFO action.GreetingServiceConsumer: consumer Unary reply <-message: "hello,service mesh, response from provider-v1: 10.0.239.189:50052, client: 10.0.239.189, local: dubbo-samples-mesh-provider, remote: null, isProviderSide: true"
...

可以看到,通过Dubbo官方提供的使用方式,Dubbo服务之间可以直接以云原生的k8s Service域名来相互调用,此时使用服务网格的各项能力都没有问题,Dubbo服务和普通的HTTP服务是没有区别的。

3. 过渡场景:在已有注册中心的情况下,通过插件使用服务网格

虽然上述最佳实践场景中实现了Dubbo和云原生基础设施的无缝结合,但是该场景要求应用不使用注册中心,对于一些已经接入注册中心并正在运行的Dubbo应用来说,可能无法立刻切换到这种方式上来。

在这种情况下,服务网格ASM在插件中心中提供了相关的支持插件,让应用能够先接入服务网格、然后过渡到社区最佳实践的云原生方案上。

3.1 步骤1:部署示例服务到Kubernetes集群

通过ACK集群的kubeconfig连接到集群、使用kubectl执行以下指令来部署示例应用。

apiVersion: v1
kind: ConfigMap
metadata:
  name: frontend-config
  namespace: dubbo-demo
data:
  application.properties: |-
    # Specify the application name of Dubbo
    dubbo.application.name=shop-frontend
    # Specify the QoS port
    dubbo.application.qos-port=20991
    # Enable token verification for each invocation
    dubbo.provider.token=false
    # Specify the registry address
    # dubbo.registry.address=nacos://localhost:8848?username=nacos&password=nacos
    dubbo.registry.address=nacos://${nacos.address:localhost}:8848?username=nacos&password=nacos
    # Specify the port of Dubbo protocol
    dubbo.protocol.port=20881
    dubbo.protocol.name=tri
    dubbo.protocol.serialization=hessian2
    #spring.freemarker.template-loader-path: /templates
    spring.freemarker.suffix=.ftl
    server.servlet.encoding.force=true
    server.servlet.encoding.charset=utf-8
    server.servlet.encoding.enabled=true
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: shop-order-v1-config
  namespace: dubbo-demo
data:
  application.properties: |-
    # Specify the application name of Dubbo
    dubbo.application.name=shop-order
    # Specify the QoS port
    dubbo.application.qos-port=20992
    # Enable token verification for each invocation
    dubbo.provider.token=false
    # Specify the registry address
    # dubbo.registry.address=nacos://localhost:8848?username=nacos&password=nacos
    dubbo.registry.address=nacos://${nacos.address:localhost}:8848?username=nacos&password=nacos
    # Specify the port of Dubbo protocol
    dubbo.protocol.port=20882
    dubbo.protocol.name=tri
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: shop-order-v2-config
  namespace: dubbo-demo
data:
  application.properties: |-
    # Specify the application name of Dubbo
    dubbo.application.name=shop-order
    # Specify the QoS port
    dubbo.application.qos-port=20993
    # Enable token verification for each invocation
    dubbo.provider.token=false
    # Specify the registry address
    # dubbo.registry.address=nacos://localhost:8848?username=nacos&password=nacos
    dubbo.registry.address=nacos://${nacos.address:localhost}:8848?username=nacos&password=nacos
    # Specify the port of Dubbo protocol
    dubbo.protocol.port=20882
    dubbo.protocol.name=tri
    dubbo.protocol.serialization=hessian2
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: shop-user-config
  namespace: dubbo-demo
data:
  application.properties: |-
    # Specify the application name of Dubbo
    dubbo.application.name=shop-user
    # Specify the QoS port
    dubbo.application.qos-port=20994
    # Enable token verification for each invocation
    dubbo.provider.token=false
    # Specify the registry address
    # dubbo.registry.address=nacos://localhost:8848?username=nacos&password=nacos
    dubbo.registry.address=nacos://${nacos.address:localhost}:8848?username=nacos&password=nacos
    # Specify the port of Dubbo protocol
    dubbo.protocol.port=20884
    dubbo.protocol.name=tri
    dubbo.protocol.serialization=hessian2
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: shop-detail-v1-config
  namespace: dubbo-demo
data:
  application.properties: |-
    # Specify the application name of Dubbo
    dubbo.application.name=shop-detail
    # Specify the QoS port
    dubbo.application.qos-port=20995
    # Enable token verification for each invocation
    dubbo.provider.token=false
    # Specify the registry address
    # dubbo.registry.address=nacos://localhost:8848?username=nacos&password=nacos
    dubbo.registry.address=nacos://${nacos.address:localhost}:8848?username=nacos&password=nacos
    # Specify the port of Dubbo protocol
    dubbo.protocol.port=20885
    dubbo.protocol.name=tri
    dubbo.protocol.serialization=hessian2
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: shop-detail-v2-config
  namespace: dubbo-demo
data:
  application.properties: |-
    # Specify the application name of Dubbo
    dubbo.application.name=shop-detail
    # Specify the QoS port
    dubbo.application.qos-port=20996
    # Enable token verification for each invocation
    dubbo.provider.token=false
    # Specify the registry address
    # dubbo.registry.address=nacos://localhost:8848?username=nacos&password=nacos
    dubbo.registry.address=nacos://${nacos.address:localhost}:8848?username=nacos&password=nacos
    # Specify the port of Dubbo protocol
    dubbo.protocol.port=20885
    dubbo.protocol.name=tri
    dubbo.protocol.serialization=hessian2
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: comment-v1-config
  namespace: dubbo-demo
data:
  application.properties: |-
    dubbo.application.name=shop-comment
    # Specify the QoS port
    dubbo.application.qos-port=20997
    # Enable token verification for each invocation
    dubbo.provider.token=false
    # Specify the registry address
    # dubbo.registry.address=nacos://localhost:8848?username=nacos&password=nacos
    dubbo.registry.address=nacos://${nacos.address:localhost}:8848?username=nacos&password=nacos
    # Specify the port of Dubbo protocol
    dubbo.protocol.port=20887
    dubbo.protocol.name=tri
    dubbo.protocol.serialization=hessian2
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: comment-v2-config
  namespace: dubbo-demo
data:
  application.properties: |-
    # Specify the application name of Dubbo
    dubbo.application.name=shop-comment
    # Specify the QoS port
    dubbo.application.qos-port=20998
    # Enable token verification for each invocation
    dubbo.provider.token=false
    # Specify the registry address
    # dubbo.registry.address=nacos://localhost:8848?username=nacos&password=nacos
    dubbo.registry.address=nacos://${nacos.address:localhost}:8848?username=nacos&password=nacos
    # Specify the port of Dubbo protocol
    dubbo.protocol.port=20888
    dubbo.protocol.name=tri
    dubbo.protocol.serialization=hessian2
---
apiVersion: v1
kind: Namespace
metadata:
  name: dubbo-system
---
# Nacos
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nacos
  namespace: dubbo-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nacos
  template:
    metadata:
      labels:
        app: nacos
    spec:
      containers:
        - name: consumer
          image: nacos/nacos-server:v2.1.2
          imagePullPolicy: Always
          resources:
            requests:
              memory: "2Gi"
              cpu: "500m"
          ports:
            - containerPort: 8848
              name: client
            - containerPort: 9848
              name: client-rpc
          env:
            - name: NACOS_SERVER_PORT
              value: "8848"
            - name: NACOS_APPLICATION_PORT
              value: "8848"
            - name: PREFER_HOST_MODE
              value: "hostname"
            - name: MODE
              value: "standalone"
            - name: NACOS_AUTH_ENABLE
              value: "true"
---
apiVersion: v1
kind: Service
metadata:
  name: nacos
  namespace: dubbo-system
spec:
  type: ClusterIP
  sessionAffinity: None
  selector:
    app: nacos
  ports:
    - port: 8848
      name: server
      targetPort: 8848
    - port: 9848
      name: client-rpc
      targetPort: 9848
---
# Dubbo Admin
apiVersion: v1
kind: ConfigMap
metadata:
  name: dubbo-admin
  namespace: dubbo-system
data:
  # Set the properties you want to override, properties not set here will be using the default values
  # check application.properties inside dubbo-admin project for the keys supported
  application.properties: |
    admin.registry.address=nacos://nacos.dubbo-system.svc:8848?username=nacos&password=nacos
    admin.config-center=nacos://nacos.dubbo-system.svc:8848?username=nacos&password=nacos
    admin.metadata-report.address=nacos://nacos.dubbo-system.svc:8848?username=nacos&password=nacos
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dubbo-admin
  namespace: dubbo-system
  labels:
    app: dubbo-admin
spec:
  replicas: 2
  selector:
    matchLabels:
      app: dubbo-admin
  template:
    metadata:
      labels:
        app: dubbo-admin
    spec:
      containers:
        - image: apache/dubbo-admin:0.6.0
          name: dubbo-admin
          ports:
            - containerPort: 38080
          volumeMounts:
            - mountPath: /config
              name: application-properties
      volumes:
        - name: application-properties
          configMap:
            name: dubbo-admin
---
apiVersion: v1
kind: Service
metadata:
  name: dubbo-admin
  namespace: dubbo-system
spec:
  selector:
    app: dubbo-admin
  ports:
    - protocol: TCP
      port: 38080
      targetPort: 38080
---
# Skywalking
apiVersion: apps/v1
kind: Deployment
metadata:
  name: skywalking-oap-server
  namespace: dubbo-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: skywalking-oap-server
  template:
    metadata:
      labels:
        app: skywalking-oap-server
    spec:
      containers:
        - name: skywalking-oap-server
          image: apache/skywalking-oap-server:9.3.0
          imagePullPolicy: Always
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: skywalking-oap-dashboard
  namespace: dubbo-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: skywalking-oap-dashboard
  template:
    metadata:
      labels:
        app: skywalking-oap-dashboard
    spec:
      containers:
        - name: skywalking-oap-dashboard
          image: apache/skywalking-ui:9.3.0
          imagePullPolicy: Always
          env:
            - name: SW_OAP_ADDRESS
              value: http://skywalking-oap-server.dubbo-system.svc:12800
---
apiVersion: v1
kind: Service
metadata:
  name: skywalking-oap-server
  namespace: dubbo-system
spec:
  type: ClusterIP
  sessionAffinity: None
  selector:
    app: skywalking-oap-server
  ports:
    - port: 12800
      name: rest
      targetPort: 12800
    - port: 11800
      name: grpc
      targetPort: 11800
---
apiVersion: v1
kind: Service
metadata:
  name: skywalking-oap-dashboard
  namespace: dubbo-system
spec:
  type: ClusterIP
  sessionAffinity: None
  selector:
    app: skywalking-oap-dashboard
  ports:
    - port: 8080
      name: http
      targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: shop-frontend
  name: shop-frontend
  namespace: dubbo-demo
spec:
  ports:
    - name: http-8080
      port: 8080
      protocol: TCP
      targetPort: 8080
    - name: grpc-tri
      port: 20881
      protocol: TCP
      targetPort: 20881
  selector:
    app: shop-frontend
---
# App FrontEnd
apiVersion: apps/v1
kind: Deployment
metadata:
  name: shop-frontend
  namespace: dubbo-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: shop-frontend
  template:
    metadata:
      labels:
        app: shop-frontend
    spec:
      volumes:
        - name: skywalking-agent
          emptyDir: { }
        - name: config-volume
          configMap:
            name: frontend-config
            items:
            - key: application.properties
              path: application.properties
      initContainers:
        - name: agent-container
          image: apache/skywalking-java-agent:8.13.0-java17
          volumeMounts:
            - name: skywalking-agent
              mountPath: /agent
          command: [ "/bin/sh" ]
          args: [ "-c", "cp -R /skywalking/agent /agent/" ]
      containers:
        - name: shop-frontend
          image: apache/dubbo-demo:dubbo-samples-shop-frontend_0.0.1
          imagePullPolicy: Always
          ports:
            - name: http-8080
              containerPort: 8080
              protocol: TCP
            - name: grpc-tri
              containerPort: 20881
              protocol: TCP
            - name: dubbo-qos
              containerPort: 20991
              protocol: TCP
          volumeMounts:
            - name: skywalking-agent
              mountPath: /skywalking
            - name: config-volume
              mountPath: /app/resources/application.properties
              subPath: application.properties
          env:
            - name: JAVA_TOOL_OPTIONS
              value: "-javaagent:/skywalking/agent/skywalking-agent.jar"
            - name: SW_AGENT_NAME
              value: shop::shop-frontend
            - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES
              value: "skywalking-oap-server.dubbo-system.svc:11800"
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: shop-order
  name: shop-order
  namespace: dubbo-demo
spec:
  ports:
    - name: grpc-tri
      port: 20882
      protocol: TCP
      targetPort: 20882
  selector:
    app: shop-order
---
# App Order V1-1
apiVersion: apps/v1
kind: Deployment
metadata:
  name: shop-order-v1
  namespace: dubbo-demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: shop-order
      orderVersion: v1
  template:
    metadata:
      labels:
        app: shop-order
        orderVersion: v1
    spec:
      volumes:
        - name: skywalking-agent
          emptyDir: { }
        - name: config-volume
          configMap:
            name: shop-order-v1-config
            items:
            - key: application.properties
              path: application.properties
      initContainers:
        - name: agent-container
          image: apache/skywalking-java-agent:8.13.0-java17
          volumeMounts:
            - name: skywalking-agent
              mountPath: /agent
          command: [ "/bin/sh" ]
          args: [ "-c", "cp -R /skywalking/agent /agent/" ]
      containers:
        - name: shop-order
          image: apache/dubbo-demo:dubbo-samples-shop-order_v1_0.0.1
          imagePullPolicy: Always
          ports:
            - name: grpc-tri
              containerPort: 20882
              protocol: TCP
            - name: dubbo-qos
              containerPort: 20992
              protocol: TCP
          volumeMounts:
            - name: skywalking-agent
              mountPath: /skywalking
            - name: config-volume
              mountPath: /app/resources/application.properties
              subPath: application.properties
          env:
            - name: JAVA_TOOL_OPTIONS
              value: "-javaagent:/skywalking/agent/skywalking-agent.jar"
            - name: SW_AGENT_NAME
              value: shop::shop-order
            - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES
              value: "skywalking-oap-server.dubbo-system.svc:11800"
            - name: DUBBO_LABELS
              value: "orderVersion=v1"
---
# App Order V2
apiVersion: apps/v1
kind: Deployment
metadata:
  name: shop-order-v2
  namespace: dubbo-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: shop-order
      orderVersion: v2
  template:
    metadata:
      labels:
        app: shop-order
        orderVersion: v2
    spec:
      volumes:
        - name: skywalking-agent
          emptyDir: { }
        - name: config-volume
          configMap:
            name: shop-order-v2-config
            items:
            - key: application.properties
              path: application.properties
      initContainers:
        - name: agent-container
          image: apache/skywalking-java-agent:8.13.0-java17
          volumeMounts:
            - name: skywalking-agent
              mountPath: /agent
          command: [ "/bin/sh" ]
          args: [ "-c", "cp -R /skywalking/agent /agent/" ]
      containers:
        - name: shop-order
          image: apache/dubbo-demo:dubbo-samples-shop-order_v2_0.0.1
          imagePullPolicy: Always
          ports:
            - name: grpc-tri
              containerPort: 20882
              protocol: TCP
            - name: dubbo-qos
              containerPort: 20993
              protocol: TCP
          volumeMounts:
            - name: skywalking-agent
              mountPath: /skywalking
            - name: config-volume
              mountPath: /app/resources/application.properties
              subPath: application.properties
          env:
            - name: JAVA_TOOL_OPTIONS
              value: "-javaagent:/skywalking/agent/skywalking-agent.jar"
            - name: SW_AGENT_NAME
              value: shop::shop-order
            - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES
              value: "skywalking-oap-server.dubbo-system.svc:11800"
            - name: DUBBO_LABELS
              value: "orderVersion=v2;"
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: shop-user
  name: shop-user
  namespace: dubbo-demo
spec:
  ports:
    - name: grpc-tri
      port: 20884
      protocol: TCP
      targetPort: 20884
  selector:
    app: shop-user
---
# App User
apiVersion: apps/v1
kind: Deployment
metadata:
  name: shop-user
  namespace: dubbo-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: shop-user
  template:
    metadata:
      labels:
        app: shop-user
    spec:
      volumes:
        - name: skywalking-agent
          emptyDir: { }
        - name: config-volume
          configMap:
            name: shop-user-config
            items:
            - key: application.properties
              path: application.properties
      initContainers:
        - name: agent-container
          image: apache/skywalking-java-agent:8.13.0-java17
          volumeMounts:
            - name: skywalking-agent
              mountPath: /agent
          command: [ "/bin/sh" ]
          args: [ "-c", "cp -R /skywalking/agent /agent/" ]
      containers:
        - name: shop-user
          image: apache/dubbo-demo:dubbo-samples-shop-user_0.0.1
          imagePullPolicy: Always
          ports:
            - name: grpc-tri
              containerPort: 20884
              protocol: TCP
            - name: dubbo-qos
              containerPort: 20994
              protocol: TCP
          volumeMounts:
            - name: skywalking-agent
              mountPath: /skywalking
            - name: config-volume
              mountPath: /app/resources/application.properties
              subPath: application.properties
          env:
            - name: JAVA_TOOL_OPTIONS
              value: "-javaagent:/skywalking/agent/skywalking-agent.jar"
            - name: SW_AGENT_NAME
              value: shop::shop-user
            - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES
              value: "skywalking-oap-server.dubbo-system.svc:11800"
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: shop-detail
  name: shop-detail
  namespace: dubbo-demo
spec:
  ports:
    - name: grpc-tri
      port: 20885
      protocol: TCP
      targetPort: 20885
  selector:
    app: shop-detail
---
# App Detail-1
apiVersion: apps/v1
kind: Deployment
metadata:
  name: shop-detail-v1
  namespace: dubbo-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: shop-detail
      detailVersion: v1
  template:
    metadata:
      labels:
        app: shop-detail
        detailVersion: v1
    spec:
      volumes:
        - name: skywalking-agent
          emptyDir: { }
        - name: config-volume
          configMap:
            name: shop-detail-v1-config
            items:
            - key: application.properties
              path: application.properties
      initContainers:
        - name: agent-container
          image: apache/skywalking-java-agent:8.13.0-java17
          volumeMounts:
            - name: skywalking-agent
              mountPath: /agent
          command: [ "/bin/sh" ]
          args: [ "-c", "cp -R /skywalking/agent /agent/" ]
      containers:
        - name: shop-detail
          image: apache/dubbo-demo:dubbo-samples-shop-detail_0.0.1
          imagePullPolicy: Always
          ports:
            - name: grpc-tri
              containerPort: 20885
              protocol: TCP
            - name: dubbo-qos
              containerPort: 20995
              protocol: TCP
          volumeMounts:
            - name: skywalking-agent
              mountPath: /skywalking
            - name: config-volume
              mountPath: /app/resources/application.properties
              subPath: application.properties
          env:
            - name: JAVA_TOOL_OPTIONS
              value: "-javaagent:/skywalking/agent/skywalking-agent.jar"
            - name: SW_AGENT_NAME
              value: shop::shop-detail
            - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES
              value: "skywalking-oap-server.dubbo-system.svc:11800"
            - name: DUBBO_LABELS
              value: "detailVersion=v1; region=beijing"
---
# App Detail-2
apiVersion: apps/v1
kind: Deployment
metadata:
  name: shop-detail-v2
  namespace: dubbo-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: shop-detail
      detailVersion: v2
  template:
    metadata:
      labels:
        app: shop-detail
        detailVersion: v2
    spec:
      volumes:
        - name: skywalking-agent
          emptyDir: { }
        - name: config-volume
          configMap:
            name: shop-detail-v2-config
            items:
            - key: application.properties
              path: application.properties
      initContainers:
        - name: agent-container
          image: apache/skywalking-java-agent:8.13.0-java17
          volumeMounts:
            - name: skywalking-agent
              mountPath: /agent
          command: [ "/bin/sh" ]
          args: [ "-c", "cp -R /skywalking/agent /agent/" ]
      containers:
        - name: shop-detail
          image: apache/dubbo-demo:dubbo-samples-shop-detail_v2_0.0.1
          imagePullPolicy: Always
          ports:
            - name: grpc-tri
              containerPort: 20885
              protocol: TCP
            - name: dubbo-qos
              containerPort: 20996
              protocol: TCP
          volumeMounts:
            - name: skywalking-agent
              mountPath: /skywalking
            - name: config-volume
              mountPath: /app/resources/application.properties
              subPath: application.properties
          env:
            - name: JAVA_TOOL_OPTIONS
              value: "-javaagent:/skywalking/agent/skywalking-agent.jar"
            - name: SW_AGENT_NAME
              value: shop::shop-detail
            - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES
              value: "skywalking-oap-server.dubbo-system.svc:11800"
            - name: DUBBO_LABELS
              value: "detailVersion=v2; region=hangzhou;"
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: shop-comment
  name: shop-comment
  namespace: dubbo-demo
spec:
  ports:
    - name: grpc-tri
      port: 20887
      protocol: TCP
      targetPort: 20887
  selector:
    app: shop-comment
---
#App Comment v1
apiVersion: apps/v1
kind: Deployment
metadata:
  name: shop-comment-v1
  namespace: dubbo-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: shop-comment
      commentVersion: v1
  template:
    metadata:
      labels:
        app: shop-comment
        commentVersion: v1
    spec:
      volumes:
        - name: skywalking-agent
          emptyDir: { }
        - name: config-volume
          configMap:
            name: comment-v1-config
            items:
            - key: application.properties
              path: application.properties
      initContainers:
        - name: agent-container
          image: apache/skywalking-java-agent:8.13.0-java17
          volumeMounts:
            - name: skywalking-agent
              mountPath: /agent
          command: [ "/bin/sh" ]
          args: [ "-c", "cp -R /skywalking/agent /agent/" ]
      containers:
        - name: shop-comment
          image: apache/dubbo-demo:dubbo-samples-shop-comment_0.0.1
          imagePullPolicy: Always
          ports:
            - name: grpc-tri
              containerPort: 20887
              protocol: TCP
            - name: dubbo-qos
              containerPort: 20997
              protocol: TCP
          volumeMounts:
            - name: skywalking-agent
              mountPath: /skywalking
            - name: config-volume
              mountPath: /app/resources/application.properties
              subPath: application.properties
          env:
            - name: JAVA_TOOL_OPTIONS
              value: "-javaagent:/skywalking/agent/skywalking-agent.jar"
            - name: SW_AGENT_NAME
              value: shop::shop-comment
            - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES
              value: "skywalking-oap-server.dubbo-system.svc:11800"
            - name: DUBBO_LABELS
              value: "commentVersion=v1; region=beijing"
---
#App Comment v2
apiVersion: apps/v1
kind: Deployment
metadata:
  name: shop-comment-v2
  namespace: dubbo-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: shop-comment
      commentVersion: v2
  template:
    metadata:
      labels:
        app: shop-comment
        commentVersion: v2
    spec:
      volumes:
        - name: skywalking-agent
          emptyDir: { }
        - name: config-volume
          configMap:
            name: comment-v2-config
            items:
            - key: application.properties
              path: application.properties
      initContainers:
        - name: agent-container
          image: apache/skywalking-java-agent:8.13.0-java17
          volumeMounts:
            - name: skywalking-agent
              mountPath: /agent
          command: [ "/bin/sh" ]
          args: [ "-c", "cp -R /skywalking/agent /agent/" ]
      containers:
        - name: shop-comment
          image: apache/dubbo-demo:dubbo-samples-shop-comment_v2_0.0.1
          imagePullPolicy: Always
          ports:
            - name: grpc-tri
              containerPort: 20887
              protocol: TCP
            - name: dubbo-qos
              containerPort: 20998
              protocol: TCP
          volumeMounts:
            - name: skywalking-agent
              mountPath: /skywalking
            - name: config-volume
              mountPath: /app/resources/application.properties
              subPath: application.properties
          env:
            - name: JAVA_TOOL_OPTIONS
              value: "-javaagent:/skywalking/agent/skywalking-agent.jar"
            - name: SW_AGENT_NAME
              value: shop::shop-comment
            - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES
              value: "skywalking-oap-server.dubbo-system.svc:11800"
            - name: DUBBO_LABELS
              value: "commentVersion=v2; region=hangzhou;"
EOF

此处的示例服务使用了Dubbo社区的sample-shop应用,有关应用的具体介绍以及代码实现,可以参考链接

该示例应用的所有服务都接入了一个nacos注册中心(部署在dubbo-system下),可以利用这一点演示这一类使用注册中心的应用过渡使用服务网格的过程。

相比社区提供的示例应用,上面的YAML主要做了以下更改以进行场景演示:

  1. 由于该示例应用还是使用dubbo协议在通信,这里通过configmap覆盖社区示例镜像里的application.properties,将通信协议改成Triple。对于当前的Dubbo版本,这样的做法仍可能导致Dubbo服务调用因为缺少protobuf依赖而失败,后续主要通过访问日志先看下接入服务网格的效果。
  2. 给每个集群中的Deployment都部署了对应的k8s Service。声明k8s Service是Dubbo引用向云原生化过渡的第一步,建议可以将k8s Service与Dubbo的application name进行统一、方便管理。服务网格将以k8s Service域名为基础管理服务的流量。

3.2 步骤2:在服务网格ASM中开启插件

当Dubbo应用使用注册中心时,调用端会通过注册信息直接得知服务端地址、并通过地址进行调用,期间并不会通过k8s Service域名发起调用。因此,直接为此类应用注入Sidecar并不能使得Sidecar拦截和管理服务之间调用的流量。

服务网格ASM提供了“支持Spring Cloud服务”插件,其主要原理是给请求加上host header来帮助服务网格识别请求的目标k8s Service、进而完成对流量的管理。从功能上来讲同样支持Dubbo应用以相同方案过渡使用服务网格能力,但要求Dubbo应用必须使用Triple等基于HTTP协议的RPC协议。

具体插件开启步骤如下:

  1. 登录ASM控制台,在左侧导航栏,选择服务网格 > 网格管理
  2. 网格管理页面,单击目标实例名称,然后在左侧导航栏,选择插件扩展中心 > 插件市场
  3. 插件市场页面,单击支持Spring Cloud服务,进入插件详情页面。
  4. 插件详情页面,单击使用指引,了解插件的具体功能和插件配置YAML的相关说明。
  5. 插件详情页面,单击新建插件实例,将插件生效范围调整为全局生效,配置插件参数,然后在页面下方,打开生效开关

对于支持Spring Cloud服务插件来说,其主要参数包含集群中的pod_cidrs以及provider_port_number。

  • pod_cidrs:ACK或ASK集群的Pod CIDR。您可以登录容器服务管理控制台,在集群信息页面的基本信息 页签下查看Pod CIDR。若容器网络插件采用Terway ,您可以在集群信息页面的集群资源页签查看Pod虚拟交换机对应的CIDR进行配置。该参数为必填,默认值仅为一个示例值。
  • provider_port_number:provider的服务端口,在集群中部署了多个provider、各自有不同的服务端口时,可以配置多个插件实例、或者不配置此参数。

例如可以使用如下参数填写:

provider_port_number: '20881' # provider为20881端口
pod_cidrs:
  - 10.0.0.0/16 # 集群中pod cidr、或者集群交换机cidr(在使用terway的情况下)

3.3 步骤3:检查应用已经接入网格

可通过port-forward方式获取应用访问地址

kubectl port-forward -n dubbo-demo deployment/shop-frontend 8080:8080

浏览器访问localhost:8080,使用任意字符充当用户名和密码登录。

image.png

由于此示例还没完全适配Triple协议,应用会产生报错,但通过frontend服务的istio-proxy可以看到访问日志:

kubectl -n dubbo-demo logs deploy/shop-frontend -c istio-proxy
...
{"authority_for":"shop-detail.dubbo-demo.svc.cluster.local:20885","bytes_received":"69","bytes_sent":"0","downstream_local_address":"10.0.239.181:20885","downstream_remote_address":"10.0.239.165:35638","duration":"31","istio_policy_status":"-","method":"POST","path":"/org.apache.dubbo.metadata.MetadataService/getMetadataInfo","protocol":"HTTP/2","request_id":"240aaced-a67d-4b10-a252-a738f67b346d","requested_server_name":"-","response_code":"200","response_flags":"URX","route_name":"default","start_time":"2024-07-15T08:21:29.620Z","trace_id":"-","upstream_cluster":"outbound|20885||shop-detail.dubbo-demo.svc.cluster.local","upstream_host":"10.0.239.169:20885","upstream_local_address":"10.0.239.165:52506","upstream_response_time":"31","upstream_service_time":"31","upstream_transport_failure_reason":"-","user_agent":"-","x_forwarded_for":"-"}

可以看到类似上述的日志,和最佳实践场景中的访问日志一样,可以看到请求以及路由目标的k8s Service都已经被服务网格正确识别。

如果关掉插件,再次访问时,则会看到类似这样的访问日志

kubectl -n dubbo-demo logs deploy/shop-frontend -c istio-proxy
...
{"authority_for":"10.0.239.163:20884","bytes_received":"65","bytes_sent":"0","downstream_local_address":"10.0.239.163:20884","downstream_remote_address":"10.0.239.165:56152","duration":"2","istio_policy_status":"-","method":"POST","path":"/org.apache.dubbo.samples.UserService/login","protocol":"HTTP/2","request_id":"1560b7ef-e9f6-4ddf-883b-e4f56d8753db","requested_server_name":"-","response_code":"200","response_flags":"-","route_name":"allow_any","start_time":"2024-07-15T08:30:02.068Z","trace_id":"-","upstream_cluster":"PassthroughCluster","upstream_host":"10.0.239.163:20884","upstream_local_address":"10.0.239.165:56166","upstream_response_time":"2","upstream_service_time":"2","upstream_transport_failure_reason":"-","user_agent":"-","x_forwarded_for":"-"

可以看到upstream_cluster字段为PassthroughCluster。此时服务网格无法将被调用服务识别为被注册在服务网格内的k8s服务,应用也将无法使用服务网格的云原生能力。

注:

上述场景应该只作为过渡方案来使用,最终我们仍然建议您过渡到Dubbo社区提供的最佳实践方案来为Dubbo应用接入服务网格,以避免任何未来可能发生的不兼容或错误。

4. 小结

本文主要就Dubbo应用如何接入服务网格、获得各项云原生能力进行了探讨,并提出了最佳实践以及过渡两种实践场景。我们首先推荐您使用Dubbo社区提供的最佳实践场景来接入服务网格,在必要时可以通过过渡方案来向最佳实践方案逐步实现过渡。

作者介绍
目录

相关产品

  • 服务网格