Files
cozystack/packages/apps/kubernetes/values.schema.json
Aleksei Sviridkin 0dbd0e1597 fix(kubernetes): make node group resources optional and prefer them over instanceType
A node group is sized either by instanceType or by explicit resources, but the
generated values schema required resources on every node group. A node group
with instanceType and no resources — or an empty resources dropped during
typed-CR to HelmRelease translation — failed schema validation with "missing
property 'resources'" even though sizing was fully specified.

Mark resources optional in the node group definition and regenerate the schema,
API types, README, and ApplicationDefinition. The chart template emits
domain.cpu/memory only when resources is present, so a node group sized purely
by instanceType now passes validation and installs.

When a node group sets both instanceType and explicit resources, explicit
resources take precedence: the instancetype is omitted from the rendered VM and
the per-group instancetype lookup is skipped, so KubeVirt accepts the explicit
domain.cpu/memory with no instancetype to conflict with. instanceType stays
required and defaulted (no schema change), so the console's required
instanceType dropdown keeps working — sending the default instanceType plus
explicit resources simply sizes the VM by resources. This replaces an earlier
render-time hard failure on the both-set case, which was unreachable to clear
from the dashboard (the required dropdown cannot be emptied).

Explicit resources are all-or-nothing alongside an instanceType: a partial spec
(only cpu, or only memory) on a group that carries a non-empty instanceType
would emit one domain override next to the instancetype — which KubeVirt rejects
— so it now fails at render time with a clear error. Partial resources on a
group without an instanceType (used to tune kubelet reservations) are unaffected.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
(cherry picked from commit a349c753ad)
2026-06-30 18:14:23 +05:00

909 lines
29 KiB
JSON

