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

Single Group to Multi-Group

Kubebuilder scaffolds single-group projects by default to keep things simple, as most projects don’t 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’re 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 don’t 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.go
  • internal/controller/batch/*.go
  • internal/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 doesn’t update paths for existing APIs. You need to manually update the path field for each resource.

Verify your PROJECT file has these changes:

  1. Check that multigroup: true is set (at the top level):
layout:
- go.kubebuilder.io/v4
multigroup: true  # Must be true
projectName: project
  1. Update the path field 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’re using an AI coding assistant (Cursor, GitHub Copilot, etc.), you can automate most of the migration steps.