Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This guide demonstrates how to use cert-manager to automatically issue and renew SSL/TLS certificates to one or more frontends of your Azure Application Gateway for Containers deployment. We use the Ingress API to configure the necessary resources.
For the purposes of this example, we have cert-manager configure certificates issued from Let's Encrypt to demonstrate an end-to-end deployment, where Application Gateway for Containers is providing TLS offloading.
For certificates to be issued by Let's Encrypt, a challenge is required by the authority to validate domain ownership. This validation happens by allowing cert-manager to create a pod and Ingress resource that exposes an endpoint during certificate issuance, proving your ownership of the domain name.
More details on cert-manager and Let's Encrypt with AKS in general may be found here.
Prerequisites
If following the BYO deployment strategy, ensure that you set up your Application Gateway for Containers resources and ALB Controller (Add-on or Helm)
If following the ALB managed deployment strategy, ensure that you provision your ALB Controller (Add-on or Helm) and the Application Gateway for Containers resources via the ApplicationLoadBalancer custom resource.
Deploy sample HTTP application Apply the following deployment.yaml file on your cluster to create a sample web application to demonstrate the header rewrite.
kubectl apply -f https://raw.githubusercontent.com/MicrosoftDocs/azure-docs/refs/heads/main/articles/application-gateway/for-containers/examples/traffic-split-scenario/deployment.yamlThis command creates the following on your cluster:
- a namespace called
test-infra - two services called
backend-v1andbackend-v2in thetest-infranamespace - two deployments called
backend-v1andbackend-v2in thetest-infranamespace
- a namespace called
Install Cert-Manager
Install cert-manager using Helm:
helm install \
cert-manager oci://quay.io/jetstack/charts/cert-manager \
--version v1.19.3 \
--namespace cert-manager \
--create-namespace \
--set config.enableGatewayAPI=true \
--set crds.enabled=true
Note
While Gateway API enablement is not required, it provides flexibility to leverage Application Gateway for Containers + Gateway API + Cert-Manager in addition to the Ingress configurations for parallel deployments in Gateway API.
Create a ClusterIssuer
Create a ClusterIssuer resource to define how cert-manager communicates with Let's Encrypt. For this example, an HTTP challenge is used. During challenge, cert-manager creates an Ingress resource and corresponding pod presenting a validation endpoint to prove ownership of the domain. This is done by creating a temporary Ingress resource with the http01 challenge type. This Ingress resource and corresponding pod created by cert-manager is deleted after the challenge is completed.
Tip
Other challenges supported by Let's Encrypt are documented on letsencrypt.org - Challenge Types
Create the ClusterIssuer resource
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory # production endpoint
#server: https://acme-staging-v02.api.letsencrypt.org/directory # staging endpoint
email: your-email@example.com
privateKeySecretRef:
name: letsencrypt-private-key
solvers:
- http01:
ingress:
ingressClassName: azure-alb-external
# This section is required for the Ingress resource created by cert-manager during the challenge
ingressTemplate:
metadata:
annotations:
alb.networking.azure.io/alb-name: alb-test
alb.networking.azure.io/alb-namespace: alb-test-infra
EOF
Verify the resource was created by running the following command:
kubectl get ClusterIssuer -A -o yaml
The status should show True and type Ready under conditions.
status:
acme:
lastPrivateKeyHash: x+xxxxxxxxxxxxxxxxxxxxxxx+MY4PAEeotr9XH3V7I=
lastRegisteredEmail: your-email@example.com
uri: https://acme-staging-v02.api.letsencrypt.org/acme/acct/190567584
conditions:
- lastTransitionTime: "2025-03-20T16:00:21Z"
message: The ACME account was registered with the ACME server
observedGeneration: 1
reason: ACMEAccountRegistered
status: "True"
type: Ready
Deploy the required Ingress resource
Create an Ingress
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-01
namespace: test-infra
annotations:
alb.networking.azure.io/alb-name: alb-test
alb.networking.azure.io/alb-namespace: alb-test-infra
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: azure-alb-external
tls:
- hosts:
- backend-v1.contoso.com
# - backend-v2.contoso.com # You can uncomment this and the host line to add an additional subject alternate name (SAN) to the certificate
secretName: tls-backend
rules:
- host: backend-v1.contoso.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: backend-v1
port:
number: 8080
# - host: backend-v2.contoso.com
# http:
# paths:
# - path: /
# pathType: Prefix
# backend:
# service:
# name: backend-v2
# port:
# number: 8080
EOF
Once the ingress resource is created, ensure the status shows the hostname of your load balancer:
kubectl get ingress ingress-01 -n test-infra -o yaml
Example output of successful Ingress creation.
status:
loadBalancer:
ingress:
- hostname: xxxxxxxxxxxxxxxx.fz13.alb.azure.com
ports:
- port: 443
protocol: TCP
As mentioned previously, cert-manager creates a temporary Ingress resource and pod to perform the challenge:
kubectl get pods -n test-infra
NAME READY STATUS RESTARTS AGE
backend-v1-56d99ddb49-mwmcc 1/1 Running 0 10m
backend-v2-8b5d4679b-rsfrg 1/1 Running 0 10m
cm-acme-http-solver-5lmmv 1/1 Running 0 2s
kubectl get ingress -n test-infra
NAME CLASS HOSTS ADDRESS PORTS AGE
cm-acme-http-solver-zrp47 azure-alb-external backend-v1.contoso.com xxxxxxxxxxxxxxxx.fz13.alb.azure.com 80 8s
ingress-01 azure-alb-external backend-v1.contoso.com xxxxxxxxxxxxxxxx.fz13.alb.azure.com 80, 443 10s
You can check the status of the challenge by running:
kubectl get challenges.acme.cert-manager.io -n test-infra
NAME STATE DOMAIN AGE
cert-backend-1-2982214480-3407407859 pending backend-v1.contoso.com 16s
kubectl get certificaterequests.cert-manager.io -n test-infra
NAME APPROVED DENIED READY ISSUER REQUESTER AGE
cert-backend-1 True False letsencrypt-prod system:serviceaccount:cert-manager:cert-manager 34s
When the challenge is successful, the status changes to READY=True and the certificate is issued:
kubectl get certificate -n test-infra
NAME READY SECRET AGE
cert-backend True cert-backend 1m
Tip
You can synchronize the hostnames of Ingress resources created in AKS automatically with Azure DNS zones by utilizing External DNS and Workload Identity.
Test access to the application
The environment is now configured to route traffic to the sample application using the hostname associated with your certificate.
Important
Ensure you replace contoso.com with the domain name you're expecting the certificate to be issued to.
curl https://backend-v1.contoso.com -v 2>&1 | grep issuer
You should see the following output:
* issuer: C=US; O=Let's Encrypt; CN=R11
You have successfully installed the ALB Controller, deployed a backend application, obtained a certificate from Let's Encrypt using cert-manager, and configured traffic routing to the application through Application Gateway for Containers.