Add Kubernetes/K3s deployment manifests and documentation

- Complete k8s manifests with Kustomize support
- Production and staging overlays
- ConfigMap/Secret management
- Ingress with TLS (Traefik/NGINX)
- Persistent storage for SQLite
- Comprehensive k8s README with operations guide
- Updated main README with k8s deployment instructions
- Gitignore for k8s secrets

Usage:
  kubectl apply -k k8s/overlays/production
This commit is contained in:
2026-02-01 16:40:16 -05:00
parent 2470f121e2
commit f3f1c0a0c8
16 changed files with 692 additions and 1 deletions

4
.gitignore vendored
View File

@@ -44,3 +44,7 @@ vendor/
# Temporary files # Temporary files
*.tmp *.tmp
*.temp *.temp
# Kubernetes secrets (never commit these!)
k8s/overlays/*/secrets.yaml
!k8s/overlays/*/secrets.example.yaml

View File

@@ -77,9 +77,11 @@ TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12
### Deployment ### Deployment
#### Docker Compose (Recommended for single node)
```bash ```bash
# Clone and start # Clone and start
git clone <repo> git clone https://git.dws.rip/DWS/dyn.git
cd dyn cd dyn
docker-compose up -d docker-compose up -d
@@ -90,6 +92,44 @@ docker-compose logs -f
docker-compose down docker-compose down
``` ```
#### Kubernetes / K3s
For production deployments on Kubernetes or K3s clusters:
```bash
# Clone repository
git clone https://git.dws.rip/DWS/dyn.git
cd dyn
# Create your secrets file
cp k8s/overlays/production/secrets.example.yaml k8s/overlays/production/secrets.yaml
# Edit secrets.yaml with your actual Technitium credentials
# Deploy with kustomize
kubectl apply -k k8s/overlays/production
# Or with kubectl 1.14+ (built-in kustomize)
kubectl apply -k k8s/overlays/production
# Check deployment status
kubectl get pods -n dyn-ddns
kubectl logs -n dyn-ddns -l app=dyn-ddns
# Delete deployment
kubectl delete -k k8s/overlays/production
```
**Kustomize Overlays:**
- `k8s/overlays/production` - Production setup (2 replicas, higher resource limits)
- `k8s/overlays/staging` - Staging environment (1 replica, relaxed rate limits)
**Requirements:**
- Cert-manager (for TLS certificates via Let's Encrypt)
- Traefik or NGINX ingress controller
- Persistent storage class (for SQLite database)
See [k8s/README.md](k8s/README.md) for detailed Kubernetes deployment documentation.
## API Endpoints ## API Endpoints
### Web UI ### Web UI

365
k8s/README.md Normal file
View File

@@ -0,0 +1,365 @@
# Kubernetes Deployment
This directory contains Kubernetes manifests for deploying the DWS Dynamic DNS service on K3s, Kubernetes, or any K8s-compatible platform.
## Directory Structure
```
k8s/
├── base/ # Base manifests (don't edit directly)
│ ├── deployment.yaml # Deployment, Service, PVC
│ ├── configmap.yaml # Non-sensitive configuration
│ ├── secrets.yaml # Sensitive configuration (placeholders)
│ ├── ingress.yaml # Ingress with TLS
│ └── kustomization.yaml # Base kustomization
├── overlays/
│ ├── production/ # Production environment
│ │ ├── kustomization.yaml # Production-specific settings
│ │ ├── deployment-patch.yaml # Resource adjustments
│ │ ├── namespace.yaml # Production namespace
│ │ ├── secrets.yaml # Production secrets (gitignored)
│ │ └── secrets.example.yaml # Example secrets template
│ │
│ └── staging/ # Staging environment
│ ├── kustomization.yaml # Staging-specific settings
│ ├── deployment-patch.yaml # Single replica, lower resources
│ ├── namespace.yaml # Staging namespace
│ ├── secrets.yaml # Staging secrets (gitignored)
│ └── secrets.example.yaml # Example secrets template
└── README.md # This file
```
## Quick Start
### Prerequisites
- Kubernetes 1.21+ or K3s cluster
- kubectl configured with cluster access
- cert-manager installed (for TLS certificates)
- Ingress controller (Traefik, NGINX, etc.)
- Storage class for persistent volumes
### Production Deployment
```bash
# 1. Clone and enter directory
git clone https://git.dws.rip/DWS/dyn.git
cd dyn/k8s
# 2. Create production secrets
cp overlays/production/secrets.example.yaml overlays/production/secrets.yaml
# 3. Edit secrets with your Technitium credentials
# Replace 'your-production-api-token-here' with actual token
nano overlays/production/secrets.yaml
# 4. Deploy to production
kubectl apply -k overlays/production
# 5. Verify deployment
kubectl get pods -n dyn-ddns
kubectl get svc -n dyn-ddns
kubectl get ingress -n dyn-ddns
# 6. Check logs
kubectl logs -n dyn-ddns -l app.kubernetes.io/name=dyn-ddns -f
```
### Staging Deployment
```bash
# Similar process for staging
cp overlays/staging/secrets.example.yaml overlays/staging/secrets.yaml
# Edit with staging credentials
kubectl apply -k overlays/staging
```
## Configuration
### Secrets (Required)
Create `overlays/production/secrets.yaml`:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: dyn-ddns-secrets
type: Opaque
stringData:
# Choose ONE authentication method:
# Method 1: API Token (recommended)
TECHNITIUM_TOKEN: "your-actual-api-token-here"
# Method 2: Username/Password
# TECHNITIUM_USERNAME: "admin"
# TECHNITIUM_PASSWORD: "your-password"
# Optional: Trusted proxies
# TRUSTED_PROXIES: "10.0.0.0/8,172.16.0.0/12"
```
**Important:** Never commit secrets.yaml to git. It's already in .gitignore.
### ConfigMap (Optional Overrides)
Edit `overlays/production/kustomization.yaml`:
```yaml
configMapGenerator:
- name: dyn-ddns-config
behavior: merge
literals:
- TECHNITIUM_URL=https://dns.dws.rip
- BASE_DOMAIN=dws.rip
- SPACE_SUBDOMAIN=space
- RATE_LIMIT_PER_IP=10
- RATE_LIMIT_PER_TOKEN=1
```
### Ingress Customization
By default, the ingress is configured for **Traefik** with cert-manager:
```yaml
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
```
For **NGINX** ingress, change annotations in `base/ingress.yaml`:
```yaml
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
```
## Architecture
### Components
1. **Deployment** - Runs the DDNS bridge container
- Replicas: 2 (production), 1 (staging)
- Resource limits configurable per overlay
- Health checks (liveness & readiness probes)
2. **Service** - ClusterIP exposing port 80
3. **Ingress** - TLS termination at edge
- Host: dyn.dws.rip (customize as needed)
- Automatic certificate via cert-manager
4. **PersistentVolumeClaim** - SQLite database storage
- Size: 1Gi (adjustable)
- AccessMode: ReadWriteOnce
### Resource Requirements
**Production:**
- CPU: 200m request / 1000m limit
- Memory: 128Mi request / 512Mi limit
- Replicas: 2
**Staging:**
- CPU: 100m request / 500m limit
- Memory: 64Mi request / 256Mi limit
- Replicas: 1
## Operations
### View Logs
```bash
# All pods
kubectl logs -n dyn-ddns -l app.kubernetes.io/name=dyn-ddns
# Specific pod
kubectl logs -n dyn-ddns -f deployment/prod-dyn-ddns
# Previous container logs (after restart)
kubectl logs -n dyn-ddns --previous deployment/prod-dyn-ddns
```
### Scale Deployment
```bash
# Scale to 3 replicas
kubectl scale deployment -n dyn-ddns prod-dyn-ddns --replicas=3
# Edit deployment directly
kubectl edit deployment -n dyn-ddns prod-dyn-ddns
```
### Database Backup
The SQLite database is stored in the persistent volume at `/data/dyn.db`:
```bash
# Find the pod
POD=$(kubectl get pod -n dyn-ddns -l app.kubernetes.io/name=dyn-ddns -o jsonpath='{.items[0].metadata.name}')
# Copy database locally
kubectl cp dyn-ddns/$POD:/data/dyn.db ./dyn-backup-$(date +%Y%m%d).db
# Or exec into pod
kubectl exec -it -n dyn-ddns $POD -- sh
# Then: sqlite3 /data/dyn.db "SELECT * FROM spaces;"
```
### Update Deployment
```bash
# Update to latest image
kubectl rollout restart deployment -n dyn-ddns prod-dyn-ddns
# Or set specific image tag
kubectl set image deployment -n dyn-ddns prod-dyn-ddns dyn-ddns=git.dws.rip/DWS/dyn:v1.0.0
# Monitor rollout
kubectl rollout status deployment -n dyn-ddns prod-dyn-ddns
```
### Troubleshooting
**Pod stuck in Pending:**
```bash
kubectl describe pod -n dyn-ddns <pod-name>
# Check: Storage class available? PV provisioned?
```
**Pod crash looping:**
```bash
kubectl logs -n dyn-ddns --previous <pod-name>
# Check: Secrets configured? Technitium URL reachable?
```
**Ingress not working:**
```bash
kubectl describe ingress -n dyn-ddns prod-dyn-ddns
kubectl get certificate -n dyn-ddns
# Check: DNS pointing to ingress controller? Cert-manager working?
```
## Advanced Usage
### Multi-Environment Setup
Deploy to multiple environments:
```bash
# Staging
kubectl apply -k overlays/staging
# Production
kubectl apply -k overlays/production
# Verify both
kubectl get pods --all-namespaces -l app.kubernetes.io/name=dyn-ddns
```
### Custom Overlay
Create your own overlay for specific needs:
```bash
mkdir overlays/custom
cat > overlays/custom/kustomization.yaml << 'EOF'
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namespace: my-namespace
configMapGenerator:
- name: dyn-ddns-config
behavior: merge
literals:
- TECHNITIUM_URL=https://my-dns-server.example.com
- RATE_LIMIT_PER_IP=5
EOF
kubectl apply -k overlays/custom
```
### Monitoring
The deployment exposes standard metrics. Add Prometheus scraping:
```yaml
# Add to deployment-patch.yaml
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
```
## Security Considerations
1. **Secrets Management**
- Never commit secrets to git
- Consider using external secrets operator (Vault, Sealed Secrets)
- Rotate Technitium API tokens regularly
2. **Network Policies**
```yaml
# Example network policy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: dyn-ddns-netpol
namespace: dyn-ddns
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: dyn-ddns
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 8080
egress:
- to: [] # Allow all egress (for Technitium API)
```
3. **Pod Security**
- Container runs as non-root (in Dockerfile)
- Read-only root filesystem recommended
- Drop all capabilities
## Maintenance
### Regular Tasks
1. **Backup database weekly**
2. **Monitor rate limit metrics**
3. **Review access logs**
4. **Update base image for security patches**
### Version Upgrades
1. Update image tag in overlay kustomization
2. Apply changes: `kubectl apply -k overlays/production`
3. Verify rollout: `kubectl rollout status ...`
4. Monitor for errors in logs
## Support
For issues specific to Kubernetes deployment:
1. Check pod logs: `kubectl logs ...`
2. Describe resources: `kubectl describe ...`
3. Check ingress controller logs
4. Verify cert-manager is issuing certificates

13
k8s/base/configmap.yaml Normal file
View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: dyn-ddns-config
data:
SERVER_PORT: "8080"
DATABASE_PATH: "/data/dyn.db"
BASE_DOMAIN: "dws.rip"
SPACE_SUBDOMAIN: "space"
RATE_LIMIT_PER_IP: "10"
RATE_LIMIT_PER_TOKEN: "1"
# TECHNITIUM_URL: "https://dns.dws.rip"
# Add your Technitium URL above or in overlays

86
k8s/base/deployment.yaml Normal file
View File

@@ -0,0 +1,86 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: dyn-ddns
labels:
app: dyn-ddns
spec:
replicas: 1
selector:
matchLabels:
app: dyn-ddns
template:
metadata:
labels:
app: dyn-ddns
spec:
containers:
- name: dyn-ddns
image: git.dws.rip/DWS/dyn:latest
imagePullPolicy: Always
ports:
- containerPort: 8080
name: http
envFrom:
- configMapRef:
name: dyn-ddns-config
- secretRef:
name: dyn-ddns-secrets
volumeMounts:
- name: data
mountPath: /data
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
volumes:
- name: data
persistentVolumeClaim:
claimName: dyn-ddns-data
---
apiVersion: v1
kind: Service
metadata:
name: dyn-ddns
labels:
app: dyn-ddns
spec:
selector:
app: dyn-ddns
ports:
- port: 80
targetPort: 8080
name: http
type: ClusterIP
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dyn-ddns-data
labels:
app: dyn-ddns
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi

30
k8s/base/ingress.yaml Normal file
View File

@@ -0,0 +1,30 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: dyn-ddns
annotations:
# Traefik
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
# NGINX (uncomment if using NGINX ingress)
# kubernetes.io/ingress.class: nginx
# cert-manager.io/cluster-issuer: "letsencrypt-prod"
# nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- dyn.dws.rip
secretName: dyn-ddns-tls
rules:
- host: dyn.dws.rip
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dyn-ddns
port:
number: 80

View File

@@ -0,0 +1,17 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- configmap.yaml
- ingress.yaml
commonLabels:
app.kubernetes.io/name: dyn-ddns
app.kubernetes.io/part-of: dws-dns
namespace: default
images:
- name: git.dws.rip/DWS/dyn
newTag: latest

17
k8s/base/secrets.yaml Normal file
View File

@@ -0,0 +1,17 @@
apiVersion: v1
kind: Secret
metadata:
name: dyn-ddns-secrets
type: Opaque
stringData:
# Choose ONE authentication method:
# Method 1: API Token (recommended)
# TECHNITIUM_TOKEN: "your-api-token-here"
# Method 2: Username/Password
# TECHNITIUM_USERNAME: "admin"
# TECHNITIUM_PASSWORD: "your-password"
# Optional: Trusted proxies (comma-separated)
# TRUSTED_PROXIES: "10.0.0.0/8,172.16.0.0/12"

View File

@@ -0,0 +1,16 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: dyn-ddns
spec:
template:
spec:
containers:
- name: dyn-ddns
resources:
requests:
memory: "128Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "1000m"

View File

@@ -0,0 +1,28 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- ../../base
- secrets.yaml
namePrefix: prod-
namespace: dyn-ddns
commonLabels:
environment: production
configMapGenerator:
- name: dyn-ddns-config
behavior: merge
literals:
- TECHNITIUM_URL=https://dns.dws.rip
- RATE_LIMIT_PER_IP=10
- RATE_LIMIT_PER_TOKEN=1
patchesStrategicMerge:
- deployment-patch.yaml
replicas:
- name: dyn-ddns
count: 2

View File

@@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: dyn-ddns
labels:
app.kubernetes.io/name: dyn-ddns
app.kubernetes.io/part-of: dws-dns

View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: Secret
metadata:
name: dyn-ddns-secrets
type: Opaque
stringData:
# Replace with your actual Technitium API token
TECHNITIUM_TOKEN: "your-production-api-token-here"
# Or use username/password (not recommended for production)
# TECHNITIUM_USERNAME: "admin"
# TECHNITIUM_PASSWORD: "your-password"

View File

@@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: dyn-ddns
spec:
replicas: 1
template:
spec:
containers:
- name: dyn-ddns
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"

View File

@@ -0,0 +1,24 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- ../../base
- secrets.yaml
namePrefix: staging-
namespace: dyn-ddns-staging
commonLabels:
environment: staging
configMapGenerator:
- name: dyn-ddns-config
behavior: merge
literals:
- TECHNITIUM_URL=https://dns-staging.dws.rip
- RATE_LIMIT_PER_IP=100
- RATE_LIMIT_PER_TOKEN=10
patchesStrategicMerge:
- deployment-patch.yaml

View File

@@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: dyn-ddns-staging
labels:
app.kubernetes.io/name: dyn-ddns
app.kubernetes.io/part-of: dws-dns

View File

@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: dyn-ddns-secrets
type: Opaque
stringData:
# Replace with your staging Technitium credentials
TECHNITIUM_TOKEN: "your-staging-api-token-here"