Online Course

Stop Installing NGINX Ingress Controller Manually on AKS. Here’s a Better Way

azure kubernetes terraform Jun 08, 2025

Deploying applications in Kubernetes can be tricky, especially when it comes to managing NGINX Ingress Controllers on Azure Kubernetes Service (AKS).

If you are installing NGINX Ingress Controllers manually with Helm charts, you are doing it wrong.

In this article, I'll show you how the AKS Application Routing Add-On can save you time and effort, making your deployment process a breeze.

By the end of the article, you will have learned how to implement the AKS Application Routing Add-On effectively, utilizing Infrastructure as Code using Terraform, saving time and reducing complexity in your Kubernetes deployments.

 

The Problem with Manual NGINX Ingress Controller

Complexity: Installing the NGINX Ingress Controller manually means constant struggle with complex configurations, Helm charts, and constant updates. This can be a real headache, taking your focus away from what’s important: building great features!

Maintenance Burden: When you manually handle the NGINX Ingress, you face the ongoing hassle of updates and configuration management. This maintenance burden takes up your time and can distract you from enhancing your application’s performance.

 

About Application Routing Add-On

The Application Routing Add-On for AKS is a smart, fully managed service that makes managing Ingress controllers very simple. By using this add-on, you can let Azure take care of the setup and maintenance, freeing you up to focus on deploying your applications. 

Benefits of the Application Routing Add-On:

  • Easy Configuration: Setup is quick! Azure manages all configurations through the cluster.
  • Automatic Updates: The service keeps the Ingress Controller updated, reducing security risks.
  • Integrated DNS Management: The add-on seamlessly integrates with Azure DNS for both public and private zones.

 

Key Resources

 

Prerequisites

Before beginning, ensure you have:

  1. Azure CLI installed and authenticated
  2. Terraform  installed
  3. Proper Azure permissions (Owner or Contributor + User Access Administrator)

 

Step 1: Terraform Code

Deploying the Application Routing Add-On can be easily automated with the Terraform configuration provided in this Git repo. Below is an outline of the necessary Terraform code to get started:

  • Define Provider and Subscription
  • Create Resource Groups and AKS Cluster
  • Enable Application Routing Add-On

 

Clone the project repo and inspect the files

git clone https://github.com/rceraline/devops.git
cd devops/2025-05-aks-web-app-addon

Folders Content

Figure 1: Git repository with the Terraform configuration files and Kubernetes Command + Script

 

Terraform Code Breakdown

Here’s a detailed overview of what each file configures:

 

data.tf

data "azurerm_client_config" "current" {}

This section retrieves the Azure client configuration for the current authenticated user. We’ll use it later to access the Tenant ID for the Key Vault creation or to retrieve the current User ID for role assignment.

 

main.tf

locals {
  aks_name             = "aks-addon-01"
  key_vault_name       = "kv20250524"
  resource_group_name  = "rg-aks-addon-01"
  custom_dns_zone_name = "sometestcustomdomain.com"
}

resource "azurerm_resource_group" "rg" {
  name     = local.resource_group_name
  location = "Canada Central"
}

## Key Vault
resource "azurerm_key_vault" "kv" {
  name                        = local.key_vault_name
  location                    = azurerm_resource_group.rg.location
  resource_group_name         = azurerm_resource_group.rg.name
  enabled_for_disk_encryption = true
  tenant_id                   = data.azurerm_client_config.current.tenant_id
  soft_delete_retention_days  = 7
  purge_protection_enabled    = false

  sku_name = "standard"

  enable_rbac_authorization = true
}

resource "azurerm_role_assignment" "administrator" {
  scope                = azurerm_key_vault.kv.id
  role_definition_name = "Key Vault Administrator"
  principal_id         = data.azurerm_client_config.current.object_id
}

## Identities
resource "azurerm_user_assigned_identity" "controlplane" {
  location            = azurerm_resource_group.rg.location
  name                = "id-controlplane-01"
  resource_group_name = azurerm_resource_group.rg.name
}

resource "azurerm_role_assignment" "controlplane_resourcegroup_contributor" {
  scope                = azurerm_resource_group.rg.id
  role_definition_name = "Contributor"
  principal_id         = azurerm_user_assigned_identity.controlplane.principal_id
}

