Files
Blaine Gardner 05ac850f7b core: add workaround for ctx race after admin cephx rotation
Add a workaround for https://github.com/rook/rook/issues/18112.

This is not a proper fix but is a quick fix that will prevent the race
involving operator reload and `ClusterController`'s near-global
context.Context from allowing multiple simultaneous CephCluster
reconciles of the same cluster object.

There may be other ways in which a CephCluster reconcile could enter
this race, but this workaround should reliably resolve the entry after
admin cephx key rotation.

Possible entry after this workaround is in place could involve systems
where the rook-ceph-operator pod is running on a node with high CPU load
which might prevent an ongoing CephCluster reconcile from getting CPU
cycles needed to exit following context cancellation before the sleep
added in this workaround.

Signed-off-by: Blaine Gardner <blaine.gardner@ibm.com>
2026-08-07 14:28:16 -06:00

170 lines
5.8 KiB
Go

/*
Copyright 2016 The Rook Authors. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package operator to manage Kubernetes storage.
package operator
import (
"context"
"os"
"os/signal"
"runtime"
"syscall"
"time"
"github.com/coreos/pkg/capnslog"
"github.com/pkg/errors"
"github.com/rook/rook/pkg/clusterd"
"github.com/rook/rook/pkg/operator/ceph/cluster"
opcontroller "github.com/rook/rook/pkg/operator/ceph/controller"
"github.com/rook/rook/pkg/operator/k8sutil"
v1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
var (
logger = capnslog.NewPackageLogger("github.com/rook/rook", "operator")
// ImmediateRetryResult Return this for a immediate retry of the reconciliation loop with the same request object.
ImmediateRetryResult = reconcile.Result{Requeue: true}
// ShutdownSignals signals to watch for to terminate the operator gracefully
// Using os.Interrupt is more portable across platforms instead of os.SIGINT
ShutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM}
// Placeholder for the CRD manager life cycle, first we have the context to manage cancellation
// of the manager.
// Then we have the cancel function that we can call anything to terminate the context
// Finally the channel to receive errors from the manager from within the go routine
opManagerContext context.Context
opManagerStop context.CancelFunc
mgrCRDErrorChan chan error
)
// Operator type for managing storage
type Operator struct {
context *clusterd.Context
resources []k8sutil.CustomResource
config *opcontroller.OperatorConfig
// The custom resource that is global to the kubernetes cluster.
// The cluster is global because you create multiple clusters in k8s
clusterController *cluster.ClusterController
}
// New creates an operator instance
func New(context *clusterd.Context, rookImage, serviceAccount string) *Operator {
schemes := []k8sutil.CustomResource{opcontroller.ClusterResource}
o := &Operator{
context: context,
resources: schemes,
config: &opcontroller.OperatorConfig{
OperatorNamespace: os.Getenv(k8sutil.PodNamespaceEnvVar),
Image: rookImage,
ServiceAccount: serviceAccount,
},
}
o.clusterController = cluster.NewClusterController(context, rookImage)
return o
}
// Run the operator instance
func (o *Operator) Run() error {
// Initialize signal handler and context for the operator process life cycle
// This context is used to handle the graceful termination of the operator
operatorContext, operatorStop := signal.NotifyContext(context.Background(), ShutdownSignals...)
defer operatorStop()
// Start the CRD manager
o.runCRDManager()
// Used to watch for operator's config changes
configChan := make(chan os.Signal, 1)
signal.Notify(configChan, syscall.SIGHUP)
// Main infinite loop to watch for channel events
for {
select {
case <-operatorContext.Done():
// Terminate the operator CRD manager, we cannot use "defer opManagerStop()" since
// earlier in this code the function has not been populated yet. So explicitly calling
// it here during the main context termination.
opManagerStop()
logger.Infof("shutdown signal received, exiting... %v", operatorContext.Err())
return nil
case <-configChan:
logger.Info("reloading operator's CRDs manager, cancelling all orchestrations!")
// Stop the operator CRD manager
opManagerStop()
// Short sleep to quickly work around https://github.com/rook/rook/issues/18112
time.Sleep(5 * time.Second)
// Run the operator CRD manager again
o.runCRDManager()
case err := <-mgrCRDErrorChan:
return errors.Wrapf(err, "gave up to run the operator manager")
}
}
}
func (o *Operator) runCRDManager() {
// Create the error channel so that the go routine can return an error
mgrCRDErrorChan = make(chan error)
// Create the context and the cancellation function
opManagerContext, opManagerStop = context.WithCancel(context.Background())
// Initialize the operator settings from the configmap
if err := k8sutil.ApplyOperatorSettingsConfigmap(opManagerContext, o.context.Clientset); err != nil {
panic("failed to load operator settings configmap. " + err.Error())
}
// The operator config manager is also watching for changes here so if the operator config map
// content changes for ROOK_CURRENT_NAMESPACE_ONLY we must reload the operator CRD manager
o.namespaceToWatch()
// Pass the parent context to the cluster controller so that the monitoring go routines can
// consume it to terminate gracefully
logger.Info("swapping clusterController context") // debug https://github.com/rook/rook/issues/18112
o.clusterController.OpManagerCtx = opManagerContext
// Run the operator CRD manager
go o.startCRDManager(opManagerContext, mgrCRDErrorChan)
// Run an informative go routine that prints the number of goroutines
go func() {
// Let's wait a bit to make sure most of the reconcilers are done
time.Sleep(time.Minute)
logger.Debugf("number of goroutines %d", runtime.NumGoroutine())
}()
}
func (o *Operator) namespaceToWatch() {
currentNamespaceOnly := k8sutil.GetOperatorSetting("ROOK_CURRENT_NAMESPACE_ONLY", "true")
if currentNamespaceOnly == "true" {
o.config.NamespaceToWatch = o.config.OperatorNamespace
logger.Infof("watching the current namespace %q for Ceph CRs", o.config.OperatorNamespace)
} else {
o.config.NamespaceToWatch = v1.NamespaceAll
logger.Infof("watching all namespaces for Ceph CRs")
}
}