Istio 常见问题 - 集群内无法访问外部服务

现象

集群内无法访问外部服务。

  1. 在集群中部署 sleep

    1
    
    kubectl apply -f samples/sleep/sleep.yaml
    
  2. 设置环境变量

    1
    
    export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
    
  3. 访问外网服务

    1
    2
    3
    4
    
    $ kubectl exec -it $SOURCE_POD -c sleep -- curl http://www.baidu.com
    
    $ kubectl exec -it $SOURCE_POD -c sleep -- curl -I https://www.baidu.com | grep  "HTTP/"
    command terminated with exit code 35
    

进入 sleep 容器,执行 curl http://www.baidu.com,无数据返回;执行 curl -I https://www.baidu.com 也访问失败。这种现象就是集群内无法访问外部服务。

原因

由于默认情况下,来自 Istio-enable Pod 的所有出站流量都会重定向到其 Sidecar 代理,群集外部 URL 的可访问性取决于代理的配置。默认情况下,Istio 将 Envoy 代理配置为允许传递未知服务的请求。但这不是 Istio 推荐的做法,因此,服务商或者其他安装配置可能会修改默认设置为不允许传递未知服务的请求。

当遇到集群内无法访问外部服务时,可运行以下命令检查 Sidecar 的代理配置,查看是否允许传递未知服务的请求。

1
2
3
4
5
6
7
8
9
$ kubectl get configmap istio -n istio-system -o yaml | grep -o "mode: ALLOW_ANY"
mode: ALLOW_ANY
mode: ALLOW_ANY

# 或者

$ kubectl get configmap istio -n istio-system -o yaml | grep -o "mode: REGISTRY_ONLY"
mode: REGISTRY_ONLY
mode: REGISTRY_ONLY

如果命令输出有 mode: ALLOW_ANY 表示 Istio 代理允许调用未知的服务; 如果命令输出有 mode: REGISTRY_ONLY 那么 Istio 代理会阻止任何没有在网格中定义的 HTTP 服务或 service entry 的主机

方法

如果是因为 Istio 的配置项为 mode: REGISTRY_ONLY 而导致集群内无法访问外部服务。可有以下三种访问外部服务的方法:

  • 方法一:修改配置为 mode: ALLOW_ANY 以允许 Sidecar 将请求传递到未在网格内配置过的服务。
  • 方法二:配置 ServiceEntry 以提供对外部服务的受控访问。
  • 方法三:对于特定范围的 IP,完全绕过 Envoy 代理。

方法一

运行以下命令,修改配置为 mode: ALLOW_ANY

1
2
$ kubectl get configmap istio -n istio-system -o yaml | sed 's/mode: REGISTRY_ONLY/mode: ALLOW_ANY/g' | kubectl replace -n istio-system -f -
configmap/istio replaced

验证配置

1
2
3
$ kubectl get configmap istio -n istio-system -o yaml | grep -o "mode: ALLOW_ANY"
mode: ALLOW_ANY
mode: ALLOW_ANY

重启部署

1
2
$ kubectl rollout restart deployment sleep
deployment.extensions/sleep restarted

访问外部服务

1
2
3
4
5
6
7
8
9
$ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})

$ kubectl exec -it $SOURCE_POD -c sleep -- curl http://www.baidu.com
<!DOCTYPE html>
......
京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>

$ kubectl exec -it $SOURCE_POD -c sleep -- curl -I https://www.baidu.com | grep  "HTTP/"
HTTP/1.1 200 OK

注:这种访问外部服务的简单方法有一个缺点,即丢失了对外部服务流量的 Istio 监控和控制

方法二

在开始方法二之前,需要先修改 Istio 的配置项为 mode: REGISTRY_ONLY

1
2
$ kubectl get configmap istio -n istio-system -o yaml | sed 's/mode: ALLOW_ANY/mode: REGISTRY_ONLY/g' | kubectl replace -n istio-system -f -
configmap "istio" replaced

