diff --git a/.gitignore b/.gitignore index a0519ac..3f9b089 100644 --- a/.gitignore +++ b/.gitignore @@ -24,5 +24,5 @@ npm-debug.log* # Laufzeit-Cache des Design-Hooks (impeccable) .impeccable/hook.cache.json -# Echte Kubernetes-Secrets (Vorlage: k8s/secrets.yaml.example) -k8s/secrets.yaml +# Echte Kubernetes-Secrets (Vorlage: k8s/02-secret.yaml.example) +k8s/02-secret.yaml diff --git a/k8s/00-namespace.yaml b/k8s/00-namespace.yaml index 1903cf7..11aea1e 100644 --- a/k8s/00-namespace.yaml +++ b/k8s/00-namespace.yaml @@ -1,6 +1,13 @@ +# Pod Security: "baseline" wird erzwungen (blockiert privilegierte Pods, +# hostPath, Host-Netzwerk usw.), laesst aber den HTTP-01-Solver-Pod von +# cert-manager zu. "restricted" wird gemeldet (warn/audit); die eigenen Pods +# dieses Namespaces erfuellen es bereits (non-root, keine Capabilities, +# seccomp RuntimeDefault). apiVersion: v1 kind: Namespace metadata: name: artista labels: - pod-security.kubernetes.io/enforce: restricted + pod-security.kubernetes.io/enforce: baseline + pod-security.kubernetes.io/warn: restricted + pod-security.kubernetes.io/audit: restricted diff --git a/k8s/01-config.yaml b/k8s/01-configmap.yaml similarity index 100% rename from k8s/01-config.yaml rename to k8s/01-configmap.yaml diff --git a/k8s/secrets.yaml.example b/k8s/02-secret.yaml.example similarity index 80% rename from k8s/secrets.yaml.example rename to k8s/02-secret.yaml.example index 3cbfcb2..e7a2b3a 100644 --- a/k8s/secrets.yaml.example +++ b/k8s/02-secret.yaml.example @@ -1,5 +1,5 @@ -# Kopieren nach k8s/secrets.yaml (ist in .gitignore), Werte eintragen, -# dann: kubectl apply -f k8s/secrets.yaml +# Kopieren nach k8s/02-secret.yaml (ist in .gitignore), Werte eintragen, +# dann: kubectl apply -f k8s/02-secret.yaml # Zufallswerte: openssl rand -base64 48 apiVersion: v1 kind: Secret @@ -17,7 +17,7 @@ stringData: SMTP_USER: "benutzer" SMTP_PASSWORD: "passwort" --- -# Zugang zur SMB-Freigabe (fuer 02-storage.yaml) +# Zugang zur SMB-Freigabe (fuer 03-storage.yaml) apiVersion: v1 kind: Secret metadata: diff --git a/k8s/02-storage.yaml b/k8s/03-storage.yaml similarity index 100% rename from k8s/02-storage.yaml rename to k8s/03-storage.yaml diff --git a/k8s/03-postgres.yaml b/k8s/04-postgres.yaml similarity index 100% rename from k8s/03-postgres.yaml rename to k8s/04-postgres.yaml diff --git a/k8s/04-app.yaml b/k8s/05-app-deployment.yaml similarity index 100% rename from k8s/04-app.yaml rename to k8s/05-app-deployment.yaml diff --git a/k8s/05-ingress.yaml b/k8s/06-ingress.yaml similarity index 94% rename from k8s/05-ingress.yaml rename to k8s/06-ingress.yaml index c0cacc1..882a489 100644 --- a/k8s/05-ingress.yaml +++ b/k8s/06-ingress.yaml @@ -1,5 +1,5 @@ # Ingress fuer Traefik + cert-manager (gleiches Muster wie Wabenhain). -# ANPASSEN: Domain (2x, auch APP_URL in 01-config.yaml). +# ANPASSEN: Domain (2x, auch APP_URL in 01-configmap.yaml). apiVersion: networking.k8s.io/v1 kind: Ingress metadata: diff --git a/k8s/07-networkpolicy.yaml b/k8s/07-networkpolicy.yaml deleted file mode 100644 index 171392d..0000000 --- a/k8s/07-networkpolicy.yaml +++ /dev/null @@ -1,47 +0,0 @@ -# Datenbank nur fuer App und Worker erreichbar; App nur ueber Traefik. -# Traefik wird an Namespace (traefik oder kube-system) UND Pod-Label erkannt -# (eigene Installation "traefik" oder RKE2-Paket "rke2-traefik"). Laeuft -# Traefik in einem anderen Namespace: dessen Namen unten ergaenzen. -# Pruefen: kubectl get pods -A -l 'app.kubernetes.io/name in (traefik,rke2-traefik)' -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: artista-modelcamp-postgres - namespace: artista -spec: - podSelector: - matchLabels: - app: artista-modelcamp-postgres - ingress: - - from: - - podSelector: - matchLabels: - db-client: "true" - ports: - - port: 5432 ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: artista-modelcamp-app - namespace: artista -spec: - podSelector: - matchLabels: - app: artista-modelcamp-app - ingress: - - from: - # Namespace UND Pod-Label muessen passen: ein Label allein koennte - # jeder setzen, der irgendwo Pods starten darf. - - namespaceSelector: - matchExpressions: - - key: kubernetes.io/metadata.name - operator: In - values: [traefik, kube-system] - podSelector: - matchExpressions: - - key: app.kubernetes.io/name - operator: In - values: [traefik, rke2-traefik] - ports: - - port: 3000 diff --git a/k8s/06-worker.yaml b/k8s/07-worker-cronjob.yaml similarity index 100% rename from k8s/06-worker.yaml rename to k8s/07-worker-cronjob.yaml diff --git a/k8s/08-networkpolicy.yaml b/k8s/08-networkpolicy.yaml new file mode 100644 index 0000000..1903d73 --- /dev/null +++ b/k8s/08-networkpolicy.yaml @@ -0,0 +1,23 @@ +# Die Datenbank ist nur fuer Pods mit dem Label db-client=true erreichbar +# (App inkl. Migration, Worker, Admin-Anlage). Beruehrt weder Traefik noch +# cert-manager. +# +# Bewusst KEINE Policy vor der App (wie bei Wabenhain/ApiCore): laeuft +# Traefik im Host-Netzwerk, wuerde sie die Website aussperren. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: artista-modelcamp-postgres + namespace: artista +spec: + podSelector: + matchLabels: + app: artista-modelcamp-postgres + policyTypes: ["Ingress"] + ingress: + - from: + - podSelector: + matchLabels: + db-client: "true" + ports: + - port: 5432 diff --git a/k8s/README.md b/k8s/README.md index 5ccd46f..bda6031 100644 --- a/k8s/README.md +++ b/k8s/README.md @@ -10,7 +10,7 @@ Prüfen: ```bash kubectl get ingressclass kubectl get clusterissuer letsencrypt-prod -kubectl get pods -A -l 'app.kubernetes.io/name in (traefik,rke2-traefik)' +kubectl get csidrivers | grep smb ``` ## Einmalige Vorbereitung @@ -21,26 +21,40 @@ kubectl get pods -A -l 'app.kubernetes.io/name in (traefik,rke2-traefik)' helm repo add csi-driver-smb https://raw.githubusercontent.com/kubernetes-csi/csi-driver-smb/master/charts helm install csi-driver-smb csi-driver-smb/csi-driver-smb -n kube-system ``` -3. **NAS:** Die Ordner `artista/modelcamp-postgres` (leer) und `artista/modelcamp-media` anlegen und SMB-Verschlüsselung erlauben. `nas.example.lan` in `02-storage.yaml` anpassen. -4. **Domain:** `modelcamp.example.org` in `01-config.yaml` (APP_URL) und `05-ingress.yaml` (2×) ersetzen. Der DNS-Eintrag muss auf den Cluster zeigen, sonst stellt cert-manager kein Zertifikat aus. -5. **Secrets:** `cp k8s/secrets.yaml.example k8s/secrets.yaml` und ausfüllen. Die Datei steht in `.gitignore`, bitte **nie** mit echten Werten committen. +3. **NAS:** Die Ordner `artista/modelcamp-postgres` (leer) und `artista/modelcamp-media` anlegen und SMB-Verschlüsselung erlauben. `nas.example.lan` in `03-storage.yaml` anpassen. +4. **Domain:** `modelcamp.example.org` in `01-configmap.yaml` (APP_URL) und `06-ingress.yaml` (2×) ersetzen. Der DNS-Eintrag muss auf den Cluster zeigen, sonst stellt cert-manager kein Zertifikat aus. +5. **Secrets:** `cp k8s/02-secret.yaml.example k8s/02-secret.yaml` und ausfüllen. Die Datei steht in `.gitignore`, bitte **nie** mit echten Werten committen. -## Deployment +## Deployment (auch für einen kompletten Neuaufbau) ```bash kubectl apply -f k8s/00-namespace.yaml -kubectl apply -f k8s/secrets.yaml -kubectl apply -f k8s/01-config.yaml -kubectl apply -f k8s/02-storage.yaml -kubectl apply -f k8s/03-postgres.yaml +kubectl apply -f k8s/01-configmap.yaml +kubectl apply -f k8s/02-secret.yaml +kubectl apply -f k8s/03-storage.yaml +kubectl apply -f k8s/04-postgres.yaml kubectl wait --for=condition=ready pod -l app=artista-modelcamp-postgres -n artista --timeout=180s -kubectl apply -f k8s/04-app.yaml -kubectl apply -f k8s/05-ingress.yaml -kubectl apply -f k8s/06-worker.yaml -kubectl apply -f k8s/07-networkpolicy.yaml +kubectl apply -f k8s/05-app-deployment.yaml +kubectl apply -f k8s/06-ingress.yaml +kubectl apply -f k8s/07-worker-cronjob.yaml +kubectl apply -f k8s/08-networkpolicy.yaml ``` -Die Migration läuft automatisch als initContainer vor dem App-Start. Den Fortschritt zeigt `kubectl rollout status deployment/artista-modelcamp-app -n artista`, das Zertifikat `kubectl get certificate -n artista`. +Die Migration läuft automatisch als initContainer vor dem App-Start. Den Stand zeigen: + +```bash +kubectl rollout status deployment/artista-modelcamp-app -n artista +kubectl get certificate -n artista # READY muss True werden (meist < 2 Minuten) +``` + +**Neuaufbau mit `kubectl delete namespace artista`:** Die PersistentVolumes (`artista-modelcamp-postgres`, `artista-modelcamp-media`) gehören keinem Namespace und bleiben auf `Released` stehen. Die Daten auf dem NAS bleiben erhalten (`Retain`). Damit die neuen Claims sie wieder binden können, vor dem erneuten Anlegen: + +```bash +kubectl patch pv artista-modelcamp-postgres -p '{"spec":{"claimRef":null}}' +kubectl patch pv artista-modelcamp-media -p '{"spec":{"claimRef":null}}' +``` + +Soll die Datenbank wirklich leer neu starten, den Ordner `modelcamp-postgres` auf dem NAS vorher leeren. ## Erstes Admin-Konto @@ -61,7 +75,9 @@ kubectl rollout restart deployment/artista-modelcamp-app -n artista ## Fehlersuche ```bash -kubectl -n artista logs deploy/artista-modelcamp-app -c migrate # Migration -kubectl -n artista logs deploy/artista-modelcamp-app -c app # App -kubectl -n artista describe certificate artista-modelcamp-tls # Zertifikat +kubectl -n artista get certificate,certificaterequest,order,challenge # Zertifikat +kubectl -n artista describe challenge +kubectl -n artista logs deploy/artista-modelcamp-app -c migrate # Migration +kubectl -n artista logs deploy/artista-modelcamp-app -c app # App +kubectl -n artista get events --sort-by=.lastTimestamp | tail -20 ```