# 05 — cert-manager **Datum:** 2026-03-17 **Version:** cert-manager v1.20.0 (Helm Chart v1.20.0) **Namespace:** cert-manager --- ## Übersicht cert-manager automatisiert die Ausstellung und Erneuerung von TLS-Zertifikaten in Kubernetes. Es integriert sich mit Let's Encrypt via ACME und nutzt Traefik als Ingress-Controller für die HTTP-01-Challenge. --- ## Installation ### 1. Jetstack Helm Repository hinzufügen ```bash helm repo add jetstack https://charts.jetstack.io helm repo update ``` Ausgabe: ``` "jetstack" has been added to your repositories ...Successfully got an update from the "jetstack" chart repository ``` ### 2. Namespace erstellen ```bash kubectl create namespace cert-manager ``` ### 3. cert-manager per Helm installieren (mit CRDs) ```bash helm install cert-manager jetstack/cert-manager \ --namespace cert-manager \ --version v1.20.0 \ --set crds.enabled=true \ --wait \ --timeout 5m ``` Ausgabe: ``` NAME: cert-manager LAST DEPLOYED: Tue Mar 17 09:38:11 2026 NAMESPACE: cert-manager STATUS: deployed REVISION: 1 cert-manager v1.20.0 has been deployed successfully! ``` --- ## Pod-Status ```bash kubectl get pods -n cert-manager ``` ``` NAME READY STATUS RESTARTS AGE cert-manager-cainjector-68c64dbb9b-xvbcr 1/1 Running 0 ... cert-manager-f5cd6c77c-lfpg7 1/1 Running 0 ... cert-manager-webhook-54d5d87669-2pgsj 1/1 Running 0 ... ``` Alle 3 Pods laufen stabil. --- ## DNS-Problem: ISP-Wildcard-Interception ### Symptom Die ClusterIssuers zeigten nach der Erstellung dauerhaft `Ready: False`: ``` Failed to register ACME account: Get "https://acme-v02.api.letsencrypt.org/directory": remote error: tls: unrecognized name ``` ### Ursache In der Netplan-Konfiguration aller Nodes war die Search-Domain `int.befast.at` statisch eingetragen. Der kubelet kopiert diese aus dem Host-`/etc/resolv.conf` direkt in alle Pods: ``` # /etc/resolv.conf auf dem Host (systemd-resolved) — vorher nameserver 127.0.0.53 search int.befast.at ``` Pods erhielten dadurch folgendes `/etc/resolv.conf`: ``` search cert-manager.svc.cluster.local svc.cluster.local cluster.local int.befast.at nameserver 10.43.0.10 options ndots:5 ``` Mit `ndots:5` wird `acme-v02.api.letsencrypt.org` (3 Punkte) zuerst als `acme-v02.api.letsencrypt.org.int.befast.at` aufgelöst — und der ISP-DNS (`84.191.81.126`) antwortete mit einem Wildcard-Eintrag, dessen TLS-Zertifikat den SNI-Namen `acme-v02.api.letsencrypt.org` nicht enthält. ### Lösung: Search-Domain aus Netplan entfernen (alle 3 Nodes) Die `search:`-Sektion wurde aus `/etc/netplan/99-br0.yaml` auf allen Nodes entfernt. Backups liegen als `/etc/netplan/99-br0.yaml.bak` auf jedem Node. **rnk-cp01 (192.168.11.170):** ```bash sudo cp /etc/netplan/99-br0.yaml /etc/netplan/99-br0.yaml.bak # search: / - int.befast.at Zeilen entfernt sudo netplan apply ``` **rnk-wrk01 + rnk-wrk02 (via SSH):** ```bash for IP in 192.168.11.171 192.168.11.172; do ssh mtkadmin@$IP " sudo cp /etc/netplan/99-br0.yaml /etc/netplan/99-br0.yaml.bak sudo sed -i '/^ search:/,/^ - int.befast.at/d' /etc/netplan/99-br0.yaml sudo netplan apply " done ``` **`/etc/netplan/99-br0.yaml` nach der Änderung (Beispiel rnk-cp01):** ```yaml network: version: 2 ethernets: enx1065308999be: dhcp4: no dhcp6: no bridges: br0: interfaces: [enx1065308999be] addresses: - "192.168.11.170/24" nameservers: addresses: - 192.168.11.1 routes: - to: "default" via: "192.168.11.1" parameters: stp: false forward-delay: 0 ``` ### Ergebnis ``` # /etc/resolv.conf auf allen Hosts — nachher nameserver 127.0.0.53 search . ``` Pods erhalten jetzt saubere DNS-Konfiguration ohne ISP-Domain: ``` search default.svc.cluster.local svc.cluster.local cluster.local nameserver 10.43.0.10 options ndots:5 ``` Überprüfung DNS-Auflösung aus einem Pod: ```bash kubectl run dns-test --image=busybox:1.28 --restart=Never -- sleep 30 kubectl exec dns-test -- nslookup acme-v02.api.letsencrypt.org # Name: acme-v02.api.letsencrypt.org → 172.65.32.248 ✓ kubectl delete pod dns-test --grace-period=0 ``` > **Hinweis:** Zusätzlich wurde CoreDNS auf `forward . 8.8.8.8 8.8.4.4 1.1.1.1` > konfiguriert (statt `forward . /etc/resolv.conf`), damit keine zukünftigen > Host-DNS-Änderungen auf Pods durchschlagen. --- ## ClusterIssuer-Konfiguration Manifest: `/home/mtkadmin/homelab/cert-manager-issuers.yaml` ```yaml --- apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-staging spec: acme: server: https://acme-staging-v02.api.letsencrypt.org/directory email: homelab@befast.at privateKeySecretRef: name: letsencrypt-staging-account-key solvers: - http01: ingress: ingressClassName: traefik --- apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-production spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: homelab@befast.at privateKeySecretRef: name: letsencrypt-production-account-key solvers: - http01: ingress: ingressClassName: traefik ``` ```bash kubectl apply -f /home/mtkadmin/homelab/cert-manager-issuers.yaml ``` ### Status ```bash kubectl get clusterissuer -o wide ``` ``` NAME READY STATUS letsencrypt-production True The ACME account was registered with the ACME server letsencrypt-staging True The ACME account was registered with the ACME server ``` --- ## Verwendung: Zertifikat für Ingress ### Staging zuerst testen (kein Rate-Limit) ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: my-app namespace: default annotations: cert-manager.io/cluster-issuer: letsencrypt-staging spec: ingressClassName: traefik tls: - hosts: - my-app.example.com secretName: my-app-tls-staging rules: - host: my-app.example.com http: paths: - path: / pathType: Prefix backend: service: name: my-app-svc port: number: 80 ``` ### Auf Production wechseln Annotation ändern auf: ```yaml cert-manager.io/cluster-issuer: letsencrypt-production ``` --- ## Helm Release Info ``` NAME NAMESPACE REVISION STATUS CHART APP VERSION cert-manager cert-manager 1 deployed cert-manager-v1.20.0 v1.20.0 ``` --- ## Nächste Schritte - [ ] Ersten echten Ingress mit TLS-Zertifikat testen (Staging) - [ ] DNS-Eintrag für öffentliche Domain auf Cluster-IP setzen - [ ] Nach erfolgreichem Staging-Test auf Production-Issuer umstellen - [ ] Zertifikatserneuerung überwachen (`kubectl get certificate -A`)