Initial commit — LoginMaster tenant deployment toolkit
Toolkit per deployare/aggiornare un tenant LoginMaster su qualsiasi Kubernetes (EKS/AKS/DOKS/Scaleway/vSphere/...). Contiene: - deploy.sh: bootstrap di un nuovo tenant (idempotente, re-run protection, storage class auto-rilevata, prompt separati api/admin tag, generazione segreti crittografici via openssl rand). - update.sh: rolling update zero-downtime con tag api/admin separati, rollback hint via 'kubectl rollout undo', riapplicazione opzionale del ConfigMap. - templates/: 8 manifest parametrici (envsubst): namespace, cert-manager TLS Mongo, NetworkPolicy intra-namespace, ConfigMap, MongoDB StatefulSet 3 repliche con TLS interno + initContainer per keyfile/PEM, tenant-api Deployment 2 repliche con CA validation, tenant-admin, ingress nginx + Let's Encrypt. Sicurezza: TLS interno Mongo (cert-manager CA self-signed 10y), keyFile per auth replica set, password client mai in argv, NetworkPolicy che isola il tenant, pod Mongo non-root (uid 999) con initContainer come root per i file runtime in tmpfs.
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
# LoginMaster Tenant — Deployment Tools
|
||||
|
||||
Toolkit per deployare un tenant LoginMaster (api-tenant + admin-tenant + MongoDB replica set) su qualsiasi cluster Kubernetes — vSphere, AWS EKS, Azure AKS, DigitalOcean DOKS, Scaleway Kapsule, Hetzner, ecc.
|
||||
|
||||
## Cosa viene deployato
|
||||
|
||||
```
|
||||
namespace: <a tua scelta, es. loginmaster-tenant-acme>
|
||||
│
|
||||
├── MongoDB replica set rs0 (3 pod, StatefulSet)
|
||||
│ ├── TLS server cert via cert-manager (CA interna self-signed, 10 anni)
|
||||
│ ├── auth replica set via keyFile, auth client via password
|
||||
│ └── PVC RWO 20Gi per nodo (storage class scelta dall'utente)
|
||||
│
|
||||
├── tenant-api (Deployment, 2 repliche, rolling update zero-downtime)
|
||||
│ └── connessione Mongo via TLS, valida server cert via la CA interna
|
||||
│
|
||||
├── tenant-admin (Deployment, 1 replica)
|
||||
│
|
||||
├── Ingress nginx con cert Let's Encrypt per i due hostname pubblici
|
||||
│
|
||||
├── NetworkPolicy che isola il tenant:
|
||||
│ ├── Mongo raggiungibile solo dai pod tenant-api e dai membri replica set
|
||||
│ └── api/admin raggiungibili solo dall'ingress controller
|
||||
│
|
||||
└── Secret per: credenziali Mongo, MASTER_ENCRYPTION_KEY, registry, SMTP password
|
||||
```
|
||||
|
||||
## Prerequisiti
|
||||
|
||||
### Sul cluster
|
||||
|
||||
| Cosa | Note |
|
||||
|---|---|
|
||||
| **cert-manager** | Con `ClusterIssuer` `letsencrypt-prod` per i certificati Ingress pubblici. Gli `Issuer` interni per il TLS Mongo sono creati dallo script. |
|
||||
| **ingress-nginx** | Installato in un namespace con la label `kubernetes.io/metadata.name: ingress-nginx` (default da K8s 1.22+). |
|
||||
| **CNI con NetworkPolicy** | Calico, Cilium, weave, kube-router. Le `NetworkPolicy` non sono enforcement-effective senza un CNI che le implementi. |
|
||||
| **Storage class default** | Auto-rilevata dallo script via annotation `storageclass.kubernetes.io/is-default-class`. Se il cluster non ne ha una marcata, lo script chiede manualmente. |
|
||||
|
||||
### Sulla macchina
|
||||
|
||||
```
|
||||
brew install kubectl gettext openssl
|
||||
brew link --force gettext # per envsubst
|
||||
```
|
||||
|
||||
### Credenziali
|
||||
|
||||
- Accesso al registry `hub.codebaker.it/loginmaster-tenant/` (username + password)
|
||||
- Context kubectl configurato per il cluster target
|
||||
|
||||
## Deploy iniziale
|
||||
|
||||
```bash
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
Lo script chiede 14 parametri in modo interattivo. Default sensati per Codebaker (in `[parentesi]`):
|
||||
|
||||
| Prompt | Default | Note |
|
||||
|---|---|---|
|
||||
| Context kubectl | corrente | Quello selezionato in `~/.kube/config` |
|
||||
| Namespace | `loginmaster-tenant` | Uno per tenant. Lo script aborta se esiste già |
|
||||
| Tag api-tenant | `prod-1.3.8` | |
|
||||
| Tag admin-tenant | `prod-1.3.7` | Le due immagini possono essere a versioni diverse |
|
||||
| Hostname API | `api.tenant.example.com` | Da sostituire col proprio dominio. Deve risolvere all'ingress controller |
|
||||
| Hostname admin | `admin.tenant.example.com` | Idem |
|
||||
| `LOGINMASTER_API_URL` | `https://api.loginmaster.it/1.3/` | API LoginMaster centrale |
|
||||
| `ADMIN_ALLOWED_ORIGIN` | `https://admin.loginmaster.it` | Origin del pannello LoginMaster globale (≠ pannello del tenant) |
|
||||
| Storage class | default cluster | Auto-rilevata |
|
||||
| Storage size | `20Gi` | Per ogni membro Mongo (totale: 3× questo) |
|
||||
| Mongo CPU/mem | 250m–2 / 1Gi–2Gi | Aumenta su carichi reali (Mongo cache cresce con la RAM) |
|
||||
| SMTP | `N` | Se sì, chiede host/port/secure/user/pass |
|
||||
| Registry user/password | — / — | Credenziali fornite per accedere a `hub.codebaker.it` |
|
||||
| Confirm | `N` | `Y` per procedere |
|
||||
|
||||
A fine deploy lo script salva `.deploy-credentials.txt` (chmod 600) con MASTER_ENCRYPTION_KEY, password root + applicativa Mongo, URI di connessione. **Spostalo in un password manager e cancellalo dal disco.**
|
||||
|
||||
### Re-run protection
|
||||
|
||||
`deploy.sh` aborta se nel namespace esiste già il Secret `mongodb-tenant-auth`: rigenerarlo causerebbe la perdita del replica set (nuovo keyfile → membri non si parlano più).
|
||||
|
||||
Per ridistribuire da zero: `kubectl delete namespace <ns>` e rilancia.
|
||||
Per aggiornare le immagini: usa `update.sh`.
|
||||
|
||||
## Aggiornare un tenant esistente
|
||||
|
||||
```bash
|
||||
./update.sh # interattivo, prefilla con i tag attuali del cluster
|
||||
./update.sh prod-1.3.9 # stesso tag per api e admin
|
||||
./update.sh prod-1.3.9 prod-1.3.8 # tag separati
|
||||
```
|
||||
|
||||
Lo script:
|
||||
1. Mostra le immagini attuali e chiede conferma per il rolling update di api e/o admin.
|
||||
2. Se l'update fallisce, stampa il comando `kubectl rollout undo` da copiare.
|
||||
3. In coda chiede se **riapplicare il ConfigMap** dal template — utile quando una nuova versione dell'API legge env var nuove. Riapplica `templates/03-configmap.yaml` usando i valori correnti dal cluster e fa `rollout restart` di tenant-api per propagarli.
|
||||
|
||||
### Rollback manuale
|
||||
|
||||
`kubectl` mantiene le ultime 10 revisioni dei Deployment:
|
||||
|
||||
```bash
|
||||
kubectl -n <ns> rollout history deployment/tenant-api
|
||||
kubectl -n <ns> rollout undo deployment/tenant-api
|
||||
kubectl -n <ns> rollout undo deployment/tenant-api --to-revision=N
|
||||
```
|
||||
|
||||
## Operazioni comuni
|
||||
|
||||
### Verificare lo stato
|
||||
|
||||
```bash
|
||||
kubectl -n <ns> get pods,ingress,certificate
|
||||
kubectl -n <ns> logs deploy/tenant-api --tail=100 -f
|
||||
```
|
||||
|
||||
### Stato replica set Mongo
|
||||
|
||||
```bash
|
||||
ROOT_PASS=$(kubectl -n <ns> get secret mongodb-tenant-auth -o jsonpath='{.data.root-password}' | base64 -d)
|
||||
kubectl -n <ns> exec mongodb-tenant-0 -c mongodb -- mongosh \
|
||||
--tls --tlsCAFile /etc/mongo-tls/ca.crt --tlsAllowInvalidHostnames --host 127.0.0.1 \
|
||||
-u admin -p "$ROOT_PASS" --authenticationDatabase admin \
|
||||
--eval 'rs.status()'
|
||||
```
|
||||
|
||||
### Backup / restore
|
||||
|
||||
Lo script non gestisce backup: usa `mongodump`/`mongorestore` con le credenziali applicative, oppure snapshot lato cloud sui PV Mongo.
|
||||
|
||||
### Scale orizzontale
|
||||
|
||||
```bash
|
||||
kubectl -n <ns> scale deployment/tenant-api --replicas=4
|
||||
```
|
||||
|
||||
Mongo è un replica set fisso a 3 membri (PDB `minAvailable: 2` lo presuppone): non scalare a meno di non rivedere PDB e quorum.
|
||||
|
||||
## Cleanup
|
||||
|
||||
```bash
|
||||
kubectl delete namespace <ns>
|
||||
```
|
||||
|
||||
> **Attenzione**: i PV con `reclaim policy: Retain` (es. `vsphere-csi`, `sbs-default-retain`) **sopravvivono** alla cancellazione del namespace e dei PVC. Vanno rimossi a mano (`kubectl delete pv <name>`) e i dischi sottostanti dal pannello del cloud provider per non lasciare costi residui.
|
||||
|
||||
## Sicurezza
|
||||
|
||||
- **TLS interno Mongo**: cert-manager emette CA self-signed (10 anni) e cert membri (1 anno). Mongo è in `requireTLS`. Niente traffico in chiaro nel namespace.
|
||||
- **Auth replica set**: keyFile (HMAC condiviso). I membri non si parlano se il keyfile non combacia.
|
||||
- **Auth client**: password generata, mai in argv di kubectl, mai in log audit (le `mongo_eval*` di `deploy.sh` autenticano via `db.auth()` su stdin).
|
||||
- **NetworkPolicy**: blocca traffico cross-namespace e intra-namespace non autorizzato.
|
||||
- **Pod Mongo non-root** (uid/gid 999) con `fsGroup` per ownership PVC.
|
||||
- **Secret K8s** (base64-encoded, non encrypted at rest se non configurato a livello cluster). MASTER_ENCRYPTION_KEY, password Mongo, password registry e SMTP risiedono qui — abilita encryption-at-rest a livello etcd se la compliance lo richiede.
|
||||
|
||||
### Limiti noti
|
||||
|
||||
- **Rinnovo cert TLS Mongo**: cert-manager rinnova il Secret automaticamente, ma **mongod non ricarica il cert a runtime**. Quando cert-manager riemette il Secret (default 30 giorni prima della scadenza, quindi ~ogni 11 mesi sui cert membri), serve `kubectl rollout restart statefulset/mongodb-tenant`. Il PDB garantisce che resti almeno 1 PRIMARY + 1 SECONDARY → zero downtime sul replica set, ma è una azione manuale.
|
||||
- **Warning `PodSecurity "restricted"`**: il pod Mongo richiede uno user-defined `runAsUser` e l'init come root, quindi triggera un warn (non enforce). Se la policy è in enforce mode, il deploy va rivisto (init come root non è compatibile con `restricted` puro).
|
||||
|
||||
## Note per cloud provider specifici
|
||||
|
||||
| Provider | Storage class default tipica | Reclaim default |
|
||||
|---|---|---|
|
||||
| AWS EKS | `gp3` (recenti) o `gp2` | `Delete` |
|
||||
| Azure AKS | `default` o `managed-csi` | `Delete` |
|
||||
| DigitalOcean DOKS | `do-block-storage` | `Delete` |
|
||||
| Scaleway Kapsule | `sbs-default-retain`, `scw-bssd` | varia |
|
||||
| GCP GKE | `standard-rwo`, `premium-rwo` | `Delete` |
|
||||
| vSphere | `vsphere-csi` | `Retain` |
|
||||
| Hetzner | `hcloud-volumes` | `Delete` |
|
||||
|
||||
**Reclaim `Delete`** = cancellando il namespace perdi i dati Mongo. Per produzione: scegliere SC con `Retain` o avere snapshot/backup esterni. Lo script non sovrascrive il reclaim della SC.
|
||||
|
||||
**Volume binding mode**: tutte le SC cloud moderne usano `WaitForFirstConsumer`, che fa partire i 3 pod Mongo correttamente in cluster multi-AZ. Le SC custom con `Immediate` su cluster multi-AZ rischiano cross-zone mount fail.
|
||||
|
||||
## Struttura del repo
|
||||
|
||||
```
|
||||
.
|
||||
├── deploy.sh # bootstrap di un nuovo tenant (idempotente: aborta su rerun)
|
||||
├── update.sh # rolling update + rollback hint + apply ConfigMap opzionale
|
||||
└── templates/
|
||||
├── 00-namespace.yaml
|
||||
├── 01-mongodb-tls.yaml # cert-manager Issuer + CA + cert membri
|
||||
├── 02-networkpolicy.yaml # isolamento intra-namespace
|
||||
├── 03-configmap.yaml # tenant-api-config
|
||||
├── 04-mongodb.yaml # StatefulSet 3 repliche, init container per keyfile/PEM tmpfs
|
||||
├── 05-tenant-api.yaml # Deployment 2 repliche + Service + PDB
|
||||
├── 06-tenant-admin.yaml # Deployment 1 replica + Service
|
||||
└── 07-ingress.yaml # ingress-nginx + Let's Encrypt
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Pod Mongo in CrashLoopBackOff
|
||||
Probabilmente cert-manager non ha emesso `mongodb-tenant-tls`. Verifica:
|
||||
```bash
|
||||
kubectl -n <ns> get certificate
|
||||
kubectl -n <ns> describe certificate mongodb-tenant-tls
|
||||
```
|
||||
|
||||
### Cert ingress `tenant-tls` resta `Ready: False`
|
||||
DNS dei due hostname non risolve all'IP dell'ingress controller, oppure la HTTP-01 challenge fallisce per altri motivi:
|
||||
```bash
|
||||
kubectl -n <ns> describe certificate tenant-tls
|
||||
kubectl -n <ns> get challenge
|
||||
```
|
||||
|
||||
### Replica set non elegge un PRIMARY
|
||||
Verifica connettività intra-namespace (NetworkPolicy può bloccare se la label `app: mongodb-tenant` viene cambiata) e che i 3 pod si vedano via DNS:
|
||||
```bash
|
||||
kubectl -n <ns> exec mongodb-tenant-0 -c mongodb -- nslookup mongodb-tenant-1.mongodb-tenant-headless
|
||||
```
|
||||
|
||||
### `ImagePullBackOff` su admin/api
|
||||
Tag inesistente sul registry. Verifica:
|
||||
```bash
|
||||
curl -u USER:PASS https://hub.codebaker.it/v2/loginmaster-tenant/admin-tenant/tags/list
|
||||
```
|
||||
Storicamente `api-tenant` esce più frequentemente di `admin-tenant`: usa tag separati con `./update.sh API_TAG ADMIN_TAG`.
|
||||
|
||||
### Errori PVC su provisioning
|
||||
Storage class non esistente o quota cloud esaurita:
|
||||
```bash
|
||||
kubectl get sc
|
||||
kubectl -n <ns> describe pvc mongodb-data-mongodb-tenant-0
|
||||
```
|
||||
Reference in New Issue
Block a user