Files
LOGINMASTER-DeploymentScript/templates/04-mongodb.yaml
T
Luca 468d4562c7 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.
2026-05-06 11:44:04 +02:00

199 lines
6.7 KiB
YAML

apiVersion: v1
kind: Service
metadata:
name: mongodb-tenant-headless
namespace: ${NAMESPACE}
spec:
clusterIP: None
selector:
app: mongodb-tenant
ports:
- port: 27017
targetPort: 27017
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongodb-tenant
namespace: ${NAMESPACE}
spec:
serviceName: mongodb-tenant-headless
replicas: 3
podManagementPolicy: Parallel
selector:
matchLabels:
app: mongodb-tenant
template:
metadata:
labels:
app: mongodb-tenant
spec:
imagePullSecrets:
- name: registry-codebaker
securityContext:
# fsGroup garantisce ownership corretta sul PVC (mongo data) per uid 999.
# runAsUser/Group: il container principale gira come mongodb (uid 999),
# non root. L'init invece deve essere root per poter chmod 400 + chown.
runAsUser: 999
runAsGroup: 999
fsGroup: 999
initContainers:
# Prepara keyfile e PEM nel tmpfs /run/mongo con owner=mongodb mode=0400.
# Mongod richiede esattamente 0400/0600 owned-by-self e quel mode non si
# può ottenere via secret defaultMode senza far girare il container come
# root. Quindi: init come root, main come 999.
- name: init-tls
image: mongo:6.0
securityContext:
runAsUser: 0
runAsGroup: 0
command:
- /bin/sh
- -c
- >-
cp /etc/secrets/keyfile /run/mongo/mongodb-keyfile &&
cat /etc/mongo-tls/tls.crt /etc/mongo-tls/tls.key > /run/mongo/mongo-server.pem &&
chown 999:999 /run/mongo/mongodb-keyfile /run/mongo/mongo-server.pem &&
chmod 400 /run/mongo/mongodb-keyfile /run/mongo/mongo-server.pem
volumeMounts:
- name: mongo-runtime
mountPath: /run/mongo
- name: mongo-keyfile
mountPath: /etc/secrets
readOnly: true
- name: mongo-tls
mountPath: /etc/mongo-tls
readOnly: true
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: mongodb-tenant
topologyKey: kubernetes.io/hostname
containers:
- name: mongodb
image: mongo:6.0
# I file runtime (keyfile + PEM cert+key) sono già stati preparati dall'
# initContainer in /run/mongo con owner mongodb:mongodb mode 0400.
command:
- mongod
- --bind_ip_all
- --replSet
- rs0
- --auth
- --keyFile
- /run/mongo/mongodb-keyfile
- --tlsMode
- requireTLS
- --tlsCertificateKeyFile
- /run/mongo/mongo-server.pem
- --tlsCAFile
- /etc/mongo-tls/ca.crt
# Senza questo flag, --tlsCAFile mette mongod in mTLS e rifiuta ogni
# client che non presenta un cert. I membri del replica set si autenticano
# via keyFile (clusterAuthMode keyFile, default), non x.509: quindi non
# serve mTLS. I client (API, probe, mongosh) presentano solo password.
- --tlsAllowConnectionsWithoutCertificates
ports:
- containerPort: 27017
volumeMounts:
- name: mongodb-data
mountPath: /data/db
- name: mongo-runtime
mountPath: /run/mongo
readOnly: true
- name: mongo-tls
mountPath: /etc/mongo-tls
readOnly: true
resources:
requests:
cpu: "${MONGO_CPU_REQUEST}"
memory: "${MONGO_MEM_REQUEST}"
limits:
cpu: "${MONGO_CPU_LIMIT}"
memory: "${MONGO_MEM_LIMIT}"
# quit(N) propaga N come exit code: probe fallisce solo se ping.ok != 1.
# Connessione su 127.0.0.1 con --tlsAllowInvalidHostnames perché il cert
# non include localhost nei SAN (la CA però viene validata).
livenessProbe:
exec:
command:
- mongosh
- --quiet
- --tls
- --tlsCAFile
- /etc/mongo-tls/ca.crt
- --tlsAllowInvalidHostnames
- --host
- "127.0.0.1"
- --eval
- "quit(db.adminCommand('ping').ok===1?0:1)"
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
exec:
command:
- mongosh
- --quiet
- --tls
- --tlsCAFile
- /etc/mongo-tls/ca.crt
- --tlsAllowInvalidHostnames
- --host
- "127.0.0.1"
- --eval
- "quit(db.adminCommand('ping').ok===1?0:1)"
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
volumes:
- name: mongo-runtime
emptyDir:
medium: Memory
sizeLimit: 16Mi
- name: mongo-keyfile
secret:
secretName: mongodb-tenant-auth
items:
- key: keyfile
path: keyfile
# Con fsGroup=999 il file è root:mongodb 0440 — leggibile solo dal gruppo
# mongodb (uid/gid 999), non world-readable. Il container poi lo copia in
# /data/db/ e fa chmod 400 perché mongod richiede mode 0400/0600 owner-only.
defaultMode: 0440
- name: mongo-tls
secret:
# Secret popolato da cert-manager (vedi templates/01-mongodb-tls.yaml).
# Contiene tls.crt, tls.key, ca.crt — leggibili dal gruppo 999 via fsGroup.
secretName: mongodb-tenant-tls
defaultMode: 0440
volumeClaimTemplates:
- metadata:
name: mongodb-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "${STORAGE_CLASS}"
resources:
requests:
storage: ${STORAGE_SIZE}
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: mongodb-tenant-pdb
namespace: ${NAMESPACE}
spec:
# Il replica set tollera la perdita di 1 membro (quorum = 2/3).
# minAvailable: 2 evita che drain/voluntary disruption tolga il quorum.
minAvailable: 2
selector:
matchLabels:
app: mongodb-tenant
# NB: rs.initiate e la creazione degli utenti root/app sono eseguite da
# `deploy.sh` via `kubectl exec` nel pod mongodb-tenant-0 (sfrutta la
# "localhost exception" di Mongo per creare il primo utente senza auth).