Merge feature/onboard-pihole (first WS-APPS onboarding)

This commit is contained in:
jakman85 committed 2026-08-24 23:27:22 -05:00
commit e022a2aea4
3 files changed
+203 -8

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
---
# Onboarded services (WS-APPS). Each app is rendered from its catalog/ ServiceDefinition by
# catalog/render.py and committed here; the `apps` Flux Kustomization (clusters/base/apps.yaml,
# dependsOn platform-config) reconciles this directory. Image-automation CRs are omitted for now
# (--no-image-automation) until the Flux image controllers are installed.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- pihole.yaml
+174
View File
@@ -0,0 +1,174 @@
---
# Namespace (trust zone; default-deny + policies apply)
apiVersion: v1
kind: Namespace
metadata:
name: pihole
labels:
homelab.keithjackson.dev/app-namespace: 'true'
gateway-access: shared
annotations:
homelab.keithjackson.dev/backup-policy: daily-offcluster
---
# Default-deny baseline (R-NET-01): deny all, allow only DNS
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: pihole
labels:
homelab.keithjackson.dev/baseline: default-deny
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress: []
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
# PVC pihole-config (R-STATE)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pihole-config
namespace: pihole
labels:
app.kubernetes.io/name: pihole
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 2Gi
---
# Deployment (R-PLAT-02)
apiVersion: apps/v1
kind: Deployment
metadata:
name: pihole
namespace: pihole
labels:
app.kubernetes.io/name: pihole
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: pihole
template:
metadata:
labels:
app.kubernetes.io/name: pihole
homelab.keithjackson.dev/egress-class: internet
homelab.keithjackson.dev/auth-mode: forward-auth
annotations:
homelab.keithjackson.dev/auth-mode: forward-auth
homelab.keithjackson.dev/auth-groups: platform-admins
spec:
containers:
- name: pihole
image: pihole/pihole
ports:
- containerPort: 80
volumeMounts:
- name: config
mountPath: /data/config
volumes:
- name: config
persistentVolumeClaim:
claimName: pihole-config
---
# Service
apiVersion: v1
kind: Service
metadata:
name: pihole
namespace: pihole
labels:
app.kubernetes.io/name: pihole
spec:
selector:
app.kubernetes.io/name: pihole
ports:
- name: port-80
port: 80
targetPort: 80
---
# HTTPRoute pihole-80 (R-TLS/R-DNS)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: pihole-80
namespace: pihole
labels:
app.kubernetes.io/name: pihole
annotations:
homelab.keithjackson.dev/auth-mode: forward-auth
homelab.keithjackson.dev/auth-groups: platform-admins
spec:
parentRefs:
- name: shared
namespace: gateway
sectionName: https
hostnames:
- pihole.home.keithjackson.dev
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: pihole
port: 80
---
# ServiceMonitor (R-OBS-01)
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: pihole
namespace: pihole
labels:
release: kube-prometheus-stack
app.kubernetes.io/name: pihole
spec:
selector:
matchLabels:
app.kubernetes.io/name: pihole
namespaceSelector:
matchNames:
- pihole
endpoints:
- port: metrics
interval: 30s
---
# PrometheusRule SLI alerts (R-OBS-01)
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: pihole-slis
namespace: pihole
labels:
release: kube-prometheus-stack
app.kubernetes.io/name: pihole
spec:
groups:
- name: pihole.slis
rules:
- alert: pihole-down
expr: up{job="pihole"} == 0
for: 5m
labels:
severity: critical
service: pihole
annotations:
summary: pihole is down
description: 'pihole has been unreachable for 5m (SLI: up).'
+20 -8
View File
@@ -580,8 +580,14 @@ def r_virtualmachine(s: dict) -> dict:
# =============================================================================
# Top-level render + emit
# =============================================================================
def render(sd: dict) -> list:
"""Return an ordered list of (header_comment, resource_dict) for a ServiceDefinition."""
def render(sd: dict, image_automation: bool = True) -> list:
"""Return an ordered list of (header_comment, resource_dict) for a ServiceDefinition.
image_automation=False omits the Flux ImageRepository/ImagePolicy CRs — used when
onboarding apps before the Flux image-reflector/-automation controllers are installed
(their CRDs would otherwise be missing and the apps Kustomization would fail). The L1
render goldens use the default (True), so their expected output is unchanged.
"""
s = apply_defaults(sd)
if s["kind"] not in ("container", "vm"):
raise ValueError(f"unknown kind: {s['kind']!r}")
@@ -620,8 +626,9 @@ def render(sd: dict) -> list:
if pr:
docs.append(("PrometheusRule SLI alerts (R-OBS-01)", pr))
for res in r_image_automation(s):
docs.append((f"Flux {res['kind']} (R-UPD)", res))
if image_automation:
for res in r_image_automation(s):
docs.append((f"Flux {res['kind']} (R-UPD)", res))
return docs
@@ -645,8 +652,8 @@ def load_sd(path: Path) -> dict:
return yaml.safe_load(fh)
def render_file(path: Path) -> str:
return emit(render(load_sd(path)))
def render_file(path: Path, image_automation: bool = True) -> str:
return emit(render(load_sd(path), image_automation=image_automation))
# =============================================================================
@@ -745,16 +752,21 @@ def main(argv=None) -> int:
ap.add_argument("--all", metavar="DIR", help="Render every *.yaml ServiceDefinition in DIR")
ap.add_argument("-o", "--out-dir", help="With --all: write <name>.yaml files into this dir")
ap.add_argument("--selftest", action="store_true", help="Run the built-in self-test")
ap.add_argument("--no-image-automation", action="store_true",
help="Omit Flux ImageRepository/ImagePolicy CRs (for onboarding before the "
"image-reflector/-automation controllers are installed)")
args = ap.parse_args(argv)
if args.selftest:
return _selftest()
image_automation = not args.no_image_automation
if args.all:
src = Path(args.all)
files = sorted(p for p in src.glob("*.yaml") if p.name != "render.py")
for f in files:
text = render_file(f)
text = render_file(f, image_automation=image_automation)
if args.out_dir:
outdir = Path(args.out_dir)
outdir.mkdir(parents=True, exist_ok=True)
@@ -766,7 +778,7 @@ def main(argv=None) -> int:
if not args.input:
ap.error("provide a ServiceDefinition file, --all DIR, or --selftest")
sys.stdout.write(render_file(Path(args.input)))
sys.stdout.write(render_file(Path(args.input), image_automation=image_automation))
return 0