added repo

This commit is contained in:
Your Name
2026-08-26 03:39:42 +05:30
parent 45c25a95af
commit b8575bb8b9
6889 changed files with 1217125 additions and 0 deletions
@@ -0,0 +1,231 @@
> Per AI Blitz Plan §platform.procedures. Layer: 1. Repo: devops-infra-helm-charts.
# Procedure — Add or modify a Contour HTTPProxy route
> **Layer:** Layer 1 — values diff + PR.
> **Blast radius:** one Contour release × one cluster (a misrouted host can break ingress for the whole BU).
> **Approval:** consuming-app owner + cluster owner. Platform team if the change touches `contour-external*`.
This procedure covers HTTPProxy route changes that flow through one of the cluster's Contour releases. Most clusters run **multiple Contour instances** — read the [contour-nodeselector-tolerations-summary.md](../../../contour-nodeselector-tolerations-summary.md) (repo root) authoritative scheduling matrix before editing **any** Contour values.
---
## Multi-Contour topology
| Release name | Plane | Typical purpose |
|--------------|-------|-----------------|
| `contour-external` | North-south | Public / external traffic (terminates at GCP external LB) |
| `contour-external-1` | North-south | Second external Contour (blue-green or capacity split) |
| `contour-internal-0` | East-west | Internal traffic, plane 0 |
| `contour-internal-1` | East-west | Internal traffic, plane 1 |
| `contour-internal-intra-0` | Intra-cluster | Cluster-local east-west, plane 0 |
| `contour-internal-intra-1` | Intra-cluster | Cluster-local east-west, plane 1 |
Each is a separate Helm release, on a separate node pool, with its own `nodeSelector` / `tolerations` / `computeClass`. Picking the wrong release is the most common authoring mistake.
---
## When to use
- Adding a new HTTPProxy / route for a service.
- Changing TLS, retry, timeout, or rate-limit policy on an existing route.
- Re-pointing an HTTPProxy at a different upstream Service.
- Adding a new host to an existing route's `virtualhost.fqdn`.
Do **not** use this procedure for:
- Changing Contour itself (sizing, scheduling, image) — that's [modify-observability-config.md](modify-observability-config.md)-style infra editing on the Contour release.
- Bumping Contour's chart version — use [update-chart-version.md](update-chart-version.md) and consult the versioned sibling (`contour-v1.33.3`).
- Curling production hostnames to test — [SANCTITY_RULES R3](../../global/SANCTITY_RULES.md) forbids it. Test from inside the cluster with a `curl` Pod.
---
## Inputs
| Input | Example |
|-------|---------|
| Target cluster | `k8s-supply-prd-ase1` |
| Target Contour release | `contour-internal-0` |
| HTTPProxy host | `api-foo.internal.meeshogcp.in` |
| Upstream Service | `foo-svc.foo-ns:8080` |
| TLS source | `cert-manager` / `external-secret` / `none` |
| Path prefixes | `/v1`, `/health` |
| Approval ticket | CMR-… (if BU policy) |
---
## Pre-conditions
- [ ] You know which Contour release is the right one for this host (north-south = `external*`; east-west between BUs = `internal-{0,1}`; cluster-local = `internal-intra-{0,1}`). When in doubt, sample existing HTTPProxies in the same namespace.
- [ ] The upstream Service exists or will exist by the time of Sync.
- [ ] The DNS name follows the cluster's `external-dns` pattern.
- [ ] The TLS source (Secret, Issuer, etc.) exists on the cluster.
---
## Steps
### 1. Identify the right Contour release
```bash
ls helm-overrides/<cluster>/ | grep '^contour'
```
Pick the release whose plane matches the new route's traffic class. Cross-reference [contour-nodeselector-tolerations-summary.md](../../../contour-nodeselector-tolerations-summary.md). If unsure, look at existing HTTPProxies on the cluster:
```bash
# Read existing routes already shipped via this repo
yq e '.. | select(has("httpproxies"))' \
helm-overrides/<cluster>/contour-internal-0/custom-values.yaml
```
### 2. Decide where the HTTPProxy lives
Two patterns exist:
| Pattern | When | Where to author |
|---------|------|-----------------|
| **Inline in Contour values** | Routes shared by the cluster's infra layer (e.g. Grafana, Argo CD). | `helm-overrides/<cluster>/<contour-release>/custom-values.yaml` under `httpproxies:` (if the chart supports it) or as a sidecar manifest in the same dir. |
| **In the consuming app's repo** | Routes for a specific service. | The service's own deployment artifacts. **Out of scope for this repo.** |
If the route is service-owned, **redirect** to the consuming team's repo and stop. This procedure only covers infra-layer HTTPProxies.
### 3. Author the HTTPProxy
Skeleton (sidecar manifest pattern):
```yaml
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: <name>
namespace: <ns>
spec:
virtualhost:
fqdn: <fqdn>
tls:
secretName: <tls-secret> # cert-manager-managed Secret
routes:
- conditions:
- prefix: /
services:
- name: <upstream-service>
port: <port>
timeoutPolicy:
response: 30s
retryPolicy:
count: 2
retryOn: 5xx
```
Drop into `helm-overrides/<cluster>/<contour-release>/httpproxies/<name>.yaml` (sidecar manifest) — see [../schemas/raw-manifest-sidecar-schema.md](../schemas/raw-manifest-sidecar-schema.md).
### 4. Lint the HTTPProxy
```bash
# Schema check (kubectl --dry-run against the cluster's CRD)
kubectl --context=<ctx> --dry-run=server -f helm-overrides/<cluster>/<contour-release>/httpproxies/<name>.yaml apply
# Or local: validate against the projectcontour CRD schema
kubeconform -schema-location default -schema-location \
'https://raw.githubusercontent.com/projectcontour/contour/main/examples/contour/01-crds.yaml' \
helm-overrides/<cluster>/<contour-release>/httpproxies/<name>.yaml
```
### 5. Render the chart
```bash
helm template <contour-release> helm-templates/<contour-chart> \
-f helm-overrides/<cluster>/<contour-release>/custom-values.yaml > /tmp/contour.yaml
```
The render must succeed; the HTTPProxy sidecar is applied alongside, not through Helm — but rendering catches values-side errors that would block sync of the same Argo Application.
### 6. Test from inside the cluster (post-merge, not pre-merge)
**Do not** curl `<fqdn>.meeshogcp.in` from your laptop / a build agent. Run a curl Pod on-cluster:
```bash
kubectl --context=<ctx> run -it --rm curl-test \
--image=curlimages/curl --restart=Never -- \
curl -v -H "Host: <fqdn>" http://<contour-svc>.projectcontour.svc.cluster.local
```
### 7. Open the PR
```bash
git checkout -b contour/<cluster>-<route-name>
git add helm-overrides/<cluster>/<contour-release>/
git commit -m "contour(<cluster>/<contour-release>): add route <name>"
git push origin contour/<cluster>-<route-name>
gh pr create --base main
```
### PR description template
```markdown
## Summary
Adds (or modifies) HTTPProxy `<name>` on `<cluster>` via `<contour-release>` for FQDN `<fqdn>`.
## Why
<1-2 sentences>
## Topology
- Cluster: `<cluster>`
- Contour release: `<contour-release>` (plane: external / internal / intra)
- FQDN: `<fqdn>`
- Upstream Service: `<svc>:<port>` in namespace `<ns>`
- TLS: cert-manager Secret `<tls-secret>`
## Validation
- [ ] HTTPProxy CRD schema validation passed (kubeconform / kubectl --dry-run)
- [ ] `helm template` rendered cleanly
- [ ] On-cluster curl from a curl Pod returns expected status
- [ ] DNS / external-dns plumbed (existing wildcard or new external-dns Service)
## Approvers
- App owner: <handle>
- Cluster owner: <handle>
- Platform (if `contour-external*`): <handle>
```
### 8. After merge — Sync
Open the cluster's Argo CD UI, find the Contour release's Application, **Sync**. Verify:
```bash
kubectl --context=<ctx> -n projectcontour get httpproxy <name>
kubectl --context=<ctx> -n projectcontour describe httpproxy <name> | grep -A5 'Status:'
```
A `Valid: true` status means Contour accepted the route. `Valid: false` with a reason → fix the values and re-PR.
If the HTTPProxy is `Valid` but traffic still 5xx → [../runbooks/ingress-down.md](../runbooks/ingress-down.md) §4.
---
## Anti-patterns
1. **Wrong Contour release.** A route mounted on `contour-internal-intra-0` is unreachable from outside the cluster. Cross-check the matrix.
2. **Curling the production FQDN** from a developer machine to "test." Forbidden — see [SANCTITY_RULES R3](../../global/SANCTITY_RULES.md).
3. **Hand-edited values for a single host across all Contour releases** — pick one release, justify it.
4. **`tls.passthrough` for plain HTTP services** — silent: TLS terminates upstream, doesn't.
5. **Wildcard hosts that overlap an existing HTTPProxy** — Contour status will mark one of them invalid; check before merging.
6. **Editing `helm-templates/contour*/`** to "tweak the chart." Layer 1 forbids casual chart edits — the chart is vanilla upstream.
---
## Rollback
- Revert the values PR.
- Sync the Contour Application — Argo will prune the HTTPProxy or restore the prior values.
---
## Related
- Reference (authoritative scheduling): [../../../contour-nodeselector-tolerations-summary.md](../../../contour-nodeselector-tolerations-summary.md).
- Runbook: [../runbooks/ingress-down.md](../runbooks/ingress-down.md).
- Schema: [../schemas/raw-manifest-sidecar-schema.md](../schemas/raw-manifest-sidecar-schema.md).
- Schema: [../schemas/custom-values-schema.md](../schemas/custom-values-schema.md).
- ADR: [../../../wiki/analyses/ADR-A3-per-cluster-scheduling.md](../../../wiki/analyses/ADR-A3-per-cluster-scheduling.md).
@@ -0,0 +1,206 @@
# Procedure — Blue-green chart migration (versioned siblings)
> **Layer:** Layer 1 — HIGH RISK.
> **Blast radius:** the chart upgrade ships in a new sibling directory; no cluster cuts over until its `Application` is repointed. Each cluster's cutover is its own decision.
> **Approval:** platform team — multi-reviewer.
This procedure handles the case where you can't safely bump a chart's pinned `dependencies[].version` in place — typically because of breaking template changes, immutable selector mismatches, or major-version semantics. The pattern is to keep both versions live as **sibling chart directories** until every consuming cluster has migrated.
The repo already shows this pattern at work:
| Original | Migration target |
|----------|------------------|
| `argo-cd` | `argo-cd-green` |
| `contour` | `contour-v1.33.3` |
| `keda` | `keda-2.17.1` |
| `opentelemetry-collector` | `opentelemetry-collector-latest` |
| `victoria-metrics-cluster` | `victoria-metrics-cluster-latest` |
| `victoria-metrics-agent` | `victoria-metrics-agent-latest` |
| `sonarqube` | `sonarqube-old` *(reverse — `sonarqube` is the new one; `-old` retained for rollback)* |
See [ADR-A2-blue-green-sibling-pattern.md](../../../wiki/analyses/ADR-A2-blue-green-sibling-pattern.md) for the rationale.
---
## When to use this procedure
- A major-version bump with breaking template changes.
- A bump with an immutable-field change (Deployment `spec.selector`, StatefulSet `volumeClaimTemplates`).
- A migration that needs cluster-by-cluster cutover with rollback windows.
This is **not** the right procedure for:
- A patch / minor bump that's a clean drop-in → use [update-chart-version.md](update-chart-version.md).
- Forking templates → use [fork-upstream-chart.md](fork-upstream-chart.md).
---
## Pre-conditions
- [ ] Platform team agrees a blue-green migration is required (not just a bump).
- [ ] You've identified the variant naming convention (`-green`, `-vX.Y.Z`, `-latest`, etc.).
- [ ] You've identified every consuming cluster: `grep -rl '<chart>' helm-overrides`.
- [ ] CMR open.
---
## Steps
### 1. Create the sibling chart directory
```bash
cp -R helm-templates/<chart> helm-templates/<chart>-<variant>
cd helm-templates/<chart>-<variant>
```
Update `Chart.yaml`:
```diff
apiVersion: v2
-name: <chart>
+name: <chart>-<variant>
dependencies:
- name: <sub>
- version: <old-version>
+ version: <new-version>
repository: <upstream-repo>
```
```bash
helm dependency update
```
### 2. Render against a representative cluster's overrides — old variant
The point of blue-green is *no surprises*. Render the **old** chart with each consuming cluster's override and capture the output.
```bash
for f in $(find helm-overrides -maxdepth 2 -name custom-values.yaml -path "*/<chart>/*"); do
cluster=$(echo "$f" | awk -F/ '{print $2}')
helm template <chart> helm-templates/<chart> -f "$f" > /tmp/old-${cluster}.yaml
done
```
### 3. Render against the same overrides — new sibling variant
```bash
for f in $(find helm-overrides -maxdepth 2 -name custom-values.yaml -path "*/<chart>/*"); do
cluster=$(echo "$f" | awk -F/ '{print $2}')
helm template <chart>-<variant> helm-templates/<chart>-<variant> -f "$f" > /tmp/new-${cluster}.yaml
done
```
### 4. Diff old → new per cluster
```bash
for cluster in $(ls /tmp/old-*.yaml | sed 's:/tmp/old-::; s:.yaml::'); do
echo "=== $cluster ==="
diff /tmp/old-${cluster}.yaml /tmp/new-${cluster}.yaml | head -30
done
```
For each cluster, decide:
- Is the diff what you expected?
- Will any value need updating to make the new chart render correctly? (If yes → that's a separate per-cluster PR after the sibling lands.)
- Is the cutover safe to do without the workload owner present? (If no → schedule.)
### 5. Open PR-1: introduce the sibling chart
```bash
git checkout -b migrate/<chart>-to-<variant>-introduce
git add helm-templates/<chart>-<variant>/
git commit
git push origin migrate/<chart>-to-<variant>-introduce
gh pr create --base main --title "migrate: introduce <chart>-<variant> sibling"
```
After merge, the new chart exists in the repo but **no cluster uses it yet** — the existing Argo `Application`s still point at `helm-templates/<chart>`.
### 6. Per-cluster cutover (one PR pair per cluster)
For each consuming cluster:
a. **Update the cluster's `custom-values.yaml`** if the new chart needs different values. Open as a values-side PR (this repo).
b. **Update the sister-repo `Application`** to repoint:
```diff
spec:
source:
repoURL: https://github.com/Meesho/devops-infra-helm-charts.git
targetRevision: main
- path: helm-templates/<chart>
+ path: helm-templates/<chart>-<variant>
```
c. **After both merge**, click Sync in the cluster's Argo CD UI.
d. **Soak** — leave it for the agreed soak period (often 2472 h) before moving to the next cluster.
### 7. Open PR-N: retire the old sibling
After every cluster has cut over and soaked:
```bash
git checkout -b migrate/<chart>-retire-old
git rm -r helm-templates/<chart>
# OR rename: git mv helm-templates/<chart> helm-templates/<chart>-old
git commit
git push origin migrate/<chart>-retire-old
gh pr create --base main --title "migrate: retire old <chart> sibling"
```
PR description:
- Confirmation every cluster has cut over (`grep -rl 'helm-templates/<chart>$' /path/to/devops-infra-argo-config` returns nothing).
- Confirmation soak period elapsed.
- Decision: delete vs rename to `-old` (kept for rollback).
---
## Why two PRs at the start, then per-cluster pairs, then a final retirement
| PR | Effect |
|----|--------|
| **PR-1: introduce sibling** | Adds the new chart. No cluster cuts over. Worst case: render errors caught before any cluster sees them. |
| **PR-2..N-1: per-cluster cutover (paired with sister repo)** | One cluster moves. Worst case: that cluster's release breaks; revert the sister-repo PR and Sync to the old chart. |
| **PR-N: retire old sibling** | Removes the old chart. Worst case: a cluster you missed becomes broken — but you grep'd, so this should be impossible. |
Bundling any of these violates the "rollback one cluster at a time" property that's the whole point of the blue-green pattern.
---
## Anti-patterns
1. **Cutting over multiple clusters in one PR.** Bundle = no per-cluster rollback.
2. **Deleting the old sibling before every cluster has cut over.** Cluster N+1 has its `Application` pointing at a path that no longer exists; sync fails immediately.
3. **Cutting over without rendering first.** Surprises after merge.
4. **Skipping the soak period.** "It looked fine in the first 5 minutes" is not soak.
5. **Renaming the *new* sibling to drop the suffix** (e.g. `argo-cd-green``argo-cd`) before the old chart is retired. The path collision will break Argo CD's caching.
---
## Rollback (per cluster)
If a cluster's cutover fails:
1. Revert the sister-repo PR for that cluster.
2. Click Sync in the cluster's Argo CD — the `Application` re-renders against `<chart>` (the old sibling). The rollback is one cluster only.
3. Investigate; iterate.
If the new sibling has a fundamental problem affecting every cluster:
1. Open PR-X: revert PR-1 (delete the sibling).
2. Any cluster that was already cutover gets reverted via its own per-cluster sister-repo revert.
3. Schedule a postmortem before re-attempting.
---
## Related
- Procedure: [update-chart-version.md](update-chart-version.md).
- Procedure: [fork-upstream-chart.md](fork-upstream-chart.md).
- ADR: [ADR-A2-blue-green-sibling-pattern.md](../../../wiki/analyses/ADR-A2-blue-green-sibling-pattern.md).
- [SANCTITY_RULES R8](../../global/SANCTITY_RULES.md).
+119
View File
@@ -0,0 +1,119 @@
# Procedure — Deboard a retired app from a cluster
> **Layer:** Layer 1 — HIGH RISK.
> **Blast radius:** the Helm release is removed from the cluster. If still in use, that's an outage.
> **Approval:** app owner + cluster owner. CMR mandatory.
This procedure removes an app's `helm-overrides/<cluster>/<app>/` directory. It does **not** delete the workload directly — but the matching sister-repo `Application` PR (which must be paired) tells Argo CD to stop tracking the release.
---
## Pre-conditions (block deboarding if any are FALSE)
- [ ] App is genuinely retired on this cluster — confirmed by app owner.
- [ ] No traffic / scrape job / dependency is still hitting it.
- [ ] All upstream consumers (alerts, dashboards, log collectors) have been migrated or notified.
- [ ] You have inventoried every cluster that runs the app and decided whether this is a single-cluster or fleet-wide deboard.
- [ ] CMR approved.
```bash
# Where does this app exist today?
find helm-overrides -maxdepth 2 -type d -name '<app>'
```
---
## Steps
### 1. Decide the scope
| Scope | Then |
|-------|------|
| Single cluster | Remove `helm-overrides/<cluster>/<app>/` only. Leave other clusters running. |
| Fleet-wide | Multiple PRs — one cluster per PR. Don't bundle. |
| App is being replaced (e.g. `victoria-metrics-cluster``victoria-metrics-cluster-latest`) | This is a **migration**, not a deboard — use [blue-green-chart-migration.md](blue-green-chart-migration.md). |
### 2. Open the sister-repo `Application` removal PR FIRST
In `github.com/Meesho/devops-infra-argo-config`:
- Remove (or scope out of) the `Application` / `ApplicationSet` entry that targets this cluster × app.
- Merge.
- The cluster's Argo CD will mark the `Application` for removal on next reconcile.
This step **must precede** the values-side removal. If you delete the values-side first, the `Application` will fail to render and the workload may go into an `Errored` state on the cluster.
### 3. Manually clean up the workload (if `automated.prune` was not set)
For most infra apps, the `Application` does not have `automated.prune: true` — so removing the `Application` does not delete the workload. Manually:
```bash
kubectl --context=<cluster> delete <kind>/<name> -n <ns>
# OR, if the whole namespace is dedicated to this release:
kubectl --context=<cluster> delete namespace <ns>
```
Coordinate with the app owner — wholesale namespace deletion is irreversible.
### 4. Open the values-side removal PR
```bash
git checkout -b deboard/<app>-from-<cluster>
git rm -r helm-overrides/<cluster>/<app>/
git commit
git push origin deboard/<app>-from-<cluster>
gh pr create --base main --title "deboard: <app> from <cluster>"
```
PR description:
- Procedure followed: this file.
- Confirmation pre-conditions are TRUE.
- Sister-repo PR (already merged).
- CMR ticket reference.
- Confirmation the workload was manually cleaned up (or scheduled).
### 5. (Optional) If this was the last cluster running the app
If `find helm-overrides -maxdepth 2 -type d -name '<app>'` now returns nothing, consider:
- **Should `helm-templates/<chart>/` also be removed?** Probably not — keeping the chart cached lets a future re-onboarding be cheap. But if it's a stale chart with security advisories you don't want to maintain, schedule a separate retirement PR for it (with platform-team review).
---
## "What if the app might come back?"
If retirement is provisional:
- **Do not** scale the workload to zero replicas via values "to deactivate it." Either it's running or it's not. Half-states are operational debt.
- **Do** keep the values directory in place but document a 30-day decision deadline in a TODO comment. If the deadline passes without a re-decision, deboard for real.
---
## Anti-patterns
1. **Deleting the values-side first** before the sister-repo `Application` is removed. The `Application` errors on next reconcile.
2. **Bulk-deleting multiple clusters in one PR.** One cluster per PR. Rollback granularity.
3. **Forgetting to clean up the workload manually.** The Helm release lingers on the cluster after the `Application` is removed (because most infra apps lack `automated.prune`).
4. **Forgetting `external-secrets` cleanup.** If the app referenced an `ExternalSecret`, the corresponding `ExternalSecret` resource (in the cluster's `external-secrets/` directory) often outlives the app. Either repurpose it or delete it in the same PR.
5. **Forgetting alert / dashboard cleanup.** Alerts firing on a workload that no longer exists cause noise; dashboards showing nothing cause confusion.
---
## Rollback
If you deboarded by mistake:
1. Revert the values-side PR (`git revert <merge-sha>`).
2. Revert the sister-repo PR.
3. Click Sync in the cluster's Argo CD UI.
4. The `Application` is recreated and renders the chart.
5. **However**, if you also manually deleted the workload (step 3), the Sync recreates it from scratch — make sure the underlying chart and values are still intact and any data PVCs were retained (see `manifests/storageclass/pd-standard-retain-dr.yaml`).
---
## Related
- Procedure: [onboard-app-to-cluster.md](onboard-app-to-cluster.md) — the inverse.
- Procedure: [blue-green-chart-migration.md](blue-green-chart-migration.md) — if "deboard" is actually a migration.
- ADR: [ADR-A5-manual-sync-default-for-infra.md](../../../wiki/analyses/ADR-A5-manual-sync-default-for-infra.md) — why the workload doesn't auto-delete.
@@ -0,0 +1,158 @@
# Procedure — Intentionally fork an upstream chart
> **Layer:** Layer 1 — HIGH RISK.
> **Blast radius:** every cluster that consumes this chart will render against the forked templates on next sync.
> **Approval:** platform team — multi-reviewer.
Most charts in `helm-templates/` are **vanilla upstream**, pulled via `helm pull`. The `Chart.yaml` is a thin wrapper declaring the upstream chart as a dependency; the actual templates come from the subchart in `charts/`. Editing a `templates/*.yaml` *under the wrapper* silently forks the chart, and the fork is clobbered the next time someone runs `helm dependency update`.
This procedure is the supported path when a fork is **intentional**.
---
## When to use this procedure
- Upstream chart lacks a feature you need (e.g. a values key that doesn't exist).
- Upstream behaviour conflicts with Meesho's environment (e.g. probe path doesn't work behind Contour).
- Upstream chart has a bug awaiting an upstream fix.
This is **not** the right procedure for:
- Adding a values knob — file a PR upstream, or override behaviour through existing knobs.
- Pinning to an old version — use [update-chart-version.md](update-chart-version.md) (or just don't bump).
- Style preferences — leave the upstream chart alone.
---
## Pre-conditions
- [ ] You've confirmed the desired behaviour cannot be achieved via the chart's existing values.
- [ ] You've checked whether an upstream PR / issue already covers this.
- [ ] Platform team agrees the fork is justified.
- [ ] The fork's rationale will be documented in the chart's `README.md`.
---
## Steps
### 1. Vendor the chart's templates
If the chart is currently a thin wrapper (templates come from a subchart in `charts/<sub>`), you must first promote the subchart's templates into the wrapper.
```bash
cd helm-templates/<chart>
ls charts/ # find the subchart .tgz
helm dependency update # ensure it's resolved
# Extract templates from the subchart .tgz
tar -xzf charts/<sub>-<ver>.tgz -C /tmp/
cp -R /tmp/<sub>/templates ./templates
cp -R /tmp/<sub>/values.yaml ./values.yaml.upstream
```
Now the wrapper has its own `templates/` — Helm will use those instead of the subchart's.
### 2. Edit `Chart.yaml` to reflect the fork
Remove the dependency (since the templates are now local), and bump `version:` (the chart's own version, not the subchart's):
```diff
apiVersion: v2
name: <chart>
-version: 0.1.0
+version: 0.1.0+fork.1
description: <description> — Meesho-forked from upstream <chart> <upstream-version>
-dependencies:
- - name: <sub>
- version: <upstream-version>
- repository: <upstream-repo>
```
### 3. Make the fork edits
Edit `templates/*.yaml` or `values.yaml` to apply the fix. Keep the diff minimal — every line away from upstream is technical debt.
### 4. Document the fork
Edit `helm-templates/<chart>/README.md` (create if missing). Use this template:
```markdown
# <chart>
**Forked from upstream <chart> <upstream-version>** at <date>.
## Why
<one-paragraph rationale — what behaviour the fork changes and why we couldn't achieve
it through values alone>
## What's changed
- `templates/<file>.yaml` — <one-line diff summary>
- `values.yaml` — <one-line diff summary>
## Upstream tracking
- Upstream PR: <link, if you've sent one>
- Upstream issue: <link>
- When upstream merges: revert this fork via [unfork procedure].
```
### 5. Render and diff
```bash
sibling=$(find helm-overrides -maxdepth 2 -type d -name '<chart>' | head -1)
helm template <chart> helm-templates/<chart> -f "$sibling/custom-values.yaml" | head -120
# Spot-check helm diff against a live cluster
helm diff upgrade <release> helm-templates/<chart> \
-f helm-overrides/<cluster>/<app>/custom-values.yaml --kube-context=<context>
```
### 6. Commit and open the PR
```bash
git checkout -b fork/<chart>-<purpose>
git add helm-templates/<chart>/
git commit
git push origin fork/<chart>-<purpose>
gh pr create --base main --title "fork: <chart> — <one-line purpose>"
```
PR description must include:
- Procedure followed: this file.
- Why the fork is necessary (link to upstream issue/PR if any).
- What templates / values are changed and why.
- Platform team approver(s).
- Plan for unforking when upstream lands the fix.
---
## Anti-patterns
1. **Editing `templates/` without first vendoring** (templates from a subchart). Your edits live in `helm-templates/<chart>/templates/` but Helm renders from `charts/<sub>/templates/` — the edits do nothing, then get clobbered.
2. **Forking a chart and not documenting why.** Six months later nobody remembers; the fork looks like accidental drift.
3. **Forking instead of overriding via values.** Always check the chart's existing values surface first.
4. **Forking and then bumping the upstream version.** A bump runs `helm dependency update`, which can clobber the fork. The fork must be re-applied or the bump must explicitly re-vendor.
5. **Sweeping cleanup of upstream code** alongside the fork edit. The diff should be exactly the change you intended; everything else is upstream.
---
## Rollback / unforking
When upstream lands the fix and you want to return to vanilla:
1. Restore `Chart.yaml` to declare the upstream as a dependency (with the new upstream version that includes the fix).
2. Delete the local `templates/` and `values.yaml` (or rename them as `.upstream` for reference).
3. Run `helm dependency update`.
4. Update `helm-templates/<chart>/README.md` to remove the "forked" status.
5. Render against a sibling override and confirm the resulting manifests match what the fork was producing.
---
## Related
- Procedure: [update-chart-version.md](update-chart-version.md).
- ADR: [ADR-A1-cache-vs-upstream-charts.md](../../../wiki/analyses/ADR-A1-cache-vs-upstream-charts.md).
- [SANCTITY_RULES R7](../../global/SANCTITY_RULES.md).
@@ -0,0 +1,227 @@
> Per AI Blitz Plan §platform.procedures. Layer: 1. Repo: devops-infra-helm-charts.
# Procedure — Modify Prometheus / VictoriaMetrics alert rules
> **Layer:** Layer 1 — values diff + PR.
> **Blast radius:** PagerDuty / on-call paging across the cluster's tenants. A wrong threshold pages everyone; a missing rule masks an outage.
> **Approval:** observability owner + on-call lead for the affected severity.
This procedure is the narrow alert-rule slice of [modify-observability-config.md](modify-observability-config.md). Use this when the entire intent of the PR is alert-rule editing — adding, removing, retuning thresholds, changing `for:` windows, or rewriting `expr:` PromQL.
---
## Where alert rules live
| File | Engine | Notes |
|------|--------|-------|
| `helm-overrides/<cluster>/vmalert/custom-values.yaml` | vmalert | Most clusters use vmalert against VM cluster as the primary alerting engine. Rules typically under `config.rules` or rendered as a `VMRule` CRD. |
| `helm-overrides/<cluster>/victoria-metrics-cluster/custom-values.yaml` | vmalert (bundled) / VM ruler | Some clusters carry rules in the cluster chart's `vmalert.config` block. |
| `helm-overrides/<cluster>/prometheus/custom-values.yaml` | Prometheus / `kube-prometheus-stack` | Rules under `additionalPrometheusRulesMap` or `serverFiles.alerting_rules.yml`. |
| `helm-overrides/<cluster>/kube-prometheus-stack/custom-values.yaml` | Prometheus | Same as above; older clusters use this chart name. |
| `helm-overrides/<cluster>/mimir/custom-values.yaml` | Mimir ruler | Rules go through the ruler API; YAML in `runtimeConfig` or via a sidecar `ConfigMap`. |
Confirm which cluster runs which engine before editing — sample existing rules in the override.
---
## When to use
- Adding/removing one or more alert rules in the cluster's primary alerting engine.
- Tuning `expr:` PromQL or `for:` windows on existing rules.
- Re-routing alerts (changing `severity`, `team`, or other routing labels).
- Adding/removing recording rules for downstream alert efficiency.
Do **not** use this procedure for:
- Touching scrape configs, retention, datasources, or sizing in the same PR — split it. See [modify-observability-config.md](modify-observability-config.md).
- Bumping the alerting chart's version — see [update-chart-version.md](update-chart-version.md).
- Writing app-specific alert rules that belong in the consuming service's own values — those go in the service's repo.
---
## Inputs
| Input | Example |
|-------|---------|
| Target cluster | `k8s-supply-prd-ase1` |
| Target chart | `vmalert` / `prometheus` / `kube-prometheus-stack` / `mimir` |
| Rule name(s) added/changed | `HighErrorRate`, `KafkaConsumerLag` |
| `expr:` PromQL | `sum(rate(http_requests_total{status=~"5.."}[5m])) by (service) > 0.05` |
| `for:` window | `5m` |
| Severity / routing labels | `severity: critical`, `team: supply-platform` |
| Approval ticket | CMR-… |
---
## Pre-conditions
- [ ] You have `promtool` and/or `vmalert` available locally.
- [ ] You know the cluster's Alertmanager / vmalert notifier routing (which `severity` / `team` label routes where).
- [ ] You have run the new PromQL expression against the cluster's VM/Prometheus to confirm it returns sensible values *before* writing the rule — open Grafana Explore.
- [ ] You have on-call sign-off if this rule pages on-call.
---
## Steps
### 1. Locate the rules block
```bash
yq e '.config.rules // .additionalPrometheusRulesMap // .serverFiles' \
helm-overrides/<cluster>/<chart>/custom-values.yaml
```
Identify the YAML path the engine expects:
- **vmalert**: `config.groups[].rules[]`.
- **kube-prometheus-stack**: `additionalPrometheusRulesMap.<group-name>.groups[].rules[]`.
- **prometheus**: `serverFiles.alerting_rules.yml.groups[].rules[]`.
- **mimir ruler**: per-tenant config — confirm the cluster's tenancy setup.
### 2. Author the rule
```yaml
- alert: <RuleName> # PascalCase, no spaces; surfaces in pages and dashboards
expr: |
<PromQL expression>
for: <duration> # e.g. 5m, 10m
labels:
severity: <warning|critical|info>
team: <owning-team> # routes to the right PagerDuty service
annotations:
summary: "<one-line>"
description: "<longer text, can reference $labels.* and $value>"
runbook_url: <link to runbook>
```
#### Threshold discipline
- Express thresholds as `rate(...) > 0.05` rather than `> 5%`. Ensure units in the expression match the units in `summary` / `description`.
- Avoid `for:` windows shorter than the scrape interval × 2. Sub-`1m` windows are flap factories.
- Avoid alerting on absolute counts (`sum(http_requests_total) > 1000`); prefer rates / ratios.
- For SLO-style alerts, use multi-window multi-burn-rate (Google SRE workbook). Single-window threshold alerts are a known anti-pattern.
#### Routing implications
Changing `severity` from `warning` to `critical` (or vice versa) **silently re-routes the page** — the rule may now wake on-call where it previously emailed (or vice versa). Same for `team:` — a typo here drops alerts into the wrong PagerDuty service. Cross-check the cluster's Alertmanager / vmalert routing config:
```bash
yq e '.config.route // .alertmanagerConfig' \
helm-overrides/<cluster>/<alertmanager-chart>/custom-values.yaml
```
### 3. Render the chart
```bash
helm template <release> helm-templates/<chart> \
-f helm-overrides/<cluster>/<chart>/custom-values.yaml > /tmp/rendered.yaml
```
### 4. Validate PromQL
For Prometheus / `kube-prometheus-stack`:
```bash
yq e 'select(.kind == "PrometheusRule")' /tmp/rendered.yaml > /tmp/rules.yaml
promtool check rules /tmp/rules.yaml
```
For vmalert:
```bash
yq e 'select(.kind == "VMRule" or .kind == "ConfigMap")' /tmp/rendered.yaml > /tmp/vmrules.yaml
vmalert -dryRun -rule=/tmp/vmrules.yaml
```
For Mimir ruler — `mimirtool rules check` against the rendered ruler config.
If validation fails: fix the PromQL. **Do not** merge a rule that fails parse.
### 5. Sanity-check expressions in Grafana Explore
In a non-production manner: open Grafana on the cluster, paste the `expr:` into Explore, run over the last 6h. Confirm:
- The series returned makes sense for the rule.
- The threshold isn't always-firing or never-firing in normal operation.
- Empty result is allowed (`absent_over_time(...)` style).
### 6. Open the PR
```bash
git checkout -b alerts/<cluster>-<rule-name>
git add helm-overrides/<cluster>/<chart>/custom-values.yaml
git commit -m "alerts(<cluster>/<chart>): <add|tune|remove> <RuleName>"
git push origin alerts/<cluster>-<rule-name>
gh pr create --base main
```
### PR description template
```markdown
## Summary
<add|tune|remove> alert rule(s) on `<cluster>` × `<chart>`.
## Rules touched
| Rule | Change | New expr | New `for:` | Severity | Team |
|------|--------|----------|-----------|----------|------|
| `<RuleName>` | added | `<expr>` | `5m` | `critical` | `supply-platform` |
## Validation
- [ ] `promtool check rules` / `vmalert -dryRun` passed
- [ ] PromQL sanity-checked in Grafana Explore over last 6h
- [ ] Threshold appropriate (no flap risk, no always-firing)
- [ ] Routing labels (`severity`, `team`) verified against Alertmanager/vmalert routes
- [ ] Runbook URL added (`annotations.runbook_url`)
## Approvers
- Observability owner: <handle>
- On-call lead (severity=<x>): <handle>
```
### 7. After merge — Sync and observe
Sync the Argo Application. Watch:
```bash
# vmalert
kubectl --context=<ctx> -n monitoring logs deploy/vmalert | grep -i "<RuleName>\|error"
# kube-prometheus-stack
kubectl --context=<ctx> -n monitoring logs prometheus-k8s-0 | grep -i "<RuleName>\|error"
```
Open the alerting engine's UI (vmalert UI / Prometheus `/alerts`) to confirm the rule loaded.
### Watch for false positives
Stay on the alert for at least one full `for:` window after sync. If the rule pages immediately and was not expected to → revert.
---
## Anti-patterns
1. **Sub-1m `for:` windows** — flap-prone.
2. **Alerting on absolute counts** instead of rates/ratios.
3. **`severity` flips without on-call sign-off** — silent re-route.
4. **Missing `runbook_url`** — pages without remediation steps waste on-call cycles.
5. **PromQL that uses `{job=~"foo.*"}` regex without anchors** — slow query, sometimes false matches.
6. **Inlining the rule body in the chart `templates/`** to "make it sticky" — that's a chart fork; see NEVER-DO.
7. **Adding the rule on every cluster in one PR** — surgical only.
---
## Rollback
- Revert the PR. Sync.
- vmalert/Prometheus reload picks up the previous ruleset within seconds.
- Any in-flight alerts resolve on next evaluation.
---
## Related
- Coding guideline: [../../global/coding-guidelines/observability.md](../../global/coding-guidelines/observability.md).
- Procedure: [modify-observability-config.md](modify-observability-config.md) — broader observability edits.
- Runbook: [../runbooks/metrics-gap.md](../runbooks/metrics-gap.md) — when an alert is silent because the metric is gone.
- Schema: [../schemas/custom-values-schema.md](../schemas/custom-values-schema.md).
- Escalation: [../../global/escalation-matrix.md](../../global/escalation-matrix.md).
@@ -0,0 +1,210 @@
> Per AI Blitz Plan §platform.procedures. Layer: 1. Repo: devops-infra-helm-charts.
# Procedure — Modify observability stack config
> **Layer:** Layer 1 — values diff + PR.
> **Blast radius:** one chart × one cluster (a metrics gap, retention change, or alert-rule edit can ripple to paging and dashboards across the BU).
> **Approval:** observability owner (`siddharth.pal@meesho.com`) + cluster owner.
This procedure covers edits to the cluster-by-cluster observability override files:
| Override path | What it controls |
|---------------|------------------|
| `helm-overrides/<cluster>/victoria-metrics-agent/custom-values.yaml` | Scrape targets, relabel rules, remote-write tenancy. |
| `helm-overrides/<cluster>/victoria-metrics-cluster/custom-values.yaml` | vmstorage retention, vmselect/vminsert sizing, ruler config. |
| `helm-overrides/<cluster>/mimir/custom-values.yaml` (also `mimir-distributed`) | Mimir distributor/ingester/ruler config and tenancy. |
| `helm-overrides/<cluster>/loki/custom-values.yaml` (also `loki-distributed`) | Loki retention, ingestion limits, multi-tenant config. |
| `helm-overrides/<cluster>/tempo/custom-values.yaml` (also `tempo-distributed`) | Tempo block retention, distributor sizing. |
| `helm-overrides/<cluster>/grafana/custom-values.yaml` | Datasources, dashboard providers, plugins. |
| `helm-overrides/<cluster>/vmalert/custom-values.yaml` | vmalert alerting rules and notifier config. |
| `helm-overrides/<cluster>/prometheus/custom-values.yaml` (or `kube-prometheus-stack`) | Prometheus alert rules, scrape config, retention. |
All of these are **versioned-sibling-aware** — confirm whether the cluster's Argo Application points at `victoria-metrics-cluster` or `victoria-metrics-cluster-latest`, etc., before editing. See [../../global/coding-guidelines/observability.md](../../global/coding-guidelines/observability.md) for stack conventions.
---
## When to use
- Adding/removing scrape targets, relabel rules, recording rules.
- Changing retention (`vmstorage.retentionPeriod`, Loki `retention_period`, Tempo `compaction.block_retention`).
- Adding/removing/tuning alert rules in `vmalert` or `kube-prometheus-stack`.
- Adding/removing Grafana datasources, dashboards, plugins.
- Tuning ingester/distributor sizing for Mimir/Loki/Tempo.
Do **not** use this procedure for:
- **Bumping the chart version** of an observability tool — use [update-chart-version.md](update-chart-version.md).
- **Onboarding a brand-new observability tool** to a cluster — use [onboard-app-to-cluster.md](onboard-app-to-cluster.md).
- **A blue-green migration** between sibling charts (`-latest`) — use [blue-green-chart-migration.md](blue-green-chart-migration.md).
---
## Inputs
| Input | Example |
|-------|---------|
| Target cluster | `k8s-supply-prd-ase1` |
| Target chart | `victoria-metrics-cluster` |
| Versioned-sibling target (if applicable) | `victoria-metrics-cluster-latest` |
| Change kind | `retention bump`, `new scrape target`, `new alert rule`, `dashboard add` |
| Promql expression(s) touched (if any) | `sum(rate(...)) by (job) > 0.05` |
| Approval ticket | CMR-1234 (if BU policy requires) |
---
## Pre-conditions
- [ ] The cluster directory and chart override exist.
- [ ] The cluster's Argo `Application` points at the chart you're editing (and not its sibling). Check `github.com/Meesho/devops-infra-argo-config`.
- [ ] You have the previous PR diff for context (most observability edits are touching a known knob).
- [ ] You have access to PromQL/promtool (or vmalert binary) locally for rule validation.
---
## Steps
### 1. Confirm which sibling the cluster runs
```bash
# In the sister repo
gh search code --repo Meesho/devops-infra-argo-config "<chart>" -- path:**/<cluster>*
```
Find the `Application` whose `spec.source.path` points at this repo. Note whether it's `victoria-metrics-cluster` or `victoria-metrics-cluster-latest`. **Editing the wrong one is silent** — the file diff merges, but no cluster picks it up.
### 2. Read the current values
```bash
yq e '.' helm-overrides/<cluster>/<chart>/custom-values.yaml | less
```
Note current retention, scrape targets, and any inline alert rules.
### 3. Author the change
Edit `helm-overrides/<cluster>/<chart>/custom-values.yaml`. Surgical — only the keys the task requires. Preserve YAML key order; preserve comments.
#### Cardinality discipline
If the change adds a label, scrape target, or `relabel_configs` rule, ask: can the new label exceed ~few-hundred distinct values? If yes, drop or aggregate. See [observability.md §Cardinality discipline](../../global/coding-guidelines/observability.md).
#### Retention changes
Increasing `vmstorage.retentionPeriod`, Loki `retention_period`, or Tempo `block_retention` grows the backing PVC. Confirm `persistence.size` (or `vmstorage.persistentVolume.size`) has been bumped to match — otherwise vmstorage runs out of disk silently.
#### Alert-rule edits
If the rule's `for:` window or `severity` label changes, the PagerDuty routing may swap. Cross-check the cluster's Alertmanager / vmalert notifier config before merging.
### 4. Render the chart locally
```bash
helm template <release> helm-templates/<chart> \
-f helm-overrides/<cluster>/<chart>/custom-values.yaml > /tmp/rendered.yaml
```
The render must succeed. If it errors, fix the values before continuing.
### 5. Validate any PromQL touched
For Prometheus/`kube-prometheus-stack`:
```bash
# Extract PrometheusRule objects from rendered output
yq e 'select(.kind == "PrometheusRule")' /tmp/rendered.yaml > /tmp/rules.yaml
# Validate
promtool check rules /tmp/rules.yaml
```
For vmalert:
```bash
# Extract VMRule (or ConfigMap with rules)
yq e 'select(.kind == "VMRule" or .kind == "ConfigMap")' /tmp/rendered.yaml > /tmp/vmrules.yaml
# vmalert dry-run
vmalert -dryRun -rule=/tmp/vmrules.yaml
```
If `promtool` / `vmalert` is unavailable locally, surface the rule expression in the PR description and request reviewer to validate.
### 6. (Grafana) Validate datasource URL is in-cluster
Datasources should point at in-cluster Service DNS (e.g. `http://victoria-metrics-cluster-vmselect:8481`), not external endpoints. **Never** pin a Grafana datasource at `*.meeshogcp.in`, `prd.meesho.int`, or any production hostname — that violates the NEVER-DO list and routes through external networking unnecessarily.
### 7. Open the PR
```bash
git checkout -b obs/<cluster>-<chart>-<short-desc>
git add helm-overrides/<cluster>/<chart>/custom-values.yaml
git commit -m "obs(<cluster>/<chart>): <short desc>"
git push origin obs/<cluster>-<chart>-<short-desc>
gh pr create --base main
```
### PR description template
```markdown
## Summary
<one-line: what changed and why>
## Cluster × chart
- Cluster: `<cluster>`
- Chart: `<chart>` (sibling: `<sibling-or-N/A>`)
- File: `helm-overrides/<cluster>/<chart>/custom-values.yaml`
## Validation
- [ ] `helm template` renders cleanly
- [ ] `promtool check rules` / `vmalert -dryRun` passed (PromQL expression: `<expr>`)
- [ ] Cardinality bounded (no unbounded label introduced)
- [ ] Retention/PVC headroom confirmed (if retention changed)
- [ ] PagerDuty routing unchanged (if alert rule changed)
## Approvers
- Observability owner: <handle>
- Cluster owner: <handle>
## Sister repo
- N/A (no Application change required)
```
### 8. After merge
Argo CD on the target cluster reconciles. Most observability charts use **manual sync** (see [observability.md](../../global/coding-guidelines/observability.md)) — open the cluster's Argo CD UI, find the Application, click **Sync**. Watch:
```bash
kubectl --context=<ctx> -n <observability-ns> get pods -w
kubectl --context=<ctx> -n <observability-ns> logs <chart>-pod | tail -50
```
If a metric goes missing post-sync → [../runbooks/metrics-gap.md](../runbooks/metrics-gap.md).
---
## Anti-patterns
1. **Editing the wrong sibling.** The Argo Application points at `*-latest`; you edit the stable chart's values. Diff merges, nothing applies.
2. **Adding `pod_name` / `request_id` / `user_id` as a label** without aggregation — explodes cardinality.
3. **Bumping retention without bumping PVC.** vmstorage runs out of disk; ingest fails silently.
4. **Silent PagerDuty re-route.** Changing `severity:` from `warning` to `critical` (or vice versa) without coordinating on-call.
5. **Inlining production hostnames** as Grafana datasource URLs — see [SANCTITY_RULES.md](../../global/SANCTITY_RULES.md) R3.
6. **Cross-cluster normalising** — touching every cluster's `custom-values.yaml` in one PR. Surgical only; one cluster per PR.
---
## Rollback
- Revert the values PR. Argo CD will re-render with the previous values; click Sync.
- For retention shrinks: data older than the new retention is dropped on next compaction. Reverting restores the *config* but not the *data*.
---
## Related
- Coding guideline: [../../global/coding-guidelines/observability.md](../../global/coding-guidelines/observability.md).
- Runbook: [../runbooks/metrics-gap.md](../runbooks/metrics-gap.md).
- Procedure: [modify-alert-rules.md](modify-alert-rules.md) — alert-only edits.
- Schema: [../schemas/custom-values-schema.md](../schemas/custom-values-schema.md).
- ADR: [../../../wiki/analyses/ADR-A2-blue-green-sibling-pattern.md](../../../wiki/analyses/ADR-A2-blue-green-sibling-pattern.md).
- Wiki: [../../../wiki/entities/DevOps%20Infra%20Helm%20Charts.md](../../../wiki/entities/DevOps%20Infra%20Helm%20Charts.md).
@@ -0,0 +1,204 @@
# Procedure — Onboard a new app to an existing cluster
> **Layer:** Layer 1 — Agent-Writable.
> **Blast radius:** one new Helm release on one cluster.
> **Approval:** app owner + cluster owner.
This procedure adds a new infrastructure-tooling Helm release to a cluster that already exists in `helm-overrides/`. It covers the slice owned by `devops-infra-helm-charts`. The matching Argo `Application` lives in `github.com/Meesho/devops-infra-argo-config` and must be paired.
---
## Inputs
| Input | Example |
|-------|---------|
| Chart name (must exist in `helm-templates/`) | `kube-state-metrics` |
| Target cluster directory | `k8s-supply-prd-ase1` |
| Release name | `kube-state-metrics` (matches chart name; may be different for variants) |
| Workload namespace | `monitoring` |
| Image tag | `v2.10.1` |
| Sized resources (CPU/memory requests + limits) | `250m / 512Mi` |
| Node-pool key (per cluster) | `dedicated: monitoring` |
| Whether this needs a sidecar `external-dns` Service | yes / no |
---
## Pre-conditions
- [ ] The chart exists in `helm-templates/<chart>/` with a current `Chart.yaml`.
- [ ] The cluster directory exists in `helm-overrides/<cluster>/`.
- [ ] The chart is appropriate for an *infra* release (services don't go here — they live in `devops-argo-config`).
- [ ] The matching sister-repo `Application` PR is drafted (or will be drafted in parallel).
- [ ] CMR ticket open if required by BU policy.
---
## Steps
### 1. Verify the chart renders with a sibling cluster's values
Pick a cluster that already runs this chart and use its values as a starting point:
```bash
sibling=$(find helm-overrides -maxdepth 2 -type d -name '<chart>' | head -1)
helm template <chart> helm-templates/<chart> -f "$sibling/custom-values.yaml" | head -60
```
If the render errors → the chart's dependencies may be unresolved. Run `helm dependency update helm-templates/<chart>` first.
### 2. Identify the cluster's scheduling profile
```bash
# Standard GKE: uses 'dedicated:' keys
grep -rh 'dedicated:' helm-overrides/<cluster>/*/custom-values.yaml | sort -u
# GKE Autopilot: uses 'cloud.google.com/compute-class' keys
grep -rh 'cloud.google.com/compute-class' helm-overrides/<cluster>/*/custom-values.yaml | sort -u
```
For Contour, cross-reference [contour-nodeselector-tolerations-summary.md](../../../contour-nodeselector-tolerations-summary.md). For others, copy from a sibling app on the **same** cluster — never from the same app on a different cluster ([SANCTITY_RULES R5](../../global/SANCTITY_RULES.md)).
### 3. Create the directory and `custom-values.yaml`
```bash
mkdir -p helm-overrides/<cluster>/<app>
$EDITOR helm-overrides/<cluster>/<app>/custom-values.yaml
```
Author from scratch using [custom-values-schema.md](../schemas/custom-values-schema.md) and the cluster's scheduling profile from step 2. Do **not** copy a sibling cluster's values verbatim.
Skeleton:
```yaml
image:
registry: asia-southeast1-docker.pkg.dev
repository: meesho-devops-admin-0622/admin/sre/<image>
tag: <tag>
replicaCount: <N>
resources:
requests: {cpu: <c>, memory: <m>}
limits: {cpu: <c>, memory: <m>}
nodeSelector:
<pool-key>: <pool-value>
tolerations:
- {key: <pool-key>, value: <pool-value>, effect: NoSchedule}
```
### 4. (If needed) Add sidecar raw manifests
If the app needs sidecar resources (e.g. `external-dns` `Service`, `ComputeClass`, `ExternalSecret`), drop them in the same directory under a subfolder:
```
helm-overrides/<cluster>/<app>/
custom-values.yaml
external-dns-services/<svc>.yaml
computeclass/<name>-cc.yaml
```
See [raw-manifest-sidecar-schema.md](../schemas/raw-manifest-sidecar-schema.md).
### 5. Validate locally
```bash
yamllint helm-overrides/<cluster>/<app>/custom-values.yaml
# Render
helm template <release> helm-templates/<chart> \
-f helm-overrides/<cluster>/<app>/custom-values.yaml | head -60
# Optional: dry-run diff against the live cluster (requires kubectl context + helm-diff plugin)
helm diff upgrade <release> helm-templates/<chart> \
-f helm-overrides/<cluster>/<app>/custom-values.yaml \
--kube-context=<context>
```
### 6. Open the sister-repo PR
In `github.com/Meesho/devops-infra-argo-config`, draft an `Application` (or add to an existing `ApplicationSet`):
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: <release>-<cluster>
spec:
destination:
name: <cluster>
namespace: <workload-namespace>
source:
repoURL: https://github.com/Meesho/devops-infra-helm-charts.git
targetRevision: main
path: helm-overrides/<cluster>/<app>
helm:
valueFiles: [custom-values.yaml]
syncPolicy:
syncOptions: [CreateNamespace=true]
# Most infra apps DO NOT use automated sync — see ADR-A5
```
### 7. Open the values-side PR (this repo)
```bash
git checkout -b onboard/<chart>-on-<cluster>
git add helm-overrides/<cluster>/<app>/
git commit
# pre-commit hook runs TruffleHog
git push origin onboard/<chart>-on-<cluster>
gh pr create --base main --title "Onboard <chart> to <cluster>"
```
PR description:
- Procedure followed: this file.
- App owner approver tag.
- Cluster owner approver tag.
- Sister-repo PR link (`devops-infra-argo-config#<n>`).
- CMR ticket reference (if applicable).
- Confirmation that the chart renders cleanly and `helm diff` (if run) showed only additions.
### 8. After both merge — sync in Argo CD
`devops-infra-argo-config`'s reconciler will create the `Application` resource on the cluster's Argo CD. Most infra apps are **manual sync** ([ADR-A5](../../../wiki/analyses/ADR-A5-manual-sync-default-for-infra.md)), so the workload deploy is a separate step:
1. Open the cluster's Argo CD UI.
2. Search for the new `Application`.
3. Verify the manifest renders (Diff view shows the chart's resources).
4. Click **Sync**.
5. Watch the rollout: `kubectl --context=<context> get pods -n <ns> -w`.
---
## Anti-patterns
1. **Copying the entire `helm-overrides/<sibling-cluster>/<app>/` directory** verbatim. Per-cluster scheduling differs.
2. **Bundling onboarding with a chart-version bump.** Two separate PRs.
3. **Skipping the sister-repo PR.** Without an `Application`, the values do nothing.
4. **Setting `automated.{prune,selfHeal}: true`** in the sister-repo `Application` "to make life easier." Manual sync is the default safety property.
5. **Inlining secrets** in `custom-values.yaml`. Use `ExternalSecret`.
---
## Rollback
If the merge causes a problem before Sync:
- Revert the values-side PR (and the sister-repo PR).
- Argo CD will prune the `Application` resource on next reconcile of the sister repo.
If Sync was clicked and the workload broke:
- Click **Rollback** in Argo CD UI to the previous synced revision (if there is one).
- Or revert both PRs and re-Sync — the previous state had no `Application`, so the workload is removed.
---
## Related
- Schema: [custom-values-schema.md](../schemas/custom-values-schema.md), [raw-manifest-sidecar-schema.md](../schemas/raw-manifest-sidecar-schema.md).
- Procedure: [update-chart-version.md](update-chart-version.md) for bumping after onboarding.
- Procedure: [deboard-app.md](deboard-app.md) for retirement.
- Skill: [skills/infra/onboard-app.md](../../../skills/infra/onboard-app.md) — agent-callable wrapper.
- ADR: [ADR-A3-per-cluster-scheduling.md](../../../wiki/analyses/ADR-A3-per-cluster-scheduling.md).
@@ -0,0 +1,140 @@
# Procedure — Onboard a new cluster's overrides
> **Layer:** Layer 1 — HIGH RISK.
> **Blast radius:** establishing a new deployment target. Mistakes here are repeated for every app subsequently onboarded.
> **Approval:** platform team + cluster owner. CMR mandatory.
When a new GKE cluster is provisioned (typically by `terraform-gcp-infra` or its successor), this repo gains a new `helm-overrides/<cluster>/` directory and the sister repo gains the `ApplicationSet` (or per-cluster Applications) that route to it. This procedure covers the slice owned by `devops-infra-helm-charts`.
---
## Pre-conditions
- [ ] The cluster exists in GCP — confirmed by the platform team.
- [ ] The cluster is registered in the relevant Argo CD instance(s).
- [ ] The cluster's `nodeSelector` / `tolerations` / `computeClass` topology is documented (node pool names + taints).
- [ ] The cluster has a `SecretStore` / `ClusterSecretStore` for `external-secrets` — or onboarding `external-secrets` is part of this PR.
- [ ] CMR open.
---
## Steps
### 1. Confirm the naming convention
| Pattern | Use |
|---------|-----|
| `k8s-<bu>-prd-ase1[c]` | Standard BU prod cluster (GCP zone-a or zone-c) |
| `k8s-shared-int-ase1` | Shared int |
| `k8s-aurva-prd-ase1` | Aurva |
| `k8s-supply-dev-ase1` | Dev/sandbox |
| `db-<numeric-id>-...` | Auto-named dataplane / data-tier |
The directory name is a **contract** — it must match the cluster name as registered in Argo CD. Deviating by a hyphen or case is a silent bind failure.
### 2. Determine the cluster's scheduling profile
| Cluster type | Scheduling key |
|--------------|----------------|
| GKE Autopilot | `cloud.google.com/compute-class` |
| Standard GKE | `dedicated:` |
Get the actual node-pool / compute-class names from the cluster owner. If GKE Autopilot, also collect the list of `ComputeClass` resources that need to land in `helm-overrides/<cluster>/<app>/computeclass/`.
### 3. Decide the minimal app set
Most clusters need at least:
- `external-secrets` — to materialise secrets from GCP Secret Manager
- `kube-state-metrics` — for fleet observability
- `victoria-metrics-agent` — to ship metrics to the central VM
- `fluentd` (or equivalent log shipper)
Plus per-cluster role:
- BU clusters: `contour-internal-0`, `contour-internal-1`, sometimes `contour-external` — see [contour-nodeselector-tolerations-summary.md](../../../contour-nodeselector-tolerations-summary.md) for the convention
- DSGPU / Spark clusters: GPU operators, `keda` for queue autoscaling
- Dataplane (`db-*`) clusters: minimal — `kube-state-metrics` + `victoria-metrics-agent` only
### 4. Create the directory and minimal apps
```bash
mkdir -p helm-overrides/<cluster>
cd helm-overrides/<cluster>
```
For each app in the minimal set, follow [onboard-app-to-cluster.md](onboard-app-to-cluster.md) (one app per sub-PR after this base PR lands, OR all in one base PR — see step 7).
### 5. Update the per-cluster Contour matrix
If the cluster runs Contour, add a section to [contour-nodeselector-tolerations-summary.md](../../../contour-nodeselector-tolerations-summary.md) with the per-instance scheduling. Without this, future Contour edits on the cluster have no reference.
### 6. (If GKE Autopilot) Add the `ComputeClass` resources
Each Autopilot Contour / pool needs a corresponding `ComputeClass`. Drop them in:
```
helm-overrides/<cluster>/<app>/computeclass/<name>-cc.yaml
```
Schema: [raw-manifest-sidecar-schema.md §ComputeClass](../schemas/raw-manifest-sidecar-schema.md). The `metadata.name` of each `ComputeClass` must equal the value the corresponding app's `nodeSelector: {cloud.google.com/compute-class: <name>}` references.
### 7. Open the base PR
```bash
git checkout -b cluster/<cluster>-base
git add helm-overrides/<cluster>/
git add contour-nodeselector-tolerations-summary.md # if updated
git commit
git push origin cluster/<cluster>-base
gh pr create --base main --title "Onboard <cluster> — base override set"
```
PR description must include:
- Cluster owner approver tag.
- Platform team approver tag.
- CMR ticket.
- Sister-repo PR for the `ApplicationSet` / per-cluster `Application` set.
- The minimal app set being shipped, with per-app rationale.
- The cluster's scheduling profile (key style + sample `nodeSelector`).
### 8. Pair the sister-repo PR
In `github.com/Meesho/devops-infra-argo-config`, draft the `ApplicationSet` (or per-cluster Application set) that walks `helm-overrides/<cluster>/`. The sister-repo PR depends on this PR being merged first (otherwise the paths it references don't exist).
### 9. Stage subsequent app additions
After the base PR merges:
- Each additional app (`contour-internal-0`, `flagger`, etc.) is its own PR via [onboard-app-to-cluster.md](onboard-app-to-cluster.md).
- Don't try to land the whole cluster in one PR — review surface explodes; rollback is all-or-nothing.
---
## Anti-patterns
1. **Naming the directory `<cluster-name>` slightly differently** from how Argo CD registered it. Silent bind failure.
2. **Cloning another cluster's directory verbatim.** Per-cluster scheduling differs; `external-secrets` `SecretStore` references differ; image-tag pinning differs.
3. **Onboarding 30 apps in the base PR.** Stage them — base + per-app.
4. **Forgetting the Contour matrix update.** Future Contour edits then have no reference and silently mis-schedule.
5. **Skipping the `ComputeClass` resources** on a GKE Autopilot cluster. `nodeSelector` references a class that doesn't exist; pods Pending.
---
## Rollback
If the cluster onboarding turns out to be premature:
- Revert the values-side PR.
- Revert the sister-repo `ApplicationSet` PR.
- The cluster reverts to "no Argo Applications" — workloads that were synced manually before revert remain (Argo CD doesn't `prune` what's not in its scope after the `Application` is removed); for a clean wipe, manually `kubectl delete ns` the affected namespaces.
---
## Related
- Procedure: [onboard-app-to-cluster.md](onboard-app-to-cluster.md) — for each app after the base PR.
- Schema: [raw-manifest-sidecar-schema.md](../schemas/raw-manifest-sidecar-schema.md) — for `ComputeClass` and `ExternalSecret` sidecars.
- Reference: [contour-nodeselector-tolerations-summary.md](../../../contour-nodeselector-tolerations-summary.md).
- ADR: [ADR-A3-per-cluster-scheduling.md](../../../wiki/analyses/ADR-A3-per-cluster-scheduling.md).
@@ -0,0 +1,176 @@
# Procedure — Update a chart's pinned dependency version
> **Layer:** Layer 1 — HIGH RISK (charts in `helm-templates/` are consumed by every cluster that runs them).
> **Blast radius:** every cluster that has an Argo `Application` referencing this chart will pick up the new version on next sync.
> **Approval:** platform team. CMR mandatory for prod-fleet charts.
In this repo, charts in `helm-templates/<chart>/` are typically thin wrappers — `Chart.yaml` declares an upstream chart as a dependency, and `Chart.lock` pins the resolved subchart. "Bumping the chart version" means:
1. Update `dependencies[].version` in `Chart.yaml`.
2. Run `helm dependency update` to refresh `Chart.lock` (and re-pull the subchart).
3. Test render with representative cluster overrides.
---
## When to use this procedure
- Upgrading a wrapper chart's pinned subchart version (e.g. `argo-cd 7.7.23``7.8.0`).
- Following a security advisory in an upstream chart.
This is **not** the right procedure for:
- A blue-green migration to a new major version → use [blue-green-chart-migration.md](blue-green-chart-migration.md).
- Editing chart templates → use [fork-upstream-chart.md](fork-upstream-chart.md).
- Changing values without a chart bump → that's per-cluster `custom-values.yaml` work, not this.
---
## Pre-conditions
- [ ] The new upstream version exists and has a published changelog.
- [ ] You have read the changelog for breaking template / values changes.
- [ ] The bump is the **explicit headline** of the PR (not a side-effect of another change).
- [ ] CMR open if this is a prod-fleet chart (Argo CD, Contour, VictoriaMetrics, ingress, cert-manager).
---
## Steps
### 1. Read the changelog
```bash
# For most upstream charts:
helm repo update
helm search repo <repo>/<chart> --versions | head -20
# Read the chart's CHANGELOG.md or release notes on GitHub.
```
Look specifically for:
- **Removed values keys** — would silently no-op overrides.
- **Renamed values keys** — same.
- **CRD changes** — might require manual `kubectl apply` of new CRDs.
- **Breaking template changes** (e.g. label selector immutability on Deployments).
- **Required Kubernetes version bumps**.
If any of these apply, this is not a simple version bump — escalate to a blue-green migration ([blue-green-chart-migration.md](blue-green-chart-migration.md)).
### 2. Update `Chart.yaml`
```bash
$EDITOR helm-templates/<chart>/Chart.yaml
```
Find the `dependencies[]` entry and bump `version:`. Example:
```diff
dependencies:
- name: argo-cd
- version: 7.7.23
+ version: 7.8.0
repository: https://argoproj.github.io/argo-helm
```
### 3. Refresh `Chart.lock`
```bash
helm dependency update helm-templates/<chart>
```
This:
- Verifies the new version resolves.
- Updates `Chart.lock` with the new digest.
- Re-pulls the subchart `.tgz` into `helm-templates/<chart>/charts/`.
The lockfile must be committed alongside `Chart.yaml`. A bump without a refreshed lockfile is incomplete.
### 4. Render against a representative cluster's overrides
Pick a cluster that runs this chart with a non-trivial override set:
```bash
sibling=$(find helm-overrides -maxdepth 2 -type d -name '<chart>' \
| grep -v '^helm-overrides/db-' | head -1)
helm template <chart> helm-templates/<chart> -f "$sibling/custom-values.yaml" | head -120
```
If the render errors → the chart bump introduced a values incompatibility. **Stop.** Either:
- Find the new values shape and update affected `custom-values.yaml` files in this PR, OR
- Defer the bump until those updates are scoped.
### 5. Spot-check `helm diff` against a live cluster (recommended)
```bash
helm diff upgrade <release> helm-templates/<chart> \
-f helm-overrides/<cluster>/<app>/custom-values.yaml \
--kube-context=<cluster-context>
```
What you want to see:
- Image tag bumps.
- Label updates (often `helm.sh/chart`).
- Possibly new CRDs or RBAC.
What's a red flag:
- `Deployment` selector changes (immutable; will fail to apply).
- `StatefulSet` `volumeClaimTemplates` changes.
- Resource removals you didn't expect.
### 6. Commit and open the PR
```bash
git checkout -b chart-bump/<chart>-<new-version>
git add helm-templates/<chart>/Chart.yaml helm-templates/<chart>/Chart.lock
git add helm-templates/<chart>/charts/ # if the .tgz was re-pulled
git commit
git push origin chart-bump/<chart>-<new-version>
gh pr create --base main --title "chart bump: <chart> <old> → <new>"
```
PR description:
- Procedure followed: this file.
- Old → new with a link to the upstream changelog.
- The list of clusters that consume this chart (`grep -rl '<chart>' helm-overrides | head`).
- The render(s) and diff(s) you ran.
- Whether any breaking-change fallout is bundled (preferably not — if so, scope to a separate PR).
### 7. After merge — Argo CD picks up the new chart on next sync
For each cluster running this chart:
- The cluster's Argo CD detects the chart hash change.
- The `Application` becomes `OutOfSync`.
- A human clicks **Sync** per [ADR-A5](../../../wiki/analyses/ADR-A5-manual-sync-default-for-infra.md).
- Watch rollout per cluster — don't sync 20 clusters simultaneously.
---
## Anti-patterns
1. **Bumping `Chart.yaml` without refreshing `Chart.lock`.** Argo CD uses the lockfile; the bump silently no-ops.
2. **Bundling a chart bump with values changes.** Two separate PRs — one for the bump, one for any values that need to change because of the bump.
3. **Bumping a major version (e.g. 7.x → 8.x) in place.** Use [blue-green-chart-migration.md](blue-green-chart-migration.md) — give yourself a sibling chart and migrate cluster-by-cluster.
4. **Syncing every cluster simultaneously after merge.** Stagger; one cluster at a time, with a soak between.
5. **Skipping the changelog read.** Surprises you'll regret.
---
## Rollback
Open a follow-up PR that reverts `Chart.yaml` and `Chart.lock` to the previous versions. After merge, each cluster's Argo CD shows `OutOfSync` against the old chart; click **Sync** to roll back per cluster.
If the chart bump landed CRDs that are now incompatible with the old version, the rollback may require manual CRD cleanup — coordinate with the platform team.
---
## Related
- Procedure: [blue-green-chart-migration.md](blue-green-chart-migration.md) — for major-version bumps.
- Procedure: [fork-upstream-chart.md](fork-upstream-chart.md) — when the bump requires template edits.
- Skill: [skills/infra/bump-chart-version.md](../../../skills/infra/bump-chart-version.md).
- ADR: [ADR-A1-cache-vs-upstream-charts.md](../../../wiki/analyses/ADR-A1-cache-vs-upstream-charts.md).