# Networking: Ingress, TLS & Service Discovery **Ingress Controller:** `nginx-ingress` (Nginx) **Load Balancer:** Cilium LB-IPAM (eBPF-based) **TLS CA:** homelab-ca (self-signed, 10-year) **Namespace:** `ingress-nginx` ## When to Use - **Public HTTPS endpoints** — External access via TLS - **Hostname-based routing** — Multiple services on same IP - **TLS termination** — Offload encryption/decryption - **Service discovery** — Internal DNS (CoreDNS) ## Quick Start **1. Create Ingress rule:** ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: myapp namespace: myapp-ns annotations: cert-manager.io/cluster-issuer: "letsencrypt-prod" # or homelab-ca spec: ingressClassName: nginx tls: - hosts: - myapp.riotpiao.homelab.com secretName: myapp-tls rules: - host: myapp.riotpiao.homelab.com http: paths: - path: / pathType: Prefix backend: service: name: myapp-svc port: number: 8080 ``` **2. Deploy:** ```bash kubectl apply -f ingress.yaml # Wait for cert issuance kubectl get certificate -n myapp-ns # Should show "Ready" after ~30s ``` **3. Test from client:** ```bash # Add to /etc/hosts (or use WireGuard) 192.168.1.160 myapp.riotpiao.homelab.com # Access curl https://myapp.riotpiao.homelab.com ``` ## Configuration | Key | Value | |-----|-------| | Ingress class | `nginx` | | Load balancer type | `LoadBalancer` (Cilium LB-IPAM) | | TLS issuer | `homelab-ca` (ClusterIssuer) | | TLS cert lifetime | 90 days (auto-renewed by cert-manager) | | DNS | CoreDNS (in-cluster), external via `/etc/hosts` or DuckDNS | ## Common Patterns **Ingress with path-based routing:** ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: api namespace: default spec: ingressClassName: nginx tls: - hosts: - api.riotpiao.homelab.com secretName: api-tls rules: - host: api.riotpiao.homelab.com http: paths: - path: /users pathType: Prefix backend: service: name: users-svc port: number: 3000 - path: /orders pathType: Prefix backend: service: name: orders-svc port: number: 3001 ``` **Ingress with basic auth:** ```bash # Generate htpasswd htpasswd -c auth admin # → prompted for password # Create Secret kubectl create secret generic basic-auth --from-file=auth -n default # Create Ingress ``` ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: protected namespace: default annotations: nginx.ingress.kubernetes.io/auth-type: basic nginx.ingress.kubernetes.io/auth-secret: basic-auth nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required' spec: ingressClassName: nginx rules: - host: protected.riotpiao.homelab.com http: paths: - path: / pathType: Prefix backend: service: name: app-svc port: number: 8080 ``` **Internal DNS (CoreDNS rewrite):** ```yaml # k8s/coredns/coredns-configmap.yaml # Rewrite: # - grafana.riotpiao.homelab.com → grafana.logging (cluster-internal) # - prometheus.riotpiao.homelab.com → prometheus-kube-prom-prometheus.monitoring # # Allows pods to use external URLs but resolve to internal Services ``` **Fixed LoadBalancer IP (Cilium LB-IPAM):** ```yaml apiVersion: v1 kind: Service metadata: name: ingress-nginx namespace: ingress-nginx annotations: io.cilium/lb-ipam-ips: "192.168.1.160" # fixed IP spec: type: LoadBalancer selector: app: nginx-ingress ports: - port: 443 targetPort: 443 protocol: TCP ``` ## TLS Certificate Management **Automatic renewal (cert-manager):** ```yaml apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: myapp-cert namespace: myapp-ns spec: secretName: myapp-tls duration: 2160h # 90 days renewBefore: 360h # renew 15 days before expiry commonName: myapp.riotpiao.homelab.com dnsNames: - myapp.riotpiao.homelab.com issuerRef: name: homelab-ca kind: ClusterIssuer ``` **Check certificate status:** ```bash # List certs k get certificate -A # View cert details k describe certificate -n myapp-ns myapp-cert # View TLS Secret k get secret -n myapp-ns myapp-tls -o json | jq '.data."tls.crt"' | base64 -d | openssl x509 -text # Check expiry date k get secret -n myapp-ns myapp-tls -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -enddate ``` ## Service Discovery **Cluster-internal DNS:** ```bash # From any pod, resolve via CoreDNS nslookup grafana.logging.svc.cluster.local # full FQDN nslookup grafana.logging # short form (same namespace) nslookup grafana # if in logging namespace # Resolved to ClusterIP (internal only) ``` **External DNS (WireGuard VPN or port-forward):** ```bash # Option 1: WireGuard tunnel # Client connects to 10.6.0.1 (WireGuard server on talos-cp-1) # All traffic tunneled to cluster # Option 2: Port-forward from jump box make pf-grafana # localhost:3000 → grafana.logging:3000 # Option 3: Add to /etc/hosts (on home network) 192.168.1.160 grafana.riotpiao.homelab.com ``` ## Monitoring **Grafana dashboard:** `svc-nginx-ingress` **Key metrics:** - `nginx_requests_total` — total requests - `nginx_request_duration_seconds` — latency histogram - `nginx_ingress_upstream_requests_total{status=~"5.."}` — backend errors - `nginx_ssl_expire_time_seconds` — cert expiry countdown **Alert on cert expiry:** ```yaml apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: name: cert-expiry namespace: ingress-nginx spec: groups: - name: cert-expiry rules: - alert: CertificateExpiringSoon expr: nginx_ssl_expire_time_seconds < 86400 * 14 # < 14 days annotations: summary: "Certificate {{ $labels.host }} expires in {{ $value | humanizeDuration }}" ``` ## Troubleshooting **Certificate stuck in "Pending":** ```bash # Check cert-manager logs k logs -n cert-manager -f deploy/cert-manager # Verify ClusterIssuer exists k get clusterissuer # Check ACME order (if using LetsEncrypt) k describe certificate -n myapp-ns myapp-cert ``` **Ingress not exposing service (503 error):** ```bash # Verify Service exists and has endpoints k get svc -n myapp-ns k get endpoints -n myapp-ns myapp-svc # Check if pods are ready k get pods -n myapp-ns # Test pod directly (port-forward) k port-forward -n myapp-ns pod/myapp-0 8080:8080 curl http://localhost:8080 ``` **DNS resolution fails from pod:** ```bash # Test from pod k run -it --rm debug --image=busybox:1.28 --restart=Never -- \ nslookup grafana.logging.svc.cluster.local # If fails, CoreDNS may be unhealthy k get pods -n kube-system -l k8s-app=kube-dns k logs -n kube-system -l k8s-app=kube-dns ``` **TLS handshake error (cert not trusted):** ```bash # Verify TLS cert Secret exists k get secret -n myapp-ns myapp-tls # Verify cert is correctly signed by homelab-ca k get secret -n myapp-ns myapp-tls -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text | grep -A 5 "Issuer:" # If cert is self-signed (homelab-ca), add to client's trusted roots # Or bypass cert verification (dev only): curl -k https://myapp.riotpiao.homelab.com ``` See `/TROUBLESHOOTING.md` for full incident guide.