Skip to content

Commit

Permalink
Introduce staggered exit when caps change
Browse files Browse the repository at this point in the history
This patch changes the behavior of how the pod is exited when the
capabilities have changed. Instead of all the replicas exiting at
the same time, the exits are staggered. If the pod is the leader
or no leader election is configured, the pod is exited after a
brief delay. Otherwise the pod is exited immediately.

This should allow the non-leaders to come back online before the
leader is exited, enabling one of the restarted pods to take over
as the leader when it exits.
  • Loading branch information
akutz committed Feb 5, 2025
1 parent 035929e commit b957ad7
Show file tree
Hide file tree
Showing 19 changed files with 258 additions and 32 deletions.
4 changes: 3 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ linters-settings:
- alias: pkgctx
pkg: github.com/vmware-tanzu/vm-operator/pkg/context
- alias: pkgerr
pkg: github.com/vmware-tanzu/vm-operator/pkg/pkgerr
pkg: github.com/vmware-tanzu/vm-operator/pkg/errors
- alias: pkgexit
pkg: github.com/vmware-tanzu/vm-operator/pkg/exit
- alias: ctxop
pkg: github.com/vmware-tanzu/vm-operator/pkg/context/operation
- alias: pkgmgr
Expand Down
2 changes: 2 additions & 0 deletions config/local/vmoperator/local_env_var_patch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ spec:
value: "true"
- name: ASYNC_CREATE_ENABLED
value: "true"
- name: EXIT_DELAY
value: "3s"
- name: MEM_STATS_PERIOD
value: "10m"
- name: VSPHERE_NETWORKING
Expand Down
6 changes: 6 additions & 0 deletions config/wcp/vmoperator/manager_env_var_patch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
name: PRIVILEGED_USERS
value: "<COMMA_SEPARATED_LIST_OF_USERS>"

- op: add
path: /spec/template/spec/containers/0/env/-
value:
name: EXIT_DELAY
value: "3s"

- op: add
path: /spec/template/spec/containers/0/env/-
value:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/manager"

"github.com/vmware-tanzu/vm-operator/controllers/infra/capability/exit"
pkgcfg "github.com/vmware-tanzu/vm-operator/pkg/config"
"github.com/vmware-tanzu/vm-operator/pkg/config/capabilities"
pkgctx "github.com/vmware-tanzu/vm-operator/pkg/context"
pkgexit "github.com/vmware-tanzu/vm-operator/pkg/exit"
pkgmgr "github.com/vmware-tanzu/vm-operator/pkg/manager"
"github.com/vmware-tanzu/vm-operator/pkg/record"
kubeutil "github.com/vmware-tanzu/vm-operator/pkg/util/kube"
Expand Down Expand Up @@ -53,6 +53,7 @@ func AddToManager(ctx *pkgctx.ControllerManagerContext, mgr manager.Manager) err
cache,
ctrl.Log.WithName("controllers").WithName(controllerName),
record.New(mgr.GetEventRecorderFor(controllerNameLong)),
mgr.Elected(),
)

// This controller is also run on the non-leaders (webhooks) pods too
Expand Down Expand Up @@ -89,13 +90,15 @@ func NewReconciler(
ctx context.Context,
client ctrlclient.Reader,
logger logr.Logger,
recorder record.Recorder) *Reconciler {
recorder record.Recorder,
elected <-chan struct{}) *Reconciler {

return &Reconciler{
Context: ctx,
Client: client,
Logger: logger,
Recorder: recorder,
Elected: elected,
}
}

Expand All @@ -104,6 +107,7 @@ type Reconciler struct {
Client ctrlclient.Reader
Logger logr.Logger
Recorder record.Recorder
Elected <-chan struct{}
}

// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch
Expand All @@ -120,8 +124,10 @@ func (r *Reconciler) Reconcile(
}

if capabilities.UpdateCapabilitiesFeatures(ctx, obj) {
r.Logger.Info("killing pod due to changed capabilities")
exit.Exit()
pkgexit.Exit(
logr.NewContext(ctx, r.Logger),
"capabilities have changed",
r.Elected)
}

return ctrl.Result{}, nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,26 @@ package capability_test
import (
"sync/atomic"
"testing"
"time"

. "github.com/onsi/ginkgo/v2"

capability "github.com/vmware-tanzu/vm-operator/controllers/infra/capability/configmap"
"github.com/vmware-tanzu/vm-operator/controllers/infra/capability/exit"
pkgcfg "github.com/vmware-tanzu/vm-operator/pkg/config"
pkgexit "github.com/vmware-tanzu/vm-operator/pkg/exit"
"github.com/vmware-tanzu/vm-operator/pkg/manager"
"github.com/vmware-tanzu/vm-operator/test/builder"
)

