Single group to multi-group
Kubebuilder scaffolds single-group projects by default to keep things simple, as most projects do not require multiple API groups. However, you can convert an existing single-group project to use multi-group layout when needed. This reorganizes your APIs and controllers into group-specific directories.
See the design doc for the rationale behind this design decision.
Understanding the Layouts
Here’s what changes when you go from single-group to multi-group:
Single-group layout (default):
api/<version>/*_types.go All your CRD schemas in one place
internal/controller/* All your controllers together
internal/webhook/<version>/* Webhooks organized by version (if you have any)
Multi-group layout:
api/<group>/<version>/*_types.go CRD schemas organized by group
internal/controller/<group>/* Controllers organized by group
internal/webhook/<group>/<version>/* Webhooks organized by group and version (if you have any)
You can tell which layout you are using by checking your PROJECT file for multigroup: true.
Migration steps
The following steps migrate the CronJob example from single-group to multi-group layout.
Step 1: Enable multi-group mode
First, tell Kubebuilder you want to use multi-group layout:
kubebuilder edit --multigroup=true
This command updates your PROJECT file by adding multigroup: true. After this change:
- New APIs you create will automatically use the multi-group structure (
api/<group>/<version>/) - Existing APIs remain in their current location and must be migrated manually (steps 3-9 below)
Step 2: Identify your group name
Check api/v1/groupversion_info.go to find your group name:
// +groupName=batch.tutorial.kubebuilder.io
package v1
The group name is the first part before the dot (batch in this example).
Step 3: Move your APIs
Create a directory for your group and move your version directories:
mkdir -p api/batch
mv api/v1 api/batch/
If you have multiple versions (like v1, v2, etc.), move them all:
mv api/v2 api/batch/
Step 4: Move your controllers
Create a group directory and move all controller files:
mkdir -p internal/controller/batch
mv internal/controller/*.go internal/controller/batch/
This will move all your controller files, including suite_test.go, into the group directory. Each group needs its own test suite.
Step 5: Move your webhooks (if you have any)
If your project has webhooks (check for an internal/webhook/ directory), add the group directory:
mkdir -p internal/webhook/batch
mv internal/webhook/v1 internal/webhook/batch/
mv internal/webhook/v2 internal/webhook/batch/ # if v2 exists
If you do not have webhooks, skip this step.
Step 6: Update import paths
Update all import statements to point to the new locations.
What used to look like this:
import (
batchv1 "tutorial.kubebuilder.io/project/api/v1"
"tutorial.kubebuilder.io/project/internal/controller"
)
Should now look like this:
import (
batchv1 "tutorial.kubebuilder.io/project/api/batch/v1"
batchcontroller "tutorial.kubebuilder.io/project/internal/controller/batch"
)
If you have webhooks, you’ll also need to update those imports:
// Before
webhookv1 "tutorial.kubebuilder.io/project/internal/webhook/v1"
// After
webhookbatchv1 "tutorial.kubebuilder.io/project/internal/webhook/batch/v1"
Files to check and update:
cmd/main.gointernal/controller/batch/*.gointernal/webhook/batch/v1/*.go(if you have webhooks)api/batch/v1/*_test.go
Tip: Use your IDE’s “Find and Replace” feature across the project.
Step 7: Update the PROJECT file
The kubebuilder edit --multigroup=true command sets multigroup: true in your PROJECT file but does not update paths for existing APIs. You need to manually update the path field for each resource.
Verify your PROJECT file has these changes:
- Check that
multigroup: trueis set (at the top level):
layout:
- go.kubebuilder.io/v4
multigroup: true # Must be true
projectName: project
- Update the
pathfield for each resource:
Before:
resources:
- api:
crdVersion: v1
namespaced: true
controller: true
group: batch
kind: CronJob
path: tutorial.kubebuilder.io/project/api/v1 # Old path
version: v1
After:
resources:
- api:
crdVersion: v1
namespaced: true
controller: true
group: batch
kind: CronJob
path: tutorial.kubebuilder.io/project/api/batch/v1 # New path with group
version: v1
Repeat this for all resources in your PROJECT file.
Step 8: Update test suite CRD paths
Update the CRD directory path in test suites. Since files moved one level deeper, add one more ".." to the path.
In internal/controller/batch/suite_test.go:
Before (was at internal/controller/suite_test.go):
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")},
}
After (now at internal/controller/batch/suite_test.go):
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")},
}
If you have webhooks, update internal/webhook/batch/v1/webhook_suite_test.go:
Before (was at internal/webhook/v1/webhook_suite_test.go):
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")},
}
After (now at internal/webhook/batch/v1/webhook_suite_test.go):
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "config", "crd", "bases")},
}
Step 9: Verify the migration
Run the following commands to verify everything works:
make manifests # Regenerate CRDs and RBAC
make generate # Regenerate code
make test # Run tests
make build # Build the project
AI-Assisted Migration
If you are using an AI coding assistant (Cursor, GitHub Copilot, etc.), you can automate most of the migration steps.