336 lines
11 KiB
Markdown
336 lines
11 KiB
Markdown
# 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 439–449):
|
|||
|
|
```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 1–29 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 400–401):
|
|||
|
|
```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 40–42):
|
|||
|
|
```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 402–416):**
|
|||
|
|
```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 64–72):
|
|||
|
|
```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 1–37):
|
|||
|
|
```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 1–47):
|
|||
|
|
```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 471–477).
|
|||
|
|
|
|||
|
|
**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 471–477):
|
|||
|
|
```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.
|