Vid Bregar
Building Kubernetes Controllers for Multi-Cluster Karmada

Building Kubernetes Controllers for Multi-Cluster Karmada

Karmada is fully compatible with Kubernetes API, so if you're familiar with writing Kubernetes controllers using Kubebuilder, you're almost good to go. In this post, we'll walk through the key differences and requirements when developing controllers for a multi-cluster Karmada environment.

Example available

Controlled Replica Propagation

Installation: Split the Kustomize Configuration

When developing for Karmada, you’ll need to split the installation into two Kustomize configurations. One to be applied to the Karmada API and one to be applied to the Karmada host cluster (Kubernetes API).

By default, Kubebuilder generates everything in config/default. We’ll instead split it into two folders: config/karmada and config/host.

config/karmada/ folder prepares resources, such as: CRDs, RBAC (service account, roles and role bindings) and namespace.

config/karmada/kustomization.yaml
resources:
  - ../crd
  - ../rbac
  - namespace.yaml

config/host/ folder handles controller deployment and related configurations for the cluster where the controller actually runs.

config/host/kustomization.yaml
resources:
  - ../manager

Connecting to the Karmada API from the Host Cluster

By default, a controller uses a Kubernetes service account to access the API of its local cluster. However, when working with Karmada, the controller needs to connect to the Karmada API, not the host cluster's API.

Disable Default Service Account

Remove the service account reference from config/manager/manager.yaml and instead mount a Karmada Kubeconfig:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: controller-manager
  ...
      containers:
      - args:
          - --leader-elect
          - --health-probe-bind-address=:8081
          - --kubeconfig=/karmadaconfig/karmada.config
        ...
        volumeMounts:
          - name: karmadaconfig
            mountPath: /karmadaconfig
            readOnly: true
      volumes:
        - name: karmadaconfig
          secret:
            secretName: karmada-my-controller-config
      # serviceAccountName: controller-manager # <- remove this
  ...

Create the Karmada Kubeconfig

After applying config/karmada, a service account will exist in the Karmada API. You'll need to generate a Kubeconfig from that service account, and then create a Secret in the host cluster so your controller can authenticate.

Here’s a helper script you can place in hack/gen_config.sh:

#!/bin/bash

set -o errexit
set -o nounset
set -o pipefail

context="karmada-apiserver"
cluster_name="karmada-apiserver"
address="$(kubectl --context "$context" config view --minify -o jsonpath='{.clusters[].cluster.server}')"

namespace="controller-namespace"
service_account_name="controller-manager"

ca="$(kubectl --context "$context" get configmap cluster-info --namespace kube-public -o json | yq '.data.kubeconfig' | yq '.clusters[0].cluster.certificate-authority-data')"
# 365 days
token="$(kubectl create token --duration 31536000s --namespace "$namespace" "$service_account_name")"

echo "apiVersion: v1
kind: Config
clusters:
- name: ${cluster_name}
  cluster:
    certificate-authority-data: ${ca}
    server: ${address}
contexts:
- name: ${service_account_name}@${cluster_name}
  context:
    cluster: ${cluster_name}
    namespace: ${namespace}
    user: ${service_account_name}
current-context: ${service_account_name}@${cluster_name}
users:
- name: ${service_account_name}
  user:
    token: ${token}" > sa.karmadaconfig

# This creates a secret with the Karmada Kubeconfig
kubectl --context="karmada-host" --namespace "$namespace" create secret generic karmada-my-controller-config --from-file=karmada.config=sa.karmadaconfig

⚠️ Note: The token expires in 365 days. Automate secret rotation in production.

Using Karmada CRDs in Your Controller

If your controller interacts with Karmada-native resources like PropagationPolicy, you’ll need to:

Register Karmada CRDs to Controller Runtime

In cmd/main.go:

import (
  karmadapolicyv1alpha1 "github.com/karmada-io/api/policy/v1alpha1"
)

func init() {
  utilruntime.Must(clientgoscheme.AddToScheme(scheme))
  utilruntime.Must(karmadapolicyv1alpha1.AddToScheme(scheme))
  utilruntime.Must(mygroupv1alpha1.AddToScheme(scheme))
}

Include Karmada CRDs in Your Test Suite

Add Karmada CRDs under test/vendor_crds, e.g., karmada-io-v1alpha1.yaml.

Update internal/controller/suite_test.go:

testEnv = &envtest.Environment{
  CRDDirectoryPaths: []string{
    filepath.Join("..", "..", "config", "crd", "bases"),
    filepath.Join("..", "..", "test", "vendor_crds", "karmada-io-v1alpha1.yaml"),
  },
  ErrorIfCRDPathMissing: true,
}

Conclusion

Building a Kubernetes controller for Karmada isn't radically different, but it does have a few gotchas. By splitting your install into two parts and using a Kubeconfig to communicate with Karmada API, you can successfully run Karmada-aware controllers.


Need help with your Kubernetes project or want to upgrade your team's skills?
Let's connect


Get notified when I post