diff --git a/.github/workflows/codegen-drift.yml b/.github/workflows/codegen-drift.yml deleted file mode 100644 index a26caf5e..00000000 --- a/.github/workflows/codegen-drift.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Codegen Drift Check - -on: - pull_request: - types: [opened, synchronize, reopened] - paths: - - 'api/**' - - 'pkg/apis/**' - - 'pkg/generated/**' - - 'internal/crdinstall/manifests/**' - - 'packages/system/cozystack-controller/definitions/**' - - 'packages/system/application-definition-crd/definition/**' - - 'packages/system/backup-controller/definitions/**' - - 'packages/system/backupstrategy-controller/definitions/**' - - 'hack/update-codegen.sh' - - 'hack/boilerplate.go.txt' - - 'Makefile' - - 'go.mod' - - 'go.sum' - - '.github/workflows/codegen-drift.yml' - -concurrency: - group: codegen-drift-${{ github.workflow }}-${{ github.event.pull_request.number }} - cancel-in-progress: true - -jobs: - codegen-drift: - name: Verify generated code is up to date - runs-on: ubuntu-22.04 - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - cache: true - - - name: Pre-fetch k8s.io/code-generator module - # hack/update-codegen.sh sources kube_codegen.sh from the Go module cache. - # The module is not declared in go.mod, so fetch it explicitly at the - # version pinned in the script. - run: | - version=$(grep -oP 'code-generator@\Kv[0-9.]+' hack/update-codegen.sh) - tmpdir=$(mktemp -d) - cd "$tmpdir" - go mod init codegen-fetch - go get "k8s.io/code-generator@${version}" - - - name: Run make generate - run: make generate - - - name: Fail on drift - run: | - if [ -n "$(git status --porcelain)" ]; then - echo "::error::'make generate' produced changes. Run 'make generate' locally and commit the result." - git status --short - git diff --color=always - exit 1 - fi diff --git a/.github/workflows/pull-requests.yaml b/.github/workflows/pull-requests.yaml index d9898616..2e276ab2 100644 --- a/.github/workflows/pull-requests.yaml +++ b/.github/workflows/pull-requests.yaml @@ -227,27 +227,18 @@ jobs: until make SANDBOX_NAME=$SANDBOX_NAME prepare-env; do attempt=$((attempt + 1)) if [ $attempt -ge 3 ]; then - echo "❌ Attempt $attempt failed, exiting..." + echo "Attempt $attempt failed, exiting..." exit 1 fi - echo "❌ Attempt $attempt failed, retrying..." + echo "Attempt $attempt failed, retrying..." done - echo "✅ The task completed successfully after $attempt attempts" + echo "Prepare environment completed after $attempt attempts" # ▸ Install Cozystack - name: Install Cozystack into sandbox run: | cd /tmp/$SANDBOX_NAME - attempt=0 - until make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME install-cozystack; do - attempt=$((attempt + 1)) - if [ $attempt -ge 3 ]; then - echo "❌ Attempt $attempt failed, exiting..." - exit 1 - fi - echo "❌ Attempt $attempt failed, retrying..." - done - echo "✅ The task completed successfully after $attempt attempts" + make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME install-cozystack - name: Run OpenAPI tests run: | @@ -262,23 +253,18 @@ jobs: failed_tests="" for app in $(ls hack/e2e-apps/*.bats | xargs -n1 basename | cut -d. -f1); do echo "::group::Testing $app" - attempt=0 - success=false - until [ $attempt -ge 3 ]; do - if make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME test-apps-$app; then - success=true - break - fi - attempt=$((attempt + 1)) - echo "❌ Attempt $attempt failed, retrying..." - done - if [ "$success" = true ]; then + if make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME test-apps-$app; then + echo "::endgroup::" echo "✅ Test $app completed successfully" else - echo "❌ Test $app failed after $attempt attempts" + echo "::endgroup::" + echo "❌ Test $app failed (no retry — see diagnostics below)" failed_tests="$failed_tests $app" + echo "::group::Diagnostics for $app" + kubectl get hr -A -o wide 2>&1 | tail -50 || true + kubectl get events -A --sort-by=.lastTimestamp 2>&1 | tail -30 || true + echo "::endgroup::" fi - echo "::endgroup::" done if [ -n "$failed_tests" ]; then echo "❌ Failed tests:$failed_tests" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6492b92d..ac0f7e30 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,17 +1,6 @@ repos: - repo: local hooks: - - id: run-make-generate-root - name: Run 'make generate' at repo root - entry: | - flock -x .git/pre-commit.lock sh -c ' - echo "Running make generate at repo root" - make generate || exit $? - git diff --color=always | cat - ' - language: system - files: ^(api/|pkg/apis/|pkg/generated/|internal/crdinstall/manifests/|packages/system/cozystack-controller/definitions/|packages/system/application-definition-crd/definition/|packages/system/backup-controller/definitions/|packages/system/backupstrategy-controller/definitions/|hack/update-codegen\.sh$|hack/boilerplate\.go\.txt$|Makefile$) - pass_filenames: false - id: run-make-generate name: Run 'make generate' in all app directories entry: | diff --git a/Makefile b/Makefile index acc9b6f6..10a55314 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,6 @@ build: build-deps make -C packages/system/lineage-controller-webhook image make -C packages/system/cilium image make -C packages/system/linstor image - make -C packages/system/linstor-gui image make -C packages/system/kubeovn-webhook image make -C packages/system/kubeovn-plunger image make -C packages/system/dashboard image diff --git a/api/apps/v1alpha1/kubernetes/types.go b/api/apps/v1alpha1/kubernetes/types.go index 88a3c97e..ea4a2b64 100644 --- a/api/apps/v1alpha1/kubernetes/types.go +++ b/api/apps/v1alpha1/kubernetes/types.go @@ -69,9 +69,6 @@ type Addons struct { // NVIDIA GPU Operator. // +kubebuilder:default:={} GpuOperator GPUOperatorAddon `json:"gpuOperator"` - // HAMi GPU virtualization middleware. - // +kubebuilder:default:={} - Hami HAMiAddon `json:"hami"` // Ingress-NGINX controller. // +kubebuilder:default:={} IngressNginx IngressNginxAddon `json:"ingressNginx"` @@ -163,15 +160,6 @@ type GatewayAPIAddon struct { Enabled bool `json:"enabled"` } -type HAMiAddon struct { - // Enable HAMi (requires GPU Operator). - // +kubebuilder:default:=false - Enabled bool `json:"enabled"` - // Custom Helm values overrides. - // +kubebuilder:default:={} - ValuesOverride k8sRuntime.RawExtension `json:"valuesOverride"` -} - type Images struct { // Image used by the wait-for-kubeconfig init container. Empty falls back to images/busybox.tag. // +kubebuilder:default:="" diff --git a/api/apps/v1alpha1/kubernetes/zz_generated.deepcopy.go b/api/apps/v1alpha1/kubernetes/zz_generated.deepcopy.go index e021fbaa..2cbe3ba1 100644 --- a/api/apps/v1alpha1/kubernetes/zz_generated.deepcopy.go +++ b/api/apps/v1alpha1/kubernetes/zz_generated.deepcopy.go @@ -49,7 +49,6 @@ func (in *Addons) DeepCopyInto(out *Addons) { in.Fluxcd.DeepCopyInto(&out.Fluxcd) out.GatewayAPI = in.GatewayAPI in.GpuOperator.DeepCopyInto(&out.GpuOperator) - in.Hami.DeepCopyInto(&out.Hami) in.IngressNginx.DeepCopyInto(&out.IngressNginx) in.MonitoringAgents.DeepCopyInto(&out.MonitoringAgents) in.Velero.DeepCopyInto(&out.Velero) @@ -136,7 +135,6 @@ func (in *ConfigSpec) DeepCopyInto(out *ConfigSpec) { } in.Addons.DeepCopyInto(&out.Addons) in.ControlPlane.DeepCopyInto(&out.ControlPlane) - out.Images = in.Images } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigSpec. @@ -262,37 +260,6 @@ func (in *GatewayAPIAddon) DeepCopy() *GatewayAPIAddon { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HAMiAddon) DeepCopyInto(out *HAMiAddon) { - *out = *in - in.ValuesOverride.DeepCopyInto(&out.ValuesOverride) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HAMiAddon. -func (in *HAMiAddon) DeepCopy() *HAMiAddon { - if in == nil { - return nil - } - out := new(HAMiAddon) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Images) DeepCopyInto(out *Images) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Images. -func (in *Images) DeepCopy() *Images { - if in == nil { - return nil - } - out := new(Images) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IngressNginxAddon) DeepCopyInto(out *IngressNginxAddon) { *out = *in diff --git a/api/apps/v1alpha1/postgresql/types.go b/api/apps/v1alpha1/postgresql/types.go index a2ff77a8..fa85d51f 100644 --- a/api/apps/v1alpha1/postgresql/types.go +++ b/api/apps/v1alpha1/postgresql/types.go @@ -92,9 +92,6 @@ type Bootstrap struct { // Timestamp (RFC3339) for point-in-time recovery; empty means latest. // +kubebuilder:default:="" RecoveryTime string `json:"recoveryTime,omitempty"` - // Barman server name (S3 path prefix) used by the original cluster when writing backups. Set this only when the original cluster had an explicit barmanObjectStore.serverName that differed from its Kubernetes resource name. - // +kubebuilder:default:="" - ServerName string `json:"serverName,omitempty"` } type Database struct { diff --git a/api/apps/v1alpha1/vminstance/types.go b/api/apps/v1alpha1/vminstance/types.go index 9c48724a..2bb059c6 100644 --- a/api/apps/v1alpha1/vminstance/types.go +++ b/api/apps/v1alpha1/vminstance/types.go @@ -26,9 +26,6 @@ type ConfigSpec struct { // Ports to forward from outside the cluster. // +kubebuilder:default:={22} ExternalPorts []int `json:"externalPorts,omitempty"` - // Whether to accept ICMP traffic to the VM in PortList mode (preserves ping and PMTU discovery). No effect in WholeIP mode. Default true so ping behaves as users expect even when port filtering is in effect. - // +kubebuilder:default:=true - ExternalAllowICMP bool `json:"externalAllowICMP"` // Requested running state of the VirtualMachineInstance // +kubebuilder:default:="Always" RunStrategy RunStrategy `json:"runStrategy"` diff --git a/api/backups/v1alpha1/zz_generated.deepcopy.go b/api/backups/v1alpha1/zz_generated.deepcopy.go index 61b8f839..89f6171f 100644 --- a/api/backups/v1alpha1/zz_generated.deepcopy.go +++ b/api/backups/v1alpha1/zz_generated.deepcopy.go @@ -620,11 +620,6 @@ func (in *RestoreJobSpec) DeepCopyInto(out *RestoreJobSpec) { *out = new(v1.TypedLocalObjectReference) (*in).DeepCopyInto(*out) } - if in.Options != nil { - in, out := &in.Options, &out.Options - *out = new(runtime.RawExtension) - (*in).DeepCopyInto(*out) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreJobSpec. diff --git a/cmd/cozystack-operator/main.go b/cmd/cozystack-operator/main.go index e215b779..08f7a860 100644 --- a/cmd/cozystack-operator/main.go +++ b/cmd/cozystack-operator/main.go @@ -83,6 +83,11 @@ func main() { var disableTelemetry bool var telemetryEndpoint string var telemetryInterval string + var helmReleaseInterval string + var helmReleaseRetryInterval string + var helmReleaseInstallTimeout string + var helmReleaseUpgradeTimeout string + var helmReleaseMaxHistory int var cozyValuesSecretName string var cozyValuesSecretNamespace string var cozyValuesNamespaceSelector string @@ -107,6 +112,28 @@ func main() { "Endpoint for sending telemetry data") flag.StringVar(&telemetryInterval, "telemetry-interval", "15m", "Interval between telemetry data collection (e.g. 15m, 1h)") + flag.StringVar(&helmReleaseInterval, "helmrelease-interval", "5m", + "Reconcile interval applied to HelmReleases created by the Package reconciler. "+ + "Lower values speed up dependency-blocked retries (e.g. during E2E install) at the cost of "+ + "controller load. Production default 5m matches existing behaviour.") + flag.StringVar(&helmReleaseRetryInterval, "helmrelease-retry-interval", "30s", + "Retry interval applied to Install.Strategy and Upgrade.Strategy of HelmReleases created "+ + "by the Package reconciler. With Strategy.Name=RetryOnFailure, this controls how long the "+ + "controller waits between failed install/upgrade attempts. Decoupled from --helmrelease-interval "+ + "(which is the healthy reconcile cadence) so failures recover fast without polling healthy "+ + "releases at the same fast cadence.") + flag.StringVar(&helmReleaseInstallTimeout, "helmrelease-install-timeout", "10m", + "Timeout for the Helm install action of HelmReleases created by the Package reconciler "+ + "(Spec.Install.Timeout). Bounds how long an individual Kubernetes operation (Job/hook/wait) "+ + "may take during install.") + flag.StringVar(&helmReleaseUpgradeTimeout, "helmrelease-upgrade-timeout", "10m", + "Timeout for the Helm upgrade action of HelmReleases created by the Package reconciler "+ + "(Spec.Upgrade.Timeout). Bounds how long an individual Kubernetes operation (Job/hook/wait) "+ + "may take during upgrade.") + flag.IntVar(&helmReleaseMaxHistory, "helmrelease-max-history", 5, + "Number of release revisions Helm keeps for HelmReleases created by the Package reconciler "+ + "(Spec.MaxHistory). 0 means unlimited; 5 matches Helm's default. Lower values reduce "+ + "per-release Secret accumulation in clusters that bounce HRs frequently (e.g. E2E sandboxes).") flag.StringVar(&platformSourceURL, "platform-source-url", "", "Platform source URL (oci:// or https://). If specified, generates OCIRepository or GitRepository resource.") flag.StringVar(&platformSourceName, "platform-source-name", "cozystack-platform", "Name for the generated platform source resource and PackageSource") flag.StringVar(&platformSourceRef, "platform-source-ref", "", "Reference specification as key=value pairs (e.g., 'branch=main' or 'digest=sha256:...,tag=v1.0'). For OCI: digest, semver, semverFilter, tag. For Git: branch, tag, semver, name, commit.") @@ -122,6 +149,31 @@ func main() { ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + hrIntervalDuration, err := time.ParseDuration(helmReleaseInterval) + if err != nil { + setupLog.Error(err, "invalid --helmrelease-interval value", "value", helmReleaseInterval) + os.Exit(1) + } + hrRetryIntervalDuration, err := time.ParseDuration(helmReleaseRetryInterval) + if err != nil { + setupLog.Error(err, "invalid --helmrelease-retry-interval value", "value", helmReleaseRetryInterval) + os.Exit(1) + } + hrInstallTimeoutDuration, err := time.ParseDuration(helmReleaseInstallTimeout) + if err != nil { + setupLog.Error(err, "invalid --helmrelease-install-timeout value", "value", helmReleaseInstallTimeout) + os.Exit(1) + } + hrUpgradeTimeoutDuration, err := time.ParseDuration(helmReleaseUpgradeTimeout) + if err != nil { + setupLog.Error(err, "invalid --helmrelease-upgrade-timeout value", "value", helmReleaseUpgradeTimeout) + os.Exit(1) + } + if helmReleaseMaxHistory < 0 { + setupLog.Error(fmt.Errorf("--helmrelease-max-history must be >= 0"), "invalid value", "value", helmReleaseMaxHistory) + os.Exit(1) + } + config := ctrl.GetConfigOrDie() // Create a direct client (without cache) for pre-start operations @@ -258,8 +310,13 @@ func main() { // Setup Package reconciler if err := (&operator.PackageReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + HelmReleaseInterval: hrIntervalDuration, + HelmReleaseRetryInterval: hrRetryIntervalDuration, + HelmReleaseInstallTimeout: hrInstallTimeoutDuration, + HelmReleaseUpgradeTimeout: hrUpgradeTimeoutDuration, + HelmReleaseMaxHistory: helmReleaseMaxHistory, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Package") os.Exit(1) diff --git a/dashboards/gpu/gpu-efficiency.json b/dashboards/gpu/gpu-efficiency.json deleted file mode 100644 index 28a24568..00000000 --- a/dashboards/gpu/gpu-efficiency.json +++ /dev/null @@ -1,819 +0,0 @@ -{ - "uid": "gpu-efficiency", - "title": "GPU Efficiency Score", - "description": "Tensor saturation, util/watt and throttling — reveals inefficient GPU workloads", - "tags": [ - "gpu", - "efficiency", - "finops" - ], - "timezone": "browser", - "editable": true, - "graphTooltip": 1, - "time": { - "from": "now-1h", - "to": "now" - }, - "fiscalYearStartMonth": 0, - "schemaVersion": 42, - "panels": [ - { - "type": "row", - "collapsed": false, - "title": "Overall efficiency metrics", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 1, - "panels": [] - }, - { - "type": "stat", - "id": 2, - "targets": [ - { - "expr": "avg(gpu:tensor_saturation:avg5m) * 100", - "refId": "A" - } - ], - "title": "Avg Tensor Saturation", - "description": "Mean tensor core saturation across all GPUs. \u003c10% means GPUs are used inefficiently (workloads could move to CPU or optimize their code). Cluster-wide — DCGM metrics cannot be attributed to workload namespaces.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 5, - "w": 8, - "x": 0, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "area", - "colorMode": "background", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "percent", - "min": 0, - "max": 100, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "red" - }, - { - "value": 10, - "color": "orange" - }, - { - "value": 30, - "color": "yellow" - }, - { - "value": 60, - "color": "green" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 3, - "targets": [ - { - "expr": "avg(gpu:util_per_watt:avg5m)", - "refId": "A" - } - ], - "title": "Avg Utilization per Watt", - "description": "NVML utilization % per watt across all GPUs. Higher value = more efficient workload. Cluster-wide — DCGM metrics cannot be attributed to workload namespaces.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 5, - "w": 8, - "x": 8, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "area", - "colorMode": "background", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "none", - "decimals": 3, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "red" - }, - { - "value": 0.5, - "color": "yellow" - }, - { - "value": 1, - "color": "green" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 4, - "targets": [ - { - "expr": "avg(gpu:power_throttle_fraction:rate5m)", - "refId": "A" - } - ], - "title": "Avg Power Throttling", - "description": "Fraction of time GPUs hit the TDP cap and lose performance. \u003e5% means tenants underutilize billed FLOPS. Cluster-wide — rule aggregates by (Hostname, gpu, UUID) and drops namespace label.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 5, - "w": 8, - "x": 16, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "area", - "colorMode": "background", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "percentunit", - "decimals": 2, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 0.05, - "color": "yellow" - }, - { - "value": 0.2, - "color": "red" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "row", - "collapsed": false, - "title": "NVML vs Tensor (mismatch detector)", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 6 - }, - "id": 10, - "panels": [] - }, - { - "type": "timeseries", - "id": 11, - "targets": [ - { - "expr": "DCGM_FI_DEV_GPU_UTIL", - "legendFormat": "{{Hostname}}/{{gpu}}", - "refId": "A" - } - ], - "title": "NVML GPU Utilization", - "description": "Classic utilization metric. Shows activity of any engine (SM, copy, encoder). Cluster-wide — DCGM namespace is the exporter's own namespace, not the workload namespace.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 7 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "percent", - "min": 0, - "max": 100, - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 10, - "showPoints": "never" - } - }, - "overrides": [] - } - }, - { - "type": "timeseries", - "id": 12, - "targets": [ - { - "expr": "DCGM_FI_PROF_PIPE_TENSOR_ACTIVE * 100", - "legendFormat": "{{Hostname}}/{{gpu}}", - "refId": "A" - } - ], - "title": "Tensor Pipe Active", - "description": "Real tensor core load (HMMA). For AI/LLM inference it should be ≥20%, otherwise the workload is unoptimized. Cluster-wide — DCGM namespace is the exporter's own namespace, not the workload namespace.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 7 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "percent", - "min": 0, - "max": 100, - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 10, - "showPoints": "never" - } - }, - "overrides": [] - } - }, - { - "type": "row", - "collapsed": false, - "title": "Per-GPU ranking", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 15 - }, - "id": 20, - "panels": [] - }, - { - "type": "table", - "id": 21, - "targets": [ - { - "expr": "topk(20, gpu:tensor_saturation:avg5m * 100)", - "instant": true, - "range": false, - "format": "table", - "refId": "A" - } - ], - "title": "Tensor Saturation per GPU (5m avg)", - "description": "Which GPUs are exercising tensor cores and which are not. Sorted descending. Grouped by Hostname/gpu/UUID — DCGM metrics cannot be attributed to workload namespaces.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 16 - }, - "repeatDirection": "h", - "transformations": [ - { - "id": "organize", - "options": { - "excludeByName": { - "DCGM_FI_DRIVER_VERSION": true, - "Time": true, - "__name__": true, - "cluster": true, - "container": true, - "device": true, - "endpoint": true, - "gpu_driver_version": true, - "instance": true, - "job": true, - "modelName": true, - "namespace": true, - "pci_bus_id": true, - "pod": true, - "prometheus": true, - "service": true, - "tenant": true, - "tier": true, - "uid": true, - "unit": true - }, - "indexByName": { - "Hostname": 0, - "UUID": 2, - "Value": 3, - "gpu": 1 - }, - "renameByName": { - "Hostname": "Node", - "UUID": "UUID", - "Value": "Saturation", - "gpu": "GPU" - } - } - } - ], - "options": { - "frameIndex": 0, - "showHeader": true, - "showTypeIcons": false, - "sortBy": [ - { - "displayName": "Saturation", - "desc": true - } - ], - "footer": { - "show": false, - "reducer": [] - }, - "cellHeight": "sm" - }, - "fieldConfig": { - "defaults": {}, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Saturation" - }, - "properties": [ - { - "id": "unit", - "value": "percent" - }, - { - "id": "min", - "value": 0 - }, - { - "id": "max", - "value": 100 - }, - { - "id": "custom.cellOptions", - "value": { - "mode": "gradient", - "type": "gauge" - } - }, - { - "id": "thresholds", - "value": { - "mode": "absolute", - "steps": [ - { - "color": "red" - }, - { - "color": "orange", - "value": 10 - }, - { - "color": "yellow", - "value": 30 - }, - { - "color": "green", - "value": 60 - } - ] - } - } - ] - } - ] - } - }, - { - "type": "table", - "id": 22, - "targets": [ - { - "expr": "topk(20, gpu:util_per_watt:avg5m)", - "instant": true, - "range": false, - "format": "table", - "refId": "A" - } - ], - "title": "Utilization / Watt per GPU (5m avg)", - "description": "How efficiently each GPU spends watts. Low value = poor optimization. Grouped by Hostname/gpu/UUID — DCGM metrics cannot be attributed to workload namespaces.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 16 - }, - "repeatDirection": "h", - "transformations": [ - { - "id": "organize", - "options": { - "excludeByName": { - "DCGM_FI_DRIVER_VERSION": true, - "Time": true, - "__name__": true, - "cluster": true, - "container": true, - "device": true, - "endpoint": true, - "gpu_driver_version": true, - "instance": true, - "job": true, - "modelName": true, - "namespace": true, - "pci_bus_id": true, - "pod": true, - "prometheus": true, - "service": true, - "tenant": true, - "tier": true, - "uid": true, - "unit": true - }, - "indexByName": { - "Hostname": 0, - "UUID": 2, - "Value": 3, - "gpu": 1 - }, - "renameByName": { - "Hostname": "Node", - "UUID": "UUID", - "Value": "Util/Watt", - "gpu": "GPU" - } - } - } - ], - "options": { - "frameIndex": 0, - "showHeader": true, - "showTypeIcons": false, - "sortBy": [ - { - "displayName": "Util/Watt", - "desc": true - } - ], - "footer": { - "show": false, - "reducer": [] - }, - "cellHeight": "sm" - }, - "fieldConfig": { - "defaults": {}, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Util/Watt" - }, - "properties": [ - { - "id": "unit", - "value": "none" - }, - { - "id": "decimals", - "value": 3 - }, - { - "id": "min", - "value": 0 - }, - { - "id": "max", - "value": 1.5 - }, - { - "id": "custom.cellOptions", - "value": { - "mode": "gradient", - "type": "gauge" - } - }, - { - "id": "thresholds", - "value": { - "mode": "absolute", - "steps": [ - { - "color": "red" - }, - { - "color": "yellow", - "value": 0.5 - }, - { - "color": "green", - "value": 1 - } - ] - } - } - ] - } - ] - } - }, - { - "type": "row", - "collapsed": false, - "title": "Throttling", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 24 - }, - "id": 30, - "panels": [] - }, - { - "type": "timeseries", - "id": 31, - "targets": [ - { - "expr": "gpu:power_throttle_fraction:rate5m", - "legendFormat": "{{Hostname}}/{{gpu}}", - "refId": "A" - } - ], - "title": "Power throttle fraction per GPU", - "description": "Fraction of time the GPU was power-throttled. 1.0 = always throttled. Cluster-wide — rule aggregates by (Hostname, gpu, UUID) and drops namespace label.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 25 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "percentunit", - "min": 0, - "max": 1, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 0.05, - "color": "yellow" - }, - { - "value": 0.2, - "color": "red" - } - ] - }, - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 25, - "showPoints": "never" - } - }, - "overrides": [] - } - }, - { - "type": "timeseries", - "id": 32, - "targets": [ - { - "expr": "gpu:thermal_throttle_fraction:rate5m", - "legendFormat": "{{Hostname}}/{{gpu}}", - "refId": "A" - } - ], - "title": "Thermal throttle fraction per GPU", - "description": "Fraction of time the GPU was thermal-throttled. Cluster-wide — rule aggregates by (Hostname, gpu, UUID) and drops namespace label.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 25 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "percentunit", - "min": 0, - "max": 1, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 0.05, - "color": "yellow" - }, - { - "value": 0.2, - "color": "red" - } - ] - }, - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 25, - "showPoints": "never" - } - }, - "overrides": [] - } - } - ], - "templating": { - "list": [ - { - "type": "datasource", - "name": "ds_prometheus", - "label": "Prometheus", - "skipUrlSync": false, - "query": "prometheus", - "current": { - "selected": false, - "text": "default", - "value": "default" - }, - "multi": false, - "allowCustomValue": true, - "includeAll": false, - "regex": "", - "auto": false, - "auto_min": "10s", - "auto_count": 30 - } - ] - }, - "annotations": {} -} diff --git a/dashboards/gpu/gpu-fleet.json b/dashboards/gpu/gpu-fleet.json deleted file mode 100644 index 60c12bfb..00000000 --- a/dashboards/gpu/gpu-fleet.json +++ /dev/null @@ -1,1157 +0,0 @@ -{ - "uid": "gpu-fleet", - "title": "GPU Fleet Overview", - "description": "Cluster-wide GPU inventory, capacity, utilization and health — admin view across the whole fleet.", - "tags": [ - "gpu", - "fleet", - "admin" - ], - "timezone": "browser", - "editable": true, - "graphTooltip": 1, - "time": { - "from": "now-6h", - "to": "now" - }, - "fiscalYearStartMonth": 0, - "schemaVersion": 42, - "panels": [ - { - "type": "row", - "collapsed": false, - "title": "Cluster snapshot", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 1, - "panels": [] - }, - { - "type": "stat", - "id": 2, - "targets": [ - { - "expr": "cluster:gpu_count:total", - "refId": "A" - } - ], - "title": "Total GPUs", - "description": "Physical GPUs discovered by DCGM across all nodes.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 0, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "value", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "blue" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 3, - "targets": [ - { - "expr": "cluster:gpu_count:allocated", - "refId": "A" - } - ], - "title": "Allocated", - "description": "Sum of kube GPU requests across all pods. Billable view — includes Pending pods.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 4, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "background", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "blue" - }, - { - "value": 1, - "color": "green" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 4, - "targets": [ - { - "expr": "cluster:gpu_count:free", - "refId": "A" - } - ], - "title": "Free", - "description": "Unallocated capacity available for new tenants.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 8, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "background", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "red" - }, - { - "value": 1, - "color": "yellow" - }, - { - "value": 2, - "color": "green" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 5, - "targets": [ - { - "expr": "count(count by (Hostname) (DCGM_FI_DEV_GPU_UTIL))", - "refId": "A" - } - ], - "title": "Nodes with GPU", - "description": "Nodes reporting at least one GPU to DCGM. Counted via DCGM_FI_DEV_GPU_UTIL because kube-state-metrics does not expose node labels by default.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 12, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "value", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "blue" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 6, - "targets": [ - { - "expr": "cluster:gpu_util:avg", - "refId": "A" - } - ], - "title": "Average utilization", - "description": "Legacy NVML utilization across all tenant GPUs.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 16, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "area", - "colorMode": "value", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "percent", - "min": 0, - "max": 100, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "blue" - }, - { - "value": 30, - "color": "green" - }, - { - "value": 80, - "color": "orange" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 7, - "targets": [ - { - "expr": "cluster:gpu_power_watts:sum", - "refId": "A" - } - ], - "title": "Total power", - "description": "Live power draw summed across the fleet.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 20, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "area", - "colorMode": "value", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "watt", - "decimals": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "row", - "collapsed": false, - "title": "Per-node GPU activity", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 5 - }, - "id": 10, - "panels": [] - }, - { - "type": "bargauge", - "id": 11, - "targets": [ - { - "expr": "avg by (Hostname) (DCGM_FI_DEV_GPU_UTIL)", - "legendFormat": "{{Hostname}}", - "refId": "A" - } - ], - "title": "GPU utilization per node", - "description": "Average NVML utilization per node — spot idle or saturated hosts at a glance.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 6 - }, - "repeatDirection": "h", - "options": { - "displayMode": "gradient", - "valueMode": "color", - "namePlacement": "auto", - "showUnfilled": true, - "sizing": "auto", - "minVizWidth": 8, - "minVizHeight": 16, - "legend": { - "displayMode": "list", - "placement": "bottom", - "showLegend": false, - "calcs": [] - }, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "maxVizHeight": 300, - "orientation": "horizontal" - }, - "fieldConfig": { - "defaults": { - "unit": "percent", - "min": 0, - "max": 100, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "blue" - }, - { - "value": 30, - "color": "green" - }, - { - "value": 80, - "color": "orange" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "bargauge", - "id": 12, - "targets": [ - { - "expr": "sum by (Hostname) (DCGM_FI_DEV_POWER_USAGE) / clamp_min(sum by (Hostname) (DCGM_FI_DEV_POWER_MGMT_LIMIT), 1) * 100", - "legendFormat": "{{Hostname}}", - "refId": "A" - } - ], - "title": "Power draw per node (% of TDP)", - "description": "GPU power usage as a fraction of the node's combined TDP cap. Works across GPU SKUs without per-model thresholds. Requires DCGM_FI_DEV_POWER_MGMT_LIMIT from custom DCGM CSV (see packages/system/gpu-operator/examples/dcgm-custom-metrics.yaml).", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 6 - }, - "repeatDirection": "h", - "options": { - "displayMode": "gradient", - "valueMode": "color", - "namePlacement": "auto", - "showUnfilled": true, - "sizing": "auto", - "minVizWidth": 8, - "minVizHeight": 16, - "legend": { - "displayMode": "list", - "placement": "bottom", - "showLegend": false, - "calcs": [] - }, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "maxVizHeight": 300, - "orientation": "horizontal" - }, - "fieldConfig": { - "defaults": { - "unit": "percent", - "min": 0, - "max": 100, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 60, - "color": "yellow" - }, - { - "value": 80, - "color": "orange" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "row", - "collapsed": false, - "title": "Usage trends", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 14 - }, - "id": 20, - "panels": [] - }, - { - "type": "timeseries", - "id": 21, - "targets": [ - { - "expr": "avg by (Hostname) (DCGM_FI_DEV_GPU_UTIL)", - "legendFormat": "{{Hostname}}", - "refId": "A" - } - ], - "title": "GPU utilization per node (trend)", - "description": "Per-node NVML utilization over time — shows shifting workload across hosts.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 15 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "percent", - "min": 0, - "max": 100, - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 20, - "showPoints": "never" - } - }, - "overrides": [] - } - }, - { - "type": "timeseries", - "id": 22, - "targets": [ - { - "expr": "sum by (Hostname) (DCGM_FI_DEV_POWER_USAGE)", - "legendFormat": "{{Hostname}}", - "refId": "A" - } - ], - "title": "Power draw per node (trend)", - "description": "Per-node GPU power consumption over time.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 15 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "watt", - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 20, - "showPoints": "never" - } - }, - "overrides": [] - } - }, - { - "type": "row", - "collapsed": false, - "title": "Health", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 23 - }, - "id": 30, - "panels": [] - }, - { - "type": "stat", - "id": 31, - "targets": [ - { - "expr": "count((max by (Hostname, gpu) (DCGM_FI_DEV_XID_ERRORS)) \u003e 0) or vector(0)", - "refId": "A" - } - ], - "title": "GPUs with XID errors", - "description": "Count of GPUs reporting a non-zero XID code. XID = NVIDIA hardware/driver error — any non-zero value needs investigation.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 0, - "y": 24 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "background", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 1, - "color": "red" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 32, - "targets": [ - { - "expr": "avg(gpu:power_throttle_fraction:rate5m)", - "refId": "A" - } - ], - "title": "Power throttling (cluster avg)", - "description": "Fraction of time the fleet spent power-throttled. \u003e5% = billed FLOPS are being underdelivered.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 6, - "y": 24 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "background", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "percentunit", - "decimals": 2, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 0.05, - "color": "yellow" - }, - { - "value": 0.2, - "color": "red" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 33, - "targets": [ - { - "expr": "avg(gpu:thermal_throttle_fraction:rate5m)", - "refId": "A" - } - ], - "title": "Thermal throttling (cluster avg)", - "description": "Fraction of time the fleet spent thermal-throttled. Indicates cooling/datacenter issues.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 12, - "y": 24 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "background", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "percentunit", - "decimals": 2, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 0.05, - "color": "yellow" - }, - { - "value": 0.2, - "color": "red" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 34, - "targets": [ - { - "expr": "max(DCGM_FI_DEV_GPU_TEMP)", - "refId": "A" - } - ], - "title": "Max GPU temperature", - "description": "Hottest GPU in the fleet right now. Adjust thresholds to match your GPU model specifications.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 18, - "y": 24 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "background", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "celsius", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 75, - "color": "yellow" - }, - { - "value": 85, - "color": "red" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "timeseries", - "id": 35, - "targets": [ - { - "expr": "DCGM_FI_DEV_GPU_TEMP", - "legendFormat": "{{Hostname}}/{{gpu}}", - "refId": "A" - } - ], - "title": "Temperature per GPU", - "description": "Per-GPU temperature trace. Sustained red zone = cooling issue or aggressive workload.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 29 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "celsius", - "min": 20, - "max": 100, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 75, - "color": "yellow" - }, - { - "value": 85, - "color": "red" - } - ] - }, - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 10, - "showPoints": "never" - } - }, - "overrides": [] - } - }, - { - "type": "timeseries", - "id": 36, - "targets": [ - { - "expr": "gpu:power_throttle_fraction:rate5m", - "legendFormat": "power {{Hostname}}/{{gpu}}", - "refId": "A" - }, - { - "expr": "gpu:thermal_throttle_fraction:rate5m", - "legendFormat": "thermal {{Hostname}}/{{gpu}}", - "refId": "B" - } - ], - "title": "Throttle fraction per GPU", - "description": "Red = power throttle, blue = thermal throttle. Both on same chart to compare causes.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 29 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "percentunit", - "min": 0, - "max": 1, - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 25, - "showPoints": "never" - } - }, - "overrides": [] - } - }, - { - "type": "row", - "collapsed": false, - "title": "Hardware inventory", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 37 - }, - "id": 40, - "panels": [] - }, - { - "type": "table", - "id": 41, - "targets": [ - { - "expr": "group by (Hostname, modelName, gpu, UUID) (DCGM_FI_DEV_GPU_UTIL)", - "instant": true, - "range": false, - "format": "table", - "refId": "A" - } - ], - "title": "GPU inventory", - "description": "One row per physical GPU. Model / index / UUID sourced from DCGM labels.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 38 - }, - "repeatDirection": "h", - "transformations": [ - { - "id": "organize", - "options": { - "excludeByName": { - "Time": true, - "Value": true, - "__name__": true, - "cluster": true, - "container": true, - "device": true, - "endpoint": true, - "instance": true, - "job": true, - "pci_bus_id": true, - "prometheus": true, - "service": true, - "tenant": true, - "tier": true, - "uid": true, - "unit": true - }, - "indexByName": { - "Hostname": 0, - "UUID": 3, - "gpu": 2, - "modelName": 1 - }, - "renameByName": { - "Hostname": "Node", - "UUID": "UUID", - "gpu": "Index", - "modelName": "GPU Model" - } - } - } - ], - "options": { - "frameIndex": 0, - "showHeader": true, - "showTypeIcons": false, - "footer": { - "show": false, - "reducer": [] - }, - "cellHeight": "sm" - } - } - ], - "templating": { - "list": [ - { - "type": "datasource", - "name": "ds_prometheus", - "label": "Prometheus", - "skipUrlSync": false, - "query": "prometheus", - "current": { - "selected": false, - "text": "default", - "value": "default" - }, - "multi": false, - "allowCustomValue": true, - "includeAll": false, - "regex": "", - "auto": false, - "auto_min": "10s", - "auto_count": 30 - } - ] - }, - "annotations": {} -} diff --git a/dashboards/gpu/gpu-performance.json b/dashboards/gpu/gpu-performance.json deleted file mode 100644 index aed9c69a..00000000 --- a/dashboards/gpu/gpu-performance.json +++ /dev/null @@ -1,957 +0,0 @@ -{ - "uid": "gpu-performance", - "title": "GPU Performance", - "tags": [ - "gpu", - "dcgm" - ], - "timezone": "browser", - "editable": true, - "graphTooltip": 1, - "time": { - "from": "now-1h", - "to": "now" - }, - "fiscalYearStartMonth": 0, - "schemaVersion": 42, - "panels": [ - { - "type": "row", - "collapsed": false, - "title": "Overview", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 1, - "panels": [] - }, - { - "type": "stat", - "id": 2, - "targets": [ - { - "expr": "cluster:gpu_count:total", - "refId": "A" - } - ], - "title": "Total GPUs", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 0, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "value", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "blue" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 3, - "targets": [ - { - "expr": "cluster:gpu_count:allocated", - "refId": "A" - } - ], - "title": "Allocated", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 6, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "value", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 1, - "color": "yellow" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 4, - "targets": [ - { - "expr": "cluster:gpu_util:avg", - "refId": "A" - } - ], - "title": "Average utilization", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 12, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "area", - "colorMode": "value", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "percent", - "min": 0, - "max": 100, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "blue" - }, - { - "value": 30, - "color": "green" - }, - { - "value": 80, - "color": "orange" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 5, - "targets": [ - { - "expr": "cluster:gpu_power_watts:sum", - "refId": "A" - } - ], - "title": "Power draw", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 18, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "area", - "colorMode": "value", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "watt", - "decimals": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "row", - "collapsed": false, - "title": "Utilization", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 5 - }, - "id": 10, - "panels": [] - }, - { - "type": "timeseries", - "id": 11, - "targets": [ - { - "expr": "DCGM_FI_DEV_GPU_UTIL{Hostname=~\"$Hostname\"}", - "legendFormat": "{{Hostname}}/{{gpu}} {{pod}}", - "refId": "A" - } - ], - "title": "GPU Utilization (NVML)", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 6 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "percent", - "min": 0, - "max": 100, - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 10, - "showPoints": "never", - "spanNulls": false - } - }, - "overrides": [] - } - }, - { - "type": "timeseries", - "id": 12, - "targets": [ - { - "expr": "DCGM_FI_PROF_PIPE_TENSOR_ACTIVE{Hostname=~\"$Hostname\"} * 100", - "legendFormat": "{{Hostname}}/{{gpu}} {{pod}}", - "refId": "A" - } - ], - "title": "Tensor Pipe Active (realistic load for LLM/AI)", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 6 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "percent", - "min": 0, - "max": 100, - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 10, - "showPoints": "never", - "spanNulls": false - } - }, - "overrides": [] - } - }, - { - "type": "timeseries", - "id": 13, - "targets": [ - { - "expr": "DCGM_FI_PROF_GR_ENGINE_ACTIVE{Hostname=~\"$Hostname\"} * 100", - "legendFormat": "{{Hostname}}/{{gpu}} {{pod}}", - "refId": "A" - } - ], - "title": "Graphics Engine Active", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 14 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "percent", - "min": 0, - "max": 100, - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 10, - "showPoints": "never", - "spanNulls": false - } - }, - "overrides": [] - } - }, - { - "type": "timeseries", - "id": 14, - "targets": [ - { - "expr": "DCGM_FI_DEV_MEM_COPY_UTIL{Hostname=~\"$Hostname\"}", - "legendFormat": "{{Hostname}}/{{gpu}} {{pod}}", - "refId": "A" - } - ], - "title": "Memory Copy Utilization", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 14 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "percent", - "min": 0, - "max": 100, - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 10, - "showPoints": "never", - "spanNulls": false - } - }, - "overrides": [] - } - }, - { - "type": "row", - "collapsed": false, - "title": "Memory", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 22 - }, - "id": 20, - "panels": [] - }, - { - "type": "timeseries", - "id": 21, - "targets": [ - { - "expr": "DCGM_FI_DEV_FB_USED{Hostname=~\"$Hostname\"} * 1048576", - "legendFormat": "{{Hostname}}/{{gpu}} {{pod}}", - "refId": "A" - } - ], - "title": "VRAM Used", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 23 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "bytes", - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 25, - "showPoints": "never" - } - }, - "overrides": [] - } - }, - { - "type": "timeseries", - "id": 22, - "targets": [ - { - "expr": "DCGM_FI_DEV_FB_FREE{Hostname=~\"$Hostname\"} * 1048576", - "legendFormat": "{{Hostname}}/{{gpu}}", - "refId": "A" - } - ], - "title": "VRAM Free", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 23 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "min" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "bytes", - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 10, - "showPoints": "never" - } - }, - "overrides": [] - } - }, - { - "type": "row", - "collapsed": false, - "title": "Power \u0026 Temperature", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 31 - }, - "id": 30, - "panels": [] - }, - { - "type": "timeseries", - "id": 31, - "targets": [ - { - "expr": "DCGM_FI_DEV_POWER_USAGE{Hostname=~\"$Hostname\"}", - "legendFormat": "{{Hostname}}/{{gpu}}", - "refId": "A" - } - ], - "title": "Power Usage per GPU", - "description": "Hardware-level metric — shown cluster-wide regardless of namespace filter.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 32 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "watt", - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 10, - "showPoints": "never" - } - }, - "overrides": [] - } - }, - { - "type": "timeseries", - "id": 32, - "targets": [ - { - "expr": "DCGM_FI_DEV_GPU_TEMP{Hostname=~\"$Hostname\"}", - "legendFormat": "{{Hostname}}/{{gpu}}", - "refId": "A" - } - ], - "title": "GPU Temperature", - "description": "Hardware-level metric — shown cluster-wide regardless of namespace filter.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 32 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "celsius", - "min": 20, - "max": 100, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 75, - "color": "yellow" - }, - { - "value": 85, - "color": "red" - } - ] - }, - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 10, - "showPoints": "never" - } - }, - "overrides": [] - } - }, - { - "type": "row", - "collapsed": false, - "title": "Health", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 40 - }, - "id": 40, - "panels": [] - }, - { - "type": "stat", - "id": 41, - "targets": [ - { - "expr": "max by (Hostname, gpu) (DCGM_FI_DEV_XID_ERRORS{Hostname=~\"$Hostname\"})", - "legendFormat": "{{Hostname}}/{{gpu}}", - "refId": "A" - } - ], - "title": "XID errors (latest)", - "description": "Hardware-level metric — shown cluster-wide regardless of namespace filter.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 5, - "w": 8, - "x": 0, - "y": 41 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "background", - "justifyMode": "auto", - "textMode": "value_and_name", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 1, - "color": "red" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "timeseries", - "id": 42, - "targets": [ - { - "expr": "rate(DCGM_FI_DEV_POWER_VIOLATION{Hostname=~\"$Hostname\"}[5m])", - "legendFormat": "{{Hostname}}/{{gpu}}", - "refId": "A" - } - ], - "title": "Power Violation (µs/s throttled due to power)", - "description": "Hardware-level metric — shown cluster-wide regardless of namespace filter.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 5, - "w": 8, - "x": 8, - "y": 41 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "list", - "placement": "bottom", - "showLegend": false, - "calcs": [] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "µs", - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 10, - "showPoints": "never" - } - }, - "overrides": [] - } - }, - { - "type": "timeseries", - "id": 43, - "targets": [ - { - "expr": "rate(DCGM_FI_DEV_THERMAL_VIOLATION{Hostname=~\"$Hostname\"}[5m])", - "legendFormat": "{{Hostname}}/{{gpu}}", - "refId": "A" - } - ], - "title": "Thermal Violation (µs/s throttled due to thermals)", - "description": "Hardware-level metric — shown cluster-wide regardless of namespace filter.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 5, - "w": 8, - "x": 16, - "y": 41 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "list", - "placement": "bottom", - "showLegend": false, - "calcs": [] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "µs", - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 10, - "showPoints": "never" - } - }, - "overrides": [] - } - } - ], - "templating": { - "list": [ - { - "type": "datasource", - "name": "ds_prometheus", - "label": "Prometheus", - "skipUrlSync": false, - "query": "prometheus", - "current": { - "selected": false, - "text": "default", - "value": "default" - }, - "multi": false, - "allowCustomValue": true, - "includeAll": false, - "regex": "", - "auto": false, - "auto_min": "10s", - "auto_count": 30 - }, - { - "type": "query", - "name": "Hostname", - "label": "Host", - "skipUrlSync": false, - "query": "label_values(DCGM_FI_DEV_GPU_UTIL, Hostname)", - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "current": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "multi": true, - "allowCustomValue": true, - "refresh": 2, - "sort": 1, - "includeAll": true, - "auto": false, - "auto_min": "10s", - "auto_count": 30 - } - ] - }, - "annotations": {} -} diff --git a/dashboards/gpu/gpu-quotas.json b/dashboards/gpu/gpu-quotas.json deleted file mode 100644 index 865fb95c..00000000 --- a/dashboards/gpu/gpu-quotas.json +++ /dev/null @@ -1,637 +0,0 @@ -{ - "uid": "gpu-quotas", - "title": "GPU Quotas \u0026 Allocation", - "tags": [ - "gpu", - "quotas" - ], - "timezone": "browser", - "editable": true, - "graphTooltip": 1, - "time": { - "from": "now-6h", - "to": "now" - }, - "fiscalYearStartMonth": 0, - "schemaVersion": 42, - "panels": [ - { - "type": "row", - "collapsed": false, - "title": "Allocation overview", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 1, - "panels": [] - }, - { - "type": "stat", - "id": 2, - "targets": [ - { - "expr": "sum(kube_node_status_allocatable{resource=\"nvidia_com_gpu\"})", - "refId": "A" - } - ], - "title": "GPU allocatable", - "description": "Total GPU capacity the cluster can schedule to pods.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 0, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "value", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "blue" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 3, - "targets": [ - { - "expr": "cluster:gpu_count:allocated", - "refId": "A" - } - ], - "title": "GPU requested", - "description": "Sum of GPU requests across all pods cluster-wide, including system namespaces.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 6, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "value", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 1, - "color": "yellow" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "gauge", - "id": 4, - "targets": [ - { - "expr": "cluster:gpu_count:allocated / sum(kube_node_status_allocatable{resource=\"nvidia_com_gpu\"}) * 100", - "refId": "A" - } - ], - "title": "Allocation ratio", - "description": "Percentage of allocatable GPUs currently requested by pods.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 12, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto", - "minVizWidth": 75, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "minVizHeight": 75, - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "percent", - "min": 0, - "max": 100, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "blue" - }, - { - "value": 40, - "color": "green" - }, - { - "value": 80, - "color": "yellow" - }, - { - "value": 95, - "color": "red" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 5, - "targets": [ - { - "expr": "count((sum by (namespace, pod) (kube_pod_container_resource_requests{resource=\"nvidia_com_gpu\"}) \u003e 0) * on(namespace, pod) group_left() (kube_pod_status_phase{phase=\"Pending\"} == 1)) or vector(0)", - "refId": "A" - } - ], - "title": "Pending pods (GPU)", - "description": "Pods requesting GPUs that are stuck in Pending state — indicates capacity shortage.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 18, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "background", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 1, - "color": "red" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "row", - "collapsed": false, - "title": "Per namespace", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 5 - }, - "id": 10, - "panels": [] - }, - { - "type": "bargauge", - "id": 11, - "targets": [ - { - "expr": "sum by (namespace) (namespace:gpu_count:allocated{namespace=~\"$namespace\"})", - "legendFormat": "{{namespace}}", - "refId": "A" - } - ], - "title": "GPU requested per namespace", - "description": "GPU allocation breakdown by namespace — spot top consumers at a glance.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 6 - }, - "repeatDirection": "h", - "options": { - "displayMode": "gradient", - "valueMode": "color", - "namePlacement": "auto", - "showUnfilled": true, - "sizing": "auto", - "minVizWidth": 8, - "minVizHeight": 16, - "legend": { - "displayMode": "list", - "placement": "bottom", - "showLegend": false, - "calcs": [] - }, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "maxVizHeight": 300, - "orientation": "horizontal" - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "min": 0, - "max": 8, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "timeseries", - "id": 12, - "targets": [ - { - "expr": "sum(kube_node_status_allocatable{resource=\"nvidia_com_gpu\"})", - "legendFormat": "Allocatable (total)", - "refId": "A" - }, - { - "expr": "sum(namespace:gpu_count:allocated{namespace=~\"$namespace\"})", - "legendFormat": "Requested", - "refId": "B" - } - ], - "title": "GPU allocated over time", - "description": "Requested vs allocatable GPUs over time — shows allocation pressure trends.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 6 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "list", - "placement": "bottom", - "showLegend": false, - "calcs": [] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "custom": { - "drawStyle": "line", - "lineInterpolation": "stepAfter", - "fillOpacity": 10, - "showPoints": "never" - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Allocatable (total)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - } - ] - } - }, - { - "type": "row", - "collapsed": false, - "title": "Pods with GPU", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 14 - }, - "id": 20, - "panels": [] - }, - { - "type": "table", - "id": 21, - "targets": [ - { - "expr": "kube_pod_container_resource_requests{resource=\"nvidia_com_gpu\", namespace=~\"$namespace\"} * on(namespace, pod) group_left(phase) (kube_pod_status_phase{phase=~\"Running|Pending\"} == 1)", - "instant": true, - "range": false, - "format": "table", - "refId": "requested" - }, - { - "expr": "kube_pod_container_resource_limits{resource=\"nvidia_com_gpu\", namespace=~\"$namespace\"} * on(namespace, pod) group_left() (kube_pod_status_phase{phase=~\"Running|Pending\"} == 1)", - "instant": true, - "range": false, - "format": "table", - "refId": "limits" - } - ], - "title": "Pods requesting GPU", - "description": "Per-pod GPU requests and limits with scheduling status — Running or Pending.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 10, - "w": 24, - "x": 0, - "y": 15 - }, - "repeatDirection": "h", - "transformations": [ - { - "id": "joinByField", - "options": { - "byField": "pod", - "mode": "outer" - } - }, - { - "id": "organize", - "options": { - "excludeByName": { - "Time": true, - "Time 1": true, - "Time 2": true, - "__name__": true, - "__name__ 1": true, - "__name__ 2": true, - "cluster": true, - "cluster 1": true, - "cluster 2": true, - "container 2": true, - "endpoint": true, - "endpoint 1": true, - "endpoint 2": true, - "instance": true, - "instance 1": true, - "instance 2": true, - "job": true, - "job 1": true, - "job 2": true, - "namespace 2": true, - "node 2": true, - "prometheus": true, - "prometheus 1": true, - "prometheus 2": true, - "resource": true, - "resource 1": true, - "resource 2": true, - "service": true, - "service 1": true, - "service 2": true, - "tenant": true, - "tenant 1": true, - "tenant 2": true, - "tier": true, - "tier 1": true, - "tier 2": true, - "uid": true, - "uid 1": true, - "uid 2": true, - "unit": true, - "unit 1": true, - "unit 2": true - }, - "indexByName": { - "Value #limits": 5, - "Value #requested": 4, - "container 1": 2, - "namespace 1": 0, - "node 1": 3, - "phase": 6, - "pod": 1 - }, - "renameByName": { - "Value #limits": "Limit", - "Value #requested": "Req", - "container 1": "Container", - "namespace 1": "Namespace", - "node 1": "Node", - "phase": "Status" - } - } - } - ], - "options": { - "frameIndex": 0, - "showHeader": true, - "showTypeIcons": false, - "footer": { - "show": false, - "reducer": [] - }, - "cellHeight": "sm" - }, - "fieldConfig": { - "defaults": {}, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Status" - }, - "properties": [ - { - "id": "custom.cellOptions", - "value": { - "mode": "basic", - "type": "color-background" - } - }, - { - "id": "mappings", - "value": [ - { - "options": { - "Failed": { - "color": "red", - "index": 2 - }, - "Pending": { - "color": "orange", - "index": 1 - }, - "Running": { - "color": "green", - "index": 0 - } - }, - "type": "value" - } - ] - } - ] - } - ] - } - } - ], - "templating": { - "list": [ - { - "type": "datasource", - "name": "ds_prometheus", - "label": "Prometheus", - "skipUrlSync": false, - "query": "prometheus", - "current": { - "selected": false, - "text": "default", - "value": "default" - }, - "multi": false, - "allowCustomValue": true, - "includeAll": false, - "regex": "", - "auto": false, - "auto_min": "10s", - "auto_count": 30 - }, - { - "type": "query", - "name": "namespace", - "label": "Namespace", - "skipUrlSync": false, - "query": "label_values(kube_pod_container_resource_requests{resource=\"nvidia_com_gpu\"}, namespace)", - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "current": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "multi": true, - "allowCustomValue": true, - "refresh": 2, - "sort": 1, - "includeAll": true, - "auto": false, - "auto_min": "10s", - "auto_count": 30 - } - ] - }, - "annotations": {} -} diff --git a/dashboards/gpu/gpu-tenants.json b/dashboards/gpu/gpu-tenants.json deleted file mode 100644 index 7fef321d..00000000 --- a/dashboards/gpu/gpu-tenants.json +++ /dev/null @@ -1,1098 +0,0 @@ -{ - "uid": "gpu-tenants", - "title": "GPU Tenant Usage", - "description": "Per-tenant GPU consumption: live allocation, utilization, tensor saturation, power, and 24h GPU-hours / kWh — admin view across all namespaces.", - "tags": [ - "gpu", - "tenants", - "admin" - ], - "timezone": "browser", - "editable": true, - "graphTooltip": 1, - "time": { - "from": "now-24h", - "to": "now" - }, - "fiscalYearStartMonth": 0, - "schemaVersion": 42, - "panels": [ - { - "type": "row", - "collapsed": false, - "title": "Tenant fleet snapshot", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 1, - "panels": [] - }, - { - "type": "stat", - "id": 2, - "targets": [ - { - "expr": "count(namespace:gpu_count:allocated{namespace=~\"$namespace\"} \u003e 0)", - "refId": "A" - } - ], - "title": "Active tenants", - "description": "Namespaces with at least one GPU request (Pending + Running).", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 0, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "value", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "blue" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 3, - "targets": [ - { - "expr": "sum(namespace:gpu_count:allocated{namespace=~\"$namespace\"})", - "refId": "A" - } - ], - "title": "Allocated GPUs", - "description": "Sum of kube GPU requests across tenants. Billable view — includes Pending pods.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 4, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "background", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "blue" - }, - { - "value": 1, - "color": "green" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 4, - "targets": [ - { - "expr": "cluster:gpu_util:avg", - "refId": "A" - } - ], - "title": "Avg cluster GPU util", - "description": "Average NVML utilization across all GPUs in the cluster. Cluster-wide — DCGM hardware metrics cannot be attributed to workload namespaces.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 8, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "area", - "colorMode": "value", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "percent", - "min": 0, - "max": 100, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "blue" - }, - { - "value": 30, - "color": "green" - }, - { - "value": 80, - "color": "orange" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 5, - "targets": [ - { - "expr": "cluster:gpu_power_watts:sum", - "refId": "A" - } - ], - "title": "Total GPU power", - "description": "Live power draw summed across all GPUs in the cluster. Cluster-wide — DCGM hardware metrics cannot be attributed to workload namespaces.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 12, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "area", - "colorMode": "value", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "watt", - "decimals": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 6, - "targets": [ - { - "expr": "sum(sum_over_time(node:power_watts:sum[24h:1m])) / 60 / 1000", - "refId": "A" - } - ], - "title": "Energy (last 24h)", - "description": "Integrated cluster-wide GPU power draw over 24h. Cluster-wide — DCGM hardware metrics cannot be attributed to workload namespaces.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 16, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "value", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "kwatth", - "decimals": 1, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "stat", - "id": 7, - "targets": [ - { - "expr": "sum(sum_over_time(namespace:gpu_count:allocated{namespace=~\"$namespace\"}[24h:1m])) / 60", - "refId": "A" - } - ], - "title": "GPU-hours (last 24h)", - "description": "Sum of (GPU count × time requested) across all tenants over the last 24h. Integrated from kube requests, so Pending pods count.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 20, - "y": 1 - }, - "repeatDirection": "h", - "options": { - "graphMode": "none", - "colorMode": "value", - "justifyMode": "auto", - "textMode": "value", - "wideLayout": true, - "showPercentChange": false, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "percentChangeColorMode": "standard", - "orientation": "" - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "decimals": 1, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "blue" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "row", - "collapsed": false, - "title": "Current allocation", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 5 - }, - "id": 10, - "panels": [] - }, - { - "type": "bargauge", - "id": 11, - "targets": [ - { - "expr": "namespace:gpu_count:allocated{namespace=~\"$namespace\"}", - "legendFormat": "{{namespace}}", - "refId": "A" - } - ], - "title": "GPUs per namespace", - "description": "Kube GPU requests per tenant (billable). Includes Pending pods.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 6 - }, - "repeatDirection": "h", - "options": { - "displayMode": "gradient", - "valueMode": "color", - "namePlacement": "auto", - "showUnfilled": true, - "sizing": "auto", - "minVizWidth": 8, - "minVizHeight": 16, - "legend": { - "displayMode": "list", - "placement": "bottom", - "showLegend": false, - "calcs": [] - }, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "maxVizHeight": 300, - "orientation": "horizontal" - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "bargauge", - "id": 12, - "targets": [ - { - "expr": "node:fb_used_bytes:sum", - "legendFormat": "{{Hostname}}", - "refId": "A" - } - ], - "title": "VRAM used per node", - "description": "FB memory actively used per node. DCGM hardware metrics cannot be attributed to workload namespaces — shown per-node instead.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 6 - }, - "repeatDirection": "h", - "options": { - "displayMode": "gradient", - "valueMode": "color", - "namePlacement": "auto", - "showUnfilled": true, - "sizing": "auto", - "minVizWidth": 8, - "minVizHeight": 16, - "legend": { - "displayMode": "list", - "placement": "bottom", - "showLegend": false, - "calcs": [] - }, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "maxVizHeight": 300, - "orientation": "horizontal" - }, - "fieldConfig": { - "defaults": { - "unit": "bytes", - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "row", - "collapsed": false, - "title": "Utilization (per-node)", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 14 - }, - "id": 20, - "panels": [] - }, - { - "type": "timeseries", - "id": 21, - "targets": [ - { - "expr": "node:gpu_util:avg", - "legendFormat": "{{Hostname}}", - "refId": "A" - } - ], - "title": "NVML utilization per node", - "description": "Per-node NVML utilization. Stacked to show fleet-wide pressure over time. DCGM hardware metrics cannot be attributed to workload namespaces — shown per-node instead.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 15 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "percent", - "min": 0, - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 40, - "showPoints": "never", - "stacking": { - "mode": "normal" - } - } - }, - "overrides": [] - } - }, - { - "type": "timeseries", - "id": 22, - "targets": [ - { - "expr": "node:tensor_active:avg * 100", - "legendFormat": "{{Hostname}}", - "refId": "A" - } - ], - "title": "Tensor saturation per node", - "description": "Real tensor core load per node. Useful to spot nodes with idle tensor cores. DCGM hardware metrics cannot be attributed to workload namespaces — shown per-node instead.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 15 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "percent", - "min": 0, - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 40, - "showPoints": "never", - "stacking": { - "mode": "normal" - } - } - }, - "overrides": [] - } - }, - { - "type": "row", - "collapsed": false, - "title": "Power \u0026 allocation trend", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 23 - }, - "id": 30, - "panels": [] - }, - { - "type": "timeseries", - "id": 31, - "targets": [ - { - "expr": "node:power_watts:sum", - "legendFormat": "{{Hostname}}", - "refId": "A" - } - ], - "title": "Power draw per node", - "description": "Per-node GPU power draw. Stacked to see cluster-wide energy burn over time. DCGM hardware metrics cannot be attributed to workload namespaces — shown per-node instead.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 24 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "watt", - "min": 0, - "custom": { - "drawStyle": "line", - "lineInterpolation": "linear", - "fillOpacity": 40, - "showPoints": "never", - "stacking": { - "mode": "normal" - } - } - }, - "overrides": [] - } - }, - { - "type": "timeseries", - "id": 32, - "targets": [ - { - "expr": "namespace:gpu_count:allocated{namespace=~\"$namespace\"}", - "legendFormat": "{{namespace}}", - "refId": "A" - } - ], - "title": "Allocated GPUs per namespace", - "description": "Kube-requested GPU count per tenant over time. Step-after interpolation reflects discrete allocation changes.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 24 - }, - "repeatDirection": "h", - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": false, - "calcs": [ - "mean", - "max" - ] - }, - "tooltip": { - "mode": "multi", - "sort": "" - } - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "min": 0, - "custom": { - "drawStyle": "line", - "lineInterpolation": "stepAfter", - "fillOpacity": 40, - "showPoints": "never", - "stacking": { - "mode": "normal" - } - } - }, - "overrides": [] - } - }, - { - "type": "row", - "collapsed": false, - "title": "Top consumers (live)", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 32 - }, - "id": 40, - "panels": [] - }, - { - "type": "table", - "id": 41, - "targets": [ - { - "expr": "namespace:gpu_count:allocated{namespace=~\"$namespace\"}", - "instant": true, - "range": false, - "format": "table", - "refId": "gpus" - } - ], - "title": "Top consumers (allocation)", - "description": "Live ranking of tenants by GPU allocation. Hardware metrics (util, tensor, power, VRAM) cannot be attributed to workload namespaces — see Fleet and Performance dashboards for per-node/per-GPU hardware views.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 10, - "w": 24, - "x": 0, - "y": 33 - }, - "repeatDirection": "h", - "transformations": [ - { - "id": "organize", - "options": { - "excludeByName": { - "Time": true, - "Time 1": true, - "Time 2": true, - "Time 3": true, - "Time 4": true, - "Time 5": true, - "__name__": true, - "__name__ 1": true, - "__name__ 2": true, - "__name__ 3": true, - "__name__ 4": true, - "__name__ 5": true, - "cluster": true, - "cluster 1": true, - "cluster 2": true, - "cluster 3": true, - "cluster 4": true, - "cluster 5": true, - "endpoint": true, - "endpoint 1": true, - "endpoint 2": true, - "endpoint 3": true, - "endpoint 4": true, - "endpoint 5": true, - "instance": true, - "instance 1": true, - "instance 2": true, - "instance 3": true, - "instance 4": true, - "instance 5": true, - "job": true, - "job 1": true, - "job 2": true, - "job 3": true, - "job 4": true, - "job 5": true, - "prometheus": true, - "prometheus 1": true, - "prometheus 2": true, - "prometheus 3": true, - "prometheus 4": true, - "prometheus 5": true, - "service": true, - "service 1": true, - "service 2": true, - "service 3": true, - "service 4": true, - "service 5": true, - "tenant": true, - "tenant 1": true, - "tenant 2": true, - "tenant 3": true, - "tenant 4": true, - "tenant 5": true, - "tier": true, - "tier 1": true, - "tier 2": true, - "tier 3": true, - "tier 4": true, - "tier 5": true, - "uid": true, - "uid 1": true, - "uid 2": true, - "uid 3": true, - "uid 4": true, - "uid 5": true, - "unit": true, - "unit 1": true, - "unit 2": true, - "unit 3": true, - "unit 4": true, - "unit 5": true - }, - "indexByName": { - "Value #gpus": 1, - "namespace": 0 - }, - "renameByName": { - "Value #gpus": "GPUs", - "namespace": "Namespace" - } - } - } - ], - "options": { - "frameIndex": 0, - "showHeader": true, - "showTypeIcons": false, - "sortBy": [ - { - "displayName": "GPUs", - "desc": true - } - ], - "footer": { - "show": false, - "reducer": [] - }, - "cellHeight": "sm" - } - }, - { - "type": "row", - "collapsed": false, - "title": "Historical usage (last 24h)", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 43 - }, - "id": 50, - "panels": [] - }, - { - "type": "bargauge", - "id": 51, - "targets": [ - { - "expr": "sum_over_time(namespace:gpu_count:allocated{namespace=~\"$namespace\"}[24h:1m]) / 60", - "legendFormat": "{{namespace}}", - "refId": "A" - } - ], - "title": "GPU-hours per namespace (last 24h)", - "description": "Billable GPU-hours per tenant over the last 24h window.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 44 - }, - "repeatDirection": "h", - "options": { - "displayMode": "gradient", - "valueMode": "color", - "namePlacement": "auto", - "showUnfilled": true, - "sizing": "auto", - "minVizWidth": 8, - "minVizHeight": 16, - "legend": { - "displayMode": "list", - "placement": "bottom", - "showLegend": false, - "calcs": [] - }, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "maxVizHeight": 300, - "orientation": "horizontal" - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "decimals": 1, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "blue" - } - ] - } - }, - "overrides": [] - } - }, - { - "type": "bargauge", - "id": 52, - "targets": [ - { - "expr": "sum_over_time(node:power_watts:sum[24h:1m]) / 60 / 1000", - "legendFormat": "{{Hostname}}", - "refId": "A" - } - ], - "title": "Energy per node (last 24h)", - "description": "kWh consumed per node over the last 24h. Integrated from 1-minute power samples. DCGM hardware metrics cannot be attributed to workload namespaces — shown per-node instead.", - "transparent": false, - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 44 - }, - "repeatDirection": "h", - "options": { - "displayMode": "gradient", - "valueMode": "color", - "namePlacement": "auto", - "showUnfilled": true, - "sizing": "auto", - "minVizWidth": 8, - "minVizHeight": 16, - "legend": { - "displayMode": "list", - "placement": "bottom", - "showLegend": false, - "calcs": [] - }, - "reduceOptions": { - "calcs": [ - "lastNotNull" - ] - }, - "maxVizHeight": 300, - "orientation": "horizontal" - }, - "fieldConfig": { - "defaults": { - "unit": "kwatth", - "decimals": 2, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - } - ] - } - }, - "overrides": [] - } - } - ], - "templating": { - "list": [ - { - "type": "datasource", - "name": "ds_prometheus", - "label": "Prometheus", - "skipUrlSync": false, - "query": "prometheus", - "current": { - "selected": false, - "text": "default", - "value": "default" - }, - "multi": false, - "allowCustomValue": true, - "includeAll": false, - "regex": "", - "auto": false, - "auto_min": "10s", - "auto_count": 30 - }, - { - "type": "query", - "name": "namespace", - "label": "Namespace", - "skipUrlSync": false, - "query": "label_values(namespace:gpu_count:allocated, namespace)", - "datasource": { - "type": "prometheus", - "uid": "$ds_prometheus" - }, - "current": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "multi": true, - "allowCustomValue": true, - "refresh": 2, - "sort": 1, - "includeAll": true, - "auto": false, - "auto_min": "10s", - "auto_count": 30 - } - ] - }, - "annotations": {} -} diff --git a/docs/changelogs/v1.3.1.md b/docs/changelogs/v1.3.1.md deleted file mode 100644 index 0f149d0d..00000000 --- a/docs/changelogs/v1.3.1.md +++ /dev/null @@ -1,37 +0,0 @@ - - -# v1.3.1 (2026-04-28) - -Patch release covering a TenantNamespace IDOR fix in the API, a destructive `post-upgrade` hook removed from the etcd chart, kamaji controller stability, a `linstor-csi` bump that fixes live migration on Protocol-A/B DRBD resources, the missing `linstor-gui` build wiring, and a velero RBAC fix that unblocked installs on bundles without Velero. - -## Security - -* **fix(api): prevent IDOR in TenantNamespace Get and Watch handlers**: Two IDOR (Insecure Direct Object Reference) vulnerabilities allowed authenticated users to read TenantNamespace metadata they had no RoleBinding for. The `Get` and `Watch` handlers now go through a new `hasAccessToNamespace()` helper that lists RoleBindings scoped only to the target namespace (orders of magnitude faster than the previous all-cluster scan), returns `NotFound` instead of leaking existence on unauthorized access, and applies the same check on the `Watch` filter path. Includes regression tests for the unauthorized paths. ([**@IvanHunters**](https://github.com/IvanHunters) in #2471, backport #2524) - -## Features - -* **feat(linstor): bump linstor-csi to v1.10.6 with Protocol-C dual-attach fix**: Live migration of KubeVirt VMs on Protocol-A/B (async) DRBD volumes no longer fails with `Protocol C required`. `linstor-csi` v1.10.6 now installs a `Protocol=C` override on the resource-definition during dual-attach and reverts it on detach, so `replicated-async` StorageClasses and other Protocol-A/B resource groups support live migration without manual `drbdadm adjust` intervention. ([**@kvaps**](https://github.com/kvaps) in #2496, backport #2505) - -## Fixes - -* **fix(backups): move velero-configmap Role to velero chart**: The `backupstrategy-controller` (a default package) declared a Role/RoleBinding scoped to the `cozy-velero` namespace for managing `ResourceModifier` ConfigMaps. On bundles where Velero was not enabled, that namespace did not exist and the HelmRelease failed with `namespaces "cozy-velero" not found`, blocking installation. The Role/RoleBinding now lives in the velero chart, so it is created only when velero is actually deployed. ([**@myasnikovdaniil**](https://github.com/myasnikovdaniil) in #2459, backport #2467) - -* **fix(etcd): remove destructive post-upgrade cert-regeneration hook**: The etcd chart ran a `post-upgrade` Helm hook on every upgrade that deleted etcd TLS Secrets (`etcd-ca-tls`, `etcd-peer-ca-tls`, `etcd-client-tls`, `etcd-peer-tls`, `etcd-server-tls`) and then deleted etcd pods, forcing cert-manager to re-issue the entire etcd CA chain. On clusters with Kamaji-managed tenant control planes this put every tenant `kube-apiserver` into CrashLoopBackOff until each DataStore was manually re-reconciled. The hook was a one-shot `2.6.0 → 2.6.1` migration that became a permanent footgun once chart versioning moved to `0.0.0+` (always `< 2.6.1` per semver) and after the underlying `rotationPolicy: Always` issue was fixed in `47d81f70`. The hook is now removed entirely. ([**@myasnikovdaniil**](https://github.com/myasnikovdaniil) in #2462, backport #2511) - -* **fix(kamaji): increase memory limits and add startup probe**: The kamaji controller frequently entered CrashLoopBackOff due to OOMKills (exit 137) within ~20–25 seconds of startup, with the readiness probe failing while the controller was still finishing initialization. Memory limit raised from 500Mi to 512Mi, request from 100Mi to 256Mi, and a 60-second startup probe (12 attempts × 5s periods) is added so the controller has room to boot before liveness/readiness probes engage. ([**@IvanHunters**](https://github.com/IvanHunters) in #2421, backport #2491) - -## Build - -* **build(linstor): include linstor-gui in root image build target**: The `linstor-gui` package (added in #2382) was never wired into the root `Makefile`'s `build:` target, so CI never built or published the image. `ghcr.io/cozystack/cozystack/linstor-gui` returned `NAME_UNKNOWN` and `values.yaml` stayed pinned to `tag: 2.3.0` without a digest. The missing build line is added so the next CI run publishes the image and the per-package `Makefile` digest-pins `values.yaml` automatically. ([**@myasnikovdaniil**](https://github.com/myasnikovdaniil) in #2498, backport #2518) - -## Contributors - -Thanks to everyone who contributed to this patch release: - -* [**@IvanHunters**](https://github.com/IvanHunters) -* [**@kvaps**](https://github.com/kvaps) -* [**@myasnikovdaniil**](https://github.com/myasnikovdaniil) - -**Full Changelog**: https://github.com/cozystack/cozystack/compare/v1.3.0...v1.3.1 diff --git a/hack/check-gpu-recording-rules.bats b/hack/check-gpu-recording-rules.bats deleted file mode 100644 index bae6785e..00000000 --- a/hack/check-gpu-recording-rules.bats +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/env bats -# ----------------------------------------------------------------------------- -# Cross-validation between GPU recording rules, the dashboards that consume -# them, and the DCGM Exporter metric set the cluster actually scrapes. Catches: -# -# 1. dangling references — a dashboard query mentions a recording rule name -# that doesn't exist in gpu-recording.rules.yaml. This is the bug the -# pre-merge review caught: gpu-efficiency.json shipped panels keyed on -# pod:tensor_saturation:avg5m without the rule being defined, so the -# panel showed "No data" everywhere. -# -# 2. typos in rule names — same bug class, manifested as a single-character -# difference between rule and reference. -# -# 3. undeclared DCGM metrics — a dashboard query or recording rule mentions -# a DCGM_FI_* metric that is neither in the upstream default CSV nor in -# the project's custom CSV (dcgm-custom-metrics.yaml), meaning DCGM -# Exporter would never emit it and the panel silently shows "No data". -# Example regression: gpu-fleet.json shipped a TDP panel referencing -# DCGM_FI_DEV_POWER_MGMT_LIMIT before the custom CSV declared it. -# -# Scope: only dashboards listed in packages/system/monitoring/dashboards-infra.list -# under the "gpu/" prefix (i.e. shipped to production), not every JSON file in -# dashboards/gpu/. Untracked drafts stay out of scope on purpose — adding one -# to dashboards-infra.list is what brings it under the test. -# -# Reverse direction (rule defined but never consumed) is intentionally NOT -# enforced: some rules exist for ad-hoc PromQL or upcoming dashboards. Treat -# unused rules as an editorial concern, not a regression. -# -# Title syntax constraints from cozytest.sh's awk parser: -# - Titles delimited by ASCII double quotes; embedded quotes truncate. -# - Only [A-Za-z0-9] from the title survives into the function name; titles -# differing only in punctuation collapse to the same function. -# -# Run with: hack/cozytest.sh hack/check-gpu-recording-rules.bats -# ----------------------------------------------------------------------------- - -REPO_ROOT="$(cd "$(dirname "${BATS_TEST_FILENAME:-$0}")/.." && pwd)" -RULES_FILE="$REPO_ROOT/packages/system/monitoring-agents/alerts/gpu-recording.rules.yaml" -DASHBOARDS_LIST="$REPO_ROOT/packages/system/monitoring/dashboards-infra.list" -DASHBOARDS_DIR="$REPO_ROOT/dashboards" -DCGM_DEFAULT_CSV="$REPO_ROOT/hack/dcgm-default-counters.csv" -DCGM_CUSTOM_CSV="$REPO_ROOT/packages/system/gpu-operator/examples/dcgm-custom-metrics.yaml" - -# Extract the set of "- record: NAME" entries from the rules YAML. -# Outputs one rule name per line, sorted and deduplicated. -extract_rules() { - awk '/^[[:space:]]*-[[:space:]]*record:[[:space:]]/ { - sub(/^[[:space:]]*-[[:space:]]*record:[[:space:]]*/, "") - sub(/[[:space:]]*$/, "") - print - }' "$RULES_FILE" | sort -u -} - -# Extract the set of recording-rule references from a dashboard JSON. -# A recording-rule reference is matched by the pattern -# :(:)+ -# where each is [a-z0-9_]. Raw DCGM metrics (DCGM_FI_*), -# kube-state-metrics (kube_*) and similar uppercase / single-word metric -# names do not match because the leading segment must be lowercase and the -# whole expression must contain at least two ':' characters. -extract_refs() { - json_file=$1 - # Prometheus convention allows 2-segment rule names (level:metric); this - # regex is tuned to the 3+ segment convention used in this repo - # (level:metric:op — e.g. cluster:gpu_count:total). Update if future - # rules use 2 segments, otherwise they will be silently skipped. - grep -hoE '[a-z][a-z0-9_]*:[a-z0-9_]+:[a-z0-9_]+' "$json_file" | sort -u -} - -# Resolve "gpu/foo" -> "$DASHBOARDS_DIR/gpu/foo.json" -list_tracked_gpu_dashboards() { - awk '/^gpu\// { print $0 ".json" }' "$DASHBOARDS_LIST" -} - -# Extract the set of DCGM_FI_* metric names declared in a CSV file. Handles -# both the upstream-style default CSV (unindented) and the ConfigMap-style -# custom CSV (YAML-indented). A declaration line starts — after any leading -# whitespace — with "DCGM_FI_," ; comment lines begin with "#" and are -# skipped. Uses POSIX awk's match()+RSTART/RLENGTH so no GNU extensions -# are required. -extract_csv_metrics() { - file=$1 - awk ' - { - line = $0 - sub(/^[[:space:]]+/, "", line) - if (line ~ /^#/) next - if (match(line, /^DCGM_FI_[A-Z0-9_]+/)) { - print substr(line, RSTART, RLENGTH) - } - } - ' "$file" | sort -u -} - -# Extract the set of DCGM_FI_* metric references from a text file (dashboard -# JSON or rules YAML). A DCGM metric name has at least two underscore-delimited -# segments after the "DCGM_FI_" prefix (e.g. DCGM_FI_DEV_GPU_UTIL, DCGM_FI_PROF_ -# PIPE_TENSOR_ACTIVE, DCGM_FI_DRIVER_VERSION). Requiring two segments keeps -# the matcher from latching onto glob stubs like "DCGM_FI_DEV_*_VIOLATION" that -# appear in comments. -extract_dcgm_refs() { - file=$1 - grep -hoE 'DCGM_FI_[A-Z0-9]+(_[A-Z0-9]+)+' "$file" | sort -u -} - -@test "every recording rule reference in tracked GPU dashboards has a matching record" { - TMP=$(mktemp -d) - trap 'rm -rf "$TMP"' EXIT - - extract_rules > "$TMP/rules.txt" - [ -s "$TMP/rules.txt" ] || { echo "no recording rules extracted from $RULES_FILE" >&2; exit 1; } - - list_tracked_gpu_dashboards > "$TMP/dashboards.txt" - [ -s "$TMP/dashboards.txt" ] || { echo "no gpu/* dashboards listed in $DASHBOARDS_LIST" >&2; exit 1; } - - failed=0 - while IFS= read -r dashboard_rel; do - dashboard="$DASHBOARDS_DIR/$dashboard_rel" - if [ ! -f "$dashboard" ]; then - echo "ERROR: dashboard listed but file missing: $dashboard" >&2 - failed=1 - continue - fi - - extract_refs "$dashboard" > "$TMP/refs.txt" - # comm -23: lines unique to refs.txt (referenced but not defined) - # Both inputs must be sorted; extract_* helpers already sort. - comm -23 "$TMP/refs.txt" "$TMP/rules.txt" > "$TMP/missing.txt" - if [ -s "$TMP/missing.txt" ]; then - echo "ERROR: $dashboard_rel references undefined recording rules:" >&2 - sed 's/^/ - /' "$TMP/missing.txt" >&2 - failed=1 - fi - done < "$TMP/dashboards.txt" - - [ "$failed" -eq 0 ] -} - -@test "every DCGM metric referenced in tracked dashboards and rules is declared" { - TMP=$(mktemp -d) - trap 'rm -rf "$TMP"' EXIT - - [ -f "$DCGM_DEFAULT_CSV" ] || { echo "missing $DCGM_DEFAULT_CSV" >&2; exit 1; } - [ -f "$DCGM_CUSTOM_CSV" ] || { echo "missing $DCGM_CUSTOM_CSV" >&2; exit 1; } - - { - extract_csv_metrics "$DCGM_DEFAULT_CSV" - extract_csv_metrics "$DCGM_CUSTOM_CSV" - } | sort -u > "$TMP/declared.txt" - [ -s "$TMP/declared.txt" ] || { echo "no DCGM metrics extracted from CSVs" >&2; exit 1; } - - list_tracked_gpu_dashboards > "$TMP/dashboards.txt" - [ -s "$TMP/dashboards.txt" ] || { echo "no gpu/* dashboards listed in $DASHBOARDS_LIST" >&2; exit 1; } - - failed=0 - - # Dashboard coverage — every dashboard's DCGM references must resolve. - while IFS= read -r dashboard_rel; do - dashboard="$DASHBOARDS_DIR/$dashboard_rel" - [ -f "$dashboard" ] || continue # handled by the existence test - extract_dcgm_refs "$dashboard" > "$TMP/refs.txt" - [ -s "$TMP/refs.txt" ] || continue # dashboard relies entirely on recording rules - comm -23 "$TMP/refs.txt" "$TMP/declared.txt" > "$TMP/missing.txt" - if [ -s "$TMP/missing.txt" ]; then - echo "ERROR: $dashboard_rel references DCGM metrics not declared in any CSV:" >&2 - sed 's/^/ - /' "$TMP/missing.txt" >&2 - failed=1 - fi - done < "$TMP/dashboards.txt" - - # Rules coverage — recording rules consume DCGM directly, so their set - # must be declared too, otherwise derived series on every dashboard - # collapse to empty. - extract_dcgm_refs "$RULES_FILE" > "$TMP/rule-refs.txt" - if [ -s "$TMP/rule-refs.txt" ]; then - comm -23 "$TMP/rule-refs.txt" "$TMP/declared.txt" > "$TMP/rule-missing.txt" - if [ -s "$TMP/rule-missing.txt" ]; then - echo "ERROR: gpu-recording.rules.yaml references DCGM metrics not declared in any CSV:" >&2 - sed 's/^/ - /' "$TMP/rule-missing.txt" >&2 - failed=1 - fi - fi - - [ "$failed" -eq 0 ] -} - -@test "every tracked GPU dashboard listed in dashboards-infra.list exists on disk" { - TMP=$(mktemp -d) - trap 'rm -rf "$TMP"' EXIT - - list_tracked_gpu_dashboards > "$TMP/dashboards.txt" - [ -s "$TMP/dashboards.txt" ] || { echo "no gpu/* dashboards listed in $DASHBOARDS_LIST" >&2; exit 1; } - - failed=0 - while IFS= read -r dashboard_rel; do - dashboard="$DASHBOARDS_DIR/$dashboard_rel" - if [ ! -f "$dashboard" ]; then - echo "ERROR: $dashboard_rel listed in dashboards-infra.list but $dashboard does not exist" >&2 - failed=1 - fi - done < "$TMP/dashboards.txt" - - [ "$failed" -eq 0 ] -} diff --git a/hack/cozyreport-summary.sh b/hack/cozyreport-summary.sh new file mode 100755 index 00000000..d90682c5 --- /dev/null +++ b/hack/cozyreport-summary.sh @@ -0,0 +1,66 @@ +#!/bin/sh +# Emit a human-readable summary of "what is broken" to a single file. +# Reads the live cluster (not the report dir) so it can use kubectl JSONPath. +# Usage: cozyreport-summary.sh > summary.txt +set -eu + +echo "# Cozystack E2E Diagnostic Summary" +echo "Generated: $(date -Iseconds)" +echo + +echo "## HelmReleases not Ready" +echo +kubectl get hr -A --no-headers 2>/dev/null \ + | awk '$4 != "True" {printf " %s/%s — %s\n", $1, $2, $5}' \ + | head -40 +echo + +echo "## Pods not Running/Succeeded" +echo +kubectl get pod -A --no-headers 2>/dev/null \ + | awk '$4 !~ /Running|Succeeded|Completed/ {printf " %s/%s — %s (restarts=%s, age=%s)\n", $1, $2, $4, $5, $6}' \ + | head -40 +echo + +echo "## ImagePullBackOff / ErrImagePull" +echo +kubectl get pod -A --no-headers 2>/dev/null \ + | awk '$4 ~ /ImagePullBackOff|ErrImagePull/ {printf " %s/%s — %s\n", $1, $2, $4}' +echo + +echo "## OOMKilled in last 30 min" +echo +kubectl get events -A --field-selector reason=OOMKilling --sort-by=.lastTimestamp 2>/dev/null \ + | tail -20 +echo + +echo "## Recent Warning events (top 30)" +echo +kubectl get events -A --field-selector type=Warning --sort-by=.lastTimestamp 2>/dev/null \ + | tail -30 +echo + +echo "## cert-manager: Certificates not Ready" +echo +if kubectl get crd certificates.cert-manager.io >/dev/null 2>&1; then + kubectl get certificates.cert-manager.io -A --no-headers 2>/dev/null \ + | awk '$3 != "True" {printf " %s/%s — Ready=%s\n", $1, $2, $3}' +fi +echo + +echo "## Flux Sources not Ready" +echo +for kind in helmrepositories.source.toolkit.fluxcd.io ocirepositories.source.toolkit.fluxcd.io gitrepositories.source.toolkit.fluxcd.io; do + kubectl get $kind -A --no-headers 2>/dev/null \ + | awk -v k="${kind%%.*}" '$4 != "True" {printf " %s %s/%s — Ready=%s\n", k, $1, $2, $4}' +done +echo + +echo "## Storage: PVCs not Bound, PVs not Bound" +echo +kubectl get pvc -A --no-headers 2>/dev/null | awk '$3 != "Bound" {printf " PVC %s/%s — %s\n", $1, $2, $3}' +kubectl get pv --no-headers 2>/dev/null | awk '$5 != "Bound" {printf " PV %s — %s\n", $1, $5}' +echo + +echo "## Node Conditions" +kubectl get nodes -o custom-columns=NAME:.metadata.name,READY:.status.conditions[?\(@.type==\"Ready\"\)].status,DISK:.status.conditions[?\(@.type==\"DiskPressure\"\)].status,MEM:.status.conditions[?\(@.type==\"MemoryPressure\"\)].status 2>/dev/null diff --git a/hack/cozyreport.sh b/hack/cozyreport.sh index 565fd435..ba65c382 100755 --- a/hack/cozyreport.sh +++ b/hack/cozyreport.sh @@ -9,10 +9,13 @@ command -V kubectl >/dev/null || exit $? command -V tar >/dev/null || exit $? # -- cozystack module - echo "Collecting Cozystack information..." mkdir -p $REPORT_DIR/cozystack kubectl get deploy -n cozy-system cozystack -o jsonpath='{.spec.template.spec.containers[0].image}' > $REPORT_DIR/cozystack/image.txt 2>&1 +if kubectl get deploy -n cozy-system cozystack-operator >/dev/null 2>&1; then + kubectl logs -n cozy-system deploy/cozystack-operator --tail=2000 > $REPORT_DIR/cozystack/operator.log 2>&1 + kubectl logs -n cozy-system deploy/cozystack-operator --tail=2000 --previous > $REPORT_DIR/cozystack/operator-previous.log 2>&1 || true +fi kubectl get cm -n cozy-system --no-headers | awk '$1 ~ /^cozystack/' | while read NAME _; do DIR=$REPORT_DIR/cozystack/configs @@ -20,6 +23,46 @@ kubectl get cm -n cozy-system --no-headers | awk '$1 ~ /^cozystack/' | kubectl get cm -n cozy-system $NAME -o yaml > $DIR/$NAME.yaml 2>&1 done +# -- flux module +echo "Collecting Flux controller state..." +mkdir -p $REPORT_DIR/flux +for ctrl in helm-controller source-controller notification-controller kustomize-controller; do + if kubectl get deploy -n cozy-fluxcd $ctrl >/dev/null 2>&1; then + kubectl logs -n cozy-fluxcd deploy/$ctrl --tail=2000 > $REPORT_DIR/flux/$ctrl.log 2>&1 + kubectl logs -n cozy-fluxcd deploy/$ctrl --tail=2000 --previous > $REPORT_DIR/flux/$ctrl-previous.log 2>&1 || true + fi +done + +echo "Collecting Flux sources..." +for kind in helmrepositories.source.toolkit.fluxcd.io ocirepositories.source.toolkit.fluxcd.io gitrepositories.source.toolkit.fluxcd.io externalartifacts.source.toolkit.fluxcd.io; do + short=${kind%%.*} + kubectl get $kind -A > $REPORT_DIR/flux/$short.txt 2>&1 + kubectl get $kind -A -o yaml > $REPORT_DIR/flux/$short.yaml 2>&1 +done + +# -- cert-manager module +if kubectl get crd certificates.cert-manager.io >/dev/null 2>&1; then + echo "Collecting cert-manager state..." + DIR=$REPORT_DIR/cert-manager + mkdir -p $DIR + kubectl get certificates.cert-manager.io -A > $DIR/certificates.txt 2>&1 + kubectl get certificaterequests.cert-manager.io -A > $DIR/certificaterequests.txt 2>&1 + kubectl get orders.acme.cert-manager.io -A > $DIR/orders.txt 2>&1 + kubectl get challenges.acme.cert-manager.io -A > $DIR/challenges.txt 2>&1 + # Per non-Ready cert: full yaml + describe + kubectl get certificates.cert-manager.io -A --no-headers 2>/dev/null | awk '$3 != "True"' | \ + while read NAMESPACE NAME _; do + cdir=$DIR/certificates/$NAMESPACE/$NAME + mkdir -p $cdir + kubectl get certificates.cert-manager.io -n $NAMESPACE $NAME -o yaml > $cdir/cert.yaml 2>&1 + kubectl describe certificates.cert-manager.io -n $NAMESPACE $NAME > $cdir/describe.txt 2>&1 + done + if kubectl get deploy -n cozy-cert-manager cert-manager >/dev/null 2>&1; then + kubectl logs -n cozy-cert-manager deploy/cert-manager --tail=2000 > $DIR/cert-manager.log 2>&1 + kubectl logs -n cozy-cert-manager deploy/cert-manager-webhook --tail=2000 > $DIR/cert-manager-webhook.log 2>&1 + fi +fi + # -- kubernetes module echo "Collecting Kubernetes information..." @@ -46,6 +89,13 @@ kubectl get ns --no-headers | awk '$2 != "Active"' | kubectl describe ns $NAME > $DIR/describe.txt 2>&1 done +echo "Collecting events..." +kubectl get events -A --sort-by=.lastTimestamp > $REPORT_DIR/kubernetes/events.txt 2>&1 +# Filter to warning-class and recent for quick triage +kubectl get events -A --sort-by=.lastTimestamp \ + -o jsonpath='{range .items[?(@.type!="Normal")]}{.lastTimestamp}{"\t"}{.involvedObject.namespace}/{.involvedObject.kind}/{.involvedObject.name}{"\t"}{.reason}{"\t"}{.message}{"\n"}{end}' \ + > $REPORT_DIR/kubernetes/events-warnings.txt 2>&1 + echo "Collecting helmreleases..." kubectl get hr -A > $REPORT_DIR/kubernetes/helmreleases.txt 2>&1 kubectl get hr -A --no-headers | awk '$4 != "True"' | \ @@ -54,6 +104,13 @@ kubectl get hr -A --no-headers | awk '$4 != "True"' | \ mkdir -p $DIR kubectl get hr -n $NAMESPACE $NAME -o yaml > $DIR/hr.yaml 2>&1 kubectl describe hr -n $NAMESPACE $NAME > $DIR/describe.txt 2>&1 + # Helm storage secrets: latest revision per release. + kubectl get secret -n $NAMESPACE -l owner=helm,name=$NAME --sort-by='.metadata.creationTimestamp' --no-headers 2>/dev/null | \ + tail -1 | awk '{print $1}' | while read SECRET; do + [ -z "$SECRET" ] && continue + kubectl get secret -n $NAMESPACE $SECRET -o jsonpath='{.data.release}' 2>/dev/null \ + | base64 -d | base64 -d | gzip -d > $DIR/helm-release.json 2>&1 || true + done done echo "Collecting packages..." @@ -76,6 +133,23 @@ kubectl get packagesources --no-headers | awk '$3 != "True"' | \ kubectl describe packagesource $NAME > $DIR/describe.txt 2>&1 done +echo "Collecting cozystack apps..." +DIR=$REPORT_DIR/cozystack-apps +mkdir -p $DIR +for kind in applications.apps.cozystack.io applicationdefinitions.apps.cozystack.io tenants.apps.cozystack.io; do + short=${kind%%.*} + if kubectl get crd $kind >/dev/null 2>&1; then + kubectl get $kind -A > $DIR/$short.txt 2>&1 + kubectl get $kind -A --no-headers 2>/dev/null | awk 'NF >= 3 && $NF != "True" && $NF != "Ready"' | \ + while read NAMESPACE NAME _; do + d=$DIR/$short/$NAMESPACE/$NAME + mkdir -p $d + kubectl get $kind -n $NAMESPACE $NAME -o yaml > $d/$short.yaml 2>&1 + kubectl describe $kind -n $NAMESPACE $NAME > $d/describe.txt 2>&1 + done + fi +done + echo "Collecting pods..." kubectl get pod -A -o wide > $REPORT_DIR/kubernetes/pods.txt 2>&1 kubectl get pod -A --no-headers | awk '$4 !~ /Running|Succeeded|Completed/' | @@ -157,8 +231,28 @@ if kubectl get deploy -n cozy-linstor linstor-controller >/dev/null 2>&1; then kubectl exec -n cozy-linstor deploy/linstor-controller -- linstor --no-color r l > $DIR/resources.txt 2>&1 fi +# -- sandbox-host module + +echo "Collecting sandbox host context..." +DIR=$REPORT_DIR/sandbox-host +mkdir -p $DIR +df -h > $DIR/df.txt 2>&1 +free -m > $DIR/free.txt 2>&1 +ps auxww > $DIR/ps.txt 2>&1 +dmesg | tail -200 > $DIR/dmesg.txt 2>&1 || true +if [ -f /workspace/talosconfig ]; then + for node in 192.168.123.11 192.168.123.12 192.168.123.13; do + talosctl --talosconfig /workspace/talosconfig -n $node dmesg --tail=200 > $DIR/talos-$node-dmesg.txt 2>&1 || true + talosctl --talosconfig /workspace/talosconfig -n $node logs kubelet --tail=500 > $DIR/talos-$node-kubelet.log 2>&1 || true + talosctl --talosconfig /workspace/talosconfig -n $node logs containerd --tail=500 > $DIR/talos-$node-containerd.log 2>&1 || true + done +fi + # -- finalization +echo "Generating summary..." +hack/cozyreport-summary.sh > $REPORT_DIR/summary.txt 2>&1 || true + echo "Creating archive..." tar -czf $REPORT_NAME.tgz -C $REPORT_PDIR . echo "Report created: $REPORT_NAME.tgz" diff --git a/hack/dcgm-default-counters.csv b/hack/dcgm-default-counters.csv deleted file mode 100644 index b5e94540..00000000 --- a/hack/dcgm-default-counters.csv +++ /dev/null @@ -1,104 +0,0 @@ -# Snapshot of the upstream DCGM Exporter default-counters.csv used as a -# fixture by hack/check-gpu-recording-rules.bats. The test verifies that -# every DCGM_FI_* metric referenced by a tracked GPU dashboard is either -# declared here (upstream defaults) or in -# packages/system/gpu-operator/examples/dcgm-custom-metrics.yaml -# (the project's custom CSV). -# -# Source: https://github.com/NVIDIA/dcgm-exporter/blob/4.1.1-4.0.4/etc/default-counters.csv -# Pinned to the DCGM Exporter image tag shipped by the gpu-operator -# chart under packages/system/gpu-operator/charts/gpu-operator/values.yaml -# (dcgmExporter.version = 4.1.1-4.0.4-ubuntu22.04). When that image is -# bumped, refresh this file from the matching tag in the NVIDIA/dcgm-exporter -# repo and re-run `./hack/cozytest.sh hack/check-gpu-recording-rules.bats`. - -# Format -# If line starts with a '#' it is considered a comment -# DCGM FIELD, Prometheus metric type, help message - -# Clocks -DCGM_FI_DEV_SM_CLOCK, gauge, SM clock frequency (in MHz). -DCGM_FI_DEV_MEM_CLOCK, gauge, Memory clock frequency (in MHz). - -# Temperature -DCGM_FI_DEV_MEMORY_TEMP, gauge, Memory temperature (in C). -DCGM_FI_DEV_GPU_TEMP, gauge, GPU temperature (in C). - -# Power -DCGM_FI_DEV_POWER_USAGE, gauge, Power draw (in W). -DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION, counter, Total energy consumption since boot (in mJ). - -# PCIE -# DCGM_FI_PROF_PCIE_TX_BYTES, counter, Total number of bytes transmitted through PCIe TX via NVML. -# DCGM_FI_PROF_PCIE_RX_BYTES, counter, Total number of bytes received through PCIe RX via NVML. -DCGM_FI_DEV_PCIE_REPLAY_COUNTER, counter, Total number of PCIe retries. - -# Utilization (the sample period varies depending on the product) -DCGM_FI_DEV_GPU_UTIL, gauge, GPU utilization (in %). -DCGM_FI_DEV_MEM_COPY_UTIL, gauge, Memory utilization (in %). -DCGM_FI_DEV_ENC_UTIL, gauge, Encoder utilization (in %). -DCGM_FI_DEV_DEC_UTIL , gauge, Decoder utilization (in %). - -# Errors and violations -DCGM_FI_DEV_XID_ERRORS, gauge, Value of the last XID error encountered. -# DCGM_FI_DEV_POWER_VIOLATION, counter, Throttling duration due to power constraints (in us). -# DCGM_FI_DEV_THERMAL_VIOLATION, counter, Throttling duration due to thermal constraints (in us). -# DCGM_FI_DEV_SYNC_BOOST_VIOLATION, counter, Throttling duration due to sync-boost constraints (in us). -# DCGM_FI_DEV_BOARD_LIMIT_VIOLATION, counter, Throttling duration due to board limit constraints (in us). -# DCGM_FI_DEV_LOW_UTIL_VIOLATION, counter, Throttling duration due to low utilization (in us). -# DCGM_FI_DEV_RELIABILITY_VIOLATION, counter, Throttling duration due to reliability constraints (in us). - -# Memory usage -DCGM_FI_DEV_FB_FREE, gauge, Framebuffer memory free (in MiB). -DCGM_FI_DEV_FB_USED, gauge, Framebuffer memory used (in MiB). - -# ECC -# DCGM_FI_DEV_ECC_SBE_VOL_TOTAL, counter, Total number of single-bit volatile ECC errors. -# DCGM_FI_DEV_ECC_DBE_VOL_TOTAL, counter, Total number of double-bit volatile ECC errors. -# DCGM_FI_DEV_ECC_SBE_AGG_TOTAL, counter, Total number of single-bit persistent ECC errors. -# DCGM_FI_DEV_ECC_DBE_AGG_TOTAL, counter, Total number of double-bit persistent ECC errors. - -# Retired pages -# DCGM_FI_DEV_RETIRED_SBE, counter, Total number of retired pages due to single-bit errors. -# DCGM_FI_DEV_RETIRED_DBE, counter, Total number of retired pages due to double-bit errors. -# DCGM_FI_DEV_RETIRED_PENDING, counter, Total number of pages pending retirement. - -# NVLink -# DCGM_FI_DEV_NVLINK_CRC_FLIT_ERROR_COUNT_TOTAL, counter, Total number of NVLink flow-control CRC errors. -# DCGM_FI_DEV_NVLINK_CRC_DATA_ERROR_COUNT_TOTAL, counter, Total number of NVLink data CRC errors. -# DCGM_FI_DEV_NVLINK_REPLAY_ERROR_COUNT_TOTAL, counter, Total number of NVLink retries. -# DCGM_FI_DEV_NVLINK_RECOVERY_ERROR_COUNT_TOTAL, counter, Total number of NVLink recovery errors. -DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL, counter, Total number of NVLink bandwidth counters for all lanes. -# DCGM_FI_DEV_NVLINK_BANDWIDTH_L0, counter, The number of bytes of active NVLink rx or tx data including both header and payload. - -# VGPU License status -DCGM_FI_DEV_VGPU_LICENSE_STATUS, gauge, vGPU License status - -# Remapped rows -DCGM_FI_DEV_UNCORRECTABLE_REMAPPED_ROWS, counter, Number of remapped rows for uncorrectable errors -DCGM_FI_DEV_CORRECTABLE_REMAPPED_ROWS, counter, Number of remapped rows for correctable errors -DCGM_FI_DEV_ROW_REMAP_FAILURE, gauge, Whether remapping of rows has failed - -# Static configuration information. These appear as labels on the other metrics -DCGM_FI_DRIVER_VERSION, label, Driver Version -# DCGM_FI_NVML_VERSION, label, NVML Version -# DCGM_FI_DEV_BRAND, label, Device Brand -# DCGM_FI_DEV_SERIAL, label, Device Serial Number -# DCGM_FI_DEV_OEM_INFOROM_VER, label, OEM inforom version -# DCGM_FI_DEV_ECC_INFOROM_VER, label, ECC inforom version -# DCGM_FI_DEV_POWER_INFOROM_VER, label, Power management object inforom version -# DCGM_FI_DEV_INFOROM_IMAGE_VER, label, Inforom image version -# DCGM_FI_DEV_VBIOS_VERSION, label, VBIOS version of the device - -# Datacenter Profiling (DCP) metrics -# NOTE: supported on Nvidia datacenter Volta GPUs and newer -DCGM_FI_PROF_GR_ENGINE_ACTIVE, gauge, Ratio of time the graphics engine is active. -# DCGM_FI_PROF_SM_ACTIVE, gauge, The ratio of cycles an SM has at least 1 warp assigned. -# DCGM_FI_PROF_SM_OCCUPANCY, gauge, The ratio of number of warps resident on an SM. -DCGM_FI_PROF_PIPE_TENSOR_ACTIVE, gauge, Ratio of cycles the tensor (HMMA) pipe is active. -DCGM_FI_PROF_DRAM_ACTIVE, gauge, Ratio of cycles the device memory interface is active sending or receiving data. -# DCGM_FI_PROF_PIPE_FP64_ACTIVE, gauge, Ratio of cycles the fp64 pipes are active. -# DCGM_FI_PROF_PIPE_FP32_ACTIVE, gauge, Ratio of cycles the fp32 pipes are active. -# DCGM_FI_PROF_PIPE_FP16_ACTIVE, gauge, Ratio of cycles the fp16 pipes are active. -DCGM_FI_PROF_PCIE_TX_BYTES, gauge, The rate of data transmitted over the PCIe bus - including both protocol headers and data payloads - in bytes per second. -DCGM_FI_PROF_PCIE_RX_BYTES, gauge, The rate of data received over the PCIe bus - including both protocol headers and data payloads - in bytes per second. diff --git a/hack/e2e-apps/bucket.bats b/hack/e2e-apps/bucket.bats index 31e7efb0..a4a349d7 100644 --- a/hack/e2e-apps/bucket.bats +++ b/hack/e2e-apps/bucket.bats @@ -20,8 +20,11 @@ EOF # Wait for the bucket to be ready kubectl -n tenant-test wait hr bucket-${name} --timeout=100s --for=condition=ready + timeout 60 sh -ec "until kubectl -n tenant-test get bucketclaims.objectstorage.k8s.io bucket-${name} >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait bucketclaims.objectstorage.k8s.io bucket-${name} --timeout=300s --for=jsonpath='{.status.bucketReady}' + timeout 60 sh -ec "until kubectl -n tenant-test get bucketaccesses.objectstorage.k8s.io bucket-${name}-admin >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait bucketaccesses.objectstorage.k8s.io bucket-${name}-admin --timeout=300s --for=jsonpath='{.status.accessGranted}' + timeout 60 sh -ec "until kubectl -n tenant-test get bucketaccesses.objectstorage.k8s.io bucket-${name}-viewer >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait bucketaccesses.objectstorage.k8s.io bucket-${name}-viewer --timeout=300s --for=jsonpath='{.status.accessGranted}' # Get admin (readwrite) credentials diff --git a/hack/e2e-apps/clickhouse.bats b/hack/e2e-apps/clickhouse.bats index fbc9f906..733da0ff 100644 --- a/hack/e2e-apps/clickhouse.bats +++ b/hack/e2e-apps/clickhouse.bats @@ -35,9 +35,12 @@ spec: resources: {} resourcesPreset: "nano" EOF - sleep 5 + # Wait for the operator to materialise the HelmRelease before kubectl wait + # kicks in (kubectl wait errors immediately if the object does not exist yet). + timeout 60 sh -ec "until kubectl -n tenant-test get hr clickhouse-$name >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait hr clickhouse-$name --timeout=20s --for=condition=ready timeout 180 sh -ec "until kubectl -n tenant-test get svc chendpoint-clickhouse-$name -o jsonpath='{.spec.ports[*].port}' | grep -q '8123 9000'; do sleep 10; done" + timeout 60 sh -ec "until kubectl -n tenant-test get statefulset.apps/chi-clickhouse-$name-clickhouse-0-0 >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait statefulset.apps/chi-clickhouse-$name-clickhouse-0-0 --timeout=120s --for=jsonpath='{.status.replicas}'=1 timeout 80 sh -ec "until kubectl -n tenant-test get endpoints chi-clickhouse-$name-clickhouse-0-0 -o jsonpath='{.subsets[*].addresses[*].ip}' | grep -q '[0-9]'; do sleep 10; done" timeout 100 sh -ec "until kubectl -n tenant-test get svc chi-clickhouse-$name-clickhouse-0-0 -o jsonpath='{.spec.ports[*].port}' | grep -q '9000 8123 9009'; do sleep 10; done" diff --git a/hack/e2e-apps/external-dns.bats b/hack/e2e-apps/external-dns.bats index ca114af8..5775927f 100644 --- a/hack/e2e-apps/external-dns.bats +++ b/hack/e2e-apps/external-dns.bats @@ -18,7 +18,9 @@ spec: - example.com EOF - sleep 5 + # Wait for the operator to materialise the HelmRelease before kubectl wait + # kicks in (kubectl wait errors immediately if the object does not exist yet). + timeout 60 sh -ec "until kubectl -n tenant-test get hr ${name} >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait hr ${name} --timeout=120s --for=condition=ready kubectl -n tenant-test wait hr ${name}-system --timeout=120s --for=condition=ready timeout 60 sh -ec "until kubectl -n tenant-test get deploy -l app.kubernetes.io/instance=${name}-system -o jsonpath='{.items[0].status.readyReplicas}' | grep -q '1'; do sleep 5; done" @@ -41,7 +43,9 @@ spec: - example.org EOF - sleep 5 + # Wait for the operator to materialise the HelmRelease before kubectl wait + # kicks in (kubectl wait errors immediately if the object does not exist yet). + timeout 60 sh -ec "until kubectl -n tenant-test get hr ${name} >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait hr ${name} --timeout=120s --for=condition=ready kubectl -n tenant-test wait hr ${name}-system --timeout=120s --for=condition=ready timeout 60 sh -ec "until kubectl -n tenant-test get deploy -l app.kubernetes.io/instance=${name}-system -o jsonpath='{.items[0].status.readyReplicas}' | grep -q '1'; do sleep 5; done" diff --git a/hack/e2e-apps/foundationdb.bats b/hack/e2e-apps/foundationdb.bats index 199bda11..accfd8e1 100644 --- a/hack/e2e-apps/foundationdb.bats +++ b/hack/e2e-apps/foundationdb.bats @@ -42,7 +42,9 @@ spec: imageType: "unified" automaticReplacements: true EOF - sleep 15 + # Wait for the operator to materialise the HelmRelease before kubectl wait + # kicks in (kubectl wait errors immediately if the object does not exist yet). + timeout 60 sh -ec "until kubectl -n tenant-test get hr foundationdb-$name >/dev/null 2>&1; do sleep 2; done" # Wait for HelmRelease to be ready kubectl -n tenant-test wait hr foundationdb-$name --timeout=300s --for=condition=ready diff --git a/hack/e2e-apps/harbor.bats b/hack/e2e-apps/harbor.bats index 26f407ea..290fef42 100644 --- a/hack/e2e-apps/harbor.bats +++ b/hack/e2e-apps/harbor.bats @@ -38,12 +38,16 @@ spec: size: 1Gi replicas: 1 EOF - sleep 5 + # Wait for the operator to materialise the HelmRelease before kubectl wait + # kicks in (kubectl wait errors immediately if the object does not exist yet). + timeout 60 sh -ec "until kubectl -n tenant-test get hr $release >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait hr $release --timeout=60s --for=condition=ready # Wait for COSI to provision bucket + timeout 60 sh -ec "until kubectl -n tenant-test get bucketclaims.objectstorage.k8s.io $release-registry >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait bucketclaims.objectstorage.k8s.io $release-registry \ --timeout=300s --for=jsonpath='{.status.bucketReady}'=true + timeout 60 sh -ec "until kubectl -n tenant-test get bucketaccesses.objectstorage.k8s.io $release-registry >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait bucketaccesses.objectstorage.k8s.io $release-registry \ --timeout=60s --for=jsonpath='{.status.accessGranted}'=true @@ -64,8 +68,11 @@ EOF kubectl -n tenant-test get secret $release-registry-bucket -o jsonpath='{.data.BucketInfo}' 2>&1 | base64 -d 2>&1 || true false } + timeout 60 sh -ec "until kubectl -n tenant-test get deploy $release-core >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait deploy $release-core --timeout=120s --for=condition=available + timeout 60 sh -ec "until kubectl -n tenant-test get deploy $release-registry >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait deploy $release-registry --timeout=120s --for=condition=available + timeout 60 sh -ec "until kubectl -n tenant-test get deploy $release-portal >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait deploy $release-portal --timeout=120s --for=condition=available kubectl -n tenant-test get secret $release-credentials -o jsonpath='{.data.admin-password}' | base64 --decode | grep -q '.' kubectl -n tenant-test get secret $release-credentials -o jsonpath='{.data.url}' | base64 --decode | grep -q 'https://' diff --git a/hack/e2e-apps/kafka.bats b/hack/e2e-apps/kafka.bats index d85837bd..c1f0adf1 100644 --- a/hack/e2e-apps/kafka.bats +++ b/hack/e2e-apps/kafka.bats @@ -39,8 +39,11 @@ spec: partitions: 1 replicas: 2 EOF - sleep 5 + # Wait for the operator to materialise the HelmRelease before kubectl wait + # kicks in (kubectl wait errors immediately if the object does not exist yet). + timeout 60 sh -ec "until kubectl -n tenant-test get hr kafka-$name >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait hr kafka-$name --timeout=30s --for=condition=ready + timeout 60 sh -ec "until kubectl -n tenant-test get kafkas test >/dev/null 2>&1; do sleep 2; done" kubectl wait kafkas -n tenant-test test --timeout=300s --for=condition=ready timeout 60 sh -ec "until kubectl -n tenant-test get pvc data-kafka-$name-zookeeper-0; do sleep 10; done" kubectl -n tenant-test wait pvc data-kafka-$name-zookeeper-0 --timeout=50s --for=jsonpath='{.status.phase}'=Bound diff --git a/hack/e2e-apps/mariadb.bats b/hack/e2e-apps/mariadb.bats index 68f5fc00..35559216 100644 --- a/hack/e2e-apps/mariadb.bats +++ b/hack/e2e-apps/mariadb.bats @@ -35,13 +35,17 @@ spec: resources: {} resourcesPreset: "nano" EOF - sleep 5 + # Wait for the operator to materialise the HelmRelease before kubectl wait + # kicks in (kubectl wait errors immediately if the object does not exist yet). + timeout 60 sh -ec "until kubectl -n tenant-test get hr mariadb-$name >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait hr mariadb-$name --timeout=30s --for=condition=ready timeout 80 sh -ec "until kubectl -n tenant-test get svc mariadb-$name -o jsonpath='{.spec.ports[0].port}' | grep -q '3306'; do sleep 10; done" timeout 80 sh -ec "until kubectl -n tenant-test get endpoints mariadb-$name -o jsonpath='{.subsets[*].addresses[*].ip}' | grep -q '[0-9]'; do sleep 10; done" + timeout 60 sh -ec "until kubectl -n tenant-test get statefulset.apps/mariadb-$name >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait statefulset.apps/mariadb-$name --timeout=110s --for=jsonpath='{.status.replicas}'=2 timeout 80 sh -ec "until kubectl -n tenant-test get svc mariadb-$name-metrics -o jsonpath='{.spec.ports[0].port}' | grep -q '9104'; do sleep 10; done" timeout 40 sh -ec "until kubectl -n tenant-test get endpoints mariadb-$name-metrics -o jsonpath='{.subsets[*].addresses[*].ip}' | grep -q '[0-9]'; do sleep 10; done" + timeout 60 sh -ec "until kubectl -n tenant-test get deployment.apps/mariadb-$name-metrics >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait deployment.apps/mariadb-$name-metrics --timeout=90s --for=jsonpath='{.status.replicas}'=1 kubectl -n tenant-test delete mariadbs.apps.cozystack.io $name } diff --git a/hack/e2e-apps/mongodb.bats b/hack/e2e-apps/mongodb.bats index 7712fe01..bc83f7ab 100644 --- a/hack/e2e-apps/mongodb.bats +++ b/hack/e2e-apps/mongodb.bats @@ -26,7 +26,9 @@ spec: backup: enabled: false EOF - sleep 5 + # Wait for the operator to materialise the HelmRelease before kubectl wait + # kicks in (kubectl wait errors immediately if the object does not exist yet). + timeout 60 sh -ec "until kubectl -n tenant-test get hr mongodb-$name >/dev/null 2>&1; do sleep 2; done" # Wait for HelmRelease kubectl -n tenant-test wait hr mongodb-$name --timeout=60s --for=condition=ready # Wait for MongoDB service (port 27017) @@ -34,6 +36,7 @@ EOF # Wait for endpoints timeout 180 sh -ec "until kubectl -n tenant-test get endpoints mongodb-$name-rs0 -o jsonpath='{.subsets[*].addresses[*].ip}' | grep -q '[0-9]'; do sleep 10; done" # Wait for StatefulSet replicas + timeout 60 sh -ec "until kubectl -n tenant-test get statefulset.apps/mongodb-$name-rs0 >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait statefulset.apps/mongodb-$name-rs0 --timeout=300s --for=jsonpath='{.status.replicas}'=1 # Cleanup kubectl -n tenant-test delete mongodbs.apps.cozystack.io $name diff --git a/hack/e2e-apps/openbao.bats b/hack/e2e-apps/openbao.bats index 35271e55..168440ad 100644 --- a/hack/e2e-apps/openbao.bats +++ b/hack/e2e-apps/openbao.bats @@ -18,7 +18,9 @@ spec: external: false ui: true EOF - sleep 5 + # Wait for the operator to materialise the HelmRelease before kubectl wait + # kicks in (kubectl wait errors immediately if the object does not exist yet). + timeout 60 sh -ec "until kubectl -n tenant-test get hr openbao-$name >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait hr openbao-$name --timeout=60s --for=condition=ready kubectl -n tenant-test wait hr openbao-$name-system --timeout=120s --for=condition=ready @@ -53,7 +55,9 @@ EOF kubectl -n tenant-test exec openbao-$name-0 -- bao operator unseal "$unseal_key" # Now wait for pod to become ready (readiness probe checks seal status) + timeout 60 sh -ec "until kubectl -n tenant-test get sts openbao-$name >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait sts openbao-$name --timeout=90s --for=jsonpath='{.status.readyReplicas}'=1 + timeout 60 sh -ec "until kubectl -n tenant-test get pvc data-openbao-$name-0 >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait pvc data-openbao-$name-0 --timeout=50s --for=jsonpath='{.status.phase}'=Bound kubectl -n tenant-test delete openbao.apps.cozystack.io $name kubectl -n tenant-test delete pvc data-openbao-$name-0 --ignore-not-found diff --git a/hack/e2e-apps/postgres.bats b/hack/e2e-apps/postgres.bats index b86bc664..3ec79409 100644 --- a/hack/e2e-apps/postgres.bats +++ b/hack/e2e-apps/postgres.bats @@ -40,8 +40,11 @@ spec: resources: {} resourcesPreset: "nano" EOF - sleep 5 + # Wait for the operator to materialise the HelmRelease before kubectl wait + # kicks in (kubectl wait errors immediately if the object does not exist yet). + timeout 60 sh -ec "until kubectl -n tenant-test get hr postgres-$name >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait hr postgres-$name --timeout=100s --for=condition=ready + timeout 60 sh -ec "until kubectl -n tenant-test get job.batch postgres-$name-init-job >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait job.batch postgres-$name-init-job --timeout=50s --for=condition=Complete timeout 40 sh -ec "until kubectl -n tenant-test get svc postgres-$name-r -o jsonpath='{.spec.ports[0].port}' | grep -q '5432'; do sleep 10; done" timeout 40 sh -ec "until kubectl -n tenant-test get svc postgres-$name-ro -o jsonpath='{.spec.ports[0].port}' | grep -q '5432'; do sleep 10; done" diff --git a/hack/e2e-apps/qdrant.bats b/hack/e2e-apps/qdrant.bats index 9d37ce01..8a889e43 100755 --- a/hack/e2e-apps/qdrant.bats +++ b/hack/e2e-apps/qdrant.bats @@ -17,10 +17,14 @@ spec: resources: {} external: false EOF - sleep 5 + # Wait for the operator to materialise the HelmRelease before kubectl wait + # kicks in (kubectl wait errors immediately if the object does not exist yet). + timeout 60 sh -ec "until kubectl -n tenant-test get hr qdrant-$name >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait hr qdrant-$name --timeout=60s --for=condition=ready kubectl -n tenant-test wait hr qdrant-$name-system --timeout=120s --for=condition=ready + timeout 60 sh -ec "until kubectl -n tenant-test get sts qdrant-$name >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait sts qdrant-$name --timeout=90s --for=jsonpath='{.status.readyReplicas}'=1 + timeout 60 sh -ec "until kubectl -n tenant-test get pvc qdrant-storage-qdrant-$name-0 >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait pvc qdrant-storage-qdrant-$name-0 --timeout=50s --for=jsonpath='{.status.phase}'=Bound kubectl -n tenant-test delete qdrant.apps.cozystack.io $name } diff --git a/hack/e2e-apps/redis.bats b/hack/e2e-apps/redis.bats index 3ddff71a..eb148701 100644 --- a/hack/e2e-apps/redis.bats +++ b/hack/e2e-apps/redis.bats @@ -18,10 +18,15 @@ spec: resources: {} resourcesPreset: "nano" EOF - sleep 5 + # Wait for the operator to materialise the HelmRelease before kubectl wait + # kicks in (kubectl wait errors immediately if the object does not exist yet). + timeout 60 sh -ec "until kubectl -n tenant-test get hr redis-$name >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait hr redis-$name --timeout=20s --for=condition=ready + timeout 60 sh -ec "until kubectl -n tenant-test get pvc redisfailover-persistent-data-rfr-redis-$name-0 >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait pvc redisfailover-persistent-data-rfr-redis-$name-0 --timeout=50s --for=jsonpath='{.status.phase}'=Bound + timeout 60 sh -ec "until kubectl -n tenant-test get deploy rfs-redis-$name >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait deploy rfs-redis-$name --timeout=90s --for=condition=available + timeout 60 sh -ec "until kubectl -n tenant-test get sts rfr-redis-$name >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait sts rfr-redis-$name --timeout=90s --for=jsonpath='{.status.replicas}'=2 kubectl -n tenant-test delete redis.apps.cozystack.io $name } diff --git a/hack/e2e-apps/run-kubernetes.sh b/hack/e2e-apps/run-kubernetes.sh index 761ee57d..96975e16 100644 --- a/hack/e2e-apps/run-kubernetes.sh +++ b/hack/e2e-apps/run-kubernetes.sh @@ -227,6 +227,11 @@ EOF exit 1 fi + # TODO(e2e-replace-fixed-timeouts): genuine retry loop. This validates an + # external HTTP path (MetalLB-advertised LB IP -> in-tenant ingress -> + # backend pod) which is not visible to the Kubernetes API as a single + # condition, so kubectl wait cannot replace it. The 20x3s = 60s budget is + # capped with `lb_ok=false` then asserted below. lb_ok=false for i in $(seq 1 20); do echo "Attempt $i" diff --git a/hack/e2e-apps/vminstance.bats b/hack/e2e-apps/vminstance.bats index fa0f0b5b..2ff60271 100644 --- a/hack/e2e-apps/vminstance.bats +++ b/hack/e2e-apps/vminstance.bats @@ -2,8 +2,15 @@ @test "Create a VM Disk" { name='test' - kubectl -n tenant-test delete vminstances.apps.cozystack.io $name --ignore-not-found --timeout=2m || true - kubectl -n tenant-test delete vmdisks.apps.cozystack.io $name --ignore-not-found --timeout=2m || true + # Delete any leftover from a previous run and BLOCK until removal completes. + # `kubectl apply` on a resource still in finalizer-drain silently no-ops + # ("Detected changes to resource ... which is currently being deleted"), + # which then races a NotFound on the downstream HR wait. The previous + # `|| true` swallowed timeout errors here and let the test continue with + # the old VMDisk still draining. Bumped timeout to 3m and removed `|| true` + # so a true delete failure surfaces immediately. + kubectl -n tenant-test delete vminstances.apps.cozystack.io $name --ignore-not-found --timeout=3m + kubectl -n tenant-test delete vmdisks.apps.cozystack.io $name --ignore-not-found --timeout=3m kubectl apply -f - </dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait hr vm-disk-$name --timeout=5s --for=condition=ready + timeout 120 sh -ec "until kubectl -n tenant-test get dv vm-disk-$name >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait dv vm-disk-$name --timeout=250s --for=condition=ready + timeout 120 sh -ec "until kubectl -n tenant-test get pvc vm-disk-$name >/dev/null 2>&1; do sleep 2; done" kubectl -n tenant-test wait pvc vm-disk-$name --timeout=200s --for=jsonpath='{.status.phase}'=Bound } @test "Create a VM Instance" { diskName='test' name='test' - kubectl -n tenant-test delete vminstances.apps.cozystack.io $name --ignore-not-found --timeout=2m || true + # Same delete-finalizer-drain race as in "Create a VM Disk" above — + # block until removal completes, surface timeouts loudly. + kubectl -n tenant-test delete vminstances.apps.cozystack.io $name --ignore-not-found --timeout=3m kubectl apply -f - </dev/null 2>&1; do sleep 2; done" + # Nested KubeVirt VM startup (virt-launcher + libvirt + cloud-init DHCP) + # routinely takes 30-60s under runner load; the previous 20s was unrealistic + # and produced flakes. 120s is a comfortable upper bound for nested virt. + timeout 120 sh -ec "until kubectl -n tenant-test get vmi vm-instance-$name -o jsonpath='{.status.interfaces[0].ipAddress}' | grep -q '[0-9]'; do sleep 2; done" kubectl -n tenant-test wait hr vm-instance-$name --timeout=5s --for=condition=ready - kubectl -n tenant-test wait vm vm-instance-$name --timeout=20s --for=condition=ready - kubectl -n tenant-test delete vminstances.apps.cozystack.io $name - kubectl -n tenant-test delete vmdisks.apps.cozystack.io $diskName + # VM ready follows IP assignment closely; 60s gives buffer for the qemu-guest-agent. + timeout 120 sh -ec "until kubectl -n tenant-test get vm vm-instance-$name >/dev/null 2>&1; do sleep 2; done" + kubectl -n tenant-test wait vm vm-instance-$name --timeout=60s --for=condition=ready + kubectl -n tenant-test delete vminstances.apps.cozystack.io $name --timeout=3m + kubectl -n tenant-test delete vmdisks.apps.cozystack.io $diskName --timeout=3m } diff --git a/hack/e2e-install-cozystack.bats b/hack/e2e-install-cozystack.bats index cf66b73f..1f995825 100644 --- a/hack/e2e-install-cozystack.bats +++ b/hack/e2e-install-cozystack.bats @@ -7,12 +7,52 @@ fi } +@test "Pre-pull platform images" { + # Cluster-member workloads (OVN raft, LINSTOR) fail if replicas start at + # different times due to image-pull stagger across nodes. Pre-pull these + # images to every node so all replicas start with images already cached. + # + # Source images directly from the rendered charts so version bumps stay in + # sync automatically. yq walks every PodSpec-shaped object and emits the + # images of each container — this scopes the result to images the kubelet + # actually pulls (skips configmap fields and CRD examples that happen to + # contain an `image:` key). Add a chart here when a new peer-sensitive + # workload is found. + { + helm template packages/system/kubeovn + helm template packages/system/linstor + } | yq -N ' + (..|select(has("containers"))|.containers[]|.image), + (..|select(has("initContainers"))|.initContainers[]|.image) + ' | hack/e2e-prepull-images.sh +} + @test "Install Cozystack" { - # Install cozy-installer chart (operator installs CRDs on startup via --install-crds) + # Pre-create the cozy-system namespace with the labels the operator pod needs. + # The chart no longer ships a Namespace resource (helm v3's --create-namespace + # collides with chart-defined Namespace). On Talos clusters that enforce PSA + # baseline-by-default, the cozystack-operator pod (hostNetwork=true) is + # rejected unless the namespace is labelled enforce=privileged BEFORE the + # pod is scheduled. A pre-install hook can't bootstrap that label because + # the hook itself runs in the same namespace and faces the same PSA gate. + # So apply the namespace from the caller (this bats / production install docs) + # — same pattern as kube-prometheus-stack, argo-cd, cert-manager. + kubectl apply -f - <<'EOF' +apiVersion: v1 +kind: Namespace +metadata: + name: cozy-system + labels: + cozystack.io/system: "true" + cozystack.io/deletion-protected: "true" + pod-security.kubernetes.io/enforce: privileged +EOF + + # Install cozy-installer chart (operator installs CRDs on startup via --install-crds). helm upgrade installer packages/core/installer \ --install \ --namespace cozy-system \ - --create-namespace \ + --set cozystackOperator.helmReleaseInterval=30s \ --wait \ --timeout 2m @@ -51,11 +91,31 @@ spec: - cozystack.external-dns-application EOF + # Launch storage + LB configuration in the background. It waits for its + # own prerequisites (linstor-controller deploy, MetalLB CRDs) and finishes + # while the parallel HR wait below is still running, so the cost overlaps + # with the platform reconcile instead of compounding it. + hack/e2e-post-install-prep.sh > /tmp/post-install-prep.log 2>&1 & + POST_PREP_PID=$! + # Wait until HelmReleases appear & reconcile them timeout 180 sh -ec 'until [ $(kubectl get hr -A --no-headers 2>/dev/null | wc -l) -gt 10 ]; do sleep 1; done' + # TODO(e2e-replace-fixed-timeouts): genuine sleep. The threshold of 10 is a + # heuristic for "enough HRs visible to start waiting"; the snapshot below + # uses whatever HRs have appeared by then. There is no objective k8s API + # signal for "all platform HRs have been emitted" without hard-coding the + # expected list, so the 5s pad lets a few late-arrivals join the snapshot. sleep 5 kubectl get hr -A | awk 'NR>1 {print "kubectl wait --timeout=15m --for=condition=ready -n "$1" hr/"$2" &"} END {print "wait"}' | sh -ex + echo "Waiting for post-install-prep to complete" + if ! wait $POST_PREP_PID; then + cat /tmp/post-install-prep.log >&2 + echo "post-install-prep failed" >&2 + exit 1 + fi + cat /tmp/post-install-prep.log + # Fail the test if any HelmRelease is not Ready if kubectl get hr -A | grep -v " True " | grep -v NAME; then kubectl get hr -A @@ -69,80 +129,8 @@ EOF kubectl wait deployment/capi-controller-manager deployment/capi-kamaji-controller-manager deployment/capi-kubeadm-bootstrap-controller-manager deployment/capi-operator-cluster-api-operator deployment/capk-controller-manager -n cozy-cluster-api --timeout=2m --for=condition=available } -@test "Wait for LINSTOR and configure storage" { - # Linstor controller and nodes - kubectl wait deployment/linstor-controller -n cozy-linstor --timeout=5m --for=condition=available - timeout 60 sh -ec 'until [ $(kubectl exec -n cozy-linstor deploy/linstor-controller -- linstor node list | grep -c Online) -eq 3 ]; do sleep 1; done' - - created_pools=$(kubectl exec -n cozy-linstor deploy/linstor-controller -- linstor sp l -s data --pastable | awk '$2 == "data" {printf " " $4} END{printf " "}') - for node in srv1 srv2 srv3; do - case $created_pools in - *" $node "*) echo "Storage pool 'data' already exists on node $node"; continue;; - esac - kubectl exec -n cozy-linstor deploy/linstor-controller -- linstor ps cdp zfs ${node} /dev/vdc --pool-name data --storage-pool data - done - - # Storage classes - kubectl apply -f - <<'EOF' ---- -apiVersion: storage.k8s.io/v1 -kind: StorageClass -metadata: - name: local - annotations: - storageclass.kubernetes.io/is-default-class: "true" -provisioner: linstor.csi.linbit.com -parameters: - linstor.csi.linbit.com/storagePool: "data" - linstor.csi.linbit.com/layerList: "storage" - linstor.csi.linbit.com/allowRemoteVolumeAccess: "false" -volumeBindingMode: WaitForFirstConsumer -allowVolumeExpansion: true ---- -apiVersion: storage.k8s.io/v1 -kind: StorageClass -metadata: - name: replicated -provisioner: linstor.csi.linbit.com -parameters: - linstor.csi.linbit.com/storagePool: "data" - linstor.csi.linbit.com/autoPlace: "3" - linstor.csi.linbit.com/layerList: "drbd storage" - linstor.csi.linbit.com/allowRemoteVolumeAccess: "true" - property.linstor.csi.linbit.com/DrbdOptions/auto-quorum: suspend-io - property.linstor.csi.linbit.com/DrbdOptions/Resource/on-no-data-accessible: suspend-io - property.linstor.csi.linbit.com/DrbdOptions/Resource/on-suspended-primary-outdated: force-secondary - property.linstor.csi.linbit.com/DrbdOptions/Net/rr-conflict: retry-connect -volumeBindingMode: Immediate -allowVolumeExpansion: true -EOF -} - -@test "Wait for MetalLB and configure address pool" { - # MetalLB address pool - kubectl apply -f - <<'EOF' ---- -apiVersion: metallb.io/v1beta1 -kind: L2Advertisement -metadata: - name: cozystack - namespace: cozy-metallb -spec: - ipAddressPools: [cozystack] ---- -apiVersion: metallb.io/v1beta1 -kind: IPAddressPool -metadata: - name: cozystack - namespace: cozy-metallb -spec: - addresses: [192.168.123.200-192.168.123.250] - autoAssign: true - avoidBuggyIPs: false -EOF -} - @test "Check Cozystack API service" { + timeout 60 sh -ec 'until kubectl get apiservices/v1alpha1.apps.cozystack.io apiservices/v1alpha1.core.cozystack.io >/dev/null 2>&1; do sleep 2; done' kubectl wait --for=condition=Available apiservices/v1alpha1.apps.cozystack.io apiservices/v1alpha1.core.cozystack.io --timeout=2m } @@ -174,15 +162,22 @@ EOF kubectl wait deploy/root-ingress-controller -n tenant-root --timeout=5m --for=condition=available # etcd statefulset + timeout 60 sh -ec 'until kubectl get sts/etcd -n tenant-root >/dev/null 2>&1; do sleep 2; done' kubectl wait sts/etcd -n tenant-root --for=jsonpath='{.status.readyReplicas}'=3 --timeout=5m # VictoriaMetrics components + timeout 60 sh -ec 'until kubectl get vmalert/vmalert-shortterm -n tenant-root >/dev/null 2>&1; do sleep 2; done' + timeout 60 sh -ec 'until kubectl get vmalertmanager/alertmanager -n tenant-root >/dev/null 2>&1; do sleep 2; done' kubectl wait vmalert/vmalert-shortterm vmalertmanager/alertmanager -n tenant-root --for=jsonpath='{.status.updateStatus}'=operational --timeout=15m + timeout 60 sh -ec 'until kubectl get vlclusters/generic -n tenant-root >/dev/null 2>&1; do sleep 2; done' kubectl wait vlclusters/generic -n tenant-root --for=jsonpath='{.status.updateStatus}'=operational --timeout=5m + timeout 60 sh -ec 'until kubectl get vmcluster/shortterm vmcluster/longterm -n tenant-root >/dev/null 2>&1; do sleep 2; done' kubectl wait vmcluster/shortterm vmcluster/longterm -n tenant-root --for=jsonpath='{.status.updateStatus}'=operational --timeout=5m # Grafana + timeout 60 sh -ec 'until kubectl get clusters.postgresql.cnpg.io/grafana-db -n tenant-root >/dev/null 2>&1; do sleep 2; done' kubectl wait clusters.postgresql.cnpg.io/grafana-db -n tenant-root --for=condition=ready --timeout=5m + timeout 60 sh -ec 'until kubectl get deploy/grafana-deployment -n tenant-root >/dev/null 2>&1; do sleep 2; done' kubectl wait deploy/grafana-deployment -n tenant-root --for=condition=available --timeout=5m # Verify Grafana via ingress @@ -273,6 +268,7 @@ spec: seaweedfs: false EOF kubectl wait hr/tenant-test -n tenant-root --timeout=1m --for=condition=ready + timeout 60 sh -ec 'until kubectl get namespace tenant-test >/dev/null 2>&1; do sleep 2; done' kubectl wait namespace tenant-test --timeout=20s --for=jsonpath='{.status.phase}'=Active # Wait for ResourceQuota to appear and assert values timeout 60 sh -ec 'until [ "$(kubectl get quota -n tenant-test --no-headers 2>/dev/null | wc -l)" -ge 1 ]; do sleep 1; done' diff --git a/hack/e2e-post-install-prep.sh b/hack/e2e-post-install-prep.sh new file mode 100755 index 00000000..a5010503 --- /dev/null +++ b/hack/e2e-post-install-prep.sh @@ -0,0 +1,96 @@ +#!/bin/sh +# Runs LINSTOR pool + StorageClass + MetalLB pool configuration as soon as +# their respective prerequisites are reachable. Designed to run in the +# background during the platform HR reconcile wait, so its wall-clock cost +# overlaps with the wait instead of compounding it. +set -eu + +echo "[post-install-prep] waiting for linstor HelmRelease object to exist" +timeout 60 sh -ec 'until kubectl get hr/linstor -n cozy-linstor >/dev/null 2>&1; do sleep 2; done' + +echo "[post-install-prep] waiting for linstor HelmRelease to be Ready" +kubectl wait helmrelease/linstor -n cozy-linstor --for=condition=Ready --timeout=15m + +echo "[post-install-prep] waiting for linstor-controller deployment to exist" +timeout 300 sh -ec 'until kubectl get deploy/linstor-controller -n cozy-linstor >/dev/null 2>&1; do sleep 2; done' + +echo "[post-install-prep] waiting for linstor-controller to be Available" +kubectl wait deployment/linstor-controller -n cozy-linstor --timeout=5m --for=condition=available + +echo "[post-install-prep] waiting for 3 LINSTOR nodes Online" +# TODO(e2e-replace-fixed-timeouts): genuine poll. LINSTOR node membership is +# reported by the linstor binary inside the controller pod, not via a +# Kubernetes API condition, so kubectl wait cannot subscribe to it. +timeout 60 sh -ec 'until [ $(kubectl exec -n cozy-linstor deploy/linstor-controller -- linstor node list | grep -c Online) -eq 3 ]; do sleep 1; done' + +echo "[post-install-prep] creating LINSTOR storage pools (parallel across nodes)" +created_pools=$(kubectl exec -n cozy-linstor deploy/linstor-controller -- linstor sp l -s data --pastable | awk '$2 == "data" {printf " " $4} END{printf " "}') +for node in srv1 srv2 srv3; do + case $created_pools in + *" $node "*) echo " pool 'data' already exists on $node"; continue;; + esac + kubectl exec -n cozy-linstor deploy/linstor-controller -- linstor ps cdp zfs ${node} /dev/vdc --pool-name data --storage-pool data & +done +wait + +echo "[post-install-prep] applying StorageClasses" +kubectl apply -f - <<'EOF' +--- +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: local + annotations: + storageclass.kubernetes.io/is-default-class: "true" +provisioner: linstor.csi.linbit.com +parameters: + linstor.csi.linbit.com/storagePool: "data" + linstor.csi.linbit.com/layerList: "storage" + linstor.csi.linbit.com/allowRemoteVolumeAccess: "false" +volumeBindingMode: WaitForFirstConsumer +allowVolumeExpansion: true +--- +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: replicated +provisioner: linstor.csi.linbit.com +parameters: + linstor.csi.linbit.com/storagePool: "data" + linstor.csi.linbit.com/autoPlace: "3" + linstor.csi.linbit.com/layerList: "drbd storage" + linstor.csi.linbit.com/allowRemoteVolumeAccess: "true" + property.linstor.csi.linbit.com/DrbdOptions/auto-quorum: suspend-io + property.linstor.csi.linbit.com/DrbdOptions/Resource/on-no-data-accessible: suspend-io + property.linstor.csi.linbit.com/DrbdOptions/Resource/on-suspended-primary-outdated: force-secondary + property.linstor.csi.linbit.com/DrbdOptions/Net/rr-conflict: retry-connect +volumeBindingMode: Immediate +allowVolumeExpansion: true +EOF + +echo "[post-install-prep] waiting for MetalLB CRDs" +timeout 300 sh -ec 'until kubectl get crd ipaddresspools.metallb.io l2advertisements.metallb.io >/dev/null 2>&1; do sleep 2; done' + +echo "[post-install-prep] applying MetalLB IPAddressPool" +kubectl apply -f - <<'EOF' +--- +apiVersion: metallb.io/v1beta1 +kind: L2Advertisement +metadata: + name: cozystack + namespace: cozy-metallb +spec: + ipAddressPools: [cozystack] +--- +apiVersion: metallb.io/v1beta1 +kind: IPAddressPool +metadata: + name: cozystack + namespace: cozy-metallb +spec: + addresses: [192.168.123.200-192.168.123.250] + autoAssign: true + avoidBuggyIPs: false +EOF + +echo "[post-install-prep] done" diff --git a/hack/e2e-prepull-images.sh b/hack/e2e-prepull-images.sh new file mode 100755 index 00000000..e90d3a1b --- /dev/null +++ b/hack/e2e-prepull-images.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# Pre-pull images to all cluster nodes. +# +# Reads image references from stdin, one per line. Empty lines and lines +# starting with '#' are ignored. +# +# Some workloads (OVN raft, LINSTOR) are sensitive to peer-readiness at +# startup: if nodes pull the image at different speeds, one replica boots +# before its peers are reachable, exhausts its raft connection retries, and +# the HelmRelease install times out. Pre-pulling eliminates the pull stagger +# so all replicas start within milliseconds of each other. +# +# Implementation: a DaemonSet with one regular container per image (parallel +# pulls — total time = max of any one image rather than sum). kubectl rollout +# status blocks until all pods are Ready (= all containers running = all +# images cached on every node), then we delete the DaemonSet. + +set -euo pipefail + +mapfile -t images < <(grep -Ev '^[[:space:]]*(#|$)' | sort -u) + +if [[ ${#images[@]} -eq 0 ]]; then + echo "e2e-prepull-images: no images on stdin, nothing to do" >&2 + exit 0 +fi + +echo "Pre-pulling ${#images[@]} image(s):" +printf ' %s\n' "${images[@]}" + +containers="" +i=0 +for image in "${images[@]}"; do + containers+=" + - name: pull-${i} + image: ${image} + command: [\"sleep\", \"infinity\"] + imagePullPolicy: IfNotPresent + resources: + requests: + cpu: 1m + memory: 1Mi" + i=$((i + 1)) +done + +kubectl apply -f - < /dev/null ) } diff --git a/internal/operator/package_reconciler.go b/internal/operator/package_reconciler.go index 0e724e49..d13cce33 100644 --- a/internal/operator/package_reconciler.go +++ b/internal/operator/package_reconciler.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "strings" + "time" cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" helmv2 "github.com/fluxcd/helm-controller/api/v2" @@ -58,7 +59,12 @@ func parseCRDPolicy(install *cozyv1alpha1.ComponentInstall) helmv2.CRDsPolicy { // PackageReconciler reconciles Package resources type PackageReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + HelmReleaseInterval time.Duration + HelmReleaseRetryInterval time.Duration + HelmReleaseInstallTimeout time.Duration + HelmReleaseUpgradeTimeout time.Duration + HelmReleaseMaxHistory int } // +kubebuilder:rbac:groups=cozystack.io,resources=packages,verbs=get;list;watch;create;update;patch;delete @@ -214,22 +220,30 @@ func (r *PackageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct Labels: labels, }, Spec: helmv2.HelmReleaseSpec{ - Interval: metav1.Duration{Duration: 5 * 60 * 1000000000}, // 5m + Interval: metav1.Duration{Duration: r.HelmReleaseInterval}, + MaxHistory: &r.HelmReleaseMaxHistory, ChartRef: &helmv2.CrossNamespaceSourceReference{ Kind: "ExternalArtifact", Name: artifactName, Namespace: "cozy-system", }, Install: &helmv2.Install{ - Timeout: &metav1.Duration{Duration: 10 * 60 * 1000000000}, // 10m - Remediation: &helmv2.InstallRemediation{ - Retries: -1, + Timeout: &metav1.Duration{Duration: r.HelmReleaseInstallTimeout}, + // Strategy=RetryOnFailure (with RetryInterval) replaces the previous + // Remediation{Retries:-1} setup. Functionally equivalent ("retry forever + // on failure"), but decouples retry timing from spec.Interval so failed + // installs recover at HelmReleaseRetryInterval (default 30s) without + // also polling healthy releases at the same fast cadence. + Strategy: &helmv2.InstallStrategy{ + Name: string(helmv2.ActionStrategyRetryOnFailure), + RetryInterval: &metav1.Duration{Duration: r.HelmReleaseRetryInterval}, }, }, Upgrade: &helmv2.Upgrade{ - Timeout: &metav1.Duration{Duration: 10 * 60 * 1000000000}, // 10m - Remediation: &helmv2.UpgradeRemediation{ - Retries: -1, + Timeout: &metav1.Duration{Duration: r.HelmReleaseUpgradeTimeout}, + Strategy: &helmv2.UpgradeStrategy{ + Name: string(helmv2.ActionStrategyRetryOnFailure), + RetryInterval: &metav1.Duration{Duration: r.HelmReleaseRetryInterval}, }, CRDs: parseCRDPolicy(component.Install), }, diff --git a/packages/apps/harbor/templates/harbor.yaml b/packages/apps/harbor/templates/harbor.yaml index bf780967..32deb4cb 100644 --- a/packages/apps/harbor/templates/harbor.yaml +++ b/packages/apps/harbor/templates/harbor.yaml @@ -65,9 +65,15 @@ spec: name: {{ .Release.Name }}-credentials valuesKey: redis-password targetPath: harbor.redis.external.password + # BucketInfo is the JSON blob populated by the COSI BucketAccess controller. + # Merge it at the values root (no targetPath) so Flux unmarshals it as YAML + # instead of running it through Helm's strvals parser, which would split the + # JSON on commas. This also gates reconciliation on the Secret being ready and + # forces a config-digest change when its contents change. + - kind: Secret + name: {{ .Release.Name }}-registry-bucket + valuesKey: BucketInfo values: - bucket: - secretName: {{ .Release.Name }}-registry-bucket db: replicas: {{ .Values.database.replicas }} size: {{ .Values.database.size }} diff --git a/packages/apps/kubernetes/README.md b/packages/apps/kubernetes/README.md index bed1117a..ecbbc31b 100644 --- a/packages/apps/kubernetes/README.md +++ b/packages/apps/kubernetes/README.md @@ -128,9 +128,6 @@ See the reference for components utilized in this service: | `addons.gpuOperator` | NVIDIA GPU Operator. | `object` | `{}` | | `addons.gpuOperator.enabled` | Enable GPU Operator. | `bool` | `false` | | `addons.gpuOperator.valuesOverride` | Custom Helm values overrides. | `object` | `{}` | -| `addons.hami` | HAMi GPU virtualization middleware. | `object` | `{}` | -| `addons.hami.enabled` | Enable HAMi (requires GPU Operator). | `bool` | `false` | -| `addons.hami.valuesOverride` | Custom Helm values overrides. | `object` | `{}` | | `addons.fluxcd` | FluxCD GitOps operator. | `object` | `{}` | | `addons.fluxcd.enabled` | Enable FluxCD. | `bool` | `false` | | `addons.fluxcd.valuesOverride` | Custom Helm values overrides. | `object` | `{}` | diff --git a/packages/apps/kubernetes/templates/helmreleases/gpu-operator.yaml b/packages/apps/kubernetes/templates/helmreleases/gpu-operator.yaml index 655dc868..e243bfad 100644 --- a/packages/apps/kubernetes/templates/helmreleases/gpu-operator.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/gpu-operator.yaml @@ -1,11 +1,3 @@ -{{- define "cozystack.defaultGpuOperatorValues" -}} -{{- if .Values.addons.hami.enabled }} -gpu-operator: - devicePlugin: - enabled: false -{{- end }} -{{- end }} - {{- if and .Values.addons.gpuOperator.enabled .Values._namespace.etcd }} apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease @@ -37,12 +29,9 @@ spec: force: true remediation: retries: -1 - {{- $defaults := fromYaml (include "cozystack.defaultGpuOperatorValues" .) }} - {{- $overrides := deepCopy (default (dict) .Values.addons.gpuOperator.valuesOverride) }} - {{- $merged := mergeOverwrite (default (dict) $defaults) $overrides }} - {{- if $merged }} + {{- with .Values.addons.gpuOperator.valuesOverride }} values: - {{- toYaml $merged | nindent 4 }} + {{- toYaml . | nindent 4 }} {{- end }} dependsOn: diff --git a/packages/apps/kubernetes/templates/helmreleases/hami.yaml b/packages/apps/kubernetes/templates/helmreleases/hami.yaml deleted file mode 100644 index f1538c7c..00000000 --- a/packages/apps/kubernetes/templates/helmreleases/hami.yaml +++ /dev/null @@ -1,49 +0,0 @@ -{{- if and .Values.addons.hami.enabled .Values._namespace.etcd }} -{{- if not .Values.addons.gpuOperator.enabled }} -{{- fail "addons.hami requires addons.gpuOperator to be enabled" }} -{{- end }} -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: {{ .Release.Name }}-hami - labels: - cozystack.io/repository: system - cozystack.io/target-cluster-name: {{ .Release.Name }} - sharding.fluxcd.io/key: tenants -spec: - releaseName: hami - chartRef: - kind: ExternalArtifact - name: cozystack-kubernetes-application-kubevirt-kubernetes-hami - namespace: cozy-system - kubeConfig: - secretRef: - name: {{ .Release.Name }}-admin-kubeconfig - key: super-admin.svc - targetNamespace: cozy-hami - storageNamespace: cozy-hami - interval: 5m - timeout: 10m - install: - createNamespace: true - remediation: - retries: -1 - upgrade: - force: true - remediation: - retries: -1 - {{- with .Values.addons.hami.valuesOverride }} - values: - {{- toYaml . | nindent 4 }} - {{- end }} - - dependsOn: - {{- if lookup "helm.toolkit.fluxcd.io/v2" "HelmRelease" .Release.Namespace .Release.Name }} - - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} - {{- end }} - - name: {{ .Release.Name }}-cilium - namespace: {{ .Release.Namespace }} - - name: {{ .Release.Name }}-gpu-operator - namespace: {{ .Release.Namespace }} -{{- end }} diff --git a/packages/apps/kubernetes/tests/gpu_operator_hami_test.yaml b/packages/apps/kubernetes/tests/gpu_operator_hami_test.yaml deleted file mode 100644 index 44528470..00000000 --- a/packages/apps/kubernetes/tests/gpu_operator_hami_test.yaml +++ /dev/null @@ -1,99 +0,0 @@ -suite: GPU Operator HelmRelease HAMi integration tests -templates: - - templates/helmreleases/gpu-operator.yaml -values: - - values-ci.yaml -tests: - - it: should disable devicePlugin when hami is enabled - set: - addons: - gpuOperator: - enabled: true - valuesOverride: {} - hami: - enabled: true - valuesOverride: {} - asserts: - - equal: - path: spec.values.gpu-operator.devicePlugin.enabled - value: false - - - it: should not have values when hami is disabled and no overrides - set: - addons: - gpuOperator: - enabled: true - valuesOverride: {} - hami: - enabled: false - valuesOverride: {} - asserts: - - notExists: - path: spec.values - - - it: should apply hami defaults when valuesOverride key is omitted - set: - addons: - gpuOperator: - enabled: true - hami: - enabled: true - asserts: - - equal: - path: spec.values.gpu-operator.devicePlugin.enabled - value: false - - - it: should allow user overrides to merge with hami defaults - set: - addons: - gpuOperator: - enabled: true - valuesOverride: - gpu-operator: - driver: - enabled: false - hami: - enabled: true - valuesOverride: {} - asserts: - - equal: - path: spec.values.gpu-operator.devicePlugin.enabled - value: false - - equal: - path: spec.values.gpu-operator.driver.enabled - value: false - - - it: should let user explicitly override devicePlugin.enabled to true with hami enabled - set: - addons: - gpuOperator: - enabled: true - valuesOverride: - gpu-operator: - devicePlugin: - enabled: true - driver: - enabled: false - hami: - enabled: true - valuesOverride: {} - asserts: - - equal: - path: spec.values.gpu-operator.devicePlugin.enabled - value: true - - equal: - path: spec.values.gpu-operator.driver.enabled - value: false - - - it: should not render when gpuOperator is disabled - set: - addons: - gpuOperator: - enabled: false - valuesOverride: {} - hami: - enabled: false - valuesOverride: {} - asserts: - - hasDocuments: - count: 0 diff --git a/packages/apps/kubernetes/tests/hami_test.yaml b/packages/apps/kubernetes/tests/hami_test.yaml deleted file mode 100644 index 27f7c75b..00000000 --- a/packages/apps/kubernetes/tests/hami_test.yaml +++ /dev/null @@ -1,153 +0,0 @@ -suite: HAMi HelmRelease tests -templates: - - templates/helmreleases/hami.yaml -values: - - values-ci.yaml -tests: - - it: should not render when hami is disabled - set: - addons: - hami: - enabled: false - valuesOverride: {} - gpuOperator: - enabled: true - valuesOverride: {} - asserts: - - hasDocuments: - count: 0 - - - it: should render HelmRelease when hami is enabled - set: - addons: - hami: - enabled: true - valuesOverride: {} - gpuOperator: - enabled: true - valuesOverride: {} - asserts: - - hasDocuments: - count: 1 - - isKind: - of: HelmRelease - - - it: should fail when gpuOperator is not enabled - set: - addons: - hami: - enabled: true - valuesOverride: {} - gpuOperator: - enabled: false - valuesOverride: {} - asserts: - - failedTemplate: - errorMessage: "addons.hami requires addons.gpuOperator to be enabled" - - - it: should have correct metadata labels - set: - addons: - hami: - enabled: true - valuesOverride: {} - gpuOperator: - enabled: true - valuesOverride: {} - asserts: - - equal: - path: metadata.labels["cozystack.io/repository"] - value: system - - equal: - path: metadata.labels["sharding.fluxcd.io/key"] - value: tenants - - - it: should use ExternalArtifact chartRef - set: - addons: - hami: - enabled: true - valuesOverride: {} - gpuOperator: - enabled: true - valuesOverride: {} - asserts: - - equal: - path: spec.chartRef.kind - value: ExternalArtifact - - equal: - path: spec.chartRef.namespace - value: cozy-system - - - it: should target cozy-hami namespace - set: - addons: - hami: - enabled: true - valuesOverride: {} - gpuOperator: - enabled: true - valuesOverride: {} - asserts: - - equal: - path: spec.targetNamespace - value: cozy-hami - - equal: - path: spec.storageNamespace - value: cozy-hami - - - it: should depend on gpu-operator and cilium - release: - name: test - namespace: test-ns - set: - addons: - hami: - enabled: true - valuesOverride: {} - gpuOperator: - enabled: true - valuesOverride: {} - asserts: - - contains: - path: spec.dependsOn - content: - name: test-cilium - namespace: test-ns - - contains: - path: spec.dependsOn - content: - name: test-gpu-operator - namespace: test-ns - - - it: should not render spec.values when valuesOverride is empty - set: - addons: - hami: - enabled: true - valuesOverride: {} - gpuOperator: - enabled: true - valuesOverride: {} - asserts: - - hasDocuments: - count: 1 - - notExists: - path: spec.values - - - it: should pass through valuesOverride - set: - addons: - hami: - enabled: true - valuesOverride: - hami: - devicePlugin: - deviceSplitCount: 5 - gpuOperator: - enabled: true - valuesOverride: {} - asserts: - - equal: - path: spec.values.hami.devicePlugin.deviceSplitCount - value: 5 diff --git a/packages/apps/kubernetes/values.schema.json b/packages/apps/kubernetes/values.schema.json index a43deaec..81e9e2b8 100644 --- a/packages/apps/kubernetes/values.schema.json +++ b/packages/apps/kubernetes/values.schema.json @@ -149,7 +149,6 @@ "fluxcd", "gatewayAPI", "gpuOperator", - "hami", "ingressNginx", "monitoringAgents", "velero", @@ -269,28 +268,6 @@ } } }, - "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", diff --git a/packages/apps/kubernetes/values.yaml b/packages/apps/kubernetes/values.yaml index d609476c..68ba07b0 100644 --- a/packages/apps/kubernetes/values.yaml +++ b/packages/apps/kubernetes/values.yaml @@ -94,10 +94,6 @@ host: "" ## @field {bool} enabled - Enable FluxCD. ## @field {object} valuesOverride - Custom Helm values overrides. -## @typedef {struct} HAMiAddon - HAMi GPU virtualization middleware. -## @field {bool} enabled - Enable HAMi (requires GPU Operator). -## @field {object} valuesOverride - Custom Helm values overrides. - ## @typedef {struct} MonitoringAgentsAddon - Monitoring agents (Fluent Bit, VMAgents). ## @field {bool} enabled - Enable monitoring agents. ## @field {object} valuesOverride - Custom Helm values overrides. @@ -118,7 +114,6 @@ host: "" ## @field {GatewayAPIAddon} gatewayAPI - Gateway API addon. ## @field {IngressNginxAddon} ingressNginx - Ingress-NGINX controller. ## @field {GPUOperatorAddon} gpuOperator - NVIDIA GPU Operator. -## @field {HAMiAddon} hami - HAMi GPU virtualization middleware. ## @field {FluxCDAddon} fluxcd - FluxCD GitOps operator. ## @field {MonitoringAgentsAddon} monitoringAgents - Monitoring agents. ## @field {VerticalPodAutoscalerAddon} verticalPodAutoscaler - Vertical Pod Autoscaler. @@ -142,9 +137,6 @@ addons: gpuOperator: enabled: false valuesOverride: {} - hami: - enabled: false - valuesOverride: {} fluxcd: enabled: false valuesOverride: {} diff --git a/packages/apps/postgres/README.md b/packages/apps/postgres/README.md index 4cda7284..5a550f7a 100644 --- a/packages/apps/postgres/README.md +++ b/packages/apps/postgres/README.md @@ -133,13 +133,12 @@ See: ### Bootstrap (recovery) parameters -| Name | Description | Type | Value | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | -| `bootstrap` | Bootstrap configuration. | `object` | `{}` | -| `bootstrap.enabled` | Whether to restore from a backup. | `bool` | `false` | -| `bootstrap.recoveryTime` | Timestamp (RFC3339) for point-in-time recovery; empty means latest. | `string` | `""` | -| `bootstrap.oldName` | Previous cluster name before deletion. | `string` | `""` | -| `bootstrap.serverName` | Barman server name (S3 path prefix) used by the original cluster when writing backups. Set this only when the original cluster had an explicit barmanObjectStore.serverName that differed from its Kubernetes resource name. | `string` | `""` | +| Name | Description | Type | Value | +| ------------------------ | ------------------------------------------------------------------- | -------- | ------- | +| `bootstrap` | Bootstrap configuration. | `object` | `{}` | +| `bootstrap.enabled` | Whether to restore from a backup. | `bool` | `false` | +| `bootstrap.recoveryTime` | Timestamp (RFC3339) for point-in-time recovery; empty means latest. | `string` | `""` | +| `bootstrap.oldName` | Previous cluster name before deletion. | `string` | `""` | ## Parameter examples and reference diff --git a/packages/apps/postgres/templates/db.yaml b/packages/apps/postgres/templates/db.yaml index 2cdaec1d..5c40b2c7 100644 --- a/packages/apps/postgres/templates/db.yaml +++ b/packages/apps/postgres/templates/db.yaml @@ -32,9 +32,6 @@ spec: - name: {{ .Values.bootstrap.oldName }} barmanObjectStore: destinationPath: {{ .Values.backup.destinationPath }} - {{- if .Values.bootstrap.serverName }} - serverName: {{ .Values.bootstrap.serverName }} - {{- end }} endpointURL: {{ .Values.backup.endpointURL }} s3Credentials: accessKeyId: diff --git a/packages/apps/postgres/values.schema.json b/packages/apps/postgres/values.schema.json index 98e29822..b2a4aeba 100644 --- a/packages/apps/postgres/values.schema.json +++ b/packages/apps/postgres/values.schema.json @@ -254,11 +254,6 @@ "description": "Timestamp (RFC3339) for point-in-time recovery; empty means latest.", "type": "string", "default": "" - }, - "serverName": { - "description": "Barman server name (S3 path prefix) used by the original cluster when writing backups. Set this only when the original cluster had an explicit barmanObjectStore.serverName that differed from its Kubernetes resource name.", - "type": "string", - "default": "" } } } diff --git a/packages/apps/postgres/values.yaml b/packages/apps/postgres/values.yaml index 2ceaa9cf..b8f07f63 100644 --- a/packages/apps/postgres/values.yaml +++ b/packages/apps/postgres/values.yaml @@ -154,7 +154,6 @@ backup: ## @field {bool} enabled - Whether to restore from a backup. ## @field {string} [recoveryTime] - Timestamp (RFC3339) for point-in-time recovery; empty means latest. ## @field {string} oldName - Previous cluster name before deletion. -## @field {string} [serverName] - Barman server name (S3 path prefix) used by the original cluster when writing backups. Set this only when the original cluster had an explicit barmanObjectStore.serverName that differed from its Kubernetes resource name. ## @param {Bootstrap} bootstrap - Bootstrap configuration. bootstrap: @@ -162,4 +161,3 @@ bootstrap: # example: 2020-11-26 15:22:00.00000+00 recoveryTime: "" oldName: "" - serverName: "" diff --git a/packages/apps/tenant/Makefile b/packages/apps/tenant/Makefile index 2f51d836..3fe3810d 100644 --- a/packages/apps/tenant/Makefile +++ b/packages/apps/tenant/Makefile @@ -3,6 +3,3 @@ include ../../../hack/package.mk generate: cozyvalues-gen -m 'tenant' -v values.yaml -s values.schema.json -r README.md -g ../../../api/apps/v1alpha1/tenant/types.go ../../../hack/update-crd.sh - -test: - helm unittest . diff --git a/packages/apps/tenant/templates/cilium-lb-pool.yaml b/packages/apps/tenant/templates/cilium-lb-pool.yaml deleted file mode 100644 index 63dbcaca..00000000 --- a/packages/apps/tenant/templates/cilium-lb-pool.yaml +++ /dev/null @@ -1,28 +0,0 @@ -{{- $exposeMode := (index .Values._cluster "expose-mode") | default "externalIPs" }} -{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }} -{{- $exposeIPs := (index .Values._cluster "expose-external-ips") | default "" | nospace }} -{{- $ipsList := list }} -{{- range splitList "," $exposeIPs }} - {{- $ip := . | trim }} - {{- if $ip }} - {{- $ipsList = append $ipsList $ip }} - {{- end }} -{{- end }} -{{- $isPublishingIngressLB := and - (eq $exposeMode "loadBalancer") - (eq $exposeIngress .Release.Namespace) - .Values.ingress }} -{{- if and $isPublishingIngressLB $ipsList }} -apiVersion: cilium.io/v2 -kind: CiliumLoadBalancerIPPool -metadata: - name: {{ trimPrefix "tenant-" .Release.Namespace }}-exposure -spec: - blocks: - {{- range $ipsList }} - - cidr: {{ . }}{{ if not (contains "/" .) }}/{{ if contains ":" . }}128{{ else }}32{{ end }}{{ end }} - {{- end }} - serviceSelector: - matchLabels: - "io.kubernetes.service.namespace": {{ .Release.Namespace | quote }} -{{- end }} diff --git a/packages/apps/tenant/tests/exposure_test.yaml b/packages/apps/tenant/tests/exposure_test.yaml deleted file mode 100644 index e86fab5b..00000000 --- a/packages/apps/tenant/tests/exposure_test.yaml +++ /dev/null @@ -1,141 +0,0 @@ -suite: tenant CiliumLoadBalancerIPPool rendering for publishing.exposure=loadBalancer -templates: - - templates/cilium-lb-pool.yaml - -release: - name: tenant-root - namespace: tenant-root - -tests: - - it: default exposure (externalIPs) renders no pool - set: - ingress: true - _cluster: - expose-ingress: tenant-root - expose-external-ips: "192.0.2.10,192.0.2.11" - asserts: - - hasDocuments: - count: 0 - - - it: loadBalancer mode in publishing tenant with ingress=true renders v2 pool with namespace-only selector - set: - ingress: true - _cluster: - expose-ingress: tenant-root - expose-external-ips: "192.0.2.10,192.0.2.11" - expose-mode: loadBalancer - asserts: - - hasDocuments: - count: 1 - - equal: - path: apiVersion - value: cilium.io/v2 - - equal: - path: kind - value: CiliumLoadBalancerIPPool - - equal: - path: metadata.name - value: root-exposure - - equal: - path: spec.blocks - value: - - cidr: 192.0.2.10/32 - - cidr: 192.0.2.11/32 - - equal: - path: spec.serviceSelector.matchLabels["io.kubernetes.service.namespace"] - value: tenant-root - - notExists: - path: spec.serviceSelector.matchLabels["app.kubernetes.io/name"] - - - it: loadBalancer mode with IPv6 emits /128 CIDR - set: - ingress: true - _cluster: - expose-ingress: tenant-root - expose-external-ips: "2001:db8::1" - expose-mode: loadBalancer - asserts: - - equal: - path: spec.blocks - value: - - cidr: 2001:db8::1/128 - - - it: loadBalancer mode with mixed IPv4 and IPv6 emits correct CIDR per family - set: - ingress: true - _cluster: - expose-ingress: tenant-root - expose-external-ips: "192.0.2.10,2001:db8::1" - expose-mode: loadBalancer - asserts: - - equal: - path: spec.blocks - value: - - cidr: 192.0.2.10/32 - - cidr: 2001:db8::1/128 - - - it: loadBalancer mode accepts pre-CIDR input without double-suffixing - set: - ingress: true - _cluster: - expose-ingress: tenant-root - expose-external-ips: "192.0.2.10/32,2001:db8::1/128" - expose-mode: loadBalancer - asserts: - - equal: - path: spec.blocks - value: - - cidr: 192.0.2.10/32 - - cidr: 2001:db8::1/128 - - - it: loadBalancer mode filters out empty entries from externalIPs (trailing, leading, repeated commas) - set: - ingress: true - _cluster: - expose-ingress: tenant-root - expose-external-ips: "192.0.2.10,,192.0.2.11," - expose-mode: loadBalancer - asserts: - - hasDocuments: - count: 1 - - equal: - path: spec.blocks - value: - - cidr: 192.0.2.10/32 - - cidr: 192.0.2.11/32 - - - it: loadBalancer mode with ingress=false in publishing tenant renders no pool - set: - ingress: false - _cluster: - expose-ingress: tenant-root - expose-external-ips: "192.0.2.10" - expose-mode: loadBalancer - asserts: - - hasDocuments: - count: 0 - - - it: loadBalancer mode in a non-publishing tenant renders no pool - set: - ingress: true - _cluster: - expose-ingress: tenant-root - expose-external-ips: "192.0.2.10" - expose-mode: loadBalancer - release: - name: tenant-u1 - namespace: tenant-u1 - asserts: - - hasDocuments: - count: 0 - - - it: loadBalancer mode in publishing tenant with empty externalIPs renders no pool - set: - ingress: true - _cluster: - expose-ingress: tenant-root - expose-external-ips: "" - expose-mode: loadBalancer - asserts: - - hasDocuments: - count: 0 diff --git a/packages/apps/vm-instance/README.md b/packages/apps/vm-instance/README.md index 9d6a52b2..a2b6603e 100644 --- a/packages/apps/vm-instance/README.md +++ b/packages/apps/vm-instance/README.md @@ -36,32 +36,31 @@ virtctl ssh @ ### Common parameters -| Name | Description | Type | Value | -| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ----------- | -| `external` | Enable external access from outside the cluster. | `bool` | `false` | -| `externalMethod` | Method to pass through traffic to the VM. | `string` | `PortList` | -| `externalPorts` | Ports to forward from outside the cluster. | `[]int` | `[22]` | -| `externalAllowICMP` | Whether to accept ICMP traffic to the VM in PortList mode (preserves ping and PMTU discovery). No effect in WholeIP mode. Default true so ping behaves as users expect even when port filtering is in effect. | `bool` | `true` | -| `runStrategy` | Requested running state of the VirtualMachineInstance | `string` | `Always` | -| `instanceType` | Virtual Machine instance type. | `string` | `u1.medium` | -| `instanceProfile` | Virtual Machine preferences profile. | `string` | `ubuntu` | -| `disks` | List of disks to attach. | `[]object` | `[]` | -| `disks[i].name` | Disk name. | `string` | `""` | -| `disks[i].bus` | Disk bus type (e.g. "sata"). | `string` | `""` | -| `networks` | Networks to attach the VM to. | `[]object` | `[]` | -| `networks[i].name` | Network attachment name. | `string` | `""` | -| `subnets` | Deprecated: use networks instead. | `[]object` | `[]` | -| `subnets[i].name` | Network attachment name. | `string` | `""` | -| `gpus` | List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM). | `[]object` | `[]` | -| `gpus[i].name` | The name of the GPU resource to attach. | `string` | `""` | -| `cpuModel` | Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map | `string` | `""` | -| `resources` | Resource configuration for the virtual machine. | `object` | `{}` | -| `resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` | -| `resources.memory` | Amount of memory allocated. | `quantity` | `""` | -| `resources.sockets` | Number of CPU sockets (vCPU topology). | `quantity` | `""` | -| `sshKeys` | List of SSH public keys for authentication. | `[]string` | `[]` | -| `cloudInit` | Cloud-init user data. | `string` | `""` | -| `cloudInitSeed` | Seed string to generate SMBIOS UUID for the VM. | `string` | `""` | +| Name | Description | Type | Value | +| ------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------- | ----------- | +| `external` | Enable external access from outside the cluster. | `bool` | `false` | +| `externalMethod` | Method to pass through traffic to the VM. | `string` | `PortList` | +| `externalPorts` | Ports to forward from outside the cluster. | `[]int` | `[22]` | +| `runStrategy` | Requested running state of the VirtualMachineInstance | `string` | `Always` | +| `instanceType` | Virtual Machine instance type. | `string` | `u1.medium` | +| `instanceProfile` | Virtual Machine preferences profile. | `string` | `ubuntu` | +| `disks` | List of disks to attach. | `[]object` | `[]` | +| `disks[i].name` | Disk name. | `string` | `""` | +| `disks[i].bus` | Disk bus type (e.g. "sata"). | `string` | `""` | +| `networks` | Networks to attach the VM to. | `[]object` | `[]` | +| `networks[i].name` | Network attachment name. | `string` | `""` | +| `subnets` | Deprecated: use networks instead. | `[]object` | `[]` | +| `subnets[i].name` | Network attachment name. | `string` | `""` | +| `gpus` | List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM). | `[]object` | `[]` | +| `gpus[i].name` | The name of the GPU resource to attach. | `string` | `""` | +| `cpuModel` | Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map | `string` | `""` | +| `resources` | Resource configuration for the virtual machine. | `object` | `{}` | +| `resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` | +| `resources.memory` | Amount of memory allocated. | `quantity` | `""` | +| `resources.sockets` | Number of CPU sockets (vCPU topology). | `quantity` | `""` | +| `sshKeys` | List of SSH public keys for authentication. | `[]string` | `[]` | +| `cloudInit` | Cloud-init user data. | `string` | `""` | +| `cloudInitSeed` | Seed string to generate SMBIOS UUID for the VM. | `string` | `""` | ## U Series diff --git a/packages/apps/vm-instance/templates/service.yaml b/packages/apps/vm-instance/templates/service.yaml index cfeffa81..b7e3a420 100644 --- a/packages/apps/vm-instance/templates/service.yaml +++ b/packages/apps/vm-instance/templates/service.yaml @@ -9,10 +9,7 @@ metadata: {{- if .Values.external }} service.kubernetes.io/service-proxy-name: "cozy-proxy" annotations: - networking.cozystack.io/wholeIP: {{ ternary "true" "false" (eq .Values.externalMethod "WholeIP") | quote }} - {{- if eq .Values.externalMethod "PortList" }} - networking.cozystack.io/allowICMP: {{ ternary "true" "false" (ne .Values.externalAllowICMP false) | quote }} - {{- end }} + networking.cozystack.io/wholeIP: "true" {{- end }} spec: type: {{ ternary "LoadBalancer" "ClusterIP" .Values.external }} diff --git a/packages/apps/vm-instance/values.schema.json b/packages/apps/vm-instance/values.schema.json index 01bd30a9..34f7f634 100644 --- a/packages/apps/vm-instance/values.schema.json +++ b/packages/apps/vm-instance/values.schema.json @@ -26,11 +26,6 @@ "type": "integer" } }, - "externalAllowICMP": { - "description": "Whether to accept ICMP traffic to the VM in PortList mode (preserves ping and PMTU discovery). No effect in WholeIP mode. Default true so ping behaves as users expect even when port filtering is in effect.", - "type": "boolean", - "default": true - }, "runStrategy": { "description": "Requested running state of the VirtualMachineInstance", "type": "string", diff --git a/packages/apps/vm-instance/values.yaml b/packages/apps/vm-instance/values.yaml index 92e399c2..f07b75d3 100644 --- a/packages/apps/vm-instance/values.yaml +++ b/packages/apps/vm-instance/values.yaml @@ -31,9 +31,6 @@ externalMethod: PortList externalPorts: - 22 -## @param {bool} externalAllowICMP - Whether to accept ICMP traffic to the VM in PortList mode (preserves ping and PMTU discovery). No effect in WholeIP mode. Default true so ping behaves as users expect even when port filtering is in effect. -externalAllowICMP: true - ## @enum {string} RunStrategy - Requested running state of the VirtualMachineInstance ## @value Always - VMI should always be running ## @value Halted - VMI should never be running diff --git a/packages/core/installer/templates/cozystack-operator.yaml b/packages/core/installer/templates/cozystack-operator.yaml index aded0995..4b48bddd 100644 --- a/packages/core/installer/templates/cozystack-operator.yaml +++ b/packages/core/installer/templates/cozystack-operator.yaml @@ -4,16 +4,6 @@ {{- end -}} --- apiVersion: v1 -kind: Namespace -metadata: - name: cozy-system - labels: - cozystack.io/system: "true" - pod-security.kubernetes.io/enforce: privileged - annotations: - helm.sh/resource-policy: keep ---- -apiVersion: v1 kind: ServiceAccount metadata: name: cozystack @@ -68,6 +58,21 @@ spec: {{- if .Values.cozystackOperator.disableTelemetry }} - --disable-telemetry {{- end }} + {{- if .Values.cozystackOperator.helmReleaseInterval }} + - --helmrelease-interval={{ .Values.cozystackOperator.helmReleaseInterval }} + {{- end }} + {{- if .Values.cozystackOperator.helmReleaseRetryInterval }} + - --helmrelease-retry-interval={{ .Values.cozystackOperator.helmReleaseRetryInterval }} + {{- end }} + {{- if .Values.cozystackOperator.helmReleaseInstallTimeout }} + - --helmrelease-install-timeout={{ .Values.cozystackOperator.helmReleaseInstallTimeout }} + {{- end }} + {{- if .Values.cozystackOperator.helmReleaseUpgradeTimeout }} + - --helmrelease-upgrade-timeout={{ .Values.cozystackOperator.helmReleaseUpgradeTimeout }} + {{- end }} + {{- if .Values.cozystackOperator.helmReleaseMaxHistory }} + - --helmrelease-max-history={{ .Values.cozystackOperator.helmReleaseMaxHistory }} + {{- end }} - --platform-source-name=cozystack-platform - --platform-source-url={{ .Values.cozystackOperator.platformSourceUrl }} {{- if .Values.cozystackOperator.platformSourceRef }} diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index eef691ea..dcd0a913 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -4,6 +4,25 @@ cozystackOperator: image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.3.0@sha256:62574f12486bb40c901cf5ed484cca264405ce5810196d86555cbb27cce1ba48 platformSourceUrl: 'oci://ghcr.io/cozystack/cozystack/cozystack-packages' platformSourceRef: 'digest=sha256:a0b9ef938446b3132d3d22ad2262beb1027c48c9037b6c2346fdc2f19acd3036' + # When non-empty, overrides the operator's --helmrelease-interval flag + # (operator default: 5m). E2E sets this to 30s; production should leave empty. + helmReleaseInterval: "" + # When non-empty, overrides the operator's --helmrelease-retry-interval flag + # (operator default: 30s). Controls how fast failed install/upgrade attempts + # are retried. Decoupled from helmReleaseInterval (healthy reconcile cadence). + helmReleaseRetryInterval: "" + # When non-empty, overrides the operator's --helmrelease-install-timeout flag + # (operator default: 10m). Bounds individual k8s operations (job/hook/wait) + # during Helm install. Charts with long-running install hooks may need longer. + helmReleaseInstallTimeout: "" + # When non-empty, overrides the operator's --helmrelease-upgrade-timeout flag + # (operator default: 10m). Same semantics as helmReleaseInstallTimeout, for + # the Helm upgrade action. + helmReleaseUpgradeTimeout: "" + # When non-empty, overrides the operator's --helmrelease-max-history flag + # (operator default: 5). Number of release revisions Helm keeps per HR. + # Use 0 for unlimited; lower values reduce Secret accumulation in test clusters. + helmReleaseMaxHistory: "" # Generic variant configuration (only used when cozystackOperator.variant=generic) cozystack: # Kubernetes API server host (IP only, no protocol/port) diff --git a/packages/core/platform/sources/hami.yaml b/packages/core/platform/sources/hami.yaml deleted file mode 100644 index 0184e405..00000000 --- a/packages/core/platform/sources/hami.yaml +++ /dev/null @@ -1,24 +0,0 @@ ---- -apiVersion: cozystack.io/v1alpha1 -kind: PackageSource -metadata: - name: cozystack.hami -spec: - sourceRef: - kind: OCIRepository - name: cozystack-packages - namespace: cozy-system - path: / - variants: - - name: default - dependsOn: - - cozystack.gpu-operator - components: - - name: hami - path: system/hami - valuesFiles: - - values.yaml - install: - privileged: true - namespace: cozy-hami - releaseName: hami diff --git a/packages/core/platform/sources/kubernetes-application.yaml b/packages/core/platform/sources/kubernetes-application.yaml index 088383ad..9787cb19 100644 --- a/packages/core/platform/sources/kubernetes-application.yaml +++ b/packages/core/platform/sources/kubernetes-application.yaml @@ -52,8 +52,6 @@ spec: path: system/cilium - name: kubernetes-gpu-operator path: system/gpu-operator - - name: kubernetes-hami - path: system/hami - name: kubernetes-vertical-pod-autoscaler path: system/vertical-pod-autoscaler - name: kubernetes-prometheus-operator-crds diff --git a/packages/core/platform/templates/apps.yaml b/packages/core/platform/templates/apps.yaml index 6e1ae25a..7ee0de52 100644 --- a/packages/core/platform/templates/apps.yaml +++ b/packages/core/platform/templates/apps.yaml @@ -30,7 +30,6 @@ stringData: expose-services: {{ .Values.publishing.exposedServices | join "," | quote }} expose-ingress: {{ .Values.publishing.ingressName | quote }} expose-external-ips: {{ .Values.publishing.externalIPs | join "," | quote }} - expose-mode: {{ .Values.publishing.exposure | default "externalIPs" | quote }} cluster-domain: {{ .Values.networking.clusterDomain | quote }} api-server-endpoint: {{ .Values.publishing.apiServerEndpoint | quote }} {{- with .Values.branding }} diff --git a/packages/core/platform/templates/bundles/iaas.yaml b/packages/core/platform/templates/bundles/iaas.yaml index 66ec98b2..ced0a322 100644 --- a/packages/core/platform/templates/bundles/iaas.yaml +++ b/packages/core/platform/templates/bundles/iaas.yaml @@ -10,7 +10,6 @@ {{include "cozystack.platform.package.default" (list "cozystack.kubevirt-cdi" $) }} {{include "cozystack.platform.package.optional.default" (list "cozystack.vm-default-images" $) }} {{include "cozystack.platform.package.optional.default" (list "cozystack.gpu-operator" $) }} -{{include "cozystack.platform.package.optional.default" (list "cozystack.hami" $) }} {{include "cozystack.platform.package.default" (list "cozystack.kamaji" $) }} {{include "cozystack.platform.package.default" (list "cozystack.capi-operator" $) }} {{include "cozystack.platform.package.default" (list "cozystack.capi-provider-bootstrap-kubeadm" $) }} diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml index 6c587c82..43ea7266 100644 --- a/packages/core/platform/templates/bundles/system.yaml +++ b/packages/core/platform/templates/bundles/system.yaml @@ -105,6 +105,9 @@ {{- /* cozystack-api DaemonSet */ -}} {{- $apiValues := dict "cozystackAPI" (dict "nodeSelector" $genericNodeSelector) -}} {{- $_ := set $cozystackEngineComponents "cozystack-api" (dict "values" $apiValues) -}} +{{- /* lineage-controller-webhook DaemonSet */ -}} +{{- $lineageValues := dict "lineageControllerWebhook" (dict "nodeSelector" $genericNodeSelector) -}} +{{- $_ := set $cozystackEngineComponents "lineage-controller-webhook" (dict "values" $lineageValues) -}} {{- end -}} {{- if .Values.authentication.oidc.enabled }} {{include "cozystack.platform.package" (list "cozystack.cozystack-engine" "oidc" $ $cozystackEngineComponents) }} diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index 605e23bc..f33926db 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -45,41 +45,6 @@ publishing: - cdi-uploadproxy apiServerEndpoint: "" # example: "https://api.example.org" externalIPs: [] - # Exposure mode for the ingress-nginx Service. When "externalIPs" (current - # default) is selected, the Service is created as ClusterIP with - # Service.spec.externalIPs set from publishing.externalIPs. When - # "loadBalancer" is selected, the Service is type: LoadBalancer and a - # CiliumLoadBalancerIPPool makes those same addresses allocatable via LB IPAM. - # - # Service.spec.externalIPs is deprecated upstream in Kubernetes v1.36 - # (KEP-5707). The AllowServiceExternalIPs feature gate is expected to default - # to false around v1.40 and the implementation removed around v1.43 — switch - # to "loadBalancer" before upgrading past v1.40. - # - # Caveats for the "loadBalancer" mode: - # - publishing.externalIPs must contain at least one non-empty address, - # otherwise the chart render fails with an explicit error (a LoadBalancer - # Service without a pool would sit in forever). - # - The ingress-nginx Service is created with externalTrafficPolicy: Local - # to preserve the client source IP. Traffic arriving on a node that does - # not host an ingress-nginx pod is dropped, so the external IP must be - # routed to a node that runs the ingress pod (floating IP / keepalived / - # upstream router / podAntiAffinity). - # - Cilium does NOT announce the IP on its own unless L2 announcements or - # BGP are enabled in the Cilium values (disabled by default in Cozystack). - # This mode assumes the operator already routes the externalIPs to a - # cluster node; enabling announcements is out of scope for this setting. - # - Switching this value on a running cluster causes the ingress-nginx - # Service to be recreated (the HelmRelease has upgrade.force: true and - # the Service kind changes between ClusterIP and LoadBalancer). Expect a - # brief interruption of ingress traffic during the flip. - # - # Scope: this setting only controls the ingress-nginx Service. Other - # cozystack components that currently write Service.spec.externalIPs directly - # (e.g. the vpn app at packages/apps/vpn/templates/service.yaml) are NOT - # migrated by flipping this value and must be addressed separately before - # the AllowServiceExternalIPs feature gate flips to off in ~v1.40. - exposure: externalIPs # "externalIPs" or "loadBalancer" certificates: solver: http01 # "http01" or "dns01" issuerName: letsencrypt-prod diff --git a/packages/extra/etcd/Makefile b/packages/extra/etcd/Makefile index 2b3ed61e..f37d6e1e 100644 --- a/packages/extra/etcd/Makefile +++ b/packages/extra/etcd/Makefile @@ -5,6 +5,3 @@ include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md ../../../hack/update-crd.sh - -test: - helm unittest . diff --git a/packages/extra/etcd/templates/hook/job.yaml b/packages/extra/etcd/templates/hook/job.yaml new file mode 100644 index 00000000..3bf2f84b --- /dev/null +++ b/packages/extra/etcd/templates/hook/job.yaml @@ -0,0 +1,39 @@ +{{- $shouldUpdateCerts := true }} +{{- $configMap := lookup "v1" "ConfigMap" .Release.Namespace "etcd-deployed-version" }} +{{- if $configMap }} + {{- $deployedVersion := index $configMap "data" "version" }} + {{- if $deployedVersion | semverCompare ">= 2.6.1" }} + {{- $shouldUpdateCerts = false }} + {{- end }} +{{- end }} + +{{- if $shouldUpdateCerts }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: etcd-hook + annotations: + helm.sh/hook: post-upgrade + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +spec: + template: + metadata: + labels: + policy.cozystack.io/allow-to-apiserver: "true" + spec: + serviceAccountName: etcd-hook + containers: + - name: kubectl + image: docker.io/alpine/k8s:1.33.4 + command: + - sh + args: + - -exc + - |- + kubectl --namespace={{ .Release.Namespace }} delete secrets etcd-ca-tls etcd-peer-ca-tls + sleep 10 + kubectl --namespace={{ .Release.Namespace }} delete secrets etcd-client-tls etcd-peer-tls etcd-server-tls + kubectl --namespace={{ .Release.Namespace }} delete pods --selector=app.kubernetes.io/instance=etcd,app.kubernetes.io/managed-by=etcd-operator,app.kubernetes.io/name=etcd,cozystack.io/service=etcd + restartPolicy: Never +{{- end }} diff --git a/packages/extra/etcd/templates/hook/role.yaml b/packages/extra/etcd/templates/hook/role.yaml new file mode 100644 index 00000000..327eeadb --- /dev/null +++ b/packages/extra/etcd/templates/hook/role.yaml @@ -0,0 +1,26 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + annotations: + helm.sh/hook: post-upgrade + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + name: etcd-hook +rules: +- apiGroups: + - "" + resources: + - secrets + - pods + verbs: + - get + - list + - watch + - delete +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch diff --git a/packages/extra/etcd/templates/hook/rolebinding.yaml b/packages/extra/etcd/templates/hook/rolebinding.yaml new file mode 100644 index 00000000..0ee0ffd1 --- /dev/null +++ b/packages/extra/etcd/templates/hook/rolebinding.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: etcd-hook + annotations: + helm.sh/hook: post-upgrade + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: etcd-hook +subjects: + - kind: ServiceAccount + name: etcd-hook + namespace: {{ .Release.Namespace | quote }} diff --git a/packages/extra/etcd/templates/hook/serviceaccount.yaml b/packages/extra/etcd/templates/hook/serviceaccount.yaml new file mode 100644 index 00000000..552fb5fc --- /dev/null +++ b/packages/extra/etcd/templates/hook/serviceaccount.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: etcd-hook + annotations: + helm.sh/hook: post-upgrade + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded diff --git a/packages/extra/etcd/templates/version.yaml b/packages/extra/etcd/templates/version.yaml new file mode 100644 index 00000000..cc9375bb --- /dev/null +++ b/packages/extra/etcd/templates/version.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: etcd-deployed-version +data: + version: {{ .Chart.Version }} diff --git a/packages/extra/etcd/tests/no-post-upgrade-hook_test.yaml b/packages/extra/etcd/tests/no-post-upgrade-hook_test.yaml deleted file mode 100644 index 0e4f5aa3..00000000 --- a/packages/extra/etcd/tests/no-post-upgrade-hook_test.yaml +++ /dev/null @@ -1,34 +0,0 @@ -suite: etcd chart does not ship a destructive post-upgrade cert-regeneration hook - -release: - name: etcd - namespace: tenant-root - -templates: - - templates/check-release-name.yaml - - templates/dashboard-resourcemap.yaml - - templates/datastore.yaml - - templates/etcd-defrag.yaml - - templates/hook/job.yaml - - templates/podscrape.yaml - - templates/prometheus-rules.yaml - - templates/version.yaml - -tests: - - it: renders no Job named etcd-hook - documentSelector: - path: metadata.name - value: etcd-hook - skipEmptyTemplates: true - asserts: - - hasDocuments: - count: 0 - - - it: renders no ConfigMap named etcd-deployed-version - documentSelector: - path: metadata.name - value: etcd-deployed-version - skipEmptyTemplates: true - asserts: - - hasDocuments: - count: 0 diff --git a/packages/extra/ingress/Makefile b/packages/extra/ingress/Makefile index 65b53c03..958ce484 100644 --- a/packages/extra/ingress/Makefile +++ b/packages/extra/ingress/Makefile @@ -10,6 +10,3 @@ get-cloudflare-ips: generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md ../../../hack/update-crd.sh - -test: - helm unittest . diff --git a/packages/extra/ingress/README.md b/packages/extra/ingress/README.md index c541d8e9..0e786dfb 100644 --- a/packages/extra/ingress/README.md +++ b/packages/extra/ingress/README.md @@ -14,17 +14,3 @@ | `resources.memory` | Memory (RAM) available to each replica. | `quantity` | `""` | | `resourcesPreset` | Default sizing preset used when `resources` is omitted. | `string` | `micro` | - -## Exposure mode - -The ingress Service type is driven by the cluster-wide `publishing.exposure` value in the platform chart, not by any key in this package. Two modes exist: - -- `externalIPs` (default) has three rendered shapes: - - Release namespace matches `publishing.ingressName` AND `publishing.externalIPs` is non-empty → Service is `ClusterIP` with `Service.spec.externalIPs` set from that list and `externalTrafficPolicy: Cluster`. - - Release namespace matches `publishing.ingressName` but `publishing.externalIPs` is empty → Service falls back to `type: LoadBalancer` with `externalTrafficPolicy: Local`. - - Release namespace does not match `publishing.ingressName` (non-root tenants) → Service is `type: LoadBalancer` with `externalTrafficPolicy: Local`. - `Service.spec.externalIPs` is deprecated upstream in Kubernetes v1.36 (KEP-5707); plan migration before v1.40. -- `loadBalancer` — Service is `type: LoadBalancer` with `externalTrafficPolicy: Local`, and a `CiliumLoadBalancerIPPool` makes the addresses in `publishing.externalIPs` allocatable via Cilium LB IPAM. Requires `publishing.externalIPs` to contain at least one non-empty address (render fails otherwise) and assumes the addresses are already routed to a cluster node (floating IP / upstream router). See the inline comment on `publishing.exposure` in the platform chart for full caveats, including the note that switching the value on a running cluster causes the ingress Service to be recreated. - -This setting only migrates ingress-nginx away from `Service.spec.externalIPs`. Other cozystack components that use the same deprecated field (e.g. the `vpn` app) must be migrated separately before Kubernetes v1.40 flips the `AllowServiceExternalIPs` feature gate off. - diff --git a/packages/extra/ingress/templates/nginx-ingress.yaml b/packages/extra/ingress/templates/nginx-ingress.yaml index 8e1f00fb..ca50d276 100644 --- a/packages/extra/ingress/templates/nginx-ingress.yaml +++ b/packages/extra/ingress/templates/nginx-ingress.yaml @@ -1,19 +1,5 @@ {{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }} {{- $exposeExternalIPs := (index .Values._cluster "expose-external-ips") | default "" | nospace }} -{{- $exposeMode := (index .Values._cluster "expose-mode") | default "externalIPs" }} -{{- $exposeIPsList := list }} -{{- range splitList "," $exposeExternalIPs }} - {{- $ip := . | trim }} - {{- if $ip }} - {{- $exposeIPsList = append $exposeIPsList $ip }} - {{- end }} -{{- end }} -{{- if not (has $exposeMode (list "externalIPs" "loadBalancer")) }} -{{- fail (printf "unknown publishing.exposure mode %q: must be \"externalIPs\" or \"loadBalancer\"" $exposeMode) }} -{{- end }} -{{- if and (eq $exposeMode "loadBalancer") (eq $exposeIngress .Release.Namespace) (not $exposeIPsList) }} -{{- fail "publishing.exposure=loadBalancer requires publishing.externalIPs to contain at least one non-empty address: the CiliumLoadBalancerIPPool has nothing to advertise and the Service would stay in ." }} -{{- end }} apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: @@ -55,12 +41,9 @@ spec: enabled: false {{- end }} service: - {{- if and (eq $exposeIngress .Release.Namespace) (eq $exposeMode "loadBalancer") }} - type: LoadBalancer - externalTrafficPolicy: Local - {{- else if and (eq $exposeIngress .Release.Namespace) $exposeIPsList }} + {{- if and (eq $exposeIngress .Release.Namespace) $exposeExternalIPs }} externalIPs: - {{- toYaml $exposeIPsList | nindent 12 }} + {{- toYaml (splitList "," $exposeExternalIPs) | nindent 12 }} type: ClusterIP externalTrafficPolicy: Cluster {{- else }} diff --git a/packages/extra/ingress/tests/exposure_test.yaml b/packages/extra/ingress/tests/exposure_test.yaml deleted file mode 100644 index 1c76b3da..00000000 --- a/packages/extra/ingress/tests/exposure_test.yaml +++ /dev/null @@ -1,165 +0,0 @@ -suite: ingress exposure modes -templates: - - templates/nginx-ingress.yaml - -release: - name: ingress - namespace: tenant-root - -tests: - - it: default exposure (externalIPs) renders ClusterIP Service with spec.externalIPs - set: - _cluster: - expose-ingress: tenant-root - expose-external-ips: "192.0.2.10,192.0.2.11" - asserts: - - template: templates/nginx-ingress.yaml - equal: - path: spec.values.ingress-nginx.controller.service.type - value: ClusterIP - - template: templates/nginx-ingress.yaml - equal: - path: spec.values.ingress-nginx.controller.service.externalTrafficPolicy - value: Cluster - - template: templates/nginx-ingress.yaml - equal: - path: spec.values.ingress-nginx.controller.service.externalIPs - value: - - 192.0.2.10 - - 192.0.2.11 - - template: templates/nginx-ingress.yaml - notExists: - path: spec.values.ingress-nginx.controller.service.labels - - - it: legacy config without expose-mode falls back to externalIPs behavior - set: - _cluster: - expose-ingress: tenant-root - expose-external-ips: "192.0.2.10" - asserts: - - template: templates/nginx-ingress.yaml - equal: - path: spec.values.ingress-nginx.controller.service.type - value: ClusterIP - - - it: externalIPs mode in a namespace other than publishing.ingressName renders LoadBalancer fallback without externalIPs - set: - _cluster: - expose-ingress: tenant-root - expose-external-ips: "192.0.2.10" - release: - namespace: tenant-other - asserts: - - template: templates/nginx-ingress.yaml - equal: - path: spec.values.ingress-nginx.controller.service.type - value: LoadBalancer - - template: templates/nginx-ingress.yaml - equal: - path: spec.values.ingress-nginx.controller.service.externalTrafficPolicy - value: Local - - template: templates/nginx-ingress.yaml - notExists: - path: spec.values.ingress-nginx.controller.service.externalIPs - - - it: loadBalancer mode renders LoadBalancer Service without externalIPs on the Service - set: - _cluster: - expose-ingress: tenant-root - expose-external-ips: "192.0.2.10,192.0.2.11" - expose-mode: loadBalancer - asserts: - - template: templates/nginx-ingress.yaml - equal: - path: spec.values.ingress-nginx.controller.service.type - value: LoadBalancer - - template: templates/nginx-ingress.yaml - equal: - path: spec.values.ingress-nginx.controller.service.externalTrafficPolicy - value: Local - - template: templates/nginx-ingress.yaml - notExists: - path: spec.values.ingress-nginx.controller.service.labels - - template: templates/nginx-ingress.yaml - notExists: - path: spec.values.ingress-nginx.controller.service.externalIPs - - - it: loadBalancer mode without externalIPs fails chart render with explicit message - set: - _cluster: - expose-ingress: tenant-root - expose-external-ips: "" - expose-mode: loadBalancer - asserts: - - template: templates/nginx-ingress.yaml - failedTemplate: - errorMessage: "publishing.exposure=loadBalancer requires publishing.externalIPs to contain at least one non-empty address: the CiliumLoadBalancerIPPool has nothing to advertise and the Service would stay in ." - - - it: unknown exposure mode is rejected with a clear error (case-sensitive enum) - set: - _cluster: - expose-ingress: tenant-root - expose-external-ips: "192.0.2.10" - expose-mode: LoadBalancer - asserts: - - template: templates/nginx-ingress.yaml - failedTemplate: - errorMessage: "unknown publishing.exposure mode \"LoadBalancer\": must be \"externalIPs\" or \"loadBalancer\"" - - - it: another typo in exposure mode also fails - set: - _cluster: - expose-ingress: tenant-root - expose-external-ips: "192.0.2.10" - expose-mode: loadbalancer - asserts: - - template: templates/nginx-ingress.yaml - failedTemplate: - errorMessage: "unknown publishing.exposure mode \"loadbalancer\": must be \"externalIPs\" or \"loadBalancer\"" - - - it: loadBalancer mode in a namespace other than publishing.ingressName falls back to LoadBalancer Service without pool - set: - _cluster: - expose-ingress: tenant-root - expose-external-ips: "192.0.2.10" - expose-mode: loadBalancer - release: - namespace: tenant-other - asserts: - - template: templates/nginx-ingress.yaml - equal: - path: spec.values.ingress-nginx.controller.service.type - value: LoadBalancer - - template: templates/nginx-ingress.yaml - equal: - path: spec.values.ingress-nginx.controller.service.externalTrafficPolicy - value: Local - - template: templates/nginx-ingress.yaml - notExists: - path: spec.values.ingress-nginx.controller.service.labels - - template: templates/nginx-ingress.yaml - notExists: - path: spec.values.ingress-nginx.controller.service.externalIPs - - - it: loadBalancer mode with only-empty externalIPs fails chart render (comma-only input) - set: - _cluster: - expose-ingress: tenant-root - expose-external-ips: ",," - expose-mode: loadBalancer - asserts: - - template: templates/nginx-ingress.yaml - failedTemplate: - errorMessage: "publishing.exposure=loadBalancer requires publishing.externalIPs to contain at least one non-empty address: the CiliumLoadBalancerIPPool has nothing to advertise and the Service would stay in ." - - - it: externalIPs mode also filters out empty entries (trailing comma) - set: - _cluster: - expose-ingress: tenant-root - expose-external-ips: "192.0.2.10," - asserts: - - template: templates/nginx-ingress.yaml - equal: - path: spec.values.ingress-nginx.controller.service.externalIPs - value: - - 192.0.2.10 diff --git a/packages/system/cozy-proxy/charts/cozy-proxy/Chart.yaml b/packages/system/cozy-proxy/charts/cozy-proxy/Chart.yaml index 58f166f6..13352680 100644 --- a/packages/system/cozy-proxy/charts/cozy-proxy/Chart.yaml +++ b/packages/system/cozy-proxy/charts/cozy-proxy/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: cozy-proxy description: A simple kube-proxy addon for 1:1 NAT services in Kubernetes using an NFT backend type: application -version: 0.3.0 -appVersion: 0.3.0 +version: 0.2.0 +appVersion: 0.2.0 diff --git a/packages/system/cozy-proxy/charts/cozy-proxy/values.yaml b/packages/system/cozy-proxy/charts/cozy-proxy/values.yaml index e143e926..8cde5bed 100644 --- a/packages/system/cozy-proxy/charts/cozy-proxy/values.yaml +++ b/packages/system/cozy-proxy/charts/cozy-proxy/values.yaml @@ -1,6 +1,6 @@ image: repository: ghcr.io/cozystack/cozystack/cozy-proxy - tag: v0.3.0 + tag: v0.2.0 pullPolicy: IfNotPresent daemonset: diff --git a/packages/system/gpu-operator/examples/README.md b/packages/system/gpu-operator/examples/README.md deleted file mode 100644 index b6f170c8..00000000 --- a/packages/system/gpu-operator/examples/README.md +++ /dev/null @@ -1,121 +0,0 @@ -# GPU operator — native pod workload on Talos (reference) - -The files in this directory are **not** templates. They are reference -artifacts that document one working configuration for running GPU -workloads directly in pods on a Talos-based Cozystack cluster, together -with the DCGM metrics needed by the `gpu/gpu-performance` Grafana -dashboard. - -The out-of-the-box `values-talos.yaml` for this package targets the -sandbox (VFIO passthrough to KubeVirt VMs) scenario. The files here -illustrate an alternative — running CUDA workloads in regular pods with -the NVIDIA device plugin — and the workarounds it currently requires on -Talos. - -## Files - -- [`values-native-talos.yaml`](./values-native-talos.yaml) — Cozystack - `Package` values that disable sandbox workloads, enable the device - plugin, point `hostPaths.driverInstallDir` at the staging location - used by the compat DaemonSet, and wire DCGM to the custom metrics - ConfigMap. -- [`dcgm-custom-metrics.yaml`](./dcgm-custom-metrics.yaml) — `ConfigMap` - with a DCGM metrics CSV that adds profiling, ECC, throttling and - energy counters on top of the upstream defaults. The CSV is a - superset needed for full coverage of the `gpu/gpu-performance` - dashboard. Which parts are actually required depends on which - dashboards you ship — see the table below. -- [`nvidia-driver-compat.yaml`](./nvidia-driver-compat.yaml) — DaemonSet - that stages `libnvidia-ml.so.1` and `nvidia-smi` from the Talos glibc - tree into a path where the NVIDIA GPU Operator validator expects - them. See the "Why the compat DaemonSet exists" section below. - -## Why these are reference, not templates - -Shipping these as first-class templates would silently impose -assumptions that do not hold for every user: - -- Whether the NVIDIA Talos system extension is installed on the nodes. -- Whether GPUs are exposed directly to pods or passed through to VMs. -- The exact path the installed driver ends up at (depends on the - extension version and Talos release). - -The sandbox-oriented `values-talos.yaml` remains the default. Operators -who want native pod GPU workloads can start from this directory and -adapt as needed. - -## Why the compat DaemonSet exists - -The NVIDIA GPU Operator validator checks for `libnvidia-ml.so.1` and -`bin/nvidia-smi` in the path given by `hostPaths.driverInstallDir`. -Talos installs them under `/usr/local/glibc/usr/lib/` and -`/usr/local/bin/`, which the validator does not look at. Until upstream -addresses [NVIDIA/gpu-operator#1687][1], the DaemonSet copies those -files into a directory the validator does inspect and creates the -`.driver-ctr-ready` flag file so the validator proceeds. - -[1]: https://github.com/NVIDIA/gpu-operator/issues/1687 - -The compat DaemonSet runs privileged and bind-mounts host paths, so -the target namespace must allow privileged pods. On clusters that -enforce the Kubernetes Pod Security Standards at `baseline` or -`restricted`, label the namespace with -`pod-security.kubernetes.io/enforce: privileged` (and the matching -`audit`/`warn` labels if the admission webhook is configured to -surface violations) before applying the manifest. - -## Dashboards and what DCGM metrics they need - -Five GPU dashboards live under `gpu/*` in -`packages/system/monitoring/dashboards-infra.list`. All of them share -`packages/system/monitoring-agents/alerts/gpu-recording.rules.yaml` as -their source of aggregated series. The recording rules are safe to -ship on any cluster — they evaluate to empty series when DCGM is not -scraped, or when optional counters are missing. - -What each dashboard needs on top of the upstream DCGM Exporter -[`default-counters.csv`][default-csv]: - -| Dashboard | Scope | Needs beyond defaults | -| ----------------- | ---------------------------------- | ----------------------------------------------------------------------- | -| `gpu-performance` | Per-node, per-GPU deep dive | `DCGM_FI_DEV_POWER_VIOLATION`, `DCGM_FI_DEV_THERMAL_VIOLATION` | -| `gpu-efficiency` | Per-workload util vs tensor active | `DCGM_FI_DEV_POWER_VIOLATION`, `DCGM_FI_DEV_THERMAL_VIOLATION` (via `gpu:*_throttle_fraction:rate5m` recording rules) | -| `gpu-fleet` | Cluster-wide admin inventory | `DCGM_FI_DEV_POWER_MGMT_LIMIT` (for the TDP vs draw panel) | -| `gpu-quotas` | Kube-quota vs live usage | `kube_pod_container_resource_requests`, `kube_pod_status_phase`, `kube_node_status_allocatable` (via `namespace:gpu_count:allocated` / `cluster:gpu_count:*` recording rules) | -| `gpu-tenants` | Per-namespace tenant view | nothing (works on default counters) | - -`DCGM_FI_PROF_PIPE_TENSOR_ACTIVE` and `DCGM_FI_PROF_GR_ENGINE_ACTIVE` -are already in the upstream default set for the pinned DCGM Exporter -version, so the tensor-saturation and engine-active panels work without -any CSV override. The three counters listed in the table — throttling -violations and the power management limit — are the only extras the -tracked dashboards need. The recording rules in -`gpu-recording.rules.yaml` consume utilization, FB used, power, -temperature and the tensor-active profiling counter from the default -set, plus `DCGM_FI_DEV_POWER_VIOLATION` and -`DCGM_FI_DEV_THERMAL_VIOLATION` — used by the -`gpu.recording.efficiency.1m` group to derive the -`gpu:power_throttle_fraction:rate5m` and -`gpu:thermal_throttle_fraction:rate5m` series consumed by the -throttling panels on the efficiency and fleet dashboards. - -The `gpu.recording.throttle.validation.5m` group additionally ships the -`GPUThrottleFractionOverOne` alert (severity `warning`) as a regression -detector: it fires when either throttle-fraction series exceeds 1.0, -which would indicate that DCGM changed the scale/divisor of the -underlying violation counters and the recording rules need to be -re-derived. - -## Verification status - -The minimum-CSV claims above are verified by -`hack/check-gpu-recording-rules.bats`, which cross-checks every -`DCGM_FI_*` reference in the tracked GPU dashboards and recording rules -against the union of the upstream default set (snapshotted at -`hack/dcgm-default-counters.csv` for the pinned DCGM Exporter version) -and the custom CSV in `dcgm-custom-metrics.yaml`. When the DCGM -Exporter image in `packages/system/gpu-operator/charts/gpu-operator/values.yaml` -is bumped, refresh the snapshot from the matching tag of the -[`NVIDIA/dcgm-exporter`][default-csv] repository and rerun the test. - -[default-csv]: https://github.com/NVIDIA/dcgm-exporter/blob/main/etc/default-counters.csv diff --git a/packages/system/gpu-operator/examples/dcgm-custom-metrics.yaml b/packages/system/gpu-operator/examples/dcgm-custom-metrics.yaml deleted file mode 100644 index ef2b0b88..00000000 --- a/packages/system/gpu-operator/examples/dcgm-custom-metrics.yaml +++ /dev/null @@ -1,88 +0,0 @@ -# Custom DCGM Exporter metrics CSV. Referenced from -# examples/values-native-talos.yaml via dcgmExporter.config.name. -# -# Extends the upstream default set with profiling counters, ECC, page -# retirement, row remap, energy and throttling violations — everything -# the gpu/gpu-performance dashboard and the GPU recording rules in -# monitoring-agents expect. -apiVersion: v1 -kind: ConfigMap -metadata: - name: dcgm-custom-metrics - namespace: cozy-gpu-operator -data: - dcgm-metrics.csv: | - # Format - # If line starts with a '#' it is considered a comment - # DCGM FIELD, Prometheus metric type, help message - - # Identity - DCGM_FI_DRIVER_VERSION, label, Driver version. - - # Clocks - DCGM_FI_DEV_SM_CLOCK, gauge, SM clock frequency (in MHz). - DCGM_FI_DEV_MEM_CLOCK, gauge, Memory clock frequency (in MHz). - - # Temperature - DCGM_FI_DEV_MEMORY_TEMP, gauge, Memory temperature (in C). - DCGM_FI_DEV_GPU_TEMP, gauge, GPU temperature (in C). - - # Power - DCGM_FI_DEV_POWER_USAGE, gauge, Power draw (in W). - DCGM_FI_DEV_POWER_MGMT_LIMIT, gauge, Current power management limit (in W). - DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION, counter, Total energy consumption since boot (in mJ). - - # PCIE - DCGM_FI_DEV_PCIE_REPLAY_COUNTER, counter, Total number of PCIe retries. - - # Utilization (the sample period varies depending on the product) - DCGM_FI_DEV_GPU_UTIL, gauge, GPU utilization (in %). - DCGM_FI_DEV_MEM_COPY_UTIL, gauge, Memory utilization (in %). - DCGM_FI_DEV_ENC_UTIL, gauge, Encoder utilization (in %). - DCGM_FI_DEV_DEC_UTIL, gauge, Decoder utilization (in %). - - # Errors and violations - DCGM_FI_DEV_XID_ERRORS, gauge, Value of the last XID error encountered. - - # Memory usage - DCGM_FI_DEV_FB_FREE, gauge, Framebuffer memory free (in MiB). - DCGM_FI_DEV_FB_USED, gauge, Framebuffer memory used (in MiB). - DCGM_FI_DEV_FB_RESERVED, gauge, Framebuffer memory reserved (in MiB). - - # ECC (supported on datacenter-class GPUs such as A10) - DCGM_FI_DEV_ECC_SBE_VOL_TOTAL, counter, Total number of single-bit volatile ECC errors. - DCGM_FI_DEV_ECC_DBE_VOL_TOTAL, counter, Total number of double-bit volatile ECC errors. - DCGM_FI_DEV_ECC_SBE_AGG_TOTAL, counter, Total number of single-bit persistent ECC errors. - DCGM_FI_DEV_ECC_DBE_AGG_TOTAL, counter, Total number of double-bit persistent ECC errors. - - # Retired pages - DCGM_FI_DEV_RETIRED_SBE, counter, Total number of retired pages due to single-bit errors. - DCGM_FI_DEV_RETIRED_DBE, counter, Total number of retired pages due to double-bit errors. - DCGM_FI_DEV_RETIRED_PENDING, counter, Total number of pages pending retirement. - - # Row remapping (not applicable to GDDR6 but left for datacenter GPUs) - DCGM_FI_DEV_UNCORRECTABLE_REMAPPED_ROWS, counter, Number of remapped rows for uncorrectable errors. - DCGM_FI_DEV_CORRECTABLE_REMAPPED_ROWS, counter, Number of remapped rows for correctable errors. - DCGM_FI_DEV_ROW_REMAP_FAILURE, gauge, Whether remapping of rows has failed. - - # Throttle / violation counters (crucial for SLA and tenant monitoring) - DCGM_FI_DEV_POWER_VIOLATION, counter, Throttling duration due to power constraints (us per docs; ns on DCGM 3.x). - DCGM_FI_DEV_THERMAL_VIOLATION, counter, Throttling duration due to thermal constraints (us per docs; ns on DCGM 3.x). - DCGM_FI_DEV_SYNC_BOOST_VIOLATION, counter, Throttling duration due to sync-boost constraints (us per docs; ns on DCGM 3.x). - DCGM_FI_DEV_BOARD_LIMIT_VIOLATION, counter, Throttling duration due to board limit constraints (us per docs; ns on DCGM 3.x). - DCGM_FI_DEV_LOW_UTIL_VIOLATION, counter, Throttling duration due to low utilization (us per docs; ns on DCGM 3.x). - DCGM_FI_DEV_RELIABILITY_VIOLATION, counter, Throttling duration due to reliability constraints (us per docs; ns on DCGM 3.x). - - # NVLink — DCGM silently drops this metric on GPUs without NVLink, so - # enabling it unconditionally is safe and keeps this file reusable - # across GPU families. - DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL, counter, Total number of NVLink bandwidth counters for all lanes. - - # DCP (profiling) metrics — supported from Ampere onwards. - DCGM_FI_PROF_GR_ENGINE_ACTIVE, gauge, Ratio of time the graphics engine is active. - DCGM_FI_PROF_SM_ACTIVE, gauge, The ratio of cycles an SM has at least 1 warp assigned. - DCGM_FI_PROF_SM_OCCUPANCY, gauge, The ratio of number of warps resident on an SM. - DCGM_FI_PROF_PIPE_TENSOR_ACTIVE, gauge, Ratio of cycles the tensor (HMMA) pipe is active. - DCGM_FI_PROF_DRAM_ACTIVE, gauge, Ratio of cycles the device memory interface is active sending or receiving data. - DCGM_FI_PROF_PCIE_TX_BYTES, counter, The number of bytes of active PCIe tx (transmit) data including both header and payload. - DCGM_FI_PROF_PCIE_RX_BYTES, counter, The number of bytes of active PCIe rx (read) data including both header and payload. diff --git a/packages/system/gpu-operator/examples/nvidia-driver-compat.yaml b/packages/system/gpu-operator/examples/nvidia-driver-compat.yaml deleted file mode 100644 index fc9d5981..00000000 --- a/packages/system/gpu-operator/examples/nvidia-driver-compat.yaml +++ /dev/null @@ -1,109 +0,0 @@ -# Workaround for https://github.com/NVIDIA/gpu-operator/issues/1687 -# -# On Talos, the NVIDIA system extension installs driver files under -# /usr/local/glibc/usr/lib/ and /usr/local/bin/. The GPU Operator -# validator only looks for libnvidia-ml.so.1 and bin/nvidia-smi under -# the path configured as hostPaths.driverInstallDir. This DaemonSet -# stages the required files into /var/nvidia-driver on each node and -# creates the .driver-ctr-ready flag so the validator proceeds. -# -# Paired with examples/values-native-talos.yaml, which sets -# hostPaths.driverInstallDir to /var/nvidia-driver. -apiVersion: apps/v1 -kind: DaemonSet -metadata: - name: nvidia-driver-compat - namespace: cozy-gpu-operator - labels: - app: nvidia-driver-compat -spec: - selector: - matchLabels: - app: nvidia-driver-compat - template: - metadata: - labels: - app: nvidia-driver-compat - # DaemonSet rather than a one-shot Job: staging is idempotent per-node, - # must re-run after every Talos reboot (hostPath contents are wiped on - # reboot when the system extension re-installs), and the pause container - # keeps the pod visible for operator observability (kubectl get pods). - spec: - priorityClassName: system-node-critical - # Restrict to GPU nodes only. Without this the DaemonSet schedules onto - # every node (including control-plane and CPU-only workers) and burns - # resources on hosts where the compat tree is meaningless. - # The label is published by Node Feature Discovery / GPU Operator's NFD - # subchart; if NFD is disabled, replace this with whatever label your - # cluster uses to mark GPU hosts. - nodeSelector: - nvidia.com/gpu.present: "true" - # Tolerate-all is intentionally broad: the nodeSelector above already - # confines scheduling to GPU nodes, so the "Exists" toleration only - # matters when those nodes carry extra taints (dedicated=gpu:NoSchedule, - # nvidia.com/gpu:NoSchedule from the GPU Operator's default policy, - # etc.). This mirrors the stance of the upstream nvidia-driver-daemonset - # and nvidia-device-plugin DaemonSets — blanket tolerations plus a - # narrow nodeSelector. - tolerations: - - operator: Exists - initContainers: - - name: create-driver-tree - image: busybox:1.37 - command: - - sh - - -c - - | - set -e - GLIBC_LIB="/host/usr/local/glibc/usr/lib" - SRC_BIN="/host/usr/local/bin" - DST="/host/var/nvidia-driver" - - mkdir -p "$DST/bin" - - [ -f "$GLIBC_LIB/libnvidia-ml.so.1" ] || { - echo "missing $GLIBC_LIB/libnvidia-ml.so.1" >&2 - exit 1 - } - [ -f "$SRC_BIN/nvidia-smi" ] || { - echo "missing $SRC_BIN/nvidia-smi" >&2 - exit 1 - } - - cp -f "$GLIBC_LIB/libnvidia-ml.so.1" "$DST/libnvidia-ml.so.1" - echo "Copied libnvidia-ml.so.1" - - cp -f "$SRC_BIN/nvidia-smi" "$DST/bin/nvidia-smi" - chmod +x "$DST/bin/nvidia-smi" - echo "Copied nvidia-smi" - - mkdir -p /host/run/nvidia/validations - touch /host/run/nvidia/validations/.driver-ctr-ready - echo "Created driver-ctr-ready flag" - echo "Done" - securityContext: - privileged: true - resources: - requests: - cpu: 10m - memory: 16Mi - limits: - cpu: 100m - memory: 64Mi - volumeMounts: - - name: host-root - mountPath: /host - containers: - - name: pause - image: registry.k8s.io/pause:3.10 - resources: - requests: - cpu: 10m - memory: 8Mi - limits: - cpu: 50m - memory: 16Mi - volumes: - - name: host-root - hostPath: - path: / diff --git a/packages/system/gpu-operator/examples/values-native-talos.yaml b/packages/system/gpu-operator/examples/values-native-talos.yaml deleted file mode 100644 index ef7b2891..00000000 --- a/packages/system/gpu-operator/examples/values-native-talos.yaml +++ /dev/null @@ -1,52 +0,0 @@ -# Cozystack Package values for running GPU workloads natively in pods -# on Talos. This is the counterpart to values-talos.yaml, which targets -# the sandbox (VFIO passthrough) scenario. -# -# Prerequisites: -# - NVIDIA Talos system extension installed on GPU nodes. -# - examples/nvidia-driver-compat.yaml deployed to stage driver files -# where the gpu-operator validator looks for them. -# - examples/dcgm-custom-metrics.yaml applied so DCGM Exporter exports -# the full set of metrics used by the dashboard and recording rules. -apiVersion: cozystack.io/v1alpha1 -kind: Package -metadata: - name: cozystack.gpu-operator -spec: - variant: default - components: - gpu-operator: - values: - gpu-operator: - # The compat DaemonSet stages libnvidia-ml.so.1 and nvidia-smi - # under /var/nvidia-driver. Point the validator at that path. - hostPaths: - driverInstallDir: "/var/nvidia-driver" - # Disable the sandbox path — workloads run in pods, not VMs. - sandboxWorkloads: - enabled: false - vfioManager: - enabled: false - # The Talos extension provides the driver and runtime hooks, - # so the operator's own toolkit and driver components must be - # switched off to avoid conflicting installations. - toolkit: - enabled: false - devicePlugin: - enabled: true - # Export full set of DCGM metrics using the ConfigMap in - # examples/dcgm-custom-metrics.yaml. - dcgmExporter: - serviceMonitor: - enabled: true - # Matches the 1m evaluation cadence of GPU recording rules with - # 4x oversampling headroom. DCGM_FI_PROF_* counters have - # documented overhead concerns below 1s; 15s is safe. - interval: "15s" - honorLabels: true - relabelings: - - sourceLabels: [__meta_kubernetes_pod_node_name] - targetLabel: node - action: replace - config: - name: dcgm-custom-metrics diff --git a/packages/system/hami/Chart.yaml b/packages/system/hami/Chart.yaml deleted file mode 100644 index 3fcf4c5d..00000000 --- a/packages/system/hami/Chart.yaml +++ /dev/null @@ -1,3 +0,0 @@ -apiVersion: v2 -name: cozy-hami -version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/hami/Makefile b/packages/system/hami/Makefile deleted file mode 100644 index 83663a66..00000000 --- a/packages/system/hami/Makefile +++ /dev/null @@ -1,43 +0,0 @@ -export NAME=hami -export NAMESPACE=cozy-$(NAME) - -include ../../../hack/common-envs.mk -include ../../../hack/package.mk - -# When bumping the HAMi version, run `make update` and then review -# the resulting diff in `charts/hami/`. The recipe below reproduces the -# top-level vendoring overrides automatically: -# -# 1. Removes the broken hami-dra subchart. Upstream's NVIDIA DRA driver -# path requires kubelet DRA support that cozystack does not enable -# and has no upstream fix tracked. See commit 3c5521e. -# 2. Empties Chart.yaml dependencies and drops Chart.lock so Helm does -# not try to re-pull hami-dra at build time. See commit 2734dc0. -# 3. Strips dra/hami-dra/podSecurityPolicy blocks from the upstream -# values.yaml since the corresponding code paths are gone. PSP is -# removed from Kubernetes 1.25+ and is unused by cozystack. -# -# Template-level patches are NOT reproduced automatically: -# -# * Scheduler templates have `{{- if .Values.dra.enabled }}` blocks -# that need to be removed because the dra value is gone (commit -# 2734dc0 stripped them). -# * device-plugin/monitorservice.yaml uses `indent` with leading -# whitespace; it must be rewritten to `nindent` for the labels block -# to render correctly when devicePlugin.service.labels is set -# (commit 3685254). -# -# After `make update`, run `git diff -- charts/hami/templates/` and -# replay these template patches against the new upstream version, then -# verify with `helm unittest`. If upstream restructured the affected -# files, the patches may need to be redesigned rather than reapplied. - -update: - rm -rf charts - helm repo add hami-charts https://project-hami.github.io/HAMi/ - helm repo update hami-charts - helm pull hami-charts/hami --untar --untardir charts - rm -rf charts/hami/charts/hami-dra - yq --inplace '.dependencies = []' charts/hami/Chart.yaml - rm -f charts/hami/Chart.lock - yq --inplace 'del(.dra) | del(.["hami-dra"]) | del(.podSecurityPolicy)' charts/hami/values.yaml diff --git a/packages/system/hami/README.md b/packages/system/hami/README.md deleted file mode 100644 index 68669ac7..00000000 --- a/packages/system/hami/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# HAMi — GPU Virtualization Middleware - -[HAMi](https://github.com/Project-HAMi/HAMi) (Heterogeneous AI Computing Virtualization Middleware) is a CNCF Sandbox project that enables fractional GPU sharing in Kubernetes. It allows workloads to request specific amounts of GPU memory and compute cores instead of claiming entire GPUs. - -## Architecture - -HAMi consists of four components: - -- **MutatingWebhook** — intercepts pod creation, injects `schedulerName: hami-scheduler` -- **Scheduler Extender** — extends kube-scheduler with GPU-aware Filter and Bind logic -- **Device Plugin** (DaemonSet) — registers vGPU resources via the Kubernetes Device Plugin API -- **HAMi-core** (`libvgpu.so`) — `LD_PRELOAD` library injected into workload containers, intercepts CUDA API calls to enforce memory and compute isolation - -## Prerequisites - -- GPU Operator must be enabled (`addons.gpuOperator.enabled: true`) -- NVIDIA driver >= 440 on host nodes -- nvidia-container-toolkit configured as the default container runtime -- GPU nodes labeled with `gpu=on` - -## Known Limitations - -### glibc < 2.34 requirement for workload containers - -HAMi-core uses `LD_PRELOAD` to intercept `dlsym()` for CUDA symbol resolution. The fallback code path relies on `_dl_sym`, a private glibc internal symbol that was removed in glibc 2.34 when libdl and libpthread were merged into libc.so. - -**This limitation affects workload containers only**, not the host OS or HAMi's own components. - -| Distribution | glibc | Result | -| --------------- | ----- | -------------------------------------------- | -| Ubuntu 18.04 | 2.27 | Full isolation (memory + compute) | -| Ubuntu 20.04 | 2.31 | Full isolation (memory + compute) | -| Ubuntu 22.04 | 2.35 | Memory isolation works, compute breaks | -| Ubuntu 24.04 | 2.39 | Both memory and compute isolation break | -| Alpine (musl) | N/A | Completely incompatible (`dlvsym` absent) | - -Most modern ML/AI base images (CUDA 12.x, PyTorch 2.x, TensorFlow 2.x) use Ubuntu 22.04+ with glibc >= 2.35, which means compute isolation will not work with these images until the upstream fix is merged. - -**Upstream tracking issues:** - -- [HAMi-core#174](https://github.com/Project-HAMi/HAMi-core/issues/174) — `_dl_sym` removal in glibc 2.34 breaks HAMi-core's CUDA symbol resolution at the symbol level -- [HAMi#1190](https://github.com/Project-HAMi/HAMi/issues/1190) — maintainer thread confirming the empirical per-glibc-version isolation behavior shown in the table above - -### musl libc (Alpine) incompatibility - -HAMi-core is completely incompatible with musl libc. The `dlvsym()` function used by HAMi-core is a glibc extension not available in musl. Only glibc-based container images (Debian, Ubuntu, RHEL, etc.) can use HAMi GPU isolation. - -## Usage - -Enable HAMi in your tenant Kubernetes cluster values: - -```yaml -addons: - gpuOperator: - enabled: true - hami: - enabled: true -``` - -When HAMi is enabled, GPU Operator's built-in device plugin is automatically disabled to avoid conflicts. This default is preserved by setting `addons.gpuOperator.valuesOverride.gpu-operator.devicePlugin.enabled: false`; advanced topologies that partition GPU pools (e.g. some nodes use HAMi while others run the standard NVIDIA device plugin via node selectors) can re-enable it explicitly through `valuesOverride`. - -### Requesting fractional GPU resources - -```yaml -resources: - limits: - nvidia.com/gpu: 1 - nvidia.com/gpumem: 3000 # 3000 MB of GPU memory - nvidia.com/gpucores: 30 # 30% of GPU compute cores -``` - -## Parameters - -Default values shown below are inherited from the upstream HAMi chart and may change with upstream updates. - -| Name | Description | Default | -| --- | --- | --- | -| `hami.devicePlugin.runtimeClassName` | RuntimeClass for device plugin pods | `nvidia` | -| `hami.devicePlugin.deviceSplitCount` | Max virtual GPUs per physical GPU | `10` | -| `hami.devicePlugin.deviceMemoryScaling` | Memory overcommit factor (> 1.0 enables overcommit) | `1` | -| `hami.scheduler.defaultSchedulerPolicy.nodeSchedulerPolicy` | Node packing strategy (`binpack` or `spread`) | `binpack` | -| `hami.scheduler.defaultSchedulerPolicy.gpuSchedulerPolicy` | GPU packing strategy (`binpack` or `spread`) | `spread` | diff --git a/packages/system/hami/charts/hami/Chart.yaml b/packages/system/hami/charts/hami/Chart.yaml deleted file mode 100644 index 55f32ab6..00000000 --- a/packages/system/hami/charts/hami/Chart.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: v2 -appVersion: 2.8.1 -dependencies: [] -description: Heterogeneous AI Computing Virtualization Middleware -keywords: -- vgpu -- gpu -kubeVersion: '>= 1.18.0-0' -maintainers: -- email: archlitchi@gmail.com - name: limengxuan -- email: xiaozhang0210@hotmail.com - name: zhangxiao -name: hami -sources: -- https://github.com/Project-HAMi/HAMi -type: application -version: 2.8.1 diff --git a/packages/system/hami/charts/hami/README.md b/packages/system/hami/charts/hami/README.md deleted file mode 100644 index e8217290..00000000 --- a/packages/system/hami/charts/hami/README.md +++ /dev/null @@ -1,237 +0,0 @@ -# HAMi Helm Chart Values Documentation - -This document provides detailed descriptions of all configurable values parameters for the HAMi Helm Chart. - -## Global Configuration - -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `global.imageRegistry` | Global Docker image registry | `""` | -| `global.imagePullSecrets` | Global Docker image pull secrets | `[]` | -| `global.imageTag` | Image tag | `"v2.8.1"` | -| `global.gpuHookPath` | GPU Hook path | `/usr/local` | -| `global.labels` | Global labels | `{}` | -| `global.annotations` | Global annotations | `{}` | -| `global.managedNodeSelectorEnable` | Whether to enable managed node selector | `false` | -| `global.managedNodeSelector.usage` | Managed node selector usage | `"gpu"` | -| `nameOverride` | Name override | `""` | -| `fullnameOverride` | Full name override | `""` | -| `namespaceOverride` | Namespace override | `""` | - -## Resource Name Configuration - -### NVIDIA GPU Resources -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `resourceName` | GPU resource name | `"nvidia.com/gpu"` | -| `resourceMem` | GPU memory resource name | `"nvidia.com/gpumem"` | -| `resourceMemPercentage` | GPU memory percentage resource name | `"nvidia.com/gpumem-percentage"` | -| `resourceCores` | GPU core resource name | `"nvidia.com/gpucores"` | -| `resourcePriority` | GPU priority resource name | `"nvidia.com/priority"` | - -### Cambricon MLU Resources -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `mluResourceName` | MLU resource name | `"cambricon.com/vmlu"` | -| `mluResourceMem` | MLU memory resource name | `"cambricon.com/mlu.smlu.vmemory"` | -| `mluResourceCores` | MLU core resource name | `"cambricon.com/mlu.smlu.vcore"` | - -### Hygon DCU Resources -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `dcuResourceName` | DCU resource name | `"hygon.com/dcunum"` | -| `dcuResourceMem` | DCU memory resource name | `"hygon.com/dcumem"` | -| `dcuResourceCores` | DCU core resource name | `"hygon.com/dcucores"` | - -### Metax GPU Resources -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `metaxResourceName` | GPU resource name | `"metax-tech.com/sgpu"` | -| `metaxResourceCore` | GPU core resource name | `"metax-tech.com/vcore"` | -| `metaxResourceMem` | GPU memory resource name | `"metax-tech.com/vmemory"` | -| `metaxsGPUTopologyAware` | GPU topology awareness | `"false"` | - -### Enflame GCU Resources -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `enflameResourceNameVGCU` | vGCU resource name | `"enflame.com/vgcu"` | -| `enflameResourceNameVGCUPercentage` | vGCU percentage resource name | `"enflame.com/vgcu-percentage"` | - -### Kunlunxin XPU Resources -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `kunlunResourceName` | XPU resource name | `"kunlunxin.com/xpu"` | - -## Scheduler Configuration - -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `schedulerName` | Scheduler name | `"hami-scheduler"` | -| `scheduler.nodeName` | Define node name, scheduler will schedule to this node | `""` | -| `scheduler.overwriteEnv` | Whether to overwrite environment variables | `"false"` | -| `scheduler.defaultSchedulerPolicy.nodeSchedulerPolicy` | Node scheduler policy | `binpack` | -| `scheduler.defaultSchedulerPolicy.gpuSchedulerPolicy` | GPU scheduler policy | `spread` | -| `scheduler.metricsBindAddress` | Metrics bind address | `":9395"` | -| `scheduler.forceOverwriteDefaultScheduler` | Whether to force overwrite default scheduler | `true` | -| `scheduler.livenessProbe` | Whether to enable liveness probe | `false` | -| `scheduler.leaderElect` | Whether to enable leader election | `true` | -| `scheduler.replicas` | Number of replicas | `1` | - -### Kube Scheduler Configuration - -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `scheduler.kubeScheduler.enabled` | Whether to run kube-scheduler container in scheduler pod | `true` | -| `scheduler.kubeScheduler.image.registry` | Kube scheduler image registry | `"registry.cn-hangzhou.aliyuncs.com"` | -| `scheduler.kubeScheduler.image.repository` | Kube scheduler image repository | `"google_containers/kube-scheduler"` | -| `scheduler.kubeScheduler.image.tag` | Kube scheduler image tag | `""` | -| `scheduler.kubeScheduler.image.pullPolicy` | Kube scheduler image pull policy | `IfNotPresent` | -| `scheduler.kubeScheduler.image.pullSecrets` | Kube scheduler image pull secrets | `[]` | -| `scheduler.kubeScheduler.extraNewArgs` | Extra new arguments | `["--config=/config/config.yaml", "-v=4"]` | -| `scheduler.kubeScheduler.extraArgs` | Extra arguments | `["--policy-config-file=/config/config.json", "-v=4"]` | - -### Extender Configuration - -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `scheduler.extender.image.registry` | Scheduler extender image registry | `"docker.io"` | -| `scheduler.extender.image.repository` | Scheduler extender image repository | `"projecthami/hami"` | -| `scheduler.extender.image.tag` | Scheduler extender image tag | `""` | -| `scheduler.extender.image.pullPolicy` | Scheduler extender image pull policy | `IfNotPresent` | -| `scheduler.extender.image.pullSecrets` | Scheduler extender image pull secrets | `[]` | -| `scheduler.extender.extraArgs` | Scheduler extender extra arguments | `["--debug", "-v=4"]` | - -### Admission Webhook Configuration - -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `scheduler.admissionWebhook.enabled` | Whether to enable admission webhook | `true` | -| `scheduler.admissionWebhook.customURL.enabled` | Whether to enable custom URL | `false` | -| `scheduler.admissionWebhook.customURL.host` | Custom URL host | `127.0.0.1` | -| `scheduler.admissionWebhook.customURL.port` | Custom URL port | `31998` | -| `scheduler.admissionWebhook.customURL.path` | Custom URL path | `/webhook` | -| `scheduler.admissionWebhook.reinvocationPolicy` | Reinvocation policy | `Never` | -| `scheduler.admissionWebhook.failurePolicy` | Failure policy | `Ignore` | - -### TLS Certificate Configuration - -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `scheduler.certManager.enabled` | Whether to use cert-manager to generate self-signed certificates | `false` | -| `scheduler.patch.enabled` | Whether to use kube-webhook-certgen to generate self-signed certificates | `true` | -| `scheduler.patch.image.registry` | Certgen image registry | `"docker.io"` | -| `scheduler.patch.image.repository` | Certgen image repository | `"jettech/kube-webhook-certgen"` | -| `scheduler.patch.image.tag` | Certgen image tag | `"v1.5.2"` | -| `scheduler.patch.image.pullPolicy` | Certgen image pull policy | `IfNotPresent` | -| `scheduler.patch.imageNew.registry` | New certgen image registry | `"docker.io"` | -| `scheduler.patch.imageNew.repository` | New certgen image repository | `"liangjw/kube-webhook-certgen"` | -| `scheduler.patch.imageNew.tag` | New certgen image tag | `"v1.1.1"` | - -### Scheduler Service Configuration - -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `scheduler.service.type` | Service type | `NodePort` | -| `scheduler.service.httpPort` | HTTP port | `443` | -| `scheduler.service.schedulerPort` | Scheduler NodePort | `31998` | -| `scheduler.service.monitorPort` | Monitor port | `31993` | -| `scheduler.service.monitorTargetPort` | Monitor target port | `9395` | - -## Device Plugin Configuration - -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `devicePlugin.image.registry` | Device plugin image registry | `"docker.io"` | -| `devicePlugin.image.repository` | Device plugin image repository | `"projecthami/hami"` | -| `devicePlugin.image.tag` | Device plugin image tag | `""` | -| `devicePlugin.image.pullPolicy` | Device plugin image pull policy | `IfNotPresent` | -| `devicePlugin.image.pullSecrets` | Device plugin image pull secrets | `[]` | - -### Monitor Configuration - -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `devicePlugin.monitor.image.registry` | Monitor image registry | `"docker.io"` | -| `devicePlugin.monitor.image.repository` | Monitor image repository | `"projecthami/hami"` | -| `devicePlugin.monitor.image.tag` | Monitor image tag | `""` | -| `devicePlugin.monitor.image.pullPolicy` | Monitor image pull policy | `IfNotPresent` | -| `devicePlugin.monitor.image.pullSecrets` | Monitor image pull secrets | `[]` | -| `devicePlugin.monitor.ctrPath` | Container path | `/usr/local/vgpu/containers` | -| `devicePlugin.monitor.extraArgs` | Monitor extra arguments | `["-v=4"]` | -| `devicePlugin.monitor.extraEnvs` | Monitor extra environments | `{}` | - -### Device Plugin Other Configuration - -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `devicePlugin.deviceSplitCount` | Integer type, default value: 10. Maximum number of tasks assigned to a single GPU device | `10` | -| `devicePlugin.deviceMemoryScaling` | Device memory scaling ratio | `1` | -| `devicePlugin.deviceCoreScaling` | Device core scaling ratio | `1` | -| `devicePlugin.runtimeClassName` | Runtime class name | `""` | -| `devicePlugin.createRuntimeClass` | Whether to create runtime class | `false` | -| `devicePlugin.migStrategy` | String type, "none" means ignore MIG functionality, "mixed" means allocate MIG devices through independent resources | `"none"` | -| `devicePlugin.disablecorelimit` | String type, "true" means disable core limit, "false" means enable core limit | `"false"` | -| `devicePlugin.passDeviceSpecsEnabled` | Whether to enable passing device specs | `false` | -| `devicePlugin.extraArgs` | Device plugin extra arguments | `["-v=4"]` | -| `devicePlugin.nodeConfiguration.config` | Node configuration for device plugin by json | An example of default configuration. | -| `devicePlugin.nodeConfiguration.externalConfigName` | Node configuration for device plugin by external congimap | `""` | -| `devicePlugin.extraEnvs` | Device plugin extra environments | `{}` | - -### Device Plugin Service Configuration - -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `devicePlugin.service.type` | Service type | `NodePort` | -| `devicePlugin.service.httpPort` | HTTP port | `31992` | - -### Device Plugin Deployment Configuration - -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `devicePlugin.pluginPath` | Plugin path | `/var/lib/kubelet/device-plugins` | -| `devicePlugin.libPath` | Library path | `/usr/local/vgpu` | -| `devicePlugin.nvidiaNodeSelector` | NVIDIA node selector | `{"gpu": "on"}` | -| `devicePlugin.updateStrategy.type` | Update strategy type | `RollingUpdate` | -| `devicePlugin.updateStrategy.rollingUpdate.maxUnavailable` | Maximum unavailable count | `1` | - -## Device Configuration - -### AWS Neuron -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `devices.awsneuron.customresources` | Custom resources | `["aws.amazon.com/neuron", "aws.amazon.com/neuroncore"]` | - -### Kunlunxin -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `devices.kunlun.enabled` | Whether to enable | `true` | -| `devices.kunlun.customresources` | Custom resources | `["kunlunxin.com/xpu"]` | - -### Mthreads -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `devices.mthreads.enabled` | Whether to enable | `true` | -| `devices.mthreads.customresources` | Custom resources | `["mthreads.com/vgpu"]` | - -### NVIDIA -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `devices.nvidia.gpuCorePolicy` | GPU core policy | `default` | -| `devices.nvidia.libCudaLogLevel` | CUDA library log level | `1` | - -### Huawei Ascend -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `devices.ascend.enabled` | Whether to enable | `false` | -| `devices.ascend.image` | Image | `""` | -| `devices.ascend.imagePullPolicy` | Image pull policy | `IfNotPresent` | -| `devices.ascend.extraArgs` | Extra arguments | `[]` | -| `devices.ascend.nodeSelector` | Node selector | `{"ascend": "on"}` | -| `devices.ascend.tolerations` | Tolerations | `[]` | -| `devices.ascend.customresources` | Custom resources | `["huawei.com/Ascend910A", "huawei.com/Ascend910A-memory", ...]` | - -### Iluvatar -| Parameter | Description | Default Value | -|-----------|-------------|---------------| -| `devices.iluvatar.enabled` | Whether to enable | `false` | -| `devices.iluvatar.customresources` | Custom resources | `["iluvatar.ai/BI-V150-vgpu", "iluvatar.ai/BI-V150.vMem","iluvatar.ai/BI-V150.vCore", ...]` | diff --git a/packages/system/hami/charts/hami/templates/NOTES.txt b/packages/system/hami/charts/hami/templates/NOTES.txt deleted file mode 100644 index 15bb1218..00000000 --- a/packages/system/hami/charts/hami/templates/NOTES.txt +++ /dev/null @@ -1,3 +0,0 @@ -** Please be patient while the chart is being deployed ** -Resource name: {{ .Values.resourceName }} - diff --git a/packages/system/hami/charts/hami/templates/_commons.tpl b/packages/system/hami/charts/hami/templates/_commons.tpl deleted file mode 100644 index b68018e4..00000000 --- a/packages/system/hami/charts/hami/templates/_commons.tpl +++ /dev/null @@ -1,49 +0,0 @@ -{{/* -Return the proper image name -{{ include "common.images.image" ( dict "imageRoot" .Values.path.to.the.image "global" $) }} -*/}} -{{- define "common.images.image" -}} -{{- $registryName := .imageRoot.registry -}} -{{- $repositoryName := .imageRoot.repository -}} -{{- $tag := .imageRoot.tag | toString -}} -{{- if .global }} - {{- if .global.imageRegistry }} - {{- $registryName = .global.imageRegistry -}} - {{- end -}} -{{- end -}} -{{- if .tag }} - {{- $tag = .tag | toString -}} -{{- end -}} -{{- if $registryName }} -{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} -{{- else -}} -{{- printf "%s:%s" $repositoryName $tag -}} -{{- end -}} -{{- end -}} - -{{/* -Return the proper Docker Image Registry Secret Names (deprecated: use common.images.renderPullSecrets instead) -{{ include "common.images.pullSecrets" ( dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "global" .Values.global) }} -*/}} -{{- define "common.images.pullSecrets" -}} - {{- $pullSecrets := list }} - - {{- if .global }} - {{- range .global.imagePullSecrets -}} - {{- $pullSecrets = append $pullSecrets . -}} - {{- end -}} - {{- end -}} - - {{- range .images -}} - {{- range .pullSecrets -}} - {{- $pullSecrets = append $pullSecrets . -}} - {{- end -}} - {{- end -}} - - {{- if (not (empty $pullSecrets)) }} -imagePullSecrets: - {{- range $pullSecrets }} - - name: {{ . }} - {{- end }} - {{- end }} -{{- end -}} diff --git a/packages/system/hami/charts/hami/templates/_helpers.tpl b/packages/system/hami/charts/hami/templates/_helpers.tpl deleted file mode 100644 index ffcc61da..00000000 --- a/packages/system/hami/charts/hami/templates/_helpers.tpl +++ /dev/null @@ -1,163 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "hami-vgpu.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "hami-vgpu.fullname" -}} -{{- if .Values.fullnameOverride -}} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Allow the release namespace to be overridden for multi-namespace deployments in combined charts -*/}} -{{- define "hami-vgpu.namespace" -}} - {{- if .Values.namespaceOverride -}} - {{- .Values.namespaceOverride -}} - {{- else -}} - {{- .Release.Namespace -}} - {{- end -}} -{{- end -}} - -{{/* -The app name for Scheduler -*/}} -{{- define "hami-vgpu.scheduler" -}} -{{- printf "%s-scheduler" ( include "hami-vgpu.fullname" . ) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The app name for DevicePlugin -*/}} -{{- define "hami-vgpu.device-plugin" -}} -{{- printf "%s-device-plugin" ( include "hami-vgpu.fullname" . ) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* - The app name for MockDevicePlugin - */}} -{{- define "hami-vgpu.mock-device-plugin" -}} -{{- printf "%s-mock-device-plugin" ( include "hami-vgpu.fullname" . ) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The tls secret name for Scheduler -*/}} -{{- define "hami-vgpu.scheduler.tls" -}} -{{- printf "%s-scheduler-tls" ( include "hami-vgpu.fullname" . ) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The webhook name -*/}} -{{- define "hami-vgpu.scheduler.webhook" -}} -{{- printf "%s-webhook" ( include "hami-vgpu.fullname" . ) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "hami-vgpu.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "hami-vgpu.labels" -}} -helm.sh/chart: {{ include "hami-vgpu.chart" . }} -{{ include "hami-vgpu.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "hami-vgpu.selectorLabels" -}} -app.kubernetes.io/name: {{ include "hami-vgpu.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - - -{{/* - Resolve the tag for kubeScheduler. -*/}} -{{- define "resolvedKubeSchedulerTag" -}} -{{- if .Values.scheduler.kubeScheduler.image.tag }} -{{- .Values.scheduler.kubeScheduler.image.tag | trim -}} -{{- else }} -{{- include "strippedKubeVersion" . | trim -}} -{{- end }} -{{- end }} - -{{/* - Return the stripped Kubernetes version string by removing extra parts after semantic version number. - v1.31.1+k3s1 -> v1.31.1 - v1.30.8-eks-2d5f260 -> v1.30.8 - v1.31.1 -> v1.31.1 -*/}} -{{- define "strippedKubeVersion" -}} -{{ regexReplaceAll "^(v[0-9]+\\.[0-9]+\\.[0-9]+)(.*)$" .Capabilities.KubeVersion.Version "$1" }} -{{- end -}} - -{{- define "hami.scheduler.kubeScheduler.image" -}} -{{ include "common.images.image" (dict "imageRoot" .Values.scheduler.kubeScheduler.image "global" .Values.global "tag" (include "resolvedKubeSchedulerTag" .)) }} -{{- end -}} - -{{- define "hami.scheduler.extender.image" -}} -{{ include "common.images.image" (dict "imageRoot" .Values.scheduler.extender.image "global" .Values.global "tag" .Values.global.imageTag) }} -{{- end -}} - -{{- define "hami.devicePlugin.image" -}} -{{ include "common.images.image" (dict "imageRoot" .Values.devicePlugin.image "global" .Values.global "tag" .Values.global.imageTag) }} -{{- end -}} - -{{- define "hami.mockDevicePlugin.image" -}} -{{ include "common.images.image" (dict "imageRoot" .Values.mockDevicePlugin.image "global" .Values.global "tag" .Values.mockDevicePlugin.tag) }} -{{- end -}} - -{{- define "hami.devicePlugin.monitor.image" -}} -{{ include "common.images.image" (dict "imageRoot" .Values.devicePlugin.monitor.image "global" .Values.global "tag" .Values.global.imageTag) }} -{{- end -}} - -{{- define "hami.scheduler.patch.image" -}} -{{ include "common.images.image" (dict "imageRoot" .Values.scheduler.patch.image "global" .Values.global) }} -{{- end -}} - -{{- define "hami.scheduler.patch.new.image" -}} -{{ include "common.images.image" (dict "imageRoot" .Values.scheduler.patch.imageNew "global" .Values.global) }} -{{- end -}} - -{{- define "hami.scheduler.extender.imagePullSecrets" -}} -{{ include "common.images.pullSecrets" (dict "images" (list .Values.scheduler.extender.image) "global" .Values.global) }} -{{- end -}} - -{{- define "hami.devicePlugin.imagePullSecrets" -}} -{{ include "common.images.pullSecrets" (dict "images" (list .Values.devicePlugin.image) "global" .Values.global) }} -{{- end -}} - -{{- define "hami.scheduler.patch.imagePullSecrets" -}} -{{ include "common.images.pullSecrets" (dict "images" (list .Values.scheduler.patch.image) "global" .Values.global) }} -{{- end -}} - -{{- define "hami.scheduler.patch.new.imagePullSecrets" -}} -{{ include "common.images.pullSecrets" (dict "images" (list .Values.scheduler.patch.imageNew) "global" .Values.global) }} -{{- end -}} diff --git a/packages/system/hami/charts/hami/templates/device-plugin/configmap.yaml b/packages/system/hami/charts/hami/templates/device-plugin/configmap.yaml deleted file mode 100644 index 74631e24..00000000 --- a/packages/system/hami/charts/hami/templates/device-plugin/configmap.yaml +++ /dev/null @@ -1,13 +0,0 @@ -{{- if and .Values.devicePlugin.enabled (not .Values.devicePlugin.nodeConfiguration.externalConfigName) -}} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "hami-vgpu.device-plugin" . }} - namespace: {{ include "hami-vgpu.namespace" . }} - labels: - app.kubernetes.io/component: hami-device-plugin - {{- include "hami-vgpu.labels" . | nindent 4 }} -data: - config.json: | -{{- .Values.devicePlugin.nodeConfiguration.config | nindent 4 }} -{{- end }} diff --git a/packages/system/hami/charts/hami/templates/device-plugin/daemonsetmock.yaml b/packages/system/hami/charts/hami/templates/device-plugin/daemonsetmock.yaml deleted file mode 100644 index a0dc4ff3..00000000 --- a/packages/system/hami/charts/hami/templates/device-plugin/daemonsetmock.yaml +++ /dev/null @@ -1,55 +0,0 @@ -{{- if .Values.mockDevicePlugin.enabled }} -apiVersion: apps/v1 -kind: DaemonSet -metadata: - name: {{ include "hami-vgpu.mock-device-plugin" . }} - namespace: {{ include "hami-vgpu.namespace" . }} -spec: - selector: - matchLabels: - app.kubernetes.io/component: hami-mock-device-plugin - {{- include "hami-vgpu.selectorLabels" . | nindent 6 }} - template: - metadata: - annotations: - scheduler.alpha.kubernetes.io/critical-pod: "" - labels: - app.kubernetes.io/component: hami-mock-device-plugin - {{- include "hami-vgpu.selectorLabels" . | nindent 8 }} - spec: - serviceAccountName: {{ include "hami-vgpu.mock-device-plugin" . }} - tolerations: - - key: CriticalAddonsOnly - operator: Exists - containers: - - image: {{ include "hami.mockDevicePlugin.image" . }} - imagePullPolicy: {{ .Values.mockDevicePlugin.image.pullPolicy }} - name: hami-mock-dp-cntr - env: - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - command: - - ./k8s-device-plugin - - -v=5 - - --device-config-file=/device-config.yaml - volumeMounts: - - name: dp - mountPath: /var/lib/kubelet/device-plugins - - name: sys - mountPath: /sys - - name: device-config - mountPath: /device-config.yaml - subPath: device-config.yaml - volumes: - - name: dp - hostPath: - path: /var/lib/kubelet/device-plugins - - name: sys - hostPath: - path: /sys - - name: device-config - configMap: - name: {{ include "hami-vgpu.scheduler" . }}-device -{{- end -}} diff --git a/packages/system/hami/charts/hami/templates/device-plugin/daemonsetnvidia.yaml b/packages/system/hami/charts/hami/templates/device-plugin/daemonsetnvidia.yaml deleted file mode 100644 index 1f4c24f3..00000000 --- a/packages/system/hami/charts/hami/templates/device-plugin/daemonsetnvidia.yaml +++ /dev/null @@ -1,262 +0,0 @@ -{{- if .Values.devicePlugin.enabled }} -apiVersion: apps/v1 -kind: DaemonSet -metadata: - name: {{ include "hami-vgpu.device-plugin" . }} - namespace: {{ include "hami-vgpu.namespace" . }} - labels: - app.kubernetes.io/component: hami-device-plugin - {{- include "hami-vgpu.labels" . | nindent 4 }} - {{- with .Values.global.labels }} - {{- toYaml . | nindent 4 }} - {{- end }} - {{- if .Values.global.annotations }} - annotations: {{ toYaml .Values.global.annotations | nindent 4}} - {{- end }} -spec: - updateStrategy: - {{- with .Values.devicePlugin.updateStrategy }} - {{- toYaml . | nindent 4 }} - {{- end }} - selector: - matchLabels: - app.kubernetes.io/component: hami-device-plugin - {{- include "hami-vgpu.selectorLabels" . | nindent 6 }} - template: - metadata: - labels: - app.kubernetes.io/component: hami-device-plugin - hami.io/webhook: ignore - {{- include "hami-vgpu.selectorLabels" . | nindent 8 }} - annotations: - {{- if ge (regexReplaceAll "[^0-9]" .Capabilities.KubeVersion.Minor "" | int) 22 }} - checksum/hami-scheduler-newversion-config: {{ include (print $.Template.BasePath "/scheduler/configmapnew.yaml") . | sha256sum }} - {{- else }} - checksum/hami-scheduler-config: {{ include (print $.Template.BasePath "/scheduler/configmap.yaml") . | sha256sum }} - {{- end }} - checksum/hami-scheduler-device-config: {{ include (print $.Template.BasePath "/scheduler/device-configmap.yaml") . | sha256sum }} - {{- if .Values.devicePlugin.podAnnotations }} - {{- toYaml .Values.devicePlugin.podAnnotations | nindent 8 }} - {{- end }} - spec: - {{- if .Values.devicePlugin.runtimeClassName }} - runtimeClassName: {{ .Values.devicePlugin.runtimeClassName }} - {{- end }} - serviceAccountName: {{ include "hami-vgpu.device-plugin" . }} - priorityClassName: system-node-critical - hostPID: true - hostNetwork: true - {{- include "hami.devicePlugin.imagePullSecrets" . | nindent 6 }} - {{- if .Values.devicePlugin.gpuOperatorToolkitReady.enabled }} - initContainers: - - name: toolkit-validation - image: {{ include "hami.devicePlugin.image" . }} - imagePullPolicy: {{ .Values.devicePlugin.image.pullPolicy }} - securityContext: - privileged: true - runAsUser: 0 - command: ["sh", "-c"] - args: - - | - echo "Waiting for NVIDIA Toolkit to be ready..." - until [ -f {{ .Values.devicePlugin.gpuOperatorToolkitReady.hostPath }}/toolkit-ready ]; do - echo "Waiting for {{ .Values.devicePlugin.gpuOperatorToolkitReady.hostPath }}/toolkit-ready..." - sleep 5 - done - echo "NVIDIA Toolkit is ready!" - volumeMounts: - - name: nvidia-validations - mountPath: {{ .Values.devicePlugin.gpuOperatorToolkitReady.hostPath | quote }} - mountPropagation: HostToContainer - readOnly: true - {{- end }} - containers: - - name: device-plugin - image: {{ include "hami.devicePlugin.image" . }} - imagePullPolicy: {{ .Values.devicePlugin.image.pullPolicy }} - lifecycle: - postStart: - exec: - command: ["/bin/sh","-c", {{ printf "/k8s-vgpu/bin/vgpu-init.sh %s/vgpu/" .Values.global.gpuHookPath | quote }}] - command: - - nvidia-device-plugin - - --config-file=/device-config.yaml - - --mig-strategy={{ .Values.devicePlugin.migStrategy }} - - --disable-core-limit={{ .Values.devicePlugin.disablecorelimit }} - {{- range .Values.devicePlugin.extraArgs }} - - {{ . }} - {{- end }} - env: - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - - name: NVIDIA_MIG_MONITOR_DEVICES - value: all - - name: DEVICE_LIST_STRATEGY - value: {{ .Values.devicePlugin.deviceListStrategy }} - - name: HOOK_PATH - value: {{ .Values.global.gpuHookPath }} - {{- if typeIs "bool" .Values.devicePlugin.passDeviceSpecsEnabled }} - - name: PASS_DEVICE_SPECS - value: {{ .Values.devicePlugin.passDeviceSpecsEnabled | quote }} - {{- end }} - {{- if typeIs "string" .Values.devicePlugin.nvidiaDriverRoot }} - - name: NVIDIA_DRIVER_ROOT - value: {{ .Values.devicePlugin.nvidiaDriverRoot }} - {{- end }} - {{- if typeIs "string" .Values.devicePlugin.nvidiaHookPath }} - - name: NVIDIA_CDI_HOOK_PATH - value: {{ .Values.devicePlugin.nvidiaHookPath }} - {{- end }} - {{- if typeIs "bool" .Values.devicePlugin.gdrcopyEnabled }} - - name: GDRCOPY_ENABLED - value: {{ .Values.devicePlugin.gdrcopyEnabled | quote }} - {{- end }} - {{- if typeIs "bool" .Values.devicePlugin.gdsEnabled }} - - name: GDS_ENABLED - value: {{ .Values.devicePlugin.gdsEnabled | quote }} - {{- end }} - {{- if typeIs "bool" .Values.devicePlugin.mofedEnabled }} - - name: MOFED_ENABLED - value: {{ .Values.devicePlugin.mofedEnabled | quote }} - {{- end }} - {{- if eq (.Values.scheduler.defaultSchedulerPolicy.gpuSchedulerPolicy | default "spread") "topology-aware" }} - - name: ENABLE_TOPOLOGY_SCORE - value: "true" - {{- end }} - {{- with .Values.devicePlugin.extraEnvs }} - {{- . | toYaml | nindent 12 }} - {{- end }} - securityContext: - privileged: true - allowPrivilegeEscalation: true - capabilities: - drop: ["ALL"] - add: ["SYS_ADMIN"] - resources: - {{- toYaml .Values.devicePlugin.resources | nindent 12 }} - volumeMounts: - - name: device-plugin - mountPath: /var/lib/kubelet/device-plugins - - name: lib - mountPath: {{ printf "%s%s" .Values.global.gpuHookPath "/vgpu" }} - - name: usrbin - mountPath: /usrbin - - name: deviceconfig - mountPath: /config - - name: hosttmp - mountPath: /tmp - - name: device-config - mountPath: /device-config.yaml - subPath: device-config.yaml - - name: cdi-root - mountPath: /var/run/cdi - {{- if typeIs "string" .Values.devicePlugin.nvidiaDriverRoot }} - # We always mount the driver root at /driver-root in the container. - # This is required for CDI detection to work correctly. - - name: driver-root - mountPath: /driver-root - readOnly: true - {{- end }} - - name: vgpu-monitor - image: {{ include "hami.devicePlugin.monitor.image" . }} - imagePullPolicy: {{ .Values.devicePlugin.monitor.image.pullPolicy }} - command: - - "vGPUmonitor" - {{- range .Values.devicePlugin.monitor.extraArgs }} - - {{ . }} - {{- end }} - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: ["ALL"] - add: ["SYS_ADMIN"] - env: - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - - name: NVIDIA_VISIBLE_DEVICES - value: "all" - - name: NVIDIA_MIG_MONITOR_DEVICES - value: "all" - - name: HOOK_PATH - value: "{{ .Values.global.gpuHookPath }}/vgpu" - - name: HAMI_RESYNC_INTERVAL - value: {{ .Values.devicePlugin.monitor.resyncInterval | default "5m" | quote }} - {{- with .Values.devicePlugin.monitor.extraEnvs }} - {{- . | toYaml | nindent 12 }} - {{- end }} - resources: - {{- toYaml .Values.devicePlugin.monitor.resources | nindent 12 }} - volumeMounts: - - name: ctrs - mountPath: {{ .Values.devicePlugin.monitor.ctrPath }} - - name: dockers - mountPath: /run/docker - - name: containerds - mountPath: /run/containerd - - name: sysinfo - mountPath: /sysinfo - - name: hostvar - mountPath: /hostvar - - name: hosttmp - mountPath: /tmp - volumes: - - name: ctrs - hostPath: - path: {{ .Values.devicePlugin.monitor.ctrPath }} - - name: hosttmp - hostPath: - path: /tmp - - name: dockers - hostPath: - path: /run/docker - - name: containerds - hostPath: - path: /run/containerd - - name: device-plugin - hostPath: - path: {{ .Values.devicePlugin.pluginPath }} - - name: lib - hostPath: - path: {{ .Values.devicePlugin.libPath }} - {{- if .Values.devicePlugin.gpuOperatorToolkitReady.enabled }} - - name: nvidia-validations - hostPath: - path: {{ .Values.devicePlugin.gpuOperatorToolkitReady.hostPath }} - type: DirectoryOrCreate - {{- end }} - {{- if typeIs "string" .Values.devicePlugin.nvidiaDriverRoot }} - - name: driver-root - hostPath: - path: {{ .Values.devicePlugin.nvidiaDriverRoot }} - type: Directory - {{- end }} - - name: cdi-root - hostPath: - path: /var/run/cdi - type: DirectoryOrCreate - - name: usrbin - hostPath: - path: /usr/bin - - name: sysinfo - hostPath: - path: /sys - - name: hostvar - hostPath: - path: /var - - name: deviceconfig - configMap: - name: {{ .Values.devicePlugin.nodeConfiguration.externalConfigName | default (include "hami-vgpu.device-plugin" .) }} - - name: device-config - configMap: - name: {{ include "hami-vgpu.scheduler" . }}-device - {{- if .Values.devicePlugin.nvidiaNodeSelector }} - nodeSelector: {{ toYaml .Values.devicePlugin.nvidiaNodeSelector | nindent 8 }} - {{- end }} - {{- if .Values.devicePlugin.tolerations }} - tolerations: {{ toYaml .Values.devicePlugin.tolerations | nindent 8 }} - {{- end }} -{{- end }} diff --git a/packages/system/hami/charts/hami/templates/device-plugin/monitorrole.yaml b/packages/system/hami/charts/hami/templates/device-plugin/monitorrole.yaml deleted file mode 100644 index 6ac757f2..00000000 --- a/packages/system/hami/charts/hami/templates/device-plugin/monitorrole.yaml +++ /dev/null @@ -1,28 +0,0 @@ -{{- if .Values.devicePlugin.enabled -}} -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: {{ include "hami-vgpu.device-plugin" . }}-monitor -rules: - - apiGroups: - - "" - resources: - - pods - verbs: - - get - - create - - watch - - list - - update - - patch - - apiGroups: - - "" - resources: - - nodes - verbs: - - get - - update - - list - - patch -{{- end -}} - diff --git a/packages/system/hami/charts/hami/templates/device-plugin/monitorrolebinding.yaml b/packages/system/hami/charts/hami/templates/device-plugin/monitorrolebinding.yaml deleted file mode 100644 index 2f0a14ba..00000000 --- a/packages/system/hami/charts/hami/templates/device-plugin/monitorrolebinding.yaml +++ /dev/null @@ -1,17 +0,0 @@ -{{- if .Values.devicePlugin.enabled -}} -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: {{ include "hami-vgpu.device-plugin" . }} - labels: - app.kubernetes.io/component: "hami-device-plugin" - {{- include "hami-vgpu.labels" . | nindent 4 }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: {{ include "hami-vgpu.device-plugin" . }}-monitor -subjects: - - kind: ServiceAccount - name: {{ include "hami-vgpu.device-plugin" . }} - namespace: {{ include "hami-vgpu.namespace" . }} -{{- end -}} diff --git a/packages/system/hami/charts/hami/templates/device-plugin/monitorservice.yaml b/packages/system/hami/charts/hami/templates/device-plugin/monitorservice.yaml deleted file mode 100644 index 88f1b214..00000000 --- a/packages/system/hami/charts/hami/templates/device-plugin/monitorservice.yaml +++ /dev/null @@ -1,29 +0,0 @@ -{{- if .Values.devicePlugin.enabled -}} -apiVersion: v1 -kind: Service -metadata: - name: {{ include "hami-vgpu.device-plugin" . }}-monitor - namespace: {{ include "hami-vgpu.namespace" . }} - labels: - app.kubernetes.io/component: hami-device-plugin - {{- include "hami-vgpu.labels" . | nindent 4 }} - {{- with .Values.devicePlugin.service.labels }} - {{- toYaml . | nindent 4 }} - {{- end }} - {{- if .Values.devicePlugin.service.annotations }} # Use devicePlugin instead of scheduler - annotations: {{ toYaml .Values.devicePlugin.service.annotations | nindent 4 }} - {{- end }} -spec: - type: {{ .Values.devicePlugin.service.type | default "NodePort" }} # Default type is NodePort - ports: - - name: monitorport - port: {{ .Values.devicePlugin.service.httpPort | default 31992 }} # Default HTTP port is 31992 - targetPort: 9394 - {{- if eq (.Values.devicePlugin.service.type | default "NodePort") "NodePort" }} # If type is NodePort, set nodePort - nodePort: {{ .Values.devicePlugin.service.httpPort | default 31992 }} - {{- end }} - protocol: TCP - selector: - app.kubernetes.io/component: hami-device-plugin - {{- include "hami-vgpu.selectorLabels" . | nindent 4 }} -{{- end -}} diff --git a/packages/system/hami/charts/hami/templates/device-plugin/monitorserviceaccount.yaml b/packages/system/hami/charts/hami/templates/device-plugin/monitorserviceaccount.yaml deleted file mode 100644 index 6e3c2a44..00000000 --- a/packages/system/hami/charts/hami/templates/device-plugin/monitorserviceaccount.yaml +++ /dev/null @@ -1,10 +0,0 @@ -{{- if .Values.devicePlugin.enabled -}} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "hami-vgpu.device-plugin" . }} - namespace: {{ include "hami-vgpu.namespace" . }} - labels: - app.kubernetes.io/component: "hami-device-plugin" - {{- include "hami-vgpu.labels" . | nindent 4 }} -{{- end -}} diff --git a/packages/system/hami/charts/hami/templates/device-plugin/runtime-class.yaml b/packages/system/hami/charts/hami/templates/device-plugin/runtime-class.yaml deleted file mode 100644 index ebcc2c9c..00000000 --- a/packages/system/hami/charts/hami/templates/device-plugin/runtime-class.yaml +++ /dev/null @@ -1,9 +0,0 @@ -{{- if and .Values.devicePlugin.enabled .Values.devicePlugin.createRuntimeClass .Values.devicePlugin.runtimeClassName -}} -apiVersion: node.k8s.io/v1 -kind: RuntimeClass -metadata: - name: {{ .Values.devicePlugin.runtimeClassName }} - annotations: - helm.sh/hook: pre-install,pre-upgrade -handler: nvidia -{{- end -}} diff --git a/packages/system/hami/charts/hami/templates/scheduler/certmanager.yaml b/packages/system/hami/charts/hami/templates/scheduler/certmanager.yaml deleted file mode 100644 index e6d28721..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/certmanager.yaml +++ /dev/null @@ -1,31 +0,0 @@ -{{- if .Values.scheduler.admissionWebhook.enabled -}} -{{- if .Values.scheduler.certManager.enabled }} -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: {{ include "hami-vgpu.scheduler" . }}-serving-cert - namespace: {{ include "hami-vgpu.namespace" . }} - labels: - app.kubernetes.io/component: hami-scheduler - {{- include "hami-vgpu.labels" . | nindent 4 }} -spec: - dnsNames: - - {{ include "hami-vgpu.scheduler" . }}.{{ include "hami-vgpu.namespace" . }}.svc - - {{ include "hami-vgpu.scheduler" . }}.{{ include "hami-vgpu.namespace" . }}.svc.cluster.local - issuerRef: - kind: Issuer - name: {{ include "hami-vgpu.scheduler" . }}-selfsigned-issuer - secretName: {{ include "hami-vgpu.scheduler.tls" . }} ---- -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - name: {{ include "hami-vgpu.scheduler" . }}-selfsigned-issuer - namespace: {{ include "hami-vgpu.namespace" . }} - labels: - app.kubernetes.io/component: hami-scheduler - {{- include "hami-vgpu.labels" . | nindent 4 }} -spec: - selfSigned: {} -{{- end }} -{{- end }} diff --git a/packages/system/hami/charts/hami/templates/scheduler/clusterrole.yaml b/packages/system/hami/charts/hami/templates/scheduler/clusterrole.yaml deleted file mode 100644 index 81c4fddf..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/clusterrole.yaml +++ /dev/null @@ -1,34 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: {{ include "hami-vgpu.scheduler" . }} - labels: - app.kubernetes.io/component: "hami-scheduler" - {{- include "hami-vgpu.labels" . | nindent 4 }} -rules: - - apiGroups: [""] - resources: ["pods", "configmaps"] - verbs: ["get", "list", "watch", "patch"] - - apiGroups: [""] - resources: ["pods/binding"] - verbs: ["create"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "patch", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["create", "get", "list"] - - apiGroups: [""] - resources: ["resourcequotas"] - verbs: ["get", "list", "watch"] ---- -{{- if .Values.mockDevicePlugin.enabled }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: {{ include "hami-vgpu.mock-device-plugin" . }} -rules: - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "update", "list", "patch"] -{{- end -}} diff --git a/packages/system/hami/charts/hami/templates/scheduler/clusterrolebinding.yaml b/packages/system/hami/charts/hami/templates/scheduler/clusterrolebinding.yaml deleted file mode 100644 index a81d425c..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/clusterrolebinding.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: {{ include "hami-vgpu.scheduler" . }}-kube - labels: - app.kubernetes.io/component: "hami-scheduler" - {{- include "hami-vgpu.labels" . | nindent 4 }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: system:kube-scheduler -subjects: - - kind: ServiceAccount - name: {{ include "hami-vgpu.scheduler" . }} - namespace: {{ include "hami-vgpu.namespace" . }} ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: {{ include "hami-vgpu.scheduler" . }}-volume - labels: - app.kubernetes.io/component: "hami-scheduler" - {{- include "hami-vgpu.labels" . | nindent 4 }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: system:volume-scheduler -subjects: - - kind: ServiceAccount - name: {{ include "hami-vgpu.scheduler" . }} - namespace: {{ include "hami-vgpu.namespace" . }} ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: {{ include "hami-vgpu.scheduler" . }} - labels: - app.kubernetes.io/component: "hami-scheduler" - {{- include "hami-vgpu.labels" . | nindent 4 }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: {{ include "hami-vgpu.scheduler" . }} -subjects: - - kind: ServiceAccount - name: {{ include "hami-vgpu.scheduler" . }} - namespace: {{ include "hami-vgpu.namespace" . }} diff --git a/packages/system/hami/charts/hami/templates/scheduler/configmap.yaml b/packages/system/hami/charts/hami/templates/scheduler/configmap.yaml deleted file mode 100644 index 109ecbce..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/configmap.yaml +++ /dev/null @@ -1,142 +0,0 @@ -{{- if .Values.scheduler.kubeScheduler.enabled -}} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "hami-vgpu.scheduler" . }} - namespace: {{ include "hami-vgpu.namespace" . }} - labels: - app.kubernetes.io/component: hami-scheduler - {{- include "hami-vgpu.labels" . | nindent 4 }} -data: - config.json: | - { - "kind": "Policy", - "apiVersion": "v1", - "extenders": [ - { - {{- if .Values.scheduler.admissionWebhook.enabled }} - "urlPrefix": "https://127.0.0.1:443", - "enableHttps": true, - "tlsConfig": { - "insecure": true - }, - {{- else }} - "urlPrefix": "http://127.0.0.1:80", - "enableHttps": false, - {{- end }} - "filterVerb": "filter", - "bindVerb": "bind", - "weight": 1, - "nodeCacheCapable": true, - "httpTimeout": 30000000000, - "managedResources": [ - {{- range .Values.devices.amd.customresources }} - { - "name": "{{ . }}", - "ignoredByScheduler": true - }, - {{- end }} - {{- if .Values.devices.ascend.enabled }} - {{- range .Values.devices.ascend.customresources }} - { - "name": "{{ . }}", - "ignoredByScheduler": true - }, - {{- end }} - {{- end }} - {{- if .Values.devices.mthreads.enabled }} - {{- range .Values.devices.mthreads.customresources }} - { - "name": "{{ . }}", - "ignoredByScheduler": true - }, - {{- end }} - {{- end }} - {{- if .Values.devices.enflame.enabled }} - {{- range .Values.devices.enflame.customresources }} - { - "name": "{{ . }}", - "ignoredByScheduler": true - }, - {{- end }} - {{- end }} - {{- if .Values.devices.kunlun.enabled }} - {{- range .Values.devices.kunlun.customresources }} - { - "name": "{{ . }}", - "ignoredByScheduler": true - }, - {{- end }} - {{- end }} - {{- range .Values.devices.awsneuron.customresources }} - { - "name": "{{ . }}", - "ignoredByScheduler": true - }, - {{- end }} - {{- if .Values.devices.iluvatar.enabled }} - {{- range .Values.devices.iluvatar.customresources }} - { - "name": "{{ . }}", - "ignoredByScheduler": true - }, - {{- end }} - {{- end }} - { - "name": "{{ .Values.resourceName }}", - "ignoredByScheduler": true - }, - { - "name": "{{ .Values.resourceMem }}", - "ignoredByScheduler": true - }, - { - "name": "{{ .Values.resourceCores }}", - "ignoredByScheduler": true - }, - { - "name": "{{ .Values.resourceMemPercentage }}", - "ignoredByScheduler": true - }, - { - "name": "{{ .Values.resourcePriority }}", - "ignoredByScheduler": true - }, - { - "name": "{{ .Values.mluResourceName }}", - "ignoredByScheduler": true - }, - { - "name": "{{ .Values.dcuResourceName }}", - "ignoredByScheduler": true - }, - { - "name": "{{ .Values.dcuResourceMem }}", - "ignoredByScheduler": true - }, - { - "name": "{{ .Values.dcuResourceCores }}", - "ignoredByScheduler": true - }, - { - "name": "metax-tech.com/gpu", - "ignoredByScheduler": true - }, - { - "name": "{{ .Values.metaxResourceName }}", - "ignoredByScheduler": true - }, - { - "name": "{{ .Values.metaxResourceCore }}", - "ignoredByScheduler": true - }, - { - "name": "{{ .Values.metaxResourceMem }}", - "ignoredByScheduler": true - } - ], - "ignoreable": false - } - ] - } -{{- end }} diff --git a/packages/system/hami/charts/hami/templates/scheduler/configmapnew.yaml b/packages/system/hami/charts/hami/templates/scheduler/configmapnew.yaml deleted file mode 100644 index 6f6db097..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/configmapnew.yaml +++ /dev/null @@ -1,102 +0,0 @@ -{{- if .Values.scheduler.kubeScheduler.enabled -}} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "hami-vgpu.scheduler" . }}-newversion - namespace: {{ include "hami-vgpu.namespace" . }} - labels: - app.kubernetes.io/component: hami-scheduler - {{- include "hami-vgpu.labels" . | nindent 4 }} -data: - config.yaml: | - {{- if gt (regexReplaceAll "[^0-9]" .Capabilities.KubeVersion.Minor "" | int) 25}} - apiVersion: kubescheduler.config.k8s.io/v1 - {{- else }} - apiVersion: kubescheduler.config.k8s.io/v1beta2 - {{- end }} - kind: KubeSchedulerConfiguration - leaderElection: - leaderElect: false - profiles: - - schedulerName: {{ .Values.schedulerName }} - extenders: - {{- if .Values.scheduler.admissionWebhook.enabled }} - - urlPrefix: "https://127.0.0.1:443" - enableHTTPS: true - tlsConfig: - insecure: true - {{- else }} - - urlPrefix: "http://127.0.0.1:80" - enableHTTPS: false - {{- end }} - filterVerb: filter - bindVerb: bind - nodeCacheCapable: true - weight: 1 - httpTimeout: 30s - managedResources: - - name: {{ .Values.resourceName }} - ignoredByScheduler: true - - name: {{ .Values.resourceMem }} - ignoredByScheduler: true - - name: {{ .Values.resourceCores }} - ignoredByScheduler: true - - name: {{ .Values.resourceMemPercentage }} - ignoredByScheduler: true - - name: {{ .Values.resourcePriority }} - ignoredByScheduler: true - - name: {{ .Values.mluResourceName }} - ignoredByScheduler: true - - name: {{ .Values.dcuResourceName }} - ignoredByScheduler: true - - name: {{ .Values.dcuResourceMem }} - ignoredByScheduler: true - - name: {{ .Values.dcuResourceCores }} - ignoredByScheduler: true - - name: "metax-tech.com/gpu" - ignoredByScheduler: true - - name: {{ .Values.metaxResourceName }} - ignoredByScheduler: true - - name: {{ .Values.metaxResourceCore }} - ignoredByScheduler: true - - name: {{ .Values.metaxResourceMem }} - ignoredByScheduler: true - {{- if .Values.devices.ascend.enabled }} - {{- range .Values.devices.ascend.customresources }} - - name: {{ . }} - ignoredByScheduler: true - {{- end }} - {{- end }} - {{- if .Values.devices.mthreads.enabled }} - {{- range .Values.devices.mthreads.customresources }} - - name: {{ . }} - ignoredByScheduler: true - {{- end }} - {{- end }} - {{- if .Values.devices.enflame.enabled }} - {{- range .Values.devices.enflame.customresources }} - - name: {{ . }} - ignoredByScheduler: true - {{- end }} - {{- end }} - {{- if .Values.devices.kunlun.enabled }} - {{- range .Values.devices.kunlun.customresources }} - - name: {{ . }} - ignoredByScheduler: true - {{- end }} - {{- end }} - {{- range .Values.devices.awsneuron.customresources }} - - name: {{ . }} - ignoredByScheduler: true - {{- end }} - {{- if .Values.devices.iluvatar.enabled }} - {{- range .Values.devices.iluvatar.customresources }} - - name: {{ . }} - ignoredByScheduler: true - {{- end }} - {{- end }} - {{- range .Values.devices.amd.customresources }} - - name: {{ . }} - ignoredByScheduler: true - {{- end }} -{{- end }} diff --git a/packages/system/hami/charts/hami/templates/scheduler/deployment.yaml b/packages/system/hami/charts/hami/templates/scheduler/deployment.yaml deleted file mode 100644 index 1d89e189..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/deployment.yaml +++ /dev/null @@ -1,225 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "hami-vgpu.scheduler" . }} - namespace: {{ include "hami-vgpu.namespace" . }} - labels: - app.kubernetes.io/component: hami-scheduler - {{- include "hami-vgpu.labels" . | nindent 4 }} - {{- with .Values.global.labels }} - {{- toYaml . | nindent 4 }} - {{- end }} - {{- if .Values.global.annotations }} - annotations: {{ toYaml .Values.global.annotations | nindent 4}} - {{- end }} -spec: - {{- if .Values.scheduler.leaderElect }} - replicas: {{ .Values.scheduler.replicas }} - {{- else }} - replicas: 1 - {{- end }} - selector: - matchLabels: - app.kubernetes.io/component: hami-scheduler - {{- include "hami-vgpu.selectorLabels" . | nindent 6 }} - template: - metadata: - labels: - app.kubernetes.io/component: hami-scheduler - {{- include "hami-vgpu.selectorLabels" . | nindent 8 }} - hami.io/webhook: ignore - annotations: - {{- if ge (regexReplaceAll "[^0-9]" .Capabilities.KubeVersion.Minor "" | int) 22 }} - checksum/hami-scheduler-newversion-config: {{ include (print $.Template.BasePath "/scheduler/configmapnew.yaml") . | sha256sum }} - {{- else }} - checksum/hami-scheduler-config: {{ include (print $.Template.BasePath "/scheduler/configmap.yaml") . | sha256sum }} - {{- end }} - checksum/hami-scheduler-device-config: {{ include (print $.Template.BasePath "/scheduler/device-configmap.yaml") . | sha256sum }} - {{- if .Values.scheduler.podAnnotations }} - {{- toYaml .Values.scheduler.podAnnotations | nindent 8 }} - {{- end }} - spec: - serviceAccountName: {{ include "hami-vgpu.scheduler" . }} - priorityClassName: system-node-critical - {{- include "hami.scheduler.extender.imagePullSecrets" . | nindent 6 }} - containers: - {{- if .Values.scheduler.kubeScheduler.enabled }} - - name: kube-scheduler - image: {{ include "hami.scheduler.kubeScheduler.image" . }} - imagePullPolicy: {{ .Values.scheduler.kubeScheduler.image.pullPolicy }} - command: - - kube-scheduler - {{- if ge (regexReplaceAll "[^0-9]" .Capabilities.KubeVersion.Minor "" | int) 22 }} - {{- range .Values.scheduler.kubeScheduler.extraNewArgs }} - - {{ . }} - {{- end }} - {{- else }} - - --scheduler-name={{ .Values.schedulerName }} - {{- range .Values.scheduler.kubeScheduler.extraArgs }} - - {{ . }} - {{- end }} - {{- end }} - - --leader-elect={{ .Values.scheduler.leaderElect }} - - --leader-elect-resource-name={{ .Values.schedulerName }} - - --leader-elect-resource-namespace={{ include "hami-vgpu.namespace" . }} - resources: - {{- toYaml .Values.scheduler.kubeScheduler.resources | nindent 12 }} - volumeMounts: - - name: scheduler-config - mountPath: /config - {{- end }} - {{- if .Values.scheduler.livenessProbe }} - livenessProbe: - failureThreshold: 8 - httpGet: - path: /healthz - port: 10259 - scheme: HTTPS - initialDelaySeconds: 10 - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 15 - {{- end }} - - name: vgpu-scheduler-extender - image: {{ include "hami.scheduler.extender.image" . }} - imagePullPolicy: {{ .Values.scheduler.extender.image.pullPolicy }} - env: - {{- if .Values.scheduler.nodeLockExpire }} - - name: HAMI_NODELOCK_EXPIRE - value: "{{ .Values.scheduler.nodeLockExpire }}" - {{- end }} - {{- if .Values.global.managedNodeSelectorEnable }} - {{- range $key, $value := .Values.global.managedNodeSelector }} - - name: NODE_SELECTOR_{{ $key | upper | replace "-" "_" }} - value: "{{ $value }}" - {{- end }} - {{- end }} - command: - - scheduler - {{- if .Values.scheduler.admissionWebhook.enabled }} - - --http_bind=0.0.0.0:443 - - --cert_file=/tls/tls.crt - - --key_file=/tls/tls.key - {{- else }} - - --http_bind=0.0.0.0:80 - {{- end }} - - --scheduler-name={{ .Values.schedulerName }} - - --metrics-bind-address={{ .Values.scheduler.metricsBindAddress }} - - --node-scheduler-policy={{ .Values.scheduler.defaultSchedulerPolicy.nodeSchedulerPolicy }} - - --gpu-scheduler-policy={{ .Values.scheduler.defaultSchedulerPolicy.gpuSchedulerPolicy }} - - --force-overwrite-default-scheduler={{ .Values.scheduler.forceOverwriteDefaultScheduler}} - - --device-config-file=/device-config.yaml - - --leader-elect={{ .Values.scheduler.leaderElect }} - - --leader-elect-resource-name={{ .Values.schedulerName }} - - --leader-elect-resource-namespace={{ include "hami-vgpu.namespace" . }} - {{- if .Values.devices.ascend.enabled }} - - --enable-ascend=true - {{- end }} - {{- if .Values.devices.iluvatar.enabled }} - - --enable-iluvatar=true - {{- end }} - {{- if .Values.scheduler.nodeLabelSelector }} - - --node-label-selector={{- $first := true -}} - {{- range $key, $value := .Values.scheduler.nodeLabelSelector -}} - {{- if not $first }},{{ end -}} - {{- $key }}={{ $value -}} - {{- $first = false -}} - {{- end -}} - {{- end }} - {{- range .Values.scheduler.extender.extraArgs }} - - {{ . }} - {{- end }} - ports: - {{- if .Values.scheduler.admissionWebhook.enabled }} - - name: https - containerPort: 443 - protocol: TCP - {{- else }} - - name: http - containerPort: 80 - protocol: TCP - {{- end }} - - name: metrics - containerPort: {{ last (splitList ":" .Values.scheduler.metricsBindAddress) | int }} - protocol: TCP - resources: - {{- toYaml .Values.scheduler.extender.resources | nindent 12 }} - volumeMounts: - - name: device-config - mountPath: /device-config.yaml - subPath: device-config.yaml - {{- if .Values.scheduler.admissionWebhook.enabled }} - - name: tls-config - mountPath: /tls - {{- end }} - {{- if .Values.scheduler.livenessProbe }} - livenessProbe: - httpGet: - path: /healthz - {{- if .Values.scheduler.admissionWebhook.enabled }} - port: https - scheme: HTTPS - {{- else }} - port: http - scheme: HTTP - {{- end }} - initialDelaySeconds: 10 - periodSeconds: 10 - failureThreshold: 3 - timeoutSeconds: 5 - {{- end }} - {{- if .Values.scheduler.leaderElect }} - readinessProbe: - httpGet: - path: /readyz - {{- if .Values.scheduler.admissionWebhook.enabled }} - port: https - scheme: HTTPS - {{- else }} - port: http - scheme: HTTP - {{- end }} - initialDelaySeconds: 10 - periodSeconds: 10 - failureThreshold: 3 - timeoutSeconds: 5 - {{- end }} - {{- if .Values.scheduler.leaderElect }} - affinity: - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchExpressions: - - key: app.kubernetes.io/component - operator: In - values: - - hami-scheduler - topologyKey: "kubernetes.io/hostname" - {{- end }} - volumes: - {{- if .Values.scheduler.admissionWebhook.enabled }} - - name: tls-config - secret: - secretName: {{ template "hami-vgpu.scheduler.tls" . }} - {{- end }} - {{- if .Values.scheduler.kubeScheduler.enabled }} - - name: scheduler-config - configMap: - {{- if ge (regexReplaceAll "[^0-9]" .Capabilities.KubeVersion.Minor "" | int) 22 }} - name: {{ template "hami-vgpu.scheduler" . }}-newversion - {{- else }} - name: {{ template "hami-vgpu.scheduler" . }} - {{- end }} - {{- end }} - - name: device-config - configMap: - name: {{ include "hami-vgpu.scheduler" . }}-device - {{- if .Values.scheduler.nodeSelector }} - nodeSelector: {{ toYaml .Values.scheduler.nodeSelector | nindent 8 }} - {{- end }} - {{- if .Values.scheduler.tolerations }} - tolerations: {{ toYaml .Values.scheduler.tolerations | nindent 8 }} - {{- end }} - {{- if .Values.scheduler.nodeName }} - nodeName: {{ .Values.scheduler.nodeName }} - {{- end }} diff --git a/packages/system/hami/charts/hami/templates/scheduler/device-configmap.yaml b/packages/system/hami/charts/hami/templates/scheduler/device-configmap.yaml deleted file mode 100644 index 873b813b..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/device-configmap.yaml +++ /dev/null @@ -1,408 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "hami-vgpu.scheduler" . }}-device - namespace: {{ include "hami-vgpu.namespace" . }} - labels: - app.kubernetes.io/component: hami-scheduler - {{- include "hami-vgpu.labels" . | nindent 4 }} -data: - device-config.yaml: |- - {{- if .Files.Glob "files/device-config.yaml" }} - {{- .Files.Get "files/device-config.yaml" | nindent 4}} - {{- else }} - nvidia: - resourceCountName: {{ .Values.resourceName }} - resourceMemoryName: {{ .Values.resourceMem }} - resourceMemoryPercentageName: {{ .Values.resourceMemPercentage }} - resourceCoreName: {{ .Values.resourceCores }} - resourcePriorityName: {{ .Values.resourcePriority }} - overwriteEnv: false - defaultMemory: 0 - defaultCores: 0 - defaultGPUNum: 1 - memoryFactor: 1 - deviceSplitCount: {{ .Values.devicePlugin.deviceSplitCount }} - deviceMemoryScaling: {{ .Values.devicePlugin.deviceMemoryScaling }} - deviceCoreScaling: {{ .Values.devicePlugin.deviceCoreScaling }} - gpuCorePolicy: {{ .Values.devices.nvidia.gpuCorePolicy }} - libCudaLogLevel: {{ .Values.devices.nvidia.libCudaLogLevel }} - runtimeClassName: "{{ .Values.devicePlugin.runtimeClassName }}" - knownMigGeometries: - - models: [ "A30" ] - allowedGeometries: - - - - name: 1g.6gb - core: 25 - memory: 6144 - count: 4 - - - - name: 2g.12gb - core: 50 - memory: 12288 - count: 2 - - - - name: 4g.24gb - core: 100 - memory: 24576 - count: 1 - - models: [ "A100-SXM4-40GB", "A100-40GB-PCIe", "A100-PCIE-40GB"] - allowedGeometries: - - - - name: 1g.5gb - core: 14 - memory: 5120 - count: 7 - - - - name: 1g.5gb - core: 14 - memory: 5120 - count: 1 - - name: 2g.10gb - core: 28 - memory: 10240 - count: 3 - - - - name: 3g.20gb - core: 42 - memory: 20480 - count: 2 - - - - name: 7g.40gb - core: 100 - memory: 40960 - count: 1 - - models: [ "A100-SXM4-80GB", "A100-80GB-PCIe", "A100-PCIE-80GB"] - allowedGeometries: - - - - name: 1g.10gb - core: 14 - memory: 10240 - count: 7 - - - - name: 1g.10gb - core: 14 - memory: 10240 - count: 1 - - name: 2g.20gb - core: 28 - memory: 20480 - count: 3 - - - - name: 3g.40gb - core: 42 - memory: 40960 - count: 2 - - - - name: 7g.79gb - core: 100 - memory: 80896 - count: 1 - - models: [ "H100-PCIE-80GB", "H100-SXM5-80GB"] - allowedGeometries: - - - - name: 1g.10gb - core: 14 - memory: 10240 - count: 7 - - - - name: 1g.10gb - core: 14 - memory: 10240 - count: 1 - - name: 2g.20gb - core: 28 - memory: 20480 - count: 3 - - - - name: 3g.40gb - core: 42 - memory: 40960 - count: 2 - - - - name: 7g.80gb - core: 100 - memory: 81920 - count: 1 - - models: [ "H100-PCIE-94GB", "H100-SXM5-94GB"] - allowedGeometries: - - - - name: 1g.12gb - core: 14 - memory: 12288 - count: 7 - - - - name: 1g.12gb - core: 14 - memory: 12288 - count: 1 - - name: 2g.24gb - core: 28 - memory: 24576 - count: 3 - - - - name: 3g.47gb - core: 42 - memory: 48128 - count: 2 - - - - name: 7g.94gb - core: 100 - memory: 96256 - count: 1 - - models: [ "H20", "H100 on GH200"] - allowedGeometries: - - - - name: 1g.12gb - core: 14 - memory: 12288 - count: 7 - - - - name: 1g.12gb - core: 14 - memory: 12288 - count: 1 - - name: 2g.24gb - core: 28 - memory: 24576 - count: 3 - - - - name: 3g.48gb - core: 42 - memory: 49152 - count: 2 - - - - name: 7g.96gb - core: 100 - memory: 98304 - count: 1 - - models: [ "H200 NVL", "H200-SXM5"] - allowedGeometries: - - - - name: 1g.18gb - core: 14 - memory: 18432 - count: 7 - - - - name: 1g.18gb - core: 14 - memory: 18432 - count: 1 - - name: 2g.35gb - core: 28 - memory: 35840 - count: 3 - - - - name: 3g.71gb - core: 42 - memory: 72704 - count: 2 - - - - name: 7g.141gb - core: 100 - memory: 144384 - count: 1 - - models: [ "B200" ] - allowedGeometries: - - - - name: 1g.23gb - core: 14 - memory: 23552 - count: 7 - - - - name: 1g.23gb - core: 14 - memory: 23552 - count: 1 - - name: 2g.45gb - core: 28 - memory: 46080 - count: 3 - - - - name: 3g.90gb - core: 42 - memory: 92160 - count: 2 - - - - name: 7g.180gb - core: 100 - memory: 184320 - count: 1 - cambricon: - resourceCountName: {{ .Values.mluResourceName }} - resourceMemoryName: {{ .Values.mluResourceMem }} - resourceCoreName: {{ .Values.mluResourceCores }} - hygon: - resourceCountName: {{ .Values.dcuResourceName }} - resourceMemoryName: {{ .Values.dcuResourceMem }} - resourceCoreName: {{ .Values.dcuResourceCores }} - memoryFactor: 1 - metax: - resourceCountName: "metax-tech.com/gpu" - resourceVCountName: {{ .Values.metaxResourceName }} - resourceVMemoryName: {{ .Values.metaxResourceMem }} - resourceVCoreName: {{ .Values.metaxResourceCore }} - sgpuTopologyAware: {{ .Values.metaxsGPUTopologyAware }} - enflame: - resourceNameGCU: "enflame.com/gcu" - resourceNameVGCU: {{ .Values.enflameResourceNameVGCU }} - resourceNameVGCUPercentage: {{ .Values.enflameResourceNameVGCUPercentage }} - mthreads: - resourceCountName: "mthreads.com/vgpu" - resourceMemoryName: "mthreads.com/sgpu-memory" - resourceCoreName: "mthreads.com/sgpu-core" - iluvatars: - - chipName: MR-V100 - commonWord: MR-V100 - resourceCountName: iluvatar.ai/MR-V100-vgpu - resourceMemoryName: iluvatar.ai/MR-V100.vMem - resourceCoreName: iluvatar.ai/MR-V100.vCore - - chipName: MR-V50 - commonWord: MR-V50 - resourceCountName: iluvatar.ai/MR-V50-vgpu - resourceMemoryName: iluvatar.ai/MR-V50.vMem - resourceCoreName: iluvatar.ai/MR-V50.vCore - - chipName: BI-V150 - commonWord: BI-V150 - resourceCountName: iluvatar.ai/BI-V150-vgpu - resourceMemoryName: iluvatar.ai/BI-V150.vMem - resourceCoreName: iluvatar.ai/BI-V150.vCore - - chipName: BI-V100 - commonWord: BI-V100 - resourceCountName: iluvatar.ai/BI-V100-vgpu - resourceMemoryName: iluvatar.ai/BI-V100.vMem - resourceCoreName: iluvatar.ai/BI-V100.vCore - kunlun: - resourceCountName: {{ .Values.kunlunResourceName }} - resourceVCountName: {{ .Values.kunlunResourceVCountName }} - resourceVMemoryName: {{ .Values.kunlunResourceVMemoryName }} - awsneuron: - resourceCountName: "aws.amazon.com/neuron" - resourceCoreName: "aws.amazon.com/neuroncore" - amd: - resourceCountName: "amd.com/gpu" - vnpus: - - chipName: 910A - commonWord: Ascend910A - resourceName: huawei.com/Ascend910A - resourceMemoryName: huawei.com/Ascend910A-memory - memoryAllocatable: 32768 - memoryCapacity: 32768 - memoryFactor: 1 - aiCore: 30 - templates: - - name: vir02 - memory: 2184 - aiCore: 2 - - name: vir04 - memory: 4369 - aiCore: 4 - - name: vir08 - memory: 8738 - aiCore: 8 - - name: vir16 - memory: 17476 - aiCore: 16 - - chipName: 910B2 - commonWord: Ascend910B2 - resourceName: huawei.com/Ascend910B2 - resourceMemoryName: huawei.com/Ascend910B2-memory - memoryAllocatable: 65536 - memoryCapacity: 65536 - memoryFactor: 1 - aiCore: 24 - aiCPU: 6 - templates: - - name: vir03_1c_8g - memory: 8192 - aiCore: 3 - aiCPU: 1 - - name: vir06_1c_16g - memory: 16384 - aiCore: 6 - aiCPU: 1 - - name: vir12_3c_32g - memory: 32768 - aiCore: 12 - aiCPU: 3 - - chipName: 910B3 - commonWord: Ascend910B3 - resourceName: huawei.com/Ascend910B3 - resourceMemoryName: huawei.com/Ascend910B3-memory - memoryAllocatable: 65536 - memoryCapacity: 65536 - memoryFactor: 1 - aiCore: 20 - aiCPU: 7 - templates: - - name: vir05_1c_16g - memory: 16384 - aiCore: 5 - aiCPU: 1 - - name: vir10_3c_32g - memory: 32768 - aiCore: 10 - aiCPU: 3 - - chipName: 910B4-1 - commonWord: Ascend910B4-1 - resourceName: huawei.com/Ascend910B4-1 - resourceMemoryName: huawei.com/Ascend910B4-1-memory - memoryAllocatable: 65536 - memoryCapacity: 65536 - memoryFactor: 1 - aiCore: 20 - aiCPU: 7 - templates: - # NOTE: Names of vnpu template for 910B4-1 are fixed by Ascend runtime and must not be changed. - # The memory is used for scheduling so the correct values must be set. - # Template vir05_1c_8g actually provides 16GB memory, - - name: vir05_1c_8g - memory: 16384 - aiCore: 5 - aiCPU: 1 - # Template vir10_3c_16g actually provides 32GB memory - - name: vir10_3c_16g - memory: 32768 - aiCore: 10 - aiCPU: 3 - - chipName: 910B4 - commonWord: Ascend910B4 - resourceName: huawei.com/Ascend910B4 - resourceMemoryName: huawei.com/Ascend910B4-memory - memoryAllocatable: 32768 - memoryCapacity: 32768 - memoryFactor: 1 - aiCore: 20 - aiCPU: 7 - templates: - - name: vir05_1c_8g - memory: 8192 - aiCore: 5 - aiCPU: 1 - - name: vir10_3c_16g - memory: 16384 - aiCore: 10 - aiCPU: 3 - - chipName: 310P3 - commonWord: Ascend310P - resourceName: huawei.com/Ascend310P - resourceMemoryName: huawei.com/Ascend310P-memory - memoryAllocatable: 21527 - memoryCapacity: 24576 - memoryFactor: 1 - aiCore: 8 - aiCPU: 7 - templates: - - name: vir01 - memory: 3072 - aiCore: 1 - aiCPU: 1 - - name: vir02 - memory: 6144 - aiCore: 2 - aiCPU: 2 - - name: vir04 - memory: 12288 - aiCore: 4 - aiCPU: 4 - {{ end }} diff --git a/packages/system/hami/charts/hami/templates/scheduler/job-patch/clusterrole.yaml b/packages/system/hami/charts/hami/templates/scheduler/job-patch/clusterrole.yaml deleted file mode 100644 index 77e891cc..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/job-patch/clusterrole.yaml +++ /dev/null @@ -1,21 +0,0 @@ -{{- if and .Values.scheduler.admissionWebhook.enabled (.Values.scheduler.patch.enabled) (not .Values.scheduler.certManager.enabled) -}} -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: {{ include "hami-vgpu.fullname" . }}-admission - annotations: - "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade - "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded - labels: - {{- include "hami-vgpu.labels" . | nindent 4 }} - app.kubernetes.io/component: admission-webhook -rules: - - apiGroups: - - admissionregistration.k8s.io - resources: - #- validatingwebhookconfigurations - - mutatingwebhookconfigurations - verbs: - - get - - update -{{- end }} diff --git a/packages/system/hami/charts/hami/templates/scheduler/job-patch/clusterrolebinding.yaml b/packages/system/hami/charts/hami/templates/scheduler/job-patch/clusterrolebinding.yaml deleted file mode 100644 index 2b82f926..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/job-patch/clusterrolebinding.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if and .Values.scheduler.admissionWebhook.enabled (.Values.scheduler.patch.enabled) (not .Values.scheduler.certManager.enabled) -}} -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: {{ include "hami-vgpu.fullname" . }}-admission - annotations: - "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade - "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded - labels: - {{- include "hami-vgpu.labels" . | nindent 4 }} - app.kubernetes.io/component: admission-webhook -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: {{ include "hami-vgpu.fullname" . }}-admission -subjects: - - kind: ServiceAccount - name: {{ include "hami-vgpu.fullname" . }}-admission - namespace: {{ include "hami-vgpu.namespace" . }} -{{- end }} diff --git a/packages/system/hami/charts/hami/templates/scheduler/job-patch/job-createSecret.yaml b/packages/system/hami/charts/hami/templates/scheduler/job-patch/job-createSecret.yaml deleted file mode 100644 index 0e36d95f..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/job-patch/job-createSecret.yaml +++ /dev/null @@ -1,68 +0,0 @@ -{{- if and .Values.scheduler.admissionWebhook.enabled (.Values.scheduler.patch.enabled) (not .Values.scheduler.certManager.enabled) -}} -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ include "hami-vgpu.fullname" . }}-admission-create - namespace: {{ include "hami-vgpu.namespace" . }} - annotations: - "helm.sh/hook": pre-install,pre-upgrade - "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded - labels: - {{- include "hami-vgpu.labels" . | nindent 4 }} - app.kubernetes.io/component: admission-webhook -spec: - {{- if .Capabilities.APIVersions.Has "batch/v1alpha1" }} - # Alpha feature since k8s 1.12 - ttlSecondsAfterFinished: 0 - {{- end }} - template: - metadata: - name: {{ include "hami-vgpu.fullname" . }}-admission-create - {{- if .Values.scheduler.patch.podAnnotations }} - annotations: {{ toYaml .Values.scheduler.patch.podAnnotations | nindent 8 }} - {{- end }} - labels: - {{- include "hami-vgpu.labels" . | nindent 8 }} - app.kubernetes.io/component: admission-webhook - hami.io/webhook: ignore - spec: - {{- if .Values.scheduler.patch.priorityClassName }} - priorityClassName: {{ .Values.scheduler.patch.priorityClassName }} - {{- end }} - {{- if ge (regexReplaceAll "[^0-9]" .Capabilities.KubeVersion.Minor "" | int) 22 }} - {{- include "hami.scheduler.patch.new.imagePullSecrets" . | nindent 6 }} - {{- else }} - {{- include "hami.scheduler.patch.imagePullSecrets" . | nindent 6 }} - {{- end }} - containers: - - name: create - {{- if ge (regexReplaceAll "[^0-9]" .Capabilities.KubeVersion.Minor "" | int) 22 }} - image: {{ include "hami.scheduler.patch.new.image" . }} - imagePullPolicy: {{ .Values.scheduler.patch.imageNew.pullPolicy }} - {{- else }} - image: {{ include "hami.scheduler.patch.image" . }} - imagePullPolicy: {{ .Values.scheduler.patch.image.pullPolicy }} - {{- end }} - args: - - create - - --cert-name=tls.crt - - --key-name=tls.key - {{- if .Values.scheduler.admissionWebhook.customURL.enabled }} - - --host={{ printf "%s.%s.svc,127.0.0.1,%s" (include "hami-vgpu.scheduler" .) (include "hami-vgpu.namespace" .) .Values.scheduler.admissionWebhook.customURL.host}} - {{- else }} - - --host={{ printf "%s.%s.svc,127.0.0.1" (include "hami-vgpu.scheduler" .) (include "hami-vgpu.namespace" .) }} - {{- end }} - - --namespace={{ include "hami-vgpu.namespace" . }} - - --secret-name={{ include "hami-vgpu.scheduler.tls" . }} - restartPolicy: OnFailure - serviceAccountName: {{ include "hami-vgpu.fullname" . }}-admission - {{- if .Values.scheduler.patch.nodeSelector }} - nodeSelector: {{ toYaml .Values.scheduler.patch.nodeSelector | nindent 8 }} - {{- end }} - {{- if .Values.scheduler.patch.tolerations }} - tolerations: {{ toYaml .Values.scheduler.patch.tolerations | nindent 8 }} - {{- end }} - securityContext: - runAsNonRoot: true - runAsUser: {{ .Values.scheduler.patch.runAsUser }} -{{- end }} diff --git a/packages/system/hami/charts/hami/templates/scheduler/job-patch/job-patchWebhook.yaml b/packages/system/hami/charts/hami/templates/scheduler/job-patch/job-patchWebhook.yaml deleted file mode 100644 index ce52042c..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/job-patch/job-patchWebhook.yaml +++ /dev/null @@ -1,63 +0,0 @@ -{{- if and .Values.scheduler.admissionWebhook.enabled (.Values.scheduler.patch.enabled) (not .Values.scheduler.certManager.enabled) -}} -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ include "hami-vgpu.fullname" . }}-admission-patch - namespace: {{ include "hami-vgpu.namespace" . }} - annotations: - "helm.sh/hook": post-install,post-upgrade - "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded - labels: - {{- include "hami-vgpu.labels" . | nindent 4 }} - app.kubernetes.io/component: admission-webhook -spec: - {{- if .Capabilities.APIVersions.Has "batch/v1alpha1" }} - # Alpha feature since k8s 1.12 - ttlSecondsAfterFinished: 0 - {{- end }} - template: - metadata: - name: {{ include "hami-vgpu.fullname" . }}-admission-patch - {{- if .Values.scheduler.patch.podAnnotations }} - annotations: {{ toYaml .Values.scheduler.patch.podAnnotations | nindent 8 }} - {{- end }} - labels: - {{- include "hami-vgpu.labels" . | nindent 8 }} - app.kubernetes.io/component: admission-webhook - hami.io/webhook: ignore - spec: - {{- if .Values.scheduler.patch.priorityClassName }} - priorityClassName: {{ .Values.scheduler.patch.priorityClassName }} - {{- end }} - {{- if ge (regexReplaceAll "[^0-9]" .Capabilities.KubeVersion.Minor "" | int) 22 }} - {{- include "hami.scheduler.patch.new.imagePullSecrets" . | nindent 6 }} - {{- else }} - {{- include "hami.scheduler.patch.imagePullSecrets" . | nindent 6 }} - {{- end }} - containers: - - name: patch - {{- if ge (regexReplaceAll "[^0-9]" .Capabilities.KubeVersion.Minor "" | int) 22 }} - image: {{ include "hami.scheduler.patch.new.image" . }} - imagePullPolicy: {{ .Values.scheduler.patch.imageNew.pullPolicy }} - {{- else }} - image: {{ include "hami.scheduler.patch.image" . }} - imagePullPolicy: {{ .Values.scheduler.patch.image.pullPolicy }} - {{- end }} - args: - - patch - - --webhook-name={{ include "hami-vgpu.scheduler.webhook" . }} - - --namespace={{ include "hami-vgpu.namespace" . }} - - --patch-validating=false - - --secret-name={{ include "hami-vgpu.scheduler.tls" . }} - restartPolicy: OnFailure - serviceAccountName: {{ include "hami-vgpu.fullname" . }}-admission - {{- if .Values.scheduler.patch.nodeSelector }} - nodeSelector: {{ toYaml .Values.scheduler.patch.nodeSelector | nindent 8 }} - {{- end }} - {{- if .Values.scheduler.patch.tolerations }} - tolerations: {{ toYaml .Values.scheduler.patch.tolerations | nindent 8 }} - {{- end }} - securityContext: - runAsNonRoot: true - runAsUser: {{ .Values.scheduler.patch.runAsUser }} -{{- end }} diff --git a/packages/system/hami/charts/hami/templates/scheduler/job-patch/role.yaml b/packages/system/hami/charts/hami/templates/scheduler/job-patch/role.yaml deleted file mode 100644 index 56682f8e..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/job-patch/role.yaml +++ /dev/null @@ -1,21 +0,0 @@ -{{- if and .Values.scheduler.admissionWebhook.enabled (.Values.scheduler.patch.enabled) (not .Values.scheduler.certManager.enabled) -}} -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ include "hami-vgpu.fullname" . }}-admission - namespace: {{ include "hami-vgpu.namespace" . }} - annotations: - "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade - "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded - labels: - {{- include "hami-vgpu.labels" . | nindent 4 }} - app.kubernetes.io/component: admission-webhook -rules: - - apiGroups: - - "" - resources: - - secrets - verbs: - - get - - create -{{- end }} diff --git a/packages/system/hami/charts/hami/templates/scheduler/job-patch/rolebinding.yaml b/packages/system/hami/charts/hami/templates/scheduler/job-patch/rolebinding.yaml deleted file mode 100644 index 7239b128..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/job-patch/rolebinding.yaml +++ /dev/null @@ -1,21 +0,0 @@ -{{- if and .Values.scheduler.admissionWebhook.enabled (.Values.scheduler.patch.enabled) (not .Values.scheduler.certManager.enabled) -}} -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: {{ include "hami-vgpu.fullname" . }}-admission - namespace: {{ include "hami-vgpu.namespace" . }} - annotations: - "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade - "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded - labels: - {{- include "hami-vgpu.labels" . | nindent 4 }} - app.kubernetes.io/component: admission-webhook -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ include "hami-vgpu.fullname" . }}-admission -subjects: - - kind: ServiceAccount - name: {{ include "hami-vgpu.fullname" . }}-admission - namespace: {{ include "hami-vgpu.namespace" . }} -{{- end }} diff --git a/packages/system/hami/charts/hami/templates/scheduler/job-patch/serviceaccount.yaml b/packages/system/hami/charts/hami/templates/scheduler/job-patch/serviceaccount.yaml deleted file mode 100644 index 857c6a8d..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/job-patch/serviceaccount.yaml +++ /dev/null @@ -1,13 +0,0 @@ -{{- if and .Values.scheduler.admissionWebhook.enabled (.Values.scheduler.patch.enabled) (not .Values.scheduler.certManager.enabled) }} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "hami-vgpu.fullname" . }}-admission - namespace: {{ include "hami-vgpu.namespace" . }} - annotations: - "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade - "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded - labels: - {{- include "hami-vgpu.labels" . | nindent 4 }} - app.kubernetes.io/component: admission-webhook -{{- end }} diff --git a/packages/system/hami/charts/hami/templates/scheduler/role.yaml b/packages/system/hami/charts/hami/templates/scheduler/role.yaml deleted file mode 100644 index 5f7d7e44..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/role.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ include "hami-vgpu.scheduler" . }} - namespace: {{ include "hami-vgpu.namespace" . }} - labels: - app.kubernetes.io/component: "hami-scheduler" - {{- include "hami-vgpu.labels" . | nindent 4 }} -rules: - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["create", "list", "watch", "get", "update", "patch"] diff --git a/packages/system/hami/charts/hami/templates/scheduler/rolebinding.yaml b/packages/system/hami/charts/hami/templates/scheduler/rolebinding.yaml deleted file mode 100644 index 96e175a1..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/rolebinding.yaml +++ /dev/null @@ -1,31 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: {{ include "hami-vgpu.scheduler" . }} - namespace: {{ include "hami-vgpu.namespace" . }} - labels: - app.kubernetes.io/component: "hami-scheduler" - {{- include "hami-vgpu.labels" . | nindent 4 }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ include "hami-vgpu.scheduler" . }} -subjects: - - kind: ServiceAccount - name: {{ include "hami-vgpu.scheduler" . }} - namespace: {{ include "hami-vgpu.namespace" . }} ---- -{{- if .Values.mockDevicePlugin.enabled }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: {{ include "hami-vgpu.mock-device-plugin" . }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: {{ include "hami-vgpu.mock-device-plugin" . }} -subjects: - - kind: ServiceAccount - name: {{ include "hami-vgpu.mock-device-plugin" . }} - namespace: {{ include "hami-vgpu.namespace" . }} -{{- end -}} diff --git a/packages/system/hami/charts/hami/templates/scheduler/service.yaml b/packages/system/hami/charts/hami/templates/scheduler/service.yaml deleted file mode 100644 index d7538fed..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/service.yaml +++ /dev/null @@ -1,34 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ include "hami-vgpu.scheduler" . }} - namespace: {{ include "hami-vgpu.namespace" . }} - labels: - app.kubernetes.io/component: hami-scheduler - {{- include "hami-vgpu.labels" . | nindent 4 }} - {{- if .Values.scheduler.service.labels }} - {{ toYaml .Values.scheduler.service.labels | indent 4 }} - {{- end }} - {{- if .Values.scheduler.service.annotations }} - annotations: {{ toYaml .Values.scheduler.service.annotations | nindent 4 }} - {{- end }} -spec: - type: {{ .Values.scheduler.service.type | default "NodePort" }} # Default type is NodePort - ports: - - name: http - port: {{ .Values.scheduler.service.httpPort | default 443 }} # Default HTTP port is 443 - targetPort: {{ .Values.scheduler.service.httpTargetPort | default 443 }} - {{- if eq (.Values.scheduler.service.type | default "NodePort") "NodePort" }} # If type is NodePort, set nodePort - nodePort: {{ .Values.scheduler.service.schedulerPort | default 31998 }} - {{- end }} - protocol: TCP - - name: monitor - port: {{ .Values.scheduler.service.monitorPort | default 31993 }} # Default monitoring port is 31993 - targetPort: {{ .Values.scheduler.service.monitorTargetPort | default 9395 }} - {{- if eq (.Values.scheduler.service.type | default "NodePort") "NodePort" }} # If type is NodePort, set nodePort - nodePort: {{ .Values.scheduler.service.monitorPort | default 31993 }} - {{- end }} - protocol: TCP - selector: - app.kubernetes.io/component: hami-scheduler - {{- include "hami-vgpu.selectorLabels" . | nindent 4 }} diff --git a/packages/system/hami/charts/hami/templates/scheduler/serviceaccount.yaml b/packages/system/hami/charts/hami/templates/scheduler/serviceaccount.yaml deleted file mode 100644 index 0435c003..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/serviceaccount.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "hami-vgpu.scheduler" . }} - namespace: {{ include "hami-vgpu.namespace" . }} - labels: - app.kubernetes.io/component: "hami-scheduler" - {{- include "hami-vgpu.labels" . | nindent 4 }} ---- -{{- if .Values.mockDevicePlugin.enabled }} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "hami-vgpu.mock-device-plugin" . }} - namespace: {{ include "hami-vgpu.namespace" . }} -{{- end -}} diff --git a/packages/system/hami/charts/hami/templates/scheduler/webhook.yaml b/packages/system/hami/charts/hami/templates/scheduler/webhook.yaml deleted file mode 100644 index db9f8029..00000000 --- a/packages/system/hami/charts/hami/templates/scheduler/webhook.yaml +++ /dev/null @@ -1,57 +0,0 @@ -{{- if .Values.scheduler.admissionWebhook.enabled -}} -apiVersion: admissionregistration.k8s.io/v1 -kind: MutatingWebhookConfiguration -metadata: - {{- if .Values.scheduler.certManager.enabled }} - annotations: - cert-manager.io/inject-ca-from: {{ include "hami-vgpu.namespace" . }}/{{ include "hami-vgpu.scheduler" . }}-serving-cert - {{- end }} - name: {{ include "hami-vgpu.scheduler.webhook" . }} -webhooks: - - admissionReviewVersions: - - v1beta1 - clientConfig: - {{- if .Values.scheduler.admissionWebhook.customURL.enabled }} - url: https://{{ .Values.scheduler.admissionWebhook.customURL.host}}:{{.Values.scheduler.admissionWebhook.customURL.port}}{{.Values.scheduler.admissionWebhook.customURL.path}} - {{- else }} - service: - name: {{ include "hami-vgpu.scheduler" . }} - namespace: {{ include "hami-vgpu.namespace" . }} - path: /webhook - port: {{ .Values.scheduler.service.httpPort }} - {{- end }} - failurePolicy: {{ .Values.scheduler.admissionWebhook.failurePolicy }} - matchPolicy: Equivalent - name: vgpu.hami.io - namespaceSelector: - matchExpressions: - - key: hami.io/webhook - operator: NotIn - values: - - ignore - {{- if .Values.scheduler.admissionWebhook.whitelistNamespaces }} - - key: kubernetes.io/metadata.name - operator: NotIn - values: - {{- toYaml .Values.scheduler.admissionWebhook.whitelistNamespaces | nindent 10 }} - {{- end }} - objectSelector: - matchExpressions: - - key: hami.io/webhook - operator: NotIn - values: - - ignore - reinvocationPolicy: {{ .Values.scheduler.admissionWebhook.reinvocationPolicy }} - rules: - - apiGroups: - - "" - apiVersions: - - v1 - operations: - - CREATE - resources: - - pods - scope: '*' - sideEffects: None - timeoutSeconds: 10 -{{- end }} diff --git a/packages/system/hami/charts/hami/values.yaml b/packages/system/hami/charts/hami/values.yaml deleted file mode 100644 index 01caca0b..00000000 --- a/packages/system/hami/charts/hami/values.yaml +++ /dev/null @@ -1,455 +0,0 @@ -## @section Global configuration -## @param global.imageRegistry Global Docker image registry -## @param global.imagePullSecrets Global Docker image pull secrets -global: - ## @param global.imageRegistry Global Docker image registry - imageRegistry: "" - ## E.g. - ## imagePullSecrets: - ## - myRegistryKeySecretName - ## @param global.imagePullSecrets Global Docker image pull secrets - imagePullSecrets: [] - imageTag: "v2.8.1" - gpuHookPath: /usr/local - labels: {} - annotations: {} - managedNodeSelectorEnable: false - managedNodeSelector: - usage: "gpu" - -nameOverride: "" -fullnameOverride: "" -namespaceOverride: "" - -#Nvidia GPU Parameters -resourceName: "nvidia.com/gpu" -resourceMem: "nvidia.com/gpumem" -resourceMemPercentage: "nvidia.com/gpumem-percentage" -resourceCores: "nvidia.com/gpucores" -resourcePriority: "nvidia.com/priority" - -#MLU Parameters -mluResourceName: "cambricon.com/vmlu" -mluResourceMem: "cambricon.com/mlu.smlu.vmemory" -mluResourceCores: "cambricon.com/mlu.smlu.vcore" - -#Hygon DCU Parameters -dcuResourceName: "hygon.com/dcunum" -dcuResourceMem: "hygon.com/dcumem" -dcuResourceCores: "hygon.com/dcucores" - -#Metax sGPU Parameters -metaxResourceName: "metax-tech.com/sgpu" -metaxResourceCore: "metax-tech.com/vcore" -metaxResourceMem: "metax-tech.com/vmemory" -metaxsGPUTopologyAware: "false" - -#Enflame VGCU Parameters -enflameResourceNameVGCU: "enflame.com/vgcu" -enflameResourceNameVGCUPercentage: "enflame.com/vgcu-percentage" - -#Kunlun XPU Parameters -kunlunResourceName: "kunlunxin.com/xpu" -kunlunResourceVCountName: "kunlunxin.com/vxpu" -kunlunResourceVMemoryName: "kunlunxin.com/vxpu-memory" - -schedulerName: "hami-scheduler" - -scheduler: - # @param nodeName defines the node name and the nvidia-vgpu-scheduler-scheduler will schedule to the node. - # if we install the nvidia-vgpu-scheduler-scheduler as default scheduler, we need to remove the k8s default - # scheduler pod from the cluster first, we must specify node name to skip the schedule workflow. - nodeName: "" - #nodeLabelSelector: - # "gpu": "on" - overwriteEnv: "false" - defaultSchedulerPolicy: - nodeSchedulerPolicy: binpack - gpuSchedulerPolicy: spread - metricsBindAddress: ":9395" - # If set to false, When Pod.Spec.SchedulerName equals to the const DefaultSchedulerName in k8s.io/api/core/v1 package, webhook will not overwrite it - forceOverwriteDefaultScheduler: true - livenessProbe: false - leaderElect: true - # when leaderElect is true, replicas is available, otherwise replicas is 1. - replicas: 1 - kubeScheduler: - # @param enabled indicate whether to run kube-scheduler container in the scheduler pod, it's true by default. - enabled: true - ## @param image.registry kube scheduler image registry - ## @param image.repository kube scheduler image repository - ## @param image.tag kube scheduler image tag (immutable tags are recommended) - ## @param image.pullPolicy kube scheduler image pull policy - ## @param image.pullSecrets Specify docker-registry secret names as an array - image: - registry: "registry.cn-hangzhou.aliyuncs.com" - repository: "google_containers/kube-scheduler" - tag: "" - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: https://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## Example: - ## pullSecrets: - ## - myRegistryKeySecretName - ## - pullSecrets: [] - resources: {} - # If you do want to specify resources, uncomment the following lines, adjust them as necessary. - # and remove the curly braces after 'resources:'. -# limits: -# cpu: 1000m -# memory: 1000Mi -# requests: -# cpu: 100m -# memory: 100Mi - extraNewArgs: - - --config=/config/config.yaml - - -v=4 - extraArgs: - - --policy-config-file=/config/config.json - - -v=4 - extender: - ## @param image.registry scheduler extender image registry - ## @param image.repository scheduler extender image repository - ## @param image.tag scheduler extender image tag (immutable tags are recommended) - ## @param image.pullPolicy scheduler extender image pull policy - ## @param image.pullSecrets Specify docker-registry secret names as an array - image: - registry: "docker.io" - repository: "projecthami/hami" - tag: "" - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: https://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## Example: - ## pullSecrets: - ## - myRegistryKeySecretName - ## - pullSecrets: [] - resources: {} - # If you do want to specify resources, uncomment the following lines, adjust them as necessary, - # and remove the curly braces after 'resources:'. -# limits: -# cpu: 1000m -# memory: 1000Mi -# requests: -# cpu: 100m -# memory: 100Mi - extraArgs: - - --debug - - -v=4 - nodeLockExpire: "5m" - podAnnotations: {} - tolerations: [] - #serviceAccountName: "hami-vgpu-scheduler-sa" - admissionWebhook: - # If set to false, the admission webhook is not installed and any pods that should use HAMi must be - # configured to use the right 'schedulerName' and other device-specific configurations. - enabled: true - customURL: - enabled: false - # must be an endpoint using https. - # should generate host certs here - host: 127.0.0.1 # hostname or ip, can be your node'IP if you want to use https://:/ - port: 31998 - path: /webhook - whitelistNamespaces: - # Specify the namespaces that the webhook will not be applied to. - # - default - # - kube-system - # - istio-system - reinvocationPolicy: Never - failurePolicy: Ignore - ## TLS Certificate Option 1: Use cert-manager to generate self-signed certificate. - ## If enabled, always takes precedence over options 2. - certManager: - enabled: false - ## TLS Certificate Option 2: Use kube-webhook-certgen to generate self-signed certificate. - ## If true and certManager.enabled is false, Helm will automatically create a self-signed cert and secret for you. - patch: - enabled: true - ## @param image.registry scheduler extender image registry - ## @param image.repository scheduler extender image repository - ## @param image.tag scheduler extender image tag (immutable tags are recommended) - ## @param image.pullPolicy scheduler extender image pull policy - ## @param image.pullSecrets Specify docker-registry secret names as an array - image: - registry: "docker.io" - repository: "jettech/kube-webhook-certgen" - tag: "v1.5.2" - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: https://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## Example: - ## pullSecrets: - ## - myRegistryKeySecretName - ## - pullSecrets: [] - ## @param image.registry scheduler extender image registry - ## @param image.repository scheduler extender image repository - ## @param image.tag scheduler extender image tag (immutable tags are recommended) - ## @param image.pullPolicy scheduler extender image pull policy - ## @param image.pullSecrets Specify docker-registry secret names as an array - imageNew: - registry: "docker.io" - repository: "liangjw/kube-webhook-certgen" - tag: "v1.1.1" - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: https://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## Example: - ## pullSecrets: - ## - myRegistryKeySecretName - ## - pullSecrets: [] - priorityClassName: "" - podAnnotations: {} - nodeSelector: {} - tolerations: [] - runAsUser: 2000 - service: - type: NodePort # Default type is NodePort, can be changed to ClusterIP - httpPort: 443 # HTTP port - schedulerPort: 31998 # NodePort for HTTP - monitorPort: 31993 # Monitoring port - monitorTargetPort: 9395 - httpTargetPort: 443 - labels: {} - annotations: {} - -devicePlugin: - enabled: true - gpuOperatorToolkitReady: - enabled: false - hostPath: "/run/nvidia/validations" - ## @param image.registry devicePlugin image registry - ## @param image.repository devicePlugin image repository - ## @param image.tag devicePlugin image tag (immutable tags are recommended) - ## @param image.pullPolicy devicePlugin image pull policy - ## @param image.pullSecrets Specify docker-registry secret names as an array - image: - registry: "docker.io" - repository: "projecthami/hami" - tag: "" - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: https://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## Example: - ## pullSecrets: - ## - myRegistryKeySecretName - ## - pullSecrets: [] - monitor: - ## @param image.registry monitor image registry - ## @param image.repository monitor image repository - ## @param image.tag monitor image tag (immutable tags are recommended) - ## @param image.pullPolicy monitor image pull policy - ## @param image.pullSecrets Specify docker-registry secret names as an array - image: - registry: "docker.io" - repository: "projecthami/hami" - tag: "" - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: https://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## Example: - ## pullSecrets: - ## - myRegistryKeySecretName - ## - pullSecrets: [] - ctrPath: /usr/local/vgpu/containers - resyncInterval: "5m" - extraArgs: - - -v=4 - extraEnvs: {} - resources: {} - # If you do want to specify resources, uncomment the following lines, adjust them as necessary. - # and remove the curly braces after 'resources:'. -# limits: -# cpu: 1000m -# memory: 1000Mi -# requests: -# cpu: 100m -# memory: 100Mi - - deviceSplitCount: 10 - deviceMemoryScaling: 1 - deviceCoreScaling: 1 - # Node configuration for device plugin, Priority: externalConfigName > config > default config - nodeConfiguration: - # If you want to use a custom config.json, you can set the content here. - # If this is set, it will override the default config.json(An example is as follows). - config: | - { - "nodeconfig": [ - { - "name": "your-node-name", - "operatingmode": "hami-core", - "devicememoryscaling": 1, - "devicesplitcount": 10, - "migstrategy": "none", - "filterdevices": { - "uuid": [], - "index": [] - } - } - ] - } - # If you want to use an existing ConfigMap, you can set the name here. - # If this is set, the chart will not create the ConfigMap and will use the existing one. - externalConfigName: "" - # The runtime class name to be used by the device plugin, and added to the pod.spec.runtimeClassName of applications utilizing NVIDIA GPUs - runtimeClassName: "" - # Whether to create runtime class, name comes from runtimeClassName when it is set - createRuntimeClass: false - migStrategy: "none" - disablecorelimit: "false" - passDeviceSpecsEnabled: false - deviceListStrategy: "envvar" - nvidiaHookPath: null - nvidiaDriverRoot: null - gdrcopyEnabled: null - gdsEnabled: null - mofedEnabled: null - - extraArgs: - - -v=4 - extraEnvs: {} - service: - type: NodePort # Default type is NodePort, can be changed to ClusterIP - httpPort: 31992 - labels: {} - annotations: {} - - pluginPath: /var/lib/kubelet/device-plugins - libPath: /usr/local/vgpu - - podAnnotations: {} - nvidiaNodeSelector: - gpu: "on" - tolerations: [] - # The updateStrategy for DevicePlugin DaemonSet. - # If you want to update the DaemonSet by manual, set type as "OnDelete". - # We recommend use OnDelete update strategy because DevicePlugin pod restart will cause business pod restart, this behavior is destructive. - # Otherwise, you can use RollingUpdate update strategy to rolling update DevicePlugin pod. - updateStrategy: - type: RollingUpdate - rollingUpdate: - maxUnavailable: 1 - - resources: {} - # If you do want to specify resources, uncomment the following lines, adjust them as necessary. - # and remove the curly braces after 'resources:'. -# limits: -# cpu: 1000m -# memory: 1000Mi -# requests: -# cpu: 100m -# memory: 100Mi - -mockDevicePlugin: - enabled: false - image: - registry: "docker.io" - repository: "projecthami/mock-device-plugin" - tag: "1.0.1" - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: https://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## Example: - ## pullSecrets: - ## - myRegistryKeySecretName - ## - pullSecrets: [] -devices: - amd: - customresources: - - amd.com/gpu - - amd.com/gpu-memory - awsneuron: - customresources: - - aws.amazon.com/neuron - - aws.amazon.com/neuroncore - kunlun: - enabled: true - customresources: - - kunlunxin.com/xpu - - kunlunxin.com/vxpu - - kunlunxin.com/vxpu-memory - enflame: - enabled: true - customresources: - - enflame.com/vgcu - - enflame.com/vgcu-percentage - - enflame.com/gcu - mthreads: - enabled: true - customresources: - - mthreads.com/vgpu - nvidia: - gpuCorePolicy: default - libCudaLogLevel: 1 - ascend: - enabled: false - image: "" - imagePullPolicy: IfNotPresent - extraArgs: [] - nodeSelector: - ascend: "on" - tolerations: [] - customresources: - - huawei.com/Ascend910A - - huawei.com/Ascend910A-memory - - huawei.com/Ascend910B2 - - huawei.com/Ascend910B2-memory - - huawei.com/Ascend910B3 - - huawei.com/Ascend910B3-memory - - huawei.com/Ascend910B4 - - huawei.com/Ascend910B4-memory - - huawei.com/Ascend910B4-1 - - huawei.com/Ascend910B4-1-memory - - huawei.com/Ascend310P - - huawei.com/Ascend310P-memory - iluvatar: - enabled: false - customresources: - - iluvatar.ai/BI-V100-vgpu - - iluvatar.ai/BI-V100.vCore - - iluvatar.ai/BI-V100.vMem - - iluvatar.ai/BI-V150-vgpu - - iluvatar.ai/BI-V150.vCore - - iluvatar.ai/BI-V150.vMem - - iluvatar.ai/MR-V100-vgpu - - iluvatar.ai/MR-V100.vCore - - iluvatar.ai/MR-V100.vMem - - iluvatar.ai/MR-V50-vgpu - - iluvatar.ai/MR-V50.vCore - - iluvatar.ai/MR-V50.vMem diff --git a/packages/system/hami/values.yaml b/packages/system/hami/values.yaml deleted file mode 100644 index 76e8d631..00000000 --- a/packages/system/hami/values.yaml +++ /dev/null @@ -1,17 +0,0 @@ -hami: - scheduler: - kubeScheduler: - image: - registry: registry.k8s.io - repository: kube-scheduler - devicePlugin: - runtimeClassName: nvidia - updateStrategy: - type: RollingUpdate - # See https://github.com/Project-HAMi/HAMi/blob/v2.8.1/deployments/charts/hami/values.yaml - # Each entry: {"name": "node-name", "operatingmode": "hami-core", "devicesplitcount": N, ...} - nodeConfiguration: - config: | - { - "nodeconfig": [] - } diff --git a/packages/system/harbor/templates/bucket-secret.yaml b/packages/system/harbor/templates/bucket-secret.yaml index 241fe638..d67aeb67 100644 --- a/packages/system/harbor/templates/bucket-secret.yaml +++ b/packages/system/harbor/templates/bucket-secret.yaml @@ -1,20 +1,17 @@ -{{- if .Values.bucket.secretName }} -{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace .Values.bucket.secretName }} -{{- $bucketInfo := fromJson (b64dec (index $existingSecret.data "BucketInfo")) }} -{{- $accessKeyID := index $bucketInfo.spec.secretS3 "accessKeyID" }} -{{- $accessSecretKey := index $bucketInfo.spec.secretS3 "accessSecretKey" }} -{{- $endpoint := index $bucketInfo.spec.secretS3 "endpoint" }} -{{- $bucketName := $bucketInfo.spec.bucketName }} +{{- /* COSI BucketInfo is merged at the values root via Flux valuesFrom (no targetPath). */ -}} +{{- with .Values.spec }} +{{- with .secretS3 }} --- apiVersion: v1 kind: Secret metadata: - name: {{ .Values.harbor.fullnameOverride }}-registry-s3 + name: {{ $.Values.harbor.fullnameOverride }}-registry-s3 type: Opaque stringData: - REGISTRY_STORAGE_S3_ACCESSKEY: {{ $accessKeyID | quote }} - REGISTRY_STORAGE_S3_SECRETKEY: {{ $accessSecretKey | quote }} - REGISTRY_STORAGE_S3_REGIONENDPOINT: {{ $endpoint | quote }} - REGISTRY_STORAGE_S3_BUCKET: {{ $bucketName | quote }} + REGISTRY_STORAGE_S3_ACCESSKEY: {{ index . "accessKeyID" | quote }} + REGISTRY_STORAGE_S3_SECRETKEY: {{ index . "accessSecretKey" | quote }} + REGISTRY_STORAGE_S3_REGIONENDPOINT: {{ index . "endpoint" | quote }} + REGISTRY_STORAGE_S3_BUCKET: {{ $.Values.spec.bucketName | quote }} REGISTRY_STORAGE_S3_REGION: "us-east-1" {{- end }} +{{- end }} diff --git a/packages/system/harbor/values.yaml b/packages/system/harbor/values.yaml index faac7068..9038bd38 100644 --- a/packages/system/harbor/values.yaml +++ b/packages/system/harbor/values.yaml @@ -1,6 +1,4 @@ harbor: {} -bucket: - secretName: "" db: replicas: 2 size: 5Gi diff --git a/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml b/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml index f81eaff0..1631c86e 100644 --- a/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml +++ b/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml @@ -22,7 +22,7 @@ spec: singular: kubernetes plural: kuberneteses openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"},"nodeGroups":{"description":"Worker nodes configuration map.","type":"object","default":{"md0":{"ephemeralStorage":"20Gi","gpus":[],"instanceType":"u1.medium","maxReplicas":10,"minReplicas":0,"resources":{},"roles":["ingress-nginx"]}},"additionalProperties":{"type":"object","required":["ephemeralStorage","instanceType","maxReplicas","minReplicas","resources"],"properties":{"ephemeralStorage":{"description":"Ephemeral storage size.","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"}}}},"instanceType":{"description":"Virtual machine instance type.","type":"string","default":"u1.medium"},"maxReplicas":{"description":"Maximum number of replicas.","type":"integer","default":10},"minReplicas":{"description":"Minimum number of replicas.","type":"integer","default":0},"resources":{"description":"CPU and memory resources for each worker node.","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"}}}}},"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 `.` if empty.","type":"string","default":""},"addons":{"description":"Cluster addons configuration.","type":"object","default":{},"required":["certManager","cilium","coredns","fluxcd","gatewayAPI","gpuOperator","hami","ingressNginx","monitoringAgents","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}}},"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":"large","enum":["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":"micro","enum":["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":"micro","enum":["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":"micro","enum":["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":""}}}}} + {"title":"Chart Values","type":"object","properties":{"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"},"nodeGroups":{"description":"Worker nodes configuration map.","type":"object","default":{"md0":{"ephemeralStorage":"20Gi","gpus":[],"instanceType":"u1.medium","maxReplicas":10,"minReplicas":0,"resources":{},"roles":["ingress-nginx"]}},"additionalProperties":{"type":"object","required":["ephemeralStorage","instanceType","maxReplicas","minReplicas","resources"],"properties":{"ephemeralStorage":{"description":"Ephemeral storage size.","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"}}}},"instanceType":{"description":"Virtual machine instance type.","type":"string","default":"u1.medium"},"maxReplicas":{"description":"Maximum number of replicas.","type":"integer","default":10},"minReplicas":{"description":"Minimum number of replicas.","type":"integer","default":0},"resources":{"description":"CPU and memory resources for each worker node.","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"}}}}},"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 `.` if empty.","type":"string","default":""},"addons":{"description":"Cluster addons configuration.","type":"object","default":{},"required":["certManager","cilium","coredns","fluxcd","gatewayAPI","gpuOperator","ingressNginx","monitoringAgents","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}}},"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}}},"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":"large","enum":["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":"micro","enum":["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":"micro","enum":["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":"micro","enum":["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":""}}}}} release: prefix: kubernetes- labels: @@ -40,7 +40,7 @@ spec: tags: - compute icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl82ODFfMjg0NSkiLz4KPHBhdGggZD0iTTcxLjk5NjggMTlDNzAuMzAzOSAxOS4wMDAyIDY4LjkzMTIgMjAuNTMyMiA2OC45MzE0IDIyLjQyMjFDNjguOTMxNCAyMi40NTExIDY4LjkzNzMgMjIuNDc4OCA2OC45Mzc5IDIyLjUwNzZDNjguOTM1NCAyMi43NjQ0IDY4LjkyMzEgMjMuMDczNyA2OC45MzE0IDIzLjI5NzNDNjguOTcxNyAyNC4zODczIDY5LjIwODIgMjUuMjIxNiA2OS4zNTA2IDI2LjIyNThDNjkuNjA4NCAyOC4zNzUyIDY5LjgyNDUgMzAuMTU2OSA2OS42OTEyIDMxLjgxM0M2OS41NjE1IDMyLjQzNzUgNjkuMTAzNyAzMy4wMDg2IDY4LjY5NTYgMzMuNDA1Nkw2OC42MjM1IDM0LjcwODZDNjYuNzgzOSAzNC44NjE3IDY0LjkzMTkgMzUuMTQyMSA2My4wODIxIDM1LjU2NDFDNTUuMTIyNiAzNy4zNzk4IDQ4LjI2OTUgNDEuNDk5MSA0My4wNTIgNDcuMDYwOUM0Mi43MTM0IDQ2LjgyODggNDIuMTIxMSA0Ni40MDE5IDQxLjk0NSA0Ni4yNzEyQzQxLjM5NzcgNDYuMzQ1NCA0MC44NDQ1IDQ2LjUxNTEgNDAuMTI0MSA0Ni4wOTM1QzM4Ljc1MjIgNDUuMTY1NyAzNy41MDI4IDQzLjg4NTEgMzUuOTkxIDQyLjM0MjRDMzUuMjk4MiA0MS42MDQ0IDM0Ljc5NjYgNDAuOTAxOCAzMy45NzM1IDQwLjE5MDRDMzMuNzg2NiA0MC4wMjg5IDMzLjUwMTQgMzkuODEwNCAzMy4yOTIzIDM5LjY0NDJDMzIuNjQ4OSAzOS4xMjg4IDMxLjg5IDM4Ljg2IDMxLjE1NyAzOC44MzQ4QzMwLjIxNDcgMzguODAyNCAyOS4zMDc1IDM5LjE3MjUgMjguNzEzOCAzOS45MjA2QzI3LjY1ODQgNDEuMjUwNiAyNy45OTYzIDQzLjI4MzMgMjkuNDY3MSA0NC40NjE0QzI5LjQ4MiA0NC40NzM0IDI5LjQ5NzkgNDQuNDgyNyAyOS41MTI5IDQ0LjQ5NDNDMjkuNzE1IDQ0LjY1ODkgMjkuOTYyNSA0NC44Njk4IDMwLjE0ODMgNDUuMDA3NkMzMS4wMjE3IDQ1LjY1NTUgMzEuODE5NSA0NS45ODcyIDMyLjY4OTcgNDYuNTAxNUMzNC41MjMxIDQ3LjYzOTEgMzYuMDQzIDQ4LjU4MjMgMzcuMjQ4NiA0OS43MTk2QzM3LjcxOTQgNTAuMjIzNyAzNy44MDE2IDUxLjExMjIgMzcuODY0MyA1MS40OTY0TDM4Ljg0NjggNTIuMzc4MkMzMy41ODcyIDYwLjMzMDggMzEuMTUzIDcwLjE1MzkgMzIuNTkxNSA4MC4xNjI3TDMxLjMwNzcgODAuNTM3OEMzMC45NjkzIDgwLjk3NjggMzAuNDkxMiA4MS42Njc2IDI5Ljk5MTEgODEuODczOEMyOC40MTM4IDgyLjM3MjkgMjYuNjM4NyA4Mi41NTYyIDI0LjQ5NTYgODIuNzgxOUMyMy40ODk0IDgyLjg2NiAyMi42MjEzIDgyLjgxNTggMjEuNTU0NiA4My4wMTg4QzIxLjMxOTggODMuMDYzNSAyMC45OTI3IDgzLjE0OTEgMjAuNzM1OCA4My4yMDk3QzIwLjcyNjkgODMuMjExNiAyMC43MTg2IDgzLjIxNDIgMjAuNzA5NiA4My4yMTYyQzIwLjY5NTYgODMuMjE5NSAyMC42NzcyIDgzLjIyNjMgMjAuNjYzOCA4My4yMjk0QzE4Ljg1NyA4My42NjggMTcuNjk2MyA4NS4zMzY1IDE4LjA2OTkgODYuOTgwNUMxOC40NDM3IDg4LjYyNDggMjAuMjA4NiA4OS42MjQ4IDIyLjAyNjIgODkuMjMxMkMyMi4wMzkzIDg5LjIyODIgMjIuMDU4NCA4OS4yMjc3IDIyLjA3MiA4OS4yMjQ2QzIyLjA5MjYgODkuMjE5OSAyMi4xMTA2IDg5LjIwOTkgMjIuMTMxIDg5LjIwNDlDMjIuMzg0NCA4OS4xNDkgMjIuNzAxOSA4OS4wODY4IDIyLjkyMzYgODkuMDI3MkMyMy45NzIzIDg4Ljc0NTEgMjQuNzMxOCA4OC4zMzA2IDI1LjY3NDYgODcuOTY3N0MyNy43MDI5IDg3LjIzNjggMjkuMzgyOCA4Ni42MjYyIDMxLjAxOTUgODYuMzg4M0MzMS43MDMgODYuMzM0NSAzMi40MjMyIDg2LjgxMiAzMi43ODE0IDg3LjAxMzRMMzQuMTE3NyA4Ni43ODMxQzM3LjE5MjYgOTYuMzYxMyA0My42MzY2IDEwNC4xMDMgNTEuNzk2MyAxMDguOTYxTDUxLjIzOTYgMTEwLjMwM0M1MS40NDAzIDExMC44MjQgNTEuNjYxNiAxMTEuNTMgNTEuNTEyMSAxMTIuMDQ1QzUwLjkxNzEgMTEzLjU5NSA0OS44OTggMTE1LjIzMSA0OC43Mzc0IDExNy4wNTVDNDguMTc1NSAxMTcuODk4IDQ3LjYwMDQgMTE4LjU1MiA0Ny4wOTM0IDExOS41MTZDNDYuOTcyIDExOS43NDcgNDYuODE3NSAxMjAuMTAyIDQ2LjcwMDQgMTIwLjM0NkM0NS45MTI1IDEyMi4wMzkgNDYuNDkwNCAxMjMuOTkgNDguMDAzOCAxMjQuNzIyQzQ5LjUyNjggMTI1LjQ1OSA1MS40MTcxIDEyNC42ODIgNTIuMjM1MiAxMjIuOTg1QzUyLjIzNjQgMTIyLjk4MiA1Mi4yNDA2IDEyMi45OCA1Mi4yNDE3IDEyMi45NzhDNTIuMjQyNiAxMjIuOTc2IDUyLjI0MDkgMTIyLjk3MyA1Mi4yNDE3IDEyMi45NzFDNTIuMzU4MiAxMjIuNzMxIDUyLjUyMzMgMTIyLjQxNSA1Mi42MjE2IDEyMi4xODhDNTMuMDU2IDEyMS4xODkgNTMuMjAwNSAxMjAuMzMyIDUzLjUwNTkgMTE5LjM2NUM1NC4zMTcgMTE3LjMxOCA1NC43NjI2IDExNS4xNyA1NS44NzkxIDExMy44MzJDNTYuMTg0OSAxMTMuNDY2IDU2LjY4MzMgMTEzLjMyNSA1Ny4yMDAxIDExMy4xODZMNTcuODk0NCAxMTEuOTIyQzY1LjAwOCAxMTQuNjY1IDcyLjk3MDUgMTE1LjQwMiA4MC45MjQ1IDExMy41ODdDODIuNzM5MSAxMTMuMTczIDg0LjQ5MDggMTEyLjYzNyA4Ni4xODQzIDExMS45OTRDODYuMzc5NCAxMTIuMzQyIDg2Ljc0MiAxMTMuMDExIDg2LjgzOTMgMTEzLjE3OUM4Ny4zNjQ0IDExMy4zNTEgODcuOTM3NyAxMTMuNDM5IDg4LjQwNDcgMTE0LjEzM0M4OS4yNDAxIDExNS41NjcgODkuODExNCAxMTcuMjYzIDkwLjUwNzMgMTE5LjMxMkM5MC44MTI4IDEyMC4yNzkgOTAuOTYzOCAxMjEuMTM2IDkxLjM5ODEgMTIyLjEzNkM5MS40OTcxIDEyMi4zNjMgOTEuNjYxNCAxMjIuNjg0IDkxLjc3OCAxMjIuOTI1QzkyLjU5NDQgMTI0LjYyOCA5NC40OTA3IDEyNS40MDcgOTYuMDE1OSAxMjQuNjY5Qzk3LjUyOTIgMTIzLjkzNyA5OC4xMDc3IDEyMS45ODYgOTcuMzE5NCAxMjAuMjkzQzk3LjIwMjMgMTIwLjA0OSA5Ny4wNDEyIDExOS42OTUgOTYuOTE5OCAxMTkuNDY0Qzk2LjQxMjcgMTE4LjQ5OSA5NS44Mzc3IDExNy44NTIgOTUuMjc1OCAxMTcuMDA5Qzk0LjExNTIgMTE1LjE4NSA5My4xNTI2IDExMy42NyA5Mi41NTc1IDExMi4xMkM5Mi4zMDg3IDExMS4zMiA5Mi41OTk1IDExMC44MjMgOTIuNzkzMyAxMTAuMzAzQzkyLjY3NzIgMTEwLjE3IDkyLjQyODggMTA5LjQxNCA5Mi4yODI0IDEwOS4wNTlDMTAwLjc2MiAxMDQuMDI5IDEwNy4wMTcgOTUuOTk4NSAxMDkuOTU1IDg2LjcyMzlDMTEwLjM1MSA4Ni43ODY1IDExMS4wNDEgODYuOTA5MSAxMTEuMjY1IDg2Ljk1NDJDMTExLjcyNiA4Ni42NDg3IDExMi4xNDkgODYuMjUwMSAxMTIuOTgxIDg2LjMxNTlDMTE0LjYxNyA4Ni41NTM3IDExNi4yOTcgODcuMTY0NSAxMTguMzI2IDg3Ljg5NTNDMTE5LjI2OCA4OC4yNTgxIDEyMC4wMjggODguNjc5MyAxMjEuMDc3IDg4Ljk2MTRDMTIxLjI5OCA4OS4wMjEgMTIxLjYxNiA4OS4wNzY2IDEyMS44NjkgODkuMTMyNUMxMjEuODg5IDg5LjEzNzUgMTIxLjkwOCA4OS4xNDc1IDEyMS45MjggODkuMTUyMkMxMjEuOTQyIDg5LjE1NTMgMTIxLjk2MSA4OS4xNTU4IDEyMS45NzQgODkuMTU4OEMxMjMuNzkyIDg5LjU1MiAxMjUuNTU3IDg4LjU1MjYgMTI1LjkzIDg2LjkwODFDMTI2LjMwMyA4NS4yNjQxIDEyNS4xNDMgODMuNTk1MiAxMjMuMzM2IDgzLjE1N0MxMjMuMDc0IDgzLjA5NyAxMjIuNzAxIDgyLjk5NSAxMjIuNDQ2IDgyLjk0NjVDMTIxLjM3OSA4Mi43NDM1IDEyMC41MTEgODIuNzkzNSAxMTkuNTA1IDgyLjcwOTVDMTE3LjM2MSA4Mi40ODM5IDExNS41ODYgODIuMzAwNCAxMTQuMDA5IDgxLjgwMTRDMTEzLjM2NiA4MS41NTA3IDExMi45MDggODAuNzgxOSAxMTIuNjg2IDgwLjQ2NTVMMTExLjQ0OCA4MC4xMDM1QzExMi4wOSA3NS40MzggMTExLjkxNyA3MC41ODI1IDExMC44MDYgNjUuNzI0M0MxMDkuNjg1IDYwLjgyMDggMTA3LjcwNCA1Ni4zMzYxIDEwNS4wNjIgNTIuMzg0OEMxMDUuMzc5IDUyLjA5NDggMTA1Ljk3OSA1MS41NjEyIDEwNi4xNDkgNTEuNDA0M0MxMDYuMTk5IDUwLjg1MTcgMTA2LjE1NiA1MC4yNzIyIDEwNi43MjUgNDkuNjYwM0MxMDcuOTMxIDQ4LjUyMyAxMDkuNDUxIDQ3LjU3OTkgMTExLjI4NCA0Ni40NDIzQzExMi4xNTQgNDUuOTI3OSAxMTIuOTU5IDQ1LjU5NjQgMTEzLjgzMiA0NC45NDg0QzExNC4wMyA0NC44MDE5IDExNC4yOTkgNDQuNTY5OSAxMTQuNTA3IDQ0LjQwMjJDMTE1Ljk3NyA0My4yMjM3IDExNi4zMTYgNDEuMTkxMSAxMTUuMjYgMzkuODYxNEMxMTQuMjA0IDM4LjUzMTcgMTEyLjE1OSAzOC40MDY1IDExMC42ODggMzkuNTg1QzExMC40NzkgMzkuNzUxNiAxMTAuMTk1IDM5Ljk2ODggMTEwLjAwNyA0MC4xMzEyQzEwOS4xODQgNDAuODQyNiAxMDguNjc2IDQxLjU0NTIgMTA3Ljk4MyA0Mi4yODMyQzEwNi40NzEgNDMuODI1OSAxMDUuMjIyIDQ1LjExMyAxMDMuODUgNDYuMDQwOUMxMDMuMjU1IDQ2LjM4ODUgMTAyLjM4NSA0Ni4yNjgyIDEwMS45OSA0Ni4yNDQ5TDEwMC44MjQgNDcuMDgwNkM5NC4xNzUzIDQwLjA3NjMgODUuMTIzNSAzNS41OTgyIDc1LjM3NjYgMzQuNzI4M0M3NS4zNDk0IDM0LjMxNzkgNzUuMzEzNyAzMy41NzYxIDc1LjMwNDYgMzMuMzUyOUM3NC45MDU2IDMyLjk2OTMgNzQuNDIzNSAzMi42NDE4IDc0LjMwMjQgMzEuODEzQzc0LjE2OTEgMzAuMTU2OSA3NC4zOTE3IDI4LjM3NTIgNzQuNjQ5NiAyNi4yMjU4Qzc0Ljc5MTkgMjUuMjIxNiA3NS4wMjg0IDI0LjM4NzMgNzUuMDY4OCAyMy4yOTczQzc1LjA3OCAyMy4wNDk1IDc1LjA2MzIgMjIuNjkgNzUuMDYyMiAyMi40MjIxQzc1LjA2MiAyMC41MzIyIDczLjY4OTggMTguOTk5OCA3MS45OTY4IDE5Wk02OC4xNTg1IDQyLjg4ODZMNjcuMjQ4IDU5LjA0NDdMNjcuMTgyNSA1OS4wNzc2QzY3LjEyMTQgNjAuNTIyOSA2NS45Mzc1IDYxLjY3NyA2NC40ODM5IDYxLjY3N0M2My44ODg0IDYxLjY3NyA2My4zMzg4IDYxLjQ4NDkgNjIuODkyMiA2MS4xNTcxTDYyLjg2NiA2MS4xNzAzTDQ5LjY4MDcgNTEuNzc5NEM1My43MzMxIDQ3Ljc3NTkgNTguOTE2NCA0NC44MTcyIDY0Ljg5IDQzLjQ1NDZDNjUuOTgxMiA0My4yMDU2IDY3LjA3MTkgNDMuMDIwOSA2OC4xNTg1IDQyLjg4ODZaTTc1Ljg0MTcgNDIuODg4NkM4Mi44MTU5IDQzLjc1MDQgODkuMjY1NyA0Ni45MjMyIDk0LjIwODEgNTEuNzg2TDgxLjEwOCA2MS4xMTc2TDgxLjA2MjEgNjEuMDk3OUM3OS44OTk0IDYxLjk1MTIgNzguMjYxMSA2MS43Mzk0IDc3LjM1NDggNjAuNTk3OEM3Ni45ODM1IDYwLjEzMDEgNzYuNzg4NyA1OS41ODAxIDc2Ljc2NTMgNTkuMDI0OUw3Ni43NTIyIDU5LjAxODRMNzUuODQxNyA0Mi44ODg2Wk00NC44OTkxIDU3LjgxNEw1Ni45MzgyIDY4LjYzM0w1Ni45MjUxIDY4LjY5ODhDNTguMDExNyA2OS42NDc5IDU4LjE3MiA3MS4yOTQ5IDU3LjI2NTcgNzIuNDM2OEM1Ni44OTQ0IDcyLjkwNDUgNTYuMzk3NSA3My4yMTgyIDU1Ljg2MzkgNzMuMzY0N0w1NS44NTA4IDczLjQxNzNMNDAuNDE4OCA3Ny44OTIzQzM5LjYzMzQgNzAuNjc2NSA0MS4zMjYxIDYzLjY2MjEgNDQuODk5MSA1Ny44MTRaTTk5LjAwOTQgNTcuODIwNkMxMDAuNzk4IDYwLjczMzYgMTAyLjE1MyA2My45ODcxIDEwMi45NTkgNjcuNTE0M0MxMDMuNzU2IDcwLjk5OTEgMTAzLjk1NiA3NC40Nzc4IDEwMy42MjcgNzcuODM5N0w4OC4xMTY2IDczLjM1MTVMODguMTAzNSA3My4yODU3Qzg2LjcxNDUgNzIuOTA0MyA4NS44NjA5IDcxLjQ4NDggODYuMTg0MyA3MC4wNjExQzg2LjMxNjggNjkuNDc3OCA4Ni42MjQ5IDY4Ljk4NDQgODcuMDQyMyA2OC42MTk4TDg3LjAzNTggNjguNTg2OUw5OS4wMDk0IDU3LjgyMDZaTTY5LjUyNzQgNjkuNDY4OEg3NC40NTk2TDc3LjUyNTEgNzMuMzE4Nkw3Ni40MjQ3IDc4LjEyMjZMNzEuOTk2OCA4MC4yNjE0TDY3LjU1NTggNzguMTE2MUw2Ni40NTU0IDczLjMxMkw2OS41Mjc0IDY5LjQ2ODhaTTg1LjMzOTMgODIuNjQzN0M4NS41NDg5IDgyLjYzMzEgODUuNzU3NiA4Mi42NTIgODUuOTYxNiA4Mi42ODk4TDg1Ljk4NzggODIuNjU2OUwxMDEuOTUgODUuMzY4MkM5OS42MTQyIDkxLjk2MjQgOTUuMTQ0IDk3LjY3NSA4OS4xNzExIDEwMS40OThMODIuOTc0NyA4Ni40NjA2TDgyLjk5NDQgODYuNDM0M0M4Mi40MjUyIDg1LjEwNTUgODIuOTk0OCA4My41NDcyIDg0LjMwNDQgODIuOTEzNUM4NC42Mzk3IDgyLjc1MTMgODQuOTkgODIuNjYxNCA4NS4zMzkzIDgyLjY0MzdaTTU4LjUyOTggODIuNzA5NUM1OS43NDggODIuNzI2NyA2MC44NDA2IDgzLjU3NjEgNjEuMTIzNyA4NC44MjJDNjEuMjU2MiA4NS40MDUyIDYxLjE5MTcgODUuOTgzMSA2MC45NzMgODYuNDkzNUw2MS4wMTg5IDg2LjU1MjdMNTQuODg4IDEwMS40MzlDNDkuMTU1OSA5Ny43NDMyIDQ0LjU5MDQgOTIuMjA5OSA0Mi4xNDgxIDg1LjQyMDhMNTcuOTczMSA4Mi43MjI3TDU3Ljk5OTMgODIuNzU1NkM1OC4xNzYzIDgyLjcyMjkgNTguMzU1OCA4Mi43MDcxIDU4LjUyOTggODIuNzA5NVpNNzEuODk4NiA4OS4yMzEyQzcyLjMyMjkgODkuMjE1NSA3Mi43NTM0IDg5LjMwMyA3My4xNjI3IDg5LjUwMUM3My42OTkyIDg5Ljc2MDYgNzQuMTEzNiA5MC4xNjkyIDc0LjM3NDUgOTAuNjU5Mkg3NC40MzM0TDgyLjIzNDYgMTA0LjgyMUM4MS4yMjIxIDEwNS4xNjIgODAuMTgxMyAxMDUuNDU0IDc5LjExNjcgMTA1LjY5N0M3My4xNTA1IDEwNy4wNTggNjcuMjAzMiAxMDYuNjQ1IDYxLjgxOCAxMDQuODAyTDY5LjU5OTUgOTAuNjY1OEg2OS42MTI2QzcwLjA3OTUgODkuNzg4OCA3MC45NjUgODkuMjY1NiA3MS44OTg2IDg5LjIzMTJaIiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIwLjI1Ii8+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfNjgxXzI4NDUiIHgxPSIxMCIgeTE9IjE1LjUiIHgyPSIxNDQiIHkyPSIxMzEuNSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjNEQ4N0ZGIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzA1NDdEMCIvPgo8L2xpbmVhckdyYWRpZW50Pgo8L2RlZnM+Cjwvc3ZnPgo= - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "storageClass"], ["spec", "nodeGroups"], ["spec", "nodeGroups", "md0"], ["spec", "nodeGroups", "md0", "minReplicas"], ["spec", "nodeGroups", "md0", "maxReplicas"], ["spec", "nodeGroups", "md0", "instanceType"], ["spec", "nodeGroups", "md0", "ephemeralStorage"], ["spec", "nodeGroups", "md0", "roles"], ["spec", "nodeGroups", "md0", "resources"], ["spec", "nodeGroups", "md0", "gpus"], ["spec", "version"], ["spec", "host"], ["spec", "addons"], ["spec", "addons", "certManager"], ["spec", "addons", "certManager", "enabled"], ["spec", "addons", "certManager", "valuesOverride"], ["spec", "addons", "cilium"], ["spec", "addons", "cilium", "valuesOverride"], ["spec", "addons", "gatewayAPI"], ["spec", "addons", "gatewayAPI", "enabled"], ["spec", "addons", "ingressNginx"], ["spec", "addons", "ingressNginx", "enabled"], ["spec", "addons", "ingressNginx", "exposeMethod"], ["spec", "addons", "ingressNginx", "hosts"], ["spec", "addons", "ingressNginx", "valuesOverride"], ["spec", "addons", "gpuOperator"], ["spec", "addons", "gpuOperator", "enabled"], ["spec", "addons", "gpuOperator", "valuesOverride"], ["spec", "addons", "hami"], ["spec", "addons", "hami", "enabled"], ["spec", "addons", "hami", "valuesOverride"], ["spec", "addons", "fluxcd"], ["spec", "addons", "fluxcd", "enabled"], ["spec", "addons", "fluxcd", "valuesOverride"], ["spec", "addons", "monitoringAgents"], ["spec", "addons", "monitoringAgents", "enabled"], ["spec", "addons", "monitoringAgents", "valuesOverride"], ["spec", "addons", "verticalPodAutoscaler"], ["spec", "addons", "verticalPodAutoscaler", "valuesOverride"], ["spec", "addons", "velero"], ["spec", "addons", "velero", "enabled"], ["spec", "addons", "velero", "valuesOverride"], ["spec", "addons", "coredns"], ["spec", "addons", "coredns", "valuesOverride"], ["spec", "controlPlane"], ["spec", "controlPlane", "replicas"], ["spec", "controlPlane", "apiServer"], ["spec", "controlPlane", "apiServer", "resources"], ["spec", "controlPlane", "apiServer", "resourcesPreset"], ["spec", "controlPlane", "controllerManager"], ["spec", "controlPlane", "controllerManager", "resources"], ["spec", "controlPlane", "controllerManager", "resourcesPreset"], ["spec", "controlPlane", "scheduler"], ["spec", "controlPlane", "scheduler", "resources"], ["spec", "controlPlane", "scheduler", "resourcesPreset"], ["spec", "controlPlane", "konnectivity"], ["spec", "controlPlane", "konnectivity", "server"], ["spec", "controlPlane", "konnectivity", "server", "resources"], ["spec", "controlPlane", "konnectivity", "server", "resourcesPreset"], ["spec", "images"], ["spec", "images", "waitForKubeconfig"]] + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "storageClass"], ["spec", "nodeGroups"], ["spec", "nodeGroups", "md0"], ["spec", "nodeGroups", "md0", "minReplicas"], ["spec", "nodeGroups", "md0", "maxReplicas"], ["spec", "nodeGroups", "md0", "instanceType"], ["spec", "nodeGroups", "md0", "ephemeralStorage"], ["spec", "nodeGroups", "md0", "roles"], ["spec", "nodeGroups", "md0", "resources"], ["spec", "nodeGroups", "md0", "gpus"], ["spec", "version"], ["spec", "host"], ["spec", "addons"], ["spec", "addons", "certManager"], ["spec", "addons", "certManager", "enabled"], ["spec", "addons", "certManager", "valuesOverride"], ["spec", "addons", "cilium"], ["spec", "addons", "cilium", "valuesOverride"], ["spec", "addons", "gatewayAPI"], ["spec", "addons", "gatewayAPI", "enabled"], ["spec", "addons", "ingressNginx"], ["spec", "addons", "ingressNginx", "enabled"], ["spec", "addons", "ingressNginx", "exposeMethod"], ["spec", "addons", "ingressNginx", "hosts"], ["spec", "addons", "ingressNginx", "valuesOverride"], ["spec", "addons", "gpuOperator"], ["spec", "addons", "gpuOperator", "enabled"], ["spec", "addons", "gpuOperator", "valuesOverride"], ["spec", "addons", "fluxcd"], ["spec", "addons", "fluxcd", "enabled"], ["spec", "addons", "fluxcd", "valuesOverride"], ["spec", "addons", "monitoringAgents"], ["spec", "addons", "monitoringAgents", "enabled"], ["spec", "addons", "monitoringAgents", "valuesOverride"], ["spec", "addons", "verticalPodAutoscaler"], ["spec", "addons", "verticalPodAutoscaler", "valuesOverride"], ["spec", "addons", "velero"], ["spec", "addons", "velero", "enabled"], ["spec", "addons", "velero", "valuesOverride"], ["spec", "addons", "coredns"], ["spec", "addons", "coredns", "valuesOverride"], ["spec", "controlPlane"], ["spec", "controlPlane", "replicas"], ["spec", "controlPlane", "apiServer"], ["spec", "controlPlane", "apiServer", "resources"], ["spec", "controlPlane", "apiServer", "resourcesPreset"], ["spec", "controlPlane", "controllerManager"], ["spec", "controlPlane", "controllerManager", "resources"], ["spec", "controlPlane", "controllerManager", "resourcesPreset"], ["spec", "controlPlane", "scheduler"], ["spec", "controlPlane", "scheduler", "resources"], ["spec", "controlPlane", "scheduler", "resourcesPreset"], ["spec", "controlPlane", "konnectivity"], ["spec", "controlPlane", "konnectivity", "server"], ["spec", "controlPlane", "konnectivity", "server", "resources"], ["spec", "controlPlane", "konnectivity", "server", "resourcesPreset"], ["spec", "images"], ["spec", "images", "waitForKubeconfig"]] secrets: exclude: [] include: diff --git a/packages/system/lineage-controller-webhook/Makefile b/packages/system/lineage-controller-webhook/Makefile index cdc1cf01..d5bada31 100644 --- a/packages/system/lineage-controller-webhook/Makefile +++ b/packages/system/lineage-controller-webhook/Makefile @@ -4,13 +4,8 @@ NAMESPACE=cozy-system include ../../../hack/common-envs.mk include ../../../hack/package.mk -.PHONY: test - image: image-lineage-controller-webhook -test: - helm unittest . - image-lineage-controller-webhook: docker buildx build -f images/lineage-controller-webhook/Dockerfile ../../.. \ --tag $(REGISTRY)/lineage-controller-webhook:$(call settag,$(TAG)) \ diff --git a/packages/system/lineage-controller-webhook/README.md b/packages/system/lineage-controller-webhook/README.md deleted file mode 100644 index 1e43c9c8..00000000 --- a/packages/system/lineage-controller-webhook/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# lineage-controller-webhook - -Cozystack system package for the **lineage controller webhook** — a mutating -admission webhook that stamps "lineage" labels onto tenant workloads, linking -each resource back to the Cozystack `Application` that ultimately owns it. - -The webhook intercepts CREATE and UPDATE on `pods`, `secrets`, `services`, -`persistentvolumeclaims`, `ingresses.networking.k8s.io`, and -`workloadmonitors.cozystack.io` outside system namespaces. For each request it -walks the ownership graph upward (Kubernetes `ownerReferences`, then the -`HelmRelease` Flux installed the resource with, then the Cozystack -`Application`-derived CRD that produced the HelmRelease) and writes the -discovered application's group, kind and name as labels -(`apps.cozystack.io/application.{group,kind,name}`) on the incoming object. -Those labels let the aggregated API server, the Cozystack dashboard, and other -lineage-aware consumers reason about which application a resource belongs to. - -The webhook serves TLS on port 9443 with a cert-manager issued certificate, and -runs with `failurePolicy: Fail` — every CREATE/UPDATE on the resources above -is gated on the webhook being reachable. - -## Topology - -The chart ships a single shape, modelled on `cozystack-api`: - -- **Deployment** with two replicas (override via `replicas`). -- **Soft `nodeAffinity`** preferring `node-role.kubernetes.io/control-plane` - (`Exists`, so it matches both Talos's empty value and k3s/kubeadm's `"true"`). - Soft means: the pod lands on a control-plane node when one is reachable, and - on any worker otherwise — no override needed for managed Kubernetes, - Cozy-in-Cozy tenants, or any other cluster where control-plane nodes aren't - visible. -- **Permissive `tolerations`** (`[{operator: Exists}]`) so the pod can land on - tainted control-plane nodes when the soft affinity is satisfiable. -- **Soft `podAntiAffinity`** on `kubernetes.io/hostname` so replicas spread - across nodes when possible (best-effort). -- **`PodDisruptionBudget`** with `maxUnavailable: 1`, unconditional. At - `replicas: 1` it's a useful no-op (allows full drain); at `replicas >= 2` - it caps disruption to one pod. -- **`Service` with `spec.trafficDistribution: PreferClose`** so the apiserver - prefers a webhook endpoint on its own node when one exists, and transparently - falls over to a remote endpoint otherwise. Requires Kubernetes ≥ 1.31; on - older clusters the field is silently ignored and traffic uses default - cluster-wide distribution. - -## Parameters - -All keys live under the top-level `lineageControllerWebhook:` map. - -| Name | Description | Value | -| ----------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------- | -| `image` | Container image (digest-pinned) | `ghcr.io/cozystack/cozystack/lineage-controller-webhook:v1.3.0@sha256:e898…6fb0` | -| `debug` | Enable `--zap-log-level=debug` instead of `info` | `false` | -| `replicas` | Deployment replica count | `2` | -| `localK8sAPIEndpoint.enabled` | **Deprecated.** See note below. | `false` | - -### `localK8sAPIEndpoint.enabled` (deprecated) - -When enabled, this injects `KUBERNETES_SERVICE_HOST=status.hostIP` and -`KUBERNETES_SERVICE_PORT=6443` so the webhook talks to the kube-apiserver on -its own node. It was originally added to avoid latency on the -webhook-to-apiserver path, but it is only valid when the pod is actually -scheduled on an apiserver-bearing node — which the chart's soft control-plane -affinity no longer guarantees. With this flag enabled and the pod scheduled -off a control-plane node, the controller will crash-loop dialing a non- -apiserver hostIP. Slated for removal once the latency motivation is addressed -in the webhook itself; **leave disabled**. diff --git a/packages/system/lineage-controller-webhook/templates/workload.yaml b/packages/system/lineage-controller-webhook/templates/daemonset.yaml similarity index 67% rename from packages/system/lineage-controller-webhook/templates/workload.yaml rename to packages/system/lineage-controller-webhook/templates/daemonset.yaml index 097919c6..860aee6e 100644 --- a/packages/system/lineage-controller-webhook/templates/workload.yaml +++ b/packages/system/lineage-controller-webhook/templates/daemonset.yaml @@ -1,11 +1,10 @@ apiVersion: apps/v1 -kind: Deployment +kind: DaemonSet metadata: name: lineage-controller-webhook labels: app: lineage-controller-webhook spec: - replicas: {{ .Values.lineageControllerWebhook.replicas }} selector: matchLabels: app: lineage-controller-webhook @@ -14,25 +13,13 @@ spec: labels: app: lineage-controller-webhook spec: - serviceAccountName: lineage-controller-webhook + {{- with .Values.lineageControllerWebhook.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} tolerations: - operator: Exists - affinity: - nodeAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 100 - preference: - matchExpressions: - - key: node-role.kubernetes.io/control-plane - operator: Exists - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 100 - podAffinityTerm: - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: lineage-controller-webhook + serviceAccountName: lineage-controller-webhook containers: - name: lineage-controller-webhook image: "{{ .Values.lineageControllerWebhook.image }}" diff --git a/packages/system/lineage-controller-webhook/templates/pdb.yaml b/packages/system/lineage-controller-webhook/templates/pdb.yaml deleted file mode 100644 index 0786c8ff..00000000 --- a/packages/system/lineage-controller-webhook/templates/pdb.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: policy/v1 -kind: PodDisruptionBudget -metadata: - name: lineage-controller-webhook -spec: - maxUnavailable: 1 - selector: - matchLabels: - app: lineage-controller-webhook diff --git a/packages/system/lineage-controller-webhook/templates/service.yaml b/packages/system/lineage-controller-webhook/templates/service.yaml index fbb3ad54..c5df90fb 100644 --- a/packages/system/lineage-controller-webhook/templates/service.yaml +++ b/packages/system/lineage-controller-webhook/templates/service.yaml @@ -5,7 +5,7 @@ metadata: labels: app: lineage-controller-webhook spec: - trafficDistribution: PreferClose + internalTrafficPolicy: Local type: ClusterIP ports: - port: 443 diff --git a/packages/system/lineage-controller-webhook/tests/service_test.yaml b/packages/system/lineage-controller-webhook/tests/service_test.yaml deleted file mode 100644 index 3cdbddd8..00000000 --- a/packages/system/lineage-controller-webhook/tests/service_test.yaml +++ /dev/null @@ -1,30 +0,0 @@ -suite: lineage-controller-webhook service -templates: - - templates/service.yaml - -release: - name: lineage-controller-webhook - namespace: cozy-system - -tests: - - it: renders a ClusterIP service on 443 -> 9443 with PreferClose traffic distribution - asserts: - - isKind: - of: Service - - equal: - path: spec.type - value: ClusterIP - - equal: - path: spec.trafficDistribution - value: PreferClose - - notExists: - path: spec.internalTrafficPolicy - - equal: - path: spec.ports[0].port - value: 443 - - equal: - path: spec.ports[0].targetPort - value: 9443 - - equal: - path: spec.selector.app - value: lineage-controller-webhook diff --git a/packages/system/lineage-controller-webhook/tests/workload_test.yaml b/packages/system/lineage-controller-webhook/tests/workload_test.yaml deleted file mode 100644 index bcb24014..00000000 --- a/packages/system/lineage-controller-webhook/tests/workload_test.yaml +++ /dev/null @@ -1,99 +0,0 @@ -suite: lineage-controller-webhook workload + PDB -templates: - - templates/workload.yaml - - templates/pdb.yaml - -release: - name: lineage-controller-webhook - namespace: cozy-system - -tests: - # ---- Default Deployment shape ---------------------------------------------- - - - it: defaults render a 2-replica Deployment with soft control-plane nodeAffinity, soft podAntiAffinity, and Exists tolerations - template: templates/workload.yaml - asserts: - - isKind: - of: Deployment - - equal: - path: spec.replicas - value: 2 - - equal: - path: spec.template.spec.tolerations - value: - - operator: Exists - - equal: - path: spec.template.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution - value: - - weight: 100 - preference: - matchExpressions: - - key: node-role.kubernetes.io/control-plane - operator: Exists - - notExists: - path: spec.template.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution - - equal: - path: spec.template.spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.topologyKey - value: kubernetes.io/hostname - - - it: defaults inject no localK8sAPIEndpoint env vars (knob is opt-in) - template: templates/workload.yaml - asserts: - - notExists: - path: spec.template.spec.containers[0].env - - # ---- PDB -------------------------------------------------------------------- - - - it: PDB renders unconditionally with maxUnavailable=1 at the default replica count - template: templates/pdb.yaml - asserts: - - isKind: - of: PodDisruptionBudget - - equal: - path: spec.maxUnavailable - value: 1 - - notExists: - path: spec.minAvailable - - - it: PDB still renders at replicas=1 (no-op, but consistent shape) - set: - lineageControllerWebhook.replicas: 1 - template: templates/pdb.yaml - asserts: - - isKind: - of: PodDisruptionBudget - - equal: - path: spec.maxUnavailable - value: 1 - - # ---- Replica override ------------------------------------------------------- - - - it: replicas value drives spec.replicas - set: - lineageControllerWebhook.replicas: 5 - template: templates/workload.yaml - asserts: - - equal: - path: spec.replicas - value: 5 - - # ---- Deprecated localK8sAPIEndpoint knob ----------------------------------- - - - it: localK8sAPIEndpoint.enabled=true injects KUBERNETES_SERVICE_HOST and PORT env vars - set: - lineageControllerWebhook.localK8sAPIEndpoint.enabled: true - template: templates/workload.yaml - asserts: - - contains: - path: spec.template.spec.containers[0].env - content: - name: KUBERNETES_SERVICE_HOST - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: status.hostIP - - contains: - path: spec.template.spec.containers[0].env - content: - name: KUBERNETES_SERVICE_PORT - value: "6443" diff --git a/packages/system/lineage-controller-webhook/values.yaml b/packages/system/lineage-controller-webhook/values.yaml index e56d332d..c5671548 100644 --- a/packages/system/lineage-controller-webhook/values.yaml +++ b/packages/system/lineage-controller-webhook/values.yaml @@ -1,13 +1,10 @@ lineageControllerWebhook: image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v1.3.0@sha256:e8984709686a5eaf19b89da378d7b8c688ea5607e0783a88d9c9e4ccfca96fb0 debug: false - replicas: 2 - # DEPRECATED. Injects KUBERNETES_SERVICE_HOST=status.hostIP and - # KUBERNETES_SERVICE_PORT=6443 so the webhook talks to the apiserver on its - # own node — only valid when the pod is actually scheduled on an apiserver- - # bearing node. Now that the chart's nodeAffinity to control-plane is soft - # (preferred, not required), the pod can land off-control-plane and crash- - # loop dialing a non-apiserver hostIP. Slated for removal once webhook - # performance work obviates the locality motivation. Leave disabled. localK8sAPIEndpoint: - enabled: false + enabled: true + # nodeSelector for the DaemonSet + # Talos uses empty value: "node-role.kubernetes.io/control-plane": "" + # Generic k8s (k3s, kubeadm) uses: "node-role.kubernetes.io/control-plane": "true" + nodeSelector: + node-role.kubernetes.io/control-plane: "" diff --git a/packages/system/linstor-gui/Makefile b/packages/system/linstor-gui/Makefile index b408491d..e202f4cc 100644 --- a/packages/system/linstor-gui/Makefile +++ b/packages/system/linstor-gui/Makefile @@ -12,13 +12,18 @@ image: image-linstor-gui image-linstor-gui: docker buildx build images/linstor-gui \ + --provenance false \ + --builder=$(BUILDER) \ + --platform=linux/amd64,linux/arm64 \ --build-arg LINSTOR_GUI_VERSION=$(LINSTOR_GUI_VERSION) \ --tag $(REGISTRY)/linstor-gui:$(call settag,$(LINSTOR_GUI_VERSION)) \ --tag $(REGISTRY)/linstor-gui:$(call settag,$(LINSTOR_GUI_VERSION)-$(TAG)) \ --cache-from type=registry,ref=$(REGISTRY)/linstor-gui:latest \ --cache-to type=inline \ --metadata-file images/linstor-gui.json \ - $(BUILDX_ARGS) + --push=$(PUSH) \ + --label "org.opencontainers.image.source=https://github.com/cozystack/cozystack" \ + --load=$(LOAD) REPOSITORY="$(REGISTRY)/linstor-gui" \ yq -i '.image.repository = strenv(REPOSITORY)' values.yaml TAG="$(call settag,$(LINSTOR_GUI_VERSION))@$$(yq e '."containerimage.digest"' images/linstor-gui.json -o json -r)" \ diff --git a/packages/system/linstor/Makefile b/packages/system/linstor/Makefile index 8d566678..2ad4def9 100644 --- a/packages/system/linstor/Makefile +++ b/packages/system/linstor/Makefile @@ -5,7 +5,7 @@ include ../../../hack/common-envs.mk include ../../../hack/package.mk LINSTOR_VERSION ?= 1.33.2 -LINSTOR_CSI_VERSION ?= v1.10.6 +LINSTOR_CSI_VERSION ?= v1.10.5 image: image-piraeus-server image-linstor-csi diff --git a/packages/system/linstor/images/linstor-csi/Dockerfile b/packages/system/linstor/images/linstor-csi/Dockerfile index df5d179d..6cfd44aa 100644 --- a/packages/system/linstor/images/linstor-csi/Dockerfile +++ b/packages/system/linstor/images/linstor-csi/Dockerfile @@ -1,6 +1,6 @@ FROM golang:1.25 AS builder -ARG VERSION=v1.10.6 +ARG VERSION=v1.10.5 ARG LINSTOR_WAIT_UNTIL_VERSION=v0.3.1 ARG TARGETARCH ARG TARGETOS diff --git a/packages/system/linstor/images/linstor-csi/patches/001-relocate-after-clone-restore.diff b/packages/system/linstor/images/linstor-csi/patches/001-relocate-after-clone-restore.diff index 0e272670..ff0aec0c 100644 --- a/packages/system/linstor/images/linstor-csi/patches/001-relocate-after-clone-restore.diff +++ b/packages/system/linstor/images/linstor-csi/patches/001-relocate-after-clone-restore.diff @@ -1,7 +1,25 @@ diff --git a/pkg/client/linstor.go b/pkg/client/linstor.go -index ac4651d..46d565d 100644 +index f544493..98e7fde 100644 --- a/pkg/client/linstor.go +++ b/pkg/client/linstor.go +@@ -181,7 +181,7 @@ func LogLevel(s string) func(*Linstor) error { + func (s *Linstor) ListAllWithStatus(ctx context.Context) ([]volume.VolumeStatus, error) { + var vols []volume.VolumeStatus + +- resourcesByName := make(map[string][]lapi.Resource) ++ resourcesByName := make(map[string][]lapi.ResourceWithVolumes) + + resDefs, err := s.client.ResourceDefinitions.GetAll(ctx, lapi.RDGetAllRequest{WithVolumeDefinitions: true}) + if err != nil { +@@ -194,7 +194,7 @@ func (s *Linstor) ListAllWithStatus(ctx context.Context) ([]volume.VolumeStatus, + } + + for i := range allResources { +- resourcesByName[allResources[i].Name] = append(resourcesByName[allResources[i].Name], allResources[i].Resource) ++ resourcesByName[allResources[i].Name] = append(resourcesByName[allResources[i].Name], allResources[i]) + } + + for _, rd := range resDefs { @@ -462,6 +462,14 @@ func (s *Linstor) Clone(ctx context.Context, vol, src *volume.Info, params *volu return err } @@ -17,7 +35,7 @@ index ac4651d..46d565d 100644 logger.Debug("reconcile extra properties") err = s.client.ResourceDefinitions.Modify(ctx, vol.ID, lapi.GenericPropsModify{OverrideProps: vol.Properties}) -@@ -1297,6 +1305,14 @@ func (s *Linstor) VolFromSnap(ctx context.Context, snap *volume.Snapshot, vol *v +@@ -1280,6 +1288,14 @@ func (s *Linstor) VolFromSnap(ctx context.Context, snap *volume.Snapshot, vol *v return err } @@ -32,7 +50,7 @@ index ac4651d..46d565d 100644 logger.Debug("reconcile extra properties") err = s.client.ResourceDefinitions.Modify(ctx, vol.ID, lapi.GenericPropsModify{OverrideProps: vol.Properties}) -@@ -1487,9 +1503,8 @@ func (s *Linstor) reconcileSnapshotResources(ctx context.Context, snapshot *volu +@@ -1470,9 +1486,8 @@ func (s *Linstor) reconcileSnapshotResources(ctx context.Context, snapshot *volu return fmt.Errorf("snapshot '%s' not deployed on any node", snap.Name) } @@ -44,7 +62,7 @@ index ac4651d..46d565d 100644 for _, snapNode := range snap.Nodes { if err := s.NodeAvailable(ctx, snapNode); err != nil { logger.WithField("selected node candidate", snapNode).WithError(err).Debug("node is not available") -@@ -1497,13 +1512,23 @@ func (s *Linstor) reconcileSnapshotResources(ctx context.Context, snapshot *volu +@@ -1480,13 +1495,23 @@ func (s *Linstor) reconcileSnapshotResources(ctx context.Context, snapshot *volu } if slices.Contains(preferredNodes, snapNode) { @@ -74,7 +92,7 @@ index ac4651d..46d565d 100644 } if selectedNode == "" { -@@ -1696,6 +1721,114 @@ func (s *Linstor) reconcileResourcePlacement(ctx context.Context, vol *volume.In +@@ -1679,6 +1704,114 @@ func (s *Linstor) reconcileResourcePlacement(ctx context.Context, vol *volume.In return nil } @@ -189,8 +207,40 @@ index ac4651d..46d565d 100644 // FindSnapsByID searches the snapshot in the backend func (s *Linstor) FindSnapsByID(ctx context.Context, id string) ([]*volume.Snapshot, error) { snapshotId, err := volume.ParseSnapshotId(id) +@@ -2173,7 +2306,7 @@ func (s *Linstor) Status(ctx context.Context, volId string) ([]string, *csi.Volu + "volume": volId, + }).Debug("getting assignments") + +- ress, err := s.client.Resources.GetAll(ctx, volId) ++ ress, err := s.client.Resources.GetResourceView(ctx, &lapi.ListOpts{Resource: []string{volId}}) + if err != nil { + return nil, nil, fmt.Errorf("failed to list resources for '%s': %w", volId, err) + } +@@ -2261,13 +2394,20 @@ func GetSnapshotRemoteAndReadiness(snap *lapi.Snapshot) (string, bool, error) { + return "", slices.Contains(snap.Flags, lapiconsts.FlagSuccessful), nil + } + +-func NodesAndConditionFromResources(ress []lapi.Resource) ([]string, *csi.VolumeCondition) { ++func NodesAndConditionFromResources(ress []lapi.ResourceWithVolumes) ([]string, *csi.VolumeCondition) { + var allNodes, abnormalNodes []string + + for i := range ress { + res := &ress[i] + +- allNodes = append(allNodes, res.NodeName) ++ // A resource is a CSI publish target if any of its volumes were created ++ // by ControllerPublishVolume, identified by the temporary-diskless-attach property. ++ if slices.ContainsFunc(res.Volumes, func(v lapi.Volume) bool { ++ createdFor, ok := v.Props[linstor.PropertyCreatedFor] ++ return ok && createdFor == linstor.CreatedForTemporaryDisklessAttach ++ }) { ++ allNodes = append(allNodes, res.NodeName) ++ } + + if res.State == nil { + abnormalNodes = append(abnormalNodes, res.NodeName) diff --git a/pkg/volume/parameter.go b/pkg/volume/parameter.go -index 39acd95..54d1dfb 100644 +index 39acd95..aed18ab 100644 --- a/pkg/volume/parameter.go +++ b/pkg/volume/parameter.go @@ -50,6 +50,7 @@ const ( diff --git a/packages/system/linstor/images/linstor-csi/patches/002-protocol-c-override-for-dual-attach.diff b/packages/system/linstor/images/linstor-csi/patches/002-protocol-c-override-for-dual-attach.diff deleted file mode 100644 index 279c636b..00000000 --- a/packages/system/linstor/images/linstor-csi/patches/002-protocol-c-override-for-dual-attach.diff +++ /dev/null @@ -1,132 +0,0 @@ -diff --git a/pkg/client/linstor.go b/pkg/client/linstor.go -index ac4651d..9d75c79 100644 ---- a/pkg/client/linstor.go -+++ b/pkg/client/linstor.go -@@ -653,11 +653,35 @@ func (s *Linstor) Attach(ctx context.Context, volId, node string, rwxBlock bool) - } - - if otherResInUse > 0 && rwxBlock { -- rdPropsModify := lapi.GenericPropsModify{OverrideProps: map[string]string{ -+ rdProps := map[string]string{ - linstor.PropertyAllowTwoPrimaries: "yes", -- }} -+ } - -- err = s.client.ResourceDefinitions.Modify(ctx, volId, rdPropsModify) -+ // DRBD requires Protocol C whenever allow-two-primaries is enabled. -+ // If the resource is configured with Protocol A or B (typically -+ // through its resource-group), drbdadm adjust on the satellites -+ // fails with "Protocol C required" (errno 139) and live migration -+ // cannot proceed. Override Protocol to C on the resource-definition -+ // itself so it applies to every connection (including diskless -+ // peers, e.g. a TieBreaker). The marker lets Detach revert exactly -+ // the override we installed without disturbing operator-set props. -+ proto, protoErr := s.getEffectiveDrbdProtocol(ctx, volId) -+ if protoErr != nil { -+ s.log.WithError(protoErr).WithField("volume", volId). -+ Warn("failed to determine effective DRBD protocol; skipping Protocol=C override for dual-attach") -+ } else if proto == "A" || proto == "B" { -+ s.log.WithFields(logrus.Fields{ -+ "volume": volId, -+ "protocol": proto, -+ }).Info("installing Protocol=C override on resource-definition for dual-attach") -+ -+ rdProps[linstor.PropertyDrbdNetProtocol] = "C" -+ rdProps[linstor.PropertyCsiProtocolOverride] = "yes" -+ } -+ -+ err = s.client.ResourceDefinitions.Modify(ctx, volId, lapi.GenericPropsModify{ -+ OverrideProps: rdProps, -+ }) - if err != nil { - return "", err - } -@@ -774,11 +798,26 @@ func (s *Linstor) Detach(ctx context.Context, volId, node string) error { - } - - if resInUse == 1 { -- rdPropsModify := lapi.GenericPropsModify{DeleteProps: []string{ -- linstor.PropertyAllowTwoPrimaries, -- }} -+ deleteProps := []string{linstor.PropertyAllowTwoPrimaries} -+ -+ // Drop the Protocol=C override only if Attach installed it (gated by -+ // our marker), so an operator-set Protocol property on the -+ // resource-definition is preserved. -+ rd, getErr := s.client.ResourceDefinitions.Get(ctx, volId) -+ if getErr != nil { -+ log.WithError(getErr).Warn("failed to fetch resource-definition props; skipping Protocol=C override removal") -+ } else if rd.Props[linstor.PropertyCsiProtocolOverride] != "" { -+ log.Info("removing Protocol=C override from resource-definition") - -- err = s.client.ResourceDefinitions.Modify(ctx, volId, rdPropsModify) -+ deleteProps = append(deleteProps, -+ linstor.PropertyDrbdNetProtocol, -+ linstor.PropertyCsiProtocolOverride, -+ ) -+ } -+ -+ err = s.client.ResourceDefinitions.Modify(ctx, volId, lapi.GenericPropsModify{ -+ DeleteProps: deleteProps, -+ }) - if err != nil { - return nil404(err) - } -@@ -805,6 +844,33 @@ func (s *Linstor) Detach(ctx context.Context, volId, node string) error { - return nil404(s.client.Resources.Delete(ctx, volId, node)) - } - -+// getEffectiveDrbdProtocol returns the DRBD network protocol ("A", "B" or "C") -+// that LINSTOR will hand to drbdadm for the given resource. Resource-definition -+// properties take precedence; otherwise the value is inherited from the -+// resource-group. An empty string is returned when neither level sets the -+// property (LINSTOR's compiled-in default is "C"). -+func (s *Linstor) getEffectiveDrbdProtocol(ctx context.Context, volId string) (string, error) { -+ rd, err := s.client.ResourceDefinitions.Get(ctx, volId) -+ if err != nil { -+ return "", err -+ } -+ -+ if v, ok := rd.Props[linstor.PropertyDrbdNetProtocol]; ok { -+ return v, nil -+ } -+ -+ if rd.ResourceGroupName == "" { -+ return "", nil -+ } -+ -+ rg, err := s.client.ResourceGroups.Get(ctx, rd.ResourceGroupName) -+ if err != nil { -+ return "", err -+ } -+ -+ return rg.Props[linstor.PropertyDrbdNetProtocol], nil -+} -+ - // CapacityBytes returns the amount of free space in the storage pool specified by the params and topology. - func (s *Linstor) CapacityBytes(ctx context.Context, storagePools []string, overProvision *float64, segments map[string]string) (int64, error) { - log := s.log.WithField("storage-pools", storagePools).WithField("segments", segments) -diff --git a/pkg/linstor/const.go b/pkg/linstor/const.go -index 9a5f79c..c8bc9c3 100644 ---- a/pkg/linstor/const.go -+++ b/pkg/linstor/const.go -@@ -44,6 +44,19 @@ const ( - // PropertyAllowTwoPrimaries is DRBD option to allow second primary. Mainly used for live-migration. - PropertyAllowTwoPrimaries = lc.NamespcDrbdNetOptions + "/allow-two-primaries" - -+ // PropertyDrbdNetProtocol is the DRBD network replication protocol option -+ // (A = async, B = semi-sync, C = sync). DRBD requires Protocol C whenever -+ // allow-two-primaries is enabled, otherwise drbdadm adjust fails with -+ // "Protocol C required" (errno 139). -+ PropertyDrbdNetProtocol = lc.NamespcDrbdNetOptions + "/protocol" -+ -+ // PropertyCsiProtocolOverride marks a resource-connection where this driver -+ // has installed a temporary Protocol=C override to make a Protocol-A/B -+ // resource compatible with allow-two-primaries during live migration. The -+ // marker lets us safely remove only the overrides we set, without touching -+ // connection-level overrides installed by the operator. -+ PropertyCsiProtocolOverride = lc.NamespcAuxiliary + "/csi-protocol-override" -+ - // CreatedForTemporaryDisklessAttach marks a resource as temporary, i.e. it should be removed after it is no longer - // needed. - CreatedForTemporaryDisklessAttach = "temporary-diskless-attach" diff --git a/packages/system/monitoring-agents/alerts/gpu-recording.rules.yaml b/packages/system/monitoring-agents/alerts/gpu-recording.rules.yaml deleted file mode 100644 index 5e0f9ea0..00000000 --- a/packages/system/monitoring-agents/alerts/gpu-recording.rules.yaml +++ /dev/null @@ -1,172 +0,0 @@ -apiVersion: operator.victoriametrics.com/v1beta1 -kind: VMRule -metadata: - name: alerts-gpu-recording.rules -spec: - groups: - - name: gpu.recording.cluster.1m - interval: 1m - params: {} - rules: - - record: cluster:gpu_count:total - expr: count(group by (UUID) (DCGM_FI_DEV_GPU_UTIL)) - # Kube-allocated GPU count: GPUs requested by *active* pods — Pending - # and Running only. Source of truth for "what tenants asked for" — used - # for capacity planning and billing. Includes system pods so it stays - # consistent with cluster:gpu_count:total when computing :free. - # - # Phase filter via group_left() on kube_pod_status_phase matches the - # canonical pattern from k8s.rules.container_resource.yaml. Without it - # kube-state-metrics keeps series for Failed/Succeeded pods until the - # apiserver garbage-collects them, which inflates billing hours and - # can push :free negative. - - record: cluster:gpu_count:allocated - expr: | - sum( - kube_pod_container_resource_requests{resource="nvidia_com_gpu"} - * on (namespace, pod) group_left() max by (namespace, pod) ( - kube_pod_status_phase{phase=~"Pending|Running"} == 1 - ) - ) - # clamp_min guards against transients at DCGM/kube-state-metrics - # restart where cluster:gpu_count:total dips before :allocated catches - # up, or rare label drift between the two sources. - - record: cluster:gpu_count:free - expr: clamp_min(cluster:gpu_count:total - (cluster:gpu_count:allocated or vector(0)), 0) - # Cluster-layer filter asymmetry — intentional, not a bug: - # - # * cluster:gpu_count:total and cluster:gpu_power_watts:sum do NOT - # filter cozy-*/kube-* namespaces because they describe the - # physical hardware fleet. A GPU draws power and occupies a - # slot regardless of which namespace's pod happens to hold it; - # excluding infra pods would under-report raw capacity and - # break capacity-planning math. - # - # No namespace filter — DCGM exporter metrics carry the exporter's - # own namespace, not the workload namespace. Filtering by namespace - # here would silently drop all series. - - record: cluster:gpu_util:avg - expr: avg(DCGM_FI_DEV_GPU_UTIL) - - record: cluster:gpu_power_watts:sum - expr: sum(DCGM_FI_DEV_POWER_USAGE) - - - name: gpu.recording.node.1m - interval: 1m - params: {} - rules: - # Kube-requested GPU count per namespace — billable/allocation view. - # This is the only namespace-level rule that works correctly because - # kube_pod_container_resource_requests carries the real workload - # namespace, unlike DCGM exporter metrics which carry the exporter's - # own namespace (cozy-gpu-operator). - - record: namespace:gpu_count:allocated - expr: | - sum by (namespace) ( - kube_pod_container_resource_requests{resource="nvidia_com_gpu", namespace!="", namespace!~"cozy-.*|kube-.*"} - * on (namespace, pod) group_left() max by (namespace, pod) ( - kube_pod_status_phase{phase=~"Pending|Running"} == 1 - ) - ) - # Hardware metrics aggregated per node (Hostname). DCGM exporter - # metrics carry a capital-H "Hostname" label identifying the node - # but no usable workload namespace — so hardware telemetry is - # aggregated at node/GPU granularity, not namespace. - - record: node:gpu_util:avg - expr: avg by (Hostname) (DCGM_FI_DEV_GPU_UTIL) - - record: node:tensor_active:avg - expr: avg by (Hostname) (DCGM_FI_PROF_PIPE_TENSOR_ACTIVE) - - record: node:fb_used_bytes:sum - expr: sum by (Hostname) (DCGM_FI_DEV_FB_USED) * 1048576 - - record: node:power_watts:sum - expr: sum by (Hostname) (DCGM_FI_DEV_POWER_USAGE) - - # Known gap: there is no lower-bound sanity alert for under-reporting. - # If DCGM ever switches POWER/THERMAL_VIOLATION counter units the other - # direction — e.g. from the current nanoseconds back to microseconds, - # making rate()/1e9 produce values around 0.001 instead of real - # fractions — the throttle signal would silently collapse to near-zero - # instead of plateauing at 1.0. GPUThrottleFractionOverOne below catches - # the >1 drift; the <<1 drift would require correlation with independent - # signals (power draw vs TDP, clock dips) that we do not yet wire up. - # Documented here so future maintainers know this is a known gap, not - # an oversight. - - name: gpu.recording.efficiency.1m - interval: 1m - params: {} - rules: - # Tensor hardware saturation per GPU — the honest "am I using the GPU" - # metric for AI/LLM workloads. Aggregated at GPU level (Hostname + - # gpu + UUID) because DCGM metrics don't carry workload namespace. - # Stored as a 0..1 ratio; consumers multiply by 100 at display time. - - record: gpu:tensor_saturation:avg5m - expr: | - avg by (Hostname, gpu, UUID) ( - avg_over_time(DCGM_FI_PROF_PIPE_TENSOR_ACTIVE[5m]) - ) - - # Power efficiency per GPU — utilization per watt, reveals - # unoptimized workloads. Explicit on(...) pins the matching set - # to GPU-identifying labels. - - record: gpu:util_per_watt:avg5m - expr: | - max by (Hostname, gpu, UUID) ( - avg_over_time(DCGM_FI_DEV_GPU_UTIL[5m]) - ) - / on (Hostname, gpu, UUID) - clamp_min( - max by (Hostname, gpu, UUID) ( - avg_over_time(DCGM_FI_DEV_POWER_USAGE[5m]) - ), - 1 - ) - - # Fraction of time power-throttled (TDP cap) — 1.0 = fully throttled. - # DCGM_FI_DEV_*_VIOLATION is documented as µs but on A10/DCGM 3.x the - # counter grows in nanoseconds in practice — divide by 1e9 to get a - # 0..1 fraction (verified empirically when /1e6 yielded >100× reality). - # clamp_max protects against rate() artefacts at counter resets. - # - # max by (Hostname, gpu, UUID) collapses duplicate series from - # dcgm-exporter's pod-mapping labels (one physical GPU may appear - # under multiple pod/container labels during restarts or under - # MIG/MPS). Throttling is a physical-GPU property; taking max - # keeps consumer avg(...) honest — a naive avg() would dilute the - # signal by the number of pods sharing the GPU. - - record: gpu:power_throttle_fraction:rate5m - expr: clamp_max(max by (Hostname, gpu, UUID) (rate(DCGM_FI_DEV_POWER_VIOLATION[5m]) / 1e9), 1) - - # Fraction of time thermal-throttled. - - record: gpu:thermal_throttle_fraction:rate5m - expr: clamp_max(max by (Hostname, gpu, UUID) (rate(DCGM_FI_DEV_THERMAL_VIOLATION[5m]) / 1e9), 1) - - # Regression watch — the /1e9 divisor on *_VIOLATION was derived - # empirically against DCGM 3.x on A10. If a future DCGM version - # restores the documented µs units, pre-clamp values would exceed 1.0 - # while clamp_max(..., 1) silently masks the drift — recorded - # throttle fractions would plateau at 1.0 and consumers would think - # every GPU is always throttled. This alert fires on that condition - # so we can rescale to /1e6 before dashboards start lying. - - name: gpu.recording.throttle.validation.5m - interval: 5m - params: {} - rules: - - alert: GPUThrottleFractionOverOne - expr: | - max by (Hostname, gpu, UUID) (rate(DCGM_FI_DEV_POWER_VIOLATION[5m]) / 1e9) > 1 - or - max by (Hostname, gpu, UUID) (rate(DCGM_FI_DEV_THERMAL_VIOLATION[5m]) / 1e9) > 1 - for: 15m - labels: - severity: warning - component: gpu-monitoring - annotations: - summary: "DCGM throttle fraction exceeds 1.0 pre-clamp on {{ $labels.Hostname }}/{{ $labels.gpu }}" - description: | - gpu:{power,thermal}_throttle_fraction:rate5m clamps to 1.0, but the raw - rate(DCGM_FI_DEV_*_VIOLATION)/1e9 value is already >1 on this GPU — the - empirical nanosecond assumption is broken. Likely the DCGM exporter - version changed and the counter now grows in its documented µs units. - Re-verify on the exporter Pod and adjust the divisor (/1e9 → /1e6) in - gpu.recording.efficiency.1m before dashboards plateau at 100% throttle. - verified_on: "NVIDIA A10, DCGM 3.x — other GPU families (H100, L40S) may require a different divisor" - runbook: "If this alert fires, DCGM may have changed POWER_VIOLATION/THERMAL_VIOLATION counter units. Recalibrate by comparing raw counter rate against known throttle events; see gpu-recording.rules.yaml comments." diff --git a/packages/system/monitoring/dashboards-infra.list b/packages/system/monitoring/dashboards-infra.list index 4c7e494d..b5b9f52a 100644 --- a/packages/system/monitoring/dashboards-infra.list +++ b/packages/system/monitoring/dashboards-infra.list @@ -32,8 +32,3 @@ hubble/overview hubble/dns-namespace hubble/l7-http-metrics hubble/network-overview -gpu/gpu-performance -gpu/gpu-efficiency -gpu/gpu-quotas -gpu/gpu-fleet -gpu/gpu-tenants diff --git a/packages/system/objectstorage-controller/images/objectstorage/Dockerfile b/packages/system/objectstorage-controller/images/objectstorage/Dockerfile index 260446c3..d71e5566 100644 --- a/packages/system/objectstorage-controller/images/objectstorage/Dockerfile +++ b/packages/system/objectstorage-controller/images/objectstorage/Dockerfile @@ -2,12 +2,15 @@ FROM alpine AS source ARG COMMIT_REF=v0.2.2 -RUN apk add --no-cache curl tar +RUN apk add --no-cache curl tar git WORKDIR /src RUN curl -sSL https://github.com/kubernetes-sigs/container-object-storage-interface/archive/${COMMIT_REF}.tar.gz \ | tar -xz --strip-components=1 +COPY patches /patches +RUN git apply /patches/*.diff + FROM --platform=$BUILDPLATFORM docker.io/golang:1.24 AS builder ARG TARGETOS ARG TARGETARCH diff --git a/packages/system/objectstorage-controller/images/objectstorage/patches/91-bucketaccess-conflict-retry.diff b/packages/system/objectstorage-controller/images/objectstorage/patches/91-bucketaccess-conflict-retry.diff new file mode 100644 index 00000000..e4b06c42 --- /dev/null +++ b/packages/system/objectstorage-controller/images/objectstorage/patches/91-bucketaccess-conflict-retry.diff @@ -0,0 +1,39 @@ +diff --git a/sidecar/pkg/bucketaccess/bucketaccess_controller.go b/sidecar/pkg/bucketaccess/bucketaccess_controller.go +index a4db5c4..b351f9e 100644 +--- a/sidecar/pkg/bucketaccess/bucketaccess_controller.go ++++ b/sidecar/pkg/bucketaccess/bucketaccess_controller.go +@@ -31,6 +31,7 @@ import ( + kube "k8s.io/client-go/kubernetes" + kubecorev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/tools/record" ++ "k8s.io/client-go/util/retry" + "k8s.io/klog/v2" + cosiapi "sigs.k8s.io/container-object-storage-interface/client/apis" + "sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/consts" +@@ -273,11 +274,22 @@ func (bal *BucketAccessListener) Add(ctx context.Context, inputBucketAccess *v1a + } + } + +- if controllerutil.AddFinalizer(bucket, consts.BABucketFinalizer) { +- _, err = bal.buckets().Update(ctx, bucket, metav1.UpdateOptions{}) +- if err != nil { +- return bal.recordError(inputBucketAccess, v1.EventTypeWarning, v1alpha1.FailedGrantAccess, err) ++ // Re-fetch and update inside RetryOnConflict to handle the case where the Bucket ++ // reconciler in the same controller process mutates the Bucket between our Get ++ // and Update, which surfaces as "the object has been modified" on the parent ++ // Bucket and emits FailedGrantAccess on the BucketAccess. ++ if err := retry.RetryOnConflict(retry.DefaultRetry, func() error { ++ latest, getErr := bal.buckets().Get(ctx, bucketClaim.Status.BucketName, metav1.GetOptions{}) ++ if getErr != nil { ++ return getErr ++ } ++ if !controllerutil.AddFinalizer(latest, consts.BABucketFinalizer) { ++ return nil + } ++ _, updateErr := bal.buckets().Update(ctx, latest, metav1.UpdateOptions{}) ++ return updateErr ++ }); err != nil { ++ return bal.recordError(inputBucketAccess, v1.EventTypeWarning, v1alpha1.FailedGrantAccess, err) + } + + if controllerutil.AddFinalizer(bucketAccess, consts.BAFinalizer) { diff --git a/packages/system/postgres-operator/Makefile b/packages/system/postgres-operator/Makefile index 5279f8a7..f6d7ddaa 100644 --- a/packages/system/postgres-operator/Makefile +++ b/packages/system/postgres-operator/Makefile @@ -3,9 +3,6 @@ export NAMESPACE=cozy-$(NAME) include ../../../hack/package.mk -test: - helm unittest . - update: rm -rf charts helm repo add cnpg https://cloudnative-pg.github.io/charts diff --git a/packages/system/postgres-operator/templates/webhook-ready-hook.yaml b/packages/system/postgres-operator/templates/webhook-ready-hook.yaml deleted file mode 100644 index 51a8ba81..00000000 --- a/packages/system/postgres-operator/templates/webhook-ready-hook.yaml +++ /dev/null @@ -1,148 +0,0 @@ -{{- /* - Post-install gate: block the HelmRelease from reporting Ready until the - cnpg admission webhook actually serves through the cluster Service. Helm - --wait on the controller pod passes once its readinessProbe passes, but - EndpointSlice propagation and kube-proxy/cilium data-plane programming - can lag by a second or two — long enough for any HelmRelease that - depends on postgres-operator (e.g. cozy-keycloak, tenant Postgres apps) - to fire its own install and have kube-apiserver hit the mcluster.cnpg.io - mutating webhook with "dial tcp :443: connect: connection refused". - - The Job uses the apiserver service proxy, which exercises the same - endpoint-resolution and apiserver-initiated pod dial that the admission - webhook path uses. Once /readyz answers through the proxy the data-plane - race is resolved. It does not verify the webhook's TLS CA bundle, so - this gate is scoped to reachability regressions, not cert rotation. - - The service name and port name are hardcoded literals. Upstream cnpg - pins the service name in charts/cloudnative-pg/values.yaml with a - comment "DO NOT CHANGE THE SERVICE NAME as it is currently used to - generate the certificate and can not be configured". The port name is - fixed in charts/cloudnative-pg/templates/service.yaml (ports[0].name: - webhook-server). If a future `make update` ever changes either literal - upstream, the sync-check helm-unittest test - (tests/webhook-ready-hook_test.yaml) renders the subchart Service and - fails if the literal drifts — forcing this template to be updated in - the same change. -*/}} -{{- $_ := required "webhookReady.image.repository must be set to the container image providing kubectl for the post-install readiness Job" .Values.webhookReady.image.repository -}} -{{- $_ := required "webhookReady.image.tag must be set for the post-install readiness Job" .Values.webhookReady.image.tag -}} -{{- /* $svcName and $portName are hardcoded literals; see header comment. */ -}} -{{- $svcName := "cnpg-webhook-service" -}} -{{- /* $portName is the service port NAME, not number — matches ports[0].name in the vendored subchart's Service. */ -}} -{{- $portName := "webhook-server" -}} -{{- $resourceName := printf "https:%s:%s" $svcName $portName -}} -{{- $maxAttempts := .Values.webhookReady.maxAttempts | default 60 -}} -{{- $sleepSeconds := .Values.webhookReady.sleepSeconds | default 2 -}} -{{- /* Derive activeDeadlineSeconds from retries + 60s slack so a values override that raises maxAttempts doesn't get silently cut. */ -}} -{{- $deadline := add (mul (int $maxAttempts) (int $sleepSeconds)) 60 -}} -{{- $image := printf "%s:%s" .Values.webhookReady.image.repository .Values.webhookReady.image.tag -}} -{{- if .Values.webhookReady.image.digest }} -{{- $image = printf "%s@%s" $image .Values.webhookReady.image.digest -}} -{{- end }} ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ .Release.Name }}-webhook-ready - namespace: {{ .Release.Namespace }} - annotations: - helm.sh/hook: post-install,post-upgrade - helm.sh/hook-weight: "0" - helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ .Release.Name }}-webhook-ready - namespace: {{ .Release.Namespace }} - annotations: - helm.sh/hook: post-install,post-upgrade - helm.sh/hook-weight: "0" - helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded -rules: - - apiGroups: [""] - resources: ["services/proxy"] - resourceNames: [{{ $resourceName | quote }}] - verbs: ["get"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: {{ .Release.Name }}-webhook-ready - namespace: {{ .Release.Namespace }} - annotations: - helm.sh/hook: post-install,post-upgrade - helm.sh/hook-weight: "0" - helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ .Release.Name }}-webhook-ready -subjects: - - kind: ServiceAccount - name: {{ .Release.Name }}-webhook-ready - namespace: {{ .Release.Namespace }} ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ .Release.Name }}-webhook-ready - namespace: {{ .Release.Namespace }} - annotations: - helm.sh/hook: post-install,post-upgrade - helm.sh/hook-weight: "10" - helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded -spec: - backoffLimit: {{ .Values.webhookReady.backoffLimit | default 2 }} - activeDeadlineSeconds: {{ $deadline }} - template: - spec: - restartPolicy: Never - serviceAccountName: {{ .Release.Name }}-webhook-ready - securityContext: - runAsNonRoot: true - runAsUser: 65532 - runAsGroup: 65532 - seccompProfile: - type: RuntimeDefault - containers: - - name: wait - image: {{ $image }} - imagePullPolicy: {{ if .Values.webhookReady.image.digest }}IfNotPresent{{ else }}Always{{ end }} - resources: - requests: - cpu: 10m - memory: 32Mi - limits: - cpu: 100m - memory: 64Mi - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - capabilities: - drop: ["ALL"] - command: - - sh - - -c - - | - set -e - ns={{ .Release.Namespace }} - proxy="/api/v1/namespaces/${ns}/services/{{ $resourceName }}/proxy/readyz" - max_attempts={{ $maxAttempts }} - sleep_seconds={{ $sleepSeconds }} - i=0 - last_err="" - until last_err=$(kubectl get --raw "$proxy" 2>&1 >/dev/null); do - i=$((i + 1)) - if [ $i -gt $max_attempts ]; then - echo "timeout: cnpg webhook did not respond through the apiserver proxy after ${max_attempts} attempts (${sleep_seconds}s each)" - echo "last error: ${last_err}" - exit 1 - fi - if [ $((i % 10)) -eq 1 ]; then - echo "attempt $i/${max_attempts}: ${last_err}" - fi - sleep "$sleep_seconds" - done - echo "cnpg webhook is ready" diff --git a/packages/system/postgres-operator/tests/webhook-ready-hook_test.yaml b/packages/system/postgres-operator/tests/webhook-ready-hook_test.yaml deleted file mode 100644 index 3253c663..00000000 --- a/packages/system/postgres-operator/tests/webhook-ready-hook_test.yaml +++ /dev/null @@ -1,328 +0,0 @@ -suite: cnpg webhook post-install readiness gate - -templates: - - templates/webhook-ready-hook.yaml - - charts/cloudnative-pg/templates/service.yaml - -release: - name: postgres-operator - namespace: cozy-postgres-operator - -tests: - - it: renders four hook objects (SA + Role + RoleBinding + Job) - template: templates/webhook-ready-hook.yaml - asserts: - - hasDocuments: - count: 4 - - - it: every rendered object carries post-install and post-upgrade hook annotations - template: templates/webhook-ready-hook.yaml - asserts: - - documentIndex: 0 - equal: - path: metadata.annotations["helm.sh/hook"] - value: post-install,post-upgrade - - documentIndex: 1 - equal: - path: metadata.annotations["helm.sh/hook"] - value: post-install,post-upgrade - - documentIndex: 2 - equal: - path: metadata.annotations["helm.sh/hook"] - value: post-install,post-upgrade - - documentIndex: 3 - equal: - path: metadata.annotations["helm.sh/hook"] - value: post-install,post-upgrade - - - it: RBAC is created before the Job (hook-weight ordering) - template: templates/webhook-ready-hook.yaml - asserts: - - documentIndex: 0 - equal: - path: kind - value: ServiceAccount - - documentIndex: 0 - equal: - path: metadata.annotations["helm.sh/hook-weight"] - value: "0" - - documentIndex: 1 - equal: - path: kind - value: Role - - documentIndex: 1 - equal: - path: metadata.annotations["helm.sh/hook-weight"] - value: "0" - - documentIndex: 2 - equal: - path: kind - value: RoleBinding - - documentIndex: 2 - equal: - path: metadata.annotations["helm.sh/hook-weight"] - value: "0" - - documentIndex: 3 - equal: - path: kind - value: Job - - documentIndex: 3 - equal: - path: metadata.annotations["helm.sh/hook-weight"] - value: "10" - - - it: RBAC resourceName matches the exact proxy URL segment the Job probes - template: templates/webhook-ready-hook.yaml - asserts: - - documentIndex: 1 - equal: - path: rules[0].resources[0] - value: services/proxy - - documentIndex: 1 - equal: - path: rules[0].resourceNames[0] - value: https:cnpg-webhook-service:webhook-server - - documentIndex: 3 - matchRegex: - path: spec.template.spec.containers[0].command[2] - pattern: "/api/v1/namespaces/\\$\\{ns\\}/services/https:cnpg-webhook-service:webhook-server/proxy/readyz" - - - it: hardcoded service name in the hook matches the vendored cnpg subchart Service (drift guard for make update) - template: charts/cloudnative-pg/templates/service.yaml - asserts: - - equal: - path: metadata.name - value: cnpg-webhook-service - - equal: - path: spec.ports[0].name - value: webhook-server - - - it: Job calls kubectl get --raw on the proxy path - template: templates/webhook-ready-hook.yaml - asserts: - - documentIndex: 3 - matchRegex: - path: spec.template.spec.containers[0].command[2] - pattern: "kubectl get --raw" - - - it: Job image is digest-pinned when webhookReady.image.digest is set - template: templates/webhook-ready-hook.yaml - set: - webhookReady: - image: - repository: docker.io/clastix/kubectl - tag: v1.32 - digest: sha256:b9ef7d8dbe65bcc81a46c09b8dc7543103055021c4f43287bf59e92a8f4fe05c - asserts: - - documentIndex: 3 - equal: - path: spec.template.spec.containers[0].image - value: docker.io/clastix/kubectl:v1.32@sha256:b9ef7d8dbe65bcc81a46c09b8dc7543103055021c4f43287bf59e92a8f4fe05c - - - it: Job image falls back to tag-only when digest is not configured - template: templates/webhook-ready-hook.yaml - set: - webhookReady: - image: - repository: docker.io/clastix/kubectl - tag: v1.32 - digest: "" - asserts: - - documentIndex: 3 - equal: - path: spec.template.spec.containers[0].image - value: docker.io/clastix/kubectl:v1.32 - - - it: backoffLimit defaults to 2 so transient pod-level failures retry instead of killing the HelmRelease - template: templates/webhook-ready-hook.yaml - asserts: - - documentIndex: 3 - equal: - path: spec.backoffLimit - value: 2 - - - it: backoffLimit is overridable - template: templates/webhook-ready-hook.yaml - set: - webhookReady: - backoffLimit: 5 - asserts: - - documentIndex: 3 - equal: - path: spec.backoffLimit - value: 5 - - - it: chart render fails when webhookReady.image.repository is empty - template: templates/webhook-ready-hook.yaml - set: - webhookReady: - image: - repository: "" - tag: v1.32 - asserts: - - failedTemplate: - errorMessage: "webhookReady.image.repository must be set to the container image providing kubectl for the post-install readiness Job" - - - it: chart render fails when webhookReady.image.tag is empty - template: templates/webhook-ready-hook.yaml - set: - webhookReady: - image: - repository: docker.io/clastix/kubectl - tag: "" - asserts: - - failedTemplate: - errorMessage: "webhookReady.image.tag must be set for the post-install readiness Job" - - - it: retry loop bounds default when blanked so a wiped override still produces a working Job - template: templates/webhook-ready-hook.yaml - set: - webhookReady: - image: - repository: docker.io/clastix/kubectl - tag: v1.32 - maxAttempts: null - sleepSeconds: null - asserts: - - documentIndex: 3 - matchRegex: - path: spec.template.spec.containers[0].command[2] - pattern: "max_attempts=60" - - documentIndex: 3 - matchRegex: - path: spec.template.spec.containers[0].command[2] - pattern: "sleep_seconds=2" - - - it: Job pod runs non-root with seccomp RuntimeDefault for restricted-PSA clusters - template: templates/webhook-ready-hook.yaml - asserts: - - documentIndex: 3 - equal: - path: spec.template.spec.securityContext.runAsNonRoot - value: true - - documentIndex: 3 - equal: - path: spec.template.spec.securityContext.seccompProfile.type - value: RuntimeDefault - - - it: wait container declares resource requests and limits for predictable scheduling - template: templates/webhook-ready-hook.yaml - asserts: - - documentIndex: 3 - equal: - path: spec.template.spec.containers[0].resources.requests.cpu - value: 10m - - documentIndex: 3 - equal: - path: spec.template.spec.containers[0].resources.requests.memory - value: 32Mi - - documentIndex: 3 - equal: - path: spec.template.spec.containers[0].resources.limits.cpu - value: 100m - - documentIndex: 3 - equal: - path: spec.template.spec.containers[0].resources.limits.memory - value: 64Mi - - - it: Job container drops all capabilities and runs read-only rootfs - template: templates/webhook-ready-hook.yaml - asserts: - - documentIndex: 3 - equal: - path: spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation - value: false - - documentIndex: 3 - equal: - path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem - value: true - - documentIndex: 3 - equal: - path: spec.template.spec.containers[0].securityContext.capabilities.drop[0] - value: ALL - - - it: imagePullPolicy is IfNotPresent when digest-pinned, Always when tag-only - template: templates/webhook-ready-hook.yaml - set: - webhookReady: - image: - repository: docker.io/clastix/kubectl - tag: v1.32 - digest: sha256:b9ef7d8dbe65bcc81a46c09b8dc7543103055021c4f43287bf59e92a8f4fe05c - asserts: - - documentIndex: 3 - equal: - path: spec.template.spec.containers[0].imagePullPolicy - value: IfNotPresent - - - it: imagePullPolicy is Always when no digest is configured - template: templates/webhook-ready-hook.yaml - set: - webhookReady: - image: - repository: docker.io/clastix/kubectl - tag: v1.32 - digest: "" - asserts: - - documentIndex: 3 - equal: - path: spec.template.spec.containers[0].imagePullPolicy - value: Always - - - it: retry loop captures and surfaces the last kubectl error message on timeout - template: templates/webhook-ready-hook.yaml - asserts: - - documentIndex: 3 - matchRegex: - path: spec.template.spec.containers[0].command[2] - pattern: 'last_err=\$\(kubectl get --raw' - - documentIndex: 3 - matchRegex: - path: spec.template.spec.containers[0].command[2] - pattern: 'last error: \$\{last_err\}' - - - it: retry loop error message stays in sync when maxAttempts is bumped - template: templates/webhook-ready-hook.yaml - set: - webhookReady: - image: - repository: docker.io/clastix/kubectl - tag: v1.32 - maxAttempts: 90 - sleepSeconds: 3 - asserts: - - documentIndex: 3 - matchRegex: - path: spec.template.spec.containers[0].command[2] - pattern: "max_attempts=90" - - documentIndex: 3 - matchRegex: - path: spec.template.spec.containers[0].command[2] - pattern: "sleep_seconds=3" - - documentIndex: 3 - matchRegex: - path: spec.template.spec.containers[0].command[2] - pattern: 'after \$\{max_attempts\} attempts' - - - it: activeDeadlineSeconds scales with retry bounds so an override raise does not silently cut - template: templates/webhook-ready-hook.yaml - set: - webhookReady: - image: - repository: docker.io/clastix/kubectl - tag: v1.32 - maxAttempts: 180 - sleepSeconds: 3 - asserts: - - documentIndex: 3 - equal: - path: spec.activeDeadlineSeconds - value: 600 - - - it: activeDeadlineSeconds defaults include 60s slack over the default retry window - template: templates/webhook-ready-hook.yaml - asserts: - - documentIndex: 3 - equal: - path: spec.activeDeadlineSeconds - value: 180 diff --git a/packages/system/postgres-operator/values.yaml b/packages/system/postgres-operator/values.yaml index c408220e..cae3dc53 100644 --- a/packages/system/postgres-operator/values.yaml +++ b/packages/system/postgres-operator/values.yaml @@ -3,30 +3,3 @@ cloudnative-pg: create: true image: tag: "1.27.3" -# Image used by the post-install webhook-readiness Job (see templates/webhook-ready-hook.yaml). -# Any image with kubectl on PATH works; the Job calls the apiserver service proxy with the -# hook ServiceAccount's token to confirm the mcluster.cnpg.io webhook is reachable end-to-end -# before the HelmRelease reports Ready, closing the "connection refused" bootstrap race. -# -# The tag is digest-pinned so an upstream retag does not change what runs on every install -# and upgrade across the fleet. Refresh by resolving the current manifest-list digest -# (`docker manifest inspect docker.io/clastix/kubectl:v1.32`) and updating `digest` below. -# renovate: datasource=docker depName=docker.io/clastix/kubectl -webhookReady: - image: - repository: docker.io/clastix/kubectl - tag: v1.32 - digest: sha256:b9ef7d8dbe65bcc81a46c09b8dc7543103055021c4f43287bf59e92a8f4fe05c - # Retry loop bounds for the readiness probe. Defaults total ~120s wall clock. - # Both are `default`-coerced in the template so an override that blanks them still - # produces a working Job. - maxAttempts: 60 - sleepSeconds: 2 - # Pod-level retry budget for transient node/registry failures (image pull rate limit, - # OOM, CNI hiccup). The shell loop inside the container only covers reachability retries. - # Pod retries and activeDeadlineSeconds (wall-clock bound on the whole Job across all - # pod retries) are ANDed, so activeDeadlineSeconds is the shorter of the two gates with - # the defaults above: the 60*2+60 = 180s deadline cuts the Job before backoffLimit=2 - # ever matters if pod-level failures eat more than ~60s of the budget. Raise - # maxAttempts/sleepSeconds alongside backoffLimit when tuning for slow image pulls. - backoffLimit: 2 diff --git a/packages/system/postgres-rd/cozyrds/postgres.yaml b/packages/system/postgres-rd/cozyrds/postgres.yaml index 72d013f1..c74c5783 100644 --- a/packages/system/postgres-rd/cozyrds/postgres.yaml +++ b/packages/system/postgres-rd/cozyrds/postgres.yaml @@ -8,7 +8,7 @@ spec: singular: postgres plural: postgreses openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"replicas":{"description":"Number of Postgres replicas.","type":"integer","default":2},"resources":{"description":"Explicit CPU and memory configuration for each PostgreSQL replica. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each replica.","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 to each replica.","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":"Default sizing preset used when `resources` is omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume Claim size available for application data.","default":"10Gi","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},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"version":{"description":"PostgreSQL major version to deploy","type":"string","default":"v18","enum":["v18","v17","v16","v15","v14","v13"]},"postgresql":{"description":"PostgreSQL server configuration.","type":"object","default":{},"properties":{"parameters":{"description":"PostgreSQL server parameters.","type":"object","default":{},"properties":{"max_connections":{"description":"Maximum number of concurrent connections to the database server.","type":"integer","default":100}}}}},"quorum":{"description":"Quorum configuration for synchronous replication.","type":"object","default":{},"required":["maxSyncReplicas","minSyncReplicas"],"properties":{"maxSyncReplicas":{"description":"Maximum number of synchronous replicas allowed (must be less than total replicas).","type":"integer","default":0},"minSyncReplicas":{"description":"Minimum number of synchronous replicas required for commit.","type":"integer","default":0}}},"users":{"description":"Users configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"password":{"description":"Password for the user.","type":"string"},"replication":{"description":"Whether the user has replication privileges.","type":"boolean"}}}},"databases":{"description":"Databases configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"extensions":{"description":"List of enabled PostgreSQL extensions.","type":"array","items":{"type":"string"}},"roles":{"description":"Roles assigned to users.","type":"object","properties":{"admin":{"description":"List of users with admin privileges.","type":"array","items":{"type":"string"}},"readonly":{"description":"List of users with read-only privileges.","type":"array","items":{"type":"string"}}}}}}},"backup":{"description":"Backup configuration.","type":"object","default":{},"required":["enabled"],"properties":{"destinationPath":{"description":"Destination path for backups (e.g. s3://bucket/path/).","type":"string","default":"s3://bucket/path/to/folder/"},"enabled":{"description":"Enable regular backups.","type":"boolean","default":false},"endpointURL":{"description":"S3 endpoint URL for uploads.","type":"string","default":"http://minio-gateway-service:9000"},"retentionPolicy":{"description":"Retention policy (e.g. \"30d\").","type":"string","default":"30d"},"s3AccessKey":{"description":"Access key for S3 authentication.","type":"string","default":""},"s3SecretKey":{"description":"Secret key for S3 authentication.","type":"string","default":""},"schedule":{"description":"Cron schedule for automated backups.","type":"string","default":"0 2 * * * *"}}},"bootstrap":{"description":"Bootstrap configuration.","type":"object","default":{},"required":["enabled","oldName"],"properties":{"enabled":{"description":"Whether to restore from a backup.","type":"boolean","default":false},"oldName":{"description":"Previous cluster name before deletion.","type":"string","default":""},"recoveryTime":{"description":"Timestamp (RFC3339) for point-in-time recovery; empty means latest.","type":"string","default":""},"serverName":{"description":"Barman server name (S3 path prefix) used by the original cluster when writing backups. Set this only when the original cluster had an explicit barmanObjectStore.serverName that differed from its Kubernetes resource name.","type":"string","default":""}}}}} + {"title":"Chart Values","type":"object","properties":{"replicas":{"description":"Number of Postgres replicas.","type":"integer","default":2},"resources":{"description":"Explicit CPU and memory configuration for each PostgreSQL replica. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each replica.","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 to each replica.","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":"Default sizing preset used when `resources` is omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume Claim size available for application data.","default":"10Gi","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},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"version":{"description":"PostgreSQL major version to deploy","type":"string","default":"v18","enum":["v18","v17","v16","v15","v14","v13"]},"postgresql":{"description":"PostgreSQL server configuration.","type":"object","default":{},"properties":{"parameters":{"description":"PostgreSQL server parameters.","type":"object","default":{},"properties":{"max_connections":{"description":"Maximum number of concurrent connections to the database server.","type":"integer","default":100}}}}},"quorum":{"description":"Quorum configuration for synchronous replication.","type":"object","default":{},"required":["maxSyncReplicas","minSyncReplicas"],"properties":{"maxSyncReplicas":{"description":"Maximum number of synchronous replicas allowed (must be less than total replicas).","type":"integer","default":0},"minSyncReplicas":{"description":"Minimum number of synchronous replicas required for commit.","type":"integer","default":0}}},"users":{"description":"Users configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"password":{"description":"Password for the user.","type":"string"},"replication":{"description":"Whether the user has replication privileges.","type":"boolean"}}}},"databases":{"description":"Databases configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"extensions":{"description":"List of enabled PostgreSQL extensions.","type":"array","items":{"type":"string"}},"roles":{"description":"Roles assigned to users.","type":"object","properties":{"admin":{"description":"List of users with admin privileges.","type":"array","items":{"type":"string"}},"readonly":{"description":"List of users with read-only privileges.","type":"array","items":{"type":"string"}}}}}}},"backup":{"description":"Backup configuration.","type":"object","default":{},"required":["enabled"],"properties":{"destinationPath":{"description":"Destination path for backups (e.g. s3://bucket/path/).","type":"string","default":"s3://bucket/path/to/folder/"},"enabled":{"description":"Enable regular backups.","type":"boolean","default":false},"endpointURL":{"description":"S3 endpoint URL for uploads.","type":"string","default":"http://minio-gateway-service:9000"},"retentionPolicy":{"description":"Retention policy (e.g. \"30d\").","type":"string","default":"30d"},"s3AccessKey":{"description":"Access key for S3 authentication.","type":"string","default":""},"s3SecretKey":{"description":"Secret key for S3 authentication.","type":"string","default":""},"schedule":{"description":"Cron schedule for automated backups.","type":"string","default":"0 2 * * * *"}}},"bootstrap":{"description":"Bootstrap configuration.","type":"object","default":{},"required":["enabled","oldName"],"properties":{"enabled":{"description":"Whether to restore from a backup.","type":"boolean","default":false},"oldName":{"description":"Previous cluster name before deletion.","type":"string","default":""},"recoveryTime":{"description":"Timestamp (RFC3339) for point-in-time recovery; empty means latest.","type":"string","default":""}}}}} release: prefix: postgres- labels: @@ -33,7 +33,7 @@ spec: # labelSelector: # helm.toolkit.fluxcd.io/name: "{reqs[0]['metadata','name']}" - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "version"], ["spec", "postgresql"], ["spec", "postgresql", "parameters"], ["spec", "postgresql", "parameters", "max_connections"], ["spec", "quorum"], ["spec", "quorum", "minSyncReplicas"], ["spec", "quorum", "maxSyncReplicas"], ["spec", "users"], ["spec", "databases"], ["spec", "backup"], ["spec", "backup", "enabled"], ["spec", "backup", "retentionPolicy"], ["spec", "backup", "destinationPath"], ["spec", "backup", "endpointURL"], ["spec", "backup", "schedule"], ["spec", "backup", "s3AccessKey"], ["spec", "backup", "s3SecretKey"], ["spec", "bootstrap"], ["spec", "bootstrap", "enabled"], ["spec", "bootstrap", "recoveryTime"], ["spec", "bootstrap", "oldName"], ["spec", "bootstrap", "serverName"]] + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "version"], ["spec", "postgresql"], ["spec", "postgresql", "parameters"], ["spec", "postgresql", "parameters", "max_connections"], ["spec", "quorum"], ["spec", "quorum", "minSyncReplicas"], ["spec", "quorum", "maxSyncReplicas"], ["spec", "users"], ["spec", "databases"], ["spec", "backup"], ["spec", "backup", "enabled"], ["spec", "backup", "retentionPolicy"], ["spec", "backup", "destinationPath"], ["spec", "backup", "endpointURL"], ["spec", "backup", "schedule"], ["spec", "backup", "s3AccessKey"], ["spec", "backup", "s3SecretKey"], ["spec", "bootstrap"], ["spec", "bootstrap", "enabled"], ["spec", "bootstrap", "recoveryTime"], ["spec", "bootstrap", "oldName"]] secrets: exclude: [] include: diff --git a/packages/system/vm-instance-rd/cozyrds/vm-instance.yaml b/packages/system/vm-instance-rd/cozyrds/vm-instance.yaml index e56bd9dd..e93fa86b 100644 --- a/packages/system/vm-instance-rd/cozyrds/vm-instance.yaml +++ b/packages/system/vm-instance-rd/cozyrds/vm-instance.yaml @@ -8,7 +8,7 @@ spec: singular: vminstance plural: vminstances openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"externalAllowICMP":{"description":"Whether to accept ICMP traffic to the VM in PortList mode (preserves ping and PMTU discovery). No effect in WholeIP mode. Default true so ping behaves as users expect even when port filtering is in effect.","type":"boolean","default":true},"runStrategy":{"description":"Requested running state of the VirtualMachineInstance","type":"string","default":"Always","enum":["Always","Halted","Manual","RerunOnFailure","Once"]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"disks":{"description":"List of disks to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"bus":{"description":"Disk bus type (e.g. \"sata\").","type":"string"},"name":{"description":"Disk name.","type":"string"}}}},"networks":{"description":"Networks to attach the VM to.","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Network attachment name.","type":"string"}}}},"subnets":{"description":"Deprecated: use networks instead.","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Network attachment name.","type":"string"}}}},"gpus":{"description":"List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"cpuModel":{"description":"Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map","type":"string","default":""},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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},"sockets":{"description":"Number of CPU sockets (vCPU topology).","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}}},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""}}} + {"title":"Chart Values","type":"object","properties":{"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"runStrategy":{"description":"Requested running state of the VirtualMachineInstance","type":"string","default":"Always","enum":["Always","Halted","Manual","RerunOnFailure","Once"]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"disks":{"description":"List of disks to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"bus":{"description":"Disk bus type (e.g. \"sata\").","type":"string"},"name":{"description":"Disk name.","type":"string"}}}},"networks":{"description":"Networks to attach the VM to.","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Network attachment name.","type":"string"}}}},"subnets":{"description":"Deprecated: use networks instead.","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Network attachment name.","type":"string"}}}},"gpus":{"description":"List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"cpuModel":{"description":"Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map","type":"string","default":""},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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},"sockets":{"description":"Number of CPU sockets (vCPU topology).","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}}},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""}}} release: prefix: vm-instance- labels: @@ -26,7 +26,7 @@ spec: tags: - compute icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl82ODdfMzQ1NCkiLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzY4N18zNDU0KSI+CjxwYXRoIGQ9Ik04OS41MDM5IDExMS43MDdINTQuNDk3QzU0LjE3MjcgMTExLjcwNyA1NC4wMTA4IDExMS4yMjEgNTQuMzM0OSAxMTEuMDU5TDU3LjI1MjIgMTA4Ljk1MkM2MC4zMzE0IDEwNi42ODMgNjEuOTUyMiAxMDIuNjMxIDYwLjk3OTcgOTguNzQxMkg4My4wMjFDODIuMDQ4NSAxMDIuNjMxIDgzLjY2OTMgMTA2LjY4MyA4Ni43NDg1IDEwOC45NTJMODkuNjY1OCAxMTEuMDU5Qzg5Ljk5IDExMS4yMjEgODkuODI3OSAxMTEuNzA3IDg5LjUwMzkgMTExLjcwN1oiIGZpbGw9IiNCMEI2QkIiLz4KPHBhdGggZD0iTTExMy4zMjggOTguNzQxSDMwLjY3MjVDMjcuNTkzMSA5OC43NDEgMjUgOTYuMTQ4IDI1IDkzLjA2ODdWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJWOTMuMDY4N0MxMTkgOTYuMTQ4IDExNi40MDcgOTguNzQxIDExMy4zMjggOTguNzQxWiIgZmlsbD0iI0U4RURFRSIvPgo8cGF0aCBkPSJNMTE5IDg0LjE1NDlIMjVWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJMMTE5IDg0LjE1NDlaIiBmaWxsPSIjMDBCM0ZGIi8+CjxwYXRoIGQ9Ik05MC42Mzc0IDExNi41NjlINTMuMzYxNkM1Mi4wNjUxIDExNi41NjkgNTAuOTMwNyAxMTUuNDM1IDUwLjkzMDcgMTE0LjEzOEM1MC45MzA3IDExMi44NDEgNTIuMDY1MSAxMTEuNzA3IDUzLjM2MTYgMTExLjcwN0g5MC42Mzc0QzkxLjkzMzkgMTExLjcwNyA5My4wNjg0IDExMi44NDEgOTMuMDY4NCAxMTQuMTM4QzkzLjA2ODQgMTE1LjQzNSA5MS45MzM5IDExNi41NjkgOTAuNjM3NCAxMTYuNTY5WiIgZmlsbD0iI0U4RURFRSIvPgo8L2c+CjxwYXRoIGQ9Ik03Mi41Mjc1IDUzLjgzNjdDNzIuNDQzMSA1My44MzUxIDcyLjM2MDUgNTMuODEyMiA3Mi4yODczIDUzLjc3MDFMNTYuNDY5OSA0NC43OTM0QzU2LjM5ODMgNDQuNzUxOSA1Ni4zMzg4IDQ0LjY5MjMgNTYuMjk3MyA0NC42MjA3QzU2LjI1NTkgNDQuNTQ5IDU2LjIzMzggNDQuNDY3OCA1Ni4yMzM0IDQ0LjM4NUM1Ni4yMzM0IDQ0LjIxNjkgNTYuMzI1OCA0NC4wNjE3IDU2LjQ2OTkgNDMuOTc4NUw3Mi4xOTEyIDM1LjA2MDlDNzIuMjYzNyAzNS4wMjEgNzIuMzQ1IDM1IDcyLjQyNzcgMzVDNzIuNTEwNSAzNSA3Mi41OTE4IDM1LjAyMSA3Mi42NjQzIDM1LjA2MDlMODguNDg3MiA0NC4wMzk1Qzg4LjU1OTEgNDQuMDgwMSA4OC42MTg4IDQ0LjEzOTIgODguNjYgNDQuMjEwN0M4OC43MDEzIDQ0LjI4MjIgODguNzIyNyA0NC4zNjM1IDg4LjcyMTkgNDQuNDQ2Qzg4LjcyMjUgNDQuNTI4NSA4OC43MDEgNDQuNjA5NyA4OC42NTk4IDQ0LjY4MTJDODguNjE4NSA0NC43NTI2IDg4LjU1ODkgNDQuODExOCA4OC40ODcyIDQ0Ljg1MjVMNzIuNzcxNCA1My43NjgzQzcyLjY5NzIgNTMuODExNCA3Mi42MTMzIDUzLjgzNDkgNzIuNTI3NSA1My44MzY3IiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBvcGFjaXR5PSIwLjciIGQ9Ik03MC4yNTUzIDc1LjY1MTdDNzAuMTcxIDc1LjY1MzUgNzAuMDg3OCA3NS42MzE3IDcwLjAxNTEgNzUuNTg4OEw1NC4yNDU4IDY2LjY0MTdDNTQuMTcxNSA2Ni42MDI0IDU0LjEwOTUgNjYuNTQzNiA1NC4wNjYxIDY2LjQ3MTZDNTQuMDIyOCA2Ni4zOTk3IDU0IDY2LjMxNzMgNTQgNjYuMjMzM1Y0OC4yNzhDNTQgNDguMTA4IDU0LjA5MjQgNDcuOTU0NiA1NC4yNDM5IDQ3Ljg2OTZDNTQuMzE3MiA0Ny44MjcxIDU0LjQwMDQgNDcuODA0NyA1NC40ODUxIDQ3LjgwNDdDNTQuNTY5NyA0Ny44MDQ3IDU0LjY1MjkgNDcuODI3MSA1NC43MjYyIDQ3Ljg2OTZMNzAuNDkzNyA1Ni44MTMxQzcwLjU2NDIgNTYuODU2NSA3MC42MjI1IDU2LjkxNyA3MC42NjMyIDU2Ljk4OTFDNzAuNzAzOSA1Ny4wNjEyIDcwLjcyNTcgNTcuMTQyNCA3MC43MjY1IDU3LjIyNTFWNzUuMTgwNUM3MC43MjU5IDc1LjI2MjggNzAuNzA0MiA3NS4zNDM2IDcwLjY2MzUgNzUuNDE1MUM3MC42MjI3IDc1LjQ4NjYgNzAuNTY0MiA3NS41NDY0IDcwLjQ5MzcgNzUuNTg4OEM3MC40MjA2IDc1LjYyOTEgNzAuMzM4NyA3NS42NTA3IDcwLjI1NTMgNzUuNjUxNyIgZmlsbD0id2hpdGUiLz4KPHBhdGggb3BhY2l0eT0iMC40IiBkPSJNNzQuNzE5OCA3NS42NTExQzc0LjYzMzMgNzUuNjUxMiA3NC41NDgyIDc1LjYyOTYgNzQuNDcyMiA3NS41ODgzQzc0LjQwMTYgNzUuNTQ2MSA3NC4zNDMyIDc1LjQ4NjIgNzQuMzAyNyA3NS40MTQ3Qzc0LjI2MjMgNzUuMzQzMSA3NC4yNDExIDc1LjI2MjIgNzQuMjQxMiA3NS4xOFY1Ny4zMzczQzc0LjI0MTIgNTcuMTcxIDc0LjMzMzYgNTcuMDE1OCA3NC40NzIyIDU2LjkyOUw5MC4yMzk3IDQ3Ljk4NTVDOTAuMzExOSA0Ny45NDM4IDkwLjM5MzggNDcuOTIxOSA5MC40NzcxIDQ3LjkyMTlDOTAuNTYwNSA0Ny45MjE5IDkwLjY0MjQgNDcuOTQzOCA5MC43MTQ2IDQ3Ljk4NTVDOTAuNzg3NiA0OC4wMjU1IDkwLjg0ODUgNDguMDg0MiA5MC44OTExIDQ4LjE1NTdDOTAuOTMzNyA0OC4yMjcyIDkwLjk1NjMgNDguMzA4OCA5MC45NTY2IDQ4LjM5MlY2Ni4yMzI4QzkwLjk1NyA2Ni4zMTY0IDkwLjkzNDcgNjYuMzk4NSA5MC44OTIxIDY2LjQ3MDRDOTAuODQ5NSA2Ni41NDI0IDkwLjc4ODEgNjYuNjAxNCA5MC43MTQ2IDY2LjY0MTFMNzQuOTUyNiA3NS41ODgzQzc0Ljg4MjUgNzUuNjMwNyA3NC44MDE4IDc1LjY1MjUgNzQuNzE5OCA3NS42NTExIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzY4N18zNDU0IiB4MT0iMTYxIiB5MT0iMTgwIiB4Mj0iMy41OTI4NGUtMDciIHkyPSI0Ljk5OTk4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNTk1NjU2Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNjg3XzM0NTQiPgo8cmVjdCB3aWR0aD0iOTQiIGhlaWdodD0iOTQiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNSAyNSkiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "externalAllowICMP"], ["spec", "runStrategy"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "disks"], ["spec", "networks"], ["spec", "subnets"], ["spec", "gpus"], ["spec", "cpuModel"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]] + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "runStrategy"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "disks"], ["spec", "networks"], ["spec", "subnets"], ["spec", "gpus"], ["spec", "cpuModel"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]] secrets: exclude: [] include: [] diff --git a/pkg/registry/core/tenantnamespace/rest.go b/pkg/registry/core/tenantnamespace/rest.go index 4ee97eb0..f1ed3fab 100644 --- a/pkg/registry/core/tenantnamespace/rest.go +++ b/pkg/registry/core/tenantnamespace/rest.go @@ -17,8 +17,6 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metainternal "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" @@ -26,7 +24,6 @@ import ( "k8s.io/apimachinery/pkg/watch" "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/client" corev1alpha1 "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1" @@ -126,18 +123,8 @@ func (r *REST) Get( return nil, apierrors.NewNotFound(r.gvr.GroupResource(), name) } - // Check if user has access to this namespace - hasAccess, err := r.hasAccessToNamespace(ctx, name) - if err != nil { - return nil, err - } - if !hasAccess { - // Return Forbidden to follow standard K8s RBAC behavior - return nil, apierrors.NewForbidden(r.gvr.GroupResource(), name, fmt.Errorf("access denied")) - } - ns := &corev1.Namespace{} - err = r.c.Get(ctx, types.NamespacedName{Namespace: "", Name: name}, ns, &client.GetOptions{Raw: opts}) + err := r.c.Get(ctx, types.NamespacedName{Namespace: "", Name: name}, ns, &client.GetOptions{Raw: opts}) if err != nil { return nil, err } @@ -156,33 +143,11 @@ func (r *REST) Get( // ----------------------------------------------------------------------------- func (r *REST) Watch(ctx context.Context, opts *metainternal.ListOptions) (watch.Interface, error) { - // Extract user identity once for the lifetime of the watch — it does not - // change between events and rebuilding it per event is wasteful. - u, ok := request.UserFrom(ctx) - if !ok { - return nil, apierrors.NewUnauthorized("user missing in context") - } - username := u.GetName() - groups := make(map[string]struct{}) - for _, group := range u.GetGroups() { - groups[group] = struct{}{} - } - nsList := &corev1.NamespaceList{} - - // Build upstream watch options with field and label selectors - rawOpts := &metav1.ListOptions{ + nsWatch, err := r.w.Watch(ctx, nsList, &client.ListOptions{Raw: &metav1.ListOptions{ Watch: true, ResourceVersion: opts.ResourceVersion, - } - if opts.FieldSelector != nil { - rawOpts.FieldSelector = opts.FieldSelector.String() - } - if opts.LabelSelector != nil { - rawOpts.LabelSelector = opts.LabelSelector.String() - } - - nsWatch, err := r.w.Watch(ctx, nsList, &client.ListOptions{Raw: rawOpts}) + }}) if err != nil { return nil, err } @@ -224,30 +189,6 @@ func (r *REST) Watch(ctx context.Context, opts *metainternal.ListOptions) (watch continue } - // Apply defensive filtering for field and label selectors - if opts.FieldSelector != nil { - if !opts.FieldSelector.Matches(fields.Set{"metadata.name": ns.Name}) { - continue - } - } - if opts.LabelSelector != nil { - if !opts.LabelSelector.Matches(labels.Set(ns.Labels)) { - continue - } - } - - // Check if user has access to this namespace using the cached - // identity — avoids re-extracting user/groups on every event. - hasAccess, err := r.hasAccessToNamespaceForUser(ctx, ns.Name, username, groups) - if err != nil { - klog.ErrorS(err, "Failed to check access for namespace in watch", "namespace", ns.Name) - continue - } - if !hasAccess { - // User doesn't have access, skip this event - continue - } - out := &corev1alpha1.TenantNamespace{ TypeMeta: metav1.TypeMeta{ APIVersion: corev1alpha1.SchemeGroupVersion.String(), @@ -368,26 +309,6 @@ func (r *REST) makeList(src *corev1.NamespaceList, allowed []string) *corev1alph return out } -// matchesSubject checks if a RoleBinding subject matches the user's identity. -// It handles Group, User, and ServiceAccount subjects with proper namespace fallback. -func matchesSubject(subj rbacv1.Subject, bindingNamespace, username string, groups map[string]struct{}) bool { - switch subj.Kind { - case "Group": - _, ok := groups[subj.Name] - return ok - case "User": - return subj.Name == username - case "ServiceAccount": - saNamespace := subj.Namespace - if saNamespace == "" { - saNamespace = bindingNamespace - } - return username == fmt.Sprintf("system:serviceaccount:%s:%s", saNamespace, subj.Name) - default: - return false - } -} - func (r *REST) filterAccessible( ctx context.Context, names []string, @@ -426,9 +347,22 @@ func (r *REST) filterAccessible( subjectLoop: for j := range rbs.Items[i].Subjects { subj := rbs.Items[i].Subjects[j] - if matchesSubject(subj, rbs.Items[i].Namespace, u.GetName(), groups) { - allowedNameSet[rbs.Items[i].Namespace] = struct{}{} - break subjectLoop + switch subj.Kind { + case "Group": + if _, ok = groups[subj.Name]; ok { + allowedNameSet[rbs.Items[i].Namespace] = struct{}{} + break subjectLoop + } + case "User": + if subj.Name == u.GetName() { + allowedNameSet[rbs.Items[i].Namespace] = struct{}{} + break subjectLoop + } + case "ServiceAccount": + if u.GetName() == fmt.Sprintf("system:serviceaccount:%s:%s", subj.Namespace, subj.Name) { + allowedNameSet[rbs.Items[i].Namespace] = struct{}{} + break subjectLoop + } } } } @@ -439,60 +373,6 @@ func (r *REST) filterAccessible( return allowed, nil } -// hasAccessToNamespace checks if the user has access to a single namespace. -// This is optimized for Get/Watch operations where we check one namespace at a time. -// It lists RoleBindings only in the target namespace instead of all cluster RoleBindings. -func (r *REST) hasAccessToNamespace( - ctx context.Context, - namespace string, -) (bool, error) { - u, ok := request.UserFrom(ctx) - if !ok { - return false, fmt.Errorf("user missing in context") - } - groups := make(map[string]struct{}) - for _, group := range u.GetGroups() { - groups[group] = struct{}{} - } - return r.hasAccessToNamespaceForUser(ctx, namespace, u.GetName(), groups) -} - -// hasAccessToNamespaceForUser is the inner check that does not re-extract user -// identity from context. Use this in hot paths (e.g. the Watch loop) where the -// caller has already cached the user name and groups. -func (r *REST) hasAccessToNamespaceForUser( - ctx context.Context, - namespace, username string, - groups map[string]struct{}, -) (bool, error) { - // Check privileged groups - if _, ok := groups["system:masters"]; ok { - return true, nil - } - if _, ok := groups["cozystack-cluster-admin"]; ok { - return true, nil - } - - // List RoleBindings only in the target namespace - rbs := &rbacv1.RoleBindingList{} - err := r.c.List(ctx, rbs, client.InNamespace(namespace)) - if err != nil { - return false, fmt.Errorf("failed to list rolebindings in %s: %w", namespace, err) - } - - // Check if user is in any RoleBinding subjects - for i := range rbs.Items { - for j := range rbs.Items[i].Subjects { - subj := rbs.Items[i].Subjects[j] - if matchesSubject(subj, rbs.Items[i].Namespace, username, groups) { - return true, nil - } - } - } - - return false, nil -} - // ----------------------------------------------------------------------------- // Boiler-plate // ----------------------------------------------------------------------------- diff --git a/pkg/registry/core/tenantnamespace/rest_test.go b/pkg/registry/core/tenantnamespace/rest_test.go index 52d2a075..7f2979bc 100644 --- a/pkg/registry/core/tenantnamespace/rest_test.go +++ b/pkg/registry/core/tenantnamespace/rest_test.go @@ -3,20 +3,10 @@ package tenantnamespace import ( - "context" "testing" corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/apiserver/pkg/endpoints/request" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - corev1alpha1 "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1" ) func TestMakeListSortsAlphabetically(t *testing.T) { @@ -48,466 +38,3 @@ func TestMakeListSortsAlphabetically(t *testing.T) { } } } - -// Security tests for IDOR fix - -func TestHasAccessToNamespace_WithUserAccess(t *testing.T) { - scheme := runtime.NewScheme() - _ = corev1.AddToScheme(scheme) - _ = rbacv1.AddToScheme(scheme) - - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{Name: "tenant-test"}, - } - - rb := &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-binding", - Namespace: "tenant-test", - }, - Subjects: []rbacv1.Subject{ - {Kind: "User", Name: "test-user", APIGroup: "rbac.authorization.k8s.io"}, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: "test-role", - }, - } - - client := fake.NewClientBuilder(). - WithScheme(scheme). - WithObjects(ns, rb). - Build() - - r := &REST{ - c: client, - gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, - } - - u := &user.DefaultInfo{ - Name: "test-user", - Groups: []string{"system:authenticated"}, - } - ctx := request.WithUser(context.Background(), u) - - hasAccess, err := r.hasAccessToNamespace(ctx, "tenant-test") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !hasAccess { - t.Error("expected user to have access, but got false") - } -} - -func TestHasAccessToNamespace_WithoutAccess(t *testing.T) { - scheme := runtime.NewScheme() - _ = corev1.AddToScheme(scheme) - _ = rbacv1.AddToScheme(scheme) - - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{Name: "tenant-test"}, - } - - // RoleBinding for different user - rb := &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-binding", - Namespace: "tenant-test", - }, - Subjects: []rbacv1.Subject{ - {Kind: "User", Name: "other-user", APIGroup: "rbac.authorization.k8s.io"}, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: "test-role", - }, - } - - client := fake.NewClientBuilder(). - WithScheme(scheme). - WithObjects(ns, rb). - Build() - - r := &REST{ - c: client, - gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, - } - - u := &user.DefaultInfo{ - Name: "test-user", - Groups: []string{"system:authenticated"}, - } - ctx := request.WithUser(context.Background(), u) - - hasAccess, err := r.hasAccessToNamespace(ctx, "tenant-test") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if hasAccess { - t.Error("expected user to NOT have access, but got true") - } -} - -func TestHasAccessToNamespace_WithGroupAccess(t *testing.T) { - scheme := runtime.NewScheme() - _ = corev1.AddToScheme(scheme) - _ = rbacv1.AddToScheme(scheme) - - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{Name: "tenant-test"}, - } - - rb := &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-binding", - Namespace: "tenant-test", - }, - Subjects: []rbacv1.Subject{ - {Kind: "Group", Name: "test-group", APIGroup: "rbac.authorization.k8s.io"}, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: "test-role", - }, - } - - client := fake.NewClientBuilder(). - WithScheme(scheme). - WithObjects(ns, rb). - Build() - - r := &REST{ - c: client, - gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, - } - - u := &user.DefaultInfo{ - Name: "test-user", - Groups: []string{"system:authenticated", "test-group"}, - } - ctx := request.WithUser(context.Background(), u) - - hasAccess, err := r.hasAccessToNamespace(ctx, "tenant-test") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !hasAccess { - t.Error("expected user to have access via group, but got false") - } -} - -func TestHasAccessToNamespace_SystemMasters(t *testing.T) { - scheme := runtime.NewScheme() - _ = corev1.AddToScheme(scheme) - _ = rbacv1.AddToScheme(scheme) - - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{Name: "tenant-test"}, - } - - client := fake.NewClientBuilder(). - WithScheme(scheme). - WithObjects(ns). - Build() - - r := &REST{ - c: client, - gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, - } - - u := &user.DefaultInfo{ - Name: "admin", - Groups: []string{"system:masters"}, - } - ctx := request.WithUser(context.Background(), u) - - hasAccess, err := r.hasAccessToNamespace(ctx, "tenant-test") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !hasAccess { - t.Error("expected system:masters to have access, but got false") - } -} - -func TestHasAccessToNamespace_CozyAdminGroup(t *testing.T) { - scheme := runtime.NewScheme() - _ = corev1.AddToScheme(scheme) - _ = rbacv1.AddToScheme(scheme) - - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{Name: "tenant-test"}, - } - - client := fake.NewClientBuilder(). - WithScheme(scheme). - WithObjects(ns). - Build() - - r := &REST{ - c: client, - gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, - } - - u := &user.DefaultInfo{ - Name: "cozy-admin", - Groups: []string{"cozystack-cluster-admin"}, - } - ctx := request.WithUser(context.Background(), u) - - hasAccess, err := r.hasAccessToNamespace(ctx, "tenant-test") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !hasAccess { - t.Error("expected cozystack-cluster-admin to have access, but got false") - } -} - -func TestHasAccessToNamespace_ServiceAccount(t *testing.T) { - scheme := runtime.NewScheme() - _ = corev1.AddToScheme(scheme) - _ = rbacv1.AddToScheme(scheme) - - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{Name: "tenant-test"}, - } - - rb := &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-binding", - Namespace: "tenant-test", - }, - Subjects: []rbacv1.Subject{ - {Kind: "ServiceAccount", Name: "test-sa", Namespace: "tenant-test"}, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: "test-role", - }, - } - - client := fake.NewClientBuilder(). - WithScheme(scheme). - WithObjects(ns, rb). - Build() - - r := &REST{ - c: client, - gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, - } - - u := &user.DefaultInfo{ - Name: "system:serviceaccount:tenant-test:test-sa", - Groups: []string{"system:authenticated"}, - } - ctx := request.WithUser(context.Background(), u) - - hasAccess, err := r.hasAccessToNamespace(ctx, "tenant-test") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !hasAccess { - t.Error("expected service account to have access, but got false") - } -} - -func TestHasAccessToNamespace_ServiceAccountEmptyNamespace(t *testing.T) { - scheme := runtime.NewScheme() - _ = corev1.AddToScheme(scheme) - _ = rbacv1.AddToScheme(scheme) - - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{Name: "tenant-test"}, - } - - // ServiceAccount subject with empty namespace should default to RoleBinding namespace - rb := &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-binding", - Namespace: "tenant-test", - }, - Subjects: []rbacv1.Subject{ - {Kind: "ServiceAccount", Name: "test-sa", Namespace: ""}, // Empty namespace - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: "test-role", - }, - } - - client := fake.NewClientBuilder(). - WithScheme(scheme). - WithObjects(ns, rb). - Build() - - r := &REST{ - c: client, - gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, - } - - u := &user.DefaultInfo{ - Name: "system:serviceaccount:tenant-test:test-sa", - Groups: []string{"system:authenticated"}, - } - ctx := request.WithUser(context.Background(), u) - - hasAccess, err := r.hasAccessToNamespace(ctx, "tenant-test") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !hasAccess { - t.Error("expected service account with empty namespace to have access, but got false") - } -} - -func TestGet_WithAccess(t *testing.T) { - scheme := runtime.NewScheme() - _ = corev1.AddToScheme(scheme) - _ = rbacv1.AddToScheme(scheme) - - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{Name: "tenant-test"}, - } - - rb := &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-binding", - Namespace: "tenant-test", - }, - Subjects: []rbacv1.Subject{ - {Kind: "User", Name: "test-user", APIGroup: "rbac.authorization.k8s.io"}, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: "test-role", - }, - } - - client := fake.NewClientBuilder(). - WithScheme(scheme). - WithObjects(ns, rb). - Build() - - r := &REST{ - c: client, - gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, - } - - u := &user.DefaultInfo{ - Name: "test-user", - Groups: []string{"system:authenticated"}, - } - ctx := request.WithUser(context.Background(), u) - - obj, err := r.Get(ctx, "tenant-test", &metav1.GetOptions{}) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - tn, ok := obj.(*corev1alpha1.TenantNamespace) - if !ok { - t.Fatalf("expected *TenantNamespace, got %T", obj) - } - if tn.Name != "tenant-test" { - t.Errorf("expected name %q, got %q", "tenant-test", tn.Name) - } - if tn.Kind != "TenantNamespace" { - t.Errorf("expected Kind=TenantNamespace, got %q", tn.Kind) - } - if tn.APIVersion != corev1alpha1.SchemeGroupVersion.String() { - t.Errorf("expected APIVersion=%q, got %q", corev1alpha1.SchemeGroupVersion.String(), tn.APIVersion) - } -} - -func TestGet_WithoutAccess(t *testing.T) { - scheme := runtime.NewScheme() - _ = corev1.AddToScheme(scheme) - _ = rbacv1.AddToScheme(scheme) - - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{Name: "tenant-test"}, - } - - // RoleBinding for different user - rb := &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-binding", - Namespace: "tenant-test", - }, - Subjects: []rbacv1.Subject{ - {Kind: "User", Name: "other-user", APIGroup: "rbac.authorization.k8s.io"}, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: "test-role", - }, - } - - client := fake.NewClientBuilder(). - WithScheme(scheme). - WithObjects(ns, rb). - Build() - - r := &REST{ - c: client, - gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, - } - - u := &user.DefaultInfo{ - Name: "test-user", - Groups: []string{"system:authenticated"}, - } - ctx := request.WithUser(context.Background(), u) - - obj, err := r.Get(ctx, "tenant-test", &metav1.GetOptions{}) - if err == nil { - t.Fatal("expected error, got nil") - } - if obj != nil { - t.Errorf("expected nil object, got %v", obj) - } - - // Verify it returns Forbidden to follow standard K8s RBAC behavior - if !apierrors.IsForbidden(err) { - t.Errorf("expected Forbidden error, got %v", err) - } -} - -func TestGet_NonTenantNamespace(t *testing.T) { - scheme := runtime.NewScheme() - _ = corev1.AddToScheme(scheme) - _ = rbacv1.AddToScheme(scheme) - - client := fake.NewClientBuilder(). - WithScheme(scheme). - Build() - - r := &REST{ - c: client, - gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, - } - - u := &user.DefaultInfo{ - Name: "test-user", - Groups: []string{"system:masters"}, - } - ctx := request.WithUser(context.Background(), u) - - obj, err := r.Get(ctx, "default", &metav1.GetOptions{}) - if err == nil { - t.Fatal("expected error for non-tenant namespace, got nil") - } - if obj != nil { - t.Errorf("expected nil object, got %v", obj) - } - if !apierrors.IsNotFound(err) { - t.Errorf("expected NotFound error, got %v", err) - } -}