允许访问外部 HTTP 服务

  1. 添加 ServiceEntry,允许访问 httpbin 的 HTTP 服务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: ServiceEntry
    metadata:
      name: httpbin-ext
    spec:
      hosts:
      - httpbin.org
      ports:
      - number: 80
        name: http
        protocol: HTTP
      resolution: DNS
      location: MESH_EXTERNAL
    EOF
    
  2. 从 sleep 向外部发送 httpbin 的 HTTP 服务请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    $ kubectl exec -it $SOURCE_POD -c sleep -- curl http://httpbin.org/headers
    {
      "headers": {
        "Accept": "*/*",
        "Content-Length": "0",
        "Host": "httpbin.org",
        "User-Agent": "curl/7.64.0",
        "X-Amzn-Trace-Id": "Root=1-5e89e3ba-e22faf007e305d9897cddf1e",
        "X-B3-Sampled": "1",
        "X-B3-Spanid": "338f3faa38eb194e",
        "X-B3-Traceid": "d0ff4841f30240b2338f3faa38eb194e",
        "X-Envoy-Decorator-Operation": "httpbin.org:80/*",
        "X-Envoy-Peer-Metadata": "ChwKDElOU1RBTkNFX0lQUxIMGgoxMC4xLjAuMjQ1CsMBCgZMQUJFTFMSuAEqtQEKDgoDYXBwEgcaBXNsZWVwCiAKEXBvZC10ZW1wbGF0ZS1oYXNoEgsaCWM2OWQ5NmM5YwokChlzZWN1cml0eS5pc3Rpby5pby90bHNNb2RlEgcaBWlzdGlvCioKH3NlcnZpY2UuaXN0aW8uaW8vY2Fub25pY2FsLW5hbWUSBxoFc2xlZXAKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0ChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAofCgROQU1FEhcaFXNsZWVwLWM2OWQ5NmM5Yy0yZnh6ZAoWCglOQU1FU1BBQ0USCRoHZGVmYXVsdApJCgVPV05FUhJAGj5rdWJlcm5ldGVzOi8vYXBpcy9hcHBzL3YxL25hbWVzcGFjZXMvZGVmYXVsdC9kZXBsb3ltZW50cy9zbGVlcAoaCg9TRVJWSUNFX0FDQ09VTlQSBxoFc2xlZXAKGAoNV09SS0xPQURfTkFNRRIHGgVzbGVlcA==",
        "X-Envoy-Peer-Metadata-Id": "sidecar~10.1.0.245~sleep-c69d96c9c-2fxzd.default~default.svc.cluster.local"
      }
    }
    

    注:返回数据中可以看到 Istio sidecar 代理添加的 headers: X-Envoy-Decorator-Operation,表示访问外部的流量经过了 sidecar

  3. 检查 sleep 的 sidecar 日志

    1
    2
    
    $  kubectl logs $SOURCE_POD -c istio-proxy | tail
    [2020-04-05T13:57:13.948Z] "GET /headers HTTP/1.1" 200 - "-" "-" 0 1117 629 628 "-" "curl/7.64.0" "d94cc283-11ef-949c-8ba0-d948ad5785ab" "httpbin.org" "52.202.2.199:80" outbound|80||httpbin.org 10.1.0.245:48116 34.230.193.231:80 10.1.0.245:56246 - default
    

允许访问外部 HTTPS 服务

  1. 添加 ServiceEntry,允许访问 httpbin 的 HTTPS 服务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: ServiceEntry
    metadata:
      name: httpbin-https-ext
    spec:
      hosts:
      - httpbin.org
      ports:
      - number: 443
        name: https
        protocol: HTTPS
      resolution: DNS
      location: MESH_EXTERNAL
    EOF
    
  2. 从 sleep 向外部发送 httpbin 的 HTTPS 服务请求

    1
    2
    3
    4
    5
    6
    7
    8
    
    $ kubectl exec -it $SOURCE_POD -c sleep -- curl -I https://httpbin.org/headers
    HTTP/2 200
    date: Sun, 05 Apr 2020 14:10:25 GMT
    content-type: application/json
    content-length: 173
    server: gunicorn/19.9.0
    access-control-allow-origin: *
    access-control-allow-credentials: true
    
  3. 检查 sleep 的 sidecar 日志

    1
    2
    
    $ kubectl logs $SOURCE_POD -c istio-proxy | tail
    [2020-04-05T14:10:23.939Z] "- - -" 0 - "-" "-" 941 5597 1695 - "-" "-" "-" "-" "3.232.168.170:443" outbound|443||httpbin.org 10.1.0.245:36844 3.232.168.170:443 10.1.0.245:36838 httpbin.org -
    

管理到外部服务的流量

