istio: 无法提供内部访问外部服务
现象
能够内部无法访问外部服务。
- 在部署测试服务
kubectl apply -f samples/sleep/sleep.yaml
- 设置环境变量
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
$ 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的代理配置,查看是否允许传递未知服务的请求。
$ 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
$ 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
验证配置
$ kubectl get configmap istio -n istio-system -o yaml | grep -o "mode: ALLOW_ANY"
mode: ALLOW_ANY
mode: ALLOW_ANY
重启部署
$ kubectl rollout restart deployment sleep
deployment.extensions/sleep restarted
访问外部服务
$ 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号 <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
。
$ 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服务
- 添加ServiceEntry,允许访问httpbin的HTTP服务
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
- 从sleep向外部发送httpbin的HTTP服务请求
$ 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代理添加的标题:X-Envoy-Decorator-Operation,表示访问外部的流量经过了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服务
- 添加ServiceEntry,允许访问httpbin的HTTPS服务
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
- 从sleep向外部发送httpbin的HTTPS服务请求
$ 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
- 检查睡眠的边车
$ 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作为替代,设置对服务访问的超时规则。
- 向外部服务httpbin.org的/ delay端点发出curl请求:
$ 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)。
-
使用kubectl设置调用外部服务httpbin.org的超时时间为3秒。
$ 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
- 几秒后,重新发出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服务。
方法三
在开始方法三之前,需要先清理对外部服务的预先访问
$ 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.includeIPRanges
或global.proxy.excludeIPRanges
配置选项,并使用kubectl apply命令更新istio-sidecar-injector的配置。istio-sidecar-injector配置的更新,影响的是新部署应用的pod。
注:与方法一使用
mode: ALLOW_ANY
流量策略来让Istio sidecar代理将调用传递给未知服务不同。更改global.proxy.includeIPRanges
或global.proxy.excludeIPRanges
配置选项的方法,完全绕过了sidecar,从而替换了指定IP的所有Istio功能。您不能像mode: ALLOW_ANY
方法为那样的特定目标增量添加服务项。因此,仅当出于性能或其他原因无法使用三轮配置外部访问时,才建议使用此配置方法。
排除所有外部IP重置到Sidecar代理的一种简单方法是将global.proxy.includeIPRanges
配置选项设置为内部可用服务使用的IP范围。这些IP范围值可以取代所在的平台。
确定平台内部的IP范围
阿里云(AKS)
注:这里先提下阿里云,因为其设置与其他平台不同。
- 查看更多信息,获取Pod网络CIDR和Service CIDR,如172.20.0.0/16和172.21.0.0/20
- 点击Istio管理,单击更新,找到
includeIPRanges
并设置为Pod网络CIDR和Service CIDR
IBM Cloud Private
- 从IBM Cloud Private的配置文件cluster / config.yaml中获取您的service_cluster_ip_range:
$ cat cluster/config.yaml | grep service_cluster_ip_range
service_cluster_ip_range: 10.0.0.1/24 //这里以 10.0.0.1/24 为例
- 使用
--set global.proxy.includeIPRanges="10.0.0.1/24"
IBM Cloud Kubernetes服务
使用 --set global.proxy.includeIPRanges="172.30.0.0/16\,172.21.0.0/16\,10.10.10.0/24"
Google容器引擎(GKE)
范围是不固定的,你需要运行gcloud容器集群描述了命令来确定要使用的范围。举个例子:
$ 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容器服务(ACS)
使用 --set global.proxy.includeIPRanges="10.244.0.0/16\,10.240.0.0/16
Minikube,用于桌面的Docker,裸机
默认值10.96.0.0/12,但不是固定的。使用以下命令确定您的实际值:
$ 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,则使用一下命令:
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程序。
$ 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,并直接到达其预期的目的地。举个例子:
$ 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代理配置为mode: ALLOW_ANY
以允许访问外部服务。使用这种方法时,您将无法监控对外部服务的访问或无法利用Istio的流量控制功能。
方法二通过添加ServiceEntry,以允许访问外部服务。可以让你使用Istio服务网格所有的功能去调用内部或转换外部的服务,这是官方推荐的方法。
三方法直接绕过了Istio边门代理,使你的服务可以直接访问任意的外部服务。但是,这种以配置方式代理需要了解集群提供商相关知识状语从句:配置。与方法一类似,也。你将失去对外部服务访问的监控,并且无法将Istio功能添加到外部服务。
方法二的做法对准“白名单”,不但能达到访问外部服务的目的,并且可以像内置内部服务一样对待(可使用Istio的流量控制功能)。另外,甚至服务遭到入侵,由于“白名单”的设置入侵者也无法(或较难)将流量回传到入侵机器,进一步保证了服务的安全性。因此,强烈推荐大家使用方法二。
最后,特别提醒一下大家。某些教程,如Istio服务无法访问外网,开放外网权限将includeOutboundIPRanges
设置为空是有问题的,这相当于将所有的服务都配置代理绕行,那sidecar就没起作用了,没了sidecar的Istio就没有灵魂了。。