{
"title": "Chart Values",
"type": "object",
"properties": {
"storageClass": {
"description": "StorageClass used to store the data.",
"type": "string",
"default": "replicated",
"x-kubernetes-validations": [
{
"rule": "self == oldSelf",
"message": "storageClass is immutable"
}
],
"x-cozystack-options": {
"source": "storageclass"
}
},
"nodeGroups": {
"description": "Worker nodes configuration map.",
"type": "object",
"default": {
"md0": {
"diskSize": "20Gi",
"gpus": [],
"instanceType": "u1.medium",
"kubelet": {},
"maxReplicas": 10,
"minReplicas": 0,
"resources": {},
"roles": [
"ingress-nginx"
],
"storageClass": ""
}
},
"additionalProperties": {
"type": "object",
"required": [
"diskSize",
"instanceType",
"maxReplicas",
"minReplicas"
],
"properties": {
"diskSize": {
"description": "Persistent disk size for kubelet and containerd data.",
"default": "20Gi",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
},
"gpus": {
"description": "List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).",
"type": "array",
"items": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"description": "Name of GPU, such as \"nvidia.com/AD102GL_L40S\".",
"type": "string",
"x-cozystack-options": {
"source": "gpu"
}
}
}
}
},
"instanceType": {
"description": "Virtual machine instance type.",
"type": "string",
"default": "u1.medium",
"x-cozystack-options": {
"source": "instancetype"
}
},
"kubelet": {
"description": "Kubelet resource reservations for this node group.",
"type": "object",
"properties": {
"evictionHardMemory": {
"description": "Hard eviction threshold for memory (absolute like 200Mi or percentage like 7%).",
"type": "string",
"default": "7%"
},
"evictionSoftMemory": {
"description": "Soft eviction threshold for memory (absolute like 1Gi or percentage like 10%).",
"type": "string",
"default": "10%"
},
"kubeReservedCpu": {
"description": "CPU reserved for kubelet and container runtime. Auto-computed from instanceType if empty.",
"type": "string"
},
"kubeReservedMemory": {
"description": "Memory reserved for kubelet and container runtime. Auto-computed from instanceType if empty.",
"type": "string"
},
"systemReservedCpu": {
"description": "CPU reserved for host OS. Auto-computed from instanceType if empty.",
"type": "string"
},
"systemReservedMemory": {
"description": "Memory reserved for host OS. Auto-computed from instanceType if empty.",
"type": "string"
}
}
},
"maxReplicas": {
"description": "Maximum number of replicas.",
"type": "integer",
"default": 10
},
"minReplicas": {
"description": "Minimum number of replicas.",
"type": "integer",
"default": 0
},
"resources": {
"description": "Explicit CPU and memory for each worker node, as an alternative to `instanceType` sizing. Optional: when omitted, the node is sized by `instanceType`. When both `cpu` and `memory` are set, they take precedence and `instanceType` is ignored for that node group (the instancetype is omitted from the VM, since KubeVirt cannot override an instancetype's CPU/memory). Set both `cpu` and `memory` together or neither; setting only one is rejected at render time.",
"type": "object",
"properties": {
"cpu": {
"description": "CPU available.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
},
"memory": {
"description": "Memory (RAM) available.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
}
}
},
"roles": {
"description": "List of node roles.",
"type": "array",
"items": {
"type": "string"
}
},
"storageClass": {
"description": "StorageClass for worker node persistent disks. When empty, uses the management cluster default StorageClass (the one annotated storageclass.kubernetes.io/is-default-class: true). NOTE: deliberately not marked immutable — the field is optional and undefaulted, so a strict `self == oldSelf` rule would block any future attempt to set it on an existing node group.",
"type": "string",
"x-cozystack-options": {
"source": "storageclass"
}
}
}
}
},
"version": {
"description": "Kubernetes major.minor version to deploy",
"type": "string",
"default": "v1.35",
"enum": [
"v1.35",
"v1.34",
"v1.33",
"v1.32",
"v1.31",
"v1.30"
]
},
"host": {
"description": "External hostname for Kubernetes cluster. Defaults to `\u003ccluster-name\u003e.\u003ctenant-host\u003e` if empty.",
"type": "string",
"default": ""
},
"addons": {
"description": "Cluster addons configuration.",
"type": "object",
"default": {},
"required": [
"certManager",
"cilium",
"coredns",
"fluxcd",
"gatewayAPI",
"gpuOperator",
"hami",
"ingressNginx",
"monitoringAgents",
"ouroboros",
"velero",
"verticalPodAutoscaler"
],
"properties": {
"certManager": {
"description": "Cert-manager addon.",
"type": "object",
"default": {},
"required": [
"enabled",
"valuesOverride"
],
"properties": {
"enabled": {
"description": "Enable cert-manager.",
"type": "boolean",
"default": false
},
"valuesOverride": {
"description": "Custom Helm values overrides.",
"type": "object",
"default": {},
"x-kubernetes-preserve-unknown-fields": true
}
}
},
"cilium": {
"description": "Cilium CNI plugin.",
"type": "object",
"default": {},
"required": [
"valuesOverride"
],
"properties": {
"valuesOverride": {
"description": "Custom Helm values overrides.",
"type": "object",
"default": {},
"x-kubernetes-preserve-unknown-fields": true
}
}
},
"coredns": {
"description": "CoreDNS addon.",
"type": "object",
"default": {},
"required": [
"valuesOverride"
],
"properties": {
"valuesOverride": {
"description": "Custom Helm values overrides.",
"type": "object",
"default": {},
"x-kubernetes-preserve-unknown-fields": true
}
}
},
"fluxcd": {
"description": "FluxCD GitOps operator.",
"type": "object",
"default": {},
"required": [
"enabled",
"valuesOverride"
],
"properties": {
"enabled": {
"description": "Enable FluxCD.",
"type": "boolean",
"default": false
},
"valuesOverride": {
"description": "Custom Helm values overrides.",
"type": "object",
"default": {},
"x-kubernetes-preserve-unknown-fields": true
}
}
},
"gatewayAPI": {
"description": "Gateway API addon.",
"type": "object",
"default": {},
"required": [
"enabled"
],
"properties": {
"enabled": {
"description": "Enable Gateway API.",
"type": "boolean",
"default": false
}
}
},
"gpuOperator": {
"description": "NVIDIA GPU Operator.",
"type": "object",
"default": {},
"required": [
"enabled",
"valuesOverride"
],
"properties": {
"enabled": {
"description": "Enable GPU Operator.",
"type": "boolean",
"default": false
},
"valuesOverride": {
"description": "Custom Helm values overrides.",
"type": "object",
"default": {},
"x-kubernetes-preserve-unknown-fields": true
}
}
},
"hami": {
"description": "HAMi GPU virtualization middleware.",
"type": "object",
"default": {},
"required": [
"enabled",
"valuesOverride"
],
"properties": {
"enabled": {
"description": "Enable HAMi (requires GPU Operator).",
"type": "boolean",
"default": false
},
"valuesOverride": {
"description": "Custom Helm values overrides.",
"type": "object",
"default": {},
"x-kubernetes-preserve-unknown-fields": true
}
}
},
"ingressNginx": {
"description": "Ingress-NGINX controller.",
"type": "object",
"default": {},
"required": [
"enabled",
"exposeMethod",
"valuesOverride"
],
"properties": {
"enabled": {
"description": "Enable the controller (requires nodes labeled `ingress-nginx`).",
"type": "boolean",
"default": false
},
"exposeMethod": {
"description": "Method to expose the controller. Allowed values: `Proxied`, `LoadBalancer`.",
"type": "string",
"default": "Proxied",
"enum": [
"Proxied",
"LoadBalancer"
]
},
"hosts": {
"description": "Domains routed to this tenant cluster when `exposeMethod` is `Proxied`.",
"type": "array",
"default": [],
"items": {
"type": "string"
}
},
"valuesOverride": {
"description": "Custom Helm values overrides.",
"type": "object",
"default": {},
"x-kubernetes-preserve-unknown-fields": true
}
}
},
"monitoringAgents": {
"description": "Monitoring agents.",
"type": "object",
"default": {},
"required": [
"enabled",
"valuesOverride"
],
"properties": {
"enabled": {
"description": "Enable monitoring agents.",
"type": "boolean",
"default": false
},
"valuesOverride": {
"description": "Custom Helm values overrides.",
"type": "object",
"default": {},
"x-kubernetes-preserve-unknown-fields": true
}
}
},
"ouroboros": {
"description": "Hairpin-NAT fix for ingress-nginx with PROXY-protocol.",
"type": "object",
"default": {},
"required": [
"enabled",
"valuesOverride"
],
"properties": {
"enabled": {
"description": "Enable ouroboros. Requires addons.ingressNginx.enabled (chart-render fail otherwise). Only useful when PROXY-protocol is wired on the tenant ingress-nginx via valuesOverride.",
"type": "boolean",
"default": false
},
"valuesOverride": {
"description": "Custom Helm values overrides. Operator-key wins over cozystack defaults.",
"type": "object",
"default": {},
"x-kubernetes-preserve-unknown-fields": true
}
}
},
"velero": {
"description": "Velero backup/restore addon.",
"type": "object",
"default": {},
"required": [
"enabled",
"valuesOverride"
],
"properties": {
"enabled": {
"description": "Enable Velero.",
"type": "boolean",
"default": false
},
"valuesOverride": {
"description": "Custom Helm values overrides.",
"type": "object",
"default": {},
"x-kubernetes-preserve-unknown-fields": true
}
}
},
"verticalPodAutoscaler": {
"description": "Vertical Pod Autoscaler.",
"type": "object",
"default": {},
"required": [
"valuesOverride"
],
"properties": {
"valuesOverride": {
"description": "Custom Helm values overrides.",
"type": "object",
"default": {},
"x-kubernetes-preserve-unknown-fields": true
}
}
}
}
},
"controlPlane": {
"description": "Kubernetes control-plane configuration.",
"type": "object",
"default": {},
"required": [
"apiServer",
"controllerManager",
"konnectivity",
"replicas",
"scheduler"
],
"properties": {
"apiServer": {
"description": "API Server configuration.",
"type": "object",
"default": {},
"required": [
"resources",
"resourcesPreset"
],
"properties": {
"resources": {
"description": "CPU and memory resources for API Server.",
"type": "object",
"default": {},
"properties": {
"cpu": {
"description": "CPU available.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
},
"memory": {
"description": "Memory (RAM) available.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
}
}
},
"resourcesPreset": {
"description": "Preset if `resources` omitted.",
"type": "string",
"default": "c1.medium",
"enum": [
"t1.nano",
"t1.micro",
"t1.small",
"t1.medium",
"t1.large",
"t1.xlarge",
"t1.2xlarge",
"t1.4xlarge",
"c1.nano",
"c1.micro",
"c1.small",
"c1.medium",
"c1.large",
"c1.xlarge",
"c1.2xlarge",
"c1.4xlarge",
"s1.nano",
"s1.micro",
"s1.small",
"s1.medium",
"s1.large",
"s1.xlarge",
"s1.2xlarge",
"s1.4xlarge",
"u1.nano",
"u1.micro",
"u1.small",
"u1.medium",
"u1.large",
"u1.xlarge",
"u1.2xlarge",
"u1.4xlarge",
"m1.nano",
"m1.micro",
"m1.small",
"m1.medium",
"m1.large",
"m1.xlarge",
"m1.2xlarge",
"m1.4xlarge",
"nano",
"micro",
"small",
"medium",
"large",
"xlarge",
"2xlarge"
]
}
}
},
"controllerManager": {
"description": "Controller Manager configuration.",
"type": "object",
"default": {},
"required": [
"resources",
"resourcesPreset"
],
"properties": {
"resources": {
"description": "CPU and memory resources for Controller Manager.",
"type": "object",
"default": {},
"properties": {
"cpu": {
"description": "CPU available.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
},
"memory": {
"description": "Memory (RAM) available.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
}
}
},
"resourcesPreset": {
"description": "Preset if `resources` omitted.",
"type": "string",
"default": "t1.micro",
"enum": [
"t1.nano",
"t1.micro",
"t1.small",
"t1.medium",
"t1.large",
"t1.xlarge",
"t1.2xlarge",
"t1.4xlarge",
"c1.nano",
"c1.micro",
"c1.small",
"c1.medium",
"c1.large",
"c1.xlarge",
"c1.2xlarge",
"c1.4xlarge",
"s1.nano",
"s1.micro",
"s1.small",
"s1.medium",
"s1.large",
"s1.xlarge",
"s1.2xlarge",
"s1.4xlarge",
"u1.nano",
"u1.micro",
"u1.small",
"u1.medium",
"u1.large",
"u1.xlarge",
"u1.2xlarge",
"u1.4xlarge",
"m1.nano",
"m1.micro",
"m1.small",
"m1.medium",
"m1.large",
"m1.xlarge",
"m1.2xlarge",
"m1.4xlarge",
"nano",
"micro",
"small",
"medium",
"large",
"xlarge",
"2xlarge"
]
}
}
},
"konnectivity": {
"description": "Konnectivity configuration.",
"type": "object",
"default": {},
"required": [
"server"
],
"properties": {
"server": {
"description": "Konnectivity Server configuration.",
"type": "object",
"default": {},
"required": [
"resources",
"resourcesPreset"
],
"properties": {
"resources": {
"description": "CPU and memory resources for Konnectivity.",
"type": "object",
"default": {},
"properties": {
"cpu": {
"description": "CPU available.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
},
"memory": {
"description": "Memory (RAM) available.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
}
}
},
"resourcesPreset": {
"description": "Preset if `resources` omitted.",
"type": "string",
"default": "t1.micro",
"enum": [
"t1.nano",
"t1.micro",
"t1.small",
"t1.medium",
"t1.large",
"t1.xlarge",
"t1.2xlarge",
"t1.4xlarge",
"c1.nano",
"c1.micro",
"c1.small",
"c1.medium",
"c1.large",
"c1.xlarge",
"c1.2xlarge",
"c1.4xlarge",
"s1.nano",
"s1.micro",
"s1.small",
"s1.medium",
"s1.large",
"s1.xlarge",
"s1.2xlarge",
"s1.4xlarge",
"u1.nano",
"u1.micro",
"u1.small",
"u1.medium",
"u1.large",
"u1.xlarge",
"u1.2xlarge",
"u1.4xlarge",
"m1.nano",
"m1.micro",
"m1.small",
"m1.medium",
"m1.large",
"m1.xlarge",
"m1.2xlarge",
"m1.4xlarge",
"nano",
"micro",
"small",
"medium",
"large",
"xlarge",
"2xlarge"
]
}
}
}
}
},
"replicas": {
"description": "Number of control-plane replicas.",
"type": "integer",
"default": 2
},
"scheduler": {
"description": "Scheduler configuration.",
"type": "object",
"default": {},
"required": [
"resources",
"resourcesPreset"
],
"properties": {
"resources": {
"description": "CPU and memory resources for Scheduler.",
"type": "object",
"default": {},
"properties": {
"cpu": {
"description": "CPU available.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
},
"memory": {
"description": "Memory (RAM) available.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
}
}
},
"resourcesPreset": {
"description": "Preset if `resources` omitted.",
"type": "string",
"default": "t1.micro",
"enum": [
"t1.nano",
"t1.micro",
"t1.small",
"t1.medium",
"t1.large",
"t1.xlarge",
"t1.2xlarge",
"t1.4xlarge",
"c1.nano",
"c1.micro",
"c1.small",
"c1.medium",
"c1.large",
"c1.xlarge",
"c1.2xlarge",
"c1.4xlarge",
"s1.nano",
"s1.micro",
"s1.small",
"s1.medium",
"s1.large",
"s1.xlarge",
"s1.2xlarge",
"s1.4xlarge",
"u1.nano",
"u1.micro",
"u1.small",
"u1.medium",
"u1.large",
"u1.xlarge",
"u1.2xlarge",
"u1.4xlarge",
"m1.nano",
"m1.micro",
"m1.small",
"m1.medium",
"m1.large",
"m1.xlarge",
"m1.2xlarge",
"m1.4xlarge",
"nano",
"micro",
"small",
"medium",
"large",
"xlarge",
"2xlarge"
]
}
}
}
}
},
"images": {
"description": "Optional image overrides for air-gapped or rate-limited registries.",
"type": "object",
"default": {},
"properties": {
"waitForKubeconfig": {
"description": "Image used by the wait-for-kubeconfig init container. Empty falls back to images/busybox.tag.",
"type": "string",
"default": ""
}
}
}
}
}