Fix resources isues 5

This commit is contained in:
2026-01-22 11:15:11 +01:00
parent 6505044f24
commit 0183f3ab72
20 changed files with 399 additions and 1193 deletions

View File

@@ -2,7 +2,7 @@
## Executive Summary ## Executive Summary
This document outlines the recommended architecture for deploying Mailu email services across development and production environments for the Bakery-IA project. The solution addresses DNSSEC validation requirements while maintaining consistency across different Kubernetes platforms. This document outlines the recommended architecture for deploying Mailu email services across development and production environments for the Bakery-IA project. The solution addresses DNSSEC validation requirements using CoreDNS with DNS-over-TLS while maintaining consistency across different Kubernetes platforms.
## Environment Overview ## Environment Overview
@@ -25,124 +25,76 @@ This document outlines the recommended architecture for deploying Mailu email se
## Architectural Solution ## Architectural Solution
### Unified DNS Resolution Strategy ### DNS Resolution Strategy
**Recommended Approach**: Deploy Unbound as a dedicated DNSSEC-validating resolver pod in both environments **Approach**: Use CoreDNS with DNS-over-TLS to Cloudflare (1.1.1.1) for DNSSEC validation
#### Benefits: #### Benefits:
- ✅ Leverages existing Kubernetes DNS infrastructure
- ✅ No additional pods required (uses CoreDNS already in cluster)
- ✅ DNSSEC validation provided by Cloudflare's DNS-over-TLS
- ✅ Consistent behavior across dev and prod - ✅ Consistent behavior across dev and prod
- ✅ Meets Mailu's DNSSEC requirements - ✅ Meets Mailu's DNSSEC requirements
-Privacy-preserving (no external DNS queries) -Simple and reliable
- ✅ Avoids rate-limiting from public DNS providers
- ✅ Full control over DNS resolution
### Implementation Components ### Implementation Components
#### 1. Unbound Deployment Manifest #### 1. CoreDNS Configuration with DNS-over-TLS
```yaml ```yaml
# unbound.yaml - Cross-environment compatible # CoreDNS Corefile configuration for DNSSEC via DNS-over-TLS
apiVersion: apps/v1 .:53 {
kind: Deployment errors
metadata: health {
name: unbound-resolver lameduck 5s
namespace: mailu }
labels: ready
app: unbound kubernetes cluster.local in-addr.arpa ip6.arpa {
component: dns pods insecure
spec: fallthrough in-addr.arpa ip6.arpa
replicas: 1 # Scale to 2+ in production with anti-affinity ttl 30
selector: }
matchLabels: prometheus :9153
app: unbound forward . tls://1.1.1.1 tls://1.0.0.1 {
template: tls_servername cloudflare-dns.com
metadata: health_check 5s
labels: }
app: unbound cache 30 {
component: dns disable success cluster.local
spec: disable denial cluster.local
containers: }
- name: unbound loop
image: mvance/unbound:latest reload
ports: loadbalance
- containerPort: 53 }
name: dns-udp
protocol: UDP
- containerPort: 53
name: dns-tcp
protocol: TCP
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "300m"
memory: "384Mi"
readinessProbe:
exec:
command: ["drill", "@127.0.0.1", "-p", "53", "+dnssec", "example.org"]
initialDelaySeconds: 10
periodSeconds: 30
securityContext:
capabilities:
add: ["NET_BIND_SERVICE"]
---
apiVersion: v1
kind: Service
metadata:
name: unbound-dns
namespace: mailu
spec:
selector:
app: unbound
ports:
- name: dns-udp
port: 53
targetPort: 53
protocol: UDP
- name: dns-tcp
port: 53
targetPort: 53
protocol: TCP
``` ```
#### 2. Mailu Configuration (values.yaml) #### 2. Mailu Configuration (values.yaml)
```yaml ```yaml
# Production-tuned Mailu configuration # Production-tuned Mailu configuration
dnsPolicy: None global:
dnsConfig: # Using Kubernetes CoreDNS for DNS resolution
nameservers: # CoreDNS is configured with DNS-over-TLS (Cloudflare) for DNSSEC validation
- "10.152.183.x" # Replace with actual unbound service IP custom_dns_servers: "10.152.183.10" # MicroK8s CoreDNS IP (adjust for your cluster)
# Component-specific DNS configuration # DNS configuration - use Kubernetes DNS (ClusterFirst)
# CoreDNS provides DNSSEC validation via DNS-over-TLS to Cloudflare
admin: admin:
dnsPolicy: None dnsPolicy: "ClusterFirst"
dnsConfig:
nameservers:
- "10.152.183.x"
rspamd: rspamd:
dnsPolicy: None dnsPolicy: "ClusterFirst"
dnsConfig:
nameservers:
- "10.152.183.x"
# Environment-specific configurations # Environment-specific configurations
persistence: persistence:
enabled: true enabled: true
# Development: use default storage class storageClass: "" # Use cluster default
# Production: use microk8s-hostpath or longhorn
storageClass: "standard"
replicas: 1 # Increase in production as needed replicas: 1 # Increase in production as needed
# Security settings # Security settings
secretKey: "generate-strong-key-here" secretKey: "generate-strong-key-here"
# Ingress configuration
# Use existing Bakery-IA ingress controller
``` ```
### Environment-Specific Adaptations ### Environment-Specific Adaptations
@@ -157,23 +109,21 @@ secretKey: "generate-strong-key-here"
**Deployment:** **Deployment:**
```bash ```bash
# Apply unbound # Get CoreDNS service IP
kubectl apply -f unbound.yaml COREDNS_IP=$(kubectl get svc kube-dns -n kube-system -o jsonpath='{.spec.clusterIP}')
# Get unbound service IP
UNBOUND_IP=$(kubectl get svc unbound-dns -n mailu -o jsonpath='{.spec.clusterIP}')
# Deploy Mailu with dev-specific values # Deploy Mailu with dev-specific values
helm upgrade --install mailu mailu/mailu \ helm upgrade --install mailu mailu/mailu \
--namespace mailu \ --namespace bakery-ia \
-f values-dev.yaml \ -f infrastructure/platform/mail/mailu-helm/values.yaml \
--set dnsConfig.nameservers[0]=$UNBOUND_IP -f infrastructure/platform/mail/mailu-helm/dev/values.yaml \
--set global.custom_dns_servers=$COREDNS_IP
``` ```
#### Production (MicroK8s/Ubuntu) #### Production (MicroK8s/Ubuntu)
**Enhancements:** **Enhancements:**
- Use Longhorn or OpenEBS for storage - Use microk8s-hostpath for storage
- Enable monitoring and logging - Enable monitoring and logging
- Configure proper ingress with TLS - Configure proper ingress with TLS
- Set up backup solutions - Set up backup solutions
@@ -181,19 +131,17 @@ helm upgrade --install mailu mailu/mailu \
**Deployment:** **Deployment:**
```bash ```bash
# Enable required MicroK8s addons # Enable required MicroK8s addons
microk8s enable dns storage ingress metallb microk8s enable dns storage ingress
# Apply unbound # Get CoreDNS service IP
kubectl apply -f unbound.yaml COREDNS_IP=$(kubectl get svc kube-dns -n kube-system -o jsonpath='{.spec.clusterIP}')
# Get unbound service IP
UNBOUND_IP=$(kubectl get svc unbound-dns -n mailu -o jsonpath='{.spec.clusterIP}')
# Deploy Mailu with production values # Deploy Mailu with production values
helm upgrade --install mailu mailu/mailu \ helm upgrade --install mailu mailu/mailu \
--namespace mailu \ --namespace bakery-ia \
-f values-prod.yaml \ -f infrastructure/platform/mail/mailu-helm/values.yaml \
--set dnsConfig.nameservers[0]=$UNBOUND_IP -f infrastructure/platform/mail/mailu-helm/prod/values.yaml \
--set global.custom_dns_servers=$COREDNS_IP
``` ```
## Verification Procedures ## Verification Procedures
@@ -201,52 +149,39 @@ helm upgrade --install mailu mailu/mailu \
### DNSSEC Validation Test ### DNSSEC Validation Test
```bash ```bash
# From within a Mailu pod # Test DNS resolution from within a Mailu pod
kubectl exec -it -n mailu deploy/mailu-admin -- bash kubectl exec -it -n bakery-ia deploy/mailu-admin -- bash
# Test DNSSEC validation # Test DNSSEC validation (via CoreDNS -> Cloudflare DNS-over-TLS)
dig @unbound-dns +short +dnssec +adflag example.org A dig +short +dnssec +adflag example.org A
# Should show AD flag in response # Should show AD flag in response indicating DNSSEC validation
``` ```
### Service Health Checks ### Service Health Checks
```bash ```bash
# Check unbound service # Check CoreDNS is running
kubectl get pods -n mailu -l app=unbound kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n mailu -l app=unbound
# Check Mailu components # Check Mailu components
kubectl get pods -n mailu kubectl get pods -n bakery-ia | grep mailu
kubectl logs -n mailu -l app.kubernetes.io/name=mailu kubectl logs -n bakery-ia -l app.kubernetes.io/name=mailu
``` ```
## Monitoring and Maintenance ## Monitoring and Maintenance
### Production Monitoring Setup ### Production Monitoring Setup
```yaml CoreDNS exposes Prometheus metrics on port 9153 by default. Monitor:
# Example monitoring configuration for production - DNS query latency
apiVersion: monitoring.coreos.com/v1 - DNS query success/failure rates
kind: ServiceMonitor - DNS cache hit ratio
metadata:
name: unbound-monitor
namespace: mailu
spec:
selector:
matchLabels:
app: unbound
endpoints:
- port: dns-tcp
interval: 30s
path: /metrics
```
### Backup Strategy ### Backup Strategy
**Production:** **Production:**
- Daily Velero backups of Mailu namespace - Daily Velero backups of bakery-ia namespace
- Weekly database dumps - Weekly database dumps
- Monthly full cluster snapshots - Monthly full cluster snapshots
@@ -258,16 +193,21 @@ spec:
### Common Issues and Solutions ### Common Issues and Solutions
**Issue: DNSSEC validation failures** **Issue: DNS resolution failures**
- Verify unbound pod logs - Verify CoreDNS pods are running
- Check network policies - Check CoreDNS logs: `kubectl logs -n kube-system -l k8s-app=kube-dns`
- Test DNS resolution from within pods - Test DNS resolution: `kubectl run -it --rm dns-test --image=busybox -- nslookup google.com`
**Issue: Mailu pods failing to start** **Issue: Mailu pods failing to start**
- Confirm DNS configuration in values.yaml - Confirm DNS configuration in values.yaml
- Verify unbound service is reachable - Verify CoreDNS service is reachable
- Check resource availability - Check resource availability
**Issue: DNSSEC validation errors**
- Ensure CoreDNS is configured with DNS-over-TLS
- Test with: `dig +dnssec example.org`
- Verify Cloudflare DNS is reachable
**Issue: Performance problems** **Issue: Performance problems**
- Monitor CPU/memory usage - Monitor CPU/memory usage
- Adjust resource limits - Adjust resource limits
@@ -327,12 +267,12 @@ spec:
## Conclusion ## Conclusion
This architecture provides a robust, consistent solution for deploying Mailu across development and production environments. By using Unbound as a dedicated DNSSEC-validating resolver, we ensure compliance with Mailu's requirements while maintaining flexibility and reliability across different Kubernetes platforms. This architecture provides a robust, consistent solution for deploying Mailu across development and production environments. By using CoreDNS with DNS-over-TLS to Cloudflare, we ensure compliance with Mailu's DNSSEC requirements while maintaining simplicity and reliability.
The solution is designed to be: The solution is designed to be:
- **Simple**: Uses existing Kubernetes DNS infrastructure
- **Consistent**: Same core architecture across environments - **Consistent**: Same core architecture across environments
- **Reliable**: Production-grade availability and monitoring - **Reliable**: Production-grade availability
- **Efficient**: Optimized resource usage - **Efficient**: No additional pods required for DNS
- **Maintainable**: Clear documentation and troubleshooting guides
This approach aligns with the Bakery-IA project's requirements for a secure, reliable email infrastructure that can be consistently deployed across different environments. This approach aligns with the Bakery-IA project's requirements for a secure, reliable email infrastructure that can be consistently deployed across different environments.