resource "azurerm_role_assignment" "web_app_routing_private_dns_zone" {
  scope                = azurerm_dns_zone.domain.id
  role_definition_name = "DNS Zone Contributor"
  principal_id         = azurerm_kubernetes_cluster.aks.web_app_routing[0].web_app_routing_identity[0].object_id
}

resource "azurerm_role_assignment" "web_app_routing_key_vault_user" {
  scope                = azurerm_key_vault.kv.id
  role_definition_name = "Key Vault Secrets User"
  principal_id         = azurerm_kubernetes_cluster.aks.web_app_routing[0].web_app_routing_identity[0].object_id
}

## Cluster
resource "azurerm_kubernetes_cluster" "aks" {
  name                = local.aks_name
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  dns_prefix          = local.aks_name
  sku_tier            = "Standard"

  default_node_pool {
    name       = "system"
    node_count = 3
    vm_size    = "Standard_D2s_v3"

    upgrade_settings {
      drain_timeout_in_minutes      = 0
      max_surge                     = "10%"
      node_soak_duration_in_minutes = 0
    }
  }

  identity {
    type         = "UserAssigned"
    identity_ids = [azurerm_user_assigned_identity.controlplane.id]
  }

  network_profile {
    network_plugin = "kubenet"
  }

  key_vault_secrets_provider {
    secret_rotation_enabled = true
  }

  web_app_routing {
    dns_zone_ids = [azurerm_dns_zone.domain.id]
  }

  depends_on = [azurerm_role_assignment.controlplane_resourcegroup_contributor]
}

## DNS
resource "azurerm_dns_zone" "domain" {
  name                = local.custom_dns_zone_name
  resource_group_name = azurerm_resource_group.rg.name
}

Local Variables:

  • Defines key variables for AKS name, Key Vault name, resource group name, and custom DNS zone name. The Key Vault name must be unique.

Resource Group:

  • Creates an Azure Resource Group called rg-aks-addon-01 in "Canada Central."

Key Vault:

  • Sets up an Azure Key Vault for storing secrets, with disk encryption enabled and soft delete for 7 days. It also includes role assignments for administrative permissions.

User-Assigned Identity:

  • Creates a managed identity for the AKS control plane.

Role Assignments:

  • Grants the "Contributor" role for the resource group to the control plane identity, and assigns "DNS Zone Contributor" and "Key Vault Secrets User" roles for managing the web application routing identity.

Kubernetes Cluster:

  • Deploys an AKS cluster named aks-addon-01 with a default node pool of 3 Standard_D2s_v3 nodes. Configures identity management, key vault secrets provider, and web application routing with DNS zones.

DNS Zone:

  • Creates a DNS zone for sometestcustomdomain.com.

 

providers.tf

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = ">=4.26.0"
    }
  }
}

provider "azurerm" {
  subscription_id = "" // update with your subscription ID
  features {
  }
}
  • Specifies required providers (azurerm) with version constraint.
  • Configures the Azure provider with the subscription ID (to be updated with your Azu subscription ID).

 

Deploy Infrastructure

Once everything is configured, initialize and deploy the solution:

terraform init
terraform apply

 

Step 2: Demo

To illustrate the ease of using the Application Routing Add-On, we will proceed through a hands-on demo. After the deployment is completed, we will have 2 resource groups created:

Figure 2: 2 resource groups are created.

MC_rg-aks-addon-01_aks-addon-01_canadacentral is the default resource group created by Azure Kubernetes Service, while rg-aks-addon-01 is the resource group we created from the Terraform configuration.

 

Figure 3: rg-aks-addon-01 resource group resources.

We have a DNS zone, an AKS cluster, a control plane identity, and a key vault.

 

1. Create a Self-Signed Certificate

To create the certificate, we will be following the manifests/policy.json can be found here in the Git Repository.

 

az keyvault certificate create --vault-name kv20250524 -n my-certificate -p @policy.json

This command creates a self-signed certificate stored in the Azure Key Vault named kv20250524. The certificate's properties are defined in the policy.json file, which includes details such as key size, validity, and auto-renewal settings.

 

