为什么要使用Istio https://istio.io/latest/zh/
- HTTP、gRPC、WebSocket和TCP流量的自动负载均衡。
- 通过丰富的路由规则、重试、故障转移和故障注入,可以对流量行为进行细粒度控制。
- 可插入的策略层和配置API,支持访问控制、速率限制和配额。
- 对出入集群入口和出口中多有流量的自动度量指标、日志记录和跟踪。
- 通过强大的基于身份的验证和授权,在集群中实现安全的服务间通信。
流量管理
连接
- 通过简单的规则配置和流量路由,可以控制服务之间的流量和 API调用。Istio 简化了断路器、
超时和重试等服务级别属性的配置,并且可以轻松设置 A/B 测试、金丝雀部署和基于百分比的流量分割的分阶段部署等重要任务。
控制
- 通过更好地了解流量和开箱即用的故障恢复功能,可以在问题出现之前先发现问题,使调用更可靠,并且使您的网络更加强大——无论您面临什么条件。
安全
- 使开发人员可以专注于应用程序级别的安全性。Istio 提供底层安全通信信道,并大规模管理服务通信的认证、授权和加密。使用
Istio,服务通信在默认情况下是安全的,它允许跨多种协议和运行时一致地实施策略——所有这些都很少或根本不需要应用程序更改。 - 虽然 Istio 与平台无关,但将其与 Kubernetes(或基础架构)网络策略结合使用,其优势会更大,包括在网络和应用层保护 Pod 间或服务间通信的能力。
可观察性
istio生成以下类型的遥测数据,以提供对整个服务网格的可观察性:
- 指标:Istio 基于4 个监控的黄金标识(延迟、流量、错误、饱和)生成了一系列服务指标。Istio 还为网格控制平面提供了更详细的指标。除此以外还提供了一组默认的基于这些指标的网格监控仪表板。
- 分布式追踪:Istio 为每个服务生成分布式追踪 span,运维人员可以理解网格内服务的依赖和调用流程。
- 访问日志:当流量流入网格中的服务时,Istio可以生成每个请求的完整记录,包括源和目标的元数据。此信息使运维人员能够将服务行为的审查控制到单个工作负载实例的级别。
所有这些功能可以更有效的设置、监控和实施服务上的SLO,快速有效的检测和修复问题。
Envoy
主流七层代理的比较
Envoy 的优势
性能
- 在具备大量特性的同时,Envoy 提供极高的吞吐量和低尾部延迟差异,而 CPU 和 RAM 消耗却相对较少。
可扩展性
- Envoy在L4 和L7都提供了丰富的可插拔过滤器能力,使用户可以轻松添加开源版本中没有的功能。
API可配置性
- Envoy 提供了一组可以通过控制平面服务实现的管理 API。如果控制平面实现所有的 API,则可以使用通用引导配置在整个基础架构上运行 Envoy。所有进一步的配置更改通过管理服务器以无缝方式动态传送,因此 Envoy 从不需要重新启动。这使得 Envoy 成为通用数据平面, 当它与一个足够复杂的控制平面相结合时,会极大的降低整体运维的复杂性。
Istio 流量管理
- Gateway
- VirtualService
- DestinationRule
- ServiceEntry
- WorkloadEntry
- Sidecar
[root@vms120 istio]# k create ns sidecar
namespace/sidecar created
8. 设置这个 namespace 要自动注入 sidecar
[root@vms120 istio]# kubectl label namespace sidecar istio-injection=enabled
namespace/sidecar labeled
9. 查询创建的 webhookconfigure
[root@vms120 istio]# kubectl get mutatingwebhookconfigurations.admissionregistration.k8s.io
NAME WEBHOOKS AGE
istio-revision-tag-default 4 41m
istio-sidecar-injector 4 42m
10. 创建的 webhookconfigure 中的 istio-sidecar-injector 会监控webhook下的pod create,并调用 istio-system 下名为 istiod 的/inject 的服务器
[root@vms120 istio]# kubectl get mutatingwebhookconfigurations.admissionregistration.k8s.io istio-sidecar-injector -oyaml
...
service:
name: istiod
namespace: istio-system
path: /inject
port: 443
failurePolicy: Fail
matchPolicy: Equivalent
name: object.sidecar-injector.istio.io
namespaceSelector:
matchLabels:
istio.io/deactivated: never-match
objectSelector:
matchLabels:
istio.io/deactivated: never-match
reinvocationPolicy: Never
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
scope: '*'
sideEffects: None
创建pod验证sidecar注入
1. 创建
[root@vms120 httpbin]# kubectl apply -f /root/istio/istio-1.14.3/samples/httpbin/httpbin.yaml -n sidecar
serviceaccount/httpbin created
service/httpbin created
deployment.apps/httpbin created
[root@vms120 httpbin]# kubens sidecar
Context "kubernetes-admin@kubernetes" modified.
Active namespace is "sidecar".
2. 查看
[root@vms120 httpbin]# kubectl get pod
NAME READY STATUS RESTARTS AGE
httpbin-85d76b4bb6-t5cdx 2/2 Running 0 49s
[root@vms120 httpbin]# kubectl get pod httpbin-85d76b4bb6-t5cdx -o yaml
...
sidecar.istio.io/status: '{"initContainers":["istio-init"],"containers":["istio-proxy"],"volumes":["workload-socket","workload-certs","istio-envoy","istio-data","istio-podinfo","istio-token","istiod-ca-cert"],"imagePullSecrets":null,"revision":"default"}'
...
image: docker.io/istio/proxyv2:1.14.3
imagePullPolicy: IfNotPresent
name: istio-proxy
ports:
- containerPort: 15090
name: http-envoy-prom
protocol: TCP
...
查看pod详情发现自动创建了Envoy 的sidecar
- 一个镜像为:docker.io/istio/proxyv2:1.14.3 名为 istio-proxy 的容器,
- 添加了很多变量,
- 添加了一个initContainers,去修改了容器内部 iptable 的转发路径
进入一个容器 curl 另一个的svc,发现能通,分析解析过程
1. 创建了两个 deploy
[root@vms120 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
httpbin-85d76b4bb6-nrlqv 2/2 Running 2 (53s ago) 17h
nginx-85b98978db-dbdjl 2/2 Running 2 (53s ago) 17h
2. httpbin 的 svc 地址为 httpbin:8000
[root@vms120 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
httpbin-85d76b4bb6-nrlqv 2/2 Running 2 (3h9m ago) 20h
nginx-85b98978db-dbdjl 2/2 Running 2 (3h9m ago) 20h
3. 容器内部 curl
[root@vms120 httpbin]# kubectl exec -it httpbin-85d76b4bb6-nrlqv -- bash
root@httpbin-85d76b4bb6-nrlqv:/# curl nginx
Hello !!!
在没有 sidecar 时,整个数据链路是
- 客户端去访问服务器端时,这个服务会进行域名解析,告诉dns他的 CLUSTER-IP
- 客户端的请求会发到这个 CLUSTER-IP 上去
- 请求到达主机时,会经过主机的 ipatbels
- iptables 规则把 CLUSTER-IP 和对应 pod 的 IP
- 请求就相当于直连了 pod 的 IP
在使用 sidecar 后,请求就不是上面的链路了
Istio 的流量劫持机制
- 为用户应用注入 Sidecar
自动注入
[root@vms120 ~]# kubectl label namespace sidecar istio-injection=enabled
手动注入
[root@vms120 ~]# istioctl kube-inject -f bookinfo.yaml
- 注入后的结果
注入了 init-container istio-init
[root@vms120 ~]# istio-iptables -p 15001 -z 15006 -u 1337 -m REDIRECT -i * -x -b * -d 15090,15021,15020
注入了 sidecar container istio-proxy
流量分析
init container
分析客户端流量
1. 查看客户端 pod 进程
[root@vms121 ~]# docker inspect -f {{.State.Pid}} 7179971e46d2
4645
2. 使用 nsenter 进入进程查看iptables
[root@vms121 ~]# nsenter -t 4645 -n iptables-save -t nat
# Generated by iptables-save v1.4.21 on Thu Sep 1 14:13:35 2022
// 所有入站 TCP 流量走 ISTIO_INBOUND
-A PREROUTING -p tcp -j ISTIO_INBOUND
// 所有出站 TCP 流量走 ISTIO_OUTPUT, 客户端请求服务端,就是要走 OUTPUT
-A OUTPUT -p tcp -j ISTIO_OUTPUT
// 忽略 health check 等端口
-A ISTIO_INBOUND -p tcp -m tcp --dport 15008 -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
// 所有入站流量走 ISTIO_IN_REDIRECT
-A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT
// TCP 流量转发至 15006 端口
-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006
// loopback passthrough (回转通道)
-A ISTIO_OUTPUT -s 127.0.0.6/32 -o lo -j RETURN
// 从 loopback passthrough (回转通道) 出来,目标非本地地址, owner 是 envoy,交由ISTIO_IN_REDIRECT处理
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT
// 从 lo 出来, owner 非 envoy,return
-A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN
// owner 是 envoy,return
-A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN
// 如果以上规则都不匹配,则交给 ISTIO_REDIRECT
-A ISTIO_OUTPUT -j ISTIO_REDIRECT
// 交给 ISTIO_REDIRECT 后,所有流量都会 REDIRECT(重定向) 到 15001 端口,15001 就是 Envoy 端口
-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001
COMMIT
# Completed on Thu Sep 1 14:13:35 2022
详解
1. 将应用容器的所有流量都转发到 Envoy 的15001 端口.
2. 使用 istio-proxy 用户身份运行, UID 为1337,即 Envoy 所处的用户空间,这也是 istio-proxy 容器默认是使用的用户,对应yaml 配置中的runAsUser 字段.
3. 使用默认的 REDIRECT 模式来重定向流量.
4. 将所有出战流量都重定向到 Envoy 代理.
5. 将所有访问 80 端口(即应用容器 httpbin 的端口)的流量重定向到 Envoy 代理.
sidecar container
分析 sidecar container 流量
[root@vms120 httpbin]# istioctl proxy-config listener httpbin-85d76b4bb6-nrlqv --port 15001
ADDRESS PORT MATCH DESTINATION
0.0.0.0 15001 ALL PassthroughCluster
0.0.0.0 15001 Addr: *:15001 Non-HTTP/Non-TCP
[root@vms120 httpbin]# istioctl proxy-config listener httpbin-85d76b4bb6-nrlqv -ojson
[
{
"name": "virtualOutbound", // 虚拟监听器
"address": {
"socketAddress": {
"address": "0.0.0.0",
"portValue": 15001
}
},
...
"name": "0.0.0.0_80",
"address": {
"socketAddress": {
"address": "0.0.0.0",
"portValue": 80
}
},
"filterChains": [
{
"filterChainMatch": {
"transportProtocol": "raw_buffer",
"applicationProtocols": [
"http/1.1",
"h2c"
]
},
"filters": [
{
"name": "envoy.filters.network.http_connection_manager",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"statPrefix": "outbound_0.0.0.0_80",
"rds": {
"configSource": {
"ads": {},
"initialFetchTimeout": "0s",
"resourceApiVersion": "V3"
},
"routeConfigName": "80" // 路由配置命名为80
详解
istio 会生成以下监听器:
1. 0.0.0.0:15001 上的监听器接收进出 POD 的所有流量,然后将请求移交给虚拟监听器
2. 每个 service IP一个虚拟监听器,每个出站 TCP/HTTPS 流量一个非 HTTP 监听器
3. 每个 Pod 入站流量暴露的端口一个虚拟监听器
4. 每个出站 HTTP 流量的 HTTP 0.0.0.0 端口一个虚拟监听器
我们请求是到80端口的 HTTP 出站请求,这意味着它被切换到了0.0.0.0:80 虚拟监听器,然后监听器在其配置的 RDS 中查找路由配置,在这种情况下,它将查找由 Pilot 配置的 RDS 中的路由 80(“routeConfigName”: “80”)。
查看名为80的路由是怎么工作的
[root@vms120 httpbin]# istioctl proxy-config route httpbin-85d76b4bb6-nrlqv --name 80 -ojson
{
"name": "nginx.sidecar.svc.cluster.local:80",
"domains": [
"nginx.sidecar.svc.cluster.local",
"nginx.sidecar.svc.cluster.local:80",
"nginx",
"nginx:80",
"nginx.sidecar.svc",
"nginx.sidecar.svc:80",
"nginx.sidecar",
"nginx.sidecar:80",
"10.99.179.59",
"10.99.179.59:80"
],
"routes": [
{
"name": "default",
"match": {
"prefix": "/" // 不管匹配的URL是什么,都会交给下面的cluster处理
},
"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"
}
}
],
“prefix”: “/” 不管匹配的URL是什么,都会交给下面的cluster处理(“cluster”: “outbound|80||nginx.sidecar.svc.cluster.local”)
1. 按服务FQDN字段的子字符串筛选群集,发现类型为 EDS
[root@vms120 httpbin]# istioctl proxy-config cluster httpbin-85d76b4bb6-nrlqv --fqdn=nginx.sidecar.svc.cluster.local
SERVICE FQDN PORT SUBSET DIRECTION TYPE DESTINATION RULE
nginx.sidecar.svc.cluster.local 80 - outbound EDS
2. 查看他的 endpoint
[root@vms120 httpbin]# istioctl proxy-config endpoint httpbin-85d76b4bb6-nrlqv --port 80
ENDPOINT STATUS OUTLIER CHECK CLUSTER
10.244.216.30:80 HEALTHY OK outbound|80||nginx.sidecar.svc.cluster.local
10.244.216.31:80 HEALTHY OK outbound|8000||httpbin.sidecar.svc.cluster.local
10.244.72.150:80 HEALTHY OK outbound|80||kuboard-v3.kuboard.svc.cluster.local
// 可以看到 CLUSTER nginx.sidecar.svc.cluster.local实际上的终点地址为 10.244.216.30:80
3. 查看 pod 的IP 就是 CLUSTER nginx.sidecar.svc.cluster.local实际上的终点
[root@vms120 httpbin]# kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
httpbin-85d76b4bb6-nrlqv 2/2 Running 2 (5h25m ago) 22h 10.244.216.31 vms121.rhce.cc <none> <none>
nginx-85b98978db-dbdjl 2/2 Running 2 (5h25m ago) 22h 10.244.216.30 vms121.rhce.cc <none> <none>
这个时候请求知道了 endpoint ,但是因为进入iptables规则并没有被 RETURN
[root@vms121 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry.aliyuncs.com/google_containers/kube-proxy v1.23.10 71b9bf9750e1 2 weeks ago 112MB
istio/proxyv2 1.14.3 77d6b65e89f8 4 weeks ago 244MB
[root@vms121 ~]# docker history 77d6b65e89f8 --no-trunc
RUN /bin/sh -c useradd -m --uid 1337 istio-proxy && echo "istio-proxy ALL=NOPASSWD: ALL" >> /etc/sudoers # buildkit
istio/proxyv2 容器创建时会创建一个名为 istio-proxy uid 为1337 的用户,并以这个用户运行容器
查看 iptable 规则
// owner 是 envoy,return
-A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN
当流量从 Envoy 出来时是 uid 为1337的用户,符合 -A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
客户端请求出站链路总结
使用 sidecar 后的链路
- istio 会获取 当前集群内的所有 service 信息并组成一个个的 cluster,一个service 就会有一个 CLUSTER,一个 Pod 会生成一个 ENDPOINT
- istio 把对应的 CLUSTER 和 ENDPOINT 组合起来
- 当我们插入sidecar 去启动一个服务时,ENDPOINT 的端口、outbound 的端口、route 的配置、cluster 的配置都会被配置好
- 第一次客户端流量出来时,会被iptables 劫持,转到15001 去(-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001)
- 15001 的 outbound 转到 80 端口 ,然后经过 Envoy 的配置确定了服务端的 ip
- 请求继续往外转时又经历了 iptables,发现在iptables中有符合的规则( -A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN),被return 后就到了服务端