Integrating LetsEncrypt with Azure Kubernetes Service (AKS) for free TLS certificates

Table of Contents

Let’s Encrypt is a free alternative to traditional certificate authorities which also takes care of the certificate renewal process for you. This tutorial goes through the steps of deploying and configuring a new Azure Kubernetes (AKS) cluster and then use the cert-manager resource to automatically generate and renew TLS certificates using Let’s Encrypt.

You will set up the new AKS cluster, deploying the Azure DNS resources, and configuring cert-manager, all using a few interactive bash scripts.

Prerequisites

  1. An Azure account with an active subscription. If you don’t have one, you can create a free account here.
  2. The Azure CLI installed on your local machine. You can find the installation instructions here.
  3. The kubectl command-line tool installed on your local machine. You can find the installation instructions here.
  4. jq installed on your local machine for parsing JSON content; install it from here.
  5. A domain name that you own and can manage the DNS records for; we will use this domain to generate the TLS certificate.

If you want to jump ahead and get the source code, it's on Github at https://github.com/pineviewlabs/Azure-samples/tree/main/letsencrypt-azure-kubernetes

Step 1 — Setup the Azure Kubernetes Service (AKS) Cluster

First, we need to create a new AKS cluster. We will use the Azure CLI to create the cluster, so make sure you have it installed on your local machine.

Login to your Azure account using the Azure CLI:

az login

If you have more than one subscription, set the default subscription to the one you want to use for this tutorial:

az account set --subscription <subscription_id>

Create a new resource group for the AKS cluster. I'm using westeurope as the location for the resource group, but you can choose a different location if you prefer. For the resource group name, I'll use a convention which includes the region name so that it's easier to identify the region where the resources are deployed:

az group create --name we1-akstutorial-rg --location westeurope

Now, let's create the AKS cluster. I've put together an interactive bash script which will guide you through the process of creating the AKS cluster.

Overview of the process

