Files
homelab/project-usage/coding-standards.md
T
Story Crater Bot dd608d3231 Step 1 complete: Bootstrap layer with ArgoCD, cert-manager, namespaces imported to TF
- ArgoCD migrated to argocd namespace
- Cert-manager issuers/certs created
- 20 namespaces imported with pod-security labels
- S3 backend temporarily offline (MinIO), using local backup
- Pending: Remove metadata drift from helm releases, re-apply
2026-07-14 13:14:46 -07:00

336 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Coding Standards — Helm, Helmfile, Kubernetes, Shell
Conventions for contributing to homelab's Helm charts, helmfiles, Kubernetes manifests, and shell scripts. These are repo-local patterns — not Go/Rust/general standards from companion repos (core CLI, kmsvc).
---
## Helm & Helmfile Conventions
### Chart Naming & Layout
- **Local charts:** `k8s/<service>/charts/<component>/` directory structure.
- **Helm chart versions:** Always use semver ranges in helmfile releases (e.g., `~1.0`, `~10`, not floating `latest`).
- Rationale: Predictable upgrades, avoids surprise breaking changes.
### helmfile.yaml.gotmpl Pattern
The helmfile is a **Go template**, not a shell script. Use Go template syntax for environment variable interpolation, not shell syntax.
**Correct:**
```yaml
set:
- name: adminPassword
value: {{ env "GRAFANA_ADMIN_PASSWORD" }}
```
**Incorrect:**
```yaml
set:
- name: adminPassword
value: ${GRAFANA_ADMIN_PASSWORD} # Shell syntax — not expanded by helmfile
```
**Organization:**
- Use logical comment headers to separate sections: `# ── cert-manager ──`.
- Group related releases together.
- Use `needs:` for dependency ordering (release A waits for release B before deploying).
- Example (from helmfile.yaml.gotmpl, lines 439449):
```yaml
- name: loki
namespace: logging
needs:
- storage/minio
- name: promtail
namespace: logging
needs:
- logging/loki
```
- **Namespace declaration:** Specify namespace at the release block level, not helmfile-level default.
```yaml
- name: prometheus
namespace: monitoring
createNamespace: true
```
### Values Files Pattern
- **Never hardcode secrets** in values.yaml or ConfigMap keys.
- Store secrets in Vault via `core put cluster/KEY KEY="value"`.
- Reference at deploy time using `{{ env "VAR" }}` in helmfile.
- **External values files:** Always use `values:` block pointing to `.yaml` files, not inline YAML.
```yaml
- name: minio
values:
- k8s/storage/minio-values.yaml
```
- **Environment-specific overrides:** For complex releases (e.g., SQS), use `environments/` subdirectory with `helmfile.yaml.gotmpl` at the service level.
- Example: `k8s/sqs/environments/homelab.yaml` (lines 129 show namespace, kafkaCluster, redis, managementService blocks).
- Helmfile loads environment-specific values dynamically: `{{ .Values.kafkaCluster.nodePool.replicas }}`.
---
## YAML & ConfigMap/Secret Patterns
### Secret Field Naming
**Rule: Field name in Secret = environment variable name in Vault.**
When storing a secret via `core put cluster/KEY KEY="value"`, the field name and Vault variable name must match. This ensures helmfile's `{{ env "VAR" }}` expansion works correctly.
Example (from CLAUDE.md gotcha "Field name = variable name"):
```bash
# Correct
core put cluster/MINIO_ROOT_PASSWORD MINIO_ROOT_PASSWORD="value"
core put cluster/GRAFANA_ADMIN_PASSWORD GRAFANA_ADMIN_PASSWORD="value"
# Incorrect (won't expand in helmfile)
core put cluster/MINIO_SECRET value="value" # Field name ≠ variable name
```
Reference in helmfile (helmfile.yaml.gotmpl, lines 400401):
```yaml
set:
- name: rootPassword
value: {{ env "MINIO_ROOT_PASSWORD" }}
```
### Never Use `--env` Flags in Manifests
Avoid kubectl flags like `--env KEY=value` in manifests or deployment specs. This exposes secrets in `kubectl describe` output.
**Correct:** Use Secret volumes (k8s/storage/minio-values.yaml, lines 4042):
```yaml
envFrom:
- secretRef:
name: minio-oidc # Reference a Secret, don't expose in manifest
```
**Incorrect:**
```yaml
env:
- name: MINIO_OIDC_SECRET
value: "sensitive-value" # Visible in kubectl describe
```
### Namespace-First Organization
Organize manifests by namespace: `k8s/<namespace>/`
Structure per namespace:
```
k8s/storage/
├── minio-values.yaml
├── minio-bucket-init.sh
└── (local charts if any)
k8s/monitoring/
├── prometheus-values.yaml
├── dashboards/ # ConfigMap files auto-loaded via helmfile postsync hook
├── servicemonitors/ # ServiceMonitor CRD instances
└── alerts/ # PrometheusRule CRD instances
```
---
## Hook Scripts & Integration Workflows
### Pre/Post-Sync Hooks
Helmfile hooks (presync/postsync) drive setup workflows. Use **relative paths from repo root**, never absolute paths.
**Pattern (helmfile.yaml.gotmpl, lines 402416):**
```yaml
- name: minio
hooks:
- events: ["presync"]
command: bash
args:
- -c
- |
bash k8s/base/namespace-setup.sh storage
bash k8s/storage/minio-bucket-init.sh storage loki-chunks loki-ruler
```
**Common presync tasks:**
- Create namespace (via `k8s/base/namespace-setup.sh`).
- Pre-create ConfigMaps/Secrets for the release.
- Initialize infrastructure (buckets, databases, etc.).
**Common postsync tasks:**
- Wait for operator/webhook readiness.
- Apply CRD instances (ServiceMonitor, PrometheusRule, Certificate).
- Perform post-deployment setup (cluster initialization, user creation).
Example (helmfile.yaml.gotmpl, lines 6472):
```yaml
hooks:
- events: ["postsync"]
command: bash
args:
- -c
- |
kubectl rollout status deploy/cert-manager -n cert-manager --timeout=120s
kubectl apply -f - <<'EOF'
# ClusterIssuer and Certificate CRD instances...
EOF
```
### ServiceMonitor Pattern
One file per service in `k8s/monitoring/servicemonitors/svc-<name>.yaml`.
**Key elements:**
- `namespaceSelector`: Match the namespace where the app runs.
- `selector.matchLabels`: Match the app label from the Deployment (e.g., `app.kubernetes.io/name: argocd-metrics`).
- `endpoints.port`: Name of the metrics port in the Service.
- `interval`: Scrape frequency (e.g., `30s`).
Example (`k8s/monitoring/servicemonitors/argocd.yaml`, lines 137):
```yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: argocd
namespace: monitoring
labels:
release: kube-prometheus-stack # Required: links to Prometheus release
spec:
namespaceSelector:
matchNames:
- cicd # App namespace
selector:
matchLabels:
app.kubernetes.io/name: argocd-metrics # Matches Deployment pod label
endpoints:
- port: metrics # Service port name (not port number)
interval: 30s
scrapeTimeout: 10s
```
### PrometheusRule Pattern
One file per service in `k8s/monitoring/alerts/svc-<name>-rules.yaml`.
**Structure:**
- `groups[].name`: Logical grouping (e.g., `minio.rules`).
- `groups[].rules[].alert`: Alert name.
- `expr`: PromQL expression (5-minute windows for rate alerts).
- `for`: Duration threshold (e.g., `10m`).
- `labels.severity`: `critical`, `warning`.
- `annotations`: summary + description (use `{{ $value }}` for metric value).
Example (`k8s/monitoring/alerts/svc-minio-rules.yaml`, lines 147):
```yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: minio-rules
namespace: storage
spec:
groups:
- name: minio.rules
interval: 15s
rules:
- alert: MinIOHighErrorRate
expr: |
(
sum(rate(minio_s3_requests_total{error="true"}[5m]))
/
sum(rate(minio_s3_requests_total[5m]))
) > 0.05
for: 10m
labels:
severity: warning
annotations:
summary: "High error rate on MinIO"
description: "{{ $value | humanizePercentage }}"
```
### Grafana Dashboard Pattern
Dashboards are stored as JSON ConfigMaps in `k8s/monitoring/dashboards/` and auto-loaded via helmfile postsync hook (see helmfile.yaml.gotmpl, lines 471477).
**6-row template (recommended layout):**
1. **Availability:** Uptime, error rate, latency (SLO band).
2. **Resources:** CPU, memory, disk usage, network I/O.
3. **Domain metrics:** Service-specific KPIs (throughput, queue depth, cache hit rate).
4. **Logs:** Recent error logs from Loki.
5. **SLO:** SLI tracking (burn rate, error budget).
6. **Related dashboards:** Links to dependent services.
Auto-load hook (helmfile.yaml.gotmpl, lines 471477):
```yaml
hooks:
- events: ["postsync"]
command: kubectl
args:
- apply
- -f
- k8s/monitoring/dashboards/
```
---
## Integration Checklist
When adding a new service to the homelab, verify the following:
- [ ] **Chart pinning:** Helm chart version pinned (`~1.0` format in helmfile, not floating `latest`).
- [ ] **Secrets management:** All secrets stored in Vault (none in values.yaml, ConfigMap, or CLI flags).
- [ ] **Metrics endpoint:** Service exports `/metrics` endpoint (Prometheus format).
- [ ] **ServiceMonitor:** Created and auto-scraped by Prometheus operator.
- [ ] **PrometheusRule:** Alert rules defined for errors, latency, SLO violations.
- [ ] **Grafana dashboard:** 6-row template auto-loaded via ConfigMap.
- [ ] **Ingress:** Rule added if external access needed (see `k8s/ingress/ingress.yaml`).
- [ ] **OIDC integration:** If UI component, integrated with Authentik (see `core iam bootstrap`).
---
## Shared/Reusable Repos & Image Publication
### When to Publish as Public GHCR
**Rule:** If a service's chart + image source lives in a separate repo (not in homelab), it **must** be published as a **public GitHub repo** under the `Riotpiaole` org.
**Rationale:**
- Local in-cluster charts are fine for infra-owned services.
- Shared/reusable service charts should be version-pinned and publicly available for:
- Reuse across different clusters (other labs, staging, prod).
- Consumption by Argo CD apps (CI/CD pipeline).
- Independent evolution without tight coupling to homelab repo.
### Workflow
**Develop locally:**
1. Create companion repo (e.g., `kafaka-management-service`, `queue-operator`) in a private or public repo.
2. Include Dockerfile and Helm chart.
**Build & publish:**
1. Set up CI/CD in the companion repo (GitHub Actions).
2. Build image and push to GHCR: `ghcr.io/Riotpiaole/<repo>:<tag>`.
3. Tag release and publish chart (npm registry, GitHub releases, or OCI registry).
**Reference in homelab:**
1. Pin image tag + chart version in helmfile.yaml.gotmpl.
2. Chart `repositories` block references the public Helm repo (or OCI registry).
**Example (commit cd1c569 — SQS charts):**
```yaml
- name: management-service
namespace: sqs
chart: k8s/sqs/charts/management-service
values:
- image:
repository: ghcr.io/Riotpiaole/kafaka-management-service
tag: v1.0.0 # Pinned tag from public build
```
---
## Cross-References
- **CLAUDE.md gotchas:** Field name = variable name, MinIO `--env` flag exposure, helmfile template syntax (`{{ env "VAR" }}` not `${VAR}`), CNPG password templating.
- **README.md:** kubectl context setup, port-forward aliases, cluster topology.
- **USAGE.md:** IAM bootstrap, secret management, deployment recipes.