2. Connect to AKS

az aks get-credentials --name aks-addon-01 --resource-group rg-aks-addon-01

This command retrieves the access credentials for your AKS cluster named aks-addon-01 within the resource group rg-aks-addon-01. This allows you to interact with your AKS cluster using kubectl.

 

3. Create a Namespace and Deploy a Test Application

kubectl create namespace aks-store
kubectl apply -f https://raw.githubusercontent.com/Azure-Samples/aks-store-demo/main/sample-manifests/docs/app-routing/aks-store-deployments-and-services.yaml -n aks-store

First, a new namespace called aks-store is created to organize your deployments. Next, a sample application is deployed in this namespace using a manifest YAML file from the specified GitHub repository.

Figure 4: Following output determines the successful application deployment for test Microsoft service.

 

4. Create an NGINX Ingress Controller

To create the Ingress Controller, we will follow the manifests/nginx-public.yaml, which can be found here in the Git Repository.

 

kubectl apply -f nginx-public.yaml -n aks-store

This command deploys a public NGINX ingress controller defined in the nginx-public.yaml configuration file.  

 

NOTE: It takes some time for the Controller to get created. If it’s not creating, check the logs for the controller you created.

kubectl get pod -A

 

Figure 5: Retrive the log of pod using the name

 

5. Create an Ingress for the Test Application

To create the Ingress Controller, we will be following the manifests/ingress.yaml can be found here in the Git Repository.

 

kubectl apply -f ingress.yaml -n aks-store

This command applies the ingress configuration specified in ingress.yaml, which routes external traffic to your deployed test application within the aks-store namespace.

 

6. List All NGINX Ingress Controllers

kubectl get NginxIngressController

This command lists all the NGINX ingress controllers currently deployed in your AKS cluster, allowing you to verify that your controller is running correctly.

 

Figure 6: List of all NginxIngressController

 

7. Delete the Default NGINX Ingress Controller

kubectl delete NginxIngressController default

This command removes the default NGINX ingress controller that might have been created automatically by AKS, as it’s not needed if you're using the public ingress controller deployed earlier.

 

8. Update Hosts File for Public DNS

Since the application will be accessed using a public DNS (e.g., app.sometestcustomdomain.com), it is necessary to update your local machine's hosts file for testing purposes because I don't actually own a domain named sometestcustomdomain.com.

Here's how to do that:

1. Locate the Hosts File:

  • The hosts file is located at: C:\Windows\System32\drivers\etc\hosts

2. Open the Hosts File:

  • Use a text editor with administrative privileges to open the hosts file.

3. Add an Entry to the Hosts File:

  • Append the following line to the end of the hosts file:
<INGRESS_PUBLIC_IP> app.sometestcustomdomain.com

Replace <INGRESS_PUBLIC_IP> with the actual public IP address of your AKS ingress controller. You can find this IP by running:

kubectl get ingress -n aks-store

4. Save Changes:

  • Save the hosts file and close the text editor.

 

9. Verification

  • After updating the hosts file, open a web browser and navigate to https://app.sometestcustomdomain.com. You should see your application loading if everything is set up correctly.

 

Figure 7: Certificated is self created so it is showed as insecured, proceed further.

 

Figure 8: Azure demo application successfully deployed

 

Conclusion

In this article, we explored a simpler way to deploy web applications on Azure Kubernetes Service (AKS) using the Application Routing Add-On. This lets you skip the headaches of manually installing an NGINX Ingress Controller. We talked about the hassle of manual setups and how Azure’s managed services can make your life easier while boosting security.

We walked through some practical steps: creating a self-signed certificate, connecting to AKS, deploying a test application, and setting up an Ingress resource for smooth HTTPS traffic management, all while using Azure Key Vault for secure certificate storage.

By adopting this approach, DevOps teams can spend more time adding value to their applications instead of dealing with complicated ingress management. So, give these practices a shot in your own projects for a more efficient and secure AKS deployment!

 

References

Work With Me

Ready to take your Azure solutions to the next level and streamline your DevOps processes? Let's work together! As an experienced Azure solutions architect and DevOps expert, I can help you achieve your goals. Click the button below to get in touch.

Get In Touch