|
| 1 | +--- |
| 2 | +title: Cert-manager and Let's Encrypt with Application Gateway for Containers - Ingress API |
| 3 | +description: Learn how to configure Application Gateway for Containers with certificates managed by the Cloud Native Computing Foundation (CNCF) project cert-manager. |
| 4 | +services: application-gateway |
| 5 | +author: philwelz |
| 6 | +ms.service: azure-appgw-for-containers |
| 7 | +ms.topic: how-to |
| 8 | +ms.date: 3/21/2025 |
| 9 | +ms.author: greglin |
| 10 | +--- |
| 11 | + |
| 12 | +# Cert-manager and Let's Encrypt with Application Gateway for Containers - Ingress API |
| 13 | + |
| 14 | +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. |
| 15 | + |
| 16 | +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. |
| 17 | + |
| 18 | +[ ](./media/how-to-cert-manager-lets-encrypt-ingress-api/how-to-cert-manager-lets-encrypt-ingress-api.svg#lightbox) |
| 19 | + |
| 20 | +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. |
| 21 | + |
| 22 | +More details on cert-manager and Let's Encrypt with AKS in general may be found [here](https://cert-manager.io/docs/tutorials/getting-started-aks-letsencrypt/). |
| 23 | + |
| 24 | +## Prerequisites |
| 25 | + |
| 26 | +1. If following the BYO deployment strategy, ensure that you set up your Application Gateway for Containers resources and [ALB Controller](quickstart-deploy-application-gateway-for-containers-alb-controller.md) |
| 27 | +2. If following the ALB managed deployment strategy, ensure that you provision your [ALB Controller](quickstart-deploy-application-gateway-for-containers-alb-controller.md) and the Application Gateway for Containers resources via the [ApplicationLoadBalancer custom resource](quickstart-create-application-gateway-for-containers-managed-by-alb-controller.md). |
| 28 | +3. Deploy sample HTTP application |
| 29 | + Apply the following deployment.yaml file on your cluster to create a sample web application to demonstrate the header rewrite. |
| 30 | + |
| 31 | + ```bash |
| 32 | + kubectl apply -f https://raw.githubusercontent.com/MicrosoftDocs/azure-docs/refs/heads/main/articles/application-gateway/for-containers/examples/traffic-split-scenario/deployment.yaml |
| 33 | + ``` |
| 34 | + |
| 35 | + This command creates the following on your cluster: |
| 36 | + |
| 37 | + - a namespace called `test-infra` |
| 38 | + - two services called `backend-v1` and `backend-v2` in the `test-infra` namespace |
| 39 | + - two deployments called `backend-v1` and `backend-v2` in the `test-infra` namespace |
| 40 | + |
| 41 | +### Install Cert-Manager |
| 42 | + |
| 43 | +Install cert-manager using Helm: |
| 44 | + |
| 45 | +```bash |
| 46 | +helm repo add jetstack https://charts.jetstack.io --force-update |
| 47 | +helm upgrade -i \ |
| 48 | + cert-manager jetstack/cert-manager \ |
| 49 | + --namespace cert-manager \ |
| 50 | + --create-namespace \ |
| 51 | + --version v1.17.1 \ |
| 52 | + --set installCRDs=true |
| 53 | +``` |
| 54 | + |
| 55 | +### Create a ClusterIssuer |
| 56 | + |
| 57 | +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. |
| 58 | + |
| 59 | +>[!Tip] |
| 60 | +>Other challenges supported by Let's Encrypt are documented on [letsencrypt.org - Challenge Types](https://letsencrypt.org/docs/challenge-types/) |
| 61 | +
|
| 62 | +# [ALB managed deployment](#tab/alb-managed) |
| 63 | + |
| 64 | +Create the ClusterIssuer resource |
| 65 | + |
| 66 | +```bash |
| 67 | +kubectl apply -f - <<EOF |
| 68 | +apiVersion: cert-manager.io/v1 |
| 69 | +kind: ClusterIssuer |
| 70 | +metadata: |
| 71 | + name: letsencrypt-prod |
| 72 | +spec: |
| 73 | + acme: |
| 74 | + server: https://acme-v02.api.letsencrypt.org/directory # production endpoint |
| 75 | + #server: https://acme-staging-v02.api.letsencrypt.org/directory # staging endpoint |
| 76 | + |
| 77 | + privateKeySecretRef: |
| 78 | + name: letsencrypt-private-key |
| 79 | + solvers: |
| 80 | + - http01: |
| 81 | + ingress: |
| 82 | + ingressClassName: azure-alb-external |
| 83 | + # This section is required for the Ingress resource created by cert-manager during the challenge |
| 84 | + ingressTemplate: |
| 85 | + metadata: |
| 86 | + annotations: |
| 87 | + alb.networking.azure.io/alb-name: alb-test |
| 88 | + alb.networking.azure.io/alb-namespace: alb-test-infra |
| 89 | +EOF |
| 90 | +``` |
| 91 | + |
| 92 | +# [Bring your own (BYO) deployment](#tab/byo) |
| 93 | + |
| 94 | +1. Set the following environment variables |
| 95 | + |
| 96 | +```bash |
| 97 | +RESOURCE_GROUP='<resource group name of the Application Gateway For Containers resource>' |
| 98 | +RESOURCE_NAME='alb-test' |
| 99 | + |
| 100 | +RESOURCE_ID=$(az network alb show --resource-group $RESOURCE_GROUP --name $RESOURCE_NAME --query id -o tsv) |
| 101 | +FRONTEND_NAME='frontend' |
| 102 | +``` |
| 103 | + |
| 104 | +2. Create the ClusterIssuer resource |
| 105 | + |
| 106 | +```bash |
| 107 | +kubectl apply -f - <<EOF |
| 108 | +apiVersion: cert-manager.io/v1 |
| 109 | +kind: ClusterIssuer |
| 110 | +metadata: |
| 111 | + name: letsencrypt-prod |
| 112 | + namespace: test-infra |
| 113 | +spec: |
| 114 | + acme: |
| 115 | + server: https://acme-v02.api.letsencrypt.org/directory # production endpoint |
| 116 | + #server: https://acme-staging-v02.api.letsencrypt.org/directory # staging endpoint |
| 117 | + |
| 118 | + privateKeySecretRef: |
| 119 | + name: letsencrypt-private-key |
| 120 | + solvers: |
| 121 | + - http01: |
| 122 | + ingress: |
| 123 | + ingressClassName: azure-alb-external |
| 124 | + # This section is required for the Ingress resource created by cert-manager during the challenge |
| 125 | + ingressTemplate: |
| 126 | + metadata: |
| 127 | + annotations: |
| 128 | + alb.networking.azure.io/alb-id: $RESOURCE_ID |
| 129 | + alb.networking.azure.io/alb-frontend: $FRONTEND_NAME |
| 130 | +EOF |
| 131 | +``` |
| 132 | + |
| 133 | +--- |
| 134 | + |
| 135 | +Verify the resource was created by running the following command: |
| 136 | + |
| 137 | +```bash |
| 138 | +kubectl get ClusterIssuer -A -o yaml |
| 139 | +``` |
| 140 | + |
| 141 | +The status should show `True` and type `Ready` under conditions. |
| 142 | + |
| 143 | +```yaml |
| 144 | + status: |
| 145 | + acme: |
| 146 | + lastPrivateKeyHash: x+xxxxxxxxxxxxxxxxxxxxxxx+MY4PAEeotr9XH3V7I= |
| 147 | + lastRegisteredEmail: [email protected] |
| 148 | + uri: https://acme-staging-v02.api.letsencrypt.org/acme/acct/190567584 |
| 149 | + conditions: |
| 150 | + - lastTransitionTime: "2025-03-20T16:00:21Z" |
| 151 | + message: The ACME account was registered with the ACME server |
| 152 | + observedGeneration: 1 |
| 153 | + reason: ACMEAccountRegistered |
| 154 | + status: "True" |
| 155 | + type: Ready |
| 156 | +``` |
| 157 | +--- |
| 158 | +## Deploy the required Ingress resource |
| 159 | +
|
| 160 | +# [ALB managed deployment](#tab/alb-managed) |
| 161 | +
|
| 162 | +Create an Ingress |
| 163 | +
|
| 164 | +```bash |
| 165 | +kubectl apply -f - <<EOF |
| 166 | +apiVersion: networking.k8s.io/v1 |
| 167 | +kind: Ingress |
| 168 | +metadata: |
| 169 | + name: ingress-01 |
| 170 | + namespace: test-infra |
| 171 | + annotations: |
| 172 | + alb.networking.azure.io/alb-name: alb-test |
| 173 | + alb.networking.azure.io/alb-namespace: alb-test-infra |
| 174 | + cert-manager.io/cluster-issuer: letsencrypt-prod |
| 175 | +spec: |
| 176 | + ingressClassName: azure-alb-external |
| 177 | + tls: |
| 178 | + - hosts: |
| 179 | + - backend-v1.contoso.com |
| 180 | + # - backend-v2.contoso.com # You can uncomment this and the host line to add an aditional subject alternate name (SAN) to the certificate |
| 181 | + secretName: tls-backend |
| 182 | + rules: |
| 183 | + - host: backend-v1.contoso.com |
| 184 | + http: |
| 185 | + paths: |
| 186 | + - path: / |
| 187 | + pathType: Prefix |
| 188 | + backend: |
| 189 | + service: |
| 190 | + name: backend-v1 |
| 191 | + port: |
| 192 | + number: 8080 |
| 193 | + # - host: backend-v2.contoso.com |
| 194 | + # http: |
| 195 | + # paths: |
| 196 | + # - path: / |
| 197 | + # pathType: Prefix |
| 198 | + # backend: |
| 199 | + # service: |
| 200 | + # name: backend-v2 |
| 201 | + # port: |
| 202 | + # number: 8080 |
| 203 | +EOF |
| 204 | +``` |
| 205 | + |
| 206 | +# [Bring your own (BYO) deployment](#tab/byo) |
| 207 | + |
| 208 | +1. Set the following environment variables |
| 209 | + |
| 210 | +```bash |
| 211 | +RESOURCE_GROUP='<resource group name of the Application Gateway For Containers resource>' |
| 212 | +RESOURCE_NAME='alb-test' |
| 213 | + |
| 214 | +RESOURCE_ID=$(az network alb show --resource-group $RESOURCE_GROUP --name $RESOURCE_NAME --query id -o tsv) |
| 215 | +FRONTEND_NAME='frontend' |
| 216 | +``` |
| 217 | + |
| 218 | +2. Create an Ingress resource |
| 219 | + |
| 220 | +```bash |
| 221 | +kubectl apply -f - <<EOF |
| 222 | +apiVersion: networking.k8s.io/v1 |
| 223 | +kind: Ingress |
| 224 | +metadata: |
| 225 | + name: ingress-01 |
| 226 | + namespace: test-infra |
| 227 | + annotations: |
| 228 | + alb.networking.azure.io/alb-id: $RESOURCE_ID |
| 229 | + alb.networking.azure.io/alb-frontend: $FRONTEND_NAME |
| 230 | + cert-manager.io/cluster-issuer: letsencrypt-prod |
| 231 | +spec: |
| 232 | + ingressClassName: azure-alb-external |
| 233 | + tls: |
| 234 | + - hosts: |
| 235 | + - backend-v1.contoso.com |
| 236 | + # - backend-v2.contoso.com # You can uncomment this and the host line to add an aditional subject alternate name (SAN) to the certificate |
| 237 | + secretName: tls-backend |
| 238 | + rules: |
| 239 | + - host: backend-v1.contoso.com |
| 240 | + http: |
| 241 | + paths: |
| 242 | + - path: / |
| 243 | + pathType: Prefix |
| 244 | + backend: |
| 245 | + service: |
| 246 | + name: backend-v1 |
| 247 | + port: |
| 248 | + number: 8080 |
| 249 | + # - host: backend-v2.contoso.com |
| 250 | + # http: |
| 251 | + # paths: |
| 252 | + # - path: / |
| 253 | + # pathType: Prefix |
| 254 | + # backend: |
| 255 | + # service: |
| 256 | + # name: backend-v2 |
| 257 | + # port: |
| 258 | + # number: 8080 |
| 259 | +EOF |
| 260 | +``` |
| 261 | + |
| 262 | +--- |
| 263 | + |
| 264 | +Once the ingress resource is created, ensure the status shows the hostname of your load balancer: |
| 265 | + |
| 266 | +```bash |
| 267 | +kubectl get ingress ingress-01 -n test-infra -o yaml |
| 268 | +``` |
| 269 | + |
| 270 | +Example output of successful Ingress creation. |
| 271 | + |
| 272 | +```yaml |
| 273 | +status: |
| 274 | + loadBalancer: |
| 275 | + ingress: |
| 276 | + - hostname: xxxxxxxxxxxxxxxx.fz13.alb.azure.com |
| 277 | + ports: |
| 278 | + - port: 443 |
| 279 | + protocol: TCP |
| 280 | +``` |
| 281 | +
|
| 282 | +As mentioned previously, cert-manager creates a temporary Ingress resource and pod to perform the challenge: |
| 283 | +
|
| 284 | +```bash |
| 285 | +kubectl get pods -n test-infra |
| 286 | +NAME READY STATUS RESTARTS AGE |
| 287 | +backend-v1-56d99ddb49-mwmcc 1/1 Running 0 10m |
| 288 | +backend-v2-8b5d4679b-rsfrg 1/1 Running 0 10m |
| 289 | +cm-acme-http-solver-5lmmv 1/1 Running 0 2s |
| 290 | + |
| 291 | +kubectl get ingress -n test-infra |
| 292 | +NAME CLASS HOSTS ADDRESS PORTS AGE |
| 293 | +cm-acme-http-solver-zrp47 azure-alb-external backend-v1.contoso.com xxxxxxxxxxxxxxxx.fz13.alb.azure.com 80 8s |
| 294 | +ingress-01 azure-alb-external backend-v1.contoso.com xxxxxxxxxxxxxxxx.fz13.alb.azure.com 80, 443 10s |
| 295 | +``` |
| 296 | + |
| 297 | +You can check the status of the challenge by running: |
| 298 | + |
| 299 | +```bash |
| 300 | +kubectl get challenges.acme.cert-manager.io -n test-infra |
| 301 | +NAME STATE DOMAIN AGE |
| 302 | +cert-backend-1-2982214480-3407407859 pending backend-v1.contoso.com 16s |
| 303 | + |
| 304 | +kubectl get certificaterequests.cert-manager.io -n test-infra |
| 305 | +NAME APPROVED DENIED READY ISSUER REQUESTER AGE |
| 306 | +cert-backend-1 True False letsencrypt-prod system:serviceaccount:cert-manager:cert-manager 34s |
| 307 | +``` |
| 308 | + |
| 309 | +When the challenge is successful, the status changes to `READY=True` and the certificate is issued: |
| 310 | + |
| 311 | +```bash |
| 312 | +kubectl get certificate -n test-infra |
| 313 | +NAME READY SECRET AGE |
| 314 | +cert-backend True cert-backend 1m |
| 315 | +``` |
| 316 | + |
| 317 | +>[!Tip] |
| 318 | +>You can synchronize the hostnames of Ingress resources created in AKS automatically with Azure DNS zones by utilizing [External DNS](https://kubernetes-sigs.github.io/external-dns/latest/docs/tutorials/azure/) and [Workload Identity](https://kubernetes-sigs.github.io/external-dns/latest/docs/tutorials/azure/#managed-identity-using-workload-identity). |
| 319 | +
|
| 320 | +## Test access to the application |
| 321 | + |
| 322 | +The environment is now configured to route traffic to the sample application using the hostname associated with your certificate. |
| 323 | + |
| 324 | +>[!IMPORTANT] |
| 325 | +>Ensure you replace `contoso.com` with the domain name you are expecting the certificate to be issued to. |
| 326 | +
|
| 327 | +```bash |
| 328 | +curl https://backend-v1.contoso.com -v 2>&1 | grep issuer |
| 329 | +``` |
| 330 | + |
| 331 | +You should see the following output: |
| 332 | + |
| 333 | +`* issuer: C=US; O=Let's Encrypt; CN=R11` |
| 334 | + |
| 335 | +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. |
0 commit comments