Skip to content

Commit a38f5e5

Browse files
committed
pkg/start: Release leader lease on graceful shutdown
So the incoming cluster-version operator doesn't need to wait for the outgoing operator's lease to expire, which can take a while [1]: I0802 10:06:01.056591 1 leaderelection.go:243] attempting to acquire leader lease openshift-cluster-version/version... ... I0802 10:07:42.632719 1 leaderelection.go:253] successfully acquired lease openshift-cluster-version/version and time out the: Cluster did not acknowledge request to upgrade in a reasonable time testcase [2]. Using ReleaseOnCancel has been the plan since 2b81f47 (cvo: Release our leader lease when we are gracefully terminated, 2019-01-16, #87). I'm not clear on why it (sometimes?) doesn't work today. The discrepancy between the "exit after 2s no matter what" comment and the 5s After dates back to dbedb7a (cvo: When the CVO restarts, perform one final sync to write status, 2019-04-27, #179), which bumped the After from 2s to 5s, but forgot to bump the comment. I'm removing that code here in favor of the two-minute timeout from b30aa0e (pkg/cvo/metrics: Graceful server shutdown, 2020-04-15, #349). We still exit immediately on a second TERM, for folks who get impatient waiting for the graceful timeout. Also set terminationGracePeriodSeconds 130 to extend the default 30s [3], to give the container the full two-minute graceful timeout window before the kubelet steps in with a KILL. Also push the Background() initialization all the way up to the command-line handler, to make it more obvious that the context is scoped to the whole 'start' invocation. [1]: https://storage.googleapis.com/origin-ci-test/pr-logs/pull/25365/pull-ci-openshift-origin-master-e2e-gcp-upgrade/1289853267223777280/artifacts/e2e-gcp-upgrade/pods/openshift-cluster-version_cluster-version-operator-5b6ff896c6-57ppb_cluster-version-operator.log [2]: https://bugzilla.redhat.com/show_bug.cgi?id=1843505#c7 [3]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#podspec-v1-core
1 parent ed864d6 commit a38f5e5

File tree

4 files changed

+39
-71
lines changed

4 files changed

+39
-71
lines changed

bootstrap/bootstrap-pod.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ spec:
3737
fieldRef:
3838
fieldPath: spec.nodeName
3939
hostNetwork: true
40+
terminationGracePeriodSeconds: 130
4041
volumes:
4142
- name: kubeconfig
4243
hostPath:

cmd/start.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package main
22

33
import (
4+
"context"
5+
46
"github.com/spf13/cobra"
57
"k8s.io/klog"
68

@@ -18,9 +20,10 @@ func init() {
1820
// To help debugging, immediately log version
1921
klog.Infof("%s", version.String)
2022

21-
if err := opts.Run(); err != nil {
23+
if err := opts.Run(context.Background()); err != nil {
2224
klog.Fatalf("error: %v", err)
2325
}
26+
klog.Info("Graceful shutdown complete.")
2427
},
2528
}
2629

install/0000_00_cluster-version-operator_03_deployment.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ spec:
5757
nodeSelector:
5858
node-role.kubernetes.io/master: ""
5959
priorityClassName: "system-cluster-critical"
60+
terminationGracePeriodSeconds: 130
6061
tolerations:
6162
- key: "node-role.kubernetes.io/master"
6263
operator: Exists

pkg/start/start.go

Lines changed: 33 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"math/rand"
1111
"os"
1212
"os/signal"
13-
"sync"
1413
"syscall"
1514
"time"
1615

@@ -97,7 +96,7 @@ func NewOptions() *Options {
9796
}
9897
}
9998

100-
func (o *Options) Run() error {
99+
func (o *Options) Run(ctx context.Context) error {
101100
if o.NodeName == "" {
102101
return fmt.Errorf("node-name is required")
103102
}
@@ -133,29 +132,6 @@ func (o *Options) Run() error {
133132
return err
134133
}
135134

136-
// TODO: Kube 1.14 will contain a ReleaseOnCancel boolean on
137-
// LeaderElectionConfig that allows us to have the lock code
138-
// release the lease when this context is cancelled. At that
139-
// time we can remove our changes to OnStartedLeading.
140-
ctx, cancel := context.WithCancel(context.Background())
141-
defer cancel()
142-
ch := make(chan os.Signal, 1)
143-
defer func() { signal.Stop(ch) }()
144-
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
145-
go func() {
146-
sig := <-ch
147-
klog.Infof("Shutting down due to %s", sig)
148-
cancel()
149-
150-
// exit after 2s no matter what
151-
select {
152-
case <-time.After(5 * time.Second):
153-
klog.Fatalf("Exiting")
154-
case <-ch:
155-
klog.Fatalf("Received shutdown signal twice, exiting")
156-
}
157-
}()
158-
159135
o.run(ctx, controllerCtx, lock)
160136
return nil
161137
}
@@ -187,6 +163,18 @@ func (o *Options) run(ctx context.Context, controllerCtx *Context, lock *resourc
187163
defer runCancel()
188164
shutdownContext, shutdownCancel := context.WithCancel(ctx)
189165
defer shutdownCancel()
166+
167+
ch := make(chan os.Signal, 1)
168+
defer func() { signal.Stop(ch) }()
169+
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
170+
go func() {
171+
sig := <-ch
172+
klog.Infof("Shutting down due to %s", sig)
173+
runCancel()
174+
sig = <-ch
175+
klog.Fatalf("Received shutdown signal twice, exiting: %s", sig)
176+
}()
177+
190178
errorChannel := make(chan error, 1)
191179
errorChannelCount := 0
192180
if o.ListenAddr != "" {
@@ -204,49 +192,26 @@ func (o *Options) run(ctx context.Context, controllerCtx *Context, lock *resourc
204192
}()
205193
}
206194

207-
exit := make(chan struct{})
208-
exitClose := sync.Once{}
209-
210-
// TODO: when we switch to graceful lock shutdown, this can be
211-
// moved back inside RunOrDie
212-
// TODO: properly wire ctx here
213-
go leaderelection.RunOrDie(context.TODO(), leaderelection.LeaderElectionConfig{
214-
Lock: lock,
215-
LeaseDuration: leaseDuration,
216-
RenewDeadline: renewDeadline,
217-
RetryPeriod: retryPeriod,
218-
Callbacks: leaderelection.LeaderCallbacks{
219-
OnStartedLeading: func(localCtx context.Context) {
220-
controllerCtx.Start(runContext)
221-
select {
222-
case <-runContext.Done():
223-
// WARNING: this is not completely safe until we have Kube 1.14 and ReleaseOnCancel
224-
// and client-go ContextCancelable, which allows us to block new API requests before
225-
// we step down. However, the CVO isn't that sensitive to races and can tolerate
226-
// brief overlap.
227-
klog.Infof("Stepping down as leader")
228-
// give the controllers some time to shut down
229-
time.Sleep(100 * time.Millisecond)
230-
// if we still hold the leader lease, clear the owner identity (other lease watchers
231-
// still have to wait for expiration) like the new ReleaseOnCancel code will do.
232-
if err := lock.Update(localCtx, resourcelock.LeaderElectionRecord{}); err == nil {
233-
// if we successfully clear the owner identity, we can safely delete the record
234-
if err := lock.Client.ConfigMaps(lock.ConfigMapMeta.Namespace).Delete(localCtx, lock.ConfigMapMeta.Name, metav1.DeleteOptions{}); err != nil {
235-
klog.Warningf("Unable to step down cleanly: %v", err)
236-
}
237-
}
238-
klog.Infof("Finished shutdown")
239-
exitClose.Do(func() { close(exit) })
240-
case <-localCtx.Done():
241-
// we will exit in OnStoppedLeading
242-
}
243-
},
244-
OnStoppedLeading: func() {
245-
klog.Warning("leaderelection lost")
246-
exitClose.Do(func() { close(exit) })
195+
errorChannelCount++
196+
go func() {
197+
leaderelection.RunOrDie(runContext, leaderelection.LeaderElectionConfig{
198+
Lock: lock,
199+
ReleaseOnCancel: true,
200+
LeaseDuration: leaseDuration,
201+
RenewDeadline: renewDeadline,
202+
RetryPeriod: retryPeriod,
203+
Callbacks: leaderelection.LeaderCallbacks{
204+
OnStartedLeading: func(ctx context.Context) {
205+
controllerCtx.Start(ctx)
206+
},
207+
OnStoppedLeading: func() {
208+
klog.Info("Stopped leading; shutting down.")
209+
runCancel()
210+
},
247211
},
248-
},
249-
})
212+
})
213+
errorChannel <- nil
214+
}()
250215

251216
for errorChannelCount > 0 {
252217
var shutdownTimer *time.Timer
@@ -270,13 +235,11 @@ func (o *Options) run(ctx context.Context, controllerCtx *Context, lock *resourc
270235
errorChannelCount--
271236
if err != nil {
272237
klog.Error(err)
273-
runCancel()
274238
}
275239
}
276240
}
277241
}
278-
279-
<-exit
242+
klog.Info("Finished collecting operator goroutines.")
280243
}
281244

282245
// createResourceLock initializes the lock.

0 commit comments

Comments
 (0)