github.com/zhyoulun/cilium@v1.6.12/pkg/nodediscovery/nodediscovery.go (about)

     1  // Copyright 2016-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 nodediscovery
    16  
    17  import (
    18  	"context"
    19  	"time"
    20  
    21  	"github.com/cilium/cilium/pkg/aws/metadata"
    22  	"github.com/cilium/cilium/pkg/cidr"
    23  	"github.com/cilium/cilium/pkg/controller"
    24  	"github.com/cilium/cilium/pkg/datapath"
    25  	"github.com/cilium/cilium/pkg/defaults"
    26  	"github.com/cilium/cilium/pkg/k8s"
    27  	ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    28  	"github.com/cilium/cilium/pkg/logging"
    29  	"github.com/cilium/cilium/pkg/logging/logfields"
    30  	"github.com/cilium/cilium/pkg/mtu"
    31  	"github.com/cilium/cilium/pkg/node"
    32  	"github.com/cilium/cilium/pkg/node/addressing"
    33  	nodemanager "github.com/cilium/cilium/pkg/node/manager"
    34  	nodestore "github.com/cilium/cilium/pkg/node/store"
    35  	"github.com/cilium/cilium/pkg/option"
    36  	"github.com/cilium/cilium/pkg/source"
    37  	cnitypes "github.com/cilium/cilium/plugins/cilium-cni/types"
    38  
    39  	"k8s.io/apimachinery/pkg/api/errors"
    40  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    41  )
    42  
    43  const (
    44  	// AutoCIDR indicates that a CIDR should be allocated
    45  	AutoCIDR = "auto"
    46  
    47  	nodeDiscoverySubsys = "nodediscovery"
    48  	maxRetryCount       = 5
    49  )
    50  
    51  var log = logging.DefaultLogger.WithField(logfields.LogSubsys, nodeDiscoverySubsys)
    52  
    53  // NodeDiscovery represents a node discovery action
    54  type NodeDiscovery struct {
    55  	Manager     *nodemanager.Manager
    56  	LocalConfig datapath.LocalNodeConfiguration
    57  	Registrar   nodestore.NodeRegistrar
    58  	LocalNode   node.Node
    59  	Registered  chan struct{}
    60  }
    61  
    62  func enableLocalNodeRoute() bool {
    63  	return !option.Config.IsFlannelMasterDeviceSet() && option.Config.IPAM != option.IPAMENI
    64  }
    65  
    66  // NewNodeDiscovery returns a pointer to new node discovery object
    67  func NewNodeDiscovery(manager *nodemanager.Manager, mtuConfig mtu.Configuration) *NodeDiscovery {
    68  	auxPrefixes := []*cidr.CIDR{}
    69  
    70  	if option.Config.IPv4ServiceRange != AutoCIDR {
    71  		serviceCIDR, err := cidr.ParseCIDR(option.Config.IPv4ServiceRange)
    72  		if err != nil {
    73  			log.WithError(err).WithField(logfields.V4Prefix, option.Config.IPv4ServiceRange).Fatal("Invalid IPv4 service prefix")
    74  		}
    75  
    76  		auxPrefixes = append(auxPrefixes, serviceCIDR)
    77  	}
    78  
    79  	if option.Config.IPv6ServiceRange != AutoCIDR {
    80  		serviceCIDR, err := cidr.ParseCIDR(option.Config.IPv6ServiceRange)
    81  		if err != nil {
    82  			log.WithError(err).WithField(logfields.V6Prefix, option.Config.IPv6ServiceRange).Fatal("Invalid IPv6 service prefix")
    83  		}
    84  
    85  		auxPrefixes = append(auxPrefixes, serviceCIDR)
    86  	}
    87  
    88  	return &NodeDiscovery{
    89  		Manager: manager,
    90  		LocalConfig: datapath.LocalNodeConfiguration{
    91  			MtuConfig:               mtuConfig,
    92  			UseSingleClusterRoute:   option.Config.UseSingleClusterRoute,
    93  			EnableIPv4:              option.Config.EnableIPv4,
    94  			EnableIPv6:              option.Config.EnableIPv6,
    95  			EnableEncapsulation:     option.Config.Tunnel != option.TunnelDisabled,
    96  			EnableAutoDirectRouting: option.Config.EnableAutoDirectRouting,
    97  			EnableLocalNodeRoute:    enableLocalNodeRoute(),
    98  			AuxiliaryPrefixes:       auxPrefixes,
    99  			EnableIPSec:             option.Config.EnableIPSec,
   100  			EncryptNode:             option.Config.EncryptNode,
   101  			IPv4PodSubnets:          option.Config.IPv4PodSubnets,
   102  			IPv6PodSubnets:          option.Config.IPv6PodSubnets,
   103  		},
   104  		LocalNode: node.Node{
   105  			Source: source.Local,
   106  		},
   107  		Registered: make(chan struct{}),
   108  	}
   109  }
   110  
   111  // Configuration is the configuration interface that must be implemented in
   112  // order to manage node discovery
   113  type Configuration interface {
   114  	// GetNetConf must return the CNI configuration as passed in by the
   115  	// user
   116  	GetNetConf() *cnitypes.NetConf
   117  }
   118  
   119  // start configures the local node and starts node discovery. This is called on
   120  // agent startup to configure the local node based on the configuration options
   121  // passed to the agent. nodeName is the name to be used in the local agent.
   122  func (n *NodeDiscovery) StartDiscovery(nodeName string, conf Configuration) {
   123  	n.LocalNode.Name = nodeName
   124  	n.LocalNode.Cluster = option.Config.ClusterName
   125  	n.LocalNode.IPAddresses = []node.Address{}
   126  	n.LocalNode.IPv4AllocCIDR = node.GetIPv4AllocRange()
   127  	n.LocalNode.IPv6AllocCIDR = node.GetIPv6AllocRange()
   128  	n.LocalNode.ClusterID = option.Config.ClusterID
   129  	n.LocalNode.EncryptionKey = node.GetIPsecKeyIdentity()
   130  
   131  	if node.GetExternalIPv4() != nil {
   132  		n.LocalNode.IPAddresses = append(n.LocalNode.IPAddresses, node.Address{
   133  			Type: addressing.NodeInternalIP,
   134  			IP:   node.GetExternalIPv4(),
   135  		})
   136  	}
   137  
   138  	if node.GetIPv6() != nil {
   139  		n.LocalNode.IPAddresses = append(n.LocalNode.IPAddresses, node.Address{
   140  			Type: addressing.NodeInternalIP,
   141  			IP:   node.GetIPv6(),
   142  		})
   143  	}
   144  
   145  	if node.GetInternalIPv4() != nil {
   146  		n.LocalNode.IPAddresses = append(n.LocalNode.IPAddresses, node.Address{
   147  			Type: addressing.NodeCiliumInternalIP,
   148  			IP:   node.GetInternalIPv4(),
   149  		})
   150  	}
   151  
   152  	if node.GetIPv6Router() != nil {
   153  		n.LocalNode.IPAddresses = append(n.LocalNode.IPAddresses, node.Address{
   154  			Type: addressing.NodeCiliumInternalIP,
   155  			IP:   node.GetIPv6Router(),
   156  		})
   157  	}
   158  
   159  	n.Manager.NodeUpdated(n.LocalNode)
   160  
   161  	go func() {
   162  		log.Info("Adding local node to cluster")
   163  		for {
   164  			if err := n.Registrar.RegisterNode(&n.LocalNode, n.Manager); err != nil {
   165  				log.WithError(err).Error("Unable to initialize local node. Retrying...")
   166  				time.Sleep(time.Second)
   167  			} else {
   168  				break
   169  			}
   170  		}
   171  		close(n.Registered)
   172  	}()
   173  
   174  	go func() {
   175  		select {
   176  		case <-n.Registered:
   177  		case <-time.NewTimer(defaults.NodeInitTimeout).C:
   178  			log.Fatalf("Unable to initialize local node due to timeout")
   179  		}
   180  	}()
   181  
   182  	if k8s.IsEnabled() {
   183  		// Creation or update of the CiliumNode can be done in the
   184  		// background, nothing depends on the completion of this.
   185  		go n.UpdateCiliumNodeResource(conf)
   186  	}
   187  
   188  	if option.Config.KVStore != "" {
   189  		go func() {
   190  			<-n.Registered
   191  			controller.NewManager().UpdateController("propagating local node change to kv-store",
   192  				controller.ControllerParams{
   193  					DoFunc: func(ctx context.Context) error {
   194  						err := n.Registrar.UpdateLocalKeySync(&n.LocalNode)
   195  						if err != nil {
   196  							log.WithError(err).Error("Unable to propagate local node change to kvstore")
   197  						}
   198  						return err
   199  					},
   200  				})
   201  		}()
   202  	}
   203  }
   204  
   205  // Close shuts down the node discovery engine
   206  func (n *NodeDiscovery) Close() {
   207  	n.Manager.Close()
   208  }
   209  
   210  // UpdateCiliumNodeResource updates the CiliumNode resource representing the
   211  // local node
   212  func (n *NodeDiscovery) UpdateCiliumNodeResource(conf Configuration) {
   213  	if !option.Config.AutoCreateCiliumNodeResource {
   214  		return
   215  	}
   216  
   217  	ciliumClient := k8s.CiliumClient()
   218  
   219  	for retryCount := 0; retryCount < maxRetryCount; retryCount++ {
   220  		performUpdate := true
   221  		nodeResource, err := ciliumClient.CiliumV2().CiliumNodes().Get(node.GetName(), metav1.GetOptions{})
   222  		if err != nil {
   223  			performUpdate = false
   224  			nodeResource = &ciliumv2.CiliumNode{
   225  				ObjectMeta: metav1.ObjectMeta{
   226  					Name: node.GetName(),
   227  				},
   228  			}
   229  		}
   230  
   231  		n.mutateNodeResource(conf, nodeResource)
   232  
   233  		if performUpdate {
   234  			if _, err := ciliumClient.CiliumV2().CiliumNodes().Update(nodeResource); err != nil {
   235  				if errors.IsConflict(err) {
   236  					log.WithError(err).Warn("Unable to update CiliumNode resource, will retry")
   237  					continue
   238  				}
   239  				log.WithError(err).Fatal("Unable to update CiliumNode resource")
   240  			} else {
   241  				return
   242  			}
   243  		} else {
   244  			if _, err = ciliumClient.CiliumV2().CiliumNodes().Create(nodeResource); err != nil {
   245  				if errors.IsConflict(err) {
   246  					log.WithError(err).Warn("Unable to create CiliumNode resource, will retry")
   247  					continue
   248  				}
   249  				log.WithError(err).Fatal("Unable to create CiliumNode resource")
   250  			} else {
   251  				log.Info("Successfully created CiliumNode resource")
   252  				return
   253  			}
   254  		}
   255  	}
   256  	log.Fatal("Could not create or update CiliumNode resource, despite retries")
   257  }
   258  
   259  func (n *NodeDiscovery) mutateNodeResource(conf Configuration, nodeResource *ciliumv2.CiliumNode) {
   260  	// Tie the CiliumNode custom resource lifecycle to the lifecycle of the
   261  	// Kubernetes node
   262  	if k8sNode, err := k8s.GetNode(k8s.Client(), node.GetName()); err != nil {
   263  		log.WithError(err).Warning("Kubernetes node resource representing own node is not available, cannot set OwnerReference")
   264  	} else {
   265  		nodeResource.ObjectMeta.OwnerReferences = []metav1.OwnerReference{{
   266  			APIVersion: "v1",
   267  			Kind:       "Node",
   268  			Name:       node.GetName(),
   269  			UID:        k8sNode.UID,
   270  		}}
   271  	}
   272  
   273  	nodeResource.Spec.Addresses = []ciliumv2.NodeAddress{}
   274  	for _, address := range n.LocalNode.IPAddresses {
   275  		nodeResource.Spec.Addresses = append(nodeResource.Spec.Addresses, ciliumv2.NodeAddress{
   276  			Type: address.Type,
   277  			IP:   address.IP.String(),
   278  		})
   279  	}
   280  
   281  	nodeResource.Spec.IPAM.PodCIDRs = []string{}
   282  	if cidr := node.GetIPv4AllocRange(); cidr != nil {
   283  		nodeResource.Spec.IPAM.PodCIDRs = append(nodeResource.Spec.IPAM.PodCIDRs, cidr.String())
   284  	}
   285  
   286  	if cidr := node.GetIPv6AllocRange(); cidr != nil {
   287  		nodeResource.Spec.IPAM.PodCIDRs = append(nodeResource.Spec.IPAM.PodCIDRs, cidr.String())
   288  	}
   289  
   290  	nodeResource.Spec.Encryption.Key = int(node.GetIPsecKeyIdentity())
   291  
   292  	nodeResource.Spec.HealthAddressing.IPv4 = ""
   293  	if ip := n.LocalNode.IPv4HealthIP; ip != nil {
   294  		nodeResource.Spec.HealthAddressing.IPv4 = ip.String()
   295  	}
   296  
   297  	nodeResource.Spec.HealthAddressing.IPv6 = ""
   298  	if ip := n.LocalNode.IPv6HealthIP; ip != nil {
   299  		nodeResource.Spec.HealthAddressing.IPv6 = ip.String()
   300  	}
   301  
   302  	nodeResource.Spec.ENI = ciliumv2.ENISpec{}
   303  	if option.Config.IPAM == option.IPAMENI {
   304  		instanceID, instanceType, availabilityZone, vpcID, err := metadata.GetInstanceMetadata()
   305  		if err != nil {
   306  			log.WithError(err).Fatal("Unable to retrieve InstanceID of own EC2 instance")
   307  		}
   308  
   309  		nodeResource.Spec.ENI.VpcID = vpcID
   310  		nodeResource.Spec.ENI.FirstInterfaceIndex = 1
   311  		nodeResource.Spec.ENI.DeleteOnTermination = true
   312  		nodeResource.Spec.ENI.PreAllocate = defaults.ENIPreAllocation
   313  
   314  		if c := conf.GetNetConf(); c != nil {
   315  			if c.ENI.MinAllocate != 0 {
   316  				nodeResource.Spec.ENI.MinAllocate = c.ENI.MinAllocate
   317  			}
   318  
   319  			if c.ENI.PreAllocate != 0 {
   320  				nodeResource.Spec.ENI.PreAllocate = c.ENI.PreAllocate
   321  			}
   322  
   323  			if c.ENI.FirstInterfaceIndex != 0 {
   324  				nodeResource.Spec.ENI.FirstInterfaceIndex = c.ENI.FirstInterfaceIndex
   325  			}
   326  
   327  			if len(c.ENI.SecurityGroups) > 0 {
   328  				nodeResource.Spec.ENI.SecurityGroups = c.ENI.SecurityGroups
   329  			}
   330  
   331  			if len(c.ENI.SubnetTags) > 0 {
   332  				nodeResource.Spec.ENI.SubnetTags = c.ENI.SubnetTags
   333  			}
   334  
   335  			if c.ENI.VpcID != "" {
   336  				nodeResource.Spec.ENI.VpcID = c.ENI.VpcID
   337  			}
   338  
   339  			nodeResource.Spec.ENI.DeleteOnTermination = c.ENI.DeleteOnTermination
   340  		}
   341  
   342  		nodeResource.Spec.ENI.InstanceID = instanceID
   343  		nodeResource.Spec.ENI.InstanceType = instanceType
   344  		nodeResource.Spec.ENI.AvailabilityZone = availabilityZone
   345  	}
   346  }