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

298 lines
6.7 KiB
Markdown

# 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`)