Skip to main content
Version: Next

Customize the scheduler

Karmada ships with a default scheduler that is described here. If the default scheduler does not suit your needs you can implement your own scheduler. Karmada's Scheduler Framework is similar to Kubernetes, but unlike K8s, Karmada needs to deploy applications to a group of clusters instead of nodes. According to the placement field of the user's scheduling policy and the internal scheduling plug-in algorithm, the user's application will be deployed to the desired cluster group.

The scheduling process can be divided into the following four steps:

  • Predicate: filter inappropriate clusters

  • Priority: score the cluster

  • SelectClusters: select cluster groups based on cluster scores and SpreadConstraint

  • ReplicaScheduling: deploy the application replicas on the selected cluster group according to the configured replica scheduling policy

    schedule process

Among them, the plug-ins for filtering and scoring can be customized and configured based on the scheduler framework.

The default scheduler has several in-tree plugins:

  • APIEnablement: a plugin that checks if the API(CRD) of the resource is installed in the target cluster.
  • TaintToleration: a plugin that checks if a propagation policy tolerates a cluster's taints.
  • ClusterAffinity: a plugin that checks if a resource selector matches the cluster label.
  • SpreadConstraint: a plugin that checks if spread property in the Cluster.Spec.
  • ClusterLocality: a score plugin that favors cluster that already have the resource.
  • ClusterEviction: a plugin that checks if the target cluster is in the GracefulEvictionTasks, which means it is in the process of eviction.

You can customize your out-of-tree plugins according to your own scenario, and implement your scheduler through Karmada's Scheduler Framework. This document will give a detailed description of how to customize a Karmada scheduler.

Before you begin

You need to have a Karmada control plane. To start up Karmada, you can refer to here. If you just want to try Karmada, we recommend building a development environment by hack/local-up-karmada.sh.

git clone https://github.com/karmada-io/karmada
cd karmada
hack/local-up-karmada.sh

Deploy a plugin

Assume you want to deploy a new filter plugin named TestFilter. You can refer to the karmada-scheduler implementation in pkg/scheduler/framework/plugins in the Karmada source directory. The code directory after development is similar to:

.
├── apienablement
├── clusteraffinity
├── clustereviction
├── clusterlocality
├── spreadconstraint
├── tainttoleration
├── testfilter
│ ├── test_filter.go

The content of the test_filter.go file is as follows, and the specific filtering logic implementation is hidden.

package testfilter

import (
"context"

clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
"github.com/karmada-io/karmada/pkg/scheduler/framework"
)

const (
// Name is the name of the plugin used in the plugin registry and configurations.
Name = "TestFilter"
)

type TestFilter struct{}

var _ framework.FilterPlugin = &TestFilter{}

// New instantiates the TestFilter plugin.
func New() (framework.Plugin, error) {
return &TestFilter{}, nil
}

// Name returns the plugin name.
func (p *TestFilter) Name() string {
return Name
}

// Filter implements the filtering logic of the TestFilter plugin.
func (p *TestFilter) Filter(ctx context.Context,
bindingSpec *workv1alpha2.ResourceBindingSpec, bindingStatus *workv1alpha2.ResourceBindingStatus, cluster *clusterv1alpha1.Cluster) *framework.Result {

// implementation

return framework.NewResult(framework.Success)
}

For a filter plugin, you must implement framework.FilterPlugin interface. And for a score plugin, you must implement framework.ScorePlugin interface.

Register the plugin

Edit the cmd/scheduler/main.go:

package main

import (
"os"

"k8s.io/component-base/cli"
_ "k8s.io/component-base/logs/json/register" // for JSON log format registration
controllerruntime "sigs.k8s.io/controller-runtime"
_ "sigs.k8s.io/controller-runtime/pkg/metrics"

"github.com/karmada-io/karmada/cmd/scheduler/app"
"github.com/karmada-io/karmada/pkg/scheduler/framework/plugins/testfilter"
)

func main() {
stopChan := controllerruntime.SetupSignalHandler().Done()
command := app.NewSchedulerCommand(stopChan, app.WithPlugin(testfilter.Name, testfilter.New))
code := cli.Run(command)
os.Exit(code)
}

To register the plugin, you need to pass in the plugin configuration in the NewSchedulerCommand function.

Package the scheduler

After you register the plugin, you need to package your scheduler binary into a container image.

cd karmada
export VERSION=## Your Image Tag
make image-karmada-scheduler
$ kubectl --kubeconfig ~/.kube/karmada.config --context karmada-host edit deploy/karmada-scheduler -nkarmada-system
...
spec:
automountServiceAccountToken: false
containers:
- command:
- /bin/karmada-scheduler
- --kubeconfig=/etc/kubeconfig
- --bind-address=0.0.0.0
- --secure-port=10351
- --enable-scheduler-estimator=true
- --v=4
image: ## Your Image Address
...

When you start the scheduler, you can find that TestFilter plugin has been enabled from the logs:

I0408 12:57:14.563522       1 scheduler.go:141] karmada-scheduler version: version.Info{GitVersion:"v1.9.0-preview5", GitCommit:"0126b90fc89d2f5509842ff8dc7e604e84288b96", GitTreeState:"clean", BuildDate:"2024-01-29T13:29:49Z", GoVersion:"go1.20.11", Compiler:"gc", Platform:"linux/amd64"}
I0408 12:57:14.564979 1 registry.go:79] Enable Scheduler plugin "ClusterAffinity"
I0408 12:57:14.564991 1 registry.go:79] Enable Scheduler plugin "SpreadConstraint"
I0408 12:57:14.564996 1 registry.go:79] Enable Scheduler plugin "ClusterLocality"
I0408 12:57:14.564999 1 registry.go:79] Enable Scheduler plugin "ClusterEviction"
I0408 12:57:14.565002 1 registry.go:79] Enable Scheduler plugin "APIEnablement"
I0408 12:57:14.565005 1 registry.go:79] Enable Scheduler plugin "TaintToleration"
I0408 12:57:14.565008 1 registry.go:79] Enable Scheduler plugin "TestFilter"

Config the plugin

You can config the plugin enablement by setting the flag --plugins. For example, the following config will disable TestFilter plugin.

$ kubectl --kubeconfig ~/.kube/karmada.config --context karmada-host edit deploy/karmada-scheduler -nkarmada-system
...
spec:
automountServiceAccountToken: false
containers:
- command:
- /bin/karmada-scheduler
- --kubeconfig=/etc/kubeconfig
- --bind-address=0.0.0.0
- --secure-port=10351
- --enable-scheduler-estimator=true
- --plugins=*,-TestFilter
- --v=4
image: ## Your Image Address
...

Configure Multiple Schedulers

Run the second scheduler in the cluster

You can run multiple schedulers simultaneously alongside the default scheduler and instruct Karmada what scheduler to use for each of your workloads. Here is an sample of the deployment config. You can save it as my-scheduler.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-karmada-scheduler
namespace: karmada-system
labels:
app: my-karmada-scheduler
spec:
replicas: 1
selector:
matchLabels:
app: my-karmada-scheduler
template:
metadata:
labels:
app: my-karmada-scheduler
spec:
automountServiceAccountToken: false
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
containers:
- name: karmada-scheduler
image: docker.io/karmada/karmada-scheduler:latest
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
path: /healthz
port: 10351
scheme: HTTP
failureThreshold: 3
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 5
command:
- /bin/karmada-scheduler
- --kubeconfig=/etc/kubeconfig
- --bind-address=0.0.0.0
- --secure-port=10351
- --enable-scheduler-estimator=true
- --leader-elect-resource-name=my-scheduler # Your custom scheduler name
- --scheduler-name=my-scheduler # Your custom scheduler name
- --v=4
volumeMounts:
- name: kubeconfig
subPath: kubeconfig
mountPath: /etc/kubeconfig
volumes:
- name: kubeconfig
secret:
secretName: kubeconfig

Note: For the --leader-elect-resource-name option, it will be karmada-scheduler by default. If you deploy another scheduler along with the default scheduler, this option should be specified and it's recommended to use the scheduler name as the value.

In order to run your scheduler in Karmada, create the deployment specified in the config above:

kubectl --context karmada-host create -f my-scheduler.yaml

Verify that the scheduler pod is running:

kubectl --context karmada-host get pods --namespace=karmada-system
NAME                                           READY     STATUS    RESTARTS   AGE
....
my-karmada-scheduler-lnf4s-4744f 1/1 Running 0 2m
...

You should see a "Running" my-karmada-scheduler pod, in addition to the default karmada-scheduler pod in this list.

Specify schedulers for deployments

Now that your second scheduler is running, create some deployments, and direct them to be scheduled by either the default scheduler or the one you deployed. In order to schedule a given deployment using a specific scheduler, specify the name of the scheduler in that propagationPolicy spec. Let's look at three examples.

  • PropagationPolicy spec without any scheduler name
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
name: nginx-propagation
spec:
resourceSelectors:
- apiVersion: apps/v1
kind: Deployment
name: nginx
placement:
clusterAffinity:
clusterNames:
- member1
- member2

When no scheduler name is supplied, the deployment is automatically scheduled using the default-scheduler.

  • PropagationPolicy spec with default-scheduler
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
name: nginx-propagation
spec:
schedulerName: default-scheduler
resourceSelectors:
- apiVersion: apps/v1
kind: Deployment
name: nginx
placement:
clusterAffinity:
clusterNames:
- member1
- member2

A scheduler is specified by supplying the scheduler name as a value to spec.schedulerName. In this case, we supply the name of the default scheduler which is default-scheduler.

  • PropagationPolicy spec with my-scheduler
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
name: nginx-propagation
spec:
schedulerName: my-scheduler
resourceSelectors:
- apiVersion: apps/v1
kind: Deployment
name: nginx
placement:
clusterAffinity:
clusterNames:
- member1
- member2

In this case, we specify that this deployment should be scheduled using the scheduler that we deployed - my-scheduler. Note that the value of spec.schedulerName should match the name supplied for the scheduler in the schedulerName field of options in the second schedulers.

Verifying that the deployments were scheduled using the desired schedulers

In order to make it easier to work through these examples, you can look at the "Scheduled" entries in the event logs to verify that the deployments were scheduled by the desired schedulers.

kubectl --context karmada-apiserver describe deploy/nginx