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 }