接上一篇《【Istio安全】服务间访问控制-RBAC》,本文主要介绍网格边缘Egress相关的配置,HTTPHTTPS以及HTTPTLS

  • Istio版本 1.1.1

开始前的准备

  # Set the default behavior of the sidecar for handling outbound traffic from the application:
  # ALLOW_ANY - outbound traffic to unknown destinations will be allowed, in case there are no
  #   services or ServiceEntries for the destination port
  # REGISTRY_ONLY - restrict outbound traffic to services defined in the service registry as well
  #   as those defined through ServiceEntries
  # ALLOW_ANY is the default in 1.1.  This means each pod will be able to make outbound requests 
  # to services outside of the mesh without any ServiceEntry.
  # REGISTRY_ONLY was the default in 1.0.  If this behavior is desired, set the value below to REGISTRY_ONLY.
  outboundTrafficPolicy:
    mode: ALLOW_ANY

Istio部署配置的global.outboundTrafficPolicy参数,在1.1开始默认ALLOW_ANY,这种情况下如果不配置ServiceEntry,访问外包HTTP流量返回404,而HTTPS流量可以正常访问,为了测试ServiceEntry配置效果,将配置改为REGISTRY_ONLY,方便观察ServiceEntry配置的效果

本文示例Istio使用helm的values-istio-demo.yaml配置安装

部署测试用例sleep

$ kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.1/samples/sleep/sleep.yaml

$ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})

访问测试outboundTrafficPolicy=ALLOW_ANY

$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://hbchen.com
HTTP/1.1 404 Not Found

$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://www.aliyun.com
HTTP/1.1 404 Not Found

$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - https://www.aliyun.com
HTTP/2 200

修改values-istio-demo.yaml

  outboundTrafficPolicy:
    mode: REGISTRY_ONLY

重新部署Istio

$ helm template install/kubernetes/helm/istio --name istio --namespace istio-system --values install/kubernetes/helm/istio/values-istio-demo.yaml | kubectl apply -f -

重新部署测试用例sleep

$ kubectl delete -f https://raw.githubusercontent.com/istio/istio/release-1.1/samples/sleep/sleep.yaml
$ kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.1/samples/sleep/sleep.yaml

$ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})

Sidecar方式

ServiceEntry

HTTP

先定义一个ServiceEntry,开启www.aliyun.comhbchen.com的外部访问

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: entry-x
spec:
  hosts:
  - www.aliyun.com
  - hbchen.com
  ports:
  - number: 80
    name: http
    protocol: HTTP
  resolution: NONE
EOF

访问测试

$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://hbchen.com
HTTP/1.1 200 OK

$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://www.aliyun.com
HTTP/1.1 301 Moved Permanently
...
command terminated with exit code 35

$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - https://www.aliyun.com
command terminated with exit code 35

HTTP & HTTPS

只开80端口,HTTP正常,而对于HTTPS以及有HTTP重定向HTTPS的请求仍会失败command terminated with exit code 35,所以实践中外部服务一般同时开启80443端口

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: entry-x
spec:
  hosts:
  - www.aliyun.com
  - hbchen.com
  ports:
  - number: 80
    name: http-port
    protocol: HTTP
  - number: 443
    name: https-port
    protocol: HTTPS
  resolution: NONE
EOF

访问测试

$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://www.aliyun.com
HTTP/1.1 301 Moved Permanently
...
HTTP/2 200

$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - https://www.aliyun.com
HTTP/2 200
...

HTTP转TLS

这时我们看到另一个问题,需要升级HTTPSHTTP请求每次都要301重定向一次,在不修改代码的情况下可以在proxyHTTP请求升级为TLS,避免二次跳转,需要加VirtualService + DestinationRule来实现。

在官方示例出口流量的-tls中,仅将HTTP升级到TLS,而对正常的HTTPS没有支持,这里稍作改动,在VirtualService中为80端口的destination定义subset,这样在DestinationRule中默认trafficPolicy不做处理,仅对指定的subsetHTTPS转发,使网格内无论发起HTTP还是HTTPS的外网请求都可以正常访问。

kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: entry-x
spec:
  hosts:
  - www.aliyun.com
  - hbchen.com
  ports:
  - number: 80
    name: http-port
    protocol: HTTP
  - number: 443
    name: https-port
    protocol: HTTPS
  resolution: DNS
  location: MESH_EXTERNAL
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: rewrite-port-for-entry
spec:
  hosts:
  - www.aliyun.com
  http:
  - match:
      - port: 80
    route:
    - destination:
        # NOTE: 为HTTP升级流量指定subset
        subset: originate-tls
        host: www.aliyun.com
        port:
          number: 443
  tls:
  - match:
      - port: 443
        sniHosts:
        - www.aliyun.com
    route:
    - destination:
        host: www.aliyun.com
        port:
          number: 443
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: originate-tls-for-entry
spec:
  host: www.aliyun.com
  trafficPolicy:
    loadBalancer:
      simple: ROUND_ROBIN
  subsets:
    - name: originate-tls
      trafficPolicy:
        loadBalancer:
          simple: ROUND_ROBIN
        portLevelSettings:
        - port:
            number: 443
          tls:
            mode: SIMPLE
EOF

访问测试

$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://hbchen.com
HTTP/1.1 200 OK

