298 lines
6.7 KiB
Markdown
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`)
|