mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-03 10:37:21 +09:00
GPUs become a catalog of hoster offers, tenant reservations against them and a per-node inventory, so a tenant can hold a GPU while its VM is off and an order that does not fit is refused at create time. The CRDs enforce what the apiserver alone can: a class name is a DNS label other than total because it becomes part of a quota key, the source is a union with dra reserved for a later member, a device's spec is immutable and orders at least one GPU, and bound devices are listable per node through a field selector on status.nodeName. A class resource name must be a valid extended resource outside gpu.cozystack.io, where it would read as a GPU budget key. The CRDs are kept on chart uninstall, since they ship as plain templates and deleting GPUDevice would drop every hold and usage record. maxCount carries no schema default: it defaults to 1 for vgpu classes and to no limit for passthrough, which a structural default cannot express, so readers resolve it through the shared model. A GPUNode device reports how many device-plugin devices kubelet counts as allocatable for it, because an mdev parent serves several from one PCI function and one healthy entry per function would undercount the node. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <f@lex.la>
241 lines
10 KiB
YAML
241 lines
10 KiB
YAML
---
|
|
apiVersion: apiextensions.k8s.io/v1
|
|
kind: CustomResourceDefinition
|
|
metadata:
|
|
annotations:
|
|
controller-gen.kubebuilder.io/version: v0.16.4
|
|
helm.sh/resource-policy: keep
|
|
name: gpuclasses.gpu.cozystack.io
|
|
spec:
|
|
group: gpu.cozystack.io
|
|
names:
|
|
kind: GPUClass
|
|
listKind: GPUClassList
|
|
plural: gpuclasses
|
|
singular: gpuclass
|
|
scope: Cluster
|
|
versions:
|
|
- additionalPrinterColumns:
|
|
- jsonPath: .spec.mode
|
|
name: Mode
|
|
type: string
|
|
- jsonPath: .spec.model
|
|
name: Model
|
|
type: string
|
|
- jsonPath: .spec.profile
|
|
name: Profile
|
|
type: string
|
|
- jsonPath: .status.free
|
|
name: Free
|
|
type: integer
|
|
- jsonPath: .metadata.creationTimestamp
|
|
name: Age
|
|
type: date
|
|
name: v1alpha1
|
|
schema:
|
|
openAPIV3Schema:
|
|
description: GPUClass is one GPU offer of the hoster's catalog.
|
|
properties:
|
|
apiVersion:
|
|
description: |-
|
|
APIVersion defines the versioned schema of this representation of an object.
|
|
Servers should convert recognized schemas to the latest internal value, and
|
|
may reject unrecognized values.
|
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
|
type: string
|
|
kind:
|
|
description: |-
|
|
Kind is a string value representing the REST resource this object represents.
|
|
Servers may infer this from the endpoint the client submits requests to.
|
|
Cannot be updated.
|
|
In CamelCase.
|
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
|
type: string
|
|
metadata:
|
|
type: object
|
|
spec:
|
|
description: GPUClassSpec defines one GPU offer of the hoster's catalog.
|
|
properties:
|
|
displayName:
|
|
description: DisplayName is the human-readable name of the offer.
|
|
maxLength: 256
|
|
type: string
|
|
maxCount:
|
|
description: |-
|
|
MaxCount is how many GPUs of this class one VM may take. Unset means 1
|
|
for mode vgpu and, for mode passthrough, no limit beyond the GPUs of
|
|
one node; read it through the shared model rather than the raw field.
|
|
format: int32
|
|
minimum: 1
|
|
type: integer
|
|
memory:
|
|
anyOf:
|
|
- type: integer
|
|
- type: string
|
|
description: Memory is the GPU memory of one unit of the class.
|
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
|
x-kubernetes-int-or-string: true
|
|
mode:
|
|
description: GPUMode is how a class's GPUs reach a VM.
|
|
enum:
|
|
- passthrough
|
|
- vgpu
|
|
type: string
|
|
model:
|
|
maxLength: 63
|
|
minLength: 1
|
|
type: string
|
|
nodeSelector:
|
|
additionalProperties:
|
|
type: string
|
|
description: NodeSelector selects the nodes that serve this class.
|
|
type: object
|
|
onCordon:
|
|
default: Move
|
|
description: OnCordon decides whether an unattached device leaves
|
|
a cordoned node.
|
|
enum:
|
|
- Move
|
|
- Stay
|
|
type: string
|
|
profile:
|
|
description: Profile is the vGPU profile, such as L40S-24C. Set only
|
|
for mode vgpu.
|
|
maxLength: 63
|
|
minLength: 1
|
|
type: string
|
|
source:
|
|
description: |-
|
|
GPUClassSource is the backend that serves a class. Exactly one member is
|
|
set. The name "dra" is reserved for a DRA-backed member.
|
|
properties:
|
|
devicePlugin:
|
|
description: DevicePluginSource serves a class through a Kubernetes
|
|
device plugin.
|
|
properties:
|
|
resourceName:
|
|
description: |-
|
|
ResourceName is the extended resource the device plugin advertises for
|
|
this class's GPUs. Two classes may share it only when their node
|
|
selectors do not overlap.
|
|
maxLength: 317
|
|
minLength: 3
|
|
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/[A-Za-z0-9]([-A-Za-z0-9_.]{0,61}[A-Za-z0-9])?$
|
|
type: string
|
|
x-kubernetes-validations:
|
|
- message: resourceName must not be in gpu.cozystack.io, whose
|
|
keys are GPU budgets
|
|
rule: '!self.startsWith(''gpu.cozystack.io/'')'
|
|
required:
|
|
- resourceName
|
|
type: object
|
|
type: object
|
|
x-kubernetes-validations:
|
|
- message: exactly one source must be set; devicePlugin is the only
|
|
source supported, dra is reserved
|
|
rule: '[has(self.devicePlugin)].filter(x, x).size() == 1'
|
|
vendor:
|
|
maxLength: 63
|
|
minLength: 1
|
|
type: string
|
|
required:
|
|
- mode
|
|
- model
|
|
- source
|
|
- vendor
|
|
type: object
|
|
x-kubernetes-validations:
|
|
- message: profile is required for mode vgpu and not allowed for mode
|
|
passthrough
|
|
rule: 'self.mode == ''vgpu'' ? has(self.profile) : !has(self.profile)'
|
|
status:
|
|
description: |-
|
|
GPUClassStatus is readable by every authenticated user, so it carries no
|
|
node names and no counts beyond free capacity.
|
|
properties:
|
|
conditions:
|
|
items:
|
|
description: Condition contains details for one aspect of the current
|
|
state of this API Resource.
|
|
properties:
|
|
lastTransitionTime:
|
|
description: |-
|
|
lastTransitionTime is the last time the condition transitioned from one status to another.
|
|
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
|
format: date-time
|
|
type: string
|
|
message:
|
|
description: |-
|
|
message is a human readable message indicating details about the transition.
|
|
This may be an empty string.
|
|
maxLength: 32768
|
|
type: string
|
|
observedGeneration:
|
|
description: |-
|
|
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
|
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
|
with respect to the current state of the instance.
|
|
format: int64
|
|
minimum: 0
|
|
type: integer
|
|
reason:
|
|
description: |-
|
|
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
|
Producers of specific condition types may define expected values and meanings for this field,
|
|
and whether the values are considered a guaranteed API.
|
|
The value should be a CamelCase string.
|
|
This field may not be empty.
|
|
maxLength: 1024
|
|
minLength: 1
|
|
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
|
type: string
|
|
status:
|
|
description: status of the condition, one of True, False, Unknown.
|
|
enum:
|
|
- "True"
|
|
- "False"
|
|
- Unknown
|
|
type: string
|
|
type:
|
|
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
|
maxLength: 316
|
|
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
|
type: string
|
|
required:
|
|
- lastTransitionTime
|
|
- message
|
|
- reason
|
|
- status
|
|
- type
|
|
type: object
|
|
type: array
|
|
x-kubernetes-list-map-keys:
|
|
- type
|
|
x-kubernetes-list-type: map
|
|
free:
|
|
description: |-
|
|
Free is the number of free units of the class on eligible nodes.
|
|
Absent until the controller has computed it.
|
|
format: int32
|
|
type: integer
|
|
maxFreePerNode:
|
|
description: MaxFreePerNode is the largest number of free units on
|
|
one node.
|
|
format: int32
|
|
type: integer
|
|
type: object
|
|
required:
|
|
- spec
|
|
type: object
|
|
x-kubernetes-validations:
|
|
- message: GPUClass name must be a DNS label, since it becomes part of a quota
|
|
key
|
|
rule: size(self.metadata.name) <= 63 && self.metadata.name.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$')
|
|
- message: GPUClass name total is reserved for the gpu.cozystack.io/total
|
|
quota key
|
|
rule: self.metadata.name != 'total'
|
|
served: true
|
|
storage: true
|
|
subresources:
|
|
status: {}
|