Cert-manager and Let's Encrypt with Application Gateway for Containers - Gateway API
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 Gateway 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 HTTPRoute 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
If following the ALB managed deployment strategy, ensure that you provision your ALB Controller 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://learn.microsoft.com/azure/application-gateway/for-containers/examples/traffic-split-scenario/deployment.yaml
This command creates the following on your cluster:
- a namespace called
test-infra
- two services called
backend-v1
andbackend-v2
in thetest-infra
namespace - two deployments called
backend-v1
andbackend-v2
in thetest-infra
namespace
- a namespace called
Create a Gateway resource
Create a new Gateway
resource that listens for HTTP requests from Let's Encrypt during the challenge process.
Create a gateway:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway-01
namespace: test-infra
annotations:
alb.networking.azure.io/alb-namespace: alb-test-infra
alb.networking.azure.io/alb-name: alb-test
cert-manager.io/issuer: letsencrypt-cert
spec:
gatewayClassName: azure-alb-external
listeners:
- name: http-listener
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
EOF
Note
When the ALB Controller creates the Application Gateway for Containers resources in ARM, it'll use the following naming convention for a frontend resource: fe-<8 randomly generated characters>
If you would like to change the name of the frontend created in Azure, consider following the bring your own deployment strategy.
Once the gateway resource is created, ensure the status is valid, the listener is Programmed, and an address is assigned to the gateway.
kubectl get gateway gateway-01 -n test-infra -o yaml
Example output of successful gateway creation.
status:
addresses:
- type: IPAddress
value: xxxx.yyyy.alb.azure.com
conditions:
- lastTransitionTime: "2023-06-19T21:04:55Z"
message: Valid Gateway
observedGeneration: 1
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: "2023-06-19T21:04:55Z"
message: Application Gateway For Containers resource has been successfully updated.
observedGeneration: 1
reason: Programmed
status: "True"
type: Programmed
listeners:
- attachedRoutes: 0
conditions:
- lastTransitionTime: "2023-06-19T21:04:55Z"
message: ""
observedGeneration: 1
reason: ResolvedRefs
status: "True"
type: ResolvedRefs
- lastTransitionTime: "2023-06-19T21:04:55Z"
message: Listener is accepted
observedGeneration: 1
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: "2023-06-19T21:04:55Z"
message: Application Gateway For Containers resource has been successfully updated.
observedGeneration: 1
reason: Programmed
status: "True"
type: Programmed
name: https-listener
supportedKinds:
- group: gateway.networking.k8s.io
kind: HTTPRoute
Install Cert-Manager
Install cert-manager using Helm:
helm repo add jetstack https://charts.jetstack.io --force-update
helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.16.0 \
--set config.enableGatewayAPI=true \
--set crds.enabled=true
The helm installation creates three deployments and some services and pods in a new namespace called cert-manager
. It also installs cluster-scoped supporting resources, such as RBAC roles and Custom Resource Definitions.
Create a ClusterIssuer
Create a ClusterIssuer resource to define how cert-manager will communicate with Let's Encrypt. For this example, an HTTP challenge is used. During challenge, cert-manager creates an HTTPRoute
resource and corresponding pod presenting a validation endpoint to prove ownership of the domain.
Tip
Other challenges supported by Let's Encrypt are documented on letsencrypt.org - Challenge Types
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
namespace: test-infra
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:
gatewayHTTPRoute:
parentRefs:
- name: gateway-01
namespace: test-infra
kind: Gateway
EOF
Verify the resource was created
kubectl get ClusterIssuer -A -o yaml
The status should show True
and type Ready
.
status:
acme:
lastPrivateKeyHash: x+xxxxxxxxxxxxxxxxxxxxxxx+MY4PAEeotr9XH3V7I=
lastRegisteredEmail: your-email@example.com
uri: https://acme-staging-v02.api.letsencrypt.org/acme/acct/165888253
conditions:
- lastTransitionTime: "2024-10-04T21:22:40Z"
message: The ACME account was registered with the ACME server
observedGeneration: 1
reason: ACMEAccountRegistered
status: "True"
type: Ready
Create a certificate
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: letsencrypt-cert
namespace: test-infra
spec:
secretName: letsencrypt-secret # name published to secret store
issuerRef:
name: letsencrypt-prod # ClusterIssuer resource name
kind: ClusterIssuer
dnsNames:
- contoso.com # domain name to be used
EOF
Execute the following command to validate issuance of the certificate. If the certificate has been issued, the value of the READY
column should be True
.
kubectl get certificate letsencrypt-cert -n test-infra
If the certificate wasn't issued, you can execute the following command to validate the status of a challenge.
Note
If the certificate has been successfully issued, the challenge will no longer be listed.
kubectl get challenges -n test-infra -o yaml
Enable HTTPS on the Gateway resource
Modify the gateway to add a second listener to terminate HTTPS requests with the issued Let's Encrypt certificate.
Create a gateway:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway-01
namespace: test-infra
annotations:
alb.networking.azure.io/alb-namespace: alb-test-infra
alb.networking.azure.io/alb-name: alb-test
cert-manager.io/issuer: letsencrypt-cert
spec:
gatewayClassName: azure-alb-external
listeners:
- name: http-listener
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
- name: https-listener
port: 443
protocol: HTTPS
tls:
certificateRefs:
- name: letsencrypt-secret
allowedRoutes:
namespaces:
from: Same
EOF
Note
When the ALB Controller creates the Application Gateway for Containers resources in ARM, it'll use the following naming convention for a frontend resource: fe-<8 randomly generated characters>
If you would like to change the name of the frontend created in Azure, consider following the bring your own deployment strategy.
Create a HTTPRoute that listens for your hostname
Create a HTTPRoute to handle requests received by the https-listener
listener.
Important
Ensure you replace contoso.com
with the domain name you are expecting the certificate to be issued to.
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: https-example
namespace: test-infra
spec:
parentRefs:
- name: gateway-01
hostnames:
- "contoso.com"
rules:
- backendRefs:
- name: backend-v1
port: 8080
EOF
Once the HTTPRoute resource is created, ensure the route is Accepted and the Application Gateway for Containers resource is Programmed.
kubectl get httproute cert-manager-route -n test-infra -o yaml
Verify the status of the Application Gateway for Containers resource is successfully updated.
status:
parents:
- conditions:
- lastTransitionTime: "2023-06-19T22:18:23Z"
message: ""
observedGeneration: 1
reason: ResolvedRefs
status: "True"
type: ResolvedRefs
- lastTransitionTime: "2023-06-19T22:18:23Z"
message: Route is Accepted
observedGeneration: 1
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: "2023-06-19T22:18:23Z"
message: Application Gateway For Containers resource has been successfully updated.
observedGeneration: 1
reason: Programmed
status: "True"
type: Programmed
controllerName: alb.networking.azure.io/alb-controller
parentRef:
group: gateway.networking.k8s.io
kind: Gateway
name: gateway-01
namespace: test-infra
Test access to the application
Now we're ready to send some traffic to our sample application, via the hostname used for your certificate.
Important
Ensure you replace contoso.com
with the domain name you are expecting the certificate to be issued to.
curl https://contoso.com/ -v 2>&1 | grep issuer
Upon result, you should see
* issuer: C=US; O=Let's Encrypt; CN=R10
Congratulations, you have installed ALB Controller, deployed a backend application, issued a certificate from Let's Encrypt with cert-manager, and routed traffic to the application via Application Gateway for Containers.