微服务学习 - Kubernetes中的网络流量
概述
本例中使用Kubesphere v3.1.0环境(Kubernetes v1.20.4 + Calico v3.16.3),网关设备为Kourier。
在Kubernetes上部署应用负载并提供服务后,访问流量将经过网关设备、负载均衡最后到达后端设备。
在这个过程中Kourier将作为网关设备解析流量的目的地址,根据域名分发到负载均衡器即Service上。
Kube-proxy(本例中为ipvs模式)将负责Service、Endpoint部分的流量处理。
Calico(本例中为IPIP模式)将负责后端设备即pod间流量的传输。
网关
Ingress是Kubernetes中用于处理7层网络负载的反向代理抽象。
Kourier基于Envoy实现了Ingress的能力。它会监听集群内资源的变化,变更Envoy的配置来保证前端的流量可以正确路由到后端的Service上。
Kube-proxy
kube-proxy的配置文件存放在kube-proxy容器的/var/lib/kube-proxy/config.conf中
同时还会使用kubernetes服务的参数,如--feature-gates
等
kube-proxy的监听工作
serviceConfig和endpointsConfig初始化时,在对应的Informer中注册了AddFunc、UpdateFunc和DeleteFunc
1 | func NewServiceConfig(serviceInformer coreinformers.ServiceInformer, resyncPeriod time.Duration) *ServiceConfig { |
之后,两个对象还分别通过RegisterEventHandler方法注册了事件处理器,即proxier
1 | serviceConfig.RegisterEventHandler(s.Proxier) |
以Service为例,当serviceConfig启动时,会调用proxier的OnServiceSynced方法处理事件
1 | func (c *ServiceConfig) Run(stopCh <-chan struct{}) { |
本例中proxier为ipvs,则参考kubernetes/pkg/proxy/ipvs/proxier.go - OnServiceSynced()
1 | func (proxier *Proxier) OnServiceSynced() { |
Calico网络规则
负责处理iptables规则的是Calico的Felix组件,参考 Felix 。
Felix的入口是felix/daemon/daemon.go - Run()函数,在Run()函数中会启动所有syncer
1 | // Start the background processing threads. |
InternalDataplane是其中一个syncer,基于iptables和ipsets来处理Felix的数据面。
它的工作内容主要分为两个步骤,第一步完成初始配置,第二部开始监听变化更新配置:
1 | func (d *InternalDataplane) Start() { |
初始配置
felix/dataplane/linux/int_dataplane.go - InternalDataplane.doStaticDataplaneConfig()
1 | func (d *InternalDataplane) doStaticDataplaneConfig() { |
规则流程
入站流量
本机流量
PREROUTING(raw -> mangle -> nat) –> 路由表 –> INPUT(mangle -> filter) –> 路由表 –> OUTPUT(raw -> mangle -> nat -> filter) –> POSTROUTING(mangle -> nat)
非本机流量
PREROUTING(raw -> mangle -> nat) –> 路由表 –> FORWARD(mangle -> filter) –> POSTROUTING(mangle -> nat)
出站流量
路由表 –> OUTPUT(raw -> mangle -> nat -> filter) –> POSTROUTING(mangle -> nat)
可以按照这个思路去分析calico的具体规则,也可以参考 Calico iptables详解 。
案例
请求地址解析
本例中使用了nip.io作为Knative的dns解析,案例url为:
1 | http://sample-svc-ksvc.default.<public-ip>.nip.io |
请求时相当于:
1 | curl -H "Host: sample-svc-serving-ksvc.default.<public-ip>.nip.io" http://<public-ip> |
可以在ingresses.networking.internal.knative.dev crd中查看上述ingress的详情,可见发送到上述url的请求将被路由到sample-svc-serving-ksvc-v100服务:
1 | kubectl get ingresses.networking.internal.knative.dev sample-svc-serving-ksvc -oyaml |
sample-svc-serving-ksvc-v100服务的后端为Knative的activator组件,用于在后端无pod实例时拦截流量,并通知autoscaler启动pod实例,待pod实例启动后再将流量发送至pod实例。
查看serverlessservices.networking.internal.knative.dev crd中的信息可以得知sample-svc-serving-ksvc-v100服务是sample-svc-serving-ksvc-v100-private服务的流量代理,即sample-svc-serving-ksvc-v100-private才是真正的后端pod负载均衡服务:
1 | kubectl get serverlessservices.networking.internal.knative.dev sample-svc-serving-ksvc-v100 -oyaml |
查看kubernetes service中的信息
将sample-svc-serving-ksvc-v100-private缩写为sample-svc
1 | kubectl get svc |
使用kubectl describe命令查看sample-svc的详情,可以发现其vip即clusterIP为10.233.49.34,代理的前端端口为80,Endpoint为10.233.96.13:80,代理协议为tcp
1 | Type: ClusterIP |
查询ipvs中的信息
执行以下命令获取ipvs中的信息,其中-l
表示列表查询connection信息,-t
表示查询tcp协议的代理地址,即上面的10.233.49.34:80
从下面的信息中可以看到,ipvs代理的后端地址与上面的endpoint一致
1 | ipvsadm -l -t 10.233.49.34:80 |
出站流量(192.168.0.2)
根据iptables的规则,出站流量需要经过:
- 路由表
- OUTPUT,表顺序:raw -> mangle -> nat -> filter
- POSTROUTING,表顺序:mangle -> nat
根据路由表信息,该报文将发送至tunl0设备,tunl0设备是IPIP(由IP层封装IP报文)传输时的隧道设备,任务是把报文传输到192.168.0.4的tunl0设备上;iptables部分可以按图索骥,略过
1 | route -n |
入站流量(192.168.0.3)
根据iptables的规则,入站流量需要经过:
- PREROUTING,表顺序:raw -> mangle -> nat
- 路由表
- INPUT,表顺序:mangle -> filter
非192.168.0.3节点的入站流量为:
- PREROUTING,表顺序:raw -> mangle -> nat
- 路由表
- FORWARD,表顺序:mangle -> filter
- POSTROUTING,表顺序:mangle -> nat
iptables部分可以按图索骥,略过
1 | route -n |
到达目的pod
网卡设备calie1e659a68ff与pod中的eth0设备为veth pair,这样流量就可以通过路由规则到达pod中的eth0网卡
1 | pod:~# ip link |