Back to Kubebuilder

Multiple controllers per resource

docs/book/src/reference/multiple-controllers.md

4.14.04.0 KB
Original Source

Multiple controllers per resource

Kubebuilder supports multiple named controllers for the same API resource. This allows different reconciliation logic for the same resource type.

Usage

Creating the first controller

bash
kubebuilder create api --group crew --version v1 --kind Captain \
  --resource=true \
  --controller=true \
  --controller-name=captain

Creates:

  • API types in api/v1/captain_types.go
  • Controller in internal/controller/captain_controller.go with struct CaptainReconciler
  • Registration in cmd/main.go

Adding additional controllers

bash
kubebuilder create api --group crew --version v1 --kind Captain \
  --resource=false \
  --controller=true \
  --controller-name=captain-backup

Creates:

  • Controller in internal/controller/captain_backup_controller.go with struct CaptainBackupReconciler
  • Additional registration in cmd/main.go

The API is only created once. Additional controllers reference the existing API.

PROJECT file format

Legacy format (still supported)

yaml
resources:
- api:
    crdVersion: v1
  controller: true
  group: crew
  kind: Captain
  version: v1

Multiple controllers format

yaml
resources:
- api:
    crdVersion: v1
  controllers:
  - name: captain
  - name: captain-backup
  group: crew
  kind: Captain
  version: v1

When both formats exist on the same resource (due to manual editing), the controllers: array takes precedence and controller: true is automatically cleared.

Controller naming

Storage

Controller names are stored in the PROJECT file exactly as provided by the user.

Code generation

Names are normalized for Go code:

  • File name: Replace hyphens with underscores: captain-backupcaptain_backup_controller.go
  • Struct name: Convert to PascalCase and append Reconciler: captain-backupCaptainBackupReconciler
  • Runtime name: Use exact name from PROJECT: Named("captain-backup")
  • Multigroup: Prefix with group name: Named("crew-captain-backup")

Validation rules

  1. Names must be unique within a resource
  2. Names must be valid DNS labels: lowercase, alphanumeric, and hyphens only, max 63 characters
  3. Different names that normalize to the same identifier are rejected (e.g., captain-backup and captainbackup)

Controller coordination

Multiple controllers for the same resource require coordination to avoid conflicts:

  • Field ownership: Each controller should manage different fields
  • Finalizers: Use unique names: {controller-name}.example.com/finalizer
  • Status updates: Assign different status subfields to each controller
  • Conditional logic: Use labels or annotations to route resources to specific controllers

Kubebuilder scaffolds the controllers but does not manage coordination between them.

Migration from legacy format

Existing projects with controller: true continue to work unchanged. When adding named controllers to a resource with controller: true, Kubebuilder automatically migrates the format:

Before migration:

yaml
resources:
- api:
    crdVersion: v1
  controller: true
  group: crew
  kind: Captain
  version: v1

After adding a named controller:

bash
kubebuilder create api --group crew --version v1 --kind Captain \
  --resource=false \
  --controller=true \
  --controller-name=captain-backup

Result:

yaml
resources:
- api:
    crdVersion: v1
  controllers:
  - name: captain
  - name: captain-backup
  group: crew
  kind: Captain
  version: v1

The original unnamed controller is assigned the name captain (lowercase kind) and the controller: true flag is automatically cleared. This maintains backward compatibility while enabling multiple controllers.

Common errors

"duplicate controller name": Two controllers have the same name. Use unique names.

"conflicts with ... both normalize to": Different names generate the same struct name. Choose distinct names.

"controller with name ... already exists": Controller already exists for this resource. Use a different name.