Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

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

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)

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

Multiple Controllers Format

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:

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

After adding a named controller:

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

Result:

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.