| /* |
| Copyright 2017 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package proxy |
| |
| import ( |
| "fmt" |
| "net" |
| |
| v1 "k8s.io/api/core/v1" |
| "k8s.io/klog/v2" |
| apiservice "k8s.io/kubernetes/pkg/api/v1/service" |
| proxyutil "k8s.io/kubernetes/pkg/proxy/util" |
| netutils "k8s.io/utils/net" |
| ) |
| |
| // ServicePort is an interface which abstracts information about a service. |
| type ServicePort interface { |
| // String returns service string. An example format can be: `IP:Port/Protocol`. |
| String() string |
| // ClusterIP returns service cluster IP in net.IP format. |
| ClusterIP() net.IP |
| // Port returns service port if present. If return 0 means not present. |
| Port() int |
| // SessionAffinityType returns service session affinity type |
| SessionAffinityType() v1.ServiceAffinity |
| // StickyMaxAgeSeconds returns service max connection age |
| StickyMaxAgeSeconds() int |
| // ExternalIPs returns service ExternalIPs |
| ExternalIPs() []net.IP |
| // LoadBalancerVIPs returns service LoadBalancerIPs which are VIP mode |
| LoadBalancerVIPs() []net.IP |
| // Protocol returns service protocol. |
| Protocol() v1.Protocol |
| // LoadBalancerSourceRanges returns service LoadBalancerSourceRanges if present empty array if not |
| LoadBalancerSourceRanges() []*net.IPNet |
| // HealthCheckNodePort returns service health check node port if present. If return 0, it means not present. |
| HealthCheckNodePort() int |
| // NodePort returns a service Node port if present. If return 0, it means not present. |
| NodePort() int |
| // ExternalPolicyLocal returns if a service has only node local endpoints for external traffic. |
| ExternalPolicyLocal() bool |
| // InternalPolicyLocal returns if a service has only node local endpoints for internal traffic. |
| InternalPolicyLocal() bool |
| // HintsAnnotation returns the value of the v1.DeprecatedAnnotationTopologyAwareHints annotation. |
| HintsAnnotation() string |
| // ExternallyAccessible returns true if the service port is reachable via something |
| // other than ClusterIP (NodePort/ExternalIP/LoadBalancer) |
| ExternallyAccessible() bool |
| // UsesClusterEndpoints returns true if the service port ever sends traffic to |
| // endpoints based on "Cluster" traffic policy |
| UsesClusterEndpoints() bool |
| // UsesLocalEndpoints returns true if the service port ever sends traffic to |
| // endpoints based on "Local" traffic policy |
| UsesLocalEndpoints() bool |
| } |
| |
| // BaseServicePortInfo contains base information that defines a service. |
| // This could be used directly by proxier while processing services, |
| // or can be used for constructing a more specific ServiceInfo struct |
| // defined by the proxier if needed. |
| type BaseServicePortInfo struct { |
| clusterIP net.IP |
| port int |
| protocol v1.Protocol |
| nodePort int |
| loadBalancerVIPs []net.IP |
| sessionAffinityType v1.ServiceAffinity |
| stickyMaxAgeSeconds int |
| externalIPs []net.IP |
| loadBalancerSourceRanges []*net.IPNet |
| healthCheckNodePort int |
| externalPolicyLocal bool |
| internalPolicyLocal bool |
| hintsAnnotation string |
| } |
| |
| var _ ServicePort = &BaseServicePortInfo{} |
| |
| // String is part of ServicePort interface. |
| func (bsvcPortInfo *BaseServicePortInfo) String() string { |
| return fmt.Sprintf("%s:%d/%s", bsvcPortInfo.clusterIP, bsvcPortInfo.port, bsvcPortInfo.protocol) |
| } |
| |
| // ClusterIP is part of ServicePort interface. |
| func (bsvcPortInfo *BaseServicePortInfo) ClusterIP() net.IP { |
| return bsvcPortInfo.clusterIP |
| } |
| |
| // Port is part of ServicePort interface. |
| func (bsvcPortInfo *BaseServicePortInfo) Port() int { |
| return bsvcPortInfo.port |
| } |
| |
| // SessionAffinityType is part of the ServicePort interface. |
| func (bsvcPortInfo *BaseServicePortInfo) SessionAffinityType() v1.ServiceAffinity { |
| return bsvcPortInfo.sessionAffinityType |
| } |
| |
| // StickyMaxAgeSeconds is part of the ServicePort interface |
| func (bsvcPortInfo *BaseServicePortInfo) StickyMaxAgeSeconds() int { |
| return bsvcPortInfo.stickyMaxAgeSeconds |
| } |
| |
| // Protocol is part of ServicePort interface. |
| func (bsvcPortInfo *BaseServicePortInfo) Protocol() v1.Protocol { |
| return bsvcPortInfo.protocol |
| } |
| |
| // LoadBalancerSourceRanges is part of ServicePort interface |
| func (bsvcPortInfo *BaseServicePortInfo) LoadBalancerSourceRanges() []*net.IPNet { |
| return bsvcPortInfo.loadBalancerSourceRanges |
| } |
| |
| // HealthCheckNodePort is part of ServicePort interface. |
| func (bsvcPortInfo *BaseServicePortInfo) HealthCheckNodePort() int { |
| return bsvcPortInfo.healthCheckNodePort |
| } |
| |
| // NodePort is part of the ServicePort interface. |
| func (bsvcPortInfo *BaseServicePortInfo) NodePort() int { |
| return bsvcPortInfo.nodePort |
| } |
| |
| // ExternalIPs is part of ServicePort interface. |
| func (bsvcPortInfo *BaseServicePortInfo) ExternalIPs() []net.IP { |
| return bsvcPortInfo.externalIPs |
| } |
| |
| // LoadBalancerVIPs is part of ServicePort interface. |
| func (bsvcPortInfo *BaseServicePortInfo) LoadBalancerVIPs() []net.IP { |
| return bsvcPortInfo.loadBalancerVIPs |
| } |
| |
| // ExternalPolicyLocal is part of ServicePort interface. |
| func (bsvcPortInfo *BaseServicePortInfo) ExternalPolicyLocal() bool { |
| return bsvcPortInfo.externalPolicyLocal |
| } |
| |
| // InternalPolicyLocal is part of ServicePort interface |
| func (bsvcPortInfo *BaseServicePortInfo) InternalPolicyLocal() bool { |
| return bsvcPortInfo.internalPolicyLocal |
| } |
| |
| // HintsAnnotation is part of ServicePort interface. |
| func (bsvcPortInfo *BaseServicePortInfo) HintsAnnotation() string { |
| return bsvcPortInfo.hintsAnnotation |
| } |
| |
| // ExternallyAccessible is part of ServicePort interface. |
| func (bsvcPortInfo *BaseServicePortInfo) ExternallyAccessible() bool { |
| return bsvcPortInfo.nodePort != 0 || len(bsvcPortInfo.loadBalancerVIPs) != 0 || len(bsvcPortInfo.externalIPs) != 0 |
| } |
| |
| // UsesClusterEndpoints is part of ServicePort interface. |
| func (bsvcPortInfo *BaseServicePortInfo) UsesClusterEndpoints() bool { |
| // The service port uses Cluster endpoints if the internal traffic policy is "Cluster", |
| // or if it accepts external traffic at all. (Even if the external traffic policy is |
| // "Local", we need Cluster endpoints to implement short circuiting.) |
| return !bsvcPortInfo.internalPolicyLocal || bsvcPortInfo.ExternallyAccessible() |
| } |
| |
| // UsesLocalEndpoints is part of ServicePort interface. |
| func (bsvcPortInfo *BaseServicePortInfo) UsesLocalEndpoints() bool { |
| return bsvcPortInfo.internalPolicyLocal || (bsvcPortInfo.externalPolicyLocal && bsvcPortInfo.ExternallyAccessible()) |
| } |
| |
| func newBaseServiceInfo(service *v1.Service, ipFamily v1.IPFamily, port *v1.ServicePort) *BaseServicePortInfo { |
| externalPolicyLocal := apiservice.ExternalPolicyLocal(service) |
| internalPolicyLocal := apiservice.InternalPolicyLocal(service) |
| |
| var stickyMaxAgeSeconds int |
| if service.Spec.SessionAffinity == v1.ServiceAffinityClientIP { |
| // Kube-apiserver side guarantees SessionAffinityConfig won't be nil when session affinity type is ClientIP |
| stickyMaxAgeSeconds = int(*service.Spec.SessionAffinityConfig.ClientIP.TimeoutSeconds) |
| } |
| |
| clusterIP := proxyutil.GetClusterIPByFamily(ipFamily, service) |
| info := &BaseServicePortInfo{ |
| clusterIP: netutils.ParseIPSloppy(clusterIP), |
| port: int(port.Port), |
| protocol: port.Protocol, |
| nodePort: int(port.NodePort), |
| sessionAffinityType: service.Spec.SessionAffinity, |
| stickyMaxAgeSeconds: stickyMaxAgeSeconds, |
| externalPolicyLocal: externalPolicyLocal, |
| internalPolicyLocal: internalPolicyLocal, |
| } |
| |
| // v1.DeprecatedAnnotationTopologyAwareHints has precedence over v1.AnnotationTopologyMode. |
| var exists bool |
| info.hintsAnnotation, exists = service.Annotations[v1.DeprecatedAnnotationTopologyAwareHints] |
| if !exists { |
| info.hintsAnnotation = service.Annotations[v1.AnnotationTopologyMode] |
| } |
| |
| // filter external ips, source ranges and ingress ips |
| // prior to dual stack services, this was considered an error, but with dual stack |
| // services, this is actually expected. Hence we downgraded from reporting by events |
| // to just log lines with high verbosity |
| ipFamilyMap := proxyutil.MapIPsByIPFamily(service.Spec.ExternalIPs) |
| info.externalIPs = ipFamilyMap[ipFamily] |
| |
| // Log the IPs not matching the ipFamily |
| if ips, ok := ipFamilyMap[proxyutil.OtherIPFamily(ipFamily)]; ok && len(ips) > 0 { |
| klog.V(4).InfoS("Service change tracker ignored the following external IPs for given service as they don't match IP Family", |
| "ipFamily", ipFamily, "externalIPs", ips, "service", klog.KObj(service)) |
| } |
| |
| cidrFamilyMap := proxyutil.MapCIDRsByIPFamily(service.Spec.LoadBalancerSourceRanges) |
| info.loadBalancerSourceRanges = cidrFamilyMap[ipFamily] |
| // Log the CIDRs not matching the ipFamily |
| if cidrs, ok := cidrFamilyMap[proxyutil.OtherIPFamily(ipFamily)]; ok && len(cidrs) > 0 { |
| klog.V(4).InfoS("Service change tracker ignored the following load balancer source ranges for given Service as they don't match IP Family", |
| "ipFamily", ipFamily, "loadBalancerSourceRanges", cidrs, "service", klog.KObj(service)) |
| } |
| |
| // Obtain Load Balancer Ingress |
| var invalidIPs []net.IP |
| for _, ing := range service.Status.LoadBalancer.Ingress { |
| if ing.IP == "" { |
| continue |
| } |
| |
| // proxy mode load balancers do not need to track the IPs in the service cache |
| // and they can also implement IP family translation, so no need to check if |
| // the status ingress.IP and the ClusterIP belong to the same family. |
| if !proxyutil.IsVIPMode(ing) { |
| klog.V(4).InfoS("Service change tracker ignored the following load balancer ingress IP for given Service as it using Proxy mode", |
| "ipFamily", ipFamily, "loadBalancerIngressIP", ing.IP, "service", klog.KObj(service)) |
| continue |
| } |
| |
| // kube-proxy does not implement IP family translation, skip addresses with |
| // different IP family |
| ip := netutils.ParseIPSloppy(ing.IP) // (already verified as an IP-address) |
| if ingFamily := proxyutil.GetIPFamilyFromIP(ip); ingFamily == ipFamily { |
| info.loadBalancerVIPs = append(info.loadBalancerVIPs, ip) |
| } else { |
| invalidIPs = append(invalidIPs, ip) |
| } |
| } |
| if len(invalidIPs) > 0 { |
| klog.V(4).InfoS("Service change tracker ignored the following load balancer ingress IPs for given Service as they don't match the IP Family", |
| "ipFamily", ipFamily, "loadBalancerIngressIPs", invalidIPs, "service", klog.KObj(service)) |
| } |
| |
| if apiservice.NeedsHealthCheck(service) { |
| p := service.Spec.HealthCheckNodePort |
| if p == 0 { |
| klog.ErrorS(nil, "Service has no healthcheck nodeport", "service", klog.KObj(service)) |
| } else { |
| info.healthCheckNodePort = int(p) |
| } |
| } |
| |
| return info |
| } |