管理集群内客户端访问集群外服务

服务网格 ASM(Service Mesh)在出口流量管理方面,提供了多种灵活且高效的解决方案,为应用程序的安全性、可观测性和可靠性提供支持。本文将介绍ASM在出口流量管理方面提供的多种功能以及为什么要使用ASM出口网关。

丰富的出口流量管理能力

服务网格提供了丰富的管理L7出口流量的能力(流量路由、可观测、安全等),您可以根据需求选择要如何进行配置来实现相应的功能。

说明

如果应用直接发起HTTPS请求,网格代理只能将其当成普通的TLS流量处理,网格的L7能力将会失效。因此,请确保您的应用发出的请求为HTTP明文请求。网格会根据您的配置,直接将HTTP请求转发给外部服务,或者自动将请求转换成HTTPS请求发往外部服务。

流量路由

在访问集群外HTTP服务时,您只需要在服务网格中配置对应的Service Entry,就可以使用例如出口【Mirroring】镜像流量、出口流量按比例路由和虚拟服务中的其他高级能力。如果您访问的服务是HTTPS协议,需要再配置一个对应的DestinationRule。流量路由能力并不依赖ASM出口网关。

观测出口流量

如果请求是明文流量,您无需做任何操作即可通过日志、监控指标和链路追踪等方式观测出口流量。如果请求的流量需要加密,您只需要配置Service EntryDestinationRule。应用发起明文流量,Sidecar自动将明文流量加密后进行转发。此时可以使用网格完整的可观测能力,该功能并不依赖ASM出口网关。

集群外服务管理简介

Ambient模式中,Ztunnel会拦截进出业务Pod的所有流量,并且对于业务Pod发出的流量,如果Ztunnel并不知道该服务的具体信息,就会选择原样转发。

默认情况下,客户端发出一个访问外部服务请求时,Ztunnel中并没有这个服务的信息,因此会直接转发。

当我们想要严格控制外部服务的访问时,应该首先拒绝当前业务Pod访问任何集群外的IP。

禁止业务Pod直接访问外部服务之后,您可以在网格中通过ServiceEntry注册合法的外部服务,并且通过给ServiceEntry启用Waypoint(不要限制Waypoint Pod的外部访问),让所有访问ServiceEntry的流量被转发给Waypoint,在Waypoint处执行各种请求转换、安全策略以及可观测策略之后,再通过Waypoint转发给真正的外部服务。流量路径如下图所示:

image

操作示例

步骤一:部署测试应用

  1. 部署示例应用。

    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: sleep
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: sleep
      labels:
        app: sleep
        service: sleep
    spec:
      ports:
      - port: 80
        name: http
      selector:
        app: sleep
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: sleep
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: sleep
      template:
        metadata:
          labels:
            app: sleep
        spec:
          terminationGracePeriodSeconds: 0
          serviceAccountName: sleep
          containers:
          - name: sleep
            image:  registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/curl:asm-sleep
            command: ["/bin/sleep", "infinity"]
            imagePullPolicy: IfNotPresent
            volumeMounts:
            - mountPath: /etc/sleep/tls
              name: secret-volume
          volumes:
          - name: secret-volume
            secret:
              secretName: sleep-secret
              optional: true
    ---
    说明

    请确保测试应用所在的命名空间已经启用Amient模式。

  2. 测试访问外部服务。

    kubectl exec deployment/sleep -- curl aliyun.com -s -I

    预期输出:

    HTTP/1.1 301 Moved Permanently
    Server: Tengine
    Date: Wed, 20 Aug 2025 07:56:27 GMT
    Content-Type: text/html
    Content-Length: 239
    Connection: keep-alive
    Location: https://aliyun.com/
    EagleEye-TraceId: 0b87b7b217556765878011141ee221
    Timing-Allow-Origin: *

步骤二:使用ASM统一管理外部服务访问

  1. ASM控制台新建命名空间:istio-egress,并且为该命名空间开启Ambient模式。

  2. istio-egress命名空间下创建Waypiont。

    kubectl apply -n istio-egress -f - <<EOF
    apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
      name: waypoint
    spec:
      gatewayClassName: istio-waypoint
      listeners:
      - name: mesh
        port: 15008
        protocol: HBONE
    EOF
  3. 为当前命名空间启用Waypoint。

    kubectl label namespace istio-egress istio.io/use-waypoint=waypoint --overwrite

