2026-07-11 19:17:54 -07:00
|
|
|
# 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:
|
2026-07-19 09:29:17 -07:00
|
|
|
- myapp.riotpiao.com
|
2026-07-11 19:17:54 -07:00
|
|
|
secretName: myapp-tls
|
|
|
|
|
rules:
|
2026-07-19 09:29:17 -07:00
|
|
|
- host: myapp.riotpiao.com
|
2026-07-11 19:17:54 -07:00
|
|
|
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)
|
2026-07-19 09:29:17 -07:00
|
|
|
192.168.1.160 myapp.riotpiao.com
|
2026-07-11 19:17:54 -07:00
|
|
|
|
|
|
|
|
# Access
|
2026-07-19 09:29:17 -07:00
|
|
|
curl https://myapp.riotpiao.com
|
2026-07-11 19:17:54 -07:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 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:
|
2026-07-19 09:29:17 -07:00
|
|
|
- api.riotpiao.com
|
2026-07-11 19:17:54 -07:00
|
|
|
secretName: api-tls
|
|
|
|
|
rules:
|
2026-07-19 09:29:17 -07:00
|
|
|
- host: api.riotpiao.com
|
2026-07-11 19:17:54 -07:00
|
|
|
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:
|
2026-07-19 09:29:17 -07:00
|
|
|
- host: protected.riotpiao.com
|
2026-07-11 19:17:54 -07:00
|
|
|
http:
|
|
|
|
|
paths:
|
|
|
|
|
- path: /
|
|
|
|
|
pathType: Prefix
|
|
|
|
|
backend:
|
|
|
|
|
service:
|
|
|
|
|
name: app-svc
|
|
|
|
|
port:
|
|
|
|
|
number: 8080
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Internal DNS (CoreDNS rewrite):**
|
|
|
|
|
```yaml
|
|
|
|
|
# k8s/coredns/coredns-configmap.yaml
|
|
|
|
|
# Rewrite:
|
2026-07-19 09:29:17 -07:00
|
|
|
# - grafana.riotpiao.com → grafana.logging (cluster-internal)
|
|
|
|
|
# - prometheus.riotpiao.com → prometheus-kube-prom-prometheus.monitoring
|
2026-07-11 19:17:54 -07:00
|
|
|
#
|
|
|
|
|
# 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
|
2026-07-19 09:29:17 -07:00
|
|
|
commonName: myapp.riotpiao.com
|
2026-07-11 19:17:54 -07:00
|
|
|
dnsNames:
|
2026-07-19 09:29:17 -07:00
|
|
|
- myapp.riotpiao.com
|
2026-07-11 19:17:54 -07:00
|
|
|
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)
|
2026-07-19 09:29:17 -07:00
|
|
|
192.168.1.160 grafana.riotpiao.com
|
2026-07-11 19:17:54 -07:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 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):
|
2026-07-19 09:29:17 -07:00
|
|
|
curl -k https://myapp.riotpiao.com
|
2026-07-11 19:17:54 -07:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
See `/TROUBLESHOOTING.md` for full incident guide.
|