Kubernetes - Istio
CRD
核心为
networking
1 | $ istioctl version |
Sidecar
创建 namespace
1 | $ k create ns sidecar |
为新建的 namespace 打上 label,该 namespace 下的 Pod 需要自动插入 sidecar
1 | $ k label ns sidecar istio-injection=enabled |
k get mutatingwebhookconfigurations.admissionregistration.k8s.io istio-sidecar-injector -oyaml
监听到Pod
的创建事件
后,调用istio-system.istiod:443/inject
1 | apiVersion: admissionregistration.k8s.io/v1 |
服务端:Deployment + Service
1 | apiVersion: apps/v1 |
部署服务端,Pod 中注入了 Sidecar,总共 2 个容器
1 | $ k apply -f nginx.yaml -n sidecar |
k get po -n sidecar nginx-deployment-7fc7f5758-46mzh -oyaml
istio/proxyv2
本质上是一个Envoy
Sidecar- 还插入了一个
initContainer
,执行istio-iptables
命令,修改了容器内部
的iptables
规则
1 | apiVersion: v1 |
istio-proxy - sidecar; istio-init - initContainer
同一个 Pod 中的所有容器是共用
网络 Namespace,所以 initContainer 能修改 iptables 规则并生效
1 | $ docker ps -a | grep nginx |
客户端
1 | kind: Deployment |
部署客户端,同样注入了 Sidecar
在没有 Sidecar 时的数据链路:
- 先对
Service
做域名解析,CoreDNS
告知ClusterIP
- 请求被发送到
ClusterIP
,到达宿主后,会有iptables/ipvs
规则,通过DNAT
指向PodIP
1 | $ k apply -f toolbox.yaml -n sidecar |
在有 Sidecar 时的数据链路
客户端,
iptables
被修改,流量被劫持
1 | $ docker ps | grep toolbox |
往外走的流量,首先经过
OUTPUT
Chain
效果:任何往外走的TCP
流量,都会转发到15001
端口(即 Envoy 监听)
- -A OUTPUT -p tcp -j ISTIO_OUTPUT
- 是 TCP 协议则走
ISTIO_OUTPUT
- 是 TCP 协议则走
- ISTIO_OUTPUT 前面的规则都不匹配,直接到
ISTIO_REDIRECT
- ISTIO_REDIRECT 转发到
15001
端口
listener
1 | $ istioctl --help |
istioctl proxy-config -n sidecar listener toolbox-97948fc8-ptlct -ojson
routeConfigName
- 80
1 | ... |
route
1 | $ istioctl proxy-config -n sidecar route toolbox-97948fc8-ptlct |
istioctl proxy-config -n sidecar route toolbox-97948fc8-ptlct –name=80 -ojson
cluster
- outbound|80||nginx.sidecar.svc.cluster.local
1 | [ |
cluster
1 | $ istioctl proxy-config -n sidecar cluster toolbox-97948fc8-ptlct |
endpoint
1 | $ istioctl proxy-config -n sidecar endpoint toolbox-97948fc8-ptlct --cluster="outbound|80||nginx.sidecar.svc.cluster.local" |
Envoy Sidecar 会
服务发现
当前 Kubernetes 集群的Service
和Endpoint
信息,并将其组装成Envoy
配置
基于 Envoy Sidecar 的数据链路,不再依赖于宿主上的iptables/ipvs
,在 Envoy Sidecar 中已经做好了路由判决
此时的流量要往 Nginx Pod 转发,同样要经过 iptables OUTPUT
useradd -m –uid1337
表明这是来自于 Envoy Sidecar 的 OUTPUT 流量,不会再进入到 ISTIO_REDIRECT,而是直接 RETURN,避免死循环
1 | $ docker images | grep istio/proxyv2 |
入站流量,先通过 PREROUTING
1 | $ docker ps | grep nginx |
效果:非特殊端口的 TCP 流量,转发到 15006 端口
- -A PREROUTING -p tcp -j ISTIO_INBOUND
- 任何 TCP 流量都会跳转到 ISTIO_INBOUND
- ISTIO_INBOUND 针对特定端口(15008、15090、15021、15020)不处理
- 80 端口会跳转到 ISTIO_IN_REDIRECT,进而转发到 15006 端口
listener
1 | $ istioctl proxy-config -n sidecar listener nginx-deployment-7fc7f5758-46mzh --port=15006 |
istioctl proxy-config -n sidecar listener nginx-deployment-7fc7f5758-46mzh -ojson
route -inbound|80||
1 | ... |
istioctl proxy-config -n sidecar route nginx-deployment-7fc7f5758-46mzh –name=80 -ojson
cluster -outbound|80||nginx.sidecar.svc.cluster.local
1 | [ |
istioctl proxy-config -n sidecar endpoint nginx-deployment-7fc7f5758-46mzh –cluster=”outbound|80||nginx.sidecar.svc.cluster.local” -ojson
1 | [ |
此时 Envoy Sidecar 准备执行 OUTPUT,与客户端类似,能标识为 Envoy Sidecar 的流量,最终到达实际的 Nginx 容器
获取 Envoy Config(非常大),Istio 默认会服务发现
所有
的 Service 和 Endpoint,对于大集群,存在性能隐患
1 | $ k exec -it -n sidecar toolbox-97948fc8-ptlct -- bash |
Envoy 只监听了
15001
和15006
,其内部配置的其余端口,并没有实际绑定 Socket
主要能力
流量管理
服务通信
- 客户端不知道服务端不同版本之间的差异
- 客户端使用服务端的主机名或者 IP 继续访问服务端
- Envoy Sidecar
拦截并转发
客户端与服务端之间的所有
请求和相应
- Istio 为同一服务版本的多个实例提供
流量负载均衡
- Istio
不提供 DNS
,应用程序尝试使 Kubernetes 的 DNS 服务来解析 FQDN
Ingress / Egress
Istio 假设进入和离开服务网格的
所有流量
都会通过 Envoy 代理进行传输
- 将 Envoy 部署在服务之前,可以进行 A/B 测试、金丝雀部署
- 使用 Envoy 将流量路由到外部 Web 服务,并为这些服务添加超时控制、重试、断路器等功能,并获得各种指标
Ingress 本身并非成熟模型,无法完成
高级流量管理
,Istio 通过一套模型统一
:入站流量、东西向流量、出站流量
服务发现 / 负载均衡
- Istio 负载均衡服务网格中
实例
之间的通信 - Istio 假设存在
服务注册表
,用来跟踪服务的Pod/VM
- 假设服务的新实例
自动注册
到服务注册表,并且不健康的实例将会被自动删除
- 假设服务的新实例
- Pilot 使用
服务注册表
的信息,并提供与平台无关的服务发现
接口- 服务网格中的
Envoy
实例执行服务发现
,并相应地更新其负载均衡池
- 服务网格中的
- 服务网格中的服务使用
FQDN
来访问其它服务- 服务的
所有 HTTP 流量
都会通过Envoy
自动重新路由
- Envoy 在
负载均衡池
中的实例之间分发流量
- 服务的
- Envoy 支持复杂的负载均衡算法,但 Istio 仅支持:
轮询
、随机
、带权重的最少请求
- Envoy 还会
定期检查
负载均衡池中每个实例的运行状况- Envoy 遵循
熔断器
风格模式,根据健康检查 API 调用的失败率
将实例分为不健康和健康- 当实例的健康检查
失败次数
超过阈值,将被从负载均衡池中弹出
- 当实例的健康检查
成功次数
超过阈值,将被添加
到负载均衡池
- 当实例的健康检查
- 如果实例响应
HTTP 503
,将立即弹出
- Envoy 遵循
故障处理
超时
处理- 基于超时预算的
重试
机制 - 基于并发连接和请求的
流量控制
- 对负载均衡器成员的
健康检查
- 细粒度的
熔断
机制
配置微调
- Istio 可以为每个服务设置
全局默认值
- 客户端可以通过特殊的 HTTP 头
覆盖
超时和重试的默认值- x-envoy-upstream-rq-timeout-ms
- x-envoy-max-retries
故障注入
- Istio 允许在
网络层
按协议
注入错误 - 注入的错误可以
基于特定的条件
,也可以设置出现错误的比例
Delay
- 提高网络延迟Aborts
- 直接返回特定的错误码
规则配置
Key | Value |
---|---|
VirtualService |
在 Istio 中定义路由规则 ,控制如何路由到服务上 |
DestinationRule |
VirtualService 路由生效后,配置应用与请求的策略集 |
ServiceEntry |
在 Istio 之外 启用对服务的请求 |
Gateway |
为 HTTP/TCP 流量配置负载均衡器 常在服务网格边缘,启用应用程序的入口流量 |
Ingress
创建 namespace
1 | $ k create ns simple |
Deployment + Service
1 | apiVersion: apps/v1 |
1 | $ k apply -f simple.yaml -n simple |
通过 Gateway 将服务发布到集群外
1 | apiVersion: networking.istio.io/v1beta1 |
VirtualService
- 对应Envoy Route
- host 为 simple.zhongmingmao.io 且端口为 80 的请求,将被转发到 simple.simple.svc.cluster.local:80
- gateways - simple,与 Gateway 产生关联
Gateway
- 对应Envoy Listener
istio: ingressgateway
实际选中的是 istio-ingressgateway-779c475c44-j4tds- 语义:往 istio-ingressgateway 的 Pod 插入一些规则
- 在 ingressgateway 上监听 http://simple.zhongmingmao.io:80
istio-ingressgateway-779c475c44-j4tds 带有
istio=ingressgateway
,本质也是一个 Envoy
1 | $ k get po -n istio-system --show-labels |
应用 VirtualService 和 Gateway
1 | $ k apply -f istio-specs.yaml -n simple |
细化七层路由规则
1 | apiVersion: networking.istio.io/v1beta1 |
1 | $ k apply -f istio-specs.yaml -n simple |
HTTPS
创建 namespace
1 | $ k create ns securesvc |
Deployment + Service
1 | apiVersion: apps/v1 |
1 | $ k create -f httpserver.yaml -n securesvc |
生成证书,需要放到
istio-system
下,Istio 监听 443 的时候需要 TLS
1 | $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=zhongmingmao Inc./CN=*.zhongmingmao.io' -keyout zhongmingmao.io.key -out zhongmingmao.io.crt |
VirtualService + Gateway
1 | apiVersion: networking.istio.io/v1beta1 |
1 | $ k apply -f istio-specs.yaml -n securesvc |
对于 HTTPS,一个 443 端口,可能支持了很多域名,需要绑定很多证书
请求借助 Server Name Indication 机制(–resolve,新增 DNS 缓存,相当于修改 /etc/hosts)
1 | $ curl --resolve httpsserver.zhongmingmao.io:443:10.108.4.104 https://httpsserver.zhongmingmao.io -v -k |
Canary
创建 namespace
1 | $ k create ns canary |
Canary V1
1 | apiVersion: apps/v1 |
1 | $ k apply -f canary-v1.yaml -n canary |
Client
1 | apiVersion: apps/v1 |
1 | $ k apply -f toolbox.yaml -n canary |
Canary V2
1 | apiVersion: apps/v1 |
1 | $ k apply -f canary-v2.yaml -n canary |
VirtualService + DestinationRule
1 | apiVersion: networking.istio.io/v1beta1 |
访问 Canary V2
1 | $ k apply -f istio-specs.yaml -n canary |
访问 Canary V1
1 | $ k exec -n canary toolbox-97948fc8-ptlct -- curl -sI canary |
故障注入
1 | apiVersion: networking.istio.io/v1beta1 |
5.755 s ≈ 6 s
1 | $ k apply -f istio-specs.yaml -n canary |
网络栈
iptables/ipvs 基于 netfilter 框架(内核协议栈),优化空间很小
Cilium 基于 eBPF
- 新版 Linux Kernel 提供了很多的 Hook,允许插入自定义程序(并非规则)
- 对 Linux Kernel 非常熟悉,编写 C 代码,编译成符合 eBPF 要求的机器码后
- 最后通过 eBPF 的用户态程序加载到 Linux Kernel
- Cilium 充分利用了 Hook
- 建立 TCP 连接依然会走内核协议栈,依然有开销
- 但
数据拷贝
,可以直接在用户态
进行 Socket 对拷,不需要再经过内核协议栈