$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://www.aliyun.com
HTTP/1.1 200 OK

$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - https://www.aliyun.com
HTTP/2 200

Gateway方式

ServiceEntry

官方示例配置 Egress gateway

Gateway与Sidecar的区别是将出口流量都转到egressgateway,再由Gateway进行转发处理,无论Gateway还是SidecarServiceEntry的配置规则都是需要的,这里我们直接开启80433,注意resolution=DNS

apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: entry-x
spec:
  hosts:
  - www.aliyun.com
  ports:
  - number: 80
    name: http-port
    protocol: HTTP
  - number: 443
    name: https-port
    protocol: HTTPS
  resolution: DNS

访问测试(略)

HTTP

流程如下

graph LR;
   S[Siedcar]-->|"①HTTP"|VS[VirtualService<br/>www.aliyun.com]
   VS-->|"②gateway=mesh"|EG[EgressGateway]
   EG-->|"③HTTP"|VS
   VS-->|"④gateway=istio-egressgateway"|E[External]
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: istio-egressgateway
spec:
  selector:
    istio: egressgateway
  servers:
  - port:
      number: 80
      name: http-port
      protocol: HTTP
    hosts:
    - www.aliyun.com
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: egressgateway-for-aliyun
spec:
  host: istio-egressgateway.istio-system.svc.cluster.local
  subsets:
  - name: aliyun
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: aliyun-through-egress-gateway
spec:
  hosts:
  - www.aliyun.com
  gateways:
  - istio-egressgateway
  - mesh
  http:
  - match:
    - gateways:
      - mesh
      port: 80
    route:
    - destination:
        host: istio-egressgateway.istio-system.svc.cluster.local
        subset: aliyun
        port:
          number: 80
      weight: 100
  - match:
    - gateways:
      - istio-egressgateway
      port: 80
    route:
    - destination:
        host: www.aliyun.com
        port:
          number: 80
      weight: 100
EOF

访问测试(这里只有HTTP)

kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://www.aliyun.com
HTTP/1.1 301 Moved Permanently
...
HTTP/2 200
...

HTTP转TLS

graph LR;
   S[Siedcar]-->|"①HTTP"|VS[VirtualService<br/>www.aliyun.com]
   VS-->|"②gateway=mesh"|EG[EgressGateway]
   EG-->|"③HTTP"|VS
   VS-->|"④gateway=istio-egressgateway<br/>DestinationRule为Subset升级TLS"|E[External]

与Sidecar方式类似,对于有HTTP重定向到HTTPS的流量可以在Gateway代理直接升级为TLS,减少跳转,实现方式与Sidecar方式类似,为HOST增加DestinationRule规则为HTTP流量发起TLS请求。

kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: istio-egressgateway
spec:
  selector:
    istio: egressgateway
  servers:
  - port:
      number: 80
      name: http-port
      protocol: HTTP
    hosts:
    - www.aliyun.com
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: egressgateway-for-aliyun
spec:
  host: istio-egressgateway.istio-system.svc.cluster.local
  subsets:
  - name: aliyun
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: aliyun-through-egress-gateway
spec:
  hosts:
  - www.aliyun.com
  gateways:
  - istio-egressgateway
  - mesh
  http:
  - match:
    - gateways:
      - mesh
      port: 80
    route:
    - destination:
        host: istio-egressgateway.istio-system.svc.cluster.local
        subset: aliyun
        port:
          number: 80
      weight: 100
  - match:
    - gateways:
      - istio-egressgateway
      port: 80
    route:
    - destination:
        host: www.aliyun.com
        # NOTE: 为HTTP升级流量指定subset
        subset: originate-tls
        port:
          number: 443
      weight: 100
---
# NOTE: 注意这里与Sidecar方式类似,需要对HTTP流量单独配置subset,否则影响正常HTTPS流量
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: originate-tls-for-aliyun
spec:
  host: www.aliyun.com
  trafficPolicy:
    loadBalancer:
      simple: ROUND_ROBIN
  subsets:
    - name: originate-tls
      trafficPolicy:
        loadBalancer:
          simple: ROUND_ROBIN
        portLevelSettings:
        - port:
            number: 443
          tls:
            mode: SIMPLE
EOF

访问测试

$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://www.aliyun.com
HTTP/1.1 200 OK

双向TLSHTTTPS透传等这里没有再做测试,有兴趣可以参考官方示例配置 Egress gateway

直接调用外部服务

ServiceEntry

Istio文档直接调用外部服务

需要修改helm的values配置更新部署。并且要让新的istio-sidecar-injector生效,重新部署sleep用例。这种方式在Sidecar的Proxy中跳过了外部IP,虽然也有excludeIPRanges方式,但修改麻烦需要更新sidecar,并且这样也失去了Istio的其它能力,所以不怎么实用。

  proxy:
    # Minikube
    includeIPRanges: "10.0.0.1/24"

总结

ServiceEntry配合VirtualServiceDestinationRule可以使外部流量做比较灵活的管控,并且可以对外部服务使用流量管理的相关功能,Gateway相对Sidecar配置还是略显复杂,而在性能方面官方BlogEgress gateway 性能测试给出的比较结果两者相差不大,至于直接调用方式相对就不怎么实用了。