云环境阿里云 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模式下,外部的请求要进到集群里边会经过以下几个步骤:
客户端请求流量到达负载均衡器
负载均衡器将流量均衡转发到节点上的 Node Port
节点上的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在处理Service的Endpoint时只会处理运行在本地的Pod,只把这些Pod添加为ipvs的real 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会利用iptables和ipset做SNAT。
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 条评论