Azure Load Balancer and Ingress Setup Guide
This guide walks through the complete setup of Azure Load Balancer, nginx-ingress controller, and cert-manager for the Fawkes platform on Azure AKS.
Architecture Overview
Internet
↓
Azure Load Balancer (Standard SKU)
↓
nginx-ingress-controller (2+ pods)
↓
Kubernetes Services (backstage, jenkins, etc.)
Prerequisites
- Azure AKS cluster provisioned (see Azure AKS Provisioning)
kubectlconfigured for the cluster- ArgoCD installed (optional, for GitOps deployment)
- Azure CLI installed (for DNS and resource verification)
Components
1. nginx-ingress Controller
The nginx-ingress controller provides:
- Layer 7 HTTP/HTTPS routing
- TLS termination
- Load balancing across service pods
- High availability with multiple replicas
- Auto-scaling based on CPU/memory
- Prometheus metrics
2. Azure Load Balancer
Azure automatically creates a Standard SKU Load Balancer when you deploy a LoadBalancer service:
- Public IP assignment
- Health probes
- Port forwarding (80, 443)
- Session affinity
- Zone redundancy (in supported regions)
3. cert-manager
Automates TLS certificate management:
- Let's Encrypt integration
- Automatic certificate issuance and renewal
- HTTP-01 and DNS-01 challenge support
- Azure DNS integration for wildcard certificates
Deployment Steps
Step 1: Deploy nginx-ingress Controller
Option A: Using ArgoCD (Recommended)
# Apply the ArgoCD Application
kubectl apply -f platform/apps/ingress-nginx/ingress-nginx-azure-application.yaml
# Check ArgoCD sync status
kubectl get application ingress-nginx -n fawkes
Option B: Using Helm
# Add the nginx-ingress Helm repository
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
# Install with Azure values
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
-f platform/apps/ingress-nginx/values-azure.yaml
Verify Deployment
# Run validation script
./platform/apps/ingress-nginx/validate-azure.sh
# Check pods
kubectl get pods -n ingress-nginx
# Check service and get external IP
kubectl get svc ingress-nginx-controller -n ingress-nginx
# Wait for external IP to be assigned (can take 2-3 minutes)
kubectl wait --for=jsonpath='{.status.loadBalancer.ingress[0].ip}' \
svc/ingress-nginx-controller -n ingress-nginx --timeout=300s
Step 2: Configure Azure DNS (Optional)
Configure Terraform Variables
Edit infra/azure/terraform.tfvars:
# Enable DNS zone creation
dns_zone_name = "fawkes.yourdomain.com"
# Set to true after ingress-nginx is deployed
create_dns_records = true
# Default name of the public IP (or your custom static IP name)
ingress_public_ip_name = "kubernetes"
Apply Terraform
cd infra/azure
# Initialize Terraform (if not done already)
terraform init
# Plan to see what will be created
terraform plan
# Apply changes
terraform apply
# Get nameservers for DNS delegation
terraform output dns_zone_name_servers
Delegate DNS (at your domain registrar)
Update your domain's nameservers with the values from the Terraform output.
Verify DNS
# Wait for DNS propagation (can take up to 48 hours)
dig jenkins.fawkes.yourdomain.com
dig focalboard.fawkes.yourdomain.com
# Should resolve to your ingress external IP
Step 3: Deploy cert-manager
Deploy cert-manager
# Apply the ArgoCD Application
kubectl apply -f platform/apps/cert-manager/cert-manager-application.yaml
# Wait for cert-manager to be ready
kubectl wait --for=condition=available --timeout=300s \
deployment/cert-manager -n cert-manager
kubectl wait --for=condition=available --timeout=300s \
deployment/cert-manager-webhook -n cert-manager
kubectl wait --for=condition=available --timeout=300s \
deployment/cert-manager-cainjector -n cert-manager
Configure ClusterIssuers
# Update email address in issuer files
sed -i 's/platform-team@example.com/your-email@example.com/g' \
platform/apps/cert-manager/cluster-issuer-*.yaml
# Apply ClusterIssuers
kubectl apply -f platform/apps/cert-manager/cluster-issuer-letsencrypt-staging.yaml
kubectl apply -f platform/apps/cert-manager/cluster-issuer-letsencrypt-prod.yaml
# Verify issuers are ready
kubectl get clusterissuer
kubectl describe clusterissuer letsencrypt-prod
Verify cert-manager
# Run validation script
./platform/apps/cert-manager/validate.sh
# Check CRDs
kubectl get crd | grep cert-manager
Step 4: Test with Sample Application
Deploy Test Echo Server
# Deploy test ingress with echo server
kubectl apply -f platform/apps/ingress-nginx/test-ingress.yaml
# Get external IP
EXTERNAL_IP=$(kubectl get svc ingress-nginx-controller -n ingress-nginx \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo "External IP: $EXTERNAL_IP"
Test HTTP Access
# Test with nip.io (no DNS required)
curl http://test.${EXTERNAL_IP}.nip.io
# Or with custom domain (if DNS is configured)
curl http://test.fawkes.yourdomain.com
Request TLS Certificate
Create an Ingress with cert-manager annotation:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: test-app-tls
namespace: ingress-test
annotations:
cert-manager.io/cluster-issuer: letsencrypt-staging # Use staging first!
spec:
ingressClassName: nginx
tls:
- hosts:
- test.fawkes.yourdomain.com
secretName: test-app-tls
rules:
- host: test.fawkes.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: echo-server
port:
number: 80
# Apply the ingress
kubectl apply -f test-ingress-tls.yaml
# Watch certificate creation
kubectl get certificate -n ingress-test -w
# Check certificate details
kubectl describe certificate test-app-tls -n ingress-test
# Once ready, test HTTPS
curl https://test.fawkes.yourdomain.com
Switch to Production Issuer
Once verified with staging:
# Update annotation to use production issuer
kubectl annotate ingress test-app-tls -n ingress-test \
cert-manager.io/cluster-issuer=letsencrypt-prod --overwrite
# Delete the staging certificate to trigger re-issuance
kubectl delete certificate test-app-tls -n ingress-test
kubectl delete secret test-app-tls -n ingress-test
Advanced Configuration
Static Public IP
To use a pre-created static public IP:
# Create public IP in node resource group
az network public-ip create \
--resource-group MC_fawkes-rg_fawkes-aks_eastus \
--name fawkes-ingress-pip \
--sku Standard \
--allocation-method Static \
--dns-name fawkes-ingress
# Get the IP address
az network public-ip show \
--resource-group MC_fawkes-rg_fawkes-aks_eastus \
--name fawkes-ingress-pip \
--query ipAddress -o tsv
# Update values-azure.yaml annotations:
# service.beta.kubernetes.io/azure-load-balancer-resource-group: "MC_fawkes-rg_fawkes-aks_eastus"
# service.beta.kubernetes.io/azure-pip-name: "fawkes-ingress-pip"
Wildcard Certificates with Azure DNS
For wildcard certificates (e.g., *.fawkes.yourdomain.com):
- Configure Azure DNS ClusterIssuer:
# Update cluster-issuer-letsencrypt-dns-azure.yaml with:
# - Azure subscription ID
# - Resource group
# - DNS zone name
# - Managed Identity client ID
kubectl apply -f platform/apps/cert-manager/cluster-issuer-letsencrypt-dns-azure.yaml
- Create wildcard certificate:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-tls
namespace: fawkes
spec:
secretName: wildcard-tls
dnsNames:
- "*.fawkes.yourdomain.com"
- fawkes.yourdomain.com
issuerRef:
name: letsencrypt-dns-azure
kind: ClusterIssuer
Internal Load Balancer
For private ingress (only accessible within VNet):
Add annotation to service:
service.beta.kubernetes.io/azure-load-balancer-internal: "true"
PROXY Protocol
If your Azure Load Balancer uses PROXY protocol:
Update values-azure.yaml:
config:
use-proxy-protocol: "true"
Monitoring
Prometheus Metrics
# Port-forward to metrics endpoint
kubectl port-forward -n ingress-nginx \
svc/ingress-nginx-controller-metrics 9402:10254
# Query metrics
curl http://localhost:9402/metrics | grep nginx_ingress
Grafana Dashboards
Import the official nginx-ingress dashboard:
- Dashboard ID: 9614 (from grafana.com)
Alerts
PrometheusRules are configured for:
- Controller down alert
- High error rate alert
- Certificate expiration alert (via cert-manager)
Troubleshooting
External IP Not Assigned
# Check service events
kubectl describe svc ingress-nginx-controller -n ingress-nginx
# Check Azure Load Balancer
az network lb list --resource-group MC_fawkes-rg_fawkes-aks_*
# Check if quota is exceeded
az vm list-usage --location eastus -o table
Ingress Not Accessible
# Check controller logs
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller --tail=100
# Check ingress resource
kubectl describe ingress <name> -n <namespace>
# Verify IngressClass
kubectl get ingressclass
Certificate Not Issuing
# Check certificate status
kubectl describe certificate <name> -n <namespace>
# Check certificate request
kubectl get certificaterequest -n <namespace>
# Check ACME challenge
kubectl get challenge -n <namespace>
kubectl describe challenge <name> -n <namespace>
# Check cert-manager logs
kubectl logs -n cert-manager deployment/cert-manager --tail=100
DNS Not Resolving
# Check DNS records in Azure
az network dns record-set a list \
--resource-group fawkes-rg \
--zone-name fawkes.yourdomain.com
# Test DNS resolution
dig +short test.fawkes.yourdomain.com
# Check nameserver delegation
dig NS fawkes.yourdomain.com
Security Best Practices
- Use Production Certificates: Always test with staging issuer first
- Enable HSTS: Force HTTPS with strict transport security
- Restrict Access: Use Azure Network Security Groups
- Monitor Certificates: Set up alerts for expiring certificates
- Use Strong TLS: Disable weak cipher suites
- Rate Limiting: Configure rate limits in nginx
- WAF Integration: Consider Azure Application Gateway with WAF
Cost Optimization
- Standard Load Balancer: ~$18/month + data processing
- Public IP: ~$3/month
- DNS Zone: $0.50/zone + queries
- cert-manager: Free (Let's Encrypt)
Total estimated cost: ~$22-25/month