Kubernetes集群中访问LoadBalancer暴露出去的SLB地址不通
1.踩坑记录
k8s集群通过Nginx Ingress提供公网服务,集群内网有部分服务通过公网地址调用,导致部分节点、Pod无法访问公网地址的坑~具体描述如下:
curl https://xx.xxxxx.cn curl: (7) Failed connect to xx.xxxxx.cn:443; Connection refused该问题导致线上支付业务受到影响。。。都是钱啊~
2.详细过程
2.1 问题描述
集群为阿里云ACK标准版集群,Ingress通过Service的SLB提供服务。
在Kubernetes集群中有部分节点能访问集群暴露出去的Local类型SLB,但是也有部分节点不能访问,且Ingress出现该问题较多。
2.2 问题原因
SLB设置了externalTrafficPolicy: Local类型,这种类型的SLB地址只有在Node中部署了对应的后端Pod,才能被访问。因为SLB的地址是集群外使用,如果集群节点和Pod不能直接访问,请求不会到SLB,会被当作Service的扩展IP地址,被kube-proxy的iptables或ipvs转发。
如果刚好集群节点或者Pod所在的节点上没有相应的后端服务Pod,就会发生网络不通的问题,而如果有相应的后端服务Pod,是可以正常访问。
2.3 解决方案
PLAN A
- 在Kubernetes集群内通过
ClusterIP或者服务名访问。 - 其中Ingress的服务名为:
nginx-ingress-lb.kube-system
PLAN B
将
LoadBalancer的Service中的externalTrafficPolicy修改为Cluster,但是在应用中会丢失源IP,Ingress的服务修改命令如下。kubectl edit svc nginx-ingress-lb -n kube-system若是
Terway的
ENI或者
ENI多IP的集群,将LoadBalancer的Service中的externalTrafficPolicy修改为Cluster,并且添加ENI直通的annotation,例如
annotation: service.beta.kubernetes.io/backend-type:"eni",具体格式如下,可以保留源IP,并且在集群内访问也没有问题。
apiVersion: v1 kind: Service metadata: annotations: service.beta.kubernetes.io/backend-type: eni labels: app: nginx-ingress-lb name: nginx-ingress-lb namespace: kube-system spec: externalTrafficPolicy: Cluster3.深入了解Kubernetes外部流量策略
3.1 摘要
external-traffic-policy,顾名思义外部流量策略,那这个配置有什么作用呢?以及external是指什么东西的外部呢,集群、节点、Pod?今天我们就来了解一下这个概念吧。3.2 什么是external-traffic-policy
在k8s的
Service对象(申明一条访问通道)中,有一个externalTrafficPolicy字段可以设置。有2个值可以设置:Cluster或者Local。Cluster表示:流量可以转发到其他节点上的Pod。Local表示:流量只发给本机的Pod。
图示一下:

3.3 这2种模式有什么区别
存在这2种模式的原因就是,当前节点的
Kube-proxy在转发报文的时候,会不会保留原始访问者的IP。.3.3.1 选择Cluster
注:这个是
默认模式,Kube-proxy不管容器实例在哪,公平转发。Kube-proxy转发时会替换掉报文的源IP。即:容器收的报文,源IP地址,已经被替换为上一个转发节点的了。原因是
Kube-proxy在做转发的时候,会做一次SNAT (source network address translation),所以源IP变成了节点1的IP地址。ps:
SNAT确保回去的报文可以原路返回,不然回去的路径不一样,客户会认为非法报文的。(我发给张三的,怎么李四给我回应?丢弃!)这种模式
好处是负载均衡会比较好,因为无论容器实例怎么分布在多个节点上,它都会转发过去。当然,由于多了一次转发,性能会损失一丢丢。3.3.2 选择Local
这种情况下,只转发给本机的容器,
绝不跨节点转发。Kube-proxy转发时会保留源IP。即:容器收到的报文,看到源IP地址还是用户的。
缺点是负载均衡可能不是很好,因为一旦容器实例分布在多个节点上,它只转发给本机,不跨节点转发流量。当然,少了一次转发,性能会相对好一丢丢。注:这种模式下的Service类型只能为
外部流量,即:LoadBalancer 或者 NodePort 两种,否则会报错。同时,由于本机不会跨节点转发报文,所以要想所有节点上的容器有负载均衡,就需要上一级的Loadbalancer来做了。

不过流量还是会不太均衡,如上图,Loadbalancer看到的是2个后端(把节点的IP),每个Node上面几个Pod对Loadbalancer来说是不知道的。
想要解决负载不均衡的问题:可以给Pod容器设置反亲和,让这些容器平均的分布在各个节点上(不要聚在一起)。
affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: k8s-app operator: In values: - my-app topologyKey: kubernetes.io/hostname像下面这样,负载均衡情况就会好很多~

3.4 两种模式该怎么选
- 要想
性能(时延)好,当然应该选Local 模式喽,毕竟流量转发少一次SNAT嘛。- 不过注意,选了这个就得考虑好怎么处理好负载均衡问题(ps:通常我们使用Pod间反亲和来达成)。
- 如果你是从外部LB接收流量的,那么使用:Local模式 + Pod反亲和,一般是足够的。
- 想要
简单化,降低运维复杂成本,当然选Local 模式,毕竟一个配置搞定了,不用考虑那么多反亲和的问题。