var numExits int32
var (
numExits int32
exited = make(chan struct{})
)

func init() {
exit.Exit = func() {
pkgexit.ExitFn = func(_ time.Duration) {
atomic.AddInt32(&numExits, 1)
exited <- struct{}{}
}
}

Expand All @@ -30,6 +35,7 @@ var suite = builder.NewTestSuiteForControllerWithContext(
pkgcfg.NewContextWithDefaultConfig(),
func(config *pkgcfg.Config) {
config.Features.SVAsyncUpgrade = false
config.ExitDelay = time.Millisecond * 100
},
),
capability.AddToManager,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var _ = Describe(
testlabels.EnvTest,
testlabels.V1Alpha3,
),
Ordered,
func() {

var (
Expand Down Expand Up @@ -61,6 +62,12 @@ var _ = Describe(
})

When("the capabilities have changed", func() {
JustBeforeEach(func() {
Eventually(exited).Should(Receive())
})
AfterEach(func() {
Consistently(exited).ShouldNot(Receive())
})
When("some capabilities are enabled", func() {
BeforeEach(func() {
pkgcfg.SetContext(
Expand Down Expand Up @@ -127,12 +134,20 @@ var _ = Describe(
})

JustBeforeEach(func() {
Eventually(exited).Should(Receive())

Eventually(func(g Gomega) {
g.Expect(pkgcfg.FromContext(suite.Context).Features.TKGMultipleCL).To(BeTrue())
}, time.Second*5).Should(Succeed())

obj.Data[capabilities.CapabilityKeyTKGMultipleContentLibraries] = "false"
Expect(ctx.Client.Update(ctx, obj)).To(Succeed())

Eventually(exited).Should(Receive())
})

AfterEach(func() {
Consistently(exited).ShouldNot(Receive())
})

Specify("the pod was exited once on create and once on update", func() {
Expand Down Expand Up @@ -162,6 +177,9 @@ var _ = Describe(
capabilities.CapabilityKeyWorkloadIsolation: "false",
}
})
JustBeforeEach(func() {
Consistently(exited).ShouldNot(Receive())
})
Specify("the pod was not exited and features were not updated", func() {
Consistently(func(g Gomega) {
g.Expect(atomic.LoadInt32(&numExits)).To(Equal(int32(0)))
Expand Down
14 changes: 10 additions & 4 deletions controllers/infra/capability/crd/crd_capability_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ import (
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/predicate"

"github.com/vmware-tanzu/vm-operator/controllers/infra/capability/exit"
capv1 "github.com/vmware-tanzu/vm-operator/external/capabilities/api/v1alpha1"
pkgcfg "github.com/vmware-tanzu/vm-operator/pkg/config"
"github.com/vmware-tanzu/vm-operator/pkg/config/capabilities"
pkgctx "github.com/vmware-tanzu/vm-operator/pkg/context"
pkgexit "github.com/vmware-tanzu/vm-operator/pkg/exit"
"github.com/vmware-tanzu/vm-operator/pkg/record"
"github.com/vmware-tanzu/vm-operator/pkg/util/ptr"
)
Expand All @@ -39,6 +39,7 @@ func AddToManager(ctx *pkgctx.ControllerManagerContext, mgr manager.Manager) err
mgr.GetClient(),
ctrl.Log.WithName("controllers").WithName(controllerName),
record.New(mgr.GetEventRecorderFor(controllerNameLong)),
mgr.Elected(),
)

return ctrl.NewControllerManagedBy(mgr).
Expand Down Expand Up @@ -66,13 +67,15 @@ func NewReconciler(
ctx context.Context,
client ctrlclient.Client,
logger logr.Logger,
recorder record.Recorder) *Reconciler {
recorder record.Recorder,
elected <-chan struct{}) *Reconciler {

return &Reconciler{
Context: ctx,
Client: client,
Logger: logger,
Recorder: recorder,
Elected: elected,
}
}

Expand All @@ -81,6 +84,7 @@ type Reconciler struct {
Client ctrlclient.Client
Logger logr.Logger
Recorder record.Recorder
Elected <-chan struct{}
}

// +kubebuilder:rbac:groups=iaas.vmware.com,resources=capabilities,verbs=get;list;watch
Expand All @@ -98,8 +102,10 @@ func (r *Reconciler) Reconcile(
}

if capabilities.UpdateCapabilitiesFeatures(ctx, obj) {
r.Logger.Info("killing pod due to changed capabilities")
exit.Exit()
pkgexit.Exit(
logr.NewContext(ctx, r.Logger),
"capabilities have changed",
r.Elected)
}

return ctrl.Result{}, nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,26 @@ package capability_test
import (
"sync/atomic"
"testing"
"time"

. "github.com/onsi/ginkgo/v2"

capability "github.com/vmware-tanzu/vm-operator/controllers/infra/capability/crd"
"github.com/vmware-tanzu/vm-operator/controllers/infra/capability/exit"
pkgcfg "github.com/vmware-tanzu/vm-operator/pkg/config"
pkgexit "github.com/vmware-tanzu/vm-operator/pkg/exit"
"github.com/vmware-tanzu/vm-operator/pkg/manager"
"github.com/vmware-tanzu/vm-operator/test/builder"
)

var numExits int32
var (
numExits int32
exited = make(chan struct{})
)

func init() {
exit.Exit = func() {
pkgexit.ExitFn = func(_ time.Duration) {
atomic.AddInt32(&numExits, 1)
exited <- struct{}{}
}
}

Expand All @@ -30,6 +35,7 @@ var suite = builder.NewTestSuiteForControllerWithContext(
pkgcfg.NewContextWithDefaultConfig(),
func(config *pkgcfg.Config) {
config.Features.SVAsyncUpgrade = true
config.ExitDelay = time.Millisecond * 100
},
),
capability.AddToManager,
Expand Down
19 changes: 19 additions & 0 deletions controllers/infra/capability/crd/crd_capability_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var _ = Describe(
testlabels.EnvTest,
testlabels.V1Alpha3,
),
Ordered,
func() {

var (
Expand Down Expand Up @@ -64,6 +65,13 @@ var _ = Describe(
})

When("the capabilities have changed", func() {
JustBeforeEach(func() {
Eventually(exited).Should(Receive())
})
AfterEach(func() {
Consistently(exited).ShouldNot(Receive())
})

When("some capabilities are enabled", func() {
BeforeEach(func() {
pkgcfg.SetContext(
Expand Down Expand Up @@ -164,6 +172,8 @@ var _ = Describe(
})

JustBeforeEach(func() {
Eventually(exited).Should(Receive())

Eventually(func(g Gomega) {
g.Expect(pkgcfg.FromContext(suite.Context).Features.BringYourOwnEncryptionKey).To(BeTrue())
g.Expect(pkgcfg.FromContext(suite.Context).Features.TKGMultipleCL).To(BeTrue())
Expand All @@ -174,6 +184,12 @@ var _ = Describe(
Activated: false,
}
Expect(ctx.Client.Status().Update(ctx, obj)).To(Succeed())

Eventually(exited).Should(Receive())
})

AfterEach(func() {
Consistently(exited).ShouldNot(Receive())
})

Specify("the pod was exited once on create and once on update", func() {
Expand Down Expand Up @@ -214,6 +230,9 @@ var _ = Describe(
},
}
})
JustBeforeEach(func() {
Consistently(exited).ShouldNot(Receive())
})
Specify("the pod was not exited and features were not updated", func() {
Consistently(func(g Gomega) {
g.Expect(atomic.LoadInt32(&numExits)).To(Equal(int32(0)))
Expand Down
17 changes: 0 additions & 17 deletions controllers/infra/capability/exit/exit.go

This file was deleted.

7 changes: 7 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
pkgcfg "github.com/vmware-tanzu/vm-operator/pkg/config"
"github.com/vmware-tanzu/vm-operator/pkg/config/capabilities"
pkgctx "github.com/vmware-tanzu/vm-operator/pkg/context"
pkgexit "github.com/vmware-tanzu/vm-operator/pkg/exit"
pkgmgr "github.com/vmware-tanzu/vm-operator/pkg/manager"
pkgmgrinit "github.com/vmware-tanzu/vm-operator/pkg/manager/init"
"github.com/vmware-tanzu/vm-operator/pkg/mem"
Expand Down Expand Up @@ -72,6 +73,8 @@ func main() {
"buildtype", pkg.BuildType,
"commit", pkg.BuildCommit)

initExitFn()

initContext()

initFlags()
Expand Down Expand Up @@ -128,6 +131,10 @@ func initMemStats() {
metrics.Registry.MustRegister)
}

func initExitFn() {
pkgexit.ExitFn = func(_ time.Duration) { os.Exit(1) }
}

func initContext() {
ctx = pkgcfg.WithConfig(defaultConfig)
ctx = cource.WithContext(ctx)
Expand Down
8 changes: 8 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ type Config struct {
// such as passwords. Defaults to false.
LogSensitiveData bool

// ExitDelay may be set to a time.Duration value and is the maximum delay
// used when exiting the pod due to an event that requires the pod be
// restarted, ex. the capabilities have changed. The exit will be delayed
// by some duration between 0-ExitDelay.
//
// Defaults to 3s.
ExitDelay time.Duration

// AsyncSignalEnabled may be set to false to disable the vm-watcher service
// used to reconcile VirtualMachine objects if their backend state has
// changed.
Expand Down
Loading

0 comments on commit b957ad7

Please sign in to comment.