Files
k3s/.claude/skills/caddy/SKILL.md
2026-02-05 00:11:05 +08:00

8.3 KiB

name, description
name description
caddy-ssl-termination 专门用于 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

# /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

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

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:

# 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

# 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

# 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

# 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

    kubectl get svc caddy -n default -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
    
  2. Update ConfigMap: Add new domain block

    kubectl edit configmap caddy-config -n default
    

    Add:

    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:

    kubectl exec -n default deployment/caddy -- caddy reload --config /etc/caddy/Caddyfile
    
  4. Verify: Check logs for certificate acquisition

    kubectl logs -n default deployment/caddy | tail -20
    

Traefik Integration

Traefik IngressRoute Example

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

# 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

# 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

# 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

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

example.com {
    rate_limit {
        zone dynamic {
            key {remote_host}
            events 100
            window 1m
        }
    }
    reverse_proxy traefik-service:80
}

Custom Error Pages

example.com {
    handle_errors {
        respond "{err.status_code} {err.status_text}"
    }
    reverse_proxy traefik-service:80
}

Health Checks

example.com {
    reverse_proxy traefik-service:80 {
        health_uri /health
        health_interval 10s
        health_timeout 5s
    }
}

Quick Reference Commands

# 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)