Files
homelab/docs/05-cert-manager.md

6.7 KiB

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

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

kubectl create namespace cert-manager

3. cert-manager per Helm installieren (mit CRDs)

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

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):

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):

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):

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:

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

---
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
kubectl apply -f /home/mtkadmin/homelab/cert-manager-issuers.yaml

Status

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)

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:

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)