Kubernetes集群中访问LoadBalancer暴露出去的SLB地址不通

1.踩坑记录

  1. k8s集群通过Nginx Ingress提供公网服务,集群内网有部分服务通过公网地址调用,导致部分节点、Pod无法访问公网地址的坑~具体描述如下:

    curl https://xx.xxxxx.cn
    curl: (7) Failed connect to xx.xxxxx.cn:443; Connection refused
  2. 该问题导致线上支付业务受到影响。。。都是钱啊~

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-proxyiptablesipvs转发。

如果刚好集群节点或者Pod所在的节点上没有相应的后端服务Pod,就会发生网络不通的问题,而如果有相应的后端服务Pod,是可以正常访问。

2.3 解决方案

PLAN A

  • 在Kubernetes集群内通过ClusterIP或者服务名访问。
  • 其中Ingress的服务名为:nginx-ingress-lb.kube-system

PLAN B

  • LoadBalancerService中的externalTrafficPolicy修改为Cluster,但是在应用中会丢失源IP,Ingress的服务修改命令如下。

    kubectl edit svc nginx-ingress-lb -n kube-system
  • 若是TerwayENI或者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: Cluster

3.深入了解Kubernetes外部流量策略

3.1 摘要

external-traffic-policy,顾名思义外部流量策略,那这个配置有什么作用呢?以及external是指什么东西的外部呢,集群、节点、Pod?今天我们就来了解一下这个概念吧。

3.2 什么是external-traffic-policy

在k8s的Service对象(申明一条访问通道)中,有一个externalTrafficPolicy字段可以设置。有2个值可以设置:Cluster或者Local

  1. Cluster表示:流量可以转发到其他节点上的Pod。
  2. Local表示:流量只发给本机的Pod。

图示一下:

image-20211011160809949

3.3 这2种模式有什么区别

存在这2种模式的原因就是,当前节点的Kube-proxy在转发报文的时候,会不会保留原始访问者的IP

.3.3.1 选择Cluster

注:这个是默认模式Kube-proxy不管容器实例在哪,公平转发

Kube-proxy转发时会替换掉报文的源IP。即:容器收的报文,源IP地址,已经被替换为上一个转发节点的了。

img

原因是Kube-proxy在做转发的时候,会做一次SNAT (source network address translation),所以源IP变成了节点1的IP地址。

ps:SNAT确保回去的报文可以原路返回,不然回去的路径不一样,客户会认为非法报文的。(我发给张三的,怎么李四给我回应?丢弃!)

这种模式好处负载均衡会比较好,因为无论容器实例怎么分布在多个节点上,它都会转发过去。当然,由于多了一次转发性能会损失一丢丢

3.3.2 选择Local

这种情况下,只转发给本机的容器,绝不跨节点转发

Kube-proxy转发时会保留源IP。即:容器收到的报文,看到源IP地址还是用户的。

image-20211011161155994

缺点负载均衡可能不是很好,因为一旦容器实例分布在多个节点上,它只转发给本机,不跨节点转发流量。当然,少了一次转发,性能会相对好一丢丢。

注:这种模式下的Service类型只能为外部流量,即:LoadBalancer 或者 NodePort 两种,否则会报错。

同时,由于本机不会跨节点转发报文,所以要想所有节点上的容器有负载均衡,就需要上一级的Loadbalancer来做了。

W4nhCl

不过流量还是会不太均衡,如上图,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

像下面这样,负载均衡情况就会好很多~

76uFFM

3.4 两种模式该怎么选

  1. 要想性能(时延)好,当然应该选 Local 模式喽,毕竟流量转发少一次SNAT嘛。

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