引言 Sidecar 是 Istio 提供的一种 CRD 资源, 用于对注入到业务 pod 中的流量劫持 sidecar 进行自定义的配置。
默认情况下,Istio 有全局统一的配置。 使用 Sidecar CRD 可以用于对 Namespace 内的 sidecar 组件进行更精细的配置, 包括指定ingress, egress 监听的 端口、协议,已经转发到的后端等。
本文主要参考 Istio 社区官方文档sidecar 的内容。鉴于本人水平有限,如有错误和不准确的地方,欢迎批评指正,感谢。
下面,本文将分别从 Spec 配置项说明、基本应用场景、sidecar iptables 流量劫持原理、配置下发源码分析四个方面,对 Istio Sidecar 做一个比较全面的介绍。
Spec 字段详细说明 istio 文档中,值得注意的点:
Sidecar CRD 是 namespace 粒度的资源。配置在 namespace 下的Pod中生效。每个 Namespace 中只能有一个全局的 sidecar 配置
支持在 meshconfig 所在 root namespace(一般为 istio-system.) 中配置的 sidecar crd 为集群缺省生效的配置。
指定 workloadSelector 来进一步限定生效的 Pod 集合。匹配规则按最 match 优先的策略,即 带 selector 的 > 不带 selector 的 > 集群全局配置的
对网关无效。
结合例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 apiVersion: networking.istio.io/v1beta1 kind: Sidecar metadata: name: partial-ip-tables namespace: prod-us1 spec: workloadSelector: labels: app: productpage ingress: - bind: 172.16 .1 .32 port: number: 80 protocol: HTTP name: somename defaultEndpoint: 127.0 .0 .1 :8080 captureMode: NONE egress: - captureMode: IPTABLES hosts: - "*/*"
需要指出的:
ingress: 是数组类型。可配置多个 inbound 监听规则
1 2 3 4 5 6 7 8 9 10 11 12 ingress: - bind: 172.16.1.32 # 非必需, 监听 Ip, UDS 不支持 port: # 必需 number: 80 protocol: HTTPS name: somename defaultEndpoint: unix:///var/run/someuds.sock # 必需, Forward 地址,支持 UDS captureMode: # 非必需,拦截模式 tls: # 非必需, 证书配置 mode: SIMPLE privateKey: "/etc/certs/privatekey.pem" serverCertificate: "/etc/certs/servercert.pem"
其中需要注意:
captureMode:
DEFAULT: 继承全局配置
IPTABLES: iptables redirection 的方式捕获流量
None: 没有流量劫持能力。在 ingress 中,用于监听进程,需要保证端口无占用。在 egress 中配置,需要确保应用进程能够直接显式的和对应endpoints Ip 建立连接。
egress: 也是数组类型,可配置多个 outbound 监听规则。
例:允许业务通过 127.0.0.1:3306 访问远端的mysql服务器
1 2 3 4 5 6 7 8 9 egress: - port: number: 3306 protocol: MYSQL name: egressmysql captureMode: NONE bind: 127.0 .0 .1 hosts: - "*/mysql.foo.com"
其中需要注意:
egress 中 bind: 支持IP,也支持 UDS ,与 ingress 不同. 但如果是 UDS, CaptureMode 不能为 IPTABLES
hosts: 配置格式:namespace/dnsName
,
dnsName:FQDN, 支持通配符, *.example.com
namespace: 支持 *
表示所有,.
表示当前,~
表示没有。 特别场景,用~/*
来禁止 proxy egress 代理功能。
outboundTrafficPolicy: 非必须,出向流量转发策略。
ALLOW_ANY: 适用于应用的出向流量无法预测且无规则,全部转发。
REGISTRY_ONLY:限定在网格内感知到的服务,如通过 K8s Services 或 ServiceEntry 注册的服务。
几个应用场景介绍
在 VM 场景下,无 Iptables-rule 规则(无init-container)或多网段中只有部分网段(192.168/16)有规则, Sidecar CRD 是唯一的能够配置 Proxy 实现流量代理功能的方法。
示例:192.168.0.0/16 subnet 上有 iptables 规则, 而另外的网卡 172.16.0.0/16 上没有,则通过这种方式,将服务暴露在 172.16 网段的IP上,用于接受来自 172.16 subnet 的流量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 apiVersion: networking.istio.io/v1beta1 kind: Sidecar metadata: name: partial-ip-tables namespace: prod-us1 spec: workloadSelector: labels: app: productpage ingress: - bind: 172.16.1.32 port: number: 80 # binds to 172.16.1.32:80 protocol: HTTP name: somename defaultEndpoint: 127.0.0.1:8080 captureMode: NONE egress: # use the system detected defaults # sets up configuration to handle outbound traffic to services # in 192.168.0.0/16 subnet, based on information provided by the # service registry - captureMode: IPTABLES hosts: - "*/*"
Sidecar TLS 终结
1 2 3 4 5 6 7 8 9 10 11 12 13 14 apiVersion: v1 kind: Service metadata: name: ratings labels: app: ratings service: ratings spec: ports: - port: 8443 name: https targetPort: 80 selector: app: ratings
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 apiVersion: networking.istio.io/v1alpha3 kind: Sidecar metadata: name: ratings namespace: prod-us1 spec: workloadSelector: labels: app: ratings ingress: - port: number: 80 protocol: HTTPS name: somename defaultEndpoint: unix:///var/run/someuds.sock tls: mode: SIMPLE privateKey: "/etc/certs/privatekey.pem" serverCertificate: "/etc/certs/servercert.pem"
Sidecar UDS 加速优化
网络包流向图:
consumer(http://uds-demo-provider.uds.svc.cluster.local:80 ) → uds → envoy → upstream(服务发现uds-demo-provider:80) → envoy(15889) → uds → provider
client 侧配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 apiVersion: networking.istio.io/v1alpha3 kind: Sidecar metadata: name: uds-demo-consumer-uds namespace: uds spec: workloadSelector: labels: app: uds-demo-consumer-default egress: - port: number: 0 protocol: HTTP name: http - captureMode: NONE bind: unix:///var/mesh/uds.sock # envoy 监听的 uds 地址 hosts: - "*/*"
Server 侧配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 apiVersion: networking.istio.io/v1alpha3 kind: Sidecar metadata: name: uds-demo-privider-uds namespace: uds spec: workloadSelector: labels: app: uds-demo-service-default ingress: - port: number: 15889 protocol: HTTP name: http defaultEndpoint: unix:///var/mesh/uds.sock ## captureMode: NONE --- apiVersion: v1 kind: Service metadata: labels: app: uds-demo-service-default name: uds-demo-provider namespace: uds spec: ports: - name: http port: 80 protocol: TCP targetPort: 15889 selector: app: uds-demo-service-default
Sidecar 使用 iptables 实现流量劫持的细节
示例,sidecar network namespace 中 详细 Iptable 规则列表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 Chain PREROUTING (policy ACCEPT) target prot opt source destination ISTIO_INBOUND tcp -- anywhere anywhere Chain INPUT (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination ISTIO_OUTPUT tcp -- anywhere anywhere RETURN udp -- anywhere anywhere udp dpt:domain owner UID match 1337 RETURN udp -- anywhere anywhere udp dpt:domain owner GID match 1337 REDIRECT udp -- anywhere anywhere udp dpt:domain redir ports 15053 Chain POSTROUTING (policy ACCEPT) target prot opt source destination Chain ISTIO_INBOUND (1 references) target prot opt source destination RETURN tcp -- anywhere anywhere tcp dpt:15008 RETURN tcp -- anywhere anywhere tcp dpt:15090 RETURN tcp -- anywhere anywhere tcp dpt:15021 RETURN tcp -- anywhere anywhere tcp dpt:15020 ISTIO_IN_REDIRECT tcp -- anywhere anywhere Chain ISTIO_IN_REDIRECT (3 references) target prot opt source destination REDIRECT tcp -- anywhere anywhere redir ports 15006 Chain ISTIO_OUTPUT (1 references) target prot opt source destination RETURN all -- 127.0.0.6 anywhere ISTIO_IN_REDIRECT tcp -- anywhere !localhost tcp dpt:!domain owner UID match 1337 RETURN tcp -- anywhere anywhere tcp dpt:!domain ! owner UID match 1337 RETURN all -- anywhere anywhere owner UID match 1337 ISTIO_IN_REDIRECT all -- anywhere !localhost owner GID match 1337 RETURN tcp -- anywhere anywhere tcp dpt:!domain ! owner GID match 1337 RETURN all -- anywhere anywhere owner GID match 1337 REDIRECT tcp -- anywhere anywhere tcp dpt:domain redir ports 15053 RETURN all -- anywhere localhost ISTIO_REDIRECT all -- anywhere anywhere Chain ISTIO_REDIRECT (1 references) target prot opt source destination REDIRECT tcp -- anywhere anywhere redir ports 15001
iptables 潜在问题:
依赖 contrack 连接追踪,大量并发连接,会导致 contrack 表爆满。
全局生效,不利于管控。
通过 loopback 实现,outbound 会两次经过协议栈,性能有损耗。
社区优化思路:
ebpf
tproxy inbound 优化
hook connect 处理 outbound
推荐阅读:
https://jimmysong.io/blog/sidecar-injection-iptables-and-traffic-routing/
https://jimmysong.io/blog/istio-sidecar-traffic-types/
https://istio.io/latest/docs/reference/commands/pilot-agent/#pilot-agent-istio-iptables
Sidecar CRD 相关代码实现介绍 - Istio 1.12 版本 核心代码路径: pilot/pkg/networking/core/v1alpha3/listener.go
Sidecar CRD 配置主要用于 proxy Listener 生成。
关键数据结构:
model.SidecarScope: 包含 sidecar, virtualservice, destinationrule 等需要 proxy 感知的资源对象。
1 2 3 4 5 6 7 // The SidecarScope object is a semi-processed view of the // service registry, and config state associated with the sidecar CRD. The // scope contains a set of inbound and outbound listeners, services/configs // per listener, etc. The sidecar scopes are precomputed based on the // Sidecar API objects in each namespace. If there is no sidecar api object // for a namespace, a default sidecarscope is assigned to the namespace // which enables connectivity to all services in the mesh.
model.Proxy:表示对应每一个 sidecar 或 Gateway 的元数据。
代码调用路径 istio v1.12
Server 初始化 ConfigGenerator 逻辑
1 2 3 NewServer |_ xds.NewDiscoveryServer |_core.NewConfigGenerator
Xds 连接和请求处理 逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ADS GRPC _pb_handler |_ DiscoveryServer.Stream |_ DiscoveryServer.initializeProxy or DiscoveryServer.processRequest or DiscoveryServer.pushConnect |_ DiscoveryServer.computeProxyState |_ proxy.SetSidecarScope(pushContext) |_ pushConext.getSidecarScope(proxy, workloadLabels) |_ ps.sidecarIndex.sidecarsByNamespace[proxy.ConfigNamespace] |_ xdsServer.pushXds or xdsServer.pushDeltaXds |_ ldsGenerator.Generate |_ configGenerator.BuildListeners(proxy, push) |_ configgen.buidSidecarListeners(build) |_ builder.buildSidecarInboundListeners |_ configgen.buildSidecarInboundListenerForPortOrUDS |_ builder.buildSidecarOutboundListeners |_ configgen.buildSidecarOutboundListenerForPortOrUD
xds 请求事件驱动 - Sidecar CRD 生成 PushContext
1 2 3 4 5 6 DiscoveryServer.Push() or |_ PushContext.initContext() // PushContext holds information during push generation. It is reset on config change, at the beginning of the pushAll. |_ pushContext.createNewContext or updateContext |_ PushContext.initSidecarScopes() // initSidecarScopes synthesizes Sidecar CRDs into objects called SidecarScope. |_ env.List(gvk.Sidecar, NamespaceAll) // List All Sidecar CRD |_ env.ConfigStore.List() // configStore is List-Watcher Store, Cache CRDs from remote k8s
初始化本地 List-watch 缓存 Store1 2 3 4 5 newDiscoveryCommand() |_ NewServer() |_ Server.initControllers() |_ Server.initConfigController() |_ s.environment.ConfigStore = model.MakeIstioStore(s.configController)