Initial: Add all homelab manifests
This commit is contained in:
361
docs/09-metallb-pihole.md
Normal file
361
docs/09-metallb-pihole.md
Normal file
@@ -0,0 +1,361 @@
|
||||
# 09 · MetalLB + Pi-hole Installation
|
||||
|
||||
**Datum:** 2026-03-18
|
||||
|
||||
---
|
||||
|
||||
## Was wurde installiert
|
||||
|
||||
- **MetalLB** v0.14.x — Layer-2 Load Balancer für den k3s-Cluster
|
||||
- **Pi-hole** (pihole/pihole:latest) — DNS-Adblocker mit Web-UI
|
||||
|
||||
---
|
||||
|
||||
## Vorbereitungen
|
||||
|
||||
### Klipper (k3s ServiceLB) deaktiviert
|
||||
|
||||
k3s hat einen eingebauten Load-Balancer (Klipper/ServiceLB), der mit MetalLB kollidiert.
|
||||
Deaktiviert durch Ergänzung in `/etc/systemd/system/k3s.service`:
|
||||
|
||||
```
|
||||
'--disable=servicelb' \
|
||||
```
|
||||
|
||||
Dann: `systemctl daemon-reload && systemctl restart k3s`
|
||||
|
||||
### DNS-Fix auf rnk-wrk02
|
||||
|
||||
`systemd-resolved` Stub-Resolver (127.0.0.53) war defekt → containerd konnte keine Images pullen.
|
||||
|
||||
Fix: `/etc/resolv.conf` Symlink auf direkte DNS-Auflösung umgestellt:
|
||||
```bash
|
||||
ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
|
||||
```
|
||||
Nameserver ist nun direkt `192.168.11.1`.
|
||||
|
||||
---
|
||||
|
||||
## Ausgeführte Befehle
|
||||
|
||||
### MetalLB
|
||||
|
||||
```bash
|
||||
helm repo add metallb https://metallb.github.io/metallb
|
||||
helm repo update
|
||||
helm upgrade --install metallb metallb/metallb \
|
||||
--namespace metallb-system \
|
||||
--create-namespace \
|
||||
--wait
|
||||
kubectl apply -f ~/homelab/k8s/metallb/metallb-config.yaml
|
||||
```
|
||||
|
||||
### Pi-hole
|
||||
|
||||
```bash
|
||||
kubectl create namespace pihole
|
||||
kubectl apply -f ~/homelab/k8s/pihole/secret.yaml
|
||||
kubectl apply -k ~/homelab/k8s/pihole/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## IP-Adressen
|
||||
|
||||
| Service | IP | Protokoll |
|
||||
|---|---|---|
|
||||
| Traefik (Ingress) | 192.168.11.180 | TCP 80/443 |
|
||||
| Pi-hole DNS | 192.168.11.181 | TCP+UDP 53 |
|
||||
|
||||
MetalLB Pool: `192.168.11.180 – 192.168.11.199`
|
||||
|
||||
---
|
||||
|
||||
## Ergebnis
|
||||
|
||||
```
|
||||
NAME READY STATUS RESTARTS
|
||||
pod/pihole-f7d664fd-v65wn 1/1 Running 0
|
||||
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP
|
||||
pihole-dns-tcp LoadBalancer 10.43.200.43 192.168.11.181
|
||||
pihole-dns-udp LoadBalancer 10.43.65.201 192.168.11.181
|
||||
pihole-web ClusterIP 10.43.25.107 <none>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Zugang
|
||||
|
||||
- **Web-UI:** http://pihole.192.168.11.181.nip.io/admin
|
||||
- **DNS-Server:** `192.168.11.181` (Port 53 TCP+UDP)
|
||||
- **Passwort:** in Secret `pihole/pihole-secret`
|
||||
|
||||
---
|
||||
|
||||
## Dateien
|
||||
|
||||
```
|
||||
~/homelab/k8s/metallb/
|
||||
metallb-config.yaml # IPAddressPool + L2Advertisement
|
||||
|
||||
~/homelab/k8s/pihole/
|
||||
kustomization.yaml
|
||||
namespace.yaml
|
||||
secret.yaml # WEBPASSWORD (nicht ins Git!)
|
||||
deployment.yaml
|
||||
services.yaml # DNS TCP/UDP LoadBalancer + Web ClusterIP
|
||||
ingress.yaml # Traefik Ingress für Web-UI
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
- Router/DHCP auf DNS `192.168.11.181` umstellen
|
||||
- Pi-hole Blocklisten konfigurieren
|
||||
- ~~Ggf. Persistent Volume für `/etc/pihole` hinzufügen~~ → erledigt (siehe unten)
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting 2026-03-19
|
||||
|
||||
### Problem 1: Web-UI nicht erreichbar
|
||||
|
||||
**Symptom:** Browser kann `http://pihole.192.168.11.180.nip.io/admin/` nicht laden.
|
||||
|
||||
**Diagnose:**
|
||||
```bash
|
||||
kubectl get pods -n pihole # Pod läuft (Running)
|
||||
kubectl logs -n pihole deployment/pihole --tail=50 # FTL startet normal
|
||||
```
|
||||
|
||||
**Ursache:** Pi-hole v6 verwendet keinen lighttpd mehr — der Webserver ist direkt in FTL integriert (Port 80/443). `service lighttpd status` schlägt in v6 fehl.
|
||||
|
||||
**Ergebnis:** Web-UI war tatsächlich erreichbar, Ingress funktionierte korrekt (HTTP 302 → `/admin/login`).
|
||||
|
||||
---
|
||||
|
||||
### Problem 2: DNS antwortet nicht (192.168.11.181:53)
|
||||
|
||||
**Symptom:** `dig @192.168.11.181 google.com` läuft in Timeout. `nc -vzu 192.168.11.181 53` zeigt Port als offen.
|
||||
|
||||
**Diagnose:**
|
||||
```bash
|
||||
kubectl exec -n pihole <POD> -- grep -v "^#" /etc/pihole/pihole.toml | grep listeningMode
|
||||
# → listeningMode = "LOCAL"
|
||||
```
|
||||
|
||||
**Ursache:** Pi-hole v6 FTL startet standardmäßig mit `dns.listeningMode = "LOCAL"`. In Kubernetes kommt LoadBalancer-Traffic (192.168.11.0/24) nicht direkt vom Pod-Interface → dnsmasq verwirft alle Anfragen (logged: `ignoring query from non-local network`).
|
||||
|
||||
**Fix:** `DNSMASQ_LISTENING=all` in `deployment.yaml` hinzugefügt:
|
||||
```yaml
|
||||
env:
|
||||
- name: DNSMASQ_LISTENING
|
||||
value: "all"
|
||||
```
|
||||
|
||||
```bash
|
||||
kubectl patch deployment pihole -n pihole --type=json \
|
||||
-p='[{"op":"add","path":"/spec/template/spec/containers/0/env/-","value":{"name":"DNSMASQ_LISTENING","value":"all"}}]'
|
||||
kubectl rollout status deployment/pihole -n pihole
|
||||
```
|
||||
|
||||
**Verifikation:**
|
||||
```bash
|
||||
dig @192.168.11.181 google.com +short
|
||||
# → 142.250.201.78 ✓
|
||||
```
|
||||
|
||||
**Persistenz:** Fix ist in `~/homelab/k8s/pihole/deployment.yaml` und in etcd gespeichert — überlebt Pod-Neustarts.
|
||||
|
||||
---
|
||||
|
||||
### Problem 3: HTTPS-Zugriff auf Web-UI schlägt fehl (2026-03-19)
|
||||
|
||||
**Symptom:** `https://pihole.192.168.11.180.nip.io/admin` nicht erreichbar — Ingress war nur auf HTTP (Port 80) konfiguriert.
|
||||
|
||||
**Ursache:** `ingress.yaml` hatte `traefik.ingress.kubernetes.io/router.entrypoints: web` — nur HTTP-Entrypoint.
|
||||
|
||||
**Fix:** Ingress auf `websecure` umgestellt + TLS aktiviert (Traefik Default-Zertifikat):
|
||||
```yaml
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- pihole.192.168.11.180.nip.io
|
||||
```
|
||||
|
||||
```bash
|
||||
kubectl apply -f ~/homelab/k8s/pihole/ingress.yaml
|
||||
```
|
||||
|
||||
**Hinweis:** Browser zeigt Zertifikatswarnung (self-signed) — Ausnahme hinzufügen.
|
||||
|
||||
**Zugang:** `https://pihole.192.168.11.180.nip.io/admin`
|
||||
|
||||
---
|
||||
|
||||
### Problem 4: Claude Code ConnectionRefused wenn Pi-hole als DNS-Proxy gesetzt (2026-03-19)
|
||||
|
||||
**Symptom:** `API Error: Unable to connect to API (ConnectionRefused)` in Claude Code wenn `192.168.11.181` als DNS-Proxy in Omada gesetzt ist.
|
||||
|
||||
**Ursache:** Pi-hole gibt für `api.anthropic.com` auch eine IPv6-Adresse (`2607:6bc0::10`) zurück. Der Node hat kein funktionierendes globales IPv6 → Verbindungsversuch auf IPv6 schlägt mit ConnectionRefused fehl.
|
||||
|
||||
**Fix:** IPv4 in `/etc/gai.conf` bevorzugen:
|
||||
```bash
|
||||
echo "precedence ::ffff:0:0/96 100" >> /etc/gai.conf
|
||||
```
|
||||
|
||||
**Verifikation:**
|
||||
```bash
|
||||
curl --connect-timeout 5 https://api.anthropic.com
|
||||
# → Anthropic API erreichbar ✓
|
||||
```
|
||||
|
||||
**Persistenz:** `/etc/gai.conf` ist persistent auf `rnk-cp01`.
|
||||
|
||||
---
|
||||
|
||||
## Migration von altem Pi-hole (2026-03-19)
|
||||
|
||||
### Ausgangslage
|
||||
|
||||
Altes Pi-hole lief als Docker-Container (`Pihole-DoT-DoH`, Image `devzwf/pihole-dot-doh:latest`) auf Unraid (`192.168.11.124`), erreichbar unter `192.168.11.123`.
|
||||
|
||||
### Vorgehen: Teleporter-Backup via HTTP-API
|
||||
|
||||
Pi-hole v6 änderte die CLI-Syntax — `pihole -a -t` funktioniert nicht mehr. Shell-Umleitung (`>`) korrumpiert Binärdaten (null bytes). Lösung: Backup direkt über die REST-API laden.
|
||||
|
||||
```bash
|
||||
# 1. Authentifizieren und Backup als gültige ZIP herunterladen
|
||||
SID=$(curl -s -X POST http://192.168.11.123/api/auth \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"password":"<altes-passwort>"}' | grep -o '"sid":"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
curl -s -X GET http://192.168.11.123/api/teleporter \
|
||||
-H "sid: $SID" \
|
||||
-o /tmp/pihole-backup.zip
|
||||
|
||||
# 2. In neuen Pod kopieren und importieren
|
||||
POD=$(kubectl get pods -n pihole -l app=pihole -o jsonpath='{.items[0].metadata.name}')
|
||||
kubectl cp /tmp/pihole-backup.zip pihole/$POD:/tmp/pihole-backup.zip
|
||||
|
||||
SID=$(kubectl exec -n pihole $POD -- curl -s -X POST \
|
||||
http://localhost/api/auth \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"password":"<neues-passwort>"}' | grep -o '"sid":"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
kubectl exec -n pihole $POD -- curl -s -X POST \
|
||||
http://localhost/api/teleporter \
|
||||
-H "sid: $SID" \
|
||||
-F "file=@/tmp/pihole-backup.zip"
|
||||
```
|
||||
|
||||
**Importierte Objekte:** pihole.toml, gravity.db (adlists, domainlists, clients, groups), dhcp.leases, hosts
|
||||
|
||||
### Ingress-URL korrigiert
|
||||
|
||||
`nip.io`-Hostname enthält die eingebettete IP — `pihole.192.168.11.181.nip.io` löst auf die DNS-IP (Port 53) auf, nicht auf Traefik.
|
||||
|
||||
**Fix:** `ingress.yaml` Host auf Traefik-IP geändert:
|
||||
```
|
||||
pihole.192.168.11.180.nip.io → Traefik (192.168.11.180) → pihole-web Service
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DNS-Loop-Fix: Pod nutzt direkt 1.1.1.1 (2026-03-19)
|
||||
|
||||
### Problem
|
||||
|
||||
Pi-hole Pod nutzte CoreDNS (10.43.0.10) als Upstream → DNS-Loop, da CoreDNS intern Pi-hole anfragen kann.
|
||||
|
||||
### Fix: dnsPolicy auf None
|
||||
|
||||
```bash
|
||||
kubectl patch deployment pihole -n pihole --patch '
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
dnsPolicy: "None"
|
||||
dnsConfig:
|
||||
nameservers:
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
searches: []
|
||||
'
|
||||
```
|
||||
|
||||
**Hinweis:** Bei RWO-Volumes (Longhorn) kann der neue Pod beim Rollout auf einem anderen Node landen → `Multi-Attach error`. Lösung: alten Pod manuell löschen, danach ggf. stuck Pod ebenfalls löschen damit Scheduler neu plant.
|
||||
|
||||
```bash
|
||||
kubectl delete pod <alter-pod> -n pihole
|
||||
kubectl delete pod <stuck-pod> -n pihole # falls ContainerCreating auf falschem Node
|
||||
```
|
||||
|
||||
**Verifikation:**
|
||||
```bash
|
||||
kubectl exec -n pihole <POD> -- cat /etc/resolv.conf
|
||||
# nameserver 1.1.1.1
|
||||
# nameserver 8.8.8.8
|
||||
```
|
||||
|
||||
**Persistenz:** In `deployment.yaml` gespeichert + in etcd.
|
||||
|
||||
---
|
||||
|
||||
## Fix: Echte Client-IPs in Pi-hole (2026-03-19)
|
||||
|
||||
### Problem
|
||||
|
||||
Alle DNS-Queries in Pi-hole zeigten als Client `10.42.0.0` (Kubernetes Pod-Netzwerk) statt der echten Geräte-IPs (192.168.11.x).
|
||||
|
||||
### Ursache
|
||||
|
||||
kube-proxy führt SNAT (Source NAT) durch wenn Traffic über einen LoadBalancer-Service läuft — die Original-Source-IP wird durch die Pod-Netzwerk-IP ersetzt.
|
||||
|
||||
Zusätzlich: Der **Omada DNS-Proxy** leitet alle Client-Anfragen über sich selbst weiter → Pi-hole sieht nur die Router-IP als Client. Auch wenn der DNS-Proxy aktiv bleibt, muss kube-proxy die echte Router-IP durchreichen.
|
||||
|
||||
### Fix: externalTrafficPolicy: Local
|
||||
|
||||
```bash
|
||||
kubectl patch svc pihole-dns-tcp -n pihole -p '{"spec":{"externalTrafficPolicy":"Local"}}'
|
||||
kubectl patch svc pihole-dns-udp -n pihole -p '{"spec":{"externalTrafficPolicy":"Local"}}'
|
||||
```
|
||||
|
||||
In `~/homelab/k8s/pihole/services.yaml` für beide LoadBalancer-Services ergänzt:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
externalTrafficPolicy: Local
|
||||
```
|
||||
|
||||
**Verifikation:**
|
||||
```bash
|
||||
# Queries aus Unraid (192.168.11.124) testen
|
||||
dig @192.168.11.181 google.com +short
|
||||
# Pi-hole Query Log zeigt danach: 192.168.11.124 → google.com ✓
|
||||
```
|
||||
|
||||
**Hinweis:** `externalTrafficPolicy: Local` bedeutet, dass Traffic nur an Nodes weitergeleitet wird auf denen der Pod läuft. Ist der Pod auf einem anderen Node, gibt es keinen Fallback — dies ist bei Pi-hole gewünscht (kein NAT, echte IPs).
|
||||
|
||||
### Omada DNS-Proxy Konfiguration
|
||||
|
||||
- DNS-Proxy in Omada leitet Anfragen weiter an `192.168.11.181` (Pi-hole)
|
||||
- Clients erhalten die Router-IP als DNS → alle Queries gehen über den Proxy
|
||||
- Pi-hole sieht die Router-IP als Client (nicht einzelne Geräte) — **akzeptabler Kompromiss**
|
||||
- Secondary DNS (8.8.8.8) wurde entfernt damit Pi-hole Blocking nicht umgangen wird
|
||||
|
||||
### Finaler Status
|
||||
|
||||
| Komponente | Status |
|
||||
|---|---|
|
||||
| Pi-hole DNS `192.168.11.181:53` | ✓ Erreichbar |
|
||||
| Web-UI `https://pihole.192.168.11.180.nip.io/admin` | ✓ Erreichbar (HTTPS, self-signed) |
|
||||
| Blocking aktiv | ✓ |
|
||||
| Echte Client-IPs sichtbar | ✓ (nach externalTrafficPolicy: Local) |
|
||||
| Queries heute | ~40.000 |
|
||||
Reference in New Issue
Block a user