Use TLS with an ingress controller on Azure Kubernetes Service (AKS)

The transport layer security (TLS) protocol uses certificates to provide security for communication, encryption, authentication, and integrity. Using TLS with an ingress controller on AKS allows you to secure communication between your applications and experience the benefits of an ingress controller.

You can bring your own certificates and integrate them with the Secrets Store CSI driver. Alternatively, you can use cert-manager, which automatically generates and configures Let's Encrypt certificates. Two applications run in the AKS cluster, each of which is accessible over a single IP address.

Important

The Application routing add-on is recommended for ingress in AKS. For more information, see Managed nginx Ingress with the application routing add-on.

Important

Microsoft does not manage or support cert-manager and any issues stemming from its use. For issues with cert-manager, see cert-manager troubleshooting documentation.

There are two open source ingress controllers for Kubernetes based on Nginx: one is maintained by the Kubernetes community (kubernetes/ingress-nginx), and one is maintained by NGINX, Inc. (nginxinc/kubernetes-ingress). This article uses the Kubernetes community ingress controller.

Before you begin

  • This article assumes you have an ingress controller and applications set up. If you need an ingress controller or example applications, see Create an ingress controller.

  • This article uses Helm 3 to install the NGINX ingress controller on a supported version of Kubernetes. Make sure you're using the latest release of Helm and have access to the ingress-nginx and jetstack Helm repositories. The steps outlined in this article may not be compatible with previous versions of the Helm chart, NGINX ingress controller, or Kubernetes.

  • This article assumes you have an existing AKS cluster with an integrated Azure Container Registry (ACR). For more information on creating an AKS cluster with an integrated ACR, see Authenticate with ACR from AKS.

  • If you're using Azure CLI, this article requires that you're running the Azure CLI version 2.0.64 or later. Run az --version to find the version. If you need to install or upgrade, see Install Azure CLI.

  • If you're using Azure PowerShell, this article requires that you're running Azure PowerShell version 5.9.0 or later. Run Get-InstalledModule -Name Az to find the version. If you need to install or upgrade, see Install Azure PowerShell.

Use TLS with your own certificates with Secrets Store CSI Driver

To use TLS with your own certificates with Secrets Store CSI Driver, you need an AKS cluster with the Secrets Store CSI Driver configured and an Azure Key Vault instance.

For more information, see Set up Secrets Store CSI Driver to enable NGINX Ingress Controller with TLS.

Use TLS with Let's Encrypt certificates

To use TLS with Let's Encrypt certificates, you'll deploy cert-manager, which automatically generates and configures Let's Encrypt certificates.

Import the cert-manager images used by the Helm chart into your ACR

  • Use az acr import to import the following images into your ACR.

    REGISTRY_NAME=<REGISTRY_NAME>
    CERT_MANAGER_REGISTRY=quay.io
    CERT_MANAGER_TAG=v1.8.0
    CERT_MANAGER_IMAGE_CONTROLLER=jetstack/cert-manager-controller
    CERT_MANAGER_IMAGE_WEBHOOK=jetstack/cert-manager-webhook
    CERT_MANAGER_IMAGE_CAINJECTOR=jetstack/cert-manager-cainjector
    
    az acr import --name $REGISTRY_NAME --source $CERT_MANAGER_REGISTRY/$CERT_MANAGER_IMAGE_CONTROLLER:$CERT_MANAGER_TAG --image $CERT_MANAGER_IMAGE_CONTROLLER:$CERT_MANAGER_TAG
    az acr import --name $REGISTRY_NAME --source $CERT_MANAGER_REGISTRY/$CERT_MANAGER_IMAGE_WEBHOOK:$CERT_MANAGER_TAG --image $CERT_MANAGER_IMAGE_WEBHOOK:$CERT_MANAGER_TAG
    az acr import --name $REGISTRY_NAME --source $CERT_MANAGER_REGISTRY/$CERT_MANAGER_IMAGE_CAINJECTOR:$CERT_MANAGER_TAG --image $CERT_MANAGER_IMAGE_CAINJECTOR:$CERT_MANAGER_TAG
    

Note

You can also import Helm charts into your ACR. For more information, see Push and pull Helm charts to an ACR.

Ingress controller configuration options

You can configure your NGINX ingress controller using either a static public IP address or a dynamic public IP address. If you're using a custom domain, you need to add an A record to your DNS zone. If you're not using a custom domain, you can configure a fully qualified domain name (FQDN) for the ingress controller IP address.

Create a static or dynamic public IP address

Use a static public IP address