View File

@@ -71,7 +71,7 @@ A complete multi-tenant SaaS platform consisting of:
│ PostgreSQL (18 DBs) │ Redis │ RabbitMQ │ MinIO │ │ PostgreSQL (18 DBs) │ Redis │ RabbitMQ │ MinIO │
├─────────────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────────────┤
│ LAYER 2: NETWORK & SECURITY │ │ LAYER 2: NETWORK & SECURITY │
Unbound DNS │ CoreDNS │ Ingress Controller │ Cert-Manager │ TLS CoreDNS (DNS-over-TLS) │ Ingress Controller │ Cert-Manager │ TLS │
├─────────────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────────────┤
│ LAYER 1: FOUNDATION │ │ LAYER 1: FOUNDATION │
│ Namespaces │ Storage Classes │ RBAC │ ConfigMaps │ Secrets │ │ Namespaces │ Storage Classes │ RBAC │ ConfigMaps │ Secrets │
@@ -1045,107 +1045,84 @@ kubectl exec -n bakery-ia deployment/gateway -- curl -s http://localhost:8000/he
## Phase 7: Deploy Optional Services ## Phase 7: Deploy Optional Services
### Step 7.1: Deploy Unbound DNS (Required for Mailu) ### Step 7.1: Configure CoreDNS with DNS-over-TLS for DNSSEC
> **Why Unbound?** Mailu requires DNSSEC validation for email security (DKIM/SPF/DMARC via rspamd). > **DNS Architecture:** CoreDNS is configured to use DNS-over-TLS with Cloudflare (1.1.1.1) for DNSSEC validation.
> CoreDNS does NOT support DNSSEC natively, so Unbound provides this capability. > This provides DNSSEC support for Mailu without requiring additional DNS pods.
```bash ```bash
# Clean up any stuck Unbound deployments from previous attempts # Check if CoreDNS is already configured with DNS-over-TLS
kubectl delete deployment -n bakery-ia -l app.kubernetes.io/name=unbound --ignore-not-found kubectl get configmap coredns -n kube-system -o jsonpath='{.data.Corefile}' | grep -o 'tls://1.1.1.1' || echo "Not configured"
# Deploy Unbound DNS resolver with minimal resources # If not configured, update CoreDNS to use DNS-over-TLS with Cloudflare
# Note: prod/values.yaml uses 50m CPU, 64Mi memory - very lightweight cat > /tmp/coredns-corefile.yaml << 'EOF'
helm upgrade --install unbound infrastructure/platform/networking/dns/unbound-helm \ apiVersion: v1
-n bakery-ia \ kind: ConfigMap
-f infrastructure/platform/networking/dns/unbound-helm/values.yaml \ metadata:
-f infrastructure/platform/networking/dns/unbound-helm/prod/values.yaml \ name: coredns
--timeout 5m \ namespace: kube-system
--wait data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . tls://1.1.1.1 tls://1.0.0.1 {
tls_servername cloudflare-dns.com
health_check 5s
}
cache 30 {
disable success cluster.local
disable denial cluster.local
}
loop
reload
loadbalance
}
EOF
# Verify Unbound pod is running kubectl apply -f /tmp/coredns-corefile.yaml
kubectl get pods -n bakery-ia -l app.kubernetes.io/name=unbound
# Restart CoreDNS to apply changes
kubectl rollout restart deployment coredns -n kube-system
kubectl rollout status deployment coredns -n kube-system --timeout=60s
# Verify CoreDNS is running
kubectl get pods -n kube-system -l k8s-app=kube-dns
# Expected: 1/1 Running # Expected: 1/1 Running
# Get Unbound service IP (will be used in subsequent steps) # Get CoreDNS service IP (will be used for Mailu)
UNBOUND_IP=$(kubectl get svc unbound-dns -n bakery-ia -o jsonpath='{.spec.clusterIP}') COREDNS_IP=$(kubectl get svc kube-dns -n kube-system -o jsonpath='{.spec.clusterIP}')
echo "Unbound DNS IP: $UNBOUND_IP" echo "CoreDNS IP: $COREDNS_IP"
# Save this IP - you'll need it for Step 7.2 and 7.3 # Save this IP - you'll need it for Step 7.2
# Test Unbound is working (from inside the cluster) # Test DNS resolution is working
kubectl run -it --rm dns-test --image=busybox --restart=Never -- \ kubectl run -it --rm dns-test --image=busybox --restart=Never -- nslookup google.com
nslookup google.com $UNBOUND_IP
# Expected: Should resolve google.com successfully # Expected: Should resolve google.com successfully
``` ```
**Troubleshooting Unbound:** **Troubleshooting CoreDNS:**
```bash ```bash
# If pod is Pending, check resources # Check CoreDNS logs
kubectl describe pod -n bakery-ia -l app.kubernetes.io/name=unbound | grep -A 5 Events kubectl logs -n kube-system -l k8s-app=kube-dns
# Check node resource availability # Check CoreDNS configuration
kubectl describe node | grep -A 10 "Allocated resources" kubectl get configmap coredns -n kube-system -o yaml
# If resources are exhausted, scale down non-critical services temporarily # Verify DNS-over-TLS is working
kubectl scale deployment signoz-frontend -n bakery-ia --replicas=0 --ignore-not-found kubectl run -it --rm dns-test --image=busybox --restart=Never -- nslookup cloudflare.com
``` ```
### Step 7.2: Configure CoreDNS (Choose ONE Option) ### Step 7.2: Deploy Mailu Email Server
> **Architecture Decision:** You have two options for DNS configuration.
> Choose based on your cluster size and requirements.
#### Option A: Mailu-Only DNSSEC (Recommended for Single-Node)
Only Mailu pods use Unbound for DNSSEC. CoreDNS uses public DNS for everything else.
This is simpler and avoids making Unbound a single point of failure for the entire cluster.
```bash
# Ensure CoreDNS uses public DNS (8.8.8.8, 1.1.1.1)
# This is likely already the default, but verify:
kubectl get configmap coredns -n kube-system -o yaml | grep forward
# If it shows forwarding to Unbound IP, restore to public DNS:
kubectl patch configmap coredns -n kube-system --type merge -p '{
"data": {
"Corefile": ".:53 {\n errors\n health {\n lameduck 5s\n }\n ready\n kubernetes cluster.local in-addr.arpa ip6.arpa {\n pods insecure\n fallthrough in-addr.arpa ip6.arpa\n ttl 30\n }\n prometheus :9153\n forward . 8.8.8.8 1.1.1.1 {\n max_concurrent 1000\n }\n cache 30\n loop\n reload\n loadbalance\n}\n"
}
}'
kubectl rollout restart deployment coredns -n kube-system
kubectl rollout status deployment coredns -n kube-system --timeout=60s
```
#### Option B: Cluster-Wide DNSSEC (For Multi-Node HA)
All cluster DNS queries go through Unbound. Provides DNSSEC for all pods.
Only use this if you have multiple Unbound replicas for high availability.
```bash
# Get Unbound IP
UNBOUND_IP=$(kubectl get svc unbound-dns -n bakery-ia -o jsonpath='{.spec.clusterIP}')
# Patch CoreDNS to forward ALL external queries to Unbound
kubectl patch configmap coredns -n kube-system --type merge -p "{
\"data\": {
\"Corefile\": \".:53 {\\n errors\\n health {\\n lameduck 5s\\n }\\n ready\\n kubernetes cluster.local in-addr.arpa ip6.arpa {\\n pods insecure\\n fallthrough in-addr.arpa ip6.arpa\\n ttl 30\\n }\\n prometheus :9153\\n forward . $UNBOUND_IP {\\n max_concurrent 1000\\n }\\n cache 30\\n loop\\n reload\\n loadbalance\\n}\\n\"
}
}"
kubectl rollout restart deployment coredns -n kube-system
kubectl rollout status deployment coredns -n kube-system --timeout=60s
```
**Verify DNS is working:**
```bash
# Test DNS resolution from a pod
kubectl run -it --rm dns-test --image=busybox --restart=Never -- nslookup google.com
# Expected: Should resolve successfully
```
### Step 7.3: Deploy Mailu Email Server
```bash ```bash
# Add Mailu Helm repository # Add Mailu Helm repository
@@ -1156,18 +1133,18 @@ helm repo update
kubectl apply -f infrastructure/platform/mail/mailu-helm/configs/mailu-admin-credentials-secret.yaml -n bakery-ia kubectl apply -f infrastructure/platform/mail/mailu-helm/configs/mailu-admin-credentials-secret.yaml -n bakery-ia
kubectl apply -f infrastructure/platform/mail/mailu-helm/configs/mailu-certificates-secret.yaml -n bakery-ia kubectl apply -f infrastructure/platform/mail/mailu-helm/configs/mailu-certificates-secret.yaml -n bakery-ia
# Get Unbound DNS IP dynamically # Get CoreDNS service IP dynamically
UNBOUND_IP=$(kubectl get svc unbound-dns -n bakery-ia -o jsonpath='{.spec.clusterIP}') COREDNS_IP=$(kubectl get svc kube-dns -n kube-system -o jsonpath='{.spec.clusterIP}')
echo "Using Unbound DNS IP: $UNBOUND_IP" echo "Using CoreDNS IP: $COREDNS_IP"
# Install Mailu with production configuration # Install Mailu with production configuration
# The --set flag dynamically passes the Unbound IP for DNSSEC validation # The --set flag dynamically passes the CoreDNS IP for DNS resolution
# DNSSEC validation is provided by CoreDNS via DNS-over-TLS to Cloudflare
helm upgrade --install mailu mailu/mailu \ helm upgrade --install mailu mailu/mailu \
-n bakery-ia \ -n bakery-ia \
-f infrastructure/platform/mail/mailu-helm/values.yaml \ -f infrastructure/platform/mail/mailu-helm/values.yaml \
-f infrastructure/platform/mail/mailu-helm/prod/values.yaml \ -f infrastructure/platform/mail/mailu-helm/prod/values.yaml \
--set global.custom_dns_servers="$UNBOUND_IP" \ --set global.custom_dns_servers="$COREDNS_IP" \
--set admin.dnsConfig.nameservers[0]="$UNBOUND_IP" \
--timeout 10m --timeout 10m
# Wait for Mailu to be ready (may take 5-10 minutes) # Wait for Mailu to be ready (may take 5-10 minutes)

154
Tiltfile
View File