通过 ServiceEntry 配置访问的外部服务,可以向集群内的请求一样设置 Istio 路由规则。下面以 httpbin 为例,设置对服务访问的超时规则。

  1. 向外部服务 httpbin.org 的 /delay endpoint 发出 curl 请求:

    1
    2
    3
    4
    5
    6
    7
    
    $ kubectl exec -it $SOURCE_POD -c sleep sh
    / # time curl -o /dev/null -s -w "%{http_code}\n" http://httpbin.org/delay/5
    200
    real    0m 6.12s
    user    0m 0.00s
    sys     0m 0.00s
    / #
    

    这个请求大约在 5 秒内返回 200 (OK)。

  2. 使用 kubectl 设置调用外部服务 httpbin.org 的超时时间为 3 秒。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: httpbin-ext
    spec:
      hosts:
        - httpbin.org
      http:
      - timeout: 3s
        route:
          - destination:
              host: httpbin.org
            weight: 100
    EOF
    
  3. 几秒后,重新发出 curl 请求:

    1
    2
    3
    4
    5
    6
    
    $ kubectl exec -it $SOURCE_POD -c sleep sh
    / # time curl -o /dev/null -s -w "%{http_code}\n" http://httpbin.org/delay/5
    504
    real    0m 3.28s
    user    0m 0.00s
    sys     0m 0.00s
    

这一次,在 3 秒后出现了 504 (Gateway Timeout)。Istio 在 3 秒后切断了响应时间为 5 秒的 httpbin.org 服务。

方法三

在开始方法三之前,需要先清理对外部服务的受控访问

1
2
3
4
5
6
7
$ kubectl delete serviceentry baidu-ext httpbin-ext httpbin-https-ext
serviceentry.networking.istio.io "baidu-ext" deleted
serviceentry.networking.istio.io "httpbin-ext" deleted
serviceentry.networking.istio.io "httpbin-https-ext" deleted

$ kubectl delete virtualservice httpbin-ext --ignore-not-found=true
virtualservice.networking.istio.io "httpbin-ext" deleted

直接访问外部服务

如果要让特定范围的 ​​IP 完全绕过 Istio,则可以配置 Envoy sidecars 以防止它们拦截外部请求。要设置绕过 Istio,请更改 global.proxy.includeIPRangesglobal.proxy.excludeIPRanges 配置选项,并使用 kubectl apply 命令更新 istio-sidecar-injector 的配置。istio-sidecar-injector 配置的更新,影响的是新部署应用的 pod

注:与方法一使用 mode: ALLOW_ANY 流量策略来让 Istio sidecar 代理将调用传递给未知服务不同。更改 global.proxy.includeIPRangesglobal.proxy.excludeIPRanges 配置选项的方法,完全绕过了 sidecar,从而实质上禁用了指定 IP 的所有 Istio 功能。你不能mode: ALLOW_ANY 方法那样为特定的目标增量添加 service entries。 因此,仅当出于性能或其他原因无法使用 sidecar 配置外部访问时,才建议使用此配置方法

排除所有外部 IP 重定向到 Sidecar 代理的一种简单方法是将 global.proxy.includeIPRanges 配置选项设置为内部集群服务使用的 IP 范围。这些 IP 范围值取决于集群所在的平台

确定平台内部的 IP 范围
阿里云(AKS)

注:这里先提下阿里云,因为其设置与其他平台不同

  1. 查看集群信息,获取 Pod 网络 CIDR 和 Service CIDR,如 172.20.0.0/16 和 172.21.0.0/20

    faq

  2. 点击 Istio 管理,点击更新,找到 includeIPRanges 并设置为 Pod 网络 CIDR 和 Service CIDR

    faq

IBM Cloud Private
  1. 从 IBM Cloud Private 的配置文件 cluster/config.yaml 中获取你的 service_cluster_ip_range:

    1
    2
    
    $ cat cluster/config.yaml | grep service_cluster_ip_range
    service_cluster_ip_range: 10.0.0.1/24 //这里以 10.0.0.1/24 为例
    
  2. 使用 --set global.proxy.includeIPRanges="10.0.0.1/24"

IBM Cloud Kubernetes Service

使用 --set global.proxy.includeIPRanges="172.30.0.0/16\,172.21.0.0/16\,10.10.10.0/24"

Google Container Engine (GKE)

范围是不固定的,你需要运行 gcloud container clusters describe 命令来确定要使用的范围。举个例子:

