github.com/noironetworks/cilium-net@v1.6.12/pkg/k8s/service.go (about)

     1  // Copyright 2018-2019 Authors of Cilium
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package k8s
    16  
    17  import (
    18  	"fmt"
    19  	"net"
    20  	"net/url"
    21  	"strings"
    22  	"time"
    23  
    24  	"github.com/cilium/cilium/pkg/annotation"
    25  	"github.com/cilium/cilium/pkg/comparator"
    26  	"github.com/cilium/cilium/pkg/k8s/types"
    27  	"github.com/cilium/cilium/pkg/loadbalancer"
    28  	"github.com/cilium/cilium/pkg/logging/logfields"
    29  	"github.com/cilium/cilium/pkg/node"
    30  	"github.com/cilium/cilium/pkg/option"
    31  	"github.com/cilium/cilium/pkg/service"
    32  
    33  	"github.com/sirupsen/logrus"
    34  	"k8s.io/api/core/v1"
    35  )
    36  
    37  func getAnnotationIncludeExternal(svc *types.Service) bool {
    38  	if value, ok := svc.ObjectMeta.Annotations[annotation.GlobalService]; ok {
    39  		return strings.ToLower(value) == "true"
    40  	}
    41  
    42  	return false
    43  }
    44  
    45  func getAnnotationShared(svc *types.Service) bool {
    46  	if value, ok := svc.ObjectMeta.Annotations[annotation.SharedService]; ok {
    47  		return strings.ToLower(value) == "true"
    48  	}
    49  
    50  	return getAnnotationIncludeExternal(svc)
    51  }
    52  
    53  // ParseServiceID parses a Kubernetes service and returns the ServiceID
    54  func ParseServiceID(svc *types.Service) ServiceID {
    55  	return ServiceID{
    56  		Name:      svc.ObjectMeta.Name,
    57  		Namespace: svc.ObjectMeta.Namespace,
    58  	}
    59  }
    60  
    61  // ParseService parses a Kubernetes service and returns a Service
    62  func ParseService(svc *types.Service) (ServiceID, *Service) {
    63  	scopedLog := log.WithFields(logrus.Fields{
    64  		logfields.K8sSvcName:    svc.ObjectMeta.Name,
    65  		logfields.K8sNamespace:  svc.ObjectMeta.Namespace,
    66  		logfields.K8sAPIVersion: svc.TypeMeta.APIVersion,
    67  		logfields.K8sSvcType:    svc.Spec.Type,
    68  	})
    69  
    70  	svcID := ParseServiceID(svc)
    71  
    72  	switch svc.Spec.Type {
    73  	case v1.ServiceTypeClusterIP, v1.ServiceTypeNodePort, v1.ServiceTypeLoadBalancer:
    74  		break
    75  
    76  	case v1.ServiceTypeExternalName:
    77  		// External-name services must be ignored
    78  		return svcID, nil
    79  
    80  	default:
    81  		scopedLog.Warn("Ignoring k8s service: unsupported type")
    82  		return svcID, nil
    83  	}
    84  
    85  	if svc.Spec.ClusterIP == "" {
    86  		return svcID, nil
    87  	}
    88  
    89  	clusterIP := net.ParseIP(svc.Spec.ClusterIP)
    90  	headless := false
    91  	if strings.ToLower(svc.Spec.ClusterIP) == "none" {
    92  		headless = true
    93  	}
    94  	svcInfo := NewService(clusterIP, headless, svc.Labels, svc.Spec.Selector)
    95  	svcInfo.IncludeExternal = getAnnotationIncludeExternal(svc)
    96  	svcInfo.Shared = getAnnotationShared(svc)
    97  
    98  	for _, port := range svc.Spec.Ports {
    99  		p := loadbalancer.NewFEPort(loadbalancer.L4Type(port.Protocol), uint16(port.Port))
   100  		portName := loadbalancer.FEPortName(port.Name)
   101  		if _, ok := svcInfo.Ports[portName]; !ok {
   102  			svcInfo.Ports[portName] = p
   103  		}
   104  		// This is a hack;-( In the case of NodePort service, we need to create
   105  		// three surrogate frontends per IP protocol - one with a zero IP addr used
   106  		// by the host-lb, one with a public iface IP addr and one with cilium_host
   107  		// IP addr.
   108  		// For each frontend we will need to store a service ID used for a reverse
   109  		// NAT translation and for deleting a service.
   110  		// Unfortunately, doing this in daemon/{loadbalancer,k8s_watcher}.go
   111  		// would introduce more complexity in already too complex LB codebase,
   112  		// so for now (until we have refactored the LB code) keep NodePort
   113  		// frontends in Service.NodePorts.
   114  		if svc.Spec.Type == v1.ServiceTypeNodePort || svc.Spec.Type == v1.ServiceTypeLoadBalancer {
   115  			if option.Config.EnableNodePort {
   116  				if _, ok := svcInfo.NodePorts[portName]; !ok {
   117  					svcInfo.NodePorts[portName] =
   118  						make(map[string]*loadbalancer.L3n4AddrID)
   119  				}
   120  				proto := loadbalancer.L4Type(port.Protocol)
   121  				port := uint16(port.NodePort)
   122  				id := loadbalancer.ID(0) // will be allocated by k8s_watcher
   123  
   124  				// TODO(brb) switch to if-clause when dual stack is supported
   125  				switch {
   126  				case option.Config.EnableIPv4 &&
   127  					clusterIP != nil && !strings.Contains(svc.Spec.ClusterIP, ":"):
   128  
   129  					for _, ip := range []net.IP{net.IPv4(0, 0, 0, 0), node.GetNodePortIPv4(), node.GetInternalIPv4()} {
   130  						nodePortFE := loadbalancer.NewL3n4AddrID(proto, ip, port, id)
   131  						svcInfo.NodePorts[portName][nodePortFE.String()] = nodePortFE
   132  					}
   133  				case option.Config.EnableIPv6 &&
   134  					clusterIP != nil && strings.Contains(svc.Spec.ClusterIP, ":"):
   135  
   136  					for _, ip := range []net.IP{net.IPv6zero, node.GetNodePortIPv6(), node.GetIPv6Router()} {
   137  						nodePortFE := loadbalancer.NewL3n4AddrID(proto, ip, port, id)
   138  						svcInfo.NodePorts[portName][nodePortFE.String()] = nodePortFE
   139  					}
   140  				}
   141  			}
   142  		}
   143  	}
   144  
   145  	return svcID, svcInfo
   146  }
   147  
   148  // ServiceID identities the Kubernetes service
   149  type ServiceID struct {
   150  	Name      string `json:"serviceName,omitempty"`
   151  	Namespace string `json:"namespace,omitempty"`
   152  }
   153  
   154  // String returns the string representation of a service ID
   155  func (s ServiceID) String() string {
   156  	return fmt.Sprintf("%s/%s", s.Namespace, s.Name)
   157  }
   158  
   159  // ParseServiceIDFrom returns a ServiceID derived from the given kubernetes
   160  // service FQDN.
   161  func ParseServiceIDFrom(dn string) *ServiceID {
   162  	// typical service name "cilium-etcd-client.kube-system.svc"
   163  	idx1 := strings.IndexByte(dn, '.')
   164  	if idx1 >= 0 {
   165  		svc := ServiceID{
   166  			Name: dn[:idx1],
   167  		}
   168  		idx2 := strings.IndexByte(dn[idx1+1:], '.')
   169  		if idx2 >= 0 {
   170  			// "cilium-etcd-client.kube-system.svc"
   171  			//                     ^idx1+1    ^ idx1+1+idx2
   172  			svc.Namespace = dn[idx1+1 : idx1+1+idx2]
   173  		} else {
   174  			// "cilium-etcd-client.kube-system"
   175  			//                     ^idx1+1
   176  			svc.Namespace = dn[idx1+1:]
   177  		}
   178  		return &svc
   179  	}
   180  	return nil
   181  }
   182  
   183  // Service is an abstraction for a k8s service that is composed by the frontend IP
   184  // address (FEIP) and the map of the frontend ports (Ports).
   185  type Service struct {
   186  	FrontendIP net.IP
   187  	IsHeadless bool
   188  
   189  	// IncludeExternal is true when external endpoints from other clusters
   190  	// should be included
   191  	IncludeExternal bool
   192  
   193  	// Shared is true when the service should be exposed/shared to other clusters
   194  	Shared bool
   195  
   196  	Ports map[loadbalancer.FEPortName]*loadbalancer.FEPort
   197  	// NodePorts stores mapping for port name => NodePort frontend addr string =>
   198  	// NodePort fronted addr. The string addr => addr indirection is to avoid
   199  	// storing duplicates.
   200  	NodePorts map[loadbalancer.FEPortName]map[string]*loadbalancer.L3n4AddrID
   201  	Labels    map[string]string
   202  	Selector  map[string]string
   203  }
   204  
   205  // String returns the string representation of a service resource
   206  func (s *Service) String() string {
   207  	if s == nil {
   208  		return "nil"
   209  	}
   210  
   211  	ports := make([]string, len(s.Ports))
   212  	i := 0
   213  	for p := range s.Ports {
   214  		ports[i] = string(p)
   215  		i++
   216  	}
   217  
   218  	return fmt.Sprintf("frontend:%s/ports=%s/selector=%v", s.FrontendIP.String(), ports, s.Selector)
   219  }
   220  
   221  // IsExternal returns true if the service is expected to serve out-of-cluster endpoints:
   222  func (s Service) IsExternal() bool {
   223  	return len(s.Selector) == 0
   224  }
   225  
   226  // DeepEquals returns true if both services are equal
   227  func (s *Service) DeepEquals(o *Service) bool {
   228  	switch {
   229  	case (s == nil) != (o == nil):
   230  		return false
   231  	case (s == nil) && (o == nil):
   232  		return true
   233  	}
   234  	if s.IsHeadless == o.IsHeadless &&
   235  		s.FrontendIP.Equal(o.FrontendIP) &&
   236  		comparator.MapStringEquals(s.Labels, o.Labels) &&
   237  		comparator.MapStringEquals(s.Selector, o.Selector) {
   238  
   239  		if ((s.Ports == nil) != (o.Ports == nil)) ||
   240  			len(s.Ports) != len(o.Ports) {
   241  			return false
   242  		}
   243  		for portName, port := range s.Ports {
   244  			oPort, ok := o.Ports[portName]
   245  			if !ok {
   246  				return false
   247  			}
   248  			if !port.EqualsIgnoreID(oPort) {
   249  				return false
   250  			}
   251  		}
   252  
   253  		if ((s.NodePorts == nil) != (o.NodePorts == nil)) ||
   254  			len(s.NodePorts) != len(o.NodePorts) {
   255  			return false
   256  		}
   257  		for portName, nodePorts := range s.NodePorts {
   258  			oNodePorts, ok := o.NodePorts[portName]
   259  			if !ok {
   260  				return false
   261  			}
   262  			if ((nodePorts == nil) != (oNodePorts == nil)) ||
   263  				len(nodePorts) != len(oNodePorts) {
   264  				return false
   265  			}
   266  			for nodePortName, nodePort := range nodePorts {
   267  				oNodePort, ok := oNodePorts[nodePortName]
   268  				if !ok {
   269  					return false
   270  				}
   271  				if !nodePort.Equals(oNodePort) {
   272  					return false
   273  				}
   274  			}
   275  		}
   276  		return true
   277  	}
   278  	return false
   279  }
   280  
   281  // NewService returns a new Service with the Ports map initialized.
   282  func NewService(ip net.IP, headless bool, labels map[string]string, selector map[string]string) *Service {
   283  	return &Service{
   284  		FrontendIP: ip,
   285  		IsHeadless: headless,
   286  		Ports:      map[loadbalancer.FEPortName]*loadbalancer.FEPort{},
   287  		NodePorts:  map[loadbalancer.FEPortName]map[string]*loadbalancer.L3n4AddrID{},
   288  		Labels:     labels,
   289  		Selector:   selector,
   290  	}
   291  }
   292  
   293  // UniquePorts returns a map of all unique ports configured in the service
   294  func (s *Service) UniquePorts() map[uint16]bool {
   295  	// We are not discriminating the different L4 protocols on the same L4
   296  	// port so we create the number of unique sets of service IP + service
   297  	// port.
   298  	uniqPorts := map[uint16]bool{}
   299  	for _, p := range s.Ports {
   300  		uniqPorts[p.Port] = true
   301  	}
   302  	return uniqPorts
   303  }
   304  
   305  // NewClusterService returns the service.ClusterService representing a
   306  // Kubernetes Service
   307  func NewClusterService(id ServiceID, k8sService *Service, k8sEndpoints *Endpoints) service.ClusterService {
   308  	svc := service.NewClusterService(id.Name, id.Namespace)
   309  
   310  	for key, value := range k8sService.Labels {
   311  		svc.Labels[key] = value
   312  	}
   313  
   314  	for key, value := range k8sService.Selector {
   315  		svc.Selector[key] = value
   316  	}
   317  
   318  	portConfig := service.PortConfiguration{}
   319  	for portName, port := range k8sService.Ports {
   320  		portConfig[string(portName)] = port.L4Addr
   321  	}
   322  
   323  	svc.Frontends = map[string]service.PortConfiguration{}
   324  	svc.Frontends[k8sService.FrontendIP.String()] = portConfig
   325  
   326  	svc.Backends = map[string]service.PortConfiguration{}
   327  	for ipString, portConfig := range k8sEndpoints.Backends {
   328  		svc.Backends[ipString] = portConfig
   329  	}
   330  
   331  	return svc
   332  }
   333  
   334  type ServiceIPGetter interface {
   335  	GetServiceIP(svcID ServiceID) *loadbalancer.L3n4Addr
   336  }
   337  
   338  // CreateCustomDialer returns a custom dialer that picks the service IP,
   339  // from the given ServiceIPGetter, if the address the used to dial is a k8s
   340  // service.
   341  func CreateCustomDialer(b ServiceIPGetter, log *logrus.Entry) func(s string, duration time.Duration) (conn net.Conn, e error) {
   342  	return func(s string, duration time.Duration) (conn net.Conn, e error) {
   343  		// If the service is available, do the service translation to
   344  		// the service IP. Otherwise dial with the original service
   345  		// name `s`.
   346  		u, err := url.Parse(s)
   347  		if err == nil {
   348  			svc := ParseServiceIDFrom(u.Host)
   349  			if svc != nil {
   350  				svcIP := b.GetServiceIP(*svc)
   351  				if svcIP != nil {
   352  					s = svcIP.String()
   353  				}
   354  			} else {
   355  				log.Debug("Service not found")
   356  			}
   357  			log.Debugf("custom dialer based on k8s service backend is dialing to %q", s)
   358  		} else {
   359  			log.Errorf("Unable to parse etcd service URL %s", err)
   360  		}
   361  		return net.Dial("tcp", s)
   362  	}
   363  }