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 :
- Let’s Encrypt : https://letsencrypt.org/
- cert-manager : https://cert-manager.io/
- cmctl : https://cert-manager.io/docs/reference/cmctl/