1
2
3
$ gcloud container clusters describe XXXXXXX --zone=XXXXXX | grep -e clusterIpv4Cidr -e servicesIpv4Cidr
clusterIpv4Cidr: 10.4.0.0/14
servicesIpv4Cidr: 10.7.240.0/20

使用 --set global.proxy.includeIPRanges="10.4.0.0/14\,10.7.240.0/20"

Azure Container Service(ACS)

使用 --set global.proxy.includeIPRanges="10.244.0.0/16\,10.240.0.0/16

Minikube, Docker For Desktop, Bare Metal

默认值为 10.96.0.0/12,但不是固定的。使用以下命令确定您的实际值:

1
2
$ kubectl describe pod kube-apiserver -n kube-system | grep 'service-cluster-ip-range'
      --service-cluster-ip-range=10.96.0.0/12

使用 –set global.proxy.includeIPRanges=”10.96.0.0/12”

配置代理绕行

使用平台的 IP 范围更新 istio-sidecar-injector 的配置。比如,如果 IP 范围是 10.0.0.1/24,则使用一下命令:

1
istioctl manifest apply <the flags you used to install Istio> --set values.global.proxy.includeIPRanges="10.0.0.1/24"

安装 Istio 命令的基础上增加 --set values.global.proxy.includeIPRanges="10.0.0.1/24"

注:若你是按照本专栏开发环境搭建教程搭建环境的话,则使用 istioctl manifest apply --set profile=demo --set values.global.proxy.includeIPRanges="10.96.0.0/12" 命令配置代理绕行即可。

访问外部服务

由于绕行配置仅影响新的部署,因此需要重新部署 sleep 程序。

1
2
3
4
5
6
7
$ kubectl delete deployment sleep
deployment.extensions "sleep" deleted

$ kubectl apply -f samples/sleep/sleep.yaml
serviceaccount/sleep unchanged
service/sleep unchanged
deployment.apps/sleep created

在更新 istio-sidecar-injector configmap 和重新部署 sleep 程序后,Istio sidecar 将仅拦截和管理集群中的内部请求。 任何外部请求都会绕过 Sidecar,并直接到达其预期的目的地。举个例子:

1
2
3
4
5
6
7
8
9
10
$ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
$ kubectl exec -it $SOURCE_POD -c sleep curl http://httpbin.org/headers
{
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.org",
    "User-Agent": "curl/7.64.0",
    "X-Amzn-Trace-Id": "Root=1-5e89f4e2-524fd7a41c99025a43c403ce"
  }
}

与通过 HTTP 和 HTTPS 访问外部服务不同,你不会看到任何与 Istio sidecar 有关的请求头,并且发送到外部服务的请求不会出现在 Sidecar 的日志中。 绕过 Istio sidecar 意味着你不能再监视对外部服务的访问。

总结

本文先介绍了集群内无法访问外部服务的现象,然后阐述了该现象的原因,最后提供了三个方法供大家选用。

方法一通过修改 Istio sidecar 代理配置为 mode: ALLOW_ANY 以允许访问外部服务。使用这种方法时,你将无法监控对外部服务的访问无法利用 Istio 的流量控制功能

方法二通过添加 ServiceEntry,以允许访问外部服务。可以让你使用 Istio 服务网格所有的功能去调用集群内或集群外的服务,这是官方推荐的方法

方法三直接绕过了 Istio Sidecar 代理,使你的服务可以直接访问任意的外部服务。 但是,以这种方式配置代理需要了解集群提供商相关知识和配置。 与方法一类似,你也将失去对外部服务访问的监控,并且无法将 Istio 功能应用于外部服务的流量

方法二的做法类似于“白名单”,不但能达到访问外部服务的目的,并且可以像集群内部服务一样对待(可使用 Istio 的流量控制功能)。另外,即使服务受到入侵,由于“白名单”的设置入侵者也无法(或较难)将流量回传到入侵机器,进一步保证了服务的安全性。因此,强烈推荐大家使用方法二

最后,特别提醒一下大家。某些教程,如Istio 服务无法访问外网,开放外网权限includeOutboundIPRanges 设置为空是有问题的,这相当于将所有的服务都配置代理绕行,那 sidecar 就没起作用了,没了 sidecar 的 Istio 就没有灵魂了。

参考

延伸阅读


CatchZeng
Written by CatchZeng Follow
AI (Machine Learning) and DevOps enthusiast.