# Schema — `custom-values.yaml` (Helm override) > Field-by-field annotation of the values file Argo CD's `valueFiles` references. > Authoritative for `helm-overrides///custom-values.yaml`. > > Cross-reference: [coding-guidelines/helm-values.md](../../global/coding-guidelines/helm-values.md), [../../architecture.md](../../architecture.md). --- ## What this file is — and isn't A `custom-values.yaml` is **not** a Helm chart. It is the *override* layer Argo CD merges over the chart's own `values.yaml` (or, for thin-wrapper charts, the upstream subchart's defaults). - **Schema is owned by the chart, not by us.** Every key here must be a key the chart understands. Adding a key the chart doesn't read does nothing. - **No multi-doc YAML.** One document per file. - **No `apiVersion` / `kind`.** This file is values, not a Kubernetes manifest. --- ## Top-level shape (typical) ```yaml # image registry / repository / tag image: registry: asia-southeast1-docker.pkg.dev repository: meesho-devops-admin-0622/admin/sre/ tag: pullPolicy: IfNotPresent # release-name pinning (rarely changed once set) fullnameOverride: --prd # e.g. kube-state-metrics-dbc-dsci-prd # scaling replicaCount: 3 # static, OR autoscaling: enabled: true minReplicas: 3 maxReplicas: 10 targetCPUUtilizationPercentage: 70 # scheduling — bespoke per cluster nodeSelector: dedicated: # standard GKE # cloud.google.com/compute-class: # GKE Autopilot tolerations: - key: dedicated value: effect: NoSchedule # resources resources: requests: cpu: 250m memory: 512Mi limits: cpu: 1 memory: 2Gi # persistence (stateful charts only) persistence: enabled: true storageClass: sc-pd-ssd # MUST exist in manifests/storageclass/ size: 100Gi accessModes: [ReadWriteOnce] # secrets — by reference, never inline existingSecret: # probes livenessProbe: initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: initialDelaySeconds: 5 periodSeconds: 5 # ingress / Contour HTTPProxy (if applicable) ingress: enabled: false # most internal services # chart-specific top-level keys # (varies — read the chart's values.yaml for the full schema) ``` --- ## §image | Field | Required | Convention | Notes | |-------|----------|------------|-------| | `image.registry` | yes (production) | `asia-southeast1-docker.pkg.dev` | Meesho's Artifact Registry mirror. ([SANCTITY_RULES R11](../../global/SANCTITY_RULES.md)) | | `image.repository` | yes | `meesho-devops-admin-0622/admin/sre/` | Mirror path. | | `image.tag` | yes (production) | semver, datestamp, or git SHA | Never `latest`. Never unpinned. | | `image.pullPolicy` | no | `IfNotPresent` | `Always` only for development; pulls slow rollout. | | `image.pullSecrets` | no | omit | Mirror is public to fleet; pull secrets are a lock-in trap. | Note: chart authors disagree on the shape — some use `image: {repository, tag}` (no `registry`), some use a single `image: `, some split into `image.repository: /`. **Read the chart's own `values.yaml`** before structuring this block. --- ## §release identity | Field | Required | Convention | Notes | |-------|----------|------------|-------| | `fullnameOverride` | varies | Set once at release creation; stable forever | Service DNS, PVC binding, ConfigMap refs depend on it. ([SANCTITY_RULES R9](../../global/SANCTITY_RULES.md)) | | `nameOverride` | rare | Omit unless explicitly needed | | | `commonLabels` / `commonAnnotations` | rare | Omit unless the chart documents the pattern | | For dataplane (`db-*`) clusters: `fullnameOverride: -dbc--prd` is the convention (e.g. `kube-state-metrics-dbc-dsci-prd`). For BU clusters, omit unless the chart's default name collides. --- ## §scaling Charts diverge sharply here. Common shapes: ```yaml # Pattern A — static replicas replicaCount: 3 # Pattern B — chart-level autoscaling block autoscaling: enabled: true minReplicas: 3 maxReplicas: 10 targetCPUUtilizationPercentage: 70 # Pattern C — separate HPA resource (KEDA / VictoriaMetrics) hpa: enabled: true min: 3 max: 20 metrics: - type: Resource resource: {name: cpu, target: {type: Utilization, averageUtilization: 70}} ``` | Rule | Why | |------|-----| | **Don't set `replicaCount`** when `autoscaling.enabled: true`. | HPA and the chart's static deployment fight; replica count flaps. | | **Set both `minReplicas` and `maxReplicas`** when autoscaling. | Without `min`, the HPA can scale to zero on a quiet hour. | | **`minReplicas` ≥ 2** for any production-traffic-path workload. | Single-replica releases die on node drain. | --- ## §scheduling The single biggest source of silent mis-deploys. Per [SANCTITY_RULES R5](../../global/SANCTITY_RULES.md): | Cluster | Key style | |---------|-----------| | GKE Autopilot (`k8s-central-prd-ase1`, `k8s-dsgpu-prd-ase1`, `k8s-shared-int-ase1`) | `cloud.google.com/compute-class:` | | Standard GKE | `dedicated:` | ```yaml # Standard GKE — Contour internal-0 on most clusters nodeSelector: dedicated: contour-internal-0 tolerations: - key: dedicated value: contour-internal-0 effect: NoSchedule # often a second toleration to allow scheduling onto shared nodes - key: dedicated value: contour-shared effect: NoSchedule # GKE Autopilot — same Contour internal-0 on k8s-central-prd-ase1 nodeSelector: cloud.google.com/compute-class: contour-internal-0-cc tolerations: - key: cloud.google.com/compute-class value: contour-internal-0-cc effect: NoSchedule - key: cloud.google.com/compute-class value: contour-shared-cc effect: NoSchedule ``` The full per-cluster Contour matrix lives in [`contour-nodeselector-tolerations-summary.md`](../../../contour-nodeselector-tolerations-summary.md). For non-Contour apps, copy from a sibling app on the **same** cluster. | Field | Notes | |-------|-------| | `nodeSelector` | Hard requirement — no scheduling onto non-matching nodes. Use exactly one key (the cluster's pool key). | | `tolerations` | Allow scheduling onto tainted nodes. May list multiple to allow shared nodes alongside dedicated. | | `affinity.nodeAffinity` | Use sparingly; `nodeSelector` is sufficient for most cases here. | | `topologySpreadConstraints` | Use for multi-zone resilience on chatty workloads (Contour, ingress-nginx). | --- ## §resources ```yaml resources: requests: cpu: 250m memory: 512Mi limits: cpu: 1 memory: 2Gi ``` | Rule | Why | |------|-----| | **Always set `requests`** in production. | Without requests, pods are best-effort; first to evict on memory pressure. | | **Set `limits` unless the chart explicitly recommends omitting them.** | Some sidecars (cert-manager, external-dns) deliberately skip `limits`. | | **Don't copy resources from another cluster.** | Workload sizing is per-traffic-tier. Supply prd is not demand prd. | | **CPU limits cause throttling**, not OOMKill. Memory limits cause OOMKill. Tune accordingly. | --- ## §persistence ```yaml persistence: enabled: true storageClass: sc-pd-ssd size: 100Gi accessModes: [ReadWriteOnce] ``` Allowed `storageClass` values (must match a file under `manifests/storageclass/`): | Class | Backend | Use case | |-------|---------|----------| | `pd-standard-retain-dr` | GCP Persistent Disk standard, retention-on-delete | DR-critical state | | `sc-pd-ssd` | GCP PD SSD | Default for write-heavy workloads (etcd, ClickHouse) | | `sc-pd-standard` | GCP PD standard | Default for read-heavy / archival | | `sc-filestore-standard` | GCP Filestore | Shared volumes (Jenkins build cache, JFrog filestore) | | Rule | Why | |------|-----| | **Never reference a `storageClass`** that doesn't exist in `manifests/storageclass/`. | PVC stays Pending forever. | | **`size` must be set explicitly.** | Chart defaults are often wrong (8Gi for everything). | | **`accessModes`** for `ReadWriteMany` requires Filestore. PD-backed classes are RWO. | --- ## §secrets ```yaml # Chart-specific — read the chart's values.yaml existingSecret: # OR auth: existingSecret: # OR envFrom: - secretRef: {name: } ``` | Rule | Why | |------|-----| | **Never inline `password:`, `apiKey:`, etc.** | Pre-commit TruffleHog will catch many; it won't catch all. ([SANCTITY_RULES R4](../../global/SANCTITY_RULES.md)) | | **The `Secret` resource is materialised** by the cluster's `external-secrets` app. If the secret name doesn't appear in `helm-overrides//external-secrets/`, it doesn't exist. | | **Reference, don't author.** Adding the secret material to a Helm-rendered `Secret` template defeats the External Secrets pattern. | --- ## §probes ```yaml livenessProbe: httpGet: {path: /healthz, port: http} initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3 readinessProbe: httpGet: {path: /ready, port: http} initialDelaySeconds: 5 periodSeconds: 5 # For slow-warm workloads (Jenkins, JFrog, ClickHouse) startupProbe: httpGet: {path: /healthz, port: http} initialDelaySeconds: 60 periodSeconds: 10 failureThreshold: 60 # 10 minutes total runway ``` | Rule | Why | |------|-----| | **Always set `liveness` + `readiness`** for any long-running container. | | **Use `startupProbe` for slow-warm workloads** instead of inflating `livenessProbe.initialDelaySeconds` to 600 s. | | **Don't set the same probe to both `liveness` and `readiness`** — they have different purposes (kill vs gate). | --- ## §ingress (Contour `HTTPProxy` / `Ingress`) For external-facing apps, ingress is via Contour `HTTPProxy`. Specifics depend on the cluster's Contour topology (multiple Contour instances per cluster — see [contour-nodeselector-tolerations-summary.md](../../../contour-nodeselector-tolerations-summary.md)). - The `external-dns` annotation is the DNS-binding step — match the cluster's DNS pattern. - TLS terminates at Contour with `cert-manager`-issued certs; reference the issuer by name. - Internal services use `contour-internal-0` / `contour-internal-1`; external services use `contour-external` / `contour-external-1`. The class is set via the `Service` annotation, not the values file directly. --- ## End-to-end example — a typical observability-side override `helm-overrides/k8s-supply-prd-ase1/kube-state-metrics/custom-values.yaml`: ```yaml image: registry: asia-southeast1-docker.pkg.dev repository: meesho-devops-admin-0622/admin/sre/kube-state-metrics tag: v2.10.1 replicaCount: 2 resources: requests: cpu: 100m memory: 256Mi limits: cpu: 500m memory: 1Gi nodeSelector: dedicated: monitoring tolerations: - key: dedicated value: monitoring effect: NoSchedule prometheus: monitor: enabled: true honorLabels: true ``` --- ## End-to-end example — a dataplane override `helm-overrides/db-2516183257845181-c-1204-195038-428/victoria-metrics-agent/custom-values.yaml`: ```yaml fullnameOverride: vmagent-dbc-supply-prd image: registry: asia-southeast1-docker.pkg.dev repository: meesho-devops-admin-0622/admin/sre/victoria-metrics-agent tag: v1.96.0 replicaCount: 1 resources: requests: cpu: 100m memory: 256Mi ``` (Dataplane clusters intentionally have minimal overrides.) --- ## Validation ```bash # Render the chart locally with this values file helm template helm-templates/ \ -f helm-overrides///custom-values.yaml | head -80 # Dry-run diff against the live cluster (requires kubectl context + helm-diff plugin) helm diff upgrade helm-templates/ \ -f helm-overrides///custom-values.yaml # Lint yamllint helm-overrides///custom-values.yaml helm lint helm-templates/ # Sanity: storageClass referenced exists yq e '.persistence.storageClass' helm-overrides///custom-values.yaml \ | xargs -I{} ls manifests/storageclass/{}.yaml 2>/dev/null \ || echo "WARN: storageClass not in manifests/storageclass/" ```