k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controlplane/apiserver/server.go (about)

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package apiserver
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"time"
    23  
    24  	coordinationapiv1 "k8s.io/api/coordination/v1"
    25  	apiv1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/util/runtime"
    28  	"k8s.io/apimachinery/pkg/util/uuid"
    29  	"k8s.io/apimachinery/pkg/util/wait"
    30  	apiserverfeatures "k8s.io/apiserver/pkg/features"
    31  	peerreconcilers "k8s.io/apiserver/pkg/reconcilers"
    32  	genericregistry "k8s.io/apiserver/pkg/registry/generic"
    33  	genericapiserver "k8s.io/apiserver/pkg/server"
    34  	"k8s.io/apiserver/pkg/server/dynamiccertificates"
    35  	serverstorage "k8s.io/apiserver/pkg/server/storage"
    36  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    37  	clientgoinformers "k8s.io/client-go/informers"
    38  	"k8s.io/client-go/kubernetes"
    39  	"k8s.io/component-helpers/apimachinery/lease"
    40  	"k8s.io/klog/v2"
    41  	"k8s.io/utils/clock"
    42  
    43  	"k8s.io/kubernetes/pkg/controlplane/controller/apiserverleasegc"
    44  	"k8s.io/kubernetes/pkg/controlplane/controller/clusterauthenticationtrust"
    45  	"k8s.io/kubernetes/pkg/controlplane/controller/legacytokentracking"
    46  	"k8s.io/kubernetes/pkg/controlplane/controller/systemnamespaces"
    47  	"k8s.io/kubernetes/pkg/features"
    48  	"k8s.io/kubernetes/pkg/routes"
    49  	"k8s.io/kubernetes/pkg/serviceaccount"
    50  )
    51  
    52  var (
    53  	// IdentityLeaseGCPeriod is the interval which the lease GC controller checks for expired leases
    54  	// IdentityLeaseGCPeriod is exposed so integration tests can tune this value.
    55  	IdentityLeaseGCPeriod = 3600 * time.Second
    56  	// IdentityLeaseDurationSeconds is the duration of kube-apiserver lease in seconds
    57  	// IdentityLeaseDurationSeconds is exposed so integration tests can tune this value.
    58  	IdentityLeaseDurationSeconds = 3600
    59  	// IdentityLeaseRenewIntervalPeriod is the interval of kube-apiserver renewing its lease in seconds
    60  	// IdentityLeaseRenewIntervalPeriod is exposed so integration tests can tune this value.
    61  	IdentityLeaseRenewIntervalPeriod = 10 * time.Second
    62  )
    63  
    64  const (
    65  	// IdentityLeaseComponentLabelKey is used to apply a component label to identity lease objects, indicating:
    66  	//   1. the lease is an identity lease (different from leader election leases)
    67  	//   2. which component owns this lease
    68  	IdentityLeaseComponentLabelKey = "apiserver.kubernetes.io/identity"
    69  )
    70  
    71  // Server is a struct that contains a generic control plane apiserver instance
    72  // that can be run to start serving the APIs.
    73  type Server struct {
    74  	GenericAPIServer *genericapiserver.GenericAPIServer
    75  
    76  	APIResourceConfigSource   serverstorage.APIResourceConfigSource
    77  	RESTOptionsGetter         genericregistry.RESTOptionsGetter
    78  	ClusterAuthenticationInfo clusterauthenticationtrust.ClusterAuthenticationInfo
    79  	VersionedInformers        clientgoinformers.SharedInformerFactory
    80  }
    81  
    82  // New returns a new instance of Master from the given config.
    83  // Certain config fields will be set to a default value if unset.
    84  // Certain config fields must be specified, including:
    85  // KubeletClientConfig
    86  func (c completedConfig) New(name string, delegationTarget genericapiserver.DelegationTarget) (*Server, error) {
    87  	generic, err := c.Generic.New(name, delegationTarget)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	if c.EnableLogsSupport {
    93  		routes.Logs{}.Install(generic.Handler.GoRestfulContainer)
    94  	}
    95  
    96  	// Metadata and keys are expected to only change across restarts at present,
    97  	// so we just marshal immediately and serve the cached JSON bytes.
    98  	md, err := serviceaccount.NewOpenIDMetadata(
    99  		c.ServiceAccountIssuerURL,
   100  		c.ServiceAccountJWKSURI,
   101  		c.Generic.ExternalAddress,
   102  		c.ServiceAccountPublicKeys,
   103  	)
   104  	if err != nil {
   105  		// If there was an error, skip installing the endpoints and log the
   106  		// error, but continue on. We don't return the error because the
   107  		// metadata responses require additional, backwards incompatible
   108  		// validation of command-line options.
   109  		msg := fmt.Sprintf("Could not construct pre-rendered responses for"+
   110  			" ServiceAccountIssuerDiscovery endpoints. Endpoints will not be"+
   111  			" enabled. Error: %v", err)
   112  		if c.ServiceAccountIssuerURL != "" {
   113  			// The user likely expects this feature to be enabled if issuer URL is
   114  			// set and the feature gate is enabled. In the future, if there is no
   115  			// longer a feature gate and issuer URL is not set, the user may not
   116  			// expect this feature to be enabled. We log the former case as an Error
   117  			// and the latter case as an Info.
   118  			klog.Error(msg)
   119  		} else {
   120  			klog.Info(msg)
   121  		}
   122  	} else {
   123  		routes.NewOpenIDMetadataServer(md.ConfigJSON, md.PublicKeysetJSON).
   124  			Install(generic.Handler.GoRestfulContainer)
   125  	}
   126  
   127  	s := &Server{
   128  		GenericAPIServer: generic,
   129  
   130  		APIResourceConfigSource:   c.APIResourceConfigSource,
   131  		RESTOptionsGetter:         c.Generic.RESTOptionsGetter,
   132  		ClusterAuthenticationInfo: c.ClusterAuthenticationInfo,
   133  		VersionedInformers:        c.VersionedInformers,
   134  	}
   135  
   136  	client, err := kubernetes.NewForConfig(s.GenericAPIServer.LoopbackClientConfig)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  	if len(c.SystemNamespaces) > 0 {
   141  		s.GenericAPIServer.AddPostStartHookOrDie("start-system-namespaces-controller", func(hookContext genericapiserver.PostStartHookContext) error {
   142  			go systemnamespaces.NewController(c.SystemNamespaces, client, s.VersionedInformers.Core().V1().Namespaces()).Run(hookContext.StopCh)
   143  			return nil
   144  		})
   145  	}
   146  
   147  	_, publicServicePort, err := c.Generic.SecureServing.HostPort()
   148  	if err != nil {
   149  		return nil, fmt.Errorf("failed to get listener address: %w", err)
   150  	}
   151  
   152  	if utilfeature.DefaultFeatureGate.Enabled(features.UnknownVersionInteroperabilityProxy) {
   153  		peeraddress := getPeerAddress(c.Extra.PeerAdvertiseAddress, c.Generic.PublicAddress, publicServicePort)
   154  		peerEndpointCtrl := peerreconcilers.New(
   155  			c.Generic.APIServerID,
   156  			peeraddress,
   157  			c.Extra.PeerEndpointLeaseReconciler,
   158  			c.Extra.PeerEndpointReconcileInterval,
   159  			client)
   160  		if err != nil {
   161  			return nil, fmt.Errorf("failed to create peer endpoint lease controller: %w", err)
   162  		}
   163  		s.GenericAPIServer.AddPostStartHookOrDie("peer-endpoint-reconciler-controller",
   164  			func(hookContext genericapiserver.PostStartHookContext) error {
   165  				peerEndpointCtrl.Start(hookContext.StopCh)
   166  				return nil
   167  			})
   168  		s.GenericAPIServer.AddPreShutdownHookOrDie("peer-endpoint-reconciler-controller",
   169  			func() error {
   170  				peerEndpointCtrl.Stop()
   171  				return nil
   172  			})
   173  		if c.Extra.PeerProxy != nil {
   174  			s.GenericAPIServer.AddPostStartHookOrDie("unknown-version-proxy-filter", func(context genericapiserver.PostStartHookContext) error {
   175  				err := c.Extra.PeerProxy.WaitForCacheSync(context.StopCh)
   176  				return err
   177  			})
   178  		}
   179  	}
   180  
   181  	s.GenericAPIServer.AddPostStartHookOrDie("start-cluster-authentication-info-controller", func(hookContext genericapiserver.PostStartHookContext) error {
   182  		controller := clusterauthenticationtrust.NewClusterAuthenticationTrustController(s.ClusterAuthenticationInfo, client)
   183  
   184  		// generate a context  from stopCh. This is to avoid modifying files which are relying on apiserver
   185  		// TODO: See if we can pass ctx to the current method
   186  		ctx := wait.ContextForChannel(hookContext.StopCh)
   187  
   188  		// prime values and start listeners
   189  		if s.ClusterAuthenticationInfo.ClientCA != nil {
   190  			s.ClusterAuthenticationInfo.ClientCA.AddListener(controller)
   191  			if controller, ok := s.ClusterAuthenticationInfo.ClientCA.(dynamiccertificates.ControllerRunner); ok {
   192  				// runonce to be sure that we have a value.
   193  				if err := controller.RunOnce(ctx); err != nil {
   194  					runtime.HandleError(err)
   195  				}
   196  				go controller.Run(ctx, 1)
   197  			}
   198  		}
   199  		if s.ClusterAuthenticationInfo.RequestHeaderCA != nil {
   200  			s.ClusterAuthenticationInfo.RequestHeaderCA.AddListener(controller)
   201  			if controller, ok := s.ClusterAuthenticationInfo.RequestHeaderCA.(dynamiccertificates.ControllerRunner); ok {
   202  				// runonce to be sure that we have a value.
   203  				if err := controller.RunOnce(ctx); err != nil {
   204  					runtime.HandleError(err)
   205  				}
   206  				go controller.Run(ctx, 1)
   207  			}
   208  		}
   209  
   210  		go controller.Run(ctx, 1)
   211  		return nil
   212  	})
   213  
   214  	if utilfeature.DefaultFeatureGate.Enabled(apiserverfeatures.APIServerIdentity) {
   215  		s.GenericAPIServer.AddPostStartHookOrDie("start-kube-apiserver-identity-lease-controller", func(hookContext genericapiserver.PostStartHookContext) error {
   216  			// generate a context  from stopCh. This is to avoid modifying files which are relying on apiserver
   217  			// TODO: See if we can pass ctx to the current method
   218  			ctx := wait.ContextForChannel(hookContext.StopCh)
   219  
   220  			leaseName := s.GenericAPIServer.APIServerID
   221  			holderIdentity := s.GenericAPIServer.APIServerID + "_" + string(uuid.NewUUID())
   222  
   223  			peeraddress := getPeerAddress(c.Extra.PeerAdvertiseAddress, c.Generic.PublicAddress, publicServicePort)
   224  			// must replace ':,[]' in [ip:port] to be able to store this as a valid label value
   225  			controller := lease.NewController(
   226  				clock.RealClock{},
   227  				client,
   228  				holderIdentity,
   229  				int32(IdentityLeaseDurationSeconds),
   230  				nil,
   231  				IdentityLeaseRenewIntervalPeriod,
   232  				leaseName,
   233  				metav1.NamespaceSystem,
   234  				// TODO: receive identity label value as a parameter when post start hook is moved to generic apiserver.
   235  				labelAPIServerHeartbeatFunc(name, peeraddress))
   236  			go controller.Run(ctx)
   237  			return nil
   238  		})
   239  		// TODO: move this into generic apiserver and make the lease identity value configurable
   240  		s.GenericAPIServer.AddPostStartHookOrDie("start-kube-apiserver-identity-lease-garbage-collector", func(hookContext genericapiserver.PostStartHookContext) error {
   241  			go apiserverleasegc.NewAPIServerLeaseGC(
   242  				client,
   243  				IdentityLeaseGCPeriod,
   244  				metav1.NamespaceSystem,
   245  				IdentityLeaseComponentLabelKey+"="+name,
   246  			).Run(hookContext.StopCh)
   247  			return nil
   248  		})
   249  	}
   250  
   251  	s.GenericAPIServer.AddPostStartHookOrDie("start-legacy-token-tracking-controller", func(hookContext genericapiserver.PostStartHookContext) error {
   252  		go legacytokentracking.NewController(client).Run(hookContext.StopCh)
   253  		return nil
   254  	})
   255  
   256  	return s, nil
   257  }
   258  
   259  func labelAPIServerHeartbeatFunc(identity string, peeraddress string) lease.ProcessLeaseFunc {
   260  	return func(lease *coordinationapiv1.Lease) error {
   261  		if lease.Labels == nil {
   262  			lease.Labels = map[string]string{}
   263  		}
   264  
   265  		if lease.Annotations == nil {
   266  			lease.Annotations = map[string]string{}
   267  		}
   268  
   269  		// This label indiciates the identity of the lease object.
   270  		lease.Labels[IdentityLeaseComponentLabelKey] = identity
   271  
   272  		hostname, err := os.Hostname()
   273  		if err != nil {
   274  			return err
   275  		}
   276  
   277  		// convenience label to easily map a lease object to a specific apiserver
   278  		lease.Labels[apiv1.LabelHostname] = hostname
   279  
   280  		// Include apiserver network location <ip_port> used by peers to proxy requests between kube-apiservers
   281  		if utilfeature.DefaultFeatureGate.Enabled(features.UnknownVersionInteroperabilityProxy) {
   282  			if peeraddress != "" {
   283  				lease.Annotations[apiv1.AnnotationPeerAdvertiseAddress] = peeraddress
   284  			}
   285  		}
   286  		return nil
   287  	}
   288  }