--- name: caddy-ssl-termination description: 专门用于 Traefik 前置 Caddy 进行 SSL 卸载的架构配置,适用于 K3s 环境。 --- # Caddy SSL Termination Skill ## Architecture Overview **Setup**: Traefik (routing) → Caddy (HTTPS/SSL termination) → HTTP backend - **Caddy**: Handles HTTPS (443) with automatic SSL certificates, forwards to Traefik on HTTP (80) - **Traefik**: Routes HTTP traffic to appropriate backend services - **Flow**: Internet → Caddy:443 (HTTPS) → Traefik:80 (HTTP) → Backend Pods ## Quick Configuration Template ### 1. Basic Caddyfile Structure ```caddy # /etc/caddy/Caddyfile # Domain configuration example.com { reverse_proxy traefik-service:80 } # Multiple domains app1.example.com { reverse_proxy traefik-service:80 } app2.example.com { reverse_proxy traefik-service:80 } # Wildcard subdomain (requires DNS wildcard) *.example.com { reverse_proxy traefik-service:80 } ``` ### 2. ConfigMap for Caddyfile ```yaml apiVersion: v1 kind: ConfigMap metadata: name: caddy-config namespace: default data: Caddyfile: | # Global options { email your-email@example.com # Use Let's Encrypt staging for testing # acme_ca https://acme-staging-v02.api.letsencrypt.org/directory } # Your domains example.com { reverse_proxy traefik-service:80 { header_up Host {host} header_up X-Real-IP {remote} header_up X-Forwarded-For {remote} header_up X-Forwarded-Proto {scheme} } } ``` ### 3. Caddy Deployment ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: caddy namespace: default spec: replicas: 1 selector: matchLabels: app: caddy template:tadata: labels: app: caddy spec: containers: - name: caddy image: caddy:latest ports: - containerPort: 80 - containerPort: 443 - containerPort: 2019 # Admin API volumeMounts: - name: config mountPath: /etc/caddy - name: data mountPath: /data - name: config-cache mountPath: /config volumes: - name: config configMap: name: caddy-config - name: data persistentVolumeClaim: claimName: caddy-data - name: config-cache emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: caddy namespace: default spec: type: LoadBalancer # or NodePort ports: - name: http port: 80 targetPort: 80 - name: https port: 443 targetPort: 443 selector: app: caddy --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: caddy-data namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi ``` ## Common Operations ### Reload Configuration After updating the ConfigMap: ```bash # Method 1: Reload via exec (preserves connections) kubectl exec -n default deployment/caddy -- caddy reload --config /etc/caddy/Caddyfile # Method 2: Restart pod (brief downtime) kubectl rollout restart deployment/caddy -n default # Method 3: Delete pod (auto-recreates) kubectl delete pod -n default -l app=caddy ``` ### Update Caddyfile ```bash # Edit ConfigMap kubectl edit configmap caddy-config -n default # Or apply updated file kubectl apply -f caddy-configmap.yaml # Then reload kubectl exec -n default deployment/caddy -- caddy reload --config /etc/caddy/Caddyfile ``` ### View Logs ```bash # Follow logs kubectl logs -n default -f deployment/caddy # Check SSL certificate issues kubectl logs -n default deployment/caddy | grep -i "certificate\|acme\|tls" ``` ### Check Configuration ```bash # Validate Caddyfile syntax kubectl exec -n default deployment/caddy -- caddy validate --config /etc/caddy/Caddyfile # View current config via API kubectl exec -n default deployment/caddy -- curl localhost:2019/config/ ``` ## Adding New Domain ### Step-by-step Process 1. **Update DNS**: Point new domain to Caddy's LoadBalancer IP ```bash kubectl get svc caddy -n default -o jsonpath='{.status.loadBalancer.ingress[0].ip}' ``` 2. **Update ConfigMap**: Add new domain block ```bash kubectl edit configmap caddy-config -n default ``` Add: ```caddy newapp.example.com { reverse_proxy traefik-service:80 { header_up Host {host} header_up X-Real-IP {remote} header_up X-Forwarded-For {remote} header_up X-Forwarded-Proto {scheme} } } ``` 3. **Reload Caddy**: ```bash kubectl exec -n default deployment/caddy -- caddy reload --config /etc/caddy/Caddyfile ``` 4. **Verify**: Check logs for certificate acquisition ```bash kubectl logs -n default deployment/caddy | tail -20 ``` ## Traefik Integration ### Traefik IngressRoute Example ```yaml apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: myapp namespace: default spec: entryPoints: - web # HTTP only, Caddy handles HTTPS routes: - match: Host(`myapp.example.com`) kind: Rule services: - name: myapp-service port: 8080 ``` ### Important Notes - Traefik should listen on HTTP (80) only - Caddy handles all HTTPS/SSL - Use `Host()` matcher in Traefik to route by domain - Caddy forwards the original `Host` header to Traefik ## Troubleshooting ### SSL Certificate Issues ```bash # Check certificate status kubectl exec -n default deployment/caddy -- caddy list-certificates # View ACME logs kubectl logs -n default deployment/caddy | grep -i acme # Common issues: # - Port 80/443 not accessible from internet # - DNS not pointing to correct IP # - Rate limit hit (use staging CA for testing) ``` ### Configuration Errors ```bash # Test config before reload kubectl exec -n default deployment/caddy -- caddy validate --config /etc/caddy/Caddyfile # Check for syntax errors kubectl logs -n default deployment/caddy | grep -i error ``` ### Connection Issues ```bash # Test from inside cluster kubectl run -it --rm debug --image=curlimages/curl --restart=Never -- curl -v http://traefik-service:80 # Check if Caddy can reach Traefik kubectl exec -n default deployment/caddy -- curl -v http://traefik-service:80 ``` ## Advanced Configurations ### Custom TLS Settings ```caddy example.com { tls { protocols tls1.2 tls1.3 ciphers TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 } reverse_proxy traefik-service:80 } ``` ### Rate Limiting ```caddy example.com { rate_limit { zone dynamic { key {remote_host} events 100 window 1m } } reverse_proxy traefik-service:80 } ``` ### Custom Error Pages ```caddy example.com { handle_errors { respond "{err.status_code} {err.status_text}" } reverse_proxy traefik-service:80 } ``` ### Health Checks ```caddy example.com { reverse_proxy traefik-service:80 { health_uri /health health_interval 10s health_timeout 5s } } ``` ## Quick Reference Commands ```bash # Get Caddy pod name kubectl get pods -n default -l app=caddy # Reload config kubectl exec -n default deployment/caddy -- caddy reload --config /etc/caddy/Caddyfile # View current config kubectl exec -n default deployment/caddy -- cat /etc/caddy/Caddyfile # Check certificates kubectl exec -n default deployment/caddy -- caddy list-certificates # Restart Caddy kubectl rollout restart deployment/caddy -n default # Watch logs kubectl logs -n default -f deployment/caddy # Get LoadBalancer IP kubectl get svc caddy -n default -o jsonpath='{.status.loadBalancer.ingress[0].ip}' ``` ## Checklist for New Service - [ ] DNS A/AAAA record points to Caddy LoadBalancer IP - [ ] Domain added to Caddy ConfigMap - [ ] Caddy reloaded successfully - [ ] Traefik IngressRoute created (HTTP only) - [ ] Backend service accessible from Traefik - [ ] SSL certificate acquired (check logs) - [ ] HTTPS access working from browser - [ ] HTTP redirects to HTTPS (Caddy default behavior) ## Notes - Caddy automatically obtains and renews SSL certificates - Certificates stored in `/data` volume (must be persistent) - Reload is graceful - no connection drops - Caddy handles HTTP→HTTPS redirect automatically - Use staging CA for testing to avoid rate limits - Wildcard certificates require DNS challenge (more complex setup)