2023-08-28 16:54:27 +00:00
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
2020-09-16 14:19:51 +00:00
// SPDX-License-Identifier: Apache-2.0
2020-07-23 15:05:21 +00:00
package apiserver
import (
2020-07-31 16:08:07 +00:00
"context"
2020-07-23 15:05:21 +00:00
"fmt"
2021-08-29 00:38:50 +00:00
"sync"
2020-07-23 15:05:21 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
2021-02-19 18:21:10 +00:00
"k8s.io/apimachinery/pkg/util/errors"
2020-07-23 15:05:21 +00:00
"k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
2023-06-22 20:12:50 +00:00
"go.pinniped.dev/internal/clientcertissuer"
2021-08-29 00:38:50 +00:00
"go.pinniped.dev/internal/controllerinit"
2020-11-10 15:22:16 +00:00
"go.pinniped.dev/internal/plog"
2023-08-28 16:54:27 +00:00
"go.pinniped.dev/internal/pversion"
2020-09-18 19:56:24 +00:00
"go.pinniped.dev/internal/registry/credentialrequest"
2021-02-19 18:21:10 +00:00
"go.pinniped.dev/internal/registry/whoamirequest"
2020-07-23 15:05:21 +00:00
)
type Config struct {
GenericConfig * genericapiserver . RecommendedConfig
ExtraConfig ExtraConfig
}
type ExtraConfig struct {
2020-09-21 16:37:54 +00:00
Authenticator credentialrequest . TokenCredentialRequestAuthenticator
2023-06-22 20:12:50 +00:00
Issuer clientcertissuer . ClientCertIssuer
2021-08-29 00:38:50 +00:00
BuildControllersPostStartHook controllerinit . RunnerBuilder
2021-01-13 01:27:41 +00:00
Scheme * runtime . Scheme
NegotiatedSerializer runtime . NegotiatedSerializer
2021-02-19 18:21:10 +00:00
LoginConciergeGroupVersion schema . GroupVersion
IdentityConciergeGroupVersion schema . GroupVersion
2020-07-23 15:05:21 +00:00
}
2020-08-20 17:54:15 +00:00
type PinnipedServer struct {
2020-07-23 15:05:21 +00:00
GenericAPIServer * genericapiserver . GenericAPIServer
}
type completedConfig struct {
GenericConfig genericapiserver . CompletedConfig
ExtraConfig * ExtraConfig
}
type CompletedConfig struct {
// Embed a private pointer that cannot be instantiated outside of this package.
* completedConfig
}
// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
func ( c * Config ) Complete ( ) CompletedConfig {
completedCfg := completedConfig {
c . GenericConfig . Complete ( ) ,
& c . ExtraConfig ,
}
2023-08-28 16:54:27 +00:00
versionInfo := pversion . Get ( )
2020-07-23 15:05:21 +00:00
completedCfg . GenericConfig . Version = & versionInfo
return CompletedConfig { completedConfig : & completedCfg }
}
// New returns a new instance of AdmissionServer from the given config.
2020-08-20 17:54:15 +00:00
func ( c completedConfig ) New ( ) ( * PinnipedServer , error ) {
2020-10-06 18:59:03 +00:00
genericServer , err := c . GenericConfig . New ( "pinniped-concierge" , genericapiserver . NewEmptyDelegate ( ) ) // completion is done in Complete, no need for a second time
2020-07-23 15:05:21 +00:00
if err != nil {
return nil , fmt . Errorf ( "completion error: %w" , err )
}
2020-08-20 17:54:15 +00:00
s := & PinnipedServer {
2020-07-23 15:05:21 +00:00
GenericAPIServer : genericServer ,
}
2022-08-24 21:45:55 +00:00
var errs [ ] error //nolint:prealloc
2021-02-19 18:21:10 +00:00
for _ , f := range [ ] func ( ) ( schema . GroupVersionResource , rest . Storage ) {
func ( ) ( schema . GroupVersionResource , rest . Storage ) {
tokenCredReqGVR := c . ExtraConfig . LoginConciergeGroupVersion . WithResource ( "tokencredentialrequests" )
tokenCredStorage := credentialrequest . NewREST ( c . ExtraConfig . Authenticator , c . ExtraConfig . Issuer , tokenCredReqGVR . GroupResource ( ) )
return tokenCredReqGVR , tokenCredStorage
} ,
func ( ) ( schema . GroupVersionResource , rest . Storage ) {
whoAmIReqGVR := c . ExtraConfig . IdentityConciergeGroupVersion . WithResource ( "whoamirequests" )
whoAmIStorage := whoamirequest . NewREST ( whoAmIReqGVR . GroupResource ( ) )
return whoAmIReqGVR , whoAmIStorage
} ,
} {
gvr , storage := f ( )
errs = append ( errs ,
s . GenericAPIServer . InstallAPIGroup (
& genericapiserver . APIGroupInfo {
PrioritizedVersions : [ ] schema . GroupVersion { gvr . GroupVersion ( ) } ,
VersionedResourcesStorageMap : map [ string ] map [ string ] rest . Storage { gvr . Version : { gvr . Resource : storage } } ,
OptionsExternalVersion : & schema . GroupVersion { Version : "v1" } ,
Scheme : c . ExtraConfig . Scheme ,
ParameterCodec : metav1 . ParameterCodec ,
NegotiatedSerializer : c . ExtraConfig . NegotiatedSerializer ,
} ,
) ,
)
}
if err := errors . NewAggregate ( errs ) ; err != nil {
return nil , fmt . Errorf ( "could not install API groups: %w" , err )
2020-07-23 15:05:21 +00:00
}
Fix deadlock during shutdown which prevented leader election cleanup
Before this fix, the deadlock would prevent the leader pod from giving
up its lease, which would make it take several minutes for new pods to
be allowed to elect a new leader. During that time, no Pinniped
controllers could write to the Kube API, so important resources were not
being updated during that window. It would also make pod shutdown take
about 1 minute.
After this fix, the leader gives up its lease immediately, and pod
shutdown takes about 1 second. This improves restart/upgrade time and
also fixes the problem where there was no leader for several minutes
after a restart/upgrade.
The deadlock was between the post-start hook and the pre-shutdown hook.
The pre-shutdown hook blocked until a certain background goroutine in
the post-start hook finished, but that goroutine could not finish until
the pre-shutdown hook finished. Thus, they were both blocked, waiting
for each other infinitely. Eventually the process would be externally
killed.
This deadlock was most likely introduced by some change in Kube's
generic api server package related to how the many complex channels used
during server shutdown interact with each other, and was not noticed
when we upgraded to the version which introduced the change.
2023-09-20 23:51:23 +00:00
controllersShutdownWaitGroup := & sync . WaitGroup { }
controllersCtx , cancelControllerCtx := context . WithCancel ( context . Background ( ) )
2020-07-31 16:08:07 +00:00
s . GenericAPIServer . AddPostStartHookOrDie ( "start-controllers" ,
func ( postStartContext genericapiserver . PostStartHookContext ) error {
2020-11-10 15:22:16 +00:00
plog . Debug ( "start-controllers post start hook starting" )
Fix deadlock during shutdown which prevented leader election cleanup
Before this fix, the deadlock would prevent the leader pod from giving
up its lease, which would make it take several minutes for new pods to
be allowed to elect a new leader. During that time, no Pinniped
controllers could write to the Kube API, so important resources were not
being updated during that window. It would also make pod shutdown take
about 1 minute.
After this fix, the leader gives up its lease immediately, and pod
shutdown takes about 1 second. This improves restart/upgrade time and
also fixes the problem where there was no leader for several minutes
after a restart/upgrade.
The deadlock was between the post-start hook and the pre-shutdown hook.
The pre-shutdown hook blocked until a certain background goroutine in
the post-start hook finished, but that goroutine could not finish until
the pre-shutdown hook finished. Thus, they were both blocked, waiting
for each other infinitely. Eventually the process would be externally
killed.
This deadlock was most likely introduced by some change in Kube's
generic api server package related to how the many complex channels used
during server shutdown interact with each other, and was not noticed
when we upgraded to the version which introduced the change.
2023-09-20 23:51:23 +00:00
defer plog . Debug ( "start-controllers post start hook completed" )
2020-07-31 16:08:07 +00:00
Fix deadlock during shutdown which prevented leader election cleanup
Before this fix, the deadlock would prevent the leader pod from giving
up its lease, which would make it take several minutes for new pods to
be allowed to elect a new leader. During that time, no Pinniped
controllers could write to the Kube API, so important resources were not
being updated during that window. It would also make pod shutdown take
about 1 minute.
After this fix, the leader gives up its lease immediately, and pod
shutdown takes about 1 second. This improves restart/upgrade time and
also fixes the problem where there was no leader for several minutes
after a restart/upgrade.
The deadlock was between the post-start hook and the pre-shutdown hook.
The pre-shutdown hook blocked until a certain background goroutine in
the post-start hook finished, but that goroutine could not finish until
the pre-shutdown hook finished. Thus, they were both blocked, waiting
for each other infinitely. Eventually the process would be externally
killed.
This deadlock was most likely introduced by some change in Kube's
generic api server package related to how the many complex channels used
during server shutdown interact with each other, and was not noticed
when we upgraded to the version which introduced the change.
2023-09-20 23:51:23 +00:00
runControllers , err := c . ExtraConfig . BuildControllersPostStartHook ( controllersCtx )
2021-08-29 00:38:50 +00:00
if err != nil {
return fmt . Errorf ( "cannot create run controller func: %w" , err )
}
Fix deadlock during shutdown which prevented leader election cleanup
Before this fix, the deadlock would prevent the leader pod from giving
up its lease, which would make it take several minutes for new pods to
be allowed to elect a new leader. During that time, no Pinniped
controllers could write to the Kube API, so important resources were not
being updated during that window. It would also make pod shutdown take
about 1 minute.
After this fix, the leader gives up its lease immediately, and pod
shutdown takes about 1 second. This improves restart/upgrade time and
also fixes the problem where there was no leader for several minutes
after a restart/upgrade.
The deadlock was between the post-start hook and the pre-shutdown hook.
The pre-shutdown hook blocked until a certain background goroutine in
the post-start hook finished, but that goroutine could not finish until
the pre-shutdown hook finished. Thus, they were both blocked, waiting
for each other infinitely. Eventually the process would be externally
killed.
This deadlock was most likely introduced by some change in Kube's
generic api server package related to how the many complex channels used
during server shutdown interact with each other, and was not noticed
when we upgraded to the version which introduced the change.
2023-09-20 23:51:23 +00:00
controllersShutdownWaitGroup . Add ( 1 )
2021-08-29 00:38:50 +00:00
go func ( ) {
Fix deadlock during shutdown which prevented leader election cleanup
Before this fix, the deadlock would prevent the leader pod from giving
up its lease, which would make it take several minutes for new pods to
be allowed to elect a new leader. During that time, no Pinniped
controllers could write to the Kube API, so important resources were not
being updated during that window. It would also make pod shutdown take
about 1 minute.
After this fix, the leader gives up its lease immediately, and pod
shutdown takes about 1 second. This improves restart/upgrade time and
also fixes the problem where there was no leader for several minutes
after a restart/upgrade.
The deadlock was between the post-start hook and the pre-shutdown hook.
The pre-shutdown hook blocked until a certain background goroutine in
the post-start hook finished, but that goroutine could not finish until
the pre-shutdown hook finished. Thus, they were both blocked, waiting
for each other infinitely. Eventually the process would be externally
killed.
This deadlock was most likely introduced by some change in Kube's
generic api server package related to how the many complex channels used
during server shutdown interact with each other, and was not noticed
when we upgraded to the version which introduced the change.
2023-09-20 23:51:23 +00:00
// When this goroutine ends, then also end the WaitGroup, allowing anyone who called Wait() to proceed.
defer controllersShutdownWaitGroup . Done ( )
2021-08-29 00:38:50 +00:00
Fix deadlock during shutdown which prevented leader election cleanup
Before this fix, the deadlock would prevent the leader pod from giving
up its lease, which would make it take several minutes for new pods to
be allowed to elect a new leader. During that time, no Pinniped
controllers could write to the Kube API, so important resources were not
being updated during that window. It would also make pod shutdown take
about 1 minute.
After this fix, the leader gives up its lease immediately, and pod
shutdown takes about 1 second. This improves restart/upgrade time and
also fixes the problem where there was no leader for several minutes
after a restart/upgrade.
The deadlock was between the post-start hook and the pre-shutdown hook.
The pre-shutdown hook blocked until a certain background goroutine in
the post-start hook finished, but that goroutine could not finish until
the pre-shutdown hook finished. Thus, they were both blocked, waiting
for each other infinitely. Eventually the process would be externally
killed.
This deadlock was most likely introduced by some change in Kube's
generic api server package related to how the many complex channels used
during server shutdown interact with each other, and was not noticed
when we upgraded to the version which introduced the change.
2023-09-20 23:51:23 +00:00
// Start the controllers and block until their context is cancelled and they have shut down.
runControllers ( controllersCtx )
plog . Debug ( "start-controllers post start hook's background goroutine saw runControllers() finish" )
2021-08-29 00:38:50 +00:00
} ( )
return nil
} ,
)
Fix deadlock during shutdown which prevented leader election cleanup
Before this fix, the deadlock would prevent the leader pod from giving
up its lease, which would make it take several minutes for new pods to
be allowed to elect a new leader. During that time, no Pinniped
controllers could write to the Kube API, so important resources were not
being updated during that window. It would also make pod shutdown take
about 1 minute.
After this fix, the leader gives up its lease immediately, and pod
shutdown takes about 1 second. This improves restart/upgrade time and
also fixes the problem where there was no leader for several minutes
after a restart/upgrade.
The deadlock was between the post-start hook and the pre-shutdown hook.
The pre-shutdown hook blocked until a certain background goroutine in
the post-start hook finished, but that goroutine could not finish until
the pre-shutdown hook finished. Thus, they were both blocked, waiting
for each other infinitely. Eventually the process would be externally
killed.
This deadlock was most likely introduced by some change in Kube's
generic api server package related to how the many complex channels used
during server shutdown interact with each other, and was not noticed
when we upgraded to the version which introduced the change.
2023-09-20 23:51:23 +00:00
2021-08-29 00:38:50 +00:00
s . GenericAPIServer . AddPreShutdownHookOrDie ( "stop-controllers" ,
func ( ) error {
plog . Debug ( "stop-controllers pre shutdown hook starting" )
defer plog . Debug ( "stop-controllers pre shutdown hook completed" )
Fix deadlock during shutdown which prevented leader election cleanup
Before this fix, the deadlock would prevent the leader pod from giving
up its lease, which would make it take several minutes for new pods to
be allowed to elect a new leader. During that time, no Pinniped
controllers could write to the Kube API, so important resources were not
being updated during that window. It would also make pod shutdown take
about 1 minute.
After this fix, the leader gives up its lease immediately, and pod
shutdown takes about 1 second. This improves restart/upgrade time and
also fixes the problem where there was no leader for several minutes
after a restart/upgrade.
The deadlock was between the post-start hook and the pre-shutdown hook.
The pre-shutdown hook blocked until a certain background goroutine in
the post-start hook finished, but that goroutine could not finish until
the pre-shutdown hook finished. Thus, they were both blocked, waiting
for each other infinitely. Eventually the process would be externally
killed.
This deadlock was most likely introduced by some change in Kube's
generic api server package related to how the many complex channels used
during server shutdown interact with each other, and was not noticed
when we upgraded to the version which introduced the change.
2023-09-20 23:51:23 +00:00
// The generic api server is telling us that it wants to shut down, so tell our controllers that we
// want them to shut down by cancelling their context.
cancelControllerCtx ( )
// Now wait for the controllers to finish shutting down. By blocking here, we prevent the generic api server's
// graceful shutdown process from continuing until we are finished shutting down our own controllers.
controllersShutdownWaitGroup . Wait ( )
2020-07-31 16:08:07 +00:00
2020-07-23 15:05:21 +00:00
return nil
} ,
)
return s , nil
}