8.7 KiB
AGENTS.md
[!NOTE] This file describes the constraints and conventions for the
k8sdirectory, which contains deployments for the haumdaucher.de Kubernetes cluster.
Project Overview
This directory contains the Kubernetes manifests and Helm charts for a single-node Kubernetes cluster (Haumdaucher).
- Domain:
*.haumdaucher.de - Orchestration: Self-managed Kubernetes (single node).
- Ingress:
ingress-nginx - SSL:
cert-manager(LetsEncrypt)
Directory Structure
- Top-level folders: Each folder corresponds to a Kubernetes namespace.
- Example:
mailu/contains resources for themailunamespace.
- Example:
- Documentation:
README.mdis the authoritative source for deployment commands. Always check it before running commands.
Code Style & Conventions
- Helm Version: Helm 3 (
helm) is used. - Implementation Order: Top-down.
- Naming: Namespaces matches folder names.
- Formatting: Standard YAML conventions.
Security & Secrets
[!IMPORTANT] Git-Crypt is enforced. Do not touch encrypted files unless you have the key and know how to unlock them.
Encrypted File Patterns:
*.secret*.secret.yaml*.secret.values*.secret.sh
Remote Access
It is possible to execute commands on the remote Linux node for information retrieval or troubleshooting.
- Host:
haumdaucher.de - User:
moritz(local user) - Privileges: Use
sudoto gain root privileges.
[!CAUTION] SSH Identity Required: The agent cannot enter an SSH passphrase. If SSH commands fail with authentication errors, request the user to run
ssh-addlocally to load their identity.
Command Execution
You can execute commands remotely via SSH. This is useful for checking node-level resources (memory, disk, etc.) that kubectl might not expose directly.
Example: Check Memory Usage
ssh moritz@haumdaucher.de "free -h"
Example: Check Disk Usage (with sudo)
ssh -t moritz@haumdaucher.de "sudo df -h"
Note: The -t flag forces pseudo-terminal allocation, which is often required for sudo prompts.
Deployment Instructions
Always consult README.md first. Deployments vary between Helm charts and raw manifests.
Common Patterns
- Helm:
helm upgrade --install <release> <chart> -n <namespace> -f <values-file> - Kubectl:
kubectl apply -f <folder>/<file>.yaml
Post-Implementation Verification
[!IMPORTANT] Verification Workflow: After a new implementation or configuration change, always:
- Run
kubectl apply -f <file>.yaml.- Run
kubectl rollout restart deployment <deployment-name> -n <namespace>if applying a ConfigMap/Secret that a deployment depends on.- Wait for 30 seconds to allow pods to roll over.
- Check logs using
kubectl logs -n <namespace> -l <label> --tail=100.The agent must always ask the user for permission to execute this verification workflow after making changes.
Operational Tasks
- Cleanup Error Pods:
kubectl get pods | grep Error | cut -d' ' -f 1 | xargs kubectl delete pod
Ingress Configuration
Ingress resources must follow these strict conventions to work with the cluster's ingress controller (traefik) and certificate manager (cert-manager).
Annotations
All Ingress resources must include:
annotations:
kubernetes.io/ingress.class: "traefik"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
kubernetes.io/tls-acme: "true"
# Standard nginx tweaks (if using dual class) or traefik configurations
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
Hostnames & TLS
- Domain: Use a subdomain of
haumdaucher.deormoritzgraf.de. - TLS Secret Name: Must use hyphens instead of dots.
- Pattern:
<subdomain>-<domain>-<tld> - Example:
n8n.moritzgraf.de->n8n-moritzgraf-de
Example
spec:
ingressClassName: traefik
tls:
- hosts:
- n8n.moritzgraf.de
secretName: n8n-moritzgraf-de
rules:
- host: n8n.moritzgraf.de
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: n8n
port:
number: 5678
Storage / Persistence
The cluster uses OpenEBS for dynamic local storage provisioning.
PersistentVolumeClaims (PVC)
- Provisioner:
openebs.io/local(or similar, managed viaopenebs-hostpath). - StorageClass:
openebs-hostpath. - AccessMode: Typically
ReadWriteOnce(RWO) as it's local storage.
To request storage, simply create a PVC or configure Helm charts to use the default storage class (or explicitly openebs-hostpath).
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: my-data
spec:
storageClassName: openebs-hostpath
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
Deployment Constraints
- Resources: Always define
requestsandlimitsfor CPU and Memory to ensure fair scheduling on the single node. - Namespaces: Every application gets its own namespace.
- Secrets: Encrypt all secrets using
git-crypt.
Network Setup (VPN & Routing)
The Kubernetes cluster (Haumdaucher) is connected to the home network via a dedicated WireGuard VPN tunnel.
graph TD
subgraph "Kubernetes Cluster (haumdaucher.de - 136.243.23.215)"
subgraph "monitoring namespace"
Exporter[fritzbox-exporter Pod]
end
HostNet[Host Network Namespace]
WgPod[wireguard Pod hostNetwork: true]
end
subgraph "Home Network (192.168.10.0/24)"
FB[FRITZ!Box Gateway - 192.168.10.1]
Taupi[Taupi Fan Shelly - 192.168.10.168]
end
Exporter -- "Queries 192.168.10.1" --> HostNet
HostNet -- "Route: 192.168.10.0/24 via wg0" --> WgPod
WgPod -- "WireGuard VPN Tunnel (51820/UDP)" --> FB
FB -- "Accesses local subnet" --> Taupi
Components & Routing
- Home Network:
192.168.10.0/24. Contains the home devices (e.g., Shelly plug at192.168.10.168) and the gateway FRITZ!Box at192.168.10.1. - Kubernetes Cluster Network: Pod subnets (
10.233.64.0/24etc.) running on the remote public server (136.243.23.215). - WireGuard VPN Link (
wg0):- The
wireguardpod in thewireguardnamespace is configured withhostNetwork: true. This exposes thewg0interface directly in the host's root network namespace. - The cluster host has a static tunnel IP of
192.168.11.1/24onwg0. - The FRITZ!Box acts as the active peer client, initiating the connection to the host node at
136.243.23.215:51820. - The host routing table directs home network traffic over the tunnel:
192.168.10.0/24 dev wg0 scope link - Pods inside the cluster (like
fritzbox-exporter) query192.168.10.1. The traffic is forwarded by the host's default CNI routing to the host's network namespace, which matching the192.168.10.0/24subnet route and sends it overwg0to the FRITZ!Box.
- The
FritzBox Exporter Egress Reject Workaround (Option B)
The Problem:
When the fritzbox-exporter pod queries the FRITZ!Box TR-064 API at 192.168.10.1 from outside the home LAN subnet (using the transit WireGuard subnet IP 192.168.11.1), the FRITZ!Box's internal security policy redirects the client to its public WAN IP (e.g. 212.42.244.122) for portal login/authentication (login_sid.lua).
When the dynamic WAN IP of the FRITZ!Box changes or the WAN ports are closed, these queries to the public IP timeout. Because Go's default HTTP client doesn't enforce a timeout, the exporter hangs for Go/Linux's default TCP connection handshake timeout of 2 minutes (120 seconds). This blocks all other metric scrapes and causes Prometheus target scrape timeouts.
The Solution:
Rather than disabling CPU/RAM/temperature metrics (-nolua) or modifying the remote FRITZ!Box WireGuard subnet configuration, we block the exporter pod from reaching the public WAN IP.
- We add an
initContainerinfritzbox-exporter.yamlrunning withNET_ADMINprivileges. - The
initContainerinstallsiptablesand adds rules directly to the pod's shared network namespace. - It permits traffic to local private subnets (RFC1918 ranges) but rejects all TCP egress to public WAN IPs on port 80/443 with a
TCP RST(Reset) immediately. - This causes redirected authentication requests to fail in milliseconds instead of hanging for 2 minutes. The exporter immediately falls back to scrape TR-064 metrics (WAN sync speed, bytes) successfully, avoiding Prometheus scrapes timeouts.