From 3920d76ba2d31cd7ef40e16c72d1670951515997 Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Sat, 25 Jul 2026 18:16:15 +1000 Subject: [PATCH] Add VPA CRDs and kubeconform schemas (#284) Split out of #281 per review: the VerticalPodAutoscaler CRDs land first, together with their generated kubeconform schemas, so CI can validate the VPA objects that follow. - Adds apps/base/vpa-system with the v1.7.0 VPA CRDs served via the artifactapi github remote (terraform-artifactapi#14, merged) - Adds schemas/autoscaling.k8s.io/ (verticalpodautoscaler + checkpoint, v1 and v1beta2) generated with the same transform as ci/generate-schemas.sh (from the CRD manifest rather than the live cluster, since the CRDs are not installed yet) - Wires the vpa-system overlay into the platform applicationset Verified: kustomize renders both CRDs; a sample updateMode Off VPA passes kubeconform against the new schemas. Merge before #281 (recommender) and #283 (per-workload VPAs). Reviewed-on: https://git.unkin.net/unkin/argocd-apps/pulls/284 Co-authored-by: Ben Vincent Co-committed-by: Ben Vincent --- apps/base/vpa-system/kustomization.yaml | 12 + apps/base/vpa-system/namespace.yaml | 5 + .../au-syd1/vpa-system/kustomization.yaml | 6 + argocd/applicationsets/platform.yaml | 1 + .../verticalpodautoscaler_v1.json | 431 ++++++++++++++++++ .../verticalpodautoscaler_v1beta2.json | 224 +++++++++ .../verticalpodautoscalercheckpoint_v1.json | 86 ++++ ...rticalpodautoscalercheckpoint_v1beta2.json | 86 ++++ 8 files changed, 851 insertions(+) create mode 100644 apps/base/vpa-system/kustomization.yaml create mode 100644 apps/base/vpa-system/namespace.yaml create mode 100644 apps/overlays/au-syd1/vpa-system/kustomization.yaml create mode 100644 schemas/autoscaling.k8s.io/verticalpodautoscaler_v1.json create mode 100644 schemas/autoscaling.k8s.io/verticalpodautoscaler_v1beta2.json create mode 100644 schemas/autoscaling.k8s.io/verticalpodautoscalercheckpoint_v1.json create mode 100644 schemas/autoscaling.k8s.io/verticalpodautoscalercheckpoint_v1beta2.json diff --git a/apps/base/vpa-system/kustomization.yaml b/apps/base/vpa-system/kustomization.yaml new file mode 100644 index 0000000..8c31f43 --- /dev/null +++ b/apps/base/vpa-system/kustomization.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - namespace.yaml + # VPA CRDs (verticalpodautoscalers + verticalpodautoscalercheckpoints) from + # the kubernetes/autoscaler repo at the pinned release tag, served through the + # artifactapi github remote (pattern allowlisted in terraform-artifactapi#14). + # The recommender deployment lands separately once these CRDs (and their + # kubeconform schemas under schemas/autoscaling.k8s.io/) are in. + - https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/github/kubernetes/autoscaler/raw/vertical-pod-autoscaler-1.7.0/vertical-pod-autoscaler/deploy/vpa-v1-crd-gen.yaml diff --git a/apps/base/vpa-system/namespace.yaml b/apps/base/vpa-system/namespace.yaml new file mode 100644 index 0000000..0e1e632 --- /dev/null +++ b/apps/base/vpa-system/namespace.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: vpa-system diff --git a/apps/overlays/au-syd1/vpa-system/kustomization.yaml b/apps/overlays/au-syd1/vpa-system/kustomization.yaml new file mode 100644 index 0000000..5be9008 --- /dev/null +++ b/apps/overlays/au-syd1/vpa-system/kustomization.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../../base/vpa-system diff --git a/argocd/applicationsets/platform.yaml b/argocd/applicationsets/platform.yaml index 28174aa..84f67c7 100644 --- a/argocd/applicationsets/platform.yaml +++ b/argocd/applicationsets/platform.yaml @@ -38,6 +38,7 @@ spec: - path: apps/overlays/*/reposync - path: apps/overlays/*/traefik-system - path: apps/overlays/*/vm-system + - path: apps/overlays/*/vpa-system - path: apps/overlays/*/vault - path: apps/overlays/*/vso-system - path: apps/overlays/*/woodpecker diff --git a/schemas/autoscaling.k8s.io/verticalpodautoscaler_v1.json b/schemas/autoscaling.k8s.io/verticalpodautoscaler_v1.json new file mode 100644 index 0000000..5757549 --- /dev/null +++ b/schemas/autoscaling.k8s.io/verticalpodautoscaler_v1.json @@ -0,0 +1,431 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "apiVersion": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "properties": { + "recommenders": { + "items": { + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "resourcePolicy": { + "properties": { + "containerPolicies": { + "items": { + "properties": { + "containerName": { + "type": "string" + }, + "controlledResources": { + "items": { + "type": "string" + }, + "type": "array" + }, + "controlledValues": { + "enum": [ + "RequestsAndLimits", + "RequestsOnly" + ], + "type": "string" + }, + "maxAllowed": { + "additionalProperties": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": "object" + }, + "memoryAggregationIntervalCount": { + "format": "int64", + "minimum": 1, + "type": "integer" + }, + "memoryAggregationIntervalSeconds": { + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "minAllowed": { + "additionalProperties": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": "object" + }, + "mode": { + "enum": [ + "Auto", + "Off" + ], + "type": "string" + }, + "oomBumpUpRatio": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "oomMinBumpUp": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "startupBoost": { + "properties": { + "cpu": { + "properties": { + "durationSeconds": { + "format": "int32", + "type": "integer" + }, + "factor": { + "format": "int32", + "type": "integer" + }, + "quantity": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": { + "enum": [ + "Factor", + "Quantity" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "factor is required when type is Factor and forbidden otherwise", + "rule": "(self.type == 'Factor') == has(self.factor)" + }, + { + "message": "quantity is required when type is Quantity and forbidden otherwise", + "rule": "(self.type == 'Quantity') == has(self.quantity)" + } + ] + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "startupBoost": { + "properties": { + "cpu": { + "properties": { + "durationSeconds": { + "format": "int32", + "type": "integer" + }, + "factor": { + "format": "int32", + "type": "integer" + }, + "quantity": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": { + "enum": [ + "Factor", + "Quantity" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "factor is required when type is Factor and forbidden otherwise", + "rule": "(self.type == 'Factor') == has(self.factor)" + }, + { + "message": "quantity is required when type is Quantity and forbidden otherwise", + "rule": "(self.type == 'Quantity') == has(self.quantity)" + } + ] + } + }, + "type": "object" + }, + "targetRef": { + "properties": { + "apiVersion": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "updatePolicy": { + "properties": { + "evictAfterOOMSeconds": { + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "evictionRequirements": { + "items": { + "properties": { + "changeRequirement": { + "enum": [ + "TargetHigherThanRequests", + "TargetLowerThanRequests" + ], + "type": "string" + }, + "resources": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "changeRequirement", + "resources" + ], + "type": "object" + }, + "type": "array" + }, + "minReplicas": { + "format": "int32", + "type": "integer" + }, + "updateMode": { + "enum": [ + "Off", + "Initial", + "Recreate", + "InPlaceOrRecreate", + "InPlace", + "Auto" + ], + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "targetRef" + ], + "type": "object" + }, + "status": { + "properties": { + "conditions": { + "items": { + "properties": { + "lastTransitionTime": { + "format": "date-time", + "type": "string" + }, + "message": { + "type": "string" + }, + "observedGeneration": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "type": "string" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "status", + "type" + ], + "type": "object" + }, + "type": "array" + }, + "observedGeneration": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "recommendation": { + "properties": { + "containerRecommendations": { + "items": { + "properties": { + "containerName": { + "type": "string" + }, + "lowerBound": { + "additionalProperties": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": "object" + }, + "target": { + "additionalProperties": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": "object" + }, + "uncappedTarget": { + "additionalProperties": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": "object" + }, + "upperBound": { + "additionalProperties": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": "object" + } + }, + "required": [ + "target" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "spec" + ], + "type": "object" +} diff --git a/schemas/autoscaling.k8s.io/verticalpodautoscaler_v1beta2.json b/schemas/autoscaling.k8s.io/verticalpodautoscaler_v1beta2.json new file mode 100644 index 0000000..e081d0f --- /dev/null +++ b/schemas/autoscaling.k8s.io/verticalpodautoscaler_v1beta2.json @@ -0,0 +1,224 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "apiVersion": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "properties": { + "resourcePolicy": { + "properties": { + "containerPolicies": { + "items": { + "properties": { + "containerName": { + "type": "string" + }, + "maxAllowed": { + "additionalProperties": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": "object" + }, + "minAllowed": { + "additionalProperties": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": "object" + }, + "mode": { + "enum": [ + "Auto", + "Off" + ], + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "targetRef": { + "properties": { + "apiVersion": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "updatePolicy": { + "properties": { + "updateMode": { + "enum": [ + "Off", + "Initial", + "Recreate", + "Auto" + ], + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "targetRef" + ], + "type": "object" + }, + "status": { + "properties": { + "conditions": { + "items": { + "properties": { + "lastTransitionTime": { + "format": "date-time", + "type": "string" + }, + "message": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "status", + "type" + ], + "type": "object" + }, + "type": "array" + }, + "recommendation": { + "properties": { + "containerRecommendations": { + "items": { + "properties": { + "containerName": { + "type": "string" + }, + "lowerBound": { + "additionalProperties": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": "object" + }, + "target": { + "additionalProperties": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": "object" + }, + "uncappedTarget": { + "additionalProperties": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": "object" + }, + "upperBound": { + "additionalProperties": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": "object" + } + }, + "required": [ + "target" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "spec" + ], + "type": "object" +} diff --git a/schemas/autoscaling.k8s.io/verticalpodautoscalercheckpoint_v1.json b/schemas/autoscaling.k8s.io/verticalpodautoscalercheckpoint_v1.json new file mode 100644 index 0000000..004dec6 --- /dev/null +++ b/schemas/autoscaling.k8s.io/verticalpodautoscalercheckpoint_v1.json @@ -0,0 +1,86 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "apiVersion": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "properties": { + "containerName": { + "type": "string" + }, + "vpaObjectName": { + "type": "string" + } + }, + "type": "object" + }, + "status": { + "properties": { + "cpuHistogram": { + "properties": { + "bucketWeights": { + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "referenceTimestamp": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "totalWeight": { + "type": "number" + } + }, + "type": "object" + }, + "firstSampleStart": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "lastSampleStart": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "lastUpdateTime": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "memoryHistogram": { + "properties": { + "bucketWeights": { + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "referenceTimestamp": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "totalWeight": { + "type": "number" + } + }, + "type": "object" + }, + "totalSamplesCount": { + "type": "integer" + }, + "version": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" +} diff --git a/schemas/autoscaling.k8s.io/verticalpodautoscalercheckpoint_v1beta2.json b/schemas/autoscaling.k8s.io/verticalpodautoscalercheckpoint_v1beta2.json new file mode 100644 index 0000000..004dec6 --- /dev/null +++ b/schemas/autoscaling.k8s.io/verticalpodautoscalercheckpoint_v1beta2.json @@ -0,0 +1,86 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "apiVersion": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "properties": { + "containerName": { + "type": "string" + }, + "vpaObjectName": { + "type": "string" + } + }, + "type": "object" + }, + "status": { + "properties": { + "cpuHistogram": { + "properties": { + "bucketWeights": { + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "referenceTimestamp": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "totalWeight": { + "type": "number" + } + }, + "type": "object" + }, + "firstSampleStart": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "lastSampleStart": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "lastUpdateTime": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "memoryHistogram": { + "properties": { + "bucketWeights": { + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "referenceTimestamp": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "totalWeight": { + "type": "number" + } + }, + "type": "object" + }, + "totalSamplesCount": { + "type": "integer" + }, + "version": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" +}