【玩转服务网格】初试Istio

98 阅读5分钟

预置条件

安装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信息。

image.png

注意: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||这个路由处理。 image.png
  • 查看80具体的router信息。istioctl pc route -n sidecar nginx-deployment-85b98978db-dqnnn --name 80 -o json image.png

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,然后根据我们输出的参数将信息过滤出来。