原文连接:Istio, mTLS, debugging a 503 error
译者:杨传胜json
你们好,本文我将与大家分享我在 Istio 官方文档中尝试熔断教程时遇到的问题。我会记录下解决此问题的全部步骤,但愿对大家有所帮助。至少对我本身来讲,在整个排错过程当中学到了不少关于 Istio 的知识。api
个人实践步骤很是简单,总共分为两步:bash
curl
的客户端)是否是很是简单?让咱们开始吧!服务器
首先安装 httpbin 服务和客户端:app
$ kubectl create ns foo
$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n foo
$ kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n foo
$ kubectl -n foo get pod,svc
NAME READY STATUS RESTARTS AGE
pod/httpbin-6bbb775889-wcp45 2/2 Running 0 35s
pod/sleep-5b597748b4-77kj5 2/2 Running 0 35s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/httpbin ClusterIP 10.105.25.98 8000/TCP 36s
service/sleep ClusterIP 10.111.0.72 80/TCP 35s
复制代码
接下来就登入客户端 Pod 并使用 curl 来调用 httpbin
:负载均衡
$ kubectl -n foo exec -it -c sleep sleep-5b597748b4-77kj5 -- curl http://httpbin:8000/get
复制代码
{
"args": {},
"headers": {
"Accept": "*/*",
"Content-Length": "0",
"Host": "httpbin:8000",
"User-Agent": "curl/7.35.0",
"X-B3-Sampled": "1",
"X-B3-Spanid": "b5d006d3d9bf1f4d",
"X-B3-Traceid": "b5d006d3d9bf1f4d",
"X-Request-Id": "970b84b2-999b-990c-91b4-b6c8d2534e77"
},
"origin": "127.0.0.1",
"url": "http://httpbin:8000/get"
}
复制代码
到目前为止一切正常。下面建立一个目标规则针对 httpbin
服务设置断路器:curl
$ cat <<EOF | kubectl -n foo apply -f - apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: httpbin
spec:
host: httpbin
trafficPolicy:
connectionPool:
tcp:
maxConnections: 1
http:
http1MaxPendingRequests: 1
maxRequestsPerConnection: 1
outlierDetection:
consecutiveErrors: 1
interval: 1s
baseEjectionTime: 3m
maxEjectionPercent: 100
EOF
复制代码
如今尝试再次调用 httpbin 服务:socket
$ kubectl -n foo exec -it -c sleep sleep-5b597748b4-77kj5 -- curl http://httpbin:8000/get
upstream connect error or disconnect/reset before headers
复制代码
哎呀出事了!咱们可让 curl 输出更加详细的信息:tcp
$ kubectl -n foo exec -it -c sleep sleep-5b597748b4-77kj5 -- curl -v http://httpbin:8000/get
* Hostname was NOT found in DNS cache
* Trying 10.105.235.142...
* Connected to httpbin (10.105.235.142) port 8000 (#0)
> GET /get HTTP/1.1
> User-Agent: curl/7.35.0
> Host: httpbin:8000
> Accept: */*
>
< HTTP/1.1 503 Service Unavailable
< content-length: 57
< content-type: text/plain
< date: Tue, 28 Aug 2018 12:26:54 GMT
* Server envoy is not blacklisted
< server: envoy
<
* Connection #0 to host httpbin left intact
upstream connect error or disconnect/reset before headers
复制代码
发现了 503 错误。。。为何呢?根据刚刚建立的 DestinationRule
,应该能够成功调用 httpbin 服务的。由于咱们将 TCP 链接的最大数量设置为 1,而 curl 命令只生成了一个链接。那么到底哪里出问题了呢?ide
我能想到的第一件事就是经过查询 istio-proxy 的状态来验证熔断策略是否生效:
$ kubectl -n foo exec -it -c istio-proxy sleep-5b597748b4-77kj5 -- curl localhost:15000/stats | grep httpbin | grep pending
cluster.outbound|8000||httpbin.foo.svc.cluster.local.upstream_rq_pending_active: 0
cluster.outbound|8000||httpbin.foo.svc.cluster.local.upstream_rq_pending_failure_eject: 0
cluster.outbound|8000||httpbin.foo.svc.cluster.local.upstream_rq_pending_overflow: 0
cluster.outbound|8000||httpbin.foo.svc.cluster.local.upstream_rq_pending_total: 5
复制代码
upstream_rq_pending_overflow
的值是 0
,说明没有任何调用被标记为熔断。
Istio sidecar(名为
istio-proxy
的 Envoy 容器)暴露出 15000 端口以提供一些实用的功能,能够经过 HTTP 访问这个端口,例如打印相关服务的一些统计信息。
所以,在上面的的命令中,咱们在客户端 Pod(sleep-5b597748b4-77kj5)的 sidecar 容器(-c istio-proxy)中执行 curl(curl localhost:15000/stats),过滤出咱们要检查的服务的统计信息(| grep httpbin),而后过滤出熔断器挂起状态(| grep pending)。
为了确认 DestinationRule
才是罪魁祸首,我决定将它删除而后再尝试调用:
$ kubectl -n foo delete DestinationRule httpbin
destinationrule.networking.istio.io "httpbin" deleted
$ kubectl -n foo exec -it -c sleep sleep-5b597748b4-77kj5 -- curl -v http://httpbin:8000/get
...
< HTTP/1.1 200 OK
...
复制代码
再将该 DestinationRule
加回来,而后再次尝试调用:
...
< HTTP/1.1 503 Service Unavailable
...
复制代码
看来问题确实出在 DestinationRule 这里,可是仍是不知道为何,咱们须要进一步研究。我灵机一动,要不先来看看 Envoy(istio-proxy sidecar)的日志吧:
$ kubectl -n foo logs -f sleep-5b597748b4-77kj5 -c istio-proxy
# 在另外一个终端执行如下命令 (kubectl -n foo exec -it -c sleep sleep-5b597748b4-77kj5 -- curl -v http://httpbin:8000/get)
# 而后会输出下面的日志:
[2018-08-28T13:06:56.454Z] "GET /get HTTP/1.1" 503 UC 0 57 0 - "-" "curl/7.35.0" "19095d07-320a-9be0-8ba5-e0d08cf58f52" "httpbin:8000" "172.17.0.14:8000"
复制代码
并无看到什么有用的信息。日志告诉咱们 Envoy 从服务器收到了 503 错误,OK,那咱们就来检查一下服务器端(httpbin)的日志:
$ kubectl -n foo logs -f httpbin-94fdb8c79-h9zrq -c istio-proxy
# 在另外一个终端执行如下命令 (kubectl -n foo exec -it -c sleep sleep-5b597748b4-77kj5 -- curl -v http://httpbin:8000/get)
# 日志输出为空
复制代码
什么?日志输出中居然没有任何内容,就好像请求根本没有到达服务器同样。那么如今该怎么办呢,可不能够增长日志输出等级?也许请求已经收到了,只是没有被输出而已。
还记得我上面讲过的 Envoy 暴露了 15000 端口做为管理接口吗?咱们能够用它来获取统计数据。看看它都提供了哪些功能:
$ kubectl -n foo exec -it -c istio-proxy httpbin-94fdb8c79-h9zrq -- curl http://localhost:15000/help
admin commands are:
/: Admin home page
/certs: print certs on machine
...
/logging: query/change logging levels
...
复制代码
嘿嘿,彷佛找到了咱们须要的东西:/logging
,试试吧:
$ kubectl -n foo exec -it -c istio-proxy httpbin-94fdb8c79-h9zrq -- curl http://localhost:15000/logging?level=trace
active loggers:
admin: trace
...
复制代码
上面的命令将服务器 Envoy 的日志等级设为 trace
,该日志等级输出的日志信息最详细。关于管理接口的更多信息,请查看 Envoy 官方文档。如今咱们再来从新查看服务器 Envoy 的日志,但愿可以获得一些有用的信息:
$ kubectl -n foo logs -f httpbin-94fdb8c79-h9zrq -c istio-proxy
# 在另外一个终端执行如下命令 (kubectl -n foo exec -it -c sleep sleep-5b597748b4-77kj5 -- curl -v http://httpbin:8000/get)
# 而后会输出下面的日志:(我过滤了一些不相关的内容)
[debug][filter] external/envoy/source/extensions/filters/listener/original_dst/original_dst.cc:18] original_dst: New connection accepted
[debug][main] external/envoy/source/server/connection_handler_impl.cc:217] [C31] new connection
[trace][connection] external/envoy/source/common/network/connection_impl.cc:389] [C31] socket event: 2
[trace][connection] external/envoy/source/common/network/connection_impl.cc:457] [C31] write ready
[debug][connection] external/envoy/source/common/ssl/ssl_socket.cc:111] [C31] handshake error: 2
[trace][connection] external/envoy/source/common/network/connection_impl.cc:389] [C31] socket event: 3
[trace][connection] external/envoy/source/common/network/connection_impl.cc:457] [C31] write ready
[debug][connection] external/envoy/source/common/ssl/ssl_socket.cc:111] [C31] handshake error: 1
[debug][connection] external/envoy/source/common/ssl/ssl_socket.cc:139] [C31] SSL error: 268435612:SSL routines:OPENSSL_internal:HTTP_REQUEST
[debug][connection] external/envoy/source/common/network/connection_impl.cc:133] [C31] closing socket: 0
复制代码
如今咱们能够看到请求确实已经到达服务器了,但因为握手错误致使了请求失败,而且 Envoy 正在关闭链接。如今的问题是:为何会发生握手错误?为何会涉及到 SSL?
当在 Istio 中谈到 SSL 时,通常指的是双向 TLS。而后我就去查看 Istio 官方文档,视图找到与个人问题相关的内容,最后终于在 基础认证策略 这篇文档中找到了我想要的东西。
我发现我在部署 Istio 时启用了 Sidecar 之间的双向 TLS 认证!
检查一下:
$ kubectl get MeshPolicy default -o yaml
apiVersion: authentication.istio.io/v1alpha1
kind: MeshPolicy
metadata: ...
spec:
peers:
- mtls: {}
$ kubectl -n istio-system get DestinationRule default -o yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata: ...
spec:
host: '*.local'
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
复制代码
上面这些输出代表集群中开启了双向 TLS 认证,由于这些全局身份验证策略和目标规则只有在开启双向 TLS 认证时才会存在。
再回到最初的问题:为何调用 httpbin 服务会失败?如今咱们已经知道了网格中开启了双向 TLS 认证,经过阅读文档能够推断出服务器端仅接受使用 TLS 的加密请求,而客户端仍在使用明文请求。如今来从新修改一个问题:为何客户端(sleep pod)会使用明文来请求服务器端(httpbin pod)?
再次仔细阅读官方文档能够找到答案。双向 TLS 认证(mTLS
)在 Istio 中的工做方式很简单:它会建立一个默认的 DestinationRule
对象(名称为 default
),它表示网格中的全部客户端都使用双向 TLS。可是当咱们为了实现熔断策略建立本身的 DestinationRule
时,用本身的配置(根本就没有设置 TLS!)覆盖了默认配置。
这是 基础认证策略 文档中的原文:
除了认证场合以外,目标规则还有其它方面的应用,例如金丝雀部署。可是全部的目标规则都适用相同的优先顺序。所以,若是一个服务须要配置其它目标规则(例如配置负载均衡),那么新规则定义中必须包含相似的 TLS 块来定义 ISTIO_MUTUAL
模式,不然它将覆盖网格或命名空间范围的 TLS 设置并禁用 TLS。
如今知道问题出在哪了,解决办法就是:修改 DestinationRule
以包含 TLS 配置项:
cat <<EOF | kubectl -n foo apply -f - apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: httpbin
spec:
host: httpbin
trafficPolicy:
connectionPool:
tcp:
maxConnections: 1
http:
http1MaxPendingRequests: 1
maxRequestsPerConnection: 1
outlierDetection:
consecutiveErrors: 1
interval: 1s
baseEjectionTime: 3m
maxEjectionPercent: 100
tls:
mode: ISTIO_MUTUAL
EOF
复制代码
再次尝试调用 httpbin 服务:
kubectl -n foo exec -it -c sleep sleep-5b597748b4-77kj5 -- curl -v http://httpbin:8000/get
...
< HTTP/1.1 200 OK
...
复制代码
如今我能够继续实验熔断教程了!
总结: