Raw New Markdown
Generating updated version of doc...
Rendered New Markdown
Generating updated version of doc...
---
title: Configure multiple ingress controllers and NGINX ingress annotations with the application routing add-on for Azure Kubernetes Service (AKS)
description: Understand the advanced configuration options that are supported with the application routing add-on with the NGINX ingress controller for Azure Kubernetes Service (AKS).
ms.subservice: aks-networking
ms.custom: devx-track-azurecli
ms.topic: how-to
ms.date: 11/21/2023
zone_pivot_groups: app-routing-nginx-configuration
ms.author: davidsmatlak
ms.service: azure-kubernetes-service
author: davidsmatlak
# Customer intent: As a Kubernetes administrator, I want to configure multiple NGINX ingress controllers with the application routing add-on, so that I can manage traffic to my applications using different load balancer settings and optimize my ingress resources.
---
# Advanced NGINX ingress controller and ingress configurations with the application routing add-on for Azure Kubernetes Service (AKS)
[!INCLUDE [ingress-nginx-retirement](./includes/ingress-nginx-retirement.md)]
This article walks you through two ways to configure ingress controllers and ingress objects with the application routing add-on for Azure Kubernetes Service (AKS):
- [Configuration of the NGINX ingress controller](#control-the-default-nginx-ingress-controller-configuration) such as creating multiple controllers, configuring private load balancers, and setting static IP addresses.
- [Configuration per ingress resource](#configuration-per-ingress-resource-through-annotations) through annotations.
## Prerequisites
- An AKS cluster with the [application routing add-on][app-routing-add-on-basic-configuration] enabled.
- `kubectl` configured to connect to your AKS cluster. For more information, see [Connect to your AKS cluster](#connect-to-your-aks-cluster).
### Connect to your AKS cluster
To connect to the Kubernetes cluster from your local computer, you use `kubectl`, the Kubernetes command-line client. You can install it locally using the [`az aks install-cli`][az-aks-install-cli] command. If you use the Azure Cloud Shell, `kubectl` is already installed.
- Configure kubectl to connect to your Kubernetes cluster using the [`az aks get-credentials`][az-aks-get-credentials] command.
```azurecli-interactive
az aks get-credentials --resource-group $RESOURCE_GROUP --name $CLUSTER_NAME
```
:::zone pivot="nginx-ingress-controller"
## Configuration properties for NGINX ingress controllers
The application routing add-on uses a Kubernetes [custom resource definition (CRD)](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) called [`NginxIngressController`](https://github.com/Azure/aks-app-routing-operator/blob/main/config/crd/bases/approuting.kubernetes.azure.com_nginxingresscontrollers.yaml) to configure NGINX ingress controllers. You can create more ingress controllers or modify existing configurations.
The following table lists properties you can set to configure an `NginxIngressController`:
| Field | Type | Description | Required | Default |
|----------------------------------------|---------|--------------------------------------------------------------------------------------------------------------------------------------------|----------|---------------------------------------|
| `controllerNamePrefix` | string | Name for the managed NGINX Ingress Controller resources. | Yes | `nginx` |
| `customHTTPErrors` | array | Array of error codes to be sent to the default backend if there's an error. | No | |
| `defaultBackendService` | object | Service to route unmatched HTTP traffic. Contains nested properties: | No | |
|  `name` | string | Service name. | Yes | |
|  `namespace` | string | Service namespace. | Yes | |
| `defaultSSLCertificate` | object | Contains the default certificate for accessing the default backend service. Contains nested properties: | No | |
|  `forceSSLRedirect` | boolean | Forces HTTPS redirection when a certificate is set. | No | `false` |
|  `keyVaultURI` | string | URI for a Key Vault secret storing the certificate. | No | |
|  `secret` | object | Holds secret information for the default SSL certificate. Contains nested properties: | No | |
|   `name` | string | Secret name. | Yes | |
|   `namespace` | string | Secret namespace. | Yes | |
| `httpDisabled` | boolean | Flag to disable HTTP traffic to the controller. | No | |
| `ingressClassName` | string | IngressClass name used by the controller. | Yes | `nginx.approuting.kubernetes.azure.com` |
| `loadBalancerAnnotations` | object | A map of annotations to control the behavior of the NGINX ingress controller's service by setting [load balancer annotations](configure-load-balancer-standard.md#customizations-via-kubernetes-annotations). | No | |
| `scaling` | object | Configuration for scaling the controller. Contains nested properties: | No | |
|  `maxReplicas` | integer | Upper limit for replicas. | No | `100` |
|  `minReplicas` | integer | Lower limit for replicas. | No | `2` |
|  `threshold` | string | Scaling threshold defining how aggressively to scale. **`rapid`** scales quickly for sudden spikes, **`steady`** favors cost-effectiveness, and **`balanced`** is a mix. | No | `balanced` |
## Control the default NGINX ingress controller configuration
When you enable the application routing add-on with NGINX, it creates an ingress controller called `default` in the `app-routing-namespace` configured with a public facing Azure load balancer. That ingress controller uses an ingress class name of `webapprouting.kubernetes.azure.com`.
You can also control if the default gets a public or an internal IP, or if it gets created at all when enabling the add-on.
Possible configuration options include:
- **`None`**: The default NGINX ingress controller isn't created and isn't deleted if it already exists. You should manually delete the default `NginxIngressController` custom resource if desired.
- **`Internal`**: The default NGINX ingress controller is created with an internal load balancer. Any annotation changes on the `NginxIngressController` custom resource to make it external are overwritten.
- **`External`**: The default NGINX ingress controller created with an external load balancer. Any annotation changes on the `NginxIngressController` custom resource to make it internal are overwritten.
- **`AnnotationControlled`** (default): The default NGINX ingress controller is created with an external load balancer. You can edit the default `NginxIngressController` custom resource to configure load balancer annotations.)
### Control the default ingress controller configuration on a new cluster
#### [Azure CLI](#tab/azure-cli)
- Enable application routing on a new cluster using the [`az aks create`][az-aks-create] command with the `--enable-app-routing` and `--app-routing-default-nginx-controller` flags. You need to set the `<DefaultIngressControllerType>` to one of the configuration options described in [Control the default NGINX ingress controller configuration](#control-the-default-nginx-ingress-controller-configuration).
```azurecli-interactive
az aks create \
--resource-group $RESOURCE_GROUP \
--name $CLUSTER_NAME \
--location $LOCATION \
--enable-app-routing \
--app-routing-default-nginx-controller <DefaultIngressControllerType>
```
#### [Bicep](#tab/bicep)
The `webAppRouting` profile has an optional `nginx` configuration with a `defaultIngressControllerType` property that you can modify to control the default configuration.
- Set the `defaultIngressControllerType` property to one of the configuration options described in [Control the default NGINX ingress controller configuration](#control-the-default-nginx-ingress-controller-configuration).
```bicep
"ingressProfile": {
"webAppRouting": {
"nginx": {
"defaultIngressControllerType": "None|Internal|External|AnnotationControlled"
}
}
```
---
### Update the default ingress controller configuration on an existing cluster
#### [Azure CLI](#tab/azure-cli)
- Update the application routing default ingress controller configuration on an existing cluster using the [`az aks approuting update`][az-aks-approuting-update] command with the `--nginx` flag. You need to set the `<DefaultIngressControllerType>` to one of the configuration options described in [Control the default NGINX ingress controller configuration](#control-the-default-nginx-ingress-controller-configuration).
```azurecli-interactive
az aks approuting update \
--resource-group $RESOURCE_GROUP \
--name $CLUSTER_NAME \
--nginx <DefaultIngressControllerType>
```
#### [Bicep](#tab/bicep)
The `webAppRouting` profile has an optional `nginx` configuration with a `defaultIngressControllerType` property that you can modify to control the default configuration.
- Set the `defaultIngressControllerType` property to one of the configuration options described in [Control the default NGINX ingress controller configuration](#control-the-default-nginx-ingress-controller-configuration).
```bicep
"ingressProfile": {
"webAppRouting": {
"nginx": {
"defaultIngressControllerType": "None|Internal|External|AnnotationControlled"
}
}
```
---
## Create another public facing NGINX ingress controller
1. Copy the following YAML manifest into a new file named `nginx-public-controller.yaml` and save the file to your local computer.
```yaml
apiVersion: approuting.kubernetes.azure.com/v1alpha1
kind: NginxIngressController
metadata:
name: nginx-public
spec:
ingressClassName: nginx-public
controllerNamePrefix: nginx-public
```
1. Create the NGINX ingress controller resources using the [`kubectl apply`][kubectl-apply] command.
```bash
kubectl apply -f nginx-public-controller.yaml
```
The following example output shows the created resource:
```output
nginxingresscontroller.approuting.kubernetes.azure.com/nginx-public created
```
## Create an internal NGINX ingress controller with a private IP address
1. Copy the following YAML manifest into a new file named `nginx-internal-controller.yaml` and save the file to your local computer.
```yaml
apiVersion: approuting.kubernetes.azure.com/v1alpha1
kind: NginxIngressController
metadata:
name: nginx-internal
spec:
ingressClassName: nginx-internal
controllerNamePrefix: nginx-internal
loadBalancerAnnotations:
service.beta.kubernetes.io/azure-load-balancer-internal: "true"
```
1. Create the NGINX ingress controller resources using the [`kubectl apply`][kubectl-apply] command.
```bash
kubectl apply -f nginx-internal-controller.yaml
```
The following example output shows the created resource:
```output
nginxingresscontroller.approuting.kubernetes.azure.com/nginx-internal created
```
## Create an NGINX ingress controller with a static IP address
1. Create an Azure resource group using the [`az group create`][az-group-create] command.
```azurecli-interactive
az group create --name $NETWORK_RESOURCE_GROUP --location $LOCATION
```
1. Create a static public IP address using the [`az network public ip create`][az-network-public-ip-create] command.
```azurecli-interactive
az network public-ip create \
--resource-group $NETWORK_RESOURCE_GROUP \
--name $PUBLIC_IP_NAME \
--sku Standard \
--allocation-method static
```
> [!NOTE]
> If you're using a *Basic* SKU load balancer in your AKS cluster, use `Basic` for the `--sku` parameter when defining a public IP. Only `Basic` SKU IPs work with the _Basic_ SKU load balancer and only `Standard` SKU IPs work with the _Standard_ SKU load balancers.
1. Ensure the cluster identity used by the AKS cluster has delegated permissions to the public IP's resource group using the [`az role assignment create`][az-role-assignment-create] command.
```azurecli-interactive
CLIENT_ID=$(az aks show --name $CLUSTER_NAME --resource-group $RESOURCE_GROUP --query identity.principalId -o tsv)
RG_SCOPE=$(az group show --name $NETWORK_RESOURCE_GROUP --query id -o tsv)
az role assignment create \
--assignee ${CLIENT_ID} \
--role "Network Contributor" \
--scope ${RG_SCOPE}
```
1. Copy the following YAML manifest into a new file named `nginx-staticip-controller.yaml` and save the file to your local computer.
> [!NOTE]
> You can either use `service.beta.kubernetes.io/azure-pip-name` for public IP name, or use `service.beta.kubernetes.io/azure-load-balancer-ipv4` for an IPv4 address and `service.beta.kubernetes.io/azure-load-balancer-ipv6` for an IPv6 address, as shown in the example YAML. Adding the `service.beta.kubernetes.io/azure-pip-name` annotation ensures the most efficient Load Balancer creation and is highly recommended to avoid potential throttling.
```yaml
apiVersion: approuting.kubernetes.azure.com/v1alpha1
kind: NginxIngressController
metadata:
name: nginx-static
spec:
ingressClassName: nginx-static
controllerNamePrefix: nginx-static
loadBalancerAnnotations:
service.beta.kubernetes.io/azure-pip-name: "$PUBLIC_IP_NAME"
service.beta.kubernetes.io/azure-load-balancer-resource-group: "$NETWORK_RESOURCE_GROUP"
```
1. Create the NGINX ingress controller resources using the [`kubectl apply`][kubectl-apply] command.
```bash
kubectl apply -f nginx-staticip-controller.yaml
```
The following example output shows the created resource:
```output
nginxingresscontroller.approuting.kubernetes.azure.com/nginx-static created
```
## Verify the ingress controller was created
- Verify the status of the NGINX ingress controller using the [`kubectl get nginxingresscontroller`][kubectl-get] command.
```bash
kubectl get nginxingresscontroller --name $INGRESS_CONTROLLER_NAME
```
The following example output shows the created resource. It may take a few minutes for the controller to be available:
```output
NAME INGRESSCLASS CONTROLLERNAMEPREFIX AVAILABLE
nginx-public nginx-public nginx True
```
### View the conditions of the ingress controller
- View the conditions of the ingress controller to troubleshoot any issues using the [`kubectl get nginxingresscontroller`][kubectl-get] command.
```bash
kubectl get nginxingresscontroller --name $INGRESS_CONTROLLER_NAME -o jsonpath='{range .items[*].status.conditions[*]}{.lastTransitionTime}{"\t"}{.status}{"\t"}{.type}{"\t"}{.message}{"\n"}{end}'
```
The following example output shows the conditions of a healthy ingress controller:
```output
2023-11-29T19:59:24Z True IngressClassReady Ingress Class is up-to-date
2023-11-29T19:59:50Z True Available Controller Deployment has minimum availability and IngressClass is up-to-date
2023-11-29T19:59:50Z True ControllerAvailable Controller Deployment is available
2023-11-29T19:59:25Z True Progressing Controller Deployment has successfully progressed
```
## Use the ingress controller in an ingress
1. Copy the following YAML manifest into a new file named `ingress.yaml` and save the file to your local computer.
> [!NOTE]
> Update `<HostName>` with your DNS host name.
> The `<IngressClassName>` is the one you defined when creating the `NginxIngressController`.
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: aks-helloworld
namespace: hello-web-app-routing
spec:
ingressClassName: <IngressClassName>
rules:
- host: <HostName>
http:
paths:
- backend:
service:
name: aks-helloworld
port:
number: 80
path: /
pathType: Prefix
```
1. Create the cluster resources using the [`kubectl apply`][kubectl-apply] command.
```bash
kubectl apply -f ingress.yaml --namespace hello-web-app-routing
```
The following example output shows the created resource:
```output
ingress.networking.k8s.io/aks-helloworld created
```
## Verify the managed ingress was created
- Verify the managed ingress was created using the [`kubectl get ingress`][kubectl-get] command.
```bash
kubectl get ingress --namespace hello-web-app-routing
```
Your output should resemble the following example output:
```output
NAME CLASS HOSTS ADDRESS PORTS AGE
aks-helloworld webapprouting.kubernetes.azure.com myapp.contoso.com 20.51.92.19 80, 443 4m
```
## Remove ingress controllers
- Remove the NGINX ingress controller using the [`kubectl delete nginxingresscontroller`][kubectl-delete] command.
```bash
kubectl delete nginxingresscontroller --name $INGRESS_CONTROLLER_NAME
```
:::zone-end
:::zone pivot="ingress-resource-annotations"
## Configuration per ingress resource through annotations
The NGINX ingress controller supports adding [annotations to specific ingress objects](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/) to customize their behavior.
You can [annotate](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) the ingress object by adding the respective annotation in the `metadata.annotations` field.
> [!NOTE]
> Annotation keys and values can only be strings. Other types, such as boolean or numeric values must be quoted. For example: `"true"`, `"false"`, `"100"`.
The following sections provide examples for common configurations. For a full list, see the [NGINX ingress annotations documentation](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/).
### Custom max body size
For NGINX, a 413 error is returned to the client when the size in a request exceeds the maximum allowed size of the client request body. To override the default value, use the annotation:
```yaml
nginx.ingress.kubernetes.io/proxy-body-size: 4m
```
Here's an example ingress configuration using this annotation:
> [!NOTE]
> Update `<HostName>` with your DNS host name.
> The `<IngressClassName>` is the one you defined when creating the `NginxIngressController`.
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: aks-helloworld
namespace: hello-web-app-routing
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 4m
spec:
ingressClassName: <IngressClassName>
rules:
- host: <HostName>
http:
paths:
- backend:
service:
name: aks-helloworld
port:
number: 80
path: /
pathType: Prefix
```
### Custom connection timeout
You can change the timeout that the NGINX ingress controller waits to close a connection with your workload. All timeout values are unitless and in seconds. To override the default timeout, use the following annotation to set a valid 120-seconds proxy read timeout:
```yaml
nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
```
Review [custom timeouts](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#custom-timeouts) for other configuration options.
Here's an example ingress configuration using this annotation:
> [!NOTE]
> Update `<HostName>` with your DNS host name.
> The `<IngressClassName>` is the one you defined when creating the `NginxIngressController`.
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: aks-helloworld
namespace: hello-web-app-routing
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
spec:
ingressClassName: <IngressClassName>
rules:
- host: <HostName>
http:
paths:
- backend:
service:
name: aks-helloworld
port:
number: 80
path: /
pathType: Prefix
```
### Backend protocol
The NGINX ingress controller uses `HTTP` to reach the services by default. To configure alternative backend protocols such as `HTTPS` or `GRPC`, use one of the following annotations:
```yaml
# HTTPS annotation
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
# GRPC annotation
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
```
Review [backend protocols](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#backend-protocol) for other configuration options.
Here's an example ingress configuration using this annotation:
> [!NOTE]
> Update `<HostName>` with your DNS host name.
> The `<IngressClassName>` is the one you defined when creating the `NginxIngressController`.
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: aks-helloworld
namespace: hello-web-app-routing
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
ingressClassName: <IngressClassName>
rules:
- host: <HostName>
http:
paths:
- backend:
service:
name: aks-helloworld
port:
number: 80
path: /
pathType: Prefix
```
### Cross-Origin Resource Sharing (CORS)
To enable Cross-Origin Resource Sharing (CORS) in an Ingress rule, use the following annotation:
```yaml
nginx.ingress.kubernetes.io/enable-cors: "true"
```
Review [enable CORS](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#enable-cors) for other configuration options.
Here's an example ingress configuration using this annotation:
> [!NOTE]
> Update `<HostName>` with your DNS host name.
> The `<IngressClassName>` is the one you defined when creating the `NginxIngressController`.
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: aks-helloworld
namespace: hello-web-app-routing
annotations:
nginx.ingress.kubernetes.io/enable-cors: "true"
spec:
ingressClassName: <IngressClassName>
rules:
- host: <HostName>
http:
paths:
- backend:
service:
name: aks-helloworld
port:
number: 80
path: /
pathType: Prefix
```
### Disable SSL redirect
The controller redirects (308) to HTTPS if TLS is enabled for an ingress by default. To disable this feature for specific ingress resources, use the following annotation:
```yaml
nginx.ingress.kubernetes.io/ssl-redirect: "false"
```
Review [server-side HTTPS enforcement through redirect](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#server-side-https-enforcement-through-redirect) for other configuration options.
Here's an example ingress configuration using this annotation:
> [!NOTE]
> Update `<HostName>` with your DNS host name.
> The `<IngressClassName>` is the one you defined when creating the `NginxIngressController`.
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: aks-helloworld
namespace: hello-web-app-routing
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
ingressClassName: <IngressClassName>
rules:
- host: <HostName>
http:
paths:
- backend:
service:
name: aks-helloworld
port:
number: 80
path: /
pathType: Prefix
```
### URL rewriting
In some scenarios, the exposed URL in the backend service differs from the specified path in the ingress rule. Without a rewrite any request returns 404. This configuration is useful with [path-based routing](https://kubernetes.github.io/ingress-nginx/user-guide/ingress-path-matching/) where you can serve two different web applications under the same domain. You can set path expected by the service using the following annotation:
```yaml
nginx.ingress.kubernetes.io/rewrite-target: /$2
```
Here's an example ingress configuration using this annotation:
> [!NOTE]
> Update `<HostName>` with your DNS host name.
> The `<IngressClassName>` is the one you defined when creating the `NginxIngressController`.
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: aks-helloworld
namespace: hello-web-app-routing
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
ingressClassName: <IngressClassName>
rules:
- host: <HostName>
http:
paths:
- path: /app-one(/|$)(.*)
pathType: Prefix
backend:
service:
name: app-one
port:
number: 80
- path: /app-two(/|$)(.*)
pathType: Prefix
backend:
service:
name: app-two
port:
number: 80
```
### NGINX health probe path update
The default health probe path for the Azure Load Balancer associated with the NGINX ingress controller must be set to `"/healthz"`. To ensure correct health checks, verify that the ingress controller service has the following annotation:
```yaml
metadata:
annotations:
service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path: "/healthz"
```
If you're using Helm to manage your NGINX ingress controller, you can define the Azure Load Balancer health-probe annotation in a values file and apply it during an upgrade:
```yaml
controller:
service:
annotations:
service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path: "/healthz"
```
This configuration helps maintain service availability and avoids unexpected traffic disruption during upgrades.
:::zone-end
## Next steps
Learn about monitoring the ingress-nginx controller metrics included with the application routing add-on with [with Prometheus in Grafana][prometheus-in-grafana] as part of analyzing the performance and usage of your application.
<!-- LINKS - external -->
[kubectl-apply]: https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#apply
[kubectl-get]: https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#get
[kubectl-delete]: https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#delete
<!-- LINKS - internal -->
[az-network-public-ip-create]: /cli/azure/network/public-ip#az-network-public-ip-create
[az-group-create]: /cli/azure/group#az-group-create
[app-routing-add-on-basic-configuration]: app-routing.md
[az-aks-approuting-update]: /cli/azure/aks/approuting#az-aks-approuting-update
[az-aks-install-cli]: /cli/azure/aks#az-aks-install-cli
[az-aks-get-credentials]: /cli/azure/aks#az-aks-get-credentials
[prometheus-in-grafana]: app-routing-nginx-prometheus.md
[az-role-assignment-create]: /cli/azure/role/assignment#az-role-assignment-create
[az-aks-create]: /cli/azure/aks#az-aks-create