Istio CRD 详解 - Sidecar

引言

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: # workload 选择器, 非必需
labels:
app: productpage
ingress: # 配置 Pod 入向流量处理规则, 非必需。缺省,会从 k8s 中相关的 services, pod 等字段生成;指定后,
- 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: # 配置业务容器出向流量处理规则,默认能劫持到的。非必需。缺省,从全局配置获取
- captureMode: IPTABLES
hosts:
- "*/*"

需要指出的:

  1. 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 建立连接。
  1. 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 代理功能。
  1. outboundTrafficPolicy: 非必须,出向流量转发策略。
    • ALLOW_ANY: 适用于应用的出向流量无法预测且无规则,全部转发。
    • REGISTRY_ONLY:限定在网格内感知到的服务,如通过 K8s Services 或 ServiceEntry 注册的服务。

几个应用场景介绍

  1. 在 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:
- "*/*"
  1. 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"
  1. 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 实现流量劫持的细节

https://jimmysong.io/blog/sidecar-injection-iptables-and-traffic-routing/istio-iptables.svg

ISTIO_ROUTE 规则的详细流程

示例,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 潜在问题:

  1. 依赖 contrack 连接追踪,大量并发连接,会导致 contrack 表爆满。
  2. 全局生效,不利于管控。
  3. 通过 loopback 实现,outbound 会两次经过协议栈,性能有损耗。

社区优化思路:

  1. ebpf
  2. tproxy inbound 优化
  3. hook connect 处理 outbound

推荐阅读:

  1. https://jimmysong.io/blog/sidecar-injection-iptables-and-traffic-routing/
  2. https://jimmysong.io/blog/istio-sidecar-traffic-types/
  3. 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 生成。

关键数据结构:

  1. 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.
  1. model.Proxy:表示对应每一个 sidecar 或 Gateway 的元数据。

代码调用路径

istio v1.12

  1. Server 初始化 ConfigGenerator 逻辑
1
2
3
NewServer
|_ xds.NewDiscoveryServer
|_core.NewConfigGenerator
  1. 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
  1. 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
  1. 初始化本地 List-watch 缓存 Store
    1
    2
    3
    4
    5
    newDiscoveryCommand()
    |_ NewServer()
    |_ Server.initControllers()
    |_ Server.initConfigController()
    |_ s.environment.ConfigStore = model.MakeIstioStore(s.configController)