预置条件
安装kubernetes环境
kubernetes环境,学习环境推荐使用minikube。 为了规避官方网络问题,此处已经更改为阿里数据源。
minikube start --kubernetes-version=v1.23.8 --image-mirror-country='cn' --image-repository='registry.cn-hangzhou.aliyuncs.com/google_containers'
配置别名,补充在~/.bashrc文件中,source ~/.bashrc生效。
alias k="minikube kubectl --"
source <(kubectl completion bash)
安装Istio环境
参考官网教程:istio.io/latest/zh/d…
$ curl -L https://istio.io/downloadIstio | sh -
指定版本:curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.12.1 TARGET_ARCH=x86_64 sh -
$ cd istio-1.12.1
$ export PATH=$PWD/bin:$PATH
$ istioctl install --set profile=demo -y
部署和尝试
- 创建namespace
k create ns sidecar
- 给命名空间添加标签,指示 Istio 在部署应用的时候,自动注入 Envoy 边车代理:为 sidecar命名空间的所有pod进行sidecar注入
$ kubectl label namespace sidecar istio-injection=enabled
- 部署服务端nginx(deployment和service)
k apply -f nginx.yaml -n sidecar
piVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
检查pod发现有两个running状态的container。
xxx@xxx-virtual-machine:~/istio/101-master/module12/istio/4.sidecar$ k get pod -o wide -n sidecar
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-85b98978db-dqnnn 2/2 Running 0 27m 172.17.0.9 minikube <none> <none>
进一步查看该pod的yaml k get pod nginx-deployment-85b98978db-dqnnn -n sidecar -o yaml
,发现自动注入了两个新的container:istio-proxy和istio-init,其中istio-init在pod启动时生效,做一些iptables的初始化操作,执行完就会退出,所以运行中的只有nginx和istio-proxy两个。
- 部署客户端toolbox
k apply -f toolbox.yaml -n sidecar
。同上的nginx,pod中也会自动注入istio的container,此处不再赘述。
apiVersion: apps/v1
kind: Deployment
metadata:
name: toolbox
spec:
replicas: 1
selector:
matchLabels:
app: toolbox
template:
metadata:
labels:
app: toolbox
access: "true"
spec:
containers:
- name: toolbox
image: centos
command:
- tail
- -f
- /dev/null
- 进入客户端toolbox。
k get pod -n sidecar
查看pod名称,执行k exec -it toolbox-78555898fb-rtrf8 -n sidecar -- bash
进入容器内。 由于上面的nginx服务端同时启动了一个nginx的server,所以直接curl nginx
进行请求即可。输出如下:
[root@toolbox-78555898fb-rtrf8 /]# curl nginx
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
此时你可能纳闷,这个过程没感受到sidecar的存在呀,嗯,这恰恰就是istio的对用户透明的原则,在用户的视角是无需感受到sidecar的存在的,但是越是上层简单的事情,背后的原理越是复杂,下面大体描述下istio注入前后的通信路径的区别。
背后原理浅析
sidecar注入前
客户端请求->coredns解析到service的clusterIp->node上的iptables/ipvs规则进行DNAT负载均衡转发->服务端的pod。这样请求到达了对端的pod。
graph TD
客户端请求 --> coredns解析到service的clusterIp
coredns解析到service的clusterIp --> node上的iptables/ipvs规则进行DNAT负载均衡转发
node上的iptables/ipvs规则进行DNAT负载均衡转发 --> 服务端的pod
sidecar注入后
- 首先进到pod内部看下iptables信息。登陆到node节点,
minikube ssh
。
如果没有安装,下载地址:
https://github.com/kubernetes-sigs/cri-tools/releases
,解压安装:sudo tar xf crictl-v1.26.0-linux-amd64.tar -C /usr/local/bin/
。
- 查找toolbox的containerid。
root@minikube:/home/docker# crictl ps | grep -E 'toolbox|CONTAINER'
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID
af0c627a44a9e centos@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177 4 hours ago Running toolbox 0 1bd0d371324b5
- 查看该container在node上的pid。
root@minikube:/home/docker# docker inspect af0c627a44a9e | grep -i pid
"Pid": 76973,
- 进入该container的namespace,查看iptables信息。
root@minikube:/home/docker# nsenter -t 76973 -n iptables-save -t nat
理论上可以看到一系列的istio配置上的iptables规则,但是翻车了,没有输出来,但是我看请求的流量确实都被envoy代理了,研究半天无果,暂时跳过,后续再更新,借用截图展示理论上的iptables信息。
注意:15001和15006是真正被sidecar监听的。
[root@toolbox-78555898fb-rtrf8 /]# netstat -anp | grep 1500
tcp 0 0 127.0.0.1:15000 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:15004 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:15006 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:15001 0.0.0.0:* LISTEN -
流量是如何发出的
可以看到对于出口流量:任何出去的tcp流量都会被重定向到15001这个端口。
-A OUTPUT -p tcp -j ISTIO_OUTPUT
-A ISTIO_OUTPUT -j ISTIO_REDIRECT
-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001
- 查看客户端toolbox的istio listener情况。
xxx@xxx-virtual-machine:~/istio/101-master/module12/istio/4.sidecar$ istioctl pc listener -n sidecar toolbox-78555898fb-rtrf8
ADDRESS PORT MATCH DESTINATION
10.96.0.10 53 ALL Cluster: outbound|53||kube-dns.kube-system.svc.cluster.local
0.0.0.0 80 Trans: raw_buffer; App: http/1.1,h2c Route: 80
0.0.0.0 80 ALL PassthroughCluster
10.105.56.232 80 Trans: raw_buffer; App: http/1.1,h2c Route: ngx-svc.default.svc.cluster.local:80
10.105.56.232 80 ALL Cluster: outbound|80||ngx-svc.default.svc.cluster.local
10.102.180.107 443 ALL Cluster: outbound|443||istio-egressgateway.istio-system.svc.cluster.local
10.105.116.90 443 ALL Cluster: outbound|443||istiod.istio-system.svc.cluster.local
10.109.100.115 443 ALL Cluster: outbound|443||istio-ingressgateway.istio-system.svc.cluster.local
10.96.0.1 443 ALL Cluster: outbound|443||kubernetes.default.svc.cluster.local
10.96.0.10 9153 Trans: raw_buffer; App: http/1.1,h2c Route: kube-dns.kube-system.svc.cluster.local:9153
10.96.0.10 9153 ALL Cluster: outbound|9153||kube-dns.kube-system.svc.cluster.local
0.0.0.0 15001 ALL PassthroughCluster
0.0.0.0 15001 Addr: *:15001 Non-HTTP/Non-TCP
0.0.0.0 15006 Addr: *:15006 Non-HTTP/Non-TCP
0.0.0.0 15006 Trans: tls; App: istio-http/1.0,istio-http/1.1,istio-h2; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4
0.0.0.0 15006 Trans: raw_buffer; App: http/1.1,h2c; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4
0.0.0.0 15006 Trans: tls; App: TCP TLS; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4
0.0.0.0 15006 Trans: raw_buffer; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4
0.0.0.0 15006 Trans: tls; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4
0.0.0.0 15010 Trans: raw_buffer; App: http/1.1,h2c Route: 15010
0.0.0.0 15010 ALL PassthroughCluster
10.105.116.90 15012 ALL Cluster: outbound|15012||istiod.istio-system.svc.cluster.local
0.0.0.0 15014 Trans: raw_buffer; App: http/1.1,h2c Route: 15014
0.0.0.0 15014 ALL PassthroughCluster
0.0.0.0 15021 ALL Inline Route: /healthz/ready*
10.109.100.115 15021 Trans: raw_buffer; App: http/1.1,h2c Route: istio-ingressgateway.istio-system.svc.cluster.local:15021
10.109.100.115 15021 ALL Cluster: outbound|15021||istio-ingressgateway.istio-system.svc.cluster.local
0.0.0.0 15090 ALL Inline Route: /stats/prometheus*
10.109.100.115 15443 ALL Cluster: outbound|15443||istio-ingressgateway.istio-system.svc.cluster.local
10.109.100.115 31400 ALL Cluster: outbound|31400||istio-ingressgateway.istio-system.svc.cluster.local
istio开始启动的时候就会监听15001的端口,该端口接收到出口流量之后会转发到对应的虚拟端口的监听器。那怎么知道转发给那个虚拟端口的监听器呢,因为我们请求的是nginx的80端口,所以会请求到sidecar的80监听器, 客户端toolbox有哪些cluster信息
xxx@xxx-virtual-machine:~/istio/101-master/module12/istio/4.sidecar$ istioctl pc cluster -n sidecar toolbox-78555898fb-rtrf8
SERVICE FQDN PORT SUBSET DIRECTION TYPE DESTINATION RULE
BlackHoleCluster - - - STATIC
InboundPassthroughClusterIpv4 - - - ORIGINAL_DST
PassthroughCluster - - - ORIGINAL_DST
agent - - - STATIC
envoy-svc.default.svc.cluster.local 80 - outbound EDS
istio-egressgateway.istio-system.svc.cluster.local 80 - outbound EDS
istio-egressgateway.istio-system.svc.cluster.local 443 - outbound EDS
istio-ingressgateway.istio-system.svc.cluster.local 80 - outbound EDS
istio-ingressgateway.istio-system.svc.cluster.local 443 - outbound EDS
istio-ingressgateway.istio-system.svc.cluster.local 15021 - outbound EDS
istio-ingressgateway.istio-system.svc.cluster.local 15443 - outbound EDS
istio-ingressgateway.istio-system.svc.cluster.local 31400 - outbound EDS
istiod.istio-system.svc.cluster.local 443 - outbound EDS
istiod.istio-system.svc.cluster.local 15010 - outbound EDS
istiod.istio-system.svc.cluster.local 15012 - outbound EDS
istiod.istio-system.svc.cluster.local 15014 - outbound EDS
kube-dns.kube-system.svc.cluster.local 53 - outbound EDS
kube-dns.kube-system.svc.cluster.local 9153 - outbound EDS
kubernetes.default.svc.cluster.local 443 - outbound EDS
nginx.sidecar.svc.cluster.local 80 - outbound EDS
ngx-svc.default.svc.cluster.local 80 - outbound EDS
prometheus_stats - - - STATIC
sds-grpc - - - STATIC
simple.default.svc.cluster.local 80 - outbound EDS
xds-grpc - - - STATIC
zipkin - - - STRICT_DNS
可以看到其中就有nginx80 service对应的cluster信息nginx.sidecar.svc.cluster.local 80 - outbound EDS
进一步查看具体的80监听路由信息
xxx@xxx-virtual-machine:~/istio/101-master/module12/istio/4.sidecar$ istioctl pc route -n sidecar toolbox-78555898fb-rtrf8 --name=80
NAME DOMAINS MATCH VIRTUAL SERVICE
80 envoy-svc.default, 10.99.140.121 /*
80 istio-egressgateway.istio-system, 10.102.180.107 /*
80 istio-ingressgateway.istio-system, 10.109.100.115 /*
80 nginx, nginx.sidecar + 1 more... /*
80 ngx-svc.default, 10.105.56.232 /*
80 simple.default, 10.98.155.230 /*
具体路由配置信息可以加上-o json 进行输出,
**[
**{
"name":"80",
"virtualHosts":**[
**Object{...},
**Object{...},
**Object{...},
**Object{...},
**{
"name":"nginx.sidecar.svc.cluster.local:80",
"domains":**[
"nginx.sidecar.svc.cluster.local",
"nginx",
"nginx.sidecar.svc",
"nginx.sidecar",
"10.105.135.206"
],
"routes":**[
**{
"name":"default",
"match":**{
"prefix":"/"
},
"route":**{
"cluster":"outbound|80||nginx.sidecar.svc.cluster.local",
"timeout":"0s",
"retryPolicy":**{
"retryOn":"connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
"numRetries":2,
"retryHostPredicate":**[
**{
"name":"envoy.retry_host_predicates.previous_hosts",
"typedConfig":**{
"@type":"type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate"
}
}
],
"hostSelectionRetryMaxAttempts":"5",
"retriableStatusCodes":**[
503
]
},
"maxGrpcTimeout":"0s"
},
"decorator":**{
"operation":"nginx.sidecar.svc.cluster.local:80/*"
}
}
],
"includeRequestAttemptCount":true
},
**Object{...},
**Object{...}
],
"validateClusters":false,
"ignorePortInHostMatching":true
}
]
上面输出中"cluster":"outbound|80||nginx.sidecar.svc.cluster.local"
可以看出cluster信息,进一步查看cluster信息。
xxx@xxx-virtual-machine:~/istio/101-master/module12/istio/4.sidecar$ istioctl pc endpoint -n sidecar toolbox-78555898fb-rtrf8
ENDPOINT STATUS OUTLIER CHECK CLUSTER
172.17.0.9:80 HEALTHY OK outbound|80||nginx.sidecar.svc.cluster.local
检查服务端nginx的pod信息是否是172.17.0.9
xxx@xxx-virtual-machine:~/istio/101-master/module12/istio/4.sidecar$ k get pod -o wide -n sidecar
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-85b98978db-dqnnn 2/2 Running 0 7h55m 172.17.0.9 minikube <none> <none>
那这些代理信息sidecar是怎么拿到的呢? istiod实时监控着kubernetes的service信息,会把所有的service信息组装成一个个cluster信息。对应service代理的所有的pod就是一个个的endpoint。所以最终组织成了所有的cluster和endpoint的映射列表信息。 当需要注入sidecar的服务启动的时候,istiod就会把这些信息以xDS的形式下发给对应的sidecar中。
到了这一步,sidecar就会把该请求定向到具体的podip了,该请求会被转发出去,当然这个时候又会被iptables拦截,此时的流量是由sidecar发出的,用户信息是1337(sidecar写死的,启动的用户就是1337),该流量就会被直接发出。
总结下请求发出的路径:
graph TD
客户端请求 --> 被iptables拦截到15001虚拟监听器
被iptables拦截到15001虚拟监听器 --> 路由到80监听器
路由到80监听器 --> 找到nginx的cluster信息
找到nginx的cluster信息 --> 路由到一个endpoint的ip
路由到一个endpoint的ip --> iptables放行发出
流量是如何接收的
可以看到对于入口流量:任何出去的tcp流量都会被iptables拦截,被重定向到15006的监听端口。但是对于15008/22之类的管理类端口进行了排除,不会代理,其余的都会被重定向。
-A PREROUTING -p tcp -j ISTIO_INBOUND
-A ISTIO_INBOUND -p tcp -m tcp --dport 15008 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 22 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15090 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15021 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15020 -j RETURN
-A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT
-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006
- 查看listener信息,可以看到80这个listener。
xxx@xxx-virtual-machine:~/istio/101-master/module12/istio/4.sidecar$ istioctl pc listener -n sidecar nginx-deployment-85b98978db-dqnnn
ADDRESS PORT MATCH DESTINATION
0.0.0.0 80 Trans: raw_buffer; App: http/1.1,h2c Route: 80
0.0.0.0 80 ALL PassthroughCluster
- 查看交由哪个router进行的处理。
istioctl pc listener -n sidecar nginx-deployment-85b98978db-dqnnn -o json
,交由inbound|80||
这个路由处理。 - 查看80具体的router信息。
istioctl pc route -n sidecar nginx-deployment-85b98978db-dqnnn --name 80 -o json
sidecar根据路由到的"cluster": "outbound|80||ngx-svc.default.svc.cluster.local"
信息,察觉到请求的目的端起时就是自己,请求目的端就会是自己的80端口。当然该请求仍然被iptables拦截到,此时的请求变成OUTPUT chain,就不会被过滤,直接请求到nginx服务中。
总结下请求接收的路径:
graph TD
接收到客户端请求 --> 被iptables拦截到15006虚拟监听器
被iptables拦截到15006虚拟监听器 --> 路由到80监听器
路由到80监听器 --> 找到nginx的cluster信息
找到nginx的cluster信息 --> 发现目的端就是自己的服务则将请求转出
发现目的端就是自己的服务则将请求转出 --> iptables放行发出
istioctl本质上就是把config_dump信息下载到istiod,然后根据我们输出的参数将信息过滤出来。