Skip to content

Commit

Permalink
Merge pull request #163 from adrianludwin/webhook-only
Browse files Browse the repository at this point in the history
EXPERIMENTAL HA support
  • Loading branch information
k8s-ci-robot authored Mar 28, 2022
2 parents 2925068 + 4e63495 commit 55c0c2a
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 22 deletions.
42 changes: 31 additions & 11 deletions cmd/manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics"

Expand All @@ -59,7 +60,7 @@ var (
maxReconciles int
enableLeaderElection bool
leaderElectionId string
novalidation bool
noWebhooks bool
debugLogs bool
testLog bool
internalCert bool
Expand All @@ -71,6 +72,7 @@ var (
managedNamespaceLabels arrayArg
managedNamespaceAnnots arrayArg
includedNamespacesRegex string
webhooksOnly bool
)

// init preloads some global vars before main() starts. Since this is the top-level module, I'm not
Expand All @@ -93,12 +95,19 @@ func main() {
defer metricsCleanupFn()
mgr := createManager()

// Make sure certs are generated and valid if webhooks are enabled and internal certs are used.
setupLog.Info("Starting certificate generation")
certsReady, err := setup.CreateCertsIfNeeded(mgr, novalidation, internalCert, restartOnSecretRefresh)
if err != nil {
setupLog.Error(err, "unable to set up cert rotation")
os.Exit(1)
// Make sure certs are managed if requested. In webhooks-only mode, we don't run the manager, and
// rely on either a controller running in a different HNC deployment, or an external tool such as
// cert-manager.
certsReady := make(chan struct{})
if internalCert && !webhooksOnly {
setupLog.Info("Starting certificate generation")
err := setup.ManageCerts(mgr, certsReady, restartOnSecretRefresh)
if err != nil {
setupLog.Error(err, "unable to set up cert rotation")
os.Exit(1)
}
} else {
close(certsReady)
}

setupProbeEndpoints(mgr, certsReady)
Expand All @@ -125,7 +134,7 @@ func parseFlags() {
"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
flag.StringVar(&leaderElectionId, "leader-election-id", "controller-leader-election-helper",
"Leader election id determines the name of the configmap that leader election will use for holding the leader lock.")
flag.BoolVar(&novalidation, "novalidation", false, "Disables validating webhook")
flag.BoolVar(&noWebhooks, "no-webhooks", false, "Disables webhooks")
flag.BoolVar(&debugLogs, "debug-logs", false, "Shows verbose logs.")
flag.BoolVar(&testLog, "enable-test-log", false, "Enables test log.")
flag.BoolVar(&internalCert, "enable-internal-cert-management", false, "Enables internal cert management. See the user guide for more information.")
Expand All @@ -139,6 +148,7 @@ func parseFlags() {
flag.BoolVar(&restartOnSecretRefresh, "cert-restart-on-secret-refresh", false, "Kills the process when secrets are refreshed so that the pod can be restarted (secrets take up to 60s to be updated by running pods)")
flag.Var(&managedNamespaceLabels, "managed-namespace-label", "A regex indicating the labels on namespaces that are managed by HNC. These labels may only be set via the HierarchyConfiguration object. All regexes are implictly wrapped by \"^...$\". This argument can be specified multiple times. See the user guide for more information.")
flag.Var(&managedNamespaceAnnots, "managed-namespace-annotation", "A regex indicating the annotations on namespaces that are managed by HNC. These annotations may only be set via the HierarchyConfiguration object. All regexes are implictly wrapped by \"^...$\". This argument can be specified multiple times. See the user guide for more information.")
flag.BoolVar(&webhooksOnly, "webhooks-only", false, "Disables the controllers so HNC can be run in HA webhook mode")
flag.Parse()

// Assign the array args to the configuration variables after the args are parsed.
Expand All @@ -148,6 +158,12 @@ func parseFlags() {
setupLog.Error(err, "Illegal flag values")
os.Exit(1)
}

// Basic legality checks
if webhooksOnly && noWebhooks {
setupLog.Info("Cannot set both --webhooks-only and --no-webhooks")
os.Exit(1)
}
}

// enableMetrics returns a function to call from main() to export any remaining metrics when main()
Expand Down Expand Up @@ -252,6 +268,10 @@ func setupProbeEndpoints(mgr ctrl.Manager, certsReady chan struct{}) {
return errors.New("HNC internal certs are not yet ready")
}
}
// If we're not running the webhooks, no point checking to see if they're up.
if noWebhooks {
checker = healthz.Ping
}
if err := mgr.AddHealthzCheck("healthz", checker); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
Expand All @@ -278,14 +298,14 @@ func startControllers(mgr ctrl.Manager, certsReady chan struct{}) {
f := forest.NewForest()

// Create all validating and mutating admission controllers.
if !novalidation {
setupLog.Info("Registering validating webhook (won't work when running locally; use --novalidation)")
if !noWebhooks {
setupLog.Info("Registering validating webhook (won't work when running locally; use --no-webhooks)")
setup.CreateWebhooks(mgr, f)
}

// Create all reconciling controllers
setupLog.Info("Creating controllers", "maxReconciles", maxReconciles)
if err := setup.CreateReconcilers(mgr, f, maxReconciles, false); err != nil {
if err := setup.CreateReconcilers(mgr, f, maxReconciles, webhooksOnly, false); err != nil {
setupLog.Error(err, "cannot create controllers")
os.Exit(1)
}
Expand Down
19 changes: 19 additions & 0 deletions internal/anchor/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ type Reconciler struct {
// https://book-v1.book.kubebuilder.io/beyond_basics/controller_watches.html) that is used to
// enqueue additional objects that need updating.
Affected chan event.GenericEvent

// ReadOnly disables writebacks
ReadOnly bool
}

// Reconcile sets up some basic variables and then calls the business logic. It currently
Expand Down Expand Up @@ -339,6 +342,10 @@ func (r *Reconciler) getInstance(ctx context.Context, pnm, nm string) (*api.Subn
}

func (r *Reconciler) writeInstance(ctx context.Context, log logr.Logger, inst *api.SubnamespaceAnchor) error {
if r.ReadOnly {
return nil
}

if inst.CreationTimestamp.IsZero() {
if err := r.Create(ctx, inst); err != nil {
log.Error(err, "while creating on apiserver")
Expand All @@ -356,6 +363,10 @@ func (r *Reconciler) writeInstance(ctx context.Context, log logr.Logger, inst *a
// deleteInstance deletes the anchor instance. Note: Make sure there's no
// finalizers on the instance before calling this function.
func (r *Reconciler) deleteInstance(ctx context.Context, inst *api.SubnamespaceAnchor) error {
if r.ReadOnly {
return nil
}

if err := r.Delete(ctx, inst); err != nil {
return fmt.Errorf("while deleting on apiserver: %w", err)
}
Expand All @@ -378,6 +389,10 @@ func (r *Reconciler) getNamespace(ctx context.Context, nm string) (*corev1.Names
}

func (r *Reconciler) writeNamespace(ctx context.Context, log logr.Logger, nm, pnm string) error {
if r.ReadOnly {
return nil
}

inst := &corev1.Namespace{}
inst.ObjectMeta.Name = nm
metadata.SetAnnotation(inst, api.SubnamespaceOf, pnm)
Expand All @@ -395,6 +410,10 @@ func (r *Reconciler) writeNamespace(ctx context.Context, log logr.Logger, nm, pn
}

func (r *Reconciler) deleteNamespace(ctx context.Context, log logr.Logger, inst *corev1.Namespace) error {
if r.ReadOnly {
return nil
}

if err := r.Delete(ctx, inst); err != nil {
log.Error(err, "While deleting subnamespace")
return err
Expand Down
10 changes: 10 additions & 0 deletions internal/hierarchyconfig/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ type Reconciler struct {
// These are interfaces to the other reconcilers
AnchorReconciler AnchorReconcilerType
HNCConfigReconciler HNCConfigReconcilerType

ReadOnly bool
}

type AnchorReconcilerType interface {
Expand Down Expand Up @@ -704,6 +706,10 @@ func (r *Reconciler) writeHierarchy(ctx context.Context, log logr.Logger, orig,
if reflect.DeepEqual(orig, inst) {
return false, nil
}
if r.ReadOnly {
// We *wanted* to change something, so assume that we need to re-reconcile other objects as well
return true, nil
}
exists := !inst.CreationTimestamp.IsZero()
if !exists && isDeletingNS {
log.Info("Will not create hierarchyconfiguration since namespace is being deleted")
Expand Down Expand Up @@ -732,6 +738,10 @@ func (r *Reconciler) writeNamespace(ctx context.Context, log logr.Logger, orig,
if reflect.DeepEqual(orig, inst) {
return false, nil
}
if r.ReadOnly {
// We *wanted* to change something, so assume that we need to re-reconcile other objects as well
return true, nil
}

// NB: HCR can't create namespaces, that's only in anchor reconciler
stats.WriteNamespace()
Expand Down
7 changes: 7 additions & 0 deletions internal/hncconfig/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ type Reconciler struct {
// not use it.
HierarchyConfigUpdates chan event.GenericEvent

// ReadOnly disables writebacks
ReadOnly bool

// activeGVKMode contains GRs that are configured in the Spec and their mapping
// GVKs and configured modes.
activeGVKMode gr2gvkMode
Expand Down Expand Up @@ -233,6 +236,10 @@ func (r *Reconciler) validateSingleton(inst *api.HNCConfiguration) {
// We will write the singleton to apiserver even it is not changed because we assume this
// reconciler is called very infrequently and is not performance critical.
func (r *Reconciler) writeSingleton(ctx context.Context, inst *api.HNCConfiguration) error {
if r.ReadOnly {
return nil
}

if inst.CreationTimestamp.IsZero() {
// No point creating it if the CRD's being deleted
if isDeleted, err := crd.IsDeletingCRD(ctx, api.HNCConfigSingletons); isDeleted || err != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/integtest/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func HNCBeforeSuite() {
Expect(err).ToNot(HaveOccurred())

By("creating reconcilers")
err = setup.CreateReconcilers(k8sManager, forest.NewForest(), 100, true)
err = setup.CreateReconcilers(k8sManager, forest.NewForest(), 100, false, true)
Expect(err).ToNot(HaveOccurred())

By("Creating clients")
Expand Down
5 changes: 4 additions & 1 deletion internal/setup/reconcilers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
// CreateReconcilers creates all reconcilers.
//
// This function is called both from main.go as well as from the integ tests.
func CreateReconcilers(mgr ctrl.Manager, f *forest.Forest, maxReconciles int, useFakeClient bool) error {
func CreateReconcilers(mgr ctrl.Manager, f *forest.Forest, maxReconciles int, readOnly, useFakeClient bool) error {
if err := crd.Setup(mgr, useFakeClient); err != nil {
return err
}
Expand All @@ -31,6 +31,7 @@ func CreateReconcilers(mgr ctrl.Manager, f *forest.Forest, maxReconciles int, us
Log: ctrl.Log.WithName("anchor").WithName("reconcile"),
Forest: f,
Affected: anchorChan,
ReadOnly: readOnly,
}
if err := ar.SetupWithManager(mgr); err != nil {
return fmt.Errorf("cannot create anchor reconciler: %s", err.Error())
Expand All @@ -50,6 +51,7 @@ func CreateReconcilers(mgr ctrl.Manager, f *forest.Forest, maxReconciles int, us
Forest: f,
Trigger: make(chan event.GenericEvent),
HierarchyConfigUpdates: hcChan,
ReadOnly: readOnly,
}
if err := hnccfgr.SetupWithManager(mgr); err != nil {
return fmt.Errorf("cannot create Config reconciler: %s", err.Error())
Expand All @@ -63,6 +65,7 @@ func CreateReconcilers(mgr ctrl.Manager, f *forest.Forest, maxReconciles int, us
AnchorReconciler: ar,
HNCConfigReconciler: hnccfgr,
Affected: hcChan,
ReadOnly: readOnly,
}
if err := hcr.SetupWithManager(mgr, maxReconciles); err != nil {
return fmt.Errorf("cannot create Hierarchy reconciler: %s", err.Error())
Expand Down
12 changes: 3 additions & 9 deletions internal/setup/webhooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,9 @@ const (
// DNSName is <service name>.<namespace>.svc
var dnsName = fmt.Sprintf("%s.%s.svc", serviceName, secretNamespace)

// CreateCertsIfNeeded creates all certs for webhooks. This function is called from main.go.
func CreateCertsIfNeeded(mgr ctrl.Manager, novalidation, internalCert, restartOnSecretRefresh bool) (chan struct{}, error) {
setupFinished := make(chan struct{})
if novalidation || !internalCert {
close(setupFinished)
return setupFinished, nil
}

return setupFinished, cert.AddRotator(mgr, &cert.CertRotator{
// ManageCerts creates all certs for webhooks. This function is called from main.go.
func ManageCerts(mgr ctrl.Manager, setupFinished chan struct{}, restartOnSecretRefresh bool) error {
return cert.AddRotator(mgr, &cert.CertRotator{
SecretKey: types.NamespacedName{
Namespace: secretNamespace,
Name: secretName,
Expand Down

0 comments on commit 55c0c2a

Please sign in to comment.