Phase 5 - AKS with Azure DNS + NGINX Ingress + cert-manager
Source: Dev.to
In this lab, we built a production‑grade HTTPS setup for applications running on:
- Azure Kubernetes Service
- Azure DNS
- NGINX Ingress Controller
- cert‑manager
- Let’s Encrypt
We exposed:
hello.az.innopy.devapi.az.innopy.dev
and secured them with valid public TLS certificates.
Guide Overview
- ✅ Azure DNS zone setup
- ✅ A‑record configuration
- ✅ cert‑manager configuration
- ✅ TLS‑enabled Ingress
- ✅ Complete troubleshooting section
🏗 Architecture Overview
User (HTTPS)
↓
Azure Public IP
↓
Azure Load Balancer
↓
NGINX Ingress Controller
↓
Kubernetes Services
↓
Pods
Certificate issuance flow
cert-manager
↓
Let’s Encrypt (HTTP‑01 challenge)
↓
Temporary solver Ingress
↓
Validation
↓
TLS Secret stored in cluster
🌍 Step 1 — Create Azure DNS Zone
If you don’t already have the zone:
az network dns zone create \
--resource-group \
--name az.innopy.dev
Verify:
az network dns zone list -o table
🌐 Step 2 — Point Subdomains to AKS Ingress
-
Get your NGINX public IP:
kubectl get svc -n ingress-nginx -
Set variables (replace placeholders with your values):
RG_DNS= DNS_ZONE=az.innopy.dev INGRESS_IP= -
Create A record for
hello:az network dns record-set a create \ --resource-group $RG_DNS \ --zone-name $DNS_ZONE \ --name hello \ --ttl 300 az network dns record-set a add-record \ --resource-group $RG_DNS \ --zone-name $DNS_ZONE \ --record-set-name hello \ --ipv4-address $INGRESS_IP -
Repeat for
api:az network dns record-set a create \ --resource-group $RG_DNS \ --zone-name $DNS_ZONE \ --name api \ --ttl 300 az network dns record-set a add-record \ --resource-group $RG_DNS \ --zone-name $DNS_ZONE \ --record-set-name api \ --ipv4-address $INGRESS_IP -
Verify propagation:
dig +short hello.az.innopy.dev dig +short api.az.innopy.dev
Both commands must return your ingress public IP.
🔐 Step 3 — Install cert‑manager
Install CRDs and controller (recommended via Helm in production). Then verify:
kubectl get pods -n cert-manager
All pods should be Running.
🔑 Step 4 — Create Let’s Encrypt ClusterIssuer
clusterissuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-production
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@innopy.dev
privateKeySecretRef:
name: letsencrypt-production-account-key
solvers:
- http01:
ingress:
class: nginx
Apply:
kubectl apply -f clusterissuer.yaml
🌍 Step 5 — Create TLS‑enabled Ingress
ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: chromia-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-production"
spec:
ingressClassName: nginx
tls:
- hosts:
- hello.az.innopy.dev
- api.az.innopy.dev
secretName: az-chromia-tls
rules:
- host: hello.az.innopy.dev
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello-service
port:
number: 80
- host: api.az.innopy.dev
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
Apply:
kubectl apply -f ingress.yaml
🔎 Step 6 — Monitor Certificate Issuance
kubectl get certificate
kubectl get challenges
kubectl get orders
Successful issuance will show:
READY: True
Order completed successfully
🌐 Step 7 — Test HTTPS
curl -v https://hello.az.innopy.dev
curl -v https://api.az.innopy.dev
You should see:
SSL certificate verify ok.
issuer: Let's Encrypt
🎉 You now have real public TLS on AKS.
🚨 Complete Troubleshooting Guide
❌ Challenge Stuck in Pending
Check the challenge details:
kubectl describe challenge
Most common causes
1️⃣ DNS not resolving
dig +short hello.az.innopy.dev
The result must match the ingress IP exactly.
2️⃣ Port 80 not reachable
Let’s Encrypt must be able to access:
http://hello.az.innopy.dev/.well-known/acme-challenge/
Ensure any network security groups, firewalls, or NGINX configurations allow inbound HTTP traffic on port 80.
Test Connectivity
curl http://hello.az.innopy.dev
HTTPS Redirect Blocking HTTP‑01
If you have the annotation:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
Action: Remove it while the certificate is being issued. Re‑enable the redirect after the cert is issued.
Wrong Ingress Class
Your ClusterIssuer must specify the same ingress class that your controller uses, e.g.:
ingressClassName: nginx
A mismatch prevents the solver ingress from being created.
No Solver Ingress Created
Check existing ingresses:
kubectl get ingress
You should see an ingress similar to cm-acme-http-solver-xxxx. If it’s missing, there may be an RBAC or configuration issue with cert‑manager.
cert‑manager Logs
kubectl logs -n cert-manager deploy/cert-manager
Look for permission errors, webhook errors, or ACME failures.
🔄 Certificate Renewal
cert-manager automatically renews certificates before they expire (typically around day 60 of a 90‑day cert).
Check the certificate’s expiry date:
kubectl describe certificate az-chromia-tls
No manual action is required; renewal happens automatically.
🏁 Final Result
You now have:
- ✅ Azure‑managed DNS
- ✅ Public subdomains
- ✅ AKS‑hosted applications
- ✅ Automated certificate issuance
- ✅ Automatic renewal
- ✅ Production‑grade HTTPS
This setup is fully cloud‑native and scalable.