You can configure your ingress controller with a static public IP address. The static public IP address remains if you delete your ingress controller. The IP address doesn't remain if you delete your AKS cluster.

When you upgrade your ingress controller, you must pass a parameter to the Helm release to ensure the ingress controller service is made aware of the load balancer that will be allocated to it. For the HTTPS certificates to work correctly, you use a DNS label to configure an FQDN for the ingress controller IP address.

  1. Get the resource group name of the AKS cluster with the az aks show command.

    az aks show --resource-group myResourceGroup --name myAKSCluster --query nodeResourceGroup -o tsv
    
  2. Create a public IP address with the static allocation method using the az network public-ip create command. The following example creates a public IP address named myAKSPublicIP in the AKS cluster resource group obtained in the previous step.

    az network public-ip create --resource-group MC_myResourceGroup_myAKSCluster_eastus --name myAKSPublicIP --sku Standard --allocation-method static --query publicIp.ipAddress -o tsv
    

Note

Alternatively, you can create an IP address in a different resource group, which you can manage separately from your AKS cluster. If you create an IP address in a different resource group, ensure the following are true:

  • The cluster identity used by the AKS cluster has delegated permissions to the resource group, such as Network Contributor.
  • Add the --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-resource-group"="<RESOURCE_GROUP>" parameter. Replace <RESOURCE_GROUP> with the name of the resource group where the IP address resides.
  1. Add the --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-dns-label-name"="<DNS_LABEL>" parameter. The DNS label can be set either when the ingress controller is first deployed, or it can be configured later.

  2. Add the --set controller.service.loadBalancerIP="<STATIC_IP>" parameter. Specify your own public IP address that was created in the previous step.

    DNS_LABEL="<DNS_LABEL>"
    NAMESPACE="ingress-basic"
    STATIC_IP=<STATIC_IP>
    
    helm upgrade ingress-nginx ingress-nginx/ingress-nginx \
      --namespace $NAMESPACE \
      --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-dns-label-name"=$DNS_LABEL \
      --set controller.service.loadBalancerIP=$STATIC_IP \
      --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz
    

For more information, see Use a static public IP address and DNS label with the AKS load balancer.

Use a dynamic public IP address

An Azure public IP address is created for your ingress controller upon creation. The public IP address is static for the lifespan of your ingress controller. The public IP address doesn't remain if you delete your ingress controller. If you create a new ingress controller, it will be assigned a new public IP address. Your output should look similar to the following sample output.

  • Use the kubectl get service command to get the public IP address for your ingress controller.

    # Get the public IP address for your ingress controller
    
    kubectl --namespace ingress-basic get services -o wide -w nginx-ingress-ingress-nginx-controller
    
    # Sample output
    
    NAME                                     TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)                      AGE   SELECTOR
    nginx-ingress-ingress-nginx-controller   LoadBalancer   10.0.74.133   EXTERNAL_IP     80:32486/TCP,443:30953/TCP   44s   app.kubernetes.io/component=controller,app.kubernetes.io/instance=nginx-ingress,app.kubernetes.io/name=ingress-nginx
    

Add an A record to your DNS zone

If you're using a custom domain, you need to add an A record to your DNS zone. If you're not using a custom domain, you can configure the public IP address with an FQDN.

  • Add an A record to your DNS zone with the external IP address of the NGINX service using az network dns record-set a add-record.

    az network dns record-set a add-record \
        --resource-group myResourceGroup \
        --zone-name MY_CUSTOM_DOMAIN \
        --record-set-name "*" \
        --ipv4-address MY_EXTERNAL_IP
    

Configure an FQDN for your ingress controller

Optionally, you can configure an FQDN for the ingress controller IP address instead of a custom domain by setting a DNS label. Your FQDN should follow this form: <CUSTOM DNS LABEL>.<AZURE REGION NAME>.cloudapp.azure.com.

Important

Your DNS label must be unique within its Azure location.

You can configure your FQDN using one of the following methods:

  • Set the DNS label using Azure CLI or Azure PowerShell.
  • Set the DNS label using Helm chart settings.

For more information, see Public IP address DNS name labels.

Set the DNS label using Azure CLI or Azure PowerShell

Make sure to replace <DNS_LABEL> with your unique DNS label.

# Public IP address of your ingress controller
IP="MY_EXTERNAL_IP"

# Name to associate with public IP address
DNSLABEL="<DNS_LABEL>"

# Get the resource-id of the public IP
PUBLICIPID=$(az network public-ip list --query "[?ipAddress!=null]|[?contains(ipAddress, '$IP')].[id]" --output tsv)

# Update public IP address with DNS name
az network public-ip update --ids $PUBLICIPID --dns-name $DNSLABEL

# Display the FQDN
az network public-ip show --ids $PUBLICIPID --query "[dnsSettings.fqdn]" --output tsv

Set the DNS label using Helm chart settings

You can pass an annotation setting to your Helm chart configuration using the --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-dns-label-name" parameter. This parameter can be set when the ingress controller is first deployed, or it can be configured later.

The following example shows how to update this setting after the controller has been deployed. Make sure to replace <DNS_LABEL> with your unique DNS label.

DNSLABEL="<DNS_LABEL>"
NAMESPACE="ingress-basic"

helm upgrade ingress-nginx ingress-nginx/ingress-nginx \
  --namespace $NAMESPACE \
  --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-dns-label-name"=$DNSLABEL \
  --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz

Install cert-manager

The NGINX ingress controller supports TLS termination. There are several ways to retrieve and configure certificates for HTTPS. This article uses cert-manager, which provides automatic Lets Encrypt certificate generation and management functionality.

To install the cert-manager controller, use the following commands.

# Set variable for ACR location to use for pulling images
ACR_URL=<REGISTRY_URL>

# Label the ingress-basic namespace to disable resource validation
kubectl label namespace ingress-basic cert-manager.io/disable-validation=true

# Add the Jetstack Helm repository
helm repo add jetstack https://charts.jetstack.io

# Update your local Helm chart repository cache
helm repo update

# Install the cert-manager Helm chart
helm install cert-manager jetstack/cert-manager \
  --namespace ingress-basic \
  --version=$CERT_MANAGER_TAG \
  --set installCRDs=true \
  --set nodeSelector."kubernetes\.io/os"=linux \
  --set image.repository=$ACR_URL/$CERT_MANAGER_IMAGE_CONTROLLER \
  --set image.tag=$CERT_MANAGER_TAG \
  --set webhook.image.repository=$ACR_URL/$CERT_MANAGER_IMAGE_WEBHOOK \
  --set webhook.image.tag=$CERT_MANAGER_TAG \
  --set cainjector.image.repository=$ACR_URL/$CERT_MANAGER_IMAGE_CAINJECTOR \
  --set cainjector.image.tag=$CERT_MANAGER_TAG

For more information on cert-manager configuration, see the cert-manager project.

Create a CA cluster issuer

Before certificates can be issued, cert-manager requires one of the following issuers:

  • An Issuer, which works in a single namespace.
  • A ClusterIssuer resource, which works across all namespaces.

For more information, see the cert-manager issuer documentation.

  1. Create a cluster issuer, such as cluster-issuer.yaml, using the following example manifest. Replace MY_EMAIL_ADDRESS with a valid address from your organization.

    apiVersion: cert-manager.io/v1
    kind: ClusterIssuer
    metadata:
      name: letsencrypt
    spec:
      acme:
        server: https://acme-v02.api.letsencrypt.org/directory
        email: MY_EMAIL_ADDRESS
        privateKeySecretRef:
          name: letsencrypt
        solvers:
        - http01:
            ingress:
              class: nginx
              podTemplate:
                spec:
                  nodeSelector:
                    "kubernetes.io/os": linux
    
  2. Apply the issuer using the kubectl apply command.

    kubectl apply -f cluster-issuer.yaml --namespace ingress-basic
    

Update your ingress routes

You need to update your ingress routes to handle traffic to your FQDN or custom domain.

In the following example, traffic is routed as such:

  • Traffic to hello-world-ingress.MY_CUSTOM_DOMAIN is routed to the aks-helloworld-one service.
  • Traffic to hello-world-ingress.MY_CUSTOM_DOMAIN/hello-world-two is routed to the aks-helloworld-two service.
  • Traffic to hello-world-ingress.MY_CUSTOM_DOMAIN/static is routed to the service named aks-helloworld-one for static assets.

Note

If you configured an FQDN for the ingress controller IP address instead of a custom domain, use the FQDN instead of hello-world-ingress.MY_CUSTOM_DOMAIN.

