4.3. Cert-Manager TLS Automation
In this lab you install Cert-Manager and configure a Let’s Encrypt ClusterIssuer so that
any Ingress in the cluster can receive a signed TLS certificate automatically.
Step 1 — Install Cert-Manager
Add the following Helm chart call to __main__.py, after the Traefik block:
Explanation
crds.enabled: true tells the Helm chart to deploy the Cert-Manager CRD bundle alongside the
controller. The CRDs define the ClusterIssuer, Certificate, and CertificateRequest kinds —
without them the Kubernetes API server rejects those resources as unknown types.
| Values key | Effect |
|---|---|
crds.enabled | Installs Cert-Manager CRDs as part of the Helm release |
Step 2 — Create a ClusterIssuer
A ClusterIssuer tells Cert-Manager which ACME server to use and how to prove domain ownership.
First, add add_custom_resource to the Kubernetes class in kubernetes.py, directly before
install_helm_chart:
Then add the following block to __main__.py after the cert-manager Helm chart:
Explanation
add_custom_resource complements install_helm_chart by allowing any Kubernetes custom resource
to be declared from __main__.py without exposing _k8s_provider as a public attribute.
pulumi.ResourceOptions.merge combines the component’s child_opts (which sets parent=self
for the resource tree) with the Kubernetes provider. The optional opts parameter is merged last,
so callers can layer in extra options such as depends_on without overriding the provider or
parent. Kwargs are forwarded directly to k8s.apiextensions.CustomResource, so the caller
controls api_version, kind, metadata, and spec.
depends_on=[cert_manager] is required here because Cert-Manager registers the ClusterIssuer
CRD when it starts. Without it Pulumi may try to create the ClusterIssuer before the CRD exists,
which causes an “unknown resource type” error. The explicit dependency ensures Pulumi waits for
the Helm release to complete before applying the custom resource.
Cert-Manager implements the ACME protocol to obtain certificates from Let’s Encrypt. With the HTTP-01 solver, the challenge flow is:
| ACME step | What happens |
|---|---|
| Order | Cert-Manager places a certificate order with the Let’s Encrypt ACME server |
| Challenge | Let’s Encrypt issues an HTTP-01 challenge token |
| Solve | Cert-Manager creates a temporary Ingress that Traefik serves on /.well-known/acme-challenge/ |
| Validate | Let’s Encrypt fetches the token over HTTP and confirms domain ownership |
| Issue | Let’s Encrypt signs the certificate; Cert-Manager stores it as a Kubernetes Secret |
privateKeySecretRef names the Kubernetes Secret where the ACME account private key is stored.
The key is created on first registration and reused for all subsequent orders.
CRD availability timing
Pulumi may attempt to create the ClusterIssuer before the Cert-Manager CRDs are registered.
If pulumi up reports no matches for kind "ClusterIssuer", run it a second time — the CRDs
will already be in place and the issuer will be created successfully.
Step 3 — Deploy and Verify
Apply the changes:
Once the deploy completes, confirm all Cert-Manager pods are running and the issuer is ready:
Expected output:
Explanation
Cert-Manager deploys three pods:
| Pod | Role |
|---|---|
cert-manager | Core controller — watches Certificate resources and drives the ACME flow |
cert-manager-cainjector | Injects CA bundles into webhook configurations and CRDs |
cert-manager-webhook | Validates and mutates Cert-Manager resources via admission webhooks |
READY: True on the ClusterIssuer confirms Cert-Manager has registered an ACME account with
Let’s Encrypt and is ready to issue certificates for your domain.
Step 4 — Test HTTPS with a Signed Certificate
Verify end-to-end TLS by deploying a test Ingress that requests a certificate from Let’s Encrypt.
Create https.yaml, replacing YOUR_USERNAME with your username:
Apply and watch the certificate being issued:
Once READY shows True, test the HTTPS endpoint:
Explanation
Adding the cert-manager.io/cluster-issuer annotation to an Ingress is all that is needed to
trigger automatic certificate issuance. Cert-Manager watches for annotated Ingress resources and
drives the full ACME flow without any further configuration:
| Resource created by Cert-Manager | Purpose |
|---|---|
CertificateRequest | Tracks a single ACME order for the requested hostnames |
Certificate | Long-lived object that Cert-Manager keeps renewed |
Secret/tls-secure | Stores the signed certificate and private key in PEM format |
Once the Secret exists, Traefik reads it automatically and terminates TLS for
secure.YOUR_USERNAME.labs.netrics.dev using the Let’s Encrypt-signed certificate.
Certificate issuance time
Let’s Encrypt must validate the HTTP-01 challenge before signing the certificate. This typically
takes 30–60 seconds. Use kubectl get certificate -n tests -w to watch progress — wait for
READY to become True before running curl.