From c190191fb52c8f92f2fb03c67f62949591a6d119 Mon Sep 17 00:00:00 2001 From: Ruhi Prasad Date: Tue, 10 Jun 2025 05:55:14 +0000 Subject: [PATCH 1/3] [KEP-5254] DRA: Constraints with CEL --- keps/prod-readiness/sig-scheduling/5254.yaml | 3 + .../5254-dra-constraints-with-cel/README.md | 1008 +++++++++++++++++ .../cel-expression-recursion-tree.png | Bin 0 -> 14464 bytes .../components.png | Bin 0 -> 92625 bytes .../5254-dra-constraints-with-cel/kep.yaml | 45 + 5 files changed, 1056 insertions(+) create mode 100644 keps/prod-readiness/sig-scheduling/5254.yaml create mode 100644 keps/sig-scheduling/5254-dra-constraints-with-cel/README.md create mode 100644 keps/sig-scheduling/5254-dra-constraints-with-cel/cel-expression-recursion-tree.png create mode 100644 keps/sig-scheduling/5254-dra-constraints-with-cel/components.png create mode 100644 keps/sig-scheduling/5254-dra-constraints-with-cel/kep.yaml diff --git a/keps/prod-readiness/sig-scheduling/5254.yaml b/keps/prod-readiness/sig-scheduling/5254.yaml new file mode 100644 index 00000000000..3768a7d3a9a --- /dev/null +++ b/keps/prod-readiness/sig-scheduling/5254.yaml @@ -0,0 +1,3 @@ +kep-number: 5254 +alpha: + approver: "@deads2k" diff --git a/keps/sig-scheduling/5254-dra-constraints-with-cel/README.md b/keps/sig-scheduling/5254-dra-constraints-with-cel/README.md new file mode 100644 index 00000000000..87c752ed2f9 --- /dev/null +++ b/keps/sig-scheduling/5254-dra-constraints-with-cel/README.md @@ -0,0 +1,1008 @@ + +# KEP-5254: DRA: Constraints with CEL + + +- [Release Signoff Checklist](#release-signoff-checklist) +- [Summary](#summary) +- [Motivation](#motivation) + - [Background](#background) + - [Goals](#goals) + - [Non-Goals](#non-goals) +- [Proposal](#proposal) + - [User Stories](#user-stories) + - [Allocating ‘connected’ devices](#allocating-connected-devices) + - [Allocating a slice of connected devices of desired shape](#allocating-a-slice-of-connected-devices-of-desired-shape) + - [Risks and Mitigations](#risks-and-mitigations) + - [Runaway expressions](#runaway-expressions) + - [Performance at scale](#performance-at-scale) +- [Design Details](#design-details) + - [Components diagram](#components-diagram) + - [kube-apiserver Updates](#kube-apiserver-updates) + - [kube-scheduler Updates](#kube-scheduler-updates) + - [Non-monotonic constraint evaluation](#non-monotonic-constraint-evaluation) + - [Existing constraint interface](#existing-constraint-interface) + - [New add method for celDeviceConstraint](#new-add-method-for-celdeviceconstraint) + - [New remove method for celDeviceConstraint](#new-remove-method-for-celdeviceconstraint) + - [Allocation Example](#allocation-example) +- [Test Plan](#test-plan) + - [Prerequisite testing updates](#prerequisite-testing-updates) + - [Unit tests](#unit-tests) + - [Integration tests](#integration-tests) + - [e2e tests](#e2e-tests) +- [Graduation Criteria](#graduation-criteria) + - [Alpha](#alpha) +- [Production Readiness Review Questionnaire](#production-readiness-review-questionnaire) + - [Feature Enablement and Rollback](#feature-enablement-and-rollback) + - [Rollout, Upgrade and Rollback Planning](#rollout-upgrade-and-rollback-planning) + - [Monitoring Requirements](#monitoring-requirements) + - [Dependencies](#dependencies) + - [DRA driver](#dra-driver) + - [CEL support](#cel-support) + - [Scalability](#scalability) + - [Troubleshooting](#troubleshooting) +- [Implementation History](#implementation-history) +- [Infrastructure Needed (Optional)](#infrastructure-needed-optional) + + +## Release Signoff Checklist + + + +Items marked with (R) are required *prior to targeting to a milestone / release*. + +- [] (R) Enhancement issue in release milestone, which links to KEP dir in [kubernetes/enhancements] (not the initial KEP PR) +- [] (R) KEP approvers have approved the KEP status as `implementable` +- [x] (R) Design details are appropriately documented +- [x] (R) Test plan is in place, giving consideration to SIG Architecture and SIG Testing input (including test refactors) + - [ ] e2e Tests for all Beta API Operations (endpoints) + - [ ] (R) Ensure GA e2e tests meet requirements for [Conformance Tests](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/conformance-tests.md) + - [ ] (R) Minimum Two Week Window for GA e2e tests to prove flake free +- [ ] (R) Graduation criteria is in place + - [ ] (R) [all GA Endpoints](https://github.com/kubernetes/community/pull/1806) must be hit by [Conformance Tests](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/conformance-tests.md) +- [x] (R) Production readiness review completed +- [] (R) Production readiness review approved +- [] "Implementation History" section is up-to-date for milestone +- [ ] User-facing documentation has been created in [kubernetes/website], for publication to [kubernetes.io] +- [ ] Supporting documentation—e.g., additional design documents, links to mailing list discussions/SIG meetings, relevant PRs/issues, release notes + + + +[kubernetes.io]: https://kubernetes.io/ +[kubernetes/enhancements]: https://git.k8s.io/enhancements +[kubernetes/kubernetes]: https://git.k8s.io/kubernetes +[kubernetes/website]: https://git.k8s.io/website + +## Summary + +This KEP defines an extension of the DRA structured parameters KEP #4381 (4381-dra-structured-parameters). + +The original KEP introduced a new API to specify a structured approach for describing resources a pod needs. Typically, such resources are devices like a GPU or other accelerators. The API supports: + +* Network-attached devices. The existing device plugin API is limited to hardware on a node. +* Sharing of allocated devices between multiple containers or pods. The device plugin API currently cannot share devices at all. It could be extended to share devices between containers in a single pod, but supporting sharing between pods would need a completely new API similar to the one in this KEP. +* Using a device that is expensive to initialize multiple times in different pods. This is not possible at the moment. +* Custom parameters that describe device configuration. With the current Pod API, annotations have to be used to capture such parameters and then hacks are needed to access them from a CSI driver or device plugin. + +The original KEP added support for MatchAttribute constraint, requiring all devices in question to have a specific attribute and that its type and value are the same across those devices. This KEP introduces a new CEL (Common Expression Language) based constraint for device selection. + +As hardware architectures become more complex, workloads need to ensure optimal device allocation based on any topology and connectivity. This KEP defines a new constraint type in the DRA API for describing how devices should be selected based on their relationships with each other. + +This feature is governed by a feature gate DRACELDeviceConstraint in kube-apiserver, kube-controller-manager and kube-scheduler, defaulting to false. + +## Motivation + +### Background + +DRA supports specifying constraints for device allocation. Currently, it supports the MatchAttribute constraint, which ensures that all selected devices have a specific attribute and the type and value of this attribute are consistent across these devices. This enhancement proposal aims to add a new constraint type that will allow a group of devices to be evaluated against a CEL expression. + +This feature was previously identified as a potential future extension in the original KEP for structured parameters, and this proposal seeks to implement that extension. [Reference](https://github.com/kubernetes/enhancements/blame/b00718dfdb496a2e4180d22312be339f3b1107b0/keps/sig-node/4381-dra-structured-parameters/README.md#L1521) + + +### Goals + +* New API - Facilitate API to let end users specify a CEL expression to select an optimal device set. + +* Compatibility - Be compatible with rest of the user experience of DRA - rest of the spec for specifying the ResourceClaim should stay as is. + +* Co-exist with MatchAttribute - this new constraint will be defined alongside MatchAttribute constraint. Users can specify zero, one, or both these constraints in their ResourceClaim. Constraints will be evaluated in the order in which they are specified in the ResourceClaim. + +* Support any hardware topology - the CEL expression can be made flexible to any topology, e.g. ring, grid, etc. + +### Non-Goals + +* This KEP will overtime benefit from CEL enhancements / usability improvements. Currently it will utilize the CEL version already available in the code base and does not aim to bring in any CEL updates. + +* Monotonic evaluation of devices is a non goal. We do not want to assume any order in the way the devices are added to the set being evaluated. We cannot conclude on the evaluation result until the full set is available. + +## Proposal + +### User Stories + +#### Allocating ‘connected’ devices + +As a user, I want to allocate my workloads onto mla (Machine Learning Accelerator) devices, but only require a subset of devices. These devices should be connected to each other. Running my workload on just 4 devices is sufficient. I know that the administrator has created an "mla.example.com" DeviceClass and set up the hardware. The vendor for the device has given me a reference ResourceClaimTemplate to select "connected" set of devices. The hardware uses ring topology where two connected devices / neighbors - have contiguous device ids. + +I can create the following Pod and ResourceClaimTemplate: + +``` +apiVersion: resource.k8s.io/v1beta1 +kind: ResourceClaimTemplate +metadata: + namespace: default + name: 4-connected-devices +spec: + spec: + devices: + requests: + - name: mla-request + deviceClassName: mla.example.com + allocationMode: ExactCount + count: 4 + constraints: + - requests: ["mla-request"] + cel: + expression: "size(devices) == 4 && \ + devices.map(d, d.attributes['mla.example.com'].deviceid).max() - \ + devices.map(d, d.attributes['mla.example.com'].deviceid).min() == 3" +--- +apiVersion: v1 +kind: Pod +metadata: + namespace: default + name: pod0 +spec: + containers: + - name: ctr0 + ... + resources: + claims: + - name: mla + resourceClaims: + - name: mla + resourceClaimTemplateName: 4-connected-devices +``` +I know that this will create a ResourceClaim named 4-connected-devices in the default namespace which will give me any 4 contiguous devices. Example output: + +``` +apiVersion: resource.k8s.io/v1beta1 +kind: ResourceClaim +metadata: + ... + name: pod0-mla-vvhh9 + namespace: default +spec: + devices: + constraints: + - cel: + expression: size(devices) == 4 && devices.map(d, d.attributes['mla.example.com'].index).max() - devices.map(d, d.attributes['mla.example.com'].index).min() == 3 + requests: + - mla-request + requests: + - allocationMode: ExactCount + count: 4 + deviceClassName: mla.example.com + name: mla-request + status: + allocation: + devices: + results: + - device: mla-0 + driver: mla.example.com + pool: dra-example-driver-cluster-worker + request: mla-request + - device: mla-1 + driver: mla.example.com + pool: dra-example-driver-cluster-worker + request: mla-request + - device: mla-2 + driver: mla.example.com + pool: dra-example-driver-cluster-worker + request: mla-request + - device: mla-3 + driver: mla.example.com + pool: dra-example-driver-cluster-worker + request: mla-request + ... +``` +The pod will be allocated on the 4 connected devices - mla0, mla1, mla2, and mla3, for example. + +#### Allocating a slice of connected devices of desired shape + +As a user, I want to allocate my workloads onto mlaExample devices, but only require a subset of devices in a specific shape. These devices should be “connected”. I know that the device topology allows creating a 2x2 connected grid. Running my workload on just 4 devices is sufficient. The administrator has created an "mla.example.com" DeviceClass and set up the hardware, surfacing attributes like "row" and "col". The vendor for the device has given me a reference ResourceClaimTemplate to select "connected" set of devices of desired shape. + +I can create the following Pod and ResourceClaimTemplate: + +``` +apiVersion: resource.k8s.io/v1beta1 +kind: ResourceClaimTemplate +metadata: + namespace: default + name: 2x2-device-grid +spec: + spec: + devices: + requests: + - name: mla-request + deviceClassName: mla.example.com + allocationMode: ExactCount + count: 4 + constraints: + - requests: ["mla-request"] + cel: + expression: "size(devices) == 4 && + devices.all(d, + devices.filter(other, + d != other && + ((d.attributes["mla.example.com"].row == other.attributes["mla.example.com"].row && + (d.attributes["mla.example.com"].col == other.attributes["mla.example.com"].col + 1 || + d.attributes["mla.example.com"].col == other.attributes["mla.example.com"].col - 1)) || + (d.attributes["mla.example.com"].col == other.attributes["mla.example.com"].col && + (d.attributes["mla.example.com"].row == other.attributes["mla.example.com"].row + 1 || + d.attributes["mla.example.com"].row == other.attributes["mla.example.com"].row - 1)) + ) + ).size() >= 2 + )" +--- +apiVersion: v1 +kind: Pod +metadata: + namespace: default + name: pod0 +spec: + containers: + - name: ctr0 + ... + resources: + claims: + - name: mla + resourceClaims: + - name: mla + resourceClaimTemplateName: 2x2-device-grid +``` +I know that this will create a ResourceClaim named 2x2-device-grid in the default namespace, which will have the following output: + +``` +apiVersion: resource.k8s.io/v1beta1 +kind: ResourceClaim +metadata: + ... + name: pod0-mla-hlw4p + namespace: default +spec: + devices: + constraints: + - cel: + expression: "size(devices) == 4 && + devices.all(d, + devices.filter(other, + d != other && + ((d.attributes["%[1]s"].row == other.attributes["%[1]s"].row && + (d.attributes["%[1]s"].col == other.attributes["%[1]s"].col + 1 || + d.attributes["%[1]s"].col == other.attributes["%[1]s"].col - 1)) || + (d.attributes["%[1]s"].col == other.attributes["%[1]s"].col && + (d.attributes["%[1]s"].row == other.attributes["%[1]s"].row + 1 || + d.attributes["%[1]s"].row == other.attributes["%[1]s"].row - 1)) + ) + ).size() >= 2 + )" + requests: + - mla-request + requests: + - allocationMode: ExactCount + count: 4 + deviceClassName: mla.example.com + name: mla-request +status: + allocation: + devices: + results: + - device: mla-0 + driver: mla.example.com + pool: dra-example-driver-cluster-worker + request: mla-request + - device: mla-1 + driver: mla.example.com + pool: dra-example-driver-cluster-worker + request: mla-request + - device: mla-5 + driver: mla.example.com + pool: dra-example-driver-cluster-worker + request: mla-request + - device: mla-4 + driver: mla.example.com + pool: dra-example-driver-cluster-worker + request: mla-request + ... +``` + +The pod will be allocated on the 4 connected devices - mla0, mla1, mla4, and mla5, for example which are connected in the topology of my hardware. + +### Risks and Mitigations + +#### Runaway expressions + +A malicious or buggy workload can specify CEL expressions that degrade the performance of constraint evaluation and scheduling. We will specify a limit on evaluation cost for the expression. There is already a mechanism to cap this today with CEL selectors that we can reuse. [Reference](https://github.com/kubernetes/kubernetes/blob/6188e5cb7b2f106b047493b7b498c1882723cab4/pkg/apis/resource/types.go#L910-L933) + +#### Performance at scale + +The feature depends on exhaustive search for devices. The worst-case performance impact of calculating all combinations is comparable to that of the pathological test case for existing MatchAttribute constraint. Scalability testing is needed to validate the performance of allocation. + +Additionally, with the introduction of [workload-aware scheduling](https://github.com/kubernetes/kubernetes/issues/132192), the performance might take a hit as filtering can be executed multiple times. + +As a partial mitigation for the performance impact, the scheduler plugin supports a configurable timeout that can be applied for a node. [Reference](https://github.com/kubernetes/enhancements/blob/5b1270421f5bc3315fe191c29e0356bc91cbfe6b/keps/sig-node/4381-dra-structured-parameters/README.md?plain=1#L2025-L2032) + +We will need to revisit this solution before beta release. + +## Design Details + +### Components diagram + +![components](./components.png) + +The following components must be implemented / modified in Kubernetes: + +1. kube-apiserver: DeviceConstraint API must be extended to include the new CEL constraint field. Also DeviceConstraint validation logic must be extended. [Reference](https://github.com/kubernetes/kubernetes/blob/6188e5cb7b2f106b047493b7b498c1882723cab4/pkg/apis/resource/validation/validation.go#L317) + +2. kube-scheduler: Filter step of the scheduling framework must be modified to evaluate the new CEL constraint. This is done by updating logic for current Allocate workflow. + +These changes will impact three core Kubernetes components: kube-apiserver for the API extensions and validation, kube-scheduler for the constraint evaluation logic, and kube-controller-manager for handling ResourceClaimTemplates that use the new constraint type. + +### kube-apiserver Updates + +The exact set of proposed API changes can be seen below (`...` is used in places where new fields +are added to existing types): +```go +// DeviceConstraint must have exactly one field set besides Requests. +type DeviceConstraint struct { + ... + + // A CEL expression which compares different devices and returns + // true if they match. + // + // +optional + // +oneOf=ConstraintType + // +featureGate=DRACELDeviceConstraint + CEL *CELDeviceConstraint `json:"cel,omitempty" protobuf:"bytes,3,opt,name=cel"` +} +type CELDeviceConstraint struct { + // Expression is a CEL expression which evaluates a group of devices. It + // must evaluate to true when the devices under consideration satisfy + // the desired criteria, and false when they do not. Any other result + // is an error and causes allocation of devices to abort. + // + // The expression's input is an object named "devices", which is a set of + // "device" objects. The "device" object carries the following properties: + // - driver (string): the name of the driver which defines this device. + // - attributes (map[string]object): the device's attributes, grouped by prefix + // (e.g. device.attributes["dra.example.com"] evaluates to an object with all + // of the attributes which were prefixed by "dra.example.com". + // - capacity (map[string]object): the device's capacities, grouped by prefix. + // + // +required + Expression string `json:"expression" protobuf:"bytes,1,name=expression"` +} +``` + +### kube-scheduler Updates + +The kube-scheduler is responsible for selecting devices to allocate based on this new CELDeviceConstraint. The scheduler already supports selecting devices according to MatchAttribute - this KEP will extend that to also support CELDeviceConstraint. + +#### Non-monotonic constraint evaluation + +Unlike matchAttribute constraint, cel constraint evaluation for devices is not monotonic since the expression needs to be evaluated against full set of devices. + +For example, if using monotonic evaluation: + +``` +Given a CEL constraint to find 4 contiguous devices. + +1. device_0 is added to the device set. + Constraint is evaluated - no constraints are violated. + +2. device_2 is added to the device set. + Constraint is evaluated - constraint is violated because 0 and 2 are not contiguous. Aborts +``` + +In the above example, `device_1` could have been added to the above device set, and the contiguous device constraint would have been satisfied. Since we cannot assume sorted order of the devices, we must ensure devices are evaluated after correct number of devices are added to the device set. + +#### Existing constraint interface + +The new constraint needs to implement the below interface that exists in the Allocate workflow: + +```go +type constraint interface { + // add is called whenever a device is about to be allocated. It must + // check whether the device matches the constraint and if yes, + // track that it is allocated. + add(requestName, subRequestName string, device *draapi.BasicDevice, deviceID DeviceID) bool + + // For every successful add there is exactly one matching removed call + // with the exact same parameters. + remove(requestName, subRequestName string, device *draapi.BasicDevice, deviceID DeviceID) +} +``` +We define celDeviceConstraint which will satisfy the above interface. Internally, it will keep state about devices being evaluated together. + +```go +// celDeviceConstraint evaluates the selected set of devices against the given cel expression. +// The evaluation needs to be done once all devices have been added to the set. +type celDeviceConstraint struct { + logger klog.Logger + requestNames sets.Set[string] + expression string + celCache *cel.Cache + + devices []*draapi.BasicDevice + numDevices int +} +``` + +#### New add method for celDeviceConstraint + +Devices are added to a device set, as long as the number of devices specified in the spec is not met (set in DeviceRequest). Once the number of devices is met, the constraint is evaluated. + +```go +// Only evaluate when we have all devices +m.logger.V(7).Info("devices added so far ", "current", len(m.devices), "expected", m.numDevices) +if m.numDevices > len(m.devices) { + // We haven't collected all devices yet + m.logger.V(7).Info("Collecting devices", "current", len(m.devices), "expected", m.numDevices) + return true +} + +var deviceList []cel.Device +for _, dev := range m.devices { + var converted resourceapi.BasicDevice + if err := draapi.Convert_api_BasicDevice_To_v1beta1_BasicDevice(dev, &converted, nil); err != nil { + m.logger.Error(err, "Failed to convert device for CEL evaluation") + return false + } + deviceList = append(deviceList, cel.Device{ + Driver: deviceID.Driver.String(), + Attributes: converted.Attributes, + Capacity: converted.Capacity, + }) +} +``` + +If the constraint evaluation fails, we remove the last device that was added from the set of devices. This ensures that new combinations of devices can be evaluated. + +```go +m.logger.V(7).Info("Evaluating expression %s", m.expression) +matches, details, err := expr.DevicesMatch(context.Background(), deviceList) // DevicesMatch handles CEL expression evaluation +if err != nil { + m.logger.Error(err, "Expression evaluation failed") + // remove the last device we added. For others, there will be a remove call + m.devices = m.devices[:len(m.devices)-1] + return false +} + +if !matches { + m.logger.V(7).Info("Devices don't satisfy expression", + "actualCost", ptr.Deref(details.ActualCost(), 0)) + // remove the last device we added. For others, there will be a remove call + m.devices = m.devices[:len(m.devices)-1] + return false +} +``` + +#### New remove method for celDeviceConstraint + +This remove() function is called when devices in the set do not produce an evaluation result of true. Remove is called exactly once for each successfully added device. + +When evaluating the constraint, we evaluate against the whole device set. If the constraint is not met, remove gets called for every successfully added device. In remove method, we remove that specific device from internal state of the evaluator. + +```go +if m.requestNames.Len() > 0 && !m.matches(requestName, subRequestName) { + return +} + +// Find and remove only the specific device from the slice +for i := 0; i < len(m.devices); i++ { + if m.devices[i] == device { // Compare the actual device pointers + // Remove this specific device by shifting remaining elements + copy(m.devices[i:], m.devices[i+1:]) + // Shrink slice by one + m.devices = m.devices[:len(m.devices)-1] + break + } +} + +m.logger.V(7).Info("Reset constraint state for re-evaluation") +``` + +### Allocation Example + +Consider a node with 8 devices. We will use a CEL expression constraint to find 4 contiguous devices. The add / remove would work like this: (Note: devices can be added to the device set in any order) + +![recursion-tree](./cel-expression-recursion-tree.png) + +Step by Step: + +1. device2 is added to the device set using add() function. Device set is [device2] + 1. Expression not evaluated because # of devices in set == 1, which doesn’t match expected 4. +2. device3 is added. Device set is [device2, device3] + 1. Expression not evaluated +3. device7 is added. Device set is [device2, device3, device7] + 1. Expression not evaluated +4. device0 is added. Device set is [device2, device3, device7, device0] + 1. Requested devices == # of devices in set, expression is evaluated. + 2. Expression returns false. device0 is deleted from device set. Device set is [device2, device3, device7] +5. Step 4 is repeated for the remaining devices (device1, device4, device5, device6) +6. All devices have been added to device set but expressions returned false (from step 5). Rollback occurs and remove() is called on device7. Device set is [device2, device3] +7. device5 is added. Device set is [device2, device3, device5] + 1. Expression not evaluated. +8. device4 is added. Device set is [device2, device3, device5, device4] + 1. Requested devices == # of devices in set, expression is evaluated. + 2. Expression returns true. Valid allocation of 4 contiguous devices has been found. + +CELDeviceConstraint algorithm depends on the exhaustive search done by allocator today to run through all combinations of the devices. + +## Test Plan + + + +[x] I/we understand the owners of the involved components may require updates to +existing tests to make this code solid enough prior to committing the changes necessary +to implement this enhancement. + +### Prerequisite testing updates + +None + +### Unit tests + + + +Unit tests will be added for the `allocator.go` file (scheduler unit tests), and `validation_resourceclaim_test.go` file (API unit tests). We will validate that the existing coverage percentage does not drop due to addition of this feature. + +Rough coverage numbers after running current POC code (WIP) are listed below. + +- `k8s.io/dynamic-resource-allocation/cel`: 72.3% +- `k8s.io/dynamic-resource-allocation/structured`: 90.2% +- `k8s.io/kubernetes/pkg/controller/resourceclaim`: 74.2% +- `k8s.io/kubernetes/pkg/kubelet/cm/dra`: 78.4% +- `k8s.io/kubernetes/pkg/kubelet/cm/dra/plugin`: 90.3% +- `k8s.io/kubernetes/pkg/kubelet/cm/dra/state`: 46.2% +- `k8s.io/kubernetes/pkg/scheduler/framework/plugins/dynamicresources`: 79.3% + +### Integration tests + + + +The scheduler plugin and resource claim controller are covered by the workloads in [test/integration/scheduler_perf/dra/performance-config.yaml](https://github.com/kubernetes/kubernetes/blob/6188e5cb7b2f106b047493b7b498c1882723cab4/test/integration/scheduler_perf/dra/performance-config.yaml). This already supports the MatchAttribute constraint. Here, we can add integration tests for CELDeviceConstraint expressions. These tests will need to examine large scale resource slices for scheduler performance. + +### e2e tests + + + +End-to-end testing depends on a working DRA driver and a container runtime with CDI support. + +e2e testing for DRA exists in [test/e2e/dra/dra.go](https://github.com/kubernetes/kubernetes/blob/6188e5cb7b2f106b047493b7b498c1882723cab4/test/e2e/dra/dra.go), which supports testing MatchAttribute constraint. We will update the existing DRA driver to add similar tests for CELDeviceConstraint expressions. + + +## Graduation Criteria + +### Alpha + +- Feature implemented behind a feature flag +- Initial e2e tests completed and enabled + +## Production Readiness Review Questionnaire + + + +### Feature Enablement and Rollback + +###### How can this feature be enabled / disabled in a live cluster? + +- [x] Feature gate (also fill in values in `kep.yaml`) + - Feature gate name: DRACELDeviceConstraint + - Components depending on the feature gate: + - kube-apiserver + - kube-scheduler + - kube-controller-manager + +Feature gates in kube-apiserver, kube-scheduler, and kube-controller-manager must all be enabled for the feature to work. + +###### Does enabling the feature change any default behavior? + +No + +###### Can the feature be disabled once it has been enabled (i.e. can we roll back the enablement)? + +Yes, through the feature gate. Pods that are already running will continue unaffected. New workloads trying to utilize the feature will not work. If any of the feature gates in kube-apiserver, kube-scheduler, and kube-controller-manager are disabled, the feature will not work. The pod will either not be created or will be in a pending state with the corresponding error message. + +###### What happens if we reenable the feature if it was previously rolled back? + +Any new incoming workloads will be able to leverage the feature. There will be no impact on running workloads. + +###### Are there any tests for feature enablement/disablement? + +Tests for apiserver will cover disabling the feature. This primarily matters for the extended ResourceClaimSpec: the new fields must be preserved during updates even when the feature is disabled. + +### Rollout, Upgrade and Rollback Planning + + + +###### How can a rollout or rollback fail? Can it impact already running workloads? + + + +Workloads not using ResourceClaims should not be impacted. +Workloads not using the feature should not be impacted. +Already running pods should not be impacted. + +When a rollout fails, kube-controller-manager fails to create ResourceClaims from ResourceClaimTemplates for workloads using this feature. Those Pods will not get scheduled. Bugs in kube-scheduler might lead to not scheduling Pods that could run or worse, schedule Pods that should not run. + +###### What specific metrics should inform a rollback? + + + +After enabling the feature, if the `scheduler_pending_pods` metric in the kube-scheduler suddenly increases, then perhaps scheduling no longer works as intended. + +Additionally, if no new pods are created in the cluster, but ` scheduler_pending_pods` metric remains constant, this will indicate that no pods have been scheduled. + +###### Were upgrade and rollback tested? Was the upgrade->downgrade->upgrade path tested? + + + +This will be done manually before transition to beta by bringing up a KinD cluster with kubeadm and changing the feature gate for individual components. +Validation of API types is covered by unit tests. + +###### Is the rollout accompanied by any deprecations and/or removals of features, APIs, fields of API types, flags, etc.? + + + +No + +### Monitoring Requirements + + + +###### How can an operator determine if the feature is in use by workloads? + + + +There will be ResourceClaims with non-empty ResourceClaimSpec.DeviceClaim.CELDeviceConstraint field + +We can emit metrics for CEL constraint and match attribute constraint to track feature usage. + +###### How can someone using this feature know that it is working for their instance? + + + +This feature will be working for their instance if their pod is in a running state, and if the resource claims created reference the cel constraint. + +###### What are the reasonable SLOs (Service Level Objectives) for the enhancement? + + + +###### What are the SLIs (Service Level Indicators) an operator can use to determine the health of the service? + + + +- [x] Metrics + - Metric name: `plugin_execution_duration_seconds` + - Metric name: `plugin_evaluation_total` + - Components exposing the metric: kube-scheduler +- [ ] Other (treat as last resort) + - Details: + +###### Are there any missing metrics that would be useful to have to improve observability of this feature? + + + +We can add metrics to track when the new CEL device constraint or matchAttribute are used in ResourceClaims to surface information on usage patterns. + +``` +Metric name: resourceclaim_controller_with_cel_constraint{claim_name, template_name} +Metric name: resourceclaim_controller_with_match_attribute_constraint{claim_name, template_name} +Metric name: resourceclaim_controller_with_cel_constraint_latency{claim_name, template_name} +Metric name: cel_expression_constraint_cost +``` + +Importantly, `cel_expression_constraint_cost` can track actual cost of evaluating DRA constraints with CEL expressions using the [ActualCost()](https://pkg.go.dev/github.com/google/cel-go/cel#EvalDetails.ActualCost) function from CEL. + +### Dependencies + + + +#### DRA driver + +As also required by the original KEP, third-party DRA drivers must be installed in the cluster, in order to surface resource information. This will be provided by hardware vendors. + +#### CEL support + +Currently, this KEP depends on CEL version cel-go v0.23.2 for specifying the device constraint. + + +###### Does this feature depend on any specific services running in the cluster? + +Third-party DRA driver + + + +### Scalability + + + +###### Will enabling / using this feature result in any new API calls? + +This feature maintains the same API call patterns as existing DRA with structured parameters. + +###### Will enabling / using this feature result in introducing new API types? + +No. It will modify the existing ResourceClaimTemplate API. + +###### Will enabling / using this feature result in non-negligible increase of resource usage (CPU, RAM, disk, IO, ...) in any components? + +Resource consumption will scale with the number of device combinations being evaluated, varying based on specific allocation patterns. + +###### Can enabling / using this feature result in resource exhaustion of some node resources (PIDs, sockets, inodes, etc.)? + +No change in node resources. + +### Troubleshooting + + + +###### How does this feature react if the API server and/or etcd is unavailable? + +The Kubernetes control plane will be down, so no new Pods can get scheduled. + +###### What are other known failure modes? + + + +**kube-scheduler cannot allocate ResourceClaims.** + +**Detection**: When pods fail to get scheduled, kube-scheduler reports that through events and pod status. For DRA, messages include "cannot allocate all claims" (insufficient resources) and "ResourceClaim not created yet" (user or kube-controller-manager haven't created the ResourceClaim yet). The "unschedulable_pods" metric will have pods counted under the "dynamicresources" plugin label. + +To troubleshoot, "kubectl describe" can be used on (in this order) Pod and ResourceClaim. + +**Mitigations:** When resources should be available but don't get advertised in ResourceSlices, debugging must focus on the DRA driver, with trouble-shooting instructions provided by the vendor. + +When ResourceClaims for ResourceClaimTemplates don't get created, the log output of the kube-controller-manager will have more information. + +When a given CEL expression fails to compile, the kube-scheduler log will log a message indicating that. + +**Diagnostics**: In kube-scheduler, -v=4 enables simple progress reporting in the "dynamicresources" plugin. -v=5 provides more information about each plugin method. The special status results mentioned above also get logged. + +**Testing**: E2E testing should cover CEL expressions for main use cases and testing for known failure scenarios. + + +###### What steps should be taken if SLOs are not being met to determine the problem? + +SLO breached due to too many devices - Mitigation will be through reducing number of devices in a resource slice. Additional mitigation can be via using combination of device selectors and CEL expression constraint to reduce the number of devices being subject to constraint evaluation. + +## Implementation History + +- 1.34: first KEP revision + +## Infrastructure Needed (Optional) + + + +Initially, all development will happen inside the main Kubernetes repository. The mock driver can be developed inside test/e2e/dra. For the generic part of that driver, i.e. the code that other drivers can reuse, and other common code a new staging repo [k8s.io/dynamic-resource-allocation](http://k8s.io/dynamic-resource-allocation) is needed. \ No newline at end of file diff --git a/keps/sig-scheduling/5254-dra-constraints-with-cel/cel-expression-recursion-tree.png b/keps/sig-scheduling/5254-dra-constraints-with-cel/cel-expression-recursion-tree.png new file mode 100644 index 0000000000000000000000000000000000000000..5d1c92e30caf7ec8c9bb57c9ad80b8c21eecfd1a GIT binary patch literal 14464 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

