Logo Kubeship

Install cert-manager on Microsoft Azure Kubernetes Engine (AKS)


Now that we have ExternalDNS to connect our cluster to Azure DNS, we can request SSL certificates.

Let’s Encrypt is easy to integrate with cert-manager. Let’s walk through the steps to install it on Azure.

First we need some preparation before we can configure Kubernetes :

Create a managed identity :

az identity create \
    --resource-group test-daniel \
    --name cert-manager
{
  "clientId": "0f45e2de-d544-458d-8dd6-0799d7a60871",
  "id": "/subscriptions/27adfb5f-be37-4f18-ab18-d51705bec2ba/resourcegroups/test-daniel/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cert-manager",
  "location": "centralus",
  "name": "cert-manager",
  "principalId": "697a8ad2-d837-4f2e-b85a-ccb47ca2b88d",
  "resourceGroup": "test-daniel",
  "systemData": null,
  "tags": {},
  "tenantId": "6a3ff222-62b9-4ee5-9094-cc1c301bd4fd",
  "type": "Microsoft.ManagedIdentity/userAssignedIdentities"
}
IDENTITY_CLIENT_ID=$(az identity show \
    --resource-group test-daniel \
    --name "${IDENTITY_NAME}" \
    --query 'clientId' -o tsv)

az role assignment create \
    --role "DNS Zone Contributor" \
    --assignee IDENTITY_CLIENT_ID \
    --scope $(az network dns zone show --name $DOMAIN_NAME -o tsv --query id)
SERVICE_ACCOUNT_NAME=cert-manager
SERVICE_ACCOUNT_NAMESPACE=cert-manager
SERVICE_ACCOUNT_ISSUER=$(az aks show --resource-group $RESOURCE_GROUP --name $CLUSTER_NAME --query "oidcIssuerProfile.issuerUrl" -o tsv)

az identity federated-credential create \
    --resource-group $RESOURCE_GROUP \
  --name "cert-manager" \
  --identity-name "${IDENTITY_NAME}" \
  --issuer "${SERVICE_ACCOUNT_ISSUER}" \
  --subject "system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:${SERVICE_ACCOUNT_NAME}"
export IDENTITY_CLIENT_ID=$(az identity show --name cert-manager --query 'clientId' -o tsv)

DNS_ZONE_ID=$(az network dns zone show \
            --name              ainareflect.ai  \
            --resource-group    $RESOURCE_GROUP \
            --query "id" \
            --output tsv)

az role assignment create \
    --role "DNS Zone Contributor" \
    --assignee $IDENTITY_CLIENT_ID \
    --scope $DNS_ZONE_ID

Next step is to deploy cert-manager in the cluster. We will use the official manifest files :

kubectl apply -f \
    https://github.com/cert-manager/cert-manager/releases/download/v1.19.1/cert-manager.yaml

Once this is done, several components will be running. We can update the Kubernetes service account to link to use GCP service account :

kubectl annotate serviceaccount cert-manager \
    --namespace cert-manager \
    iam.gke.io/gcp-service-account="cert-manager@<PROJECT-ID>.iam.gserviceaccount.com"

Create the cluster issuer :

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: <EMAIL_ADDRESS>
    profile: tlsserver
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - dns01:
        cloudDNS:
          project: <PROJECT_ID>

There are several ways to request a certificate. I recommend to create a certificate resource and make sure the certificate has been issued. It will be possible to reuse it between many different ingresses.

Create the following resource in the namespace of your choice :

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: certificate
  namespace: demo
spec:
  secretName: certificate-tls
  privateKey:
    rotationPolicy: Always
  dnsNames:
    - demo.mydomain.com
  usages:
    - digital signature
    - key encipherment
    - server auth
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt-prod

We can now examine the status of the certificate. If you have cmctl installed, execute the command :

cmctl status certificate certificate -n demo

You should see a result similar to :

Name: certificate
Namespace: demo
Created at: 2025-10-17T16:05:27-04:00
Conditions:
  Ready: False, Reason: RequestChanged, Message: Fields on existing CertificateRequest resource not up to date: [spec.dnsNames]
  Issuing: True, Reason: RequestChanged, Message: Fields on existing CertificateRequest resource not up to date: [spec.dnsNames]
DNS Names:
- demo.mydomain.com
Events:
  Type    Reason     Age   From                                       Message
  ----    ------     ----  ----                                       -------
  Normal  Issuing    16m   cert-manager-certificates-trigger          Issuing certificate as Secret does not exist
...
Challenges:
- Name: certificate-1234-5678, Type: DNS-01, Token: 1234567890, Key: 1234567890, State: pending, Reason: , Processing: true, Presented: false

When the certificate is issued, it will be saved in the secret ‘certificate-tls’. You can view it with cmctl :

cmctl inspect secret certificate-tls -n demo

You can now update your ingress to use the certificate in the secret :

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx
spec:
  ingressClassName: nginx
  rules:
  - host: example.mydomain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: example-service
            port:
              number: 80
  tls:
  - hosts:
    - example.mydomain.com
    secretName: certificate-tls

References :

Keywords : Microsoft Azure, Kubernetes, cert-manager, SSL, Let's Encrypt