DEV Community

iapilgrim
iapilgrim

Posted on

Phase 5 - AKS with Azure DNS + NGINX Ingress + cert-manager

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.dev
  • api.az.innopy.dev

And secured them with valid public TLS certificates.

This guide includes:

✅ 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
Enter fullscreen mode Exit fullscreen mode

For certificate issuance:

cert-manager
   ↓
Let's Encrypt (HTTP-01 challenge)
   ↓
Temporary solver ingress
   ↓
Validation
   ↓
TLS Secret stored in cluster
Enter fullscreen mode Exit fullscreen mode

🌍 Step 1 — Create Azure DNS Zone

If you don’t already have the zone:

az network dns zone create \
  --resource-group <dns-rg> \
  --name az.innopy.dev
Enter fullscreen mode Exit fullscreen mode

Verify:

az network dns zone list -o table
Enter fullscreen mode Exit fullscreen mode

🌐 Step 2 — Point Subdomains to AKS Ingress

Get your NGINX public IP:

kubectl get svc -n ingress-nginx
Enter fullscreen mode Exit fullscreen mode

Set variables:

RG_DNS=<dns-rg>
DNS_ZONE=az.innopy.dev
INGRESS_IP=<public-ip>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Verify propagation:

dig +short hello.az.innopy.dev
dig +short api.az.innopy.dev
Enter fullscreen mode Exit fullscreen mode

Must return your ingress public IP.


🔐 Step 3 — Install cert-manager

Install CRDs and controller (recommended via Helm in production).

Verify:

kubectl get pods -n cert-manager
Enter fullscreen mode Exit fullscreen mode

All pods must be Running.


🔑 Step 4 — Create Let’s Encrypt ClusterIssuer

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
Enter fullscreen mode Exit fullscreen mode

Apply:

kubectl apply -f clusterissuer.yaml
Enter fullscreen mode Exit fullscreen mode

🌍 Step 5 — Create TLS-enabled Ingress

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
Enter fullscreen mode Exit fullscreen mode

Apply:

kubectl apply -f ingress.yaml
Enter fullscreen mode Exit fullscreen mode

🔎 Step 6 — Monitor Certificate Issuance

kubectl get certificate
kubectl get challenges
kubectl get orders
Enter fullscreen mode Exit fullscreen mode

Successful issuance will show:

READY: True
Order completed successfully
Enter fullscreen mode Exit fullscreen mode

🌐 Step 7 — Test HTTPS

curl -v https://hello.az.innopy.dev
curl -v https://api.az.innopy.dev
Enter fullscreen mode Exit fullscreen mode

You should see:

SSL certificate verify ok.
issuer: Let's Encrypt
Enter fullscreen mode Exit fullscreen mode

🎉 You now have real public TLS on AKS.


🚨 Complete Troubleshooting Guide

Here are the real-world issues we hit and how to fix them.


❌ Challenge Stuck in Pending

Check:

kubectl describe challenge <name>
Enter fullscreen mode Exit fullscreen mode

Most common causes:

1️⃣ DNS not resolving

dig +short hello.az.innopy.dev
Enter fullscreen mode Exit fullscreen mode

Must match ingress IP exactly.


2️⃣ Port 80 not reachable

Let’s Encrypt must access:

http://hello.az.innopy.dev/.well-known/acme-challenge/<token>
Enter fullscreen mode Exit fullscreen mode

Test:

curl http://hello.az.innopy.dev
Enter fullscreen mode Exit fullscreen mode

3️⃣ HTTPS Redirect Blocking HTTP-01

If you have:

nginx.ingress.kubernetes.io/ssl-redirect: "true"
Enter fullscreen mode Exit fullscreen mode

Remove it during certificate issuance.

Let HTTP remain open.

After cert is issued, you can re-enable redirect.


4️⃣ Wrong Ingress Class

Your ClusterIssuer must match:

ingressClassName: nginx
Enter fullscreen mode Exit fullscreen mode

If class mismatches → solver ingress not created.


5️⃣ No Solver Ingress Created

Check:

kubectl get ingress
Enter fullscreen mode Exit fullscreen mode

You should see:

cm-acme-http-solver-xxxx
Enter fullscreen mode Exit fullscreen mode

If not → cert-manager RBAC or config issue.


6️⃣ cert-manager Logs

kubectl logs -n cert-manager deploy/cert-manager
Enter fullscreen mode Exit fullscreen mode

Look for:

  • permission errors
  • webhook errors
  • ACME failures

🔄 Certificate Renewal

cert-manager automatically renews certificates before expiration (usually at ~60 days).

Check expiry:

kubectl describe certificate az-chromia-tls
Enter fullscreen mode Exit fullscreen mode

No manual action required.


🏁 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.

Top comments (0)