169 lines
6.9 KiB
Markdown
169 lines
6.9 KiB
Markdown
# planpalto-helm
|
|
|
|
Helm chart for the PlanPal stack. Converted from `planpalto-infrato` (kustomize)
|
|
one-to-one, targeting the fakhri-k8 EKS cluster: ESO reads secrets from AWS
|
|
Secrets Manager, and an ALB ingress serves the app.
|
|
|
|
## Two ways to run it
|
|
|
|
1. **Direct Helm** (below) - one release, no Argo. Good for a quick test.
|
|
2. **Argo CD App-of-Apps** ([argocd/](argocd/)) - Git-driven, two environments
|
|
(staging + prod), self-healing. This is the deploy path. See
|
|
[Argo CD App-of-Apps](#argo-cd-app-of-apps).
|
|
|
|
## Install (direct Helm)
|
|
|
|
```bash
|
|
helm upgrade --install planpal ./planpalto-helm
|
|
```
|
|
|
|
The chart creates the `planpal` namespace and the `planpal-backend` service
|
|
account itself. It does NOT install the controllers those objects depend on -
|
|
External Secrets Operator, the AWS Load Balancer Controller, and metrics-server
|
|
must already run on the cluster (they do on fakhri-k8).
|
|
|
|
## Argo CD App-of-Apps
|
|
|
|
One **root** Application ([argocd/root-app.yaml](argocd/root-app.yaml)) syncs the
|
|
child Applications in [argocd/apps/](argocd/apps/): one per environment. Each
|
|
child installs *this same chart* with a different value-file, so the two envs
|
|
share nothing.
|
|
|
|
```
|
|
planpal-root (root App-of-Apps)
|
|
├─ planpal-staging ns planpal-staging host staging.fakhri-k8... own ALB
|
|
└─ planpal-prod ns planpal-prod host fakhri-k8... planpal-helm ALB
|
|
```
|
|
|
|
Objects inside each env are ordered by `argocd.argoproj.io/sync-wave`:
|
|
|
|
| wave | objects |
|
|
|------|---------|
|
|
| 0 | ClusterSecretStore + ExternalSecrets, redis, nats, Services |
|
|
| 1 | app Deployments |
|
|
| 2 | HPA + PDB (need their Deployment to exist first) |
|
|
| 3 | ALB ingress |
|
|
| 4 | seed-admin Job |
|
|
|
|
Argo waits for a wave to be healthy before the next. So secrets and deps land
|
|
before app pods, HPAs after the Deployments they target, ingress next, seed last.
|
|
HPA and PDB sit one wave *after* the Deployments on purpose: an HPA applied
|
|
before its target Deployment errors "scaleTargetRef not found" and fails the
|
|
whole sync.
|
|
|
|
### Chart source: in-cluster Gitea
|
|
|
|
Argo pulls the chart from Git, so the chart lives in a repo. This setup uses a
|
|
self-hosted **Gitea** on the cluster ([gitea/gitea.yaml](gitea/gitea.yaml)):
|
|
one pod, SQLite, one 5Gi PVC. Argo reaches it over the cluster network at
|
|
`http://gitea-http.gitea.svc.cluster.local:3000`, so no ingress or TLS is needed
|
|
for the Argo-to-Gitea hop.
|
|
|
|
Gitea is installed **outside** Argo (plain `kubectl apply`) because Argo pulls
|
|
its own manifests *from* Gitea - it cannot manage the git host it depends on.
|
|
|
|
### Bootstrap order (once)
|
|
|
|
```bash
|
|
# 1. Git host
|
|
kubectl apply -f planpalto-helm/gitea/gitea.yaml
|
|
kubectl -n gitea rollout status deploy/gitea
|
|
|
|
# 2. Create the admin user + an org/repo in Gitea, then push this tree.
|
|
# (port-forward for the browser, or use the API)
|
|
kubectl -n gitea exec deploy/gitea -- \
|
|
gitea admin user create --admin --username planpal \
|
|
--password 'CHANGE_ME' --email you@example.com --must-change-password=false
|
|
kubectl -n gitea port-forward svc/gitea-http 3000:3000 # open http://localhost:3000, make org "planpal" + repo "ch4"
|
|
|
|
git init && git add . && git commit -m "init"
|
|
git remote add gitea http://planpal:CHANGE_ME@localhost:3000/planpal/ch4.git
|
|
git push gitea HEAD:main
|
|
|
|
# 3. Point Argo at it: the repoURL in the three argocd/*.yaml files is already
|
|
# http://gitea-http.gitea.svc.cluster.local:3000/planpal/ch4.git .
|
|
# Add the repo credentials to Argo, then apply the root:
|
|
argocd repo add http://gitea-http.gitea.svc.cluster.local:3000/planpal/ch4.git \
|
|
--username planpal --password CHANGE_ME
|
|
kubectl apply -f planpalto-helm/argocd/root-app.yaml
|
|
```
|
|
|
|
After that, `git push` to Gitea is the deploy. Argo notices, syncs, self-heals.
|
|
|
|
> Change the `repoURL` org/repo (`planpal/ch4`) in all three
|
|
> [argocd/](argocd/) files if you name yours differently.
|
|
|
|
## What it renders
|
|
|
|
| Group | Objects |
|
|
|-------|---------|
|
|
| Apps | Deployments backend, frontend, schedule/notification/ai workers; Services for backend+frontend; HPAs for all five; PDBs for backend+frontend |
|
|
| Deps | redis, nats (Deployment + Service each) |
|
|
| Secrets | 1 ClusterSecretStore + 4 ExternalSecrets (backend-env, aws-env, seed-env, frontend-env) |
|
|
| Ingress | ALB ingress on `.Values.ingress.host` with the ACM cert |
|
|
| Seed | one-shot `seed-admin` Job (TTL 300s) |
|
|
|
|
## Multiple installs on one cluster
|
|
|
|
Each release is fully separated. `namespace`, the ALB `group.name`, and the
|
|
cluster-scoped `ClusterSecretStore` all default to the release name, so two
|
|
installs share nothing:
|
|
|
|
```bash
|
|
helm install planpal-a ./planpalto-helm --set ingress.host=a.example.com
|
|
helm install planpal-b ./planpalto-helm --set ingress.host=b.example.com
|
|
```
|
|
|
|
| Per-release resource | planpal-a | planpal-b |
|
|
|----------------------|-----------|-----------|
|
|
| Namespace | planpal-a | planpal-b |
|
|
| ClusterSecretStore | planpal-a-aws-planpal | planpal-b-aws-planpal |
|
|
| ALB (group.name) | planpal-a | planpal-b |
|
|
|
|
Give each release a distinct `ingress.host`. Each release gets its own ALB, so
|
|
each also gets its own DNS name. Set the same `ingress.groupName` on two
|
|
releases only if you later want them to share one ALB.
|
|
|
|
Releases share the same AWS Secrets Manager keys (`planpal/*`) by default. Same
|
|
DB, admin seed, and config across releases, which you said is fine. To give a
|
|
release its own data, point `externalSecrets.secrets` at different keys and
|
|
allow the ESO controller role to read them.
|
|
|
|
## external-dns
|
|
|
|
Set `ingress.externalDNS=true` to add the external-dns hostname annotation. The
|
|
external-dns controller then creates the Route53 record for `ingress.host`. This
|
|
needs external-dns installed on the cluster and permitted on the target hosted
|
|
zone. Without it, create the DNS record by hand (CNAME/alias to the ALB).
|
|
|
|
## Common overrides
|
|
|
|
```bash
|
|
# different DNS name + cert
|
|
helm upgrade --install planpal ./planpalto-helm \
|
|
--set ingress.host=my.example.com \
|
|
--set ingress.certArn=arn:aws:acm:...
|
|
|
|
# bump the frontend image tag
|
|
helm upgrade --install planpal ./planpalto-helm \
|
|
--set image.frontend.tag=1.2
|
|
|
|
# skip the seed Job on a re-deploy
|
|
helm upgrade --install planpal ./planpalto-helm --set seedJob.enabled=false
|
|
```
|
|
|
|
## Notes on the conversion
|
|
|
|
- The five app Deployments share one templated shape in `templates/apps.yaml`,
|
|
driven by the `apps:` map in `values.yaml`. The map key is the workload name.
|
|
An entry with `image: backend` gets the service account and backend image; an
|
|
entry with `service:` gets a Service; `hpa:`/`pdb:` blocks render only when
|
|
present. This is the kustomize base + per-app difference, expressed as data.
|
|
- ESO is fully templated: add a `target: smKey` pair under
|
|
`externalSecrets.secrets` and a new ExternalSecret appears.
|
|
- The kustomize `secretGenerator` for `planpal-tls` and the `bootstrap.yaml`
|
|
RDS-bootstrap Pod are NOT in this chart. Neither was in the kustomize
|
|
`resources:` list either. TLS now comes from ACM on the ALB, and RDS
|
|
bootstrap is a one-shot manual step. Run it by hand if a fresh DB needs it.
|
|
```
|