d=$R0q28G+m205G$WK@RMzAE_;2qFtBzct$;I#g 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ðl*t2D#-(7UcBOZ@F;4yS82pQ38Cx zqnON7uqzGsa2zxpQqqyyJ%-uUs$q$74~U3xL!}=J4>>998&W(NDl@bJ5%NT|1*wsS zj|ZJJ%&?FE3e2XrA2{$5=@G2nM;}%;`B6-AfGYaauKR9ZRPQI;ZA;a9&^bTWrXrX9 z%C`T8rA;KfW%bFIAO2}Er=CKZvX?yv0sh2zr5)-voJy> zN}*eVo8f51w{|CFgPY>+;b9z7r3*;|?%)lo{L6@3vtx##E2$W((5Zno@CQ zoAuqtpQ`(=G8#_&U3Yh+TjBU7-Jz7hF8gH9$xXU8HZ}`~O@kTTKECqwWH;-5S(GmM z-HKgQo)tjDqFo9#VU@T(aR=ww4a!fd>p*F8KiXW8RN33v%}z2}kYaBj7Ki!<+H

O1_jY{*sM6*oWvefMf+puY~s}$6~LSA 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 literal 0 HcmV?d00001 diff --git a/keps/sig-scheduling/5254-dra-constraints-with-cel/kep.yaml b/keps/sig-scheduling/5254-dra-constraints-with-cel/kep.yaml new file mode 100644 index 00000000000..70644a129f5 --- /dev/null +++ b/keps/sig-scheduling/5254-dra-constraints-with-cel/kep.yaml @@ -0,0 +1,45 @@ +title: DRA Constraints with CEL +kep-number: 5254 +authors: + - "@aws-ruhip" + - "@geetasg" +owning-sig: sig-scheduling +status: implementable +creation-date: 2025-06-10 +reviewers: + - "@deads2k" + - "@macsko" + - "@pohly" +approvers: + - "@alculquicondor" # SIG Scheduling + +see-also: + - "/keps/sig-node/4381-dra-structured-parameters" + +# The target maturity stage in the current dev cycle for this KEP. +stage: alpha + +# The most recent milestone for which work toward delivery of this KEP has been +# done. This can be the current (upcoming) milestone, if it is being actively +# worked on. +latest-milestone: "v1.34" + +# The milestone at which this feature was, or is targeted to be, at each stage. +milestone: + alpha: "v1.34" + +# The following PRR answers are required at alpha release +# List the feature gate name and the components for which it must be enabled +feature-gates: + - name: DRACELDeviceConstraint + components: + - kube-apiserver + - kube-controller-manager + - kube-scheduler +disable-supported: true + +# The following PRR answers are required at beta release +metrics: + - resourceclaim_controller_with_cel_constraint + - resourceclaim_controller_with_match_attribute_constraint + - resourceclaim_controller_with_cel_constraint_latency From 801f606a3d5161e895772a1c85110b2a01173aae Mon Sep 17 00:00:00 2001 From: Geeta Gharpure Date: Fri, 20 Jun 2025 15:31:09 -0700 Subject: [PATCH 2/3] [KEP-5254] DRA: Constraints with CEL - Update feature gate description --- keps/sig-scheduling/5254-dra-constraints-with-cel/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/keps/sig-scheduling/5254-dra-constraints-with-cel/README.md b/keps/sig-scheduling/5254-dra-constraints-with-cel/README.md index 87c752ed2f9..2ad0f2eb066 100644 --- a/keps/sig-scheduling/5254-dra-constraints-with-cel/README.md +++ b/keps/sig-scheduling/5254-dra-constraints-with-cel/README.md @@ -732,7 +732,7 @@ No ###### Can the feature be disabled once it has been enabled (i.e. can we roll back the enablement)? -Yes, through the feature gate. Pods that are already running will continue unaffected. New workloads trying to utilize the feature will not work. If any of the feature gates in kube-apiserver, kube-scheduler, and kube-controller-manager are disabled, the feature will not work. The pod will either not be created or will be in a pending state with the corresponding error message. +Yes, through feature gates. If the feature gate is disabled in kube-apiserver, kube-scheduler, or kube-controller-manager, the feature will not work. Existing running Pods continue unaffected. New workloads cannot use the feature, and pod creation will fail if attempting to use the feature. Existing pods that need rescheduling will remain in pending state. Pod description will notify users to enable the feature gate. The reason for not allowing existing pods to re-schedule is because users expect optimal placement for these pods, which cannot happen when the feature is disabled ###### What happens if we reenable the feature if it was previously rolled back? @@ -1005,4 +1005,4 @@ new subproject, repos requested, or GitHub details. Listing these here allows a SIG to get the process for these resources started right away. --> -Initially, all development will happen inside the main Kubernetes repository. The mock driver can be developed inside test/e2e/dra. For the generic part of that driver, i.e. the code that other drivers can reuse, and other common code a new staging repo [k8s.io/dynamic-resource-allocation](http://k8s.io/dynamic-resource-allocation) is needed. \ No newline at end of file +Initially, all development will happen inside the main Kubernetes repository. The mock driver can be developed inside test/e2e/dra. For the generic part of that driver, i.e. the code that other drivers can reuse, and other common code a new staging repo [k8s.io/dynamic-resource-allocation](http://k8s.io/dynamic-resource-allocation) is needed. From cf9939354d469a9545bc76e05cbd191dda30a3c6 Mon Sep 17 00:00:00 2001 From: Ruhi Prasad Date: Tue, 10 Jun 2025 05:55:14 +0000 Subject: [PATCH 3/3] [KEP-5254] DRA: Constraints with CEL - update performance callout --- .../5254-dra-constraints-with-cel/README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/keps/sig-scheduling/5254-dra-constraints-with-cel/README.md b/keps/sig-scheduling/5254-dra-constraints-with-cel/README.md index 2ad0f2eb066..9c62ec6c2a3 100644 --- a/keps/sig-scheduling/5254-dra-constraints-with-cel/README.md +++ b/keps/sig-scheduling/5254-dra-constraints-with-cel/README.md @@ -74,6 +74,7 @@ SIG Architecture for cross-cutting KEPs). - [Risks and Mitigations](#risks-and-mitigations) - [Runaway expressions](#runaway-expressions) - [Performance at scale](#performance-at-scale) + - [Mitigation](#mitigation) - [Design Details](#design-details) - [Components diagram](#components-diagram) - [kube-apiserver Updates](#kube-apiserver-updates) @@ -394,7 +395,7 @@ The pod will be allocated on the 4 connected devices - mla0, mla1, mla4, and mla #### Runaway expressions -A malicious or buggy workload can specify CEL expressions that degrade the performance of constraint evaluation and scheduling. We will specify a limit on evaluation cost for the expression. There is already a mechanism to cap this today with CEL selectors that we can reuse. [Reference](https://github.com/kubernetes/kubernetes/blob/6188e5cb7b2f106b047493b7b498c1882723cab4/pkg/apis/resource/types.go#L910-L933) +A malicious or buggy workload can specify CEL expressions that degrade the performance of constraint evaluation and scheduling. We will specify a limit on evaluation cost for the expression. There is already a mechanism to cap this today with CEL selectors that we can reuse [Reference](https://github.com/kubernetes/kubernetes/blob/6188e5cb7b2f106b047493b7b498c1882723cab4/pkg/apis/resource/types.go#L910-L933). Additionally, the scheduler supports a configurable timeout during filtering that can limit this evaluation [Reference](https://github.com/kubernetes/kubernetes/pull/132033). #### Performance at scale @@ -402,9 +403,17 @@ The feature depends on exhaustive search for devices. The worst-case performance Additionally, with the introduction of [workload-aware scheduling](https://github.com/kubernetes/kubernetes/issues/132192), the performance might take a hit as filtering can be executed multiple times. -As a partial mitigation for the performance impact, the scheduler plugin supports a configurable timeout that can be applied for a node. [Reference](https://github.com/kubernetes/enhancements/blob/5b1270421f5bc3315fe191c29e0356bc91cbfe6b/keps/sig-node/4381-dra-structured-parameters/README.md?plain=1#L2025-L2032) +##### Mitigation -We will need to revisit this solution before beta release. +To minimize the performance impact, users are recommended to trim down the solution space using existing filtering mechanisms before utilizing this constraint type, i.e. define it in the spec template after mechanisms such as CEL selectors and MatchAttribute. This will limit how many device combinations need to be evaluated. The feature is not expected to reach the scheduler timeout as the combination space has been reduced by previous filtering. + +Note: Users are discouraged from specifying a CEL expression which takes into account the order of devices since this will result in permutational complexity instead of combinational complexity. If users specify expensive queries, it should be noted that the evaluation cost limit and scheduler timeout described earlier will limit the exhaustive search. + +For example, if there are 12 devices to choose from and a user specifies needing 6 of those devices, the maximum number of calculations taken should be: + +12C6 = 12! / (6!)(6!) = 924 + +If the user specifies an order in which these devices should be evaluated, i.e. set should contain device0 first, then device1, device2, etc., this will result in permutational complexity, and is strongly discouraged. Feature will not work and return an error in this case since it crosses the preset threshold for evaluation cost. ## Design Details @@ -732,7 +741,7 @@ No ###### Can the feature be disabled once it has been enabled (i.e. can we roll back the enablement)? -Yes, through feature gates. If the feature gate is disabled in kube-apiserver, kube-scheduler, or kube-controller-manager, the feature will not work. Existing running Pods continue unaffected. New workloads cannot use the feature, and pod creation will fail if attempting to use the feature. Existing pods that need rescheduling will remain in pending state. Pod description will notify users to enable the feature gate. The reason for not allowing existing pods to re-schedule is because users expect optimal placement for these pods, which cannot happen when the feature is disabled +Yes, through feature gates. If the feature gate is disabled in kube-apiserver, kube-scheduler, or kube-controller-manager, the feature will not work. Existing running Pods continue unaffected. New workloads cannot use the feature, and pod creation will fail if attempting to use the feature. Existing pods that need rescheduling will remain in pending state. Pod description will notify users to enable the feature gate. The reason for not allowing existing pods to re-schedule is because users expect optimal placement for these pods, which cannot happen when the feature is disabled. ###### What happens if we reenable the feature if it was previously rolled back?