步骤三:创建ServiceEntry并测试

  1. 创建ServiceEntry。

    kubectl apply -n istio-egress -f - <<EOF
    apiVersion: networking.istio.io/v1
    kind: ServiceEntry
    metadata:
      name: external-svc-aliyun
    spec:
      hosts:
      - aliyun.com
      location: MESH_EXTERNAL
      ports:
      - number: 80
        name: http
        protocol: HTTP
      - number: 443
        name: https
        protocol: HTTPS
      resolution: DNS
    EOF
  2. 测试访问。

    kubectl exec deployment/sleep -- curl aliyun.com -s -I

    预期输出:

    HTTP/1.1 301 Moved Permanently
    server: envoy
    date: Mon, 18 Aug 2025 09:36:30 GMT
    content-type: text/html
    content-length: 239
    location: https://aliyun.com/
    eagleeye-traceid: 0be3e0ac17555097903637743e684e
    timing-allow-origin: *
    x-envoy-upstream-service-time: 27

    可以看到正常返回了301状态码。说明请求经由istio-egress命名空间的Waypoint转发出去,成功访问了外部服务。您可以使用以下命令查看Waypoint的对应日志:

    kubectl -n istio-egress logs deployments/waypoint  | tail -1 | jq

    预期输出:

    {                                                                                                                                                                          
     "authority_for": "aliyun.com",                                                                                                                                           
     "bytes_received": 0,                                                                                                                                                     
     "bytes_sent": 239,                                                                                                                                                       
     "downstream_local_address": "240.240.0.7:80",                                                                                                                            
     "downstream_remote_address": "172.16.5.26:33356",                                                                                                                        
     "duration": 24,                                                                                                                                                          
     "istio_policy_status": null,                                                                                                                                             
     "method": "GET",                                                                                                                                                         
     "path": "/",                                                                                                                                                             
     "protocol": "HTTP/1.1",                                                                                                                                                  
     "request_id": "234a2e8b-e170-42f6-b9db-693cd757974d",                                                                                                                    
     "requested_server_name": null,                                                                                                                                           
     "response_code": 301,                                                                                                                                                    
     "response_flags": "-",                                                                                                                                                   
     "route_name": "default",                                                                                                                                                 
     "start_time": "2025-08-20T08:06:06.502Z",                                                                                                                                
     "trace_id": null,                                                                                                                                                        
     "upstream_cluster": "inbound-vip|80|http|aliyun.com;",                                                                                                                   
     "upstream_host": "140.205.135.3:80",                                                                                                                                     
     "upstream_local_address": "172.16.5.23:34524",                                                                                                                           
     "upstream_response_time": "23",                                                                                                                                          
     "upstream_service_time": "23",                                                                                                                                           
     "upstream_transport_failure_reason": null,                                                                                                                               
     "user_agent": "curl/8.8.0",                                                                                                                                              
     "x_forwarded_for": null                                                                                                                                                  
    }

步骤五:应用发起HTTP请求,由Waypoint升级为HTTPS

默认情况下,Waypoint使用同样的协议转发收到的请求,比如应用发起HTTP,Waypoint就通过HTTP访问外部服务;应用如果使用HTTPS,Waypoint就会直接转发HTTPS流量。

但是由于HTTPS流量是加密的,Waypoint无法看到其中的L7信息,只能执行一些TCP层面的鉴权和流量路由,无法使用7层高级能力。这种场景下,我们推荐应用发起HTTP,然后由Waypoint执行HTTPS升级,最终使用HTTPS访问外部服务。由于应用到Waypoint之间是mTLS,所以整个链路依然是加密的。

  1. 更新ServiceEntry,如果应用访问80端口,Waypoint会转发给外部服务的443端口。

    kubectl apply -n istio-egress -f - <<EOF
    apiVersion: networking.istio.io/v1
    kind: ServiceEntry
    metadata:
      name: external-svc-aliyun
    spec:
      hosts:
      - aliyun.com
      location: MESH_EXTERNAL
      ports:
      - number: 80
        name: http
        protocol: HTTP
        targetPort: 443
      - number: 443
        name: https
        protocol: HTTPS
      resolution: DNS
    EOF
  2. 配置一个DestinationRule,让Waypoint对该服务的443端口执行HTTPS升级。

    kubectl apply -n istio-egress -f - <<EOF
    apiVersion: networking.istio.io/v1
    kind: DestinationRule
    metadata:
      name: aliyun-com
    spec:
      host: aliyun.com
      trafficPolicy:
        portLevelSettings:
        - port:
            number: 80
          tls:
            mode: SIMPLE
    EOF
  3. 测试访问。

    kubectl exec deployment/sleep -- curl aliyun.com -s -I

    预期输出:

    HTTP/1.1 301 Moved Permanently
    server: envoy
    date: Mon, 18 Aug 2025 09:36:30 GMT
    content-type: text/html
    content-length: 239
    location: https://aliyun.com/
    eagleeye-traceid: 0be3e0ac17555097903637743e684e
    timing-allow-origin: *
    x-envoy-upstream-service-time: 27

    此时再次查看Waypoint的日志,可以看到请求已经转发到了443端口。