The script will ask you for:

  1. Resource group name – can be a new or existing resource group (if it doesn't exist, it will be created)
  2. AKS cluster name
  3. Kubernetes node count (i.e. how many VMs to use)
  4. Node size (i.e. the VM size to use) -- see the list of available VM sizes; by default we'll use Standard_D2s_v3
  5. Whether you want to generate SSH keys for the cluster.

Running the script

To run the script, copy and paste the following command in your terminal (assuming you have curl installed):

bash <(curl -sL https://raw.githubusercontent.com/pineviewlabs/Azure-samples/main/letsencrypt-azure-kubernetes/01-create-aks-cluster.sh)

You can also clone the repository and run the script from your local machine:

git clone https://github.com/pineviewlabs/Azure-samples.git
cd letsencrypt-azure-kubernetes
chmod +x 01-create-aks-cluster.sh
./01-create-aks-cluster.sh

After the script completes,you should have a new AKS cluster running in your Azure subscription.

Example output

$ ./01-create-aks-cluster.sh

Enter Azure Subscription ID [xxxxxxxx-xxxx-xxxx-xxxx-xxxx]: 
  ✔️  Using Azure Subscription ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxx

Enter Azure resource group name [we1-akstutorial-rg]: 
ℹ️  Checking if resource group 'we1-akstutorial-rg' exists...
true
  ✔️  Resource group 'we1-akstutorial-rg' already exists. Using existing group.
Use existing SSH key found at /home/tutorials-user/.ssh/id_rsa.pub? [Y/n]: Y
  ✔️  Using existing SSH key.
Enter AKS cluster name [we1-akstutorial-cluster]: 
Enter node count [1]: 2
Enter node VM size [Standard_D2s_v3]: 
ℹ️  Creating AKS cluster 'we1-akstutorial-cluster' in resource group 'we1-akstutorial-rg'...
 _| Running... 
Merged "we1-akstutorial-cluster" as current context in /Users/<your-user>/.kube/linux-config
✔️  AKS cluster created.

Verify that you can connect to the AKS cluster by running the following command:

kubectl get nodes

Congratulations! We're now done with the Kubernetes cluster setup.

Step 2 — Deploy cert-manager and ClusterIssuer resources

Now that we have the AKS cluster set up, the Azure DNS resources created, and the DNS records updated, we can proceed to deploy the cert-manager resource to the AKS cluster.

Overview of the process

For this step of the process, we'll run an interactive bash script which will do the following:

  1. after the preliminary checks, the script will prompt you for an Azure Service Principal name
    - by default it is cert-manager-dnssp in the Azure AD tenant associated with the subscription;
    - this service principal will be used by cert-manager to authenticate with Azure DNS and manage the DNS records for the domain;
    - if the service principal already exists, the script will create a new client secret for it.
  2. create a Kubernetes secret named azuredns-config, in a separeate cert-manager namespace, which will store the Azure Service Principal credentials.
    - this is required by cert-manager to authenticate with Azure DNS and validate the domain ownership.
  3. deploy the cert-manager resources to the AKS cluster.
  4. load the cluster-issuer-template.yaml template file and replace the placeholders with the actual values provided by the user.
  5. run the kubectl apply command to deploy the cert-manager and ClusterIssuer resources to the AKS cluster.

Running the script

To run the script,open a new terminal and run the following command:

bash <(curl -sL https://raw.githubusercontent.com/pineviewlabs/Azure-samples/main/letsencrypt-azure-kubernetes/02-install-cert-manager.sh)

Or clone the repository and run the script from your local machine:

git clone https://github.com/pineviewlabs/Azure-samples.git
cd letsencrypt-azure-kubernetes
chmod +x 02-install-cert-manager.sh
./02-install-cert-manager.sh

Example output

$ ./02-install-cert-manager.sh

Enter Azure Subscription ID [xxxxxxxx-xxxx-xxxx-xxxx-xxxx]: 
  ✔️  Using Azure Subscription ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxx

Enter Azure resource group name [we1-akstutorial-rg]: 
Enter the email address to use with Let's Encrypt: <your-email-address>
Enter your domain name [letsencrypt-aks-tutorial.yourdomain.dev]: letsencrypt-aks-tutorial.pineview.io
ℹ️  Checking for existing service principal named 'cert-manager-dnssp'...
ℹ️  Service Principal already exists with ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxx
ℹ️  Tenant ID retrieved: xxxxxxxx-xxxx-xxxx-xxxx-xxxx
ℹ️  Creating a new client secret for Service Principal ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxx...
WARNING: The output includes credentials that you must protect. Be sure that you do not include these credentials in your code or check the credentials into your source control. For more information, see https://aka.ms/azadsp-cli
ℹ️  New client secret created successfully.

ℹ️  Creating Kubernetes secret...
ℹ️  Namespace 'cert-manager' already exists.
ℹ️  Installing or updating cert-manager to version v1.14.5...

# Lots of output from kubectl ...

✔️  ClusterIssuer configuration has been applied.
⚠️
If you see an error that says Error from server (InternalError): error when creating "STDIN": Internal error occurred: failed calling webhook "webhook.cert-manager.io":..., that means that the cert-manager resources are not yet ready. You can just re-run the script in a minute or two and it should work.

Step 3 — Deploy and Nginx Ingress and setup the Azure DNS resources

Now that we have the cert-manager and ClusterIssuer resources deployed, we can proceed to deploy the TLS ingress resource to the AKS cluster.

This is the final step in the process and will configure the cert-manager to automatically generate and renew TLS certificates for the domain we specified.

Next, assuming you already have a registred domain name, we need to deploy the Azure DNS resources that will be used to manage the DNS records for the domain we want to use for the TLS certificates.

Overview of the process

The interactive bash script in this section will attempt to do the following:

  1. deploy the nginx-ingress controller to the AKS cluster
  2. deploy a static website using the dockersamples/static-site image setup the service
  3. configure an ingress resources to route traffic to the static website
  4. create an Azure DNS zone and an A record for the domain name

Issuer Configuration

💡
We are using the DNS01 challange provider for the cert-manager Issuer resource,which means that the cert-manager will create a new DNS TXT record for the domain and Let's Encrypt will verify the domain ownership by checking the TXT record. 

You can also use the HTTP01 challange provider, however, for this tutorial we have opted for the DNS01 challenge provider which integrates better with Azure DNS. Read more about the challenge providers here.

Running the script

To run the script, open a new terminal and run following command:

bash <(curl -sL https://raw.githubusercontent.com/pineviewlabs/Azure-samples/main/letsencrypt-azure-kubernetes/03-deploy-ingress.sh)

Or clone the repository and run the script from your local machine:

git clone https://github.com/pineviewlabs/Azure-samples.git
cd letsencrypt-azure-kubernetes
chmod +x 03-deploy-ingress.sh
./03-deploy-ingress.sh

Example output

$ ./03-deploy-ingress.sh
Enter the domain name for the DNS zone [letsencrypt-aks-tutorial.yourdomain.dev]: 
Enter the IPv4 address for the DNS A record [xxx.xxx.xxx.xxx]: 
ℹ️  Creating DNS zone 'letsencrypt-aks-tutorial.pineview.io' in resource group 'we1-kubernetes-dev'...
{
  "etag": "...",
  "id": "/subscriptions/.../resourceGroups/we1-kubernetes-dev/providers/Microsoft.Network/dnszones/letsencrypt-aks-tutorial.pineview.io",
  "location": "global",
  "maxNumberOfRecordSets": 10000,
  "name": "letsencrypt-aks-tutorial.pineview.io",
  "nameServers": [
    "ns1-35.azure-dns.com.",
    "ns2-35.azure-dns.net.",
    "ns3-35.azure-dns.org.",
    "ns4-35.azure-dns.info."
  ],
  "numberOfRecordSets": 2,
  "resourceGroup": "we1-kubernetes-dev",
  "tags": {},
  "type": "Microsoft.Network/dnszones",
  "zoneType": "Public"
}
✔️  DNS zone created.

ℹ️  Adding A record with IP 'xxx.xxx.xxx.xxx' to zone 'letsencrypt-aks-tutorial.pineview.io'...
{
  "ARecords": [
    {
      "ipv4Address": "xxx.xxx.xxx.xxx"
    }
  ],
  "TTL": 3600,
  "etag": "...",
  "fqdn": "letsencrypt-aks-tutorial.pineview.io.",
  "id": "/subscriptions/.../resourceGroups/we1-kubernetes-dev/providers/Microsoft.Network/dnszones/letsencrypt-aks-tutorial.pineview.io/A/@",
  "name": "@",
  "provisioningState": "Succeeded",
  "resourceGroup": "we1-kubernetes-dev",
  "targetResource": {},
  "trafficManagementProfile": {},
  "type": "Microsoft.Network/dnszones/A"
}
✔️  Done. Ingress Controller has been deployed and DNS zone has been updated.
ℹ️ Add a new NS record for 'letsencrypt-aks-tutorial.pineview.io' with these values in your registrar:
ns1-35.azure-dns.com
ns2-35.azure-dns.net
ns3-35.azure-dns.org
ns4-35.azure-dns.info
⚠️
If you see an error that saysError from server (InternalError): error when creating "STDIN": Internal error occurred: failed calling webhook "validate.nginx.ingress.kubernetes.io":...,that means that the Nginx Ingress resources are not yet ready.You can just re-run the script in a minute or two.

Verifying the certificate status

To check the certificate status, you can run the following command:

kubectl get certificaterequests

If the certificate is not ready,you can describe the requests to get more information:

kubectl describe certificaterequests

Troubleshooting

If you are still waiting on the certificate to be issues for longer than a few minutes,check the logs of the cert-manager pod that handles the certificate request:

kubectl logs -n cert-manager $(kubectl get pods -n cert-manager -l app=cert-manager -o jsonpath='{.items[0].metadata.name}')

In more complex DNS setups,the certificate request might get stuck.In that case,you can try to delete the certificate request pod and let cert-manager recreate it:

kubectl delete pod -n cert-manager $(kubectl get pods -n cert-manager -l app=cert-manager -o jsonpath='{.items[0].metadata.name}')

Update the domain's DNS records

After running the script, you will get the name servers for the newly created DNS zone. You need to update the domain's DNS records in your domain registrar's control panel to point to these name servers. This is usually done by updating the NS records for the domain and unfortunately it is a manual process.

If you are using Azure DNS as your domain registrar, you can follow the instructions here to delegate the domain to Azure DNS.

Otherwise, go into your domain registrar's control panel and add a new NS record for the domain (in this example we used letsencrypt-aks-tutorial.pineview.io) and the NS records were provided in the output of the script:

ns1-35.azure-dns.com.
ns2-35.azure-dns.net.
ns3-35.azure-dns.org.
ns4-35.azure-dns.info.

Conclusion

Deploying and configuring TLS certificates for your Kubernetes workloads can be a complex process, evidently. I went ahead and created the interactive bash scripts presented in this tutorial to automate most of the process, which is something we often have to do for our customers here at Pineview.

There's more information available in the official guide for AKS from the cert-manager at https://cert-manager.io/docs/tutorials/getting-started-aks-letsencrypt.

You can find the complete source code on Github: https://github.com/pineviewlabs/Azure-samples/

You reach out to us via the contact form on our hompage here: pineview.io, via Twitter at @pineviewlabs or find us on LinkedIn at Pineview.