Due bug che bloccavano deploy.sh su tenant con provisioning PVC lento: 1. Race DNS: lo step 8 aspettava solo mongodb-tenant-0 prima di rs.initiate. Se i pod 1/2 erano ancora in provisioning, i loro record DNS nel service headless non esistevano e rs.initiate falliva il quorum check con "Could not find address for mongodb-tenant-1...". Ora si attende che tutti e 3 i pod siano Ready. 2. mongosh "connection <monitor> closed": con --host 127.0.0.1, appena il replica set è inizializzato mongosh passa in modalità topology e prova a monitorare il PRIMARY via hostname annunciato, chiudendo la connessione. Sostituito con URI directConnection=true, compatibile con la localhost exception per la creazione del primo utente. Allineati gli hint diagnostici in deploy.sh e l'esempio rs.status() nel README al nuovo form directConnection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
10 KiB
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
./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
./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:
- Mostra le immagini attuali e chiede conferma per il rolling update di api e/o admin.
- Se l'update fallisce, stampa il comando
kubectl rollout undoda copiare. - In coda chiede se riapplicare il ConfigMap dal template — utile quando una nuova versione dell'API legge env var nuove. Riapplica
templates/03-configmap.yamlusando i valori correnti dal cluster e farollout restartdi tenant-api per propagarli.
Rollback manuale
kubectl mantiene le ultime 10 revisioni dei Deployment:
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
kubectl -n <ns> get pods,ingress,certificate
kubectl -n <ns> logs deploy/tenant-api --tail=100 -f
Stato replica set Mongo
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 --quiet \
'mongodb://127.0.0.1:27017/?directConnection=true&tls=true&tlsCAFile=/etc/mongo-tls/ca.crt&tlsAllowInvalidHostnames=true' \
-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
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
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*dideploy.shautenticano viadb.auth()su stdin). - NetworkPolicy: blocca traffico cross-namespace e intra-namespace non autorizzato.
- Pod Mongo non-root (uid/gid 999) con
fsGroupper 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-definedrunAsUsere 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 conrestrictedpuro).
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:
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:
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:
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:
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:
kubectl get sc
kubectl -n <ns> describe pvc mongodb-data-mongodb-tenant-0