前面三章已经简要的介绍了一下istio的使用方法,没看的朋友建议看一下,可以加深理解,这次我们来开始研究istio的数据平面,之前就说过要单独抽出来sidecar好好讲一讲,今天来兑现这个承诺。
什么是sidecar
将应用程序的功能划分为单独的进程运行在同一个最小调度单元中(例如 Kubernetes 中的 Pod)可以被视为 sidecar 模式。如下图所示,sidecar 模式允许您在应用程序旁边添加更多功能。
这样的模式可以保证我们在控制pod级别流量时,不会对应用本身产生修改的需求。
sidecar的优势有哪些
sidecar模式下,流量控制container和业务container共享着存储、网络等资源,有点类似于一个虚拟机里的两个线程,这种部署方式可以让sidecar具有以下优势:
- 将与应用业务逻辑无关的功能抽象到共同基础设施,降低了微服务代码的复杂度。
- 因为不再需要编写相同的第三方组件配置文件和代码,所以能够降低微服务架构中的代码重复度。
- Sidecar 可独立升级,降低应用程序代码和底层平台的耦合度。
istio的sidecar里有什么
我们以之前实战文章中的bookinfo里的productpage为例,看一下sidecar会启动什么:
在productpage部署时,istio为其部署了两个sidecar,一个是init container,另外一个则是伴生的proxy,启动过程中pod会先启动init container,在环境初始化完毕后,启动productpage和istio-proxy,待两个container都就绪后将pod状态置为running。
init容器解析
我们来看一下yaml中init的代码
initContainers:
- args:
- istio-iptables
- -p
- "15001"
- -z
- "15006"
- -u
- "1337"
- -m
- REDIRECT
- -i
- '*'
- -x
- ""
- -b
- '*'
- -d
- 15090,15021,15020
- --run-validation
- --skip-rule-apply
env:
- name: DNS_AGENT
image: rancher/istio-proxyv2:1.7.3
imagePullPolicy: Always
name: istio-validation
resources:
limits:
cpu: "2"
memory: 1Gi
requests:
cpu: 10m
memory: 10Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
privileged: false
readOnlyRootFilesystem: true
runAsGroup: 1337
runAsNonRoot: true
runAsUser: 1337
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: bookinfo-productpage-token-88mvv
readOnly: true
可以看到关键的istio-iptables的操作,查看一下对应的文档:
istio-iptables [flags]
-p: 指定重定向所有 TCP 流量的 sidecar 端口(默认为 $ENVOY_PORT = 15001)
-m: 指定入站连接重定向到 sidecar 的模式,“REDIRECT” 或 “TPROXY”(默认为 $ISTIO_INBOUND_INTERCEPTION_MODE)
-b: 逗号分隔的入站端口列表,其流量将重定向到 Envoy(可选)。使用通配符 “*” 表示重定向所有端口。为空时表示禁用所有入站重定向(默认为 $ISTIO_INBOUND_PORTS)
-d: 指定要从重定向到 sidecar 中排除的入站端口列表(可选),以逗号格式分隔。使用通配符“*” 表示重定向所有入站流量(默认为 $ISTIO_LOCAL_EXCLUDE_PORTS)
-o:逗号分隔的出站端口列表,不包括重定向到 Envoy 的端口。
-i: 指定重定向到 sidecar 的 IP 地址范围(可选),以逗号分隔的 CIDR 格式列表。使用通配符 “*” 表示重定向所有出站流量。空列表将禁用所有出站重定向(默认为 $ISTIO_SERVICE_CIDR)
-x: 指定将从重定向中排除的 IP 地址范围,以逗号分隔的 CIDR 格式列表。使用通配符 “*” 表示重定向所有出站流量(默认为 $ISTIO_SERVICE_EXCLUDE_CIDR)。
-k:逗号分隔的虚拟接口列表,其入站流量(来自虚拟机的)将被视为出站流量。
-g:指定不应用重定向的用户的 GID。(默认值与 -u param 相同)
-u:指定不应用重定向的用户的 UID。通常情况下,这是代理容器的 UID(默认值是 1337,即 istio-proxy 的 UID)。
-z: 所有进入 pod/VM 的 TCP 流量应被重定向到的端口(默认 $INBOUND_CAPTURE_PORT = 15006)。
该容器存在的意义就是让 sidecar 代理可以拦截所有的进出 pod 的流量,15090 端口(Mixer 使用)和 15092 端口(Ingress Gateway)除外的所有入站(inbound)流量重定向到 15006 端口(sidecar),再拦截应用容器的出站(outbound)流量经过 sidecar 处理(通过 15001 端口监听)后再出站。关于 Istio 中端口用途请参考 Istio 官方文档。
总结一下,init容器的作用是:
- 将应用容器的所有流量都转发到 sidecar 的 15006 端口。
- 使用 istio-proxy 用户身份运行, UID 为 1337,即 sidecar 所处的用户空间,这也是 istio-proxy 容器默认使用的用户,见 YAML 配置中的 runAsUser 字段。
- 使用默认的 REDIRECT 模式来重定向流量。
- 将所有出站流量都重定向到 sidecar 代理(通过 15001 端口)。
init容器的iptables注入解析
为了查看init容器配置的iptables规则,我们需要有root权限,kubectl不支持root,我们登录到对应的机器上用docker去查看。
su - root #输入root密码
docker top `docker ps|grep "istio-proxy_productpage"|cut -d " " -f1`
拿到对应的PID
nsenter -n --target 31565
iptables -t nat -L -v
图上是iptables中nat配置的路由规则,由此我们可以看出istio init的一些修改
- PREROUTING 链:用于目标地址转换(DNAT),将所有入站 TCP 流量跳转到 ISTIO_INBOUND 链上。
- OUTPUT 链:将所有出站数据包跳转到 ISTIO_OUTPUT 链上。
- INPUT 链和POSTROUTING 链未做处理
- ISTIO_INBOUND 链:将所有入站流量重定向到 ISTIO_IN_REDIRECT 链上,目的地为 15090(mixer 使用)和 15020(Ingress gateway 使用,用于 Pilot 健康检查)端口的流量除外,发送到以上两个端口的流量将返回 iptables 规则链的调用点,即 PREROUTING 链的后继 POSTROUTING。
- ISTIO_IN_REDIRECT 链:将所有的入站流量跳转到本地的 15006 端口,至此成功的拦截了流量到 sidecar 中。
- ISTIO_OUTPUT 链:选择需要重定向到 Envoy(即本地) 的出站流量,所有非 localhost 的流量全部转发到 ISTIO_REDIRECT。为了避免流量在该 Pod 中无限循环,所有到 istio-proxy 用户空间的流量都返回到它的调用点中的下一条规则,本例中即 OUTPUT 链,因为跳出 ISTIO_OUTPUT 规则之后就进入下一条链 POSTROUTING。如果目的地非 localhost 就跳转到 ISTIO_REDIRECT;如果流量是来自 istio-proxy 用户空间的,那么就跳出该链,返回它的调用链继续执行下一条规则(OUTPUT 的下一条规则,无需对流量进行处理);所有的非 istio-proxy 用户空间的目的地是 localhost 的流量就跳转到 ISTIO_REDIRECT。
- ISTIO_REDIRECT 链:将所有流量重定向到 Sidecar(即本地) 的 15001 端口。
小结
由此我们看到了sidecar对流量劫持的全过程,init通过iptables执行流量的劫持,而proxy并不在这一过程中,它的工作是执行控制平面下发的规则。