@@ -728,100 +728,20 @@ k8s_resource('rabbitmq', resource_deps=['security-setup'], labels=['01-infrastru
k8s_resource('minio', resource_deps=['security-setup'], labels=['01-infrastructure']) k8s_resource('minio', resource_deps=['security-setup'], labels=['01-infrastructure'])
k8s_resource('minio-bucket-init', resource_deps=['minio'], labels=['01-infrastructure']) k8s_resource('minio-bucket-init', resource_deps=['minio'], labels=['01-infrastructure'])
# Unbound DNSSEC Resolver - Infrastructure component for Mailu DNS validation # CoreDNS DNSSEC Configuration - Infrastructure component for Mailu DNS validation
local_resource( local_resource(
'unbound-helm', 'coredns-dnssec',
cmd=''' cmd='''
echo "Deploying Unbound DNS resolver via Helm..." echo "Configuring CoreDNS with DNS-over-TLS for DNSSEC validation..."
echo "" echo ""
# Check if Unbound is already deployed # Check if CoreDNS is already configured with DNS-over-TLS
if helm list -n bakery-ia | grep -q unbound; then CURRENT_FORWARD=$(kubectl get configmap coredns -n kube-system -o jsonpath='{.data.Corefile}' 2>/dev/null | grep -o 'tls://1.1.1.1' || echo "")
echo "Unbound already deployed, checking status..."
helm status unbound -n bakery-ia
else
echo "Installing Unbound..."
# Determine environment (dev or prod) based on context if [ -z "$CURRENT_FORWARD" ]; then
ENVIRONMENT="dev" echo "Updating CoreDNS to use DNS-over-TLS with Cloudflare..."
if [[ "$(kubectl config current-context)" == *"prod"* ]]; then
ENVIRONMENT="prod"
fi
echo "Environment detected: $ENVIRONMENT" # Create a temporary Corefile with the DNS-over-TLS configuration
# Install Unbound with appropriate values
if [ "$ENVIRONMENT" = "dev" ]; then
helm upgrade --install unbound infrastructure/platform/networking/dns/unbound-helm \
-n bakery-ia \
--create-namespace \
-f infrastructure/platform/networking/dns/unbound-helm/values.yaml \
-f infrastructure/platform/networking/dns/unbound-helm/dev/values.yaml \
--timeout 5m \
--wait
else
helm upgrade --install unbound infrastructure/platform/networking/dns/unbound-helm \
-n bakery-ia \
--create-namespace \
-f infrastructure/platform/networking/dns/unbound-helm/values.yaml \
-f infrastructure/platform/networking/dns/unbound-helm/prod/values.yaml \
--timeout 5m \
--wait
fi
echo ""
echo "Unbound deployment completed"
fi
echo ""
echo "Unbound DNS Service Information:"
echo " Service Name: unbound-dns.bakery-ia.svc.cluster.local"
echo " Ports: UDP/TCP 53"
echo " Used by: Mailu for DNS validation"
echo ""
echo "To check pod status: kubectl get pods -n bakery-ia | grep unbound"
''',
resource_deps=['security-setup'],
labels=['01-infrastructure'],
auto_init=True # Auto-deploy with Tilt startup
)
# Mail Infrastructure (Mailu) - Manual trigger for Helm deployment
local_resource(
'mailu-helm',
cmd='''
echo "Deploying Mailu via Helm..."
echo ""
# =====================================================
# Step 1: Ensure Unbound is deployed and get its IP
# =====================================================
echo "Checking Unbound DNS resolver..."
if ! kubectl get svc unbound-dns -n bakery-ia &>/dev/null; then
echo "ERROR: Unbound DNS service not found!"
echo "Please deploy Unbound first by triggering 'unbound-helm' resource"
exit 1
fi
UNBOUND_IP=$(kubectl get svc unbound-dns -n bakery-ia -o jsonpath='{.spec.clusterIP}')
echo "Unbound DNS service IP: $UNBOUND_IP"
# =====================================================
# Step 2: Configure CoreDNS to forward to Unbound
# =====================================================
echo ""
echo "Configuring CoreDNS to forward external queries to Unbound for DNSSEC validation..."
# Check current CoreDNS forward configuration
CURRENT_FORWARD=$(kubectl get configmap coredns -n kube-system -o jsonpath='{.data.Corefile}' | grep -o 'forward \\. [0-9.]*' | awk '{print $3}')
if [ "$CURRENT_FORWARD" != "$UNBOUND_IP" ]; then
echo "Updating CoreDNS to forward to Unbound ($UNBOUND_IP)..."
# Change to project root to ensure correct file paths
cd /Users/urtzialfaro/Documents/bakery-ia
# Create a temporary Corefile with the forwarding configuration
TEMP_COREFILE=$(mktemp) TEMP_COREFILE=$(mktemp)
cat > "$TEMP_COREFILE" << EOF cat > "$TEMP_COREFILE" << EOF
.:53 { .:53 {
@@ -836,8 +756,9 @@ local_resource(
ttl 30 ttl 30
} }
prometheus :9153 prometheus :9153
forward . $UNBOUND_IP { forward . tls://1.1.1.1 tls://1.0.0.1 {
max_concurrent 1000 tls_servername cloudflare-dns.com
health_check 5s
} }
cache 30 { cache 30 {
disable success cluster.local disable success cluster.local
@@ -871,13 +792,44 @@ EOF
kubectl rollout restart deployment coredns -n kube-system kubectl rollout restart deployment coredns -n kube-system
echo "Waiting for CoreDNS to restart..." echo "Waiting for CoreDNS to restart..."
kubectl rollout status deployment coredns -n kube-system --timeout=60s kubectl rollout status deployment coredns -n kube-system --timeout=60s
echo "CoreDNS configured successfully" echo "CoreDNS configured successfully with DNS-over-TLS"
else else
echo "CoreDNS already configured to forward to Unbound" echo "CoreDNS already configured with DNS-over-TLS"
fi fi
# Get CoreDNS service IP
COREDNS_IP=$(kubectl get svc kube-dns -n kube-system -o jsonpath='{.spec.clusterIP}')
echo ""
echo "CoreDNS DNSSEC Configuration:"
echo " CoreDNS IP: $COREDNS_IP"
echo " Upstream: Cloudflare DNS-over-TLS (1.1.1.1, 1.0.0.1)"
echo " DNSSEC: Validated by Cloudflare"
echo " Used by: Mailu for DNS validation"
echo ""
echo "To check CoreDNS status: kubectl get pods -n kube-system -l k8s-app=kube-dns"
''',
resource_deps=['security-setup'],
labels=['01-infrastructure'],
auto_init=True # Auto-deploy with Tilt startup
)
# Mail Infrastructure (Mailu) - Manual trigger for Helm deployment
local_resource(
'mailu-helm',
cmd='''
echo "Deploying Mailu via Helm..."
echo ""
# ===================================================== # =====================================================
# Step 3: Create self-signed TLS certificate for Mailu Front # Step 1: Get CoreDNS service IP
# =====================================================
echo "Getting CoreDNS service IP..."
COREDNS_IP=$(kubectl get svc kube-dns -n kube-system -o jsonpath='{.spec.clusterIP}')
echo "CoreDNS service IP: $COREDNS_IP"
# =====================================================
# Step 2: Create self-signed TLS certificate for Mailu Front
# ===================================================== # =====================================================
echo "" echo ""
echo "Checking Mailu TLS certificates..." echo "Checking Mailu TLS certificates..."
@@ -905,7 +857,7 @@ EOF
fi fi
# ===================================================== # =====================================================
# Step 4: Deploy Mailu via Helm # Step 3: Deploy Mailu via Helm
# ===================================================== # =====================================================
echo "" echo ""
@@ -938,6 +890,7 @@ EOF
--create-namespace \ --create-namespace \
-f infrastructure/platform/mail/mailu-helm/values.yaml \ -f infrastructure/platform/mail/mailu-helm/values.yaml \
-f infrastructure/platform/mail/mailu-helm/dev/values.yaml \ -f infrastructure/platform/mail/mailu-helm/dev/values.yaml \
--set global.custom_dns_servers="$COREDNS_IP" \
--timeout 10m --timeout 10m
else else
helm upgrade --install mailu mailu/mailu \ helm upgrade --install mailu mailu/mailu \
@@ -945,6 +898,7 @@ EOF
--create-namespace \ --create-namespace \
-f infrastructure/platform/mail/mailu-helm/values.yaml \ -f infrastructure/platform/mail/mailu-helm/values.yaml \
-f infrastructure/platform/mail/mailu-helm/prod/values.yaml \ -f infrastructure/platform/mail/mailu-helm/prod/values.yaml \
--set global.custom_dns_servers="$COREDNS_IP" \
--timeout 10m --timeout 10m
fi fi
@@ -953,7 +907,7 @@ EOF
fi fi
# ===================================================== # =====================================================
# Step 5: Apply Mailu Ingress # Step 4: Apply Mailu Ingress
# ===================================================== # =====================================================
echo "" echo ""
echo "Applying Mailu ingress configuration..." echo "Applying Mailu ingress configuration..."
@@ -962,7 +916,7 @@ EOF
echo "Mailu ingress applied for mail.bakery-ia.dev" echo "Mailu ingress applied for mail.bakery-ia.dev"
# ===================================================== # =====================================================
# Step 6: Wait for pods and show status # Step 5: Wait for pods and show status
# ===================================================== # =====================================================
echo "" echo ""
echo "Waiting for Mailu pods to be ready..." echo "Waiting for Mailu pods to be ready..."
@@ -975,16 +929,20 @@ EOF
echo "" echo ""
echo "Mailu Access Information:" echo "Mailu Access Information:"
echo " Admin Panel: https://mail.bakery-ia.dev/admin" echo " Admin Panel: https://mail.bakery-ia.dev/admin"
echo " Webmail: https://mail.bakery-ia.ldev/webmail" echo " Webmail: https://mail.bakery-ia.dev/webmail"
echo " SMTP: mail.bakery-ia.dev:587 (STARTTLS)" echo " SMTP: mail.bakery-ia.dev:587 (STARTTLS)"
echo " IMAP: mail.bakery-ia.dev:993 (SSL/TLS)" echo " IMAP: mail.bakery-ia.dev:993 (SSL/TLS)"
echo "" echo ""
echo "DNS Configuration:"
echo " CoreDNS IP: $COREDNS_IP"
echo " DNSSEC: Provided via DNS-over-TLS (Cloudflare)"
echo ""
echo "To create admin user:" echo "To create admin user:"
echo " Admin user created automatically via initialAccount feature in Helm values" echo " Admin user created automatically via initialAccount feature in Helm values"
echo "" echo ""
echo "To check pod status: kubectl get pods -n bakery-ia | grep mailu" echo "To check pod status: kubectl get pods -n bakery-ia | grep mailu"
''', ''',
resource_deps=['unbound-helm'], # Ensure Unbound is deployed first resource_deps=['coredns-dnssec'], # Ensure CoreDNS DNSSEC is configured first
labels=['01-infrastructure'], labels=['01-infrastructure'],
auto_init=False, # Manual trigger only auto_init=False, # Manual trigger only
) )

View File

@@ -91,7 +91,7 @@ The Bakery-IA platform is organized into distinct infrastructure layers, each wi
│ PostgreSQL (18 DBs) │ Redis │ RabbitMQ │ MinIO │ │ PostgreSQL (18 DBs) │ Redis │ RabbitMQ │ MinIO │
├─────────────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────────────┤
│ LAYER 2: NETWORK & SECURITY │ │ LAYER 2: NETWORK & SECURITY │
Unbound DNS │ CoreDNS │ Ingress Controller │ Cert-Manager │ TLS CoreDNS (DNS-over-TLS) │ Ingress Controller │ Cert-Manager │ TLS │
├─────────────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────────────┤
│ LAYER 1: FOUNDATION │ │ LAYER 1: FOUNDATION │
│ Namespaces │ Storage Classes │ RBAC │ ConfigMaps │ Secrets │ │ Namespaces │ Storage Classes │ RBAC │ ConfigMaps │ Secrets │
@@ -112,11 +112,9 @@ Components must be deployed in a specific order due to dependencies:
3. TLS Certificates (internal + ingress) 3. TLS Certificates (internal + ingress)
4. Unbound DNS Resolver (required for Mailu DNSSEC) 4. CoreDNS Configuration (DNS-over-TLS for DNSSEC)
5. CoreDNS Configuration (forward to Unbound) 5. Ingress Controller & Resources
6. Ingress Controller & Resources
7. Data Layer: PostgreSQL, Redis, RabbitMQ, MinIO 7. Data Layer: PostgreSQL, Redis, RabbitMQ, MinIO
@@ -146,7 +144,6 @@ Components must be deployed in a specific order due to dependencies:
| **Redis** | Caching & sessions | Yes | bakery-ia | | **Redis** | Caching & sessions | Yes | bakery-ia |
| **RabbitMQ** | Message broker | Yes | bakery-ia | | **RabbitMQ** | Message broker | Yes | bakery-ia |
| **MinIO** | Object storage (ML models) | Yes | bakery-ia | | **MinIO** | Object storage (ML models) | Yes | bakery-ia |
| **Unbound DNS** | DNSSEC resolver | For Mailu | bakery-ia |
| **Mailu** | Self-hosted email server | Optional | bakery-ia | | **Mailu** | Self-hosted email server | Optional | bakery-ia |
| **Nominatim** | Geocoding service | Optional | bakery-ia | | **Nominatim** | Geocoding service | Optional | bakery-ia |
| **Gitea** | Git server + container registry | Optional | gitea | | **Gitea** | Git server + container registry | Optional | gitea |
@@ -945,9 +942,8 @@ SMTP_PORT: 587
#### Prerequisites #### Prerequisites
Before deploying Mailu, ensure: Before deploying Mailu, ensure:
1. **Unbound DNS is deployed** (for DNSSEC validation) 1. **CoreDNS is configured** with DNS-over-TLS for DNSSEC validation
2. **CoreDNS is configured** to forward to Unbound 2. **DNS records are configured** for your domain
3. **DNS records are configured** for your domain
#### Step 1: Configure DNS Records #### Step 1: Configure DNS Records
@@ -963,55 +959,64 @@ TXT _dmarc v=DMARC1; p=reject; rua=... Auto
**DKIM record** will be generated after Mailu is running - you'll add it later. **DKIM record** will be generated after Mailu is running - you'll add it later.
#### Step 2: Deploy Unbound DNS Resolver #### Step 2: Configure CoreDNS for DNSSEC (DNS-over-TLS)
Unbound provides DNSSEC validation required by Mailu for email authentication. Mailu requires DNSSEC validation. Configure CoreDNS to use DNS-over-TLS with Cloudflare:
```bash ```bash
# On VPS - Deploy Unbound via Helm # Check if CoreDNS is already configured with DNS-over-TLS
helm upgrade --install unbound infrastructure/platform/networking/dns/unbound-helm \ kubectl get configmap coredns -n kube-system -o jsonpath='{.data.Corefile}' | grep -o 'tls://1.1.1.1' || echo "Not configured"
-n bakery-ia \
--create-namespace \
-f infrastructure/platform/networking/dns/unbound-helm/values.yaml \
-f infrastructure/platform/networking/dns/unbound-helm/prod/values.yaml \
--timeout 5m \
--wait
# Verify Unbound is running # If not configured, update CoreDNS
kubectl get pods -n bakery-ia | grep unbound cat > /tmp/coredns-corefile.yaml << 'EOF'
# Should show: unbound-xxx 1/1 Running apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . tls://1.1.1.1 tls://1.0.0.1 {
tls_servername cloudflare-dns.com
health_check 5s
}
cache 30 {
disable success cluster.local
disable denial cluster.local
}
loop
reload
loadbalance
}
EOF
# Get Unbound service IP (needed for CoreDNS configuration) kubectl apply -f /tmp/coredns-corefile.yaml
UNBOUND_IP=$(kubectl get svc unbound-dns -n bakery-ia -o jsonpath='{.spec.clusterIP}')
echo "Unbound DNS IP: $UNBOUND_IP"
```
#### Step 3: Configure CoreDNS for DNSSEC
Mailu requires DNSSEC validation. Configure CoreDNS to forward external queries to Unbound:
```bash
# Get the Unbound service IP
UNBOUND_IP=$(kubectl get svc unbound-dns -n bakery-ia -o jsonpath='{.spec.clusterIP}')
# Patch CoreDNS to forward to Unbound
kubectl patch configmap coredns -n kube-system --type merge -p "{
\"data\": {
\"Corefile\": \".:53 {\\n errors\\n health {\\n lameduck 5s\\n }\\n ready\\n kubernetes cluster.local in-addr.arpa ip6.arpa {\\n pods insecure\\n fallthrough in-addr.arpa ip6.arpa\\n ttl 30\\n }\\n prometheus :9153\\n forward . $UNBOUND_IP {\\n max_concurrent 1000\\n }\\n cache 30 {\\n disable success cluster.local\\n disable denial cluster.local\\n }\\n loop\\n reload\\n loadbalance\\n}\\n\"
}
}"
# Restart CoreDNS to apply changes # Restart CoreDNS to apply changes
kubectl rollout restart deployment coredns -n kube-system kubectl rollout restart deployment coredns -n kube-system
kubectl rollout status deployment coredns -n kube-system --timeout=60s kubectl rollout status deployment coredns -n kube-system --timeout=60s
# Verify DNSSEC is working # Get CoreDNS service IP (needed for Mailu configuration)
kubectl run -it --rm debug --image=alpine --restart=Never -- \ COREDNS_IP=$(kubectl get svc kube-dns -n kube-system -o jsonpath='{.spec.clusterIP}')
sh -c "apk add drill && drill -D google.com" echo "CoreDNS IP: $COREDNS_IP"
# Should show: ;; flags: ... ad ... (ad = authenticated data = DNSSEC valid)
# Verify DNS resolution is working
kubectl run -it --rm dns-test --image=busybox --restart=Never -- nslookup google.com
``` ```
#### Step 4: Create TLS Certificate Secret #### Step 3: Create TLS Certificate Secret
Mailu Front pod requires a TLS certificate: Mailu Front pod requires a TLS certificate:
@@ -1036,7 +1041,7 @@ rm -rf "$TEMP_DIR"
kubectl get secret mailu-certificates -n bakery-ia kubectl get secret mailu-certificates -n bakery-ia
``` ```
#### Step 5: Create Admin Credentials Secret #### Step 4: Create Admin Credentials Secret
```bash ```bash
# Generate a secure password (or use your own) # Generate a secure password (or use your own)
@@ -1050,30 +1055,35 @@ kubectl create secret generic mailu-admin-credentials \
-n bakery-ia -n bakery-ia
``` ```
#### Step 6: Deploy Mailu via Helm #### Step 5: Deploy Mailu via Helm
```bash ```bash
# Add Mailu Helm repository # Add Mailu Helm repository
helm repo add mailu https://mailu.github.io/helm-charts helm repo add mailu https://mailu.github.io/helm-charts
helm repo update mailu helm repo update mailu
# Get CoreDNS service IP
COREDNS_IP=$(kubectl get svc kube-dns -n kube-system -o jsonpath='{.spec.clusterIP}')
# Deploy Mailu with production values # Deploy Mailu with production values
# Admin user is created automatically via initialAccount feature # Admin user is created automatically via initialAccount feature
# CoreDNS provides DNSSEC validation via DNS-over-TLS to Cloudflare
helm upgrade --install mailu mailu/mailu \ helm upgrade --install mailu mailu/mailu \
-n bakery-ia \ -n bakery-ia \
--create-namespace \ --create-namespace \
-f infrastructure/platform/mail/mailu-helm/values.yaml \ -f infrastructure/platform/mail/mailu-helm/values.yaml \
-f infrastructure/platform/mail/mailu-helm/prod/values.yaml \ -f infrastructure/platform/mail/mailu-helm/prod/values.yaml \
--set global.custom_dns_servers="$COREDNS_IP" \
--timeout 10m --timeout 10m
# Wait for pods to be ready (may take 5-10 minutes for ClamAV) # Wait for pods to be ready (may take 5-10 minutes for ClamAV)
kubectl get pods -n bakery-ia -l app.kubernetes.io/instance=mailu -w kubectl get pods -n bakery-ia -l app.kubernetes.io/instance=mailu -w
# Admin user (admin@bakewise.ai) is created automatically! # Admin user (admin@bakewise.ai) is created automatically!
# Password is the one you set in Step 5 # Password is the one you set in Step 4
``` ```
#### Step 7: Configure DKIM #### Step 6: Configure DKIM
After Mailu is running, get the DKIM key and add it to DNS: After Mailu is running, get the DKIM key and add it to DNS:
@@ -1087,7 +1097,7 @@ kubectl exec -n bakery-ia deployment/mailu-admin -- \
# Value: (the key from above) # Value: (the key from above)
``` ```
#### Step 8: Verify Email Setup #### Step 7: Verify Email Setup
```bash ```bash
# Check all Mailu pods are running # Check all Mailu pods are running
@@ -1117,11 +1127,11 @@ kubectl port-forward -n bakery-ia svc/mailu-front 8080:80
**Issue: Admin pod CrashLoopBackOff with "DNSSEC validation" error** **Issue: Admin pod CrashLoopBackOff with "DNSSEC validation" error**
```bash ```bash
# Verify CoreDNS is forwarding to Unbound # Verify CoreDNS is configured with DNS-over-TLS
kubectl get configmap coredns -n kube-system -o yaml | grep forward kubectl get configmap coredns -n kube-system -o yaml | grep 'tls://'
# Should show: forward . <unbound-ip> # Should show: tls://1.1.1.1 tls://1.0.0.1
# If not, re-run Step 3 above # If not, re-run Step 2 above
``` ```
**Issue: Front pod stuck in ContainerCreating** **Issue: Front pod stuck in ContainerCreating**
@@ -1129,7 +1139,7 @@ kubectl get configmap coredns -n kube-system -o yaml | grep forward
# Check for missing certificate secret # Check for missing certificate secret
kubectl describe pod -n bakery-ia -l app.kubernetes.io/component=front | grep -A5 Events kubectl describe pod -n bakery-ia -l app.kubernetes.io/component=front | grep -A5 Events
# If missing mailu-certificates, re-run Step 4 above # If missing mailu-certificates, re-run Step 3 above
``` ```
**Issue: Admin pod can't connect to Redis** **Issue: Admin pod can't connect to Redis**
@@ -2018,41 +2028,22 @@ Mailu is a full-featured, self-hosted email server with built-in antispam, webma
### Prerequisites ### Prerequisites
Before deploying Mailu: Before deploying Mailu:
- [ ] Unbound DNS resolver deployed (for DNSSEC validation) - [ ] CoreDNS configured with DNS-over-TLS for DNSSEC validation
- [ ] DNS records configured for mail domain - [ ] DNS records configured for mail domain
- [ ] TLS certificates available - [ ] TLS certificates available
- [ ] Mailgun account created and domain verified (for outbound email relay) - [ ] Mailgun account created and domain verified (for outbound email relay)
### Step 1: Deploy Unbound DNS Resolver ### Step 1: Configure CoreDNS for DNSSEC (DNS-over-TLS)
Mailu requires DNSSEC validation for email authentication (DKIM/SPF/DMARC). Mailu requires DNSSEC validation for email authentication (DKIM/SPF/DMARC).
CoreDNS is configured to use DNS-over-TLS with Cloudflare for DNSSEC validation.
```bash ```bash
# Deploy Unbound via Helm # Check if CoreDNS is already configured with DNS-over-TLS
helm upgrade --install unbound infrastructure/platform/networking/dns/unbound-helm \ kubectl get configmap coredns -n kube-system -o jsonpath='{.data.Corefile}' | grep -o 'tls://1.1.1.1' || echo "Not configured"
-n bakery-ia \
--create-namespace \
-f infrastructure/platform/networking/dns/unbound-helm/values.yaml \
-f infrastructure/platform/networking/dns/unbound-helm/prod/values.yaml \
--timeout 5m \
--wait
# Verify Unbound is running # If not configured, update CoreDNS ConfigMap
kubectl get pods -n bakery-ia | grep unbound cat > /tmp/coredns-config.yaml << 'EOF'
# Get Unbound service IP
UNBOUND_IP=$(kubectl get svc unbound-dns -n bakery-ia -o jsonpath='{.spec.clusterIP}')
echo "Unbound DNS IP: $UNBOUND_IP"
```
### Step 2: Configure CoreDNS for DNSSEC
```bash
# Get Unbound IP
UNBOUND_IP=$(kubectl get svc unbound-dns -n bakery-ia -o jsonpath='{.spec.clusterIP}')
# Create updated CoreDNS ConfigMap
cat > /tmp/coredns-config.yaml <<EOF
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
@@ -2072,8 +2063,9 @@ data:
ttl 30 ttl 30
} }
prometheus :9153 prometheus :9153
forward . $UNBOUND_IP { forward . tls://1.1.1.1 tls://1.0.0.1 {
max_concurrent 1000 tls_servername cloudflare-dns.com
health_check 5s
} }
cache 30 { cache 30 {
disable success cluster.local disable success cluster.local
@@ -2092,10 +2084,12 @@ kubectl apply -f /tmp/coredns-config.yaml
kubectl rollout restart deployment coredns -n kube-system kubectl rollout restart deployment coredns -n kube-system
kubectl rollout status deployment coredns -n kube-system --timeout=60s kubectl rollout status deployment coredns -n kube-system --timeout=60s
# Verify DNSSEC is working # Get CoreDNS service IP
kubectl run -it --rm dns-test --image=alpine --restart=Never -- \ COREDNS_IP=$(kubectl get svc kube-dns -n kube-system -o jsonpath='{.spec.clusterIP}')
sh -c "apk add drill && drill -D google.com" echo "CoreDNS IP: $COREDNS_IP"
# Look for "ad" flag (authenticated data) in output
# Verify DNS resolution is working
kubectl run -it --rm dns-test --image=busybox --restart=Never -- nslookup google.com
``` ```
### Step 3: Configure Mailgun (External SMTP Relay) ### Step 3: Configure Mailgun (External SMTP Relay)
@@ -2216,15 +2210,20 @@ kubectl apply -f infrastructure/platform/mail/mailu-helm/configs/mailu-admin-cre
helm repo add mailu https://mailu.github.io/helm-charts helm repo add mailu https://mailu.github.io/helm-charts
helm repo update mailu helm repo update mailu
# Get CoreDNS service IP for Mailu DNS configuration
COREDNS_IP=$(kubectl get svc kube-dns -n kube-system -o jsonpath='{.spec.clusterIP}')
# Deploy Mailu with production values # Deploy Mailu with production values
# Note: # Note:
# - externalRelay uses Mailgun via the secret created in Step 3 # - externalRelay uses Mailgun via the secret created in Step 3
# - initialAccount creates admin user automatically using the secret from Step 6 # - initialAccount creates admin user automatically using the secret from Step 6
# - CoreDNS provides DNSSEC validation via DNS-over-TLS (Cloudflare)
helm upgrade --install mailu mailu/mailu \ helm upgrade --install mailu mailu/mailu \
-n bakery-ia \ -n bakery-ia \
--create-namespace \ --create-namespace \
-f infrastructure/platform/mail/mailu-helm/values.yaml \ -f infrastructure/platform/mail/mailu-helm/values.yaml \
-f infrastructure/platform/mail/mailu-helm/prod/values.yaml \ -f infrastructure/platform/mail/mailu-helm/prod/values.yaml \
--set global.custom_dns_servers="$COREDNS_IP" \
--timeout 10m --timeout 10m
# Wait for pods to be ready (ClamAV may take 5-10 minutes) # Wait for pods to be ready (ClamAV may take 5-10 minutes)
@@ -2306,11 +2305,11 @@ kubectl port-forward -n bakery-ia svc/mailu-front 8080:80
#### Admin Pod CrashLoopBackOff with DNSSEC Error #### Admin Pod CrashLoopBackOff with DNSSEC Error
```bash ```bash
# Verify CoreDNS is forwarding to Unbound # Verify CoreDNS is configured with DNS-over-TLS
kubectl get configmap coredns -n kube-system -o yaml | grep forward kubectl get configmap coredns -n kube-system -o yaml | grep 'tls://'
# Should show: forward . <unbound-ip> # Should show: tls://1.1.1.1 tls://1.0.0.1
# If not configured, re-run Step 2 # If not configured, re-run Step 1
``` ```
#### Front Pod Stuck in ContainerCreating #### Front Pod Stuck in ContainerCreating
@@ -3419,8 +3418,7 @@ kubectl scale deployment monitoring -n bakery-ia --replicas=0
- [ ] End-to-end pipeline test successful - [ ] End-to-end pipeline test successful
### Email Infrastructure (Optional - Mailu) ### Email Infrastructure (Optional - Mailu)
- [ ] Unbound DNS resolver deployed - [ ] CoreDNS configured with DNS-over-TLS for DNSSEC
- [ ] CoreDNS configured for DNSSEC
- [ ] Mailu TLS certificate created - [ ] Mailu TLS certificate created
- [ ] Mailu deployed via Helm - [ ] Mailu deployed via Helm
- [ ] Admin user created - [ ] Admin user created
@@ -3473,8 +3471,7 @@ kubectl scale deployment monitoring -n bakery-ia --replicas=0
- Webhook integration and end-to-end testing - Webhook integration and end-to-end testing
- Troubleshooting guide for CI/CD issues - Troubleshooting guide for CI/CD issues
- **NEW: Mailu Email Server Deployment** - Comprehensive self-hosted email setup - **NEW: Mailu Email Server Deployment** - Comprehensive self-hosted email setup
- Unbound DNS resolver deployment for DNSSEC - CoreDNS configuration with DNS-over-TLS for DNSSEC validation
- CoreDNS configuration for mail authentication
- Mailu Helm deployment with all components - Mailu Helm deployment with all components
- DKIM/SPF/DMARC configuration - DKIM/SPF/DMARC configuration
- Troubleshooting common Mailu issues - Troubleshooting common Mailu issues

View File

@@ -1,38 +0,0 @@
# CoreDNS ConfigMap patch to forward external DNS queries to Unbound for DNSSEC validation
# This is required for Mailu Admin which requires DNSSEC-validating DNS resolver
#
# Apply with: kubectl apply -f coredns-unbound-patch.yaml
# Then restart CoreDNS: kubectl rollout restart deployment coredns -n kube-system
#
# Note: The Unbound service IP (10.104.127.213) may change when the cluster is recreated.
# The setup script will automatically update this based on the actual Unbound service IP.
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . UNBOUND_SERVICE_IP {
max_concurrent 1000
}
cache 30 {
disable success cluster.local
disable denial cluster.local
}
loop
reload
loadbalance
}

View File

@@ -1,34 +1,19 @@
# Development-tuned Mailu configuration # Development-tuned Mailu configuration
global: global:
# Using Unbound DNS for DNSSEC validation (required by Mailu admin) # Using Kubernetes CoreDNS for DNS resolution
# This value is dynamically set via --set during helm install: # CoreDNS is configured with DNS-over-TLS (Cloudflare) for DNSSEC validation
# UNBOUND_IP=$(kubectl get svc unbound-dns -n bakery-ia -o jsonpath='{.spec.clusterIP}') # Default to Kubernetes DNS IP (will be overridden dynamically if needed)
# helm upgrade --install mailu ... --set global.custom_dns_servers="$UNBOUND_IP" custom_dns_servers: "10.96.0.10" # Kubernetes DNS IP
# Default fallback to Kubernetes DNS (will be overridden by --set)
custom_dns_servers: "10.96.0.10" # Override with Unbound IP via --set
# Redis configuration - use built-in Mailu Redis (no authentication needed) # Redis configuration - use built-in Mailu Redis (no authentication needed)
externalRedis: externalRedis:
enabled: false enabled: false
# Component-specific DNS configuration # DNS configuration - use Kubernetes DNS (ClusterFirst)
# Admin requires DNSSEC validation - use Unbound DNS (forwards cluster.local to kube-dns) # CoreDNS provides DNSSEC validation via DNS-over-TLS to Cloudflare
# NOTE: dnsConfig.nameservers is dynamically set via --set during helm install
admin: admin:
dnsPolicy: "None" dnsPolicy: "ClusterFirst"
dnsConfig:
nameservers:
- "10.96.0.10" # Override with Unbound IP via --set admin.dnsConfig.nameservers[0]
searches:
- "bakery-ia.svc.cluster.local"
- "svc.cluster.local"
- "cluster.local"
options:
- name: ndots
value: "5"
# RSPAMD needs Unbound for DNSSEC validation (DKIM/SPF/DMARC checks)
# Using ClusterFirst with search domains + Kubernetes DNS which can forward to Unbound
rspamd: rspamd:
dnsPolicy: "ClusterFirst" dnsPolicy: "ClusterFirst"

View File

@@ -1,15 +1,15 @@
# Production-tuned Mailu configuration # Production-tuned Mailu configuration
global: global:
# Using Kubernetes cluster DNS for name resolution # Using Kubernetes CoreDNS for DNS resolution
custom_dns_servers: "10.96.0.10" # Kubernetes cluster DNS IP # CoreDNS is configured with DNS-over-TLS (Cloudflare) for DNSSEC validation
custom_dns_servers: "10.152.183.10" # MicroK8s CoreDNS IP
# Redis configuration - use built-in Mailu Redis (no authentication needed for internal) # Redis configuration - use built-in Mailu Redis (no authentication needed for internal)
externalRedis: externalRedis:
enabled: false enabled: false
# DNS configuration for production # DNS configuration for production
# Use Kubernetes DNS (ClusterFirst) which forwards to Unbound via CoreDNS # Use Kubernetes DNS (ClusterFirst) - CoreDNS provides DNSSEC via DNS-over-TLS
# This is configured automatically by the mailu-helm Tilt resource
admin: admin:
dnsPolicy: "ClusterFirst" dnsPolicy: "ClusterFirst"

View File

@@ -4,11 +4,10 @@
# ============================================================================= # =============================================================================
# This script automates the deployment of Mailu mail server for production. # This script automates the deployment of Mailu mail server for production.
# It handles: # It handles:
# 1. Unbound DNS deployment (for DNSSEC validation) # 1. CoreDNS configuration with DNS-over-TLS for DNSSEC validation
# 2. CoreDNS configuration (forward to Unbound) # 2. TLS certificate secret creation
# 3. TLS certificate secret creation # 3. Admin credentials secret creation
# 4. Admin credentials secret creation # 4. Mailu Helm deployment (admin user created automatically via initialAccount)
# 5. Mailu Helm deployment (admin user created automatically via initialAccount)
# #
# Usage: # Usage:
# ./deploy-mailu-prod.sh [--domain DOMAIN] [--admin-password PASSWORD] # ./deploy-mailu-prod.sh [--domain DOMAIN] [--admin-password PASSWORD]
@@ -99,52 +98,15 @@ fi
print_success "Prerequisites check passed" print_success "Prerequisites check passed"
# ============================================================================= # =============================================================================
# Step 1: Deploy Unbound DNS Resolver # Step 1: Configure CoreDNS with DNS-over-TLS for DNSSEC
# ============================================================================= # =============================================================================
print_step "Step 1: Deploying Unbound DNS resolver..." print_step "Step 1: Configuring CoreDNS with DNS-over-TLS for DNSSEC validation..."
if kubectl get deployment unbound -n "$NAMESPACE" &>/dev/null; then # Check if CoreDNS is already configured with DNS-over-TLS
print_success "Unbound already deployed" CURRENT_FORWARD=$(kubectl get configmap coredns -n kube-system -o jsonpath='{.data.Corefile}' 2>/dev/null | grep -o 'tls://1.1.1.1' || echo "")
else
helm upgrade --install unbound "$MAILU_HELM_DIR/../../networking/dns/unbound-helm" \
-n "$NAMESPACE" \
-f "$MAILU_HELM_DIR/../../networking/dns/unbound-helm/values.yaml" \
-f "$MAILU_HELM_DIR/../../networking/dns/unbound-helm/prod/values.yaml" \
--timeout 5m \
--wait
print_success "Unbound deployed" if [ -z "$CURRENT_FORWARD" ]; then
fi echo "Updating CoreDNS to use DNS-over-TLS with Cloudflare for DNSSEC validation..."
# Wait for Unbound to be ready
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=unbound -n "$NAMESPACE" --timeout=120s
# Get Unbound service IP (dynamic resolution)
echo "Waiting for Unbound service to get assigned IP..."
for i in {1..30}; do
UNBOUND_IP=$(kubectl get svc unbound-dns -n "$NAMESPACE" -o jsonpath='{.spec.clusterIP}' 2>/dev/null || echo "")
if [ -n "$UNBOUND_IP" ] && [ "$UNBOUND_IP" != "<none>" ]; then
echo "Unbound DNS service IP: $UNBOUND_IP"
break
fi
if [ $i -eq 30 ]; then
print_error "Failed to get Unbound service IP"
exit 1
fi
sleep 2
echo "Waiting for Unbound service IP... (attempt $i/30)"
done
# =============================================================================
# Step 2: Configure CoreDNS to Forward to Unbound
# =============================================================================
print_step "Step 2: Configuring CoreDNS for DNSSEC validation..."
# Check current CoreDNS forward configuration
CURRENT_FORWARD=$(kubectl get configmap coredns -n kube-system -o jsonpath='{.data.Corefile}' | grep -o 'forward \. [0-9.]*' | awk '{print $3}' || echo "")
if [ "$CURRENT_FORWARD" != "$UNBOUND_IP" ]; then
echo "Updating CoreDNS to forward to Unbound ($UNBOUND_IP)..."
# Create a temporary file with the CoreDNS configuration # Create a temporary file with the CoreDNS configuration
TEMP_COREFILE=$(mktemp) TEMP_COREFILE=$(mktemp)
@@ -161,8 +123,9 @@ if [ "$CURRENT_FORWARD" != "$UNBOUND_IP" ]; then
ttl 30 ttl 30
} }
prometheus :9153 prometheus :9153
forward . $UNBOUND_IP { forward . tls://1.1.1.1 tls://1.0.0.1 {
max_concurrent 1000 tls_servername cloudflare-dns.com
health_check 5s
} }
cache 30 { cache 30 {
disable success cluster.local disable success cluster.local
@@ -187,15 +150,19 @@ EOF
kubectl rollout restart deployment coredns -n kube-system kubectl rollout restart deployment coredns -n kube-system
kubectl rollout status deployment coredns -n kube-system --timeout=60s kubectl rollout status deployment coredns -n kube-system --timeout=60s
print_success "CoreDNS configured to forward to Unbound" print_success "CoreDNS configured with DNS-over-TLS for DNSSEC validation"
else else
print_success "CoreDNS already configured for Unbound" print_success "CoreDNS already configured with DNS-over-TLS"
fi fi
# Get CoreDNS service IP for Mailu configuration
COREDNS_IP=$(kubectl get svc kube-dns -n kube-system -o jsonpath='{.spec.clusterIP}')
echo "CoreDNS service IP: $COREDNS_IP"
# ============================================================================= # =============================================================================
# Step 3: Create TLS Certificate Secret # Step 2: Create TLS Certificate Secret
# ============================================================================= # =============================================================================
print_step "Step 3: Creating TLS certificate secret..." print_step "Step 2: Creating TLS certificate secret..."
if kubectl get secret mailu-certificates -n "$NAMESPACE" &>/dev/null; then if kubectl get secret mailu-certificates -n "$NAMESPACE" &>/dev/null; then
print_success "TLS certificate secret already exists" print_success "TLS certificate secret already exists"
@@ -217,9 +184,9 @@ else
fi fi
# ============================================================================= # =============================================================================
# Step 4: Create Admin Credentials Secret # Step 3: Create Admin Credentials Secret
# ============================================================================= # =============================================================================
print_step "Step 4: Creating admin credentials secret..." print_step "Step 3: Creating admin credentials secret..."
if kubectl get secret mailu-admin-credentials -n "$NAMESPACE" &>/dev/null; then if kubectl get secret mailu-admin-credentials -n "$NAMESPACE" &>/dev/null; then
print_success "Admin credentials secret already exists" print_success "Admin credentials secret already exists"
@@ -243,33 +210,28 @@ else
fi fi
# ============================================================================= # =============================================================================
# Step 5: Deploy Mailu via Helm # Step 4: Deploy Mailu via Helm
# ============================================================================= # =============================================================================
print_step "Step 5: Deploying Mailu via Helm..." print_step "Step 4: Deploying Mailu via Helm..."
# Add Mailu Helm repository # Add Mailu Helm repository
helm repo add mailu https://mailu.github.io/helm-charts 2>/dev/null || true helm repo add mailu https://mailu.github.io/helm-charts 2>/dev/null || true
helm repo update mailu helm repo update mailu
# Create temporary values file with dynamic DNS server # Deploy Mailu with CoreDNS configuration
TEMP_VALUES=$(mktemp)
cat "$MAILU_HELM_DIR/values.yaml" | sed "s/# custom_dns_servers: \"\" # Will be set dynamically by deployment script/custom_dns_servers: \"$UNBOUND_IP\"/" > "$TEMP_VALUES"
# Deploy Mailu with dynamic DNS configuration
helm upgrade --install mailu mailu/mailu \ helm upgrade --install mailu mailu/mailu \
-n "$NAMESPACE" \ -n "$NAMESPACE" \
-f "$TEMP_VALUES" \ -f "$MAILU_HELM_DIR/values.yaml" \
-f "$MAILU_HELM_DIR/prod/values.yaml" \ -f "$MAILU_HELM_DIR/prod/values.yaml" \
--set global.custom_dns_servers="$COREDNS_IP" \
--timeout 10m --timeout 10m
rm -f "$TEMP_VALUES"
print_success "Mailu Helm release deployed (admin user will be created automatically)" print_success "Mailu Helm release deployed (admin user will be created automatically)"
# ============================================================================= # =============================================================================
# Step 6: Wait for Pods to be Ready # Step 5: Wait for Pods to be Ready
# ============================================================================= # =============================================================================
print_step "Step 6: Waiting for Mailu pods to be ready..." print_step "Step 5: Waiting for Mailu pods to be ready..."
echo "This may take 5-10 minutes (ClamAV takes time to initialize)..." echo "This may take 5-10 minutes (ClamAV takes time to initialize)..."
@@ -307,6 +269,10 @@ echo " Webmail: https://mail.$DOMAIN/webmail"
echo " SMTP: mail.$DOMAIN:587 (STARTTLS)" echo " SMTP: mail.$DOMAIN:587 (STARTTLS)"
echo " IMAP: mail.$DOMAIN:993 (SSL)" echo " IMAP: mail.$DOMAIN:993 (SSL)"
echo "" echo ""
echo "DNS Configuration:"
echo " CoreDNS is configured with DNS-over-TLS (Cloudflare) for DNSSEC validation"
echo " CoreDNS IP: $COREDNS_IP"
echo ""
echo "Next Steps:" echo "Next Steps:"
echo " 1. Configure DNS records (A, MX, SPF, DMARC)" echo " 1. Configure DNS records (A, MX, SPF, DMARC)"
echo " 2. Get DKIM key: kubectl exec -n $NAMESPACE deployment/mailu-admin -- cat /dkim/$DOMAIN.dkim.pub" echo " 2. Get DKIM key: kubectl exec -n $NAMESPACE deployment/mailu-admin -- cat /dkim/$DOMAIN.dkim.pub"

View File

@@ -3,16 +3,13 @@
# Phase 7: Deploy Optional Services - Fixed Version # Phase 7: Deploy Optional Services - Fixed Version
# ============================================================================= # =============================================================================
# This script deploys the optional services for production: # This script deploys the optional services for production:
# 1. Unbound DNS (with dynamic IP resolution) # 1. CoreDNS configuration with DNS-over-TLS for DNSSEC validation
# 2. CoreDNS configuration for DNSSEC # 2. Mailu Email Server
# 3. Mailu Email Server # 3. SigNoz Monitoring
# 4. SigNoz Monitoring
# #
# Fixed issues: # DNS Architecture:
# - Removed static ClusterIP that caused CIDR range conflicts # - CoreDNS uses DNS-over-TLS with Cloudflare (1.1.1.1) for DNSSEC validation
# - Implemented dynamic IP resolution for Unbound DNS # - Mailu uses CoreDNS for DNS resolution (internal K8s + external DNSSEC)
# - Updated CoreDNS patching to use dynamic IP
# - Updated Mailu configuration to use dynamic DNS server
# ============================================================================= # =============================================================================
set -e set -e
@@ -40,49 +37,15 @@ print_success() {
} }
# ============================================================================= # =============================================================================
# Step 7.1: Deploy Unbound DNS (with dynamic IP) # Step 7.1: Configure CoreDNS with DNS-over-TLS for DNSSEC
# ============================================================================= # =============================================================================
print_step "Step 7.1: Deploying Unbound DNS resolver (dynamic IP)..." print_step "Step 7.1: Configuring CoreDNS with DNS-over-TLS for DNSSEC validation..."
if kubectl get deployment unbound -n "$NAMESPACE" &>/dev/null; then # Check if CoreDNS is already configured with DNS-over-TLS
print_success "Unbound already deployed" CURRENT_FORWARD=$(kubectl get configmap coredns -n kube-system -o jsonpath='{.data.Corefile}' 2>/dev/null | grep -o 'tls://1.1.1.1' || echo "")
else
helm upgrade --install unbound infrastructure/platform/networking/dns/unbound-helm \
-n "$NAMESPACE" \
-f infrastructure/platform/networking/dns/unbound-helm/values.yaml \
-f infrastructure/platform/networking/dns/unbound-helm/prod/values.yaml \
--timeout 5m \
--wait
print_success "Unbound deployed" if [ -z "$CURRENT_FORWARD" ]; then
fi echo "Updating CoreDNS to use DNS-over-TLS with Cloudflare..."
# Wait for Unbound service to get assigned IP
print_step "Waiting for Unbound service to get assigned IP..."
for i in {1..30}; do
UNBOUND_IP=$(kubectl get svc unbound-dns -n "$NAMESPACE" -o jsonpath='{.spec.clusterIP}' 2>/dev/null || echo "")
if [ -n "$UNBOUND_IP" ] && [ "$UNBOUND_IP" != "<none>" ]; then
echo "Unbound DNS service IP: $UNBOUND_IP"
break
fi
if [ $i -eq 30 ]; then
print_error "Failed to get Unbound service IP"
exit 1
fi
sleep 2
echo "Waiting for Unbound service IP... (attempt $i/30)"
done
# =============================================================================
# Step 7.2: Configure CoreDNS for DNSSEC (dynamic IP)
# =============================================================================
print_step "Step 7.2: Configuring CoreDNS for DNSSEC validation..."
# Check current CoreDNS forward configuration
CURRENT_FORWARD=$(kubectl get configmap coredns -n kube-system -o jsonpath='{.data.Corefile}' | grep -o 'forward \. [0-9.]*' | awk '{print $3}' || echo "")
if [ "$CURRENT_FORWARD" != "$UNBOUND_IP" ]; then
echo "Updating CoreDNS to forward to Unbound ($UNBOUND_IP)..."
# Create a temporary file with the CoreDNS configuration # Create a temporary file with the CoreDNS configuration
TEMP_COREFILE=$(mktemp) TEMP_COREFILE=$(mktemp)
@@ -99,8 +62,9 @@ if [ "$CURRENT_FORWARD" != "$UNBOUND_IP" ]; then
ttl 30 ttl 30
} }
prometheus :9153 prometheus :9153
forward . $UNBOUND_IP { forward . tls://1.1.1.1 tls://1.0.0.1 {
max_concurrent 1000 tls_servername cloudflare-dns.com
health_check 5s
} }
cache 30 { cache 30 {
disable success cluster.local disable success cluster.local
@@ -125,33 +89,32 @@ EOF
kubectl rollout restart deployment coredns -n kube-system kubectl rollout restart deployment coredns -n kube-system
kubectl rollout status deployment coredns -n kube-system --timeout=60s kubectl rollout status deployment coredns -n kube-system --timeout=60s
print_success "CoreDNS configured to forward to Unbound" print_success "CoreDNS configured with DNS-over-TLS"
else else
print_success "CoreDNS already configured for Unbound" print_success "CoreDNS already configured with DNS-over-TLS"
fi fi
# Get CoreDNS service IP
COREDNS_IP=$(kubectl get svc kube-dns -n kube-system -o jsonpath='{.spec.clusterIP}')
echo "CoreDNS service IP: $COREDNS_IP"
# ============================================================================= # =============================================================================
# Step 7.3: Deploy Mailu Email Server (dynamic DNS) # Step 7.2: Deploy Mailu Email Server
# ============================================================================= # =============================================================================
print_step "Step 7.3: Deploying Mailu Email Server..." print_step "Step 7.2: Deploying Mailu Email Server..."
# Add Mailu Helm repository # Add Mailu Helm repository
helm repo add mailu https://mailu.github.io/helm-charts 2>/dev/null || true helm repo add mailu https://mailu.github.io/helm-charts 2>/dev/null || true
helm repo update mailu helm repo update mailu
# Create temporary values file with dynamic DNS server # Deploy Mailu with CoreDNS configuration
TEMP_VALUES=$(mktemp)
cat infrastructure/platform/mail/mailu-helm/values.yaml | sed "s/# custom_dns_servers: \"\" # Will be set dynamically by deployment script/custom_dns_servers: \"$UNBOUND_IP\"/" > "$TEMP_VALUES"
# Deploy Mailu with dynamic DNS configuration
helm upgrade --install mailu mailu/mailu \ helm upgrade --install mailu mailu/mailu \
-n "$NAMESPACE" \ -n "$NAMESPACE" \
-f "$TEMP_VALUES" \ -f infrastructure/platform/mail/mailu-helm/values.yaml \
-f infrastructure/platform/mail/mailu-helm/prod/values.yaml \ -f infrastructure/platform/mail/mailu-helm/prod/values.yaml \
--set global.custom_dns_servers="$COREDNS_IP" \
--timeout 10m --timeout 10m
rm -f "$TEMP_VALUES"
print_success "Mailu Helm release deployed" print_success "Mailu Helm release deployed"
# Wait for Mailu pods to be ready # Wait for Mailu pods to be ready
@@ -165,9 +128,9 @@ kubectl wait --for=condition=ready pod -l app.kubernetes.io/component=admin -n "
print_success "Mailu deployment completed" print_success "Mailu deployment completed"
# ============================================================================= # =============================================================================
# Step 7.4: Deploy SigNoz Monitoring # Step 7.3: Deploy SigNoz Monitoring
# ============================================================================= # =============================================================================
print_step "Step 7.4: Deploying SigNoz Monitoring..." print_step "Step 7.3: Deploying SigNoz Monitoring..."
# Add SigNoz Helm repository # Add SigNoz Helm repository
helm repo add signoz https://charts.signoz.io 2>/dev/null || true helm repo add signoz https://charts.signoz.io 2>/dev/null || true
@@ -196,9 +159,8 @@ echo -e "${GREEN}Phase 7 Deployment Complete!${NC}"
echo "==============================================" echo "=============================================="
echo "" echo ""
echo "Deployed Services:" echo "Deployed Services:"
echo "Unbound DNS (IP: $UNBOUND_IP)" echo " ✓ CoreDNS (configured with DNS-over-TLS for DNSSEC)"
echo " ✓ CoreDNS (configured for DNSSEC)" echo "Mailu Email Server (using CoreDNS IP: $COREDNS_IP)"
echo " ✓ Mailu Email Server"
echo " ✓ SigNoz Monitoring" echo " ✓ SigNoz Monitoring"
echo "" echo ""
echo "Next Steps:" echo "Next Steps:"

View File

@@ -3,8 +3,8 @@
# Global DNS configuration for DNSSEC validation # Global DNS configuration for DNSSEC validation
global: global:
# Using Unbound DNS resolver directly for DNSSEC validation # Using Kubernetes CoreDNS with DNS-over-TLS for DNSSEC validation
# Unbound service is available at unbound-dns.bakery-ia.svc.cluster.local # CoreDNS is configured to forward external queries to Cloudflare (tls://1.1.1.1)
# DNS server IP will be dynamically resolved during deployment # DNS server IP will be dynamically resolved during deployment
# custom_dns_servers: "" # Will be set dynamically by deployment script # custom_dns_servers: "" # Will be set dynamically by deployment script
@@ -230,6 +230,5 @@ networkPolicy:
# DNS Policy Configuration # DNS Policy Configuration
# Use Kubernetes DNS (ClusterFirst) for internal service resolution # Use Kubernetes DNS (ClusterFirst) for internal service resolution
# DNSSEC validation for email is handled by rspamd component # DNSSEC validation is provided by CoreDNS with DNS-over-TLS (Cloudflare)
# Note: For production with DNSSEC needs, configure CoreDNS to forward to Unbound
dnsPolicy: "ClusterFirst" dnsPolicy: "ClusterFirst"

View File

@@ -1,18 +0,0 @@
apiVersion: v2
name: unbound
description: A Helm chart for deploying Unbound DNS resolver for Bakery-IA
type: application
version: 0.1.0
appVersion: "1.19.1"
maintainers:
- name: Bakery-IA Team
email: devops@bakery-ia.com
keywords:
- dns
- resolver
- caching
- unbound
home: https://www.nlnetlabs.nl/projects/unbound/
sources:
- https://github.com/NLnetLabs/unbound
- https://hub.docker.com/r/mvance/unbound

View File

@@ -1,64 +0,0 @@
# Development values for unbound DNS resolver
# Using same configuration as production for consistency
# Use official image for development (same as production)
image:
repository: "mvance/unbound"
tag: "latest"
pullPolicy: "IfNotPresent"
# Resource settings (slightly lower than production for dev)
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "300m"
memory: "384Mi"
# Single replica for development (can be scaled if needed)
replicaCount: 1
# Development annotations
podAnnotations:
environment: "development"
managed-by: "helm"
# Probe settings (same as production but slightly faster)
probes:
readiness:
initialDelaySeconds: 10
periodSeconds: 30
command: "drill @127.0.0.1 -p 53 example.org || echo 'DNS query test'"
liveness:
initialDelaySeconds: 30
periodSeconds: 60
command: "drill @127.0.0.1 -p 53 example.org || echo 'DNS query test'"
# Custom Unbound forward records for Kubernetes DNS
config:
enabled: true
# The mvance/unbound image includes forward-records.conf
# We need to add Kubernetes-specific forwarding zones
forwardRecords: |
# Forward all queries to Cloudflare with DNSSEC (catch-all)
forward-zone:
name: "."
forward-tls-upstream: yes
forward-addr: 1.1.1.1@853#cloudflare-dns.com
forward-addr: 1.0.0.1@853#cloudflare-dns.com
# Additional server config to mark cluster.local as insecure (no DNSSEC)
# and use stub zones for Kubernetes internal DNS (more reliable than forward)
serverConfig: |
domain-insecure: "cluster.local."
private-domain: "cluster.local."
local-zone: "10.in-addr.arpa." nodefault
stub-zone:
name: "cluster.local."
stub-addr: 10.96.0.10
stub-zone:
name: "10.in-addr.arpa."
stub-addr: 10.96.0.10

View File

@@ -1,130 +0,0 @@
# Production-specific values for unbound DNS resolver
# Overrides for the production environment
#
# ARCHITECTURE NOTE:
# Unbound provides DNSSEC validation required by Mailu (rspamd for DKIM/SPF/DMARC).
# CoreDNS does NOT support DNSSEC, so we need Unbound as a dedicated resolver.
#
# Two deployment options:
# 1. Mailu-only: Only Mailu pods use Unbound (via dnsPolicy: None)
# - CoreDNS forwards to public DNS (8.8.8.8, 1.1.1.1)
# - Lower resource usage, simpler architecture
#
# 2. Cluster-wide: CoreDNS forwards ALL external queries to Unbound
# - All pods get DNSSEC validation
# - Higher resource usage, single point of failure for DNS
# Use official image for production
image:
repository: "mvance/unbound"
tag: "latest"
pullPolicy: "IfNotPresent"
# Production resource settings - MINIMAL for single-node clusters
# Unbound is very lightweight - DNS queries use minimal CPU
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "256Mi"
# Single replica for single-node clusters (saves resources)
# Increase to 2 for multi-node HA deployments
replicaCount: 1
# Production annotations
podAnnotations:
environment: "production"
critical: "true"
# Anti-affinity disabled for single-node clusters
# Uncomment for multi-node HA deployments
# affinity:
# podAntiAffinity:
# preferredDuringSchedulingIgnoredDuringExecution:
# - weight: 100
# podAffinityTerm:
# labelSelector:
# matchExpressions:
# - key: app.kubernetes.io/name
# operator: In
# values:
# - unbound
# topologyKey: "kubernetes.io/hostname"
# Production probe settings (more conservative)
# NOTE: mvance/unbound image does NOT have 'nc' (netcat), use 'drill' instead
probes:
readiness:
initialDelaySeconds: 10
periodSeconds: 30
command: "drill @127.0.0.1 localhost || exit 1"
liveness:
initialDelaySeconds: 30
periodSeconds: 60
command: "drill @127.0.0.1 localhost || exit 1"
# Custom unbound configuration to forward internal Kubernetes zones to CoreDNS
config:
enabled: true
content: |
server:
interface: 0.0.0.0
port: 53
do-ip4: yes
do-ip6: no
do-udp: yes
do-tcp: yes
# Access control - allow all private networks
access-control: 10.0.0.0/8 allow
access-control: 172.16.0.0/12 allow
access-control: 192.168.0.0/16 allow
access-control: 127.0.0.0/8 allow
# DNSSEC validation (required for Mailu)
auto-trust-anchor-file: "/opt/unbound/etc/unbound/root.key"
# Performance tuning
num-threads: 2
msg-cache-size: 32m
rrset-cache-size: 64m
cache-min-ttl: 60
cache-max-ttl: 86400
# Logging
verbosity: 1
log-queries: no
log-replies: no
# Private addresses - don't send to upstream
private-address: 10.0.0.0/8
private-address: 172.16.0.0/12
private-address: 192.168.0.0/16
# Forward Kubernetes internal zones to CoreDNS (10.152.183.10 for MicroK8s)
forward-zone:
name: "cluster.local."
forward-addr: 10.152.183.10
forward-zone:
name: "svc.cluster.local."
forward-addr: 10.152.183.10
forward-zone:
name: "bakery-ia.svc.cluster.local."
forward-addr: 10.152.183.10
# Forward in-addr.arpa for reverse DNS lookups within cluster
forward-zone:
name: "in-addr.arpa."
forward-addr: 10.152.183.10
# Forward all other queries to upstream DNS with DNSSEC
forward-zone:
name: "."
forward-tls-upstream: yes
forward-addr: 1.1.1.1@853#cloudflare-dns.com
forward-addr: 8.8.8.8@853#dns.google

View File

@@ -1,63 +0,0 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "unbound.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
*/}}
{{- define "unbound.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "unbound.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Common labels
*/}}
{{- define "unbound.labels" -}}
helm.sh/chart: {{ include "unbound.chart" . }}
{{ include "unbound.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
{{/*
Selector labels
*/}}
{{- define "unbound.selectorLabels" -}}
app.kubernetes.io/name: {{ include "unbound.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: dns
app.kubernetes.io/part-of: bakery-ia
{{- end -}}
{{/*
Create the name of the service account to use
*/}}
{{- define "unbound.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{ default (include "unbound.fullname" .) .Values.serviceAccount.name }}
{{- else -}}
{{ default "default" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}

View File

@@ -1,22 +0,0 @@
{{- if .Values.config.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "unbound.fullname" . }}-config
namespace: {{ .Values.global.namespace }}
labels:
{{- include "unbound.labels" . | nindent 4 }}
data:
{{- if .Values.config.forwardRecords }}
forward-records.conf: |
{{ .Values.config.forwardRecords | indent 4 }}
{{- end }}
{{- if .Values.config.serverConfig }}
a-records.conf: |
{{ .Values.config.serverConfig | indent 4 }}
{{- end }}
{{- if .Values.config.content }}
unbound.conf: |
{{ .Values.config.content | indent 4 }}
{{- end }}
{{- end }}

View File

@@ -1,117 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "unbound.fullname" . }}
namespace: {{ .Values.global.namespace }}
labels:
{{- include "unbound.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "unbound.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "unbound.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "unbound.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: dns-udp
containerPort: {{ .Values.service.ports.dnsUdp }}
protocol: UDP
- name: dns-tcp
containerPort: {{ .Values.service.ports.dnsTcp }}
protocol: TCP
{{- if .Values.probes.readiness.enabled }}
readinessProbe:
exec:
command:
- sh
- -c
- {{ .Values.probes.readiness.command | quote }}
initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
{{- end }}
{{- if .Values.probes.liveness.enabled }}
livenessProbe:
exec:
command:
- sh
- -c
- {{ .Values.probes.liveness.command | quote }}
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
{{- if .Values.config.enabled }}
{{- if .Values.config.forwardRecords }}
- name: unbound-config
mountPath: /opt/unbound/etc/unbound/forward-records.conf
subPath: forward-records.conf
{{- end }}
{{- if .Values.config.serverConfig }}
- name: unbound-config
mountPath: /opt/unbound/etc/unbound/a-records.conf
subPath: a-records.conf
{{- end }}
{{- if .Values.config.content }}
- name: unbound-config
mountPath: /opt/unbound/etc/unbound/unbound.conf
subPath: unbound.conf
{{- end }}
{{- end }}
{{- with .Values.volumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.env }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
volumes:
{{- if .Values.config.enabled }}
- name: unbound-config
configMap:
name: {{ include "unbound.fullname" . }}-config
{{- end }}
{{- with .Values.volumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.extraInitContainers }}
initContainers:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.extraContainers }}
containers:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -1,27 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.global.dnsServiceName }}
namespace: {{ .Values.global.namespace }}
labels:
{{- include "unbound.labels" . | nindent 4 }}
{{- with .Values.serviceAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
{{- if .Values.service.clusterIP }}
clusterIP: {{ .Values.service.clusterIP }}
{{- end }}
ports:
- name: dns-udp
port: {{ .Values.service.ports.dnsUdp }}
targetPort: {{ .Values.service.ports.dnsUdp }}
protocol: UDP
- name: dns-tcp
port: {{ .Values.service.ports.dnsTcp }}
targetPort: {{ .Values.service.ports.dnsTcp }}
protocol: TCP
selector:
{{- include "unbound.selectorLabels" . | nindent 4 }}

View File

@@ -1,13 +0,0 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "unbound.serviceAccountName" . }}
namespace: {{ .Values.global.namespace }}
labels:
{{- include "unbound.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end -}}

View File

@@ -1,105 +0,0 @@
# Default values for unbound DNS resolver
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
#
# PURPOSE: Provides DNSSEC validation for Mailu email server
# CoreDNS does NOT support DNSSEC, so Unbound fills this gap.
# Mailu's rspamd requires DNSSEC for DKIM/SPF/DMARC validation.
# Global settings
global:
# DNS service name for other services to reference
dnsServiceName: "unbound-dns"
namespace: "bakery-ia"
# Unbound image configuration
image:
repository: "mvance/unbound"
tag: "latest"
pullPolicy: "IfNotPresent"
# Deployment configuration
replicaCount: 1
# Resource limits and requests
# Unbound is very lightweight - these minimal resources are sufficient
resources:
requests:
cpu: "25m"
memory: "32Mi"
limits:
cpu: "100m"
memory: "128Mi"
# Security context
securityContext:
capabilities:
add: ["NET_BIND_SERVICE"]
# Service configuration
service:
type: "ClusterIP"
# Dynamic ClusterIP - Kubernetes will assign automatically
# clusterIP: "" # Leave empty for automatic assignment
ports:
dnsUdp: 53
dnsTcp: 53
# Health probes configuration
# NOTE: mvance/unbound image does NOT have 'nc' (netcat), use 'drill' instead
probes:
readiness:
enabled: true
initialDelaySeconds: 10
periodSeconds: 30
# Use drill (DNS lookup tool included in unbound image)
command: "drill @127.0.0.1 localhost || exit 1"
liveness:
enabled: true
initialDelaySeconds: 30
periodSeconds: 60
# Use drill (DNS lookup tool included in unbound image)
command: "drill @127.0.0.1 localhost || exit 1"
# Additional environment variables
env: {}
# Additional volume mounts
volumeMounts: []
# Additional volumes
volumes: []
# Node selector
nodeSelector: {}
# Tolerations
tolerations: []
# Affinity
affinity: {}
# Pod annotations
podAnnotations: {}
# Service annotations
serviceAnnotations: {}
# Custom unbound configuration
config:
enabled: false
# Additional containers (sidecars)
extraContainers: []
# Additional init containers
extraInitContainers: []
# Service account configuration
serviceAccount:
create: false
annotations: {}
name: ""
# Pod security context
podSecurityContext: {}

View File

@@ -45,6 +45,7 @@ spec:
containers: containers:
- name: postgres - name: postgres
image: postgres:17-alpine image: postgres:17-alpine
command: ["docker-entrypoint.sh", "-c", "config_file=/etc/postgresql/postgresql.conf"]
ports: ports:
- containerPort: 5432 - containerPort: 5432
name: postgres name: postgres
@@ -66,11 +67,23 @@ spec:
value: demo_session_db value: demo_session_db
- name: PGDATA - name: PGDATA
value: /var/lib/postgresql/data/pgdata value: /var/lib/postgresql/data/pgdata
- name: POSTGRES_HOST_SSL
value: "on"
- name: PGSSLCERT
value: /tls/server-cert.pem
- name: PGSSLKEY
value: /tls/server-key.pem
- name: PGSSLROOTCERT
value: /tls/ca-cert.pem
volumeMounts: volumeMounts:
- name: demo-session-db-data - name: demo-session-db-data
mountPath: /var/lib/postgresql/data mountPath: /var/lib/postgresql/data
- name: init-scripts
mountPath: /docker-entrypoint-initdb.d
- name: tls-certs-writable - name: tls-certs-writable
mountPath: /tls mountPath: /tls
- name: postgres-config
mountPath: /etc/postgresql
readOnly: true readOnly: true
resources: resources:
requests: requests:
@@ -103,6 +116,9 @@ spec:
- name: demo-session-db-data - name: demo-session-db-data
persistentVolumeClaim: persistentVolumeClaim:
claimName: demo-session-db-pvc claimName: demo-session-db-pvc
- name: init-scripts
configMap:
name: postgres-init-config
- name: tls-certs-source - name: tls-certs-source
secret: secret:
secretName: postgres-tls secretName: postgres-tls
@@ -115,6 +131,9 @@ spec:
path: ca-cert.pem path: ca-cert.pem
- name: tls-certs-writable - name: tls-certs-writable
emptyDir: {} emptyDir: {}
- name: postgres-config
configMap:
name: postgres-logging-config
--- ---