For example, if your FQDN is demo-aks-ingress.eastus.cloudapp.azure.com, replace hello-world-ingress.MY_CUSTOM_DOMAIN with demo-aks-ingress.eastus.cloudapp.azure.com in hello-world-ingress.yaml.

  1. Create or update the hello-world-ingress.yaml file using the following example YAML file. Update the spec.tls.hosts and spec.rules.host to the DNS name you created in a previous step.

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: hello-world-ingress
      annotations:
        nginx.ingress.kubernetes.io/rewrite-target: /$2
        nginx.ingress.kubernetes.io/use-regex: "true"
        cert-manager.io/cluster-issuer: letsencrypt
    spec:
      ingressClassName: nginx
      tls:
      - hosts:
        - hello-world-ingress.MY_CUSTOM_DOMAIN
        secretName: tls-secret
      rules:
      - host: hello-world-ingress.MY_CUSTOM_DOMAIN
        http:
          paths:
          - path: /hello-world-one(/|$)(.*)
            pathType: Prefix
            backend:
              service:
                name: aks-helloworld-one
                port:
                  number: 80
          - path: /hello-world-two(/|$)(.*)
            pathType: Prefix
            backend:
              service:
                name: aks-helloworld-two
                port:
                  number: 80
          - path: /(.*)
            pathType: Prefix
            backend:
              service:
                name: aks-helloworld-one
                port:
                  number: 80
    ---
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: hello-world-ingress-static
      annotations:
        nginx.ingress.kubernetes.io/ssl-redirect: "false"
        nginx.ingress.kubernetes.io/rewrite-target: /static/$2
    spec:
      ingressClassName: nginx
      tls:
      - hosts:
        - hello-world-ingress.MY_CUSTOM_DOMAIN
        secretName: tls-secret
      rules:
      - host: hello-world-ingress.MY_CUSTOM_DOMAIN
        http:
          paths:
          - path: /static(/|$)(.*)
            pathType: Prefix
            backend:
              service:
                name: aks-helloworld-one
                port:
                  number: 80
    
  2. Update the ingress resource using the kubectl apply command.

    kubectl apply -f hello-world-ingress.yaml --namespace ingress-basic
    

Verify a certificate object has been created

Next, a certificate resource must be created. The certificate resource defines the desired X.509 certificate. For more information, see cert-manager certificates.

Cert-manager automatically creates a certificate object for you using ingress-shim, which is automatically deployed with cert-manager since v0.2.2. For more information, see the ingress-shim documentation.

To verify that the certificate was created successfully, use the kubectl get certificate --namespace ingress-basic command and verify READY is True. It may take several minutes to get the output.

kubectl get certificate --namespace ingress-basic

The following output shows the certificate's status.

NAME         READY   SECRET       AGE
tls-secret   True    tls-secret   11m

Test the ingress configuration

Open a web browser to hello-world-ingress.MY_CUSTOM_DOMAIN or the FQDN of your Kubernetes ingress controller. Ensure the following are true:

  • You're redirected to use HTTPS.
  • The certificate is trusted.
  • The demo application is shown in the web browser.
  • Add /hello-world-two to the end of the domain and ensure the second demo application with the custom title is shown.

Clean up resources

This article used Helm to install the ingress components, certificates, and sample apps. When you deploy a Helm chart, many Kubernetes resources are created. These resources include pods, deployments, and services. To clean up these resources, you can either delete the entire sample namespace or the individual resources.

Delete the sample namespace and all resources

Deleting the sample namespace also deletes all the resources in the namespace.

  • Delete the entire sample namespace using the kubectl delete command and specifying your namespace name.

    kubectl delete namespace ingress-basic
    

Delete resources individually

Alternatively, you can delete the resource individually.

  1. Remove the cluster issuer resources.

    kubectl delete -f cluster-issuer.yaml --namespace ingress-basic
    
  2. List the Helm releases with the helm list command. Look for charts named nginx and cert-manager, as shown in the following example output.

    $ helm list --namespace ingress-basic
    
    NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
    cert-manager            ingress-basic   1               2020-01-15 10:23:36.515514 -0600 CST    deployed        cert-manager-v0.13.0    v0.13.0
    nginx                   ingress-basic   1               2020-01-15 10:09:45.982693 -0600 CST    deployed        nginx-ingress-1.29.1    0.27.0
    
  3. Uninstall the releases using the helm uninstall command. The following example uninstalls the NGINX ingress and cert-manager deployments.

    $ helm uninstall cert-manager nginx --namespace ingress-basic
    
    release "cert-manager" uninstalled
    release "nginx" uninstalled
    
  4. Remove the two sample applications.

    kubectl delete -f aks-helloworld-one.yaml --namespace ingress-basic
    kubectl delete -f aks-helloworld-two.yaml --namespace ingress-basic
    
  5. Remove the ingress route that directed traffic to the sample apps.

    kubectl delete -f hello-world-ingress.yaml --namespace ingress-basic
    
  6. Delete the itself namespace. Use the kubectl delete command and specify your namespace name.

    kubectl delete namespace ingress-basic
    

Next steps

This article included some external components to AKS. To learn more about these components, see the following project pages:

You can also: