• Ukieweb

    佳的博客

    曾梦想仗剑天涯,后来工作忙没去。

云环境阿里云 ingress 调用原理 Kubernetes HealthCheckNodePort 参数作用分析

Ingress是从Kubernetes集群外部访问集群内部服务入口,但Ingress控制器本身不具备将自身暴露到公网的能力,需要借助LoadBalancer类型Service,如果你的Kubernetes 集群是运行在AWS阿里云公有云上时,通过指定service的类型很容易实现。

   internet
        |
   [ Ingress ]
   --|-----|--
   [ Services ]

创建一个 nginx-ingress LoadBalancer Service的配置大概如下:

apiVersion: v1
kind: Service
metadata:
  annotations:
    service.beta.kubernetes.io/alicloud-loadbalancer-address-type: intranet
  labels:
    app: nginx-ingress-lb
  name: nginx-ingress-lb
  namespace: kube-system
spec:
  clusterIP: 10.5.38.199
  externalTrafficPolicy: Local
  healthCheckNodePort: 32075
  ports:
  - name: http
    nodePort: 30247
    port: 80
    protocol: TCP
    targetPort: 80
  - name: https
    nodePort: 30136
    port: 443
    protocol: TCP
    targetPort: 443
  selector:
    app: ingress-nginx
  sessionAffinity: None
  type: LoadBalancer

着重讲解下边两个参数

  • externalTrafficPolicy

  • healthCheckNodePort

kubernetes 在创建LoadBalancer类型的Service时,默认会将集群中除了Unschedulable、Master、NodeReady外的其它节点都加入负载均衡器,成为负载均衡后端 RealServer具体节点的处理逻辑getNodeConditionPredicate 这个函数。

func getNodeConditionPredicate() corelisters.NodeConditionPredicate {
	return func(node *v1.Node) bool {
		// We add the master to the node list, but its unschedulable.  So we use this to filter
		// the master.
		if node.Spec.Unschedulable {
			return false
		}
		// As of 1.6, we will taint the master, but not necessarily mark it unschedulable.
		// Recognize nodes labeled as master, and filter them also, as we were doing previously.
		if _, hasMasterRoleLabel := node.Labels[LabelNodeRoleMaster]; hasMasterRoleLabel {
			return false
		}
		// ServiceNodeExclusion:启用从云提供商创建的负载均衡器中排除节点。
		// 如果节点标记有 alpha.service-controller.kubernetes.io/exclude-balancer 键(启用 LegacyNodeRoleBehavior 时)
		// 或 node.kubernetes.io/exclude-from-external-load-balancers,则可以排除节点。
		if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.ServiceNodeExclusion) {
			if _, hasExcludeBalancerLabel := node.Labels[LabelNodeRoleExcludeBalancer]; hasExcludeBalancerLabel {
				return false
			}
		}
		// If we have no info, don't accept
		if len(node.Status.Conditions) == 0 {
			return false
		}
		for _, cond := range node.Status.Conditions {
			// We consider the node for load balancing only when its NodeReady condition status
			// is ConditionTrue
			if cond.Type == v1.NodeReady && cond.Status != v1.ConditionTrue {
				klog.V(4).Infof("Ignoring node %v with %v condition status %v", node.Name, cond.Type, cond.Status)
				return false
			}
		}
		return true
	}
}

外部请求K8S集群

如果 kube-proxy 是运行在ipvs模式下,外部的请求要进到集群里边会经过以下几个步骤

image.png

  1. 客户端请求流量到达负载均衡器

  2. 负载均衡器将流量均衡转发到节点上的 Node Port

  3. 节点上的Node Port再通过 Ipvs 将流量均衡转发到endpoint(Pod)

service endpoint 不在本机时(本机即负载均衡随机转发到的node)

ipvs会将流量转发(DNAT)其它节点上的endpoint(Pod)上,为了保证回去的包(Node->SLB)源IP不变会再用iptables一次SNAT,在这种情况下流量虽然在整个集群内得到了更好的均衡,但是也由于做了SNAT后导致后端Pod无法获取到客户端的IP

为了解决客户端IP丢失的问题 externalTrafficPolicy

kubernetes引入externalTrafficPolicy参数来控制负载均衡器的调度策略。

默认值为 Cluster,代表流量会在整个集群内得到均衡

另外一个参数 local,代表负载均衡器只会流量转发到那些本机运行有Endpoint的节点上,node上如果没有 Endpoint,那么该节点不参与做负载

负载均衡器是怎么知道节点上有没有 service 的 Endpoint 呢?healthCheckNodePort 

这就是 healthCheckNodePort 参数的作用。

healthCheckNodePort 的作用是 kube-proxy 会暴露一个http接口供前端负载均衡器来做健康检测,当节点上运行有Endpoint时该接口就返回200,该节点就能正常参与做流量转发,反之,如果节点上没有运行Endpoint时接口就会返回503状态码负载均衡器就会将该节点从集群中移除不参与做流量转发

healthCheckNodePort虽然解决了不让负载均衡器转发流量到无Endpoint的节点,但是有Endpoint的节点外部流量进入到节点后,节点上的ipvs又是如何保证流量不会转发到其它无Endpoint的节点呢?

这主要是靠Proxier的处理逻辑,当externalTrafficPolicy值为local时,Proxier在处理ServiceEndpoint只会处理运行在本地的Pod,只把这些Pod添加为ipvsreal server,具体处理逻辑:

func (proxier *Proxier) syncEndpoint(svcPortName proxy.ServicePortName, onlyNodeLocalEndpoints bool, vs *utilipvs.VirtualServer) error {
    
    ...//省略
    for _, epInfo := range proxier.endpointsMap[svcPortName] {
        //@xnile 如果Endpoint和Kube-proxy不是运行在同一节点上,且externaltrafficpolicy值为local时,ipvs在添加Realserver时会略过该Endpoint。 
        if onlyNodeLocalEndpoints && !epInfo.GetIsLocal() {
            continue
        }
        newEndpoints.Insert(epInfo.String())
    }
    ...//省略
  
    return nil
}

externalTrafficPolicy  为 Cluster 

externalTrafficPolicy 值为 Cluster 时,流量又是如何在整个集群中做负载的呢?

Proxier 在处理 endpoint 不再判断是否在本地,而是将所有 endpoint 作为 ipvs 的后端 real server

流量在转发到其它节点上的endpoint时,Proxier利用iptablesipsetSNAT

kube-proxy运行在ipvs模式时的iptables规则处理过程

-> PREROUTING -> KUBE-SERVICES -> KUBE-SERVICES -> KUBE-LOAD-BALANCER -> (RETURN或KUBE-MARK-MASQ)

如果externalTrafficPolicy不为Local时,外边进来的包在经过PREROUTING链时就会打上标记,在经过ipvs处理后发现应该转发其它节点上的endpoint,那么数据包在被送出去前经过POSTROUTING时会为已经打上标记包SNAT,修改源IP为本机IP

这里需要注意一下,为了让iptables能给数据包打上标记有一个很重要的内核参数需要启用:net.ipv4.vs.conntrack=1,关于这个参数的作用可以参见我的另一篇文章 LVS+Iptables实现FULLNAT及原理分析

阿里云的 healthCheckNodePort

另外需要说明一下,上边分析的逻辑仅适用于legacy-cloud-providers内的云厂商,aws,azure,gce这些。

阿里云的ACK虽然也有这两个参数,但是它的内部实现没有遵循上边的处理逻辑,它并不会默认将全部节点都加到负载均衡集群,而是仅加入那些有edpoint的节点,所以就不需healthCheckNodePort充当健康检测的作用。






摘自:

Kubernetes HealthCheckNodePort参数作用分析


0
0
下一篇:Kubernetes 对象版本控制 ResourceVersion 和 Generation 区别

0 条评论

老佳啊

85后,大专学历,中原人士,家里没矿。

由于年轻时长的比较帅气,导致在别人眼里,我一直不谈恋爱的原因是清高,实则是自己的小自卑。最大的人生目标就是找一个相知相爱相容的人,共度余生。

和人相处时如果能感受到真诚,会非常注重彼此的关系,对别人没有什么心机,即使有利益冲突,一般也会以和为贵,因为在这个世界上,物质的东西,从来不会吸引到我。

特别迷恋那些大山大水,如果现在还能隐居,可能早就去了。对那些宏伟的有底蕴的人文景观比较不感冒。

从事于IT行业,却一直对厨房念念不忘,由于身材魁梧,总觉得自己上辈子是个将军,可惜这辈子没当兵,也不会打架。