From port-forward to a real domain with TLS in 15 minutes.
L10 chart has ingress.enabled: false; we access via port-forward.
We flip the bit. Ingress controller + cert-manager + ExternalDNS = real hostname + HTTPS, with DNS records the cluster manages itself.
ingress-nginx) — the L7 reverse proxy that reads Ingress resources and routes traffic.Certificate resources, requests certs from Let’s Encrypt (or other ACME issuer), stores them as TLS Secrets.Ingress resources, creates DNS records at your DNS provider.apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shortener
annotations:
cert-manager.io/cluster-issuer: letsencrypt-staging
external-dns.alpha.kubernetes.io/hostname: shortener.example.com
spec:
ingressClassName: nginx
tls:
- hosts: [shortener.example.com]
secretName: shortener-tls
rules:
- host: shortener.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service: {name: shortener-api, port: {number: 80}}
Annotations wire cert-manager + ExternalDNS to the same resource; the controller handles the rest.
ingress-nginxhelm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespace \
--set controller.publishService.enabled=true
A Service of type LoadBalancer is created — on EKS/GKE/AKS this provisions a cloud LB; on kind you use the kind-specific shim (see slide 11).
helm repo add jetstack https://charts.jetstack.io
helm upgrade --install cert-manager jetstack/cert-manager \
--namespace cert-manager --create-namespace \
--set crds.enabled=true
CRDs install with the chart now (used to be a separate step); Issuer / ClusterIssuer / Certificate resources become available cluster-wide.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata: {name: letsencrypt-staging}
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef: {name: letsencrypt-staging-key}
solvers:
- http01:
ingress: {class: nginx}
Always start with staging. Let’s Encrypt rate-limits production hard; you’ll get blocked by misconfigurations.
You write tls: on the Ingress + the cert-manager.io/cluster-issuer annotation. cert-manager sees the Ingress, creates a Certificate resource, the Certificate controller runs the ACME http-01 challenge through ingress-nginx, stores the cert in a Secret named per secretName, and ingress-nginx serves it. Cert renewal is automatic.
# Helm values for ExternalDNS targeting Cloudflare:
provider: cloudflare
cloudflare:
apiToken: REPLACE_ME # from a Secret in real installs
sources: [ingress]
policy: sync
domainFilters: ["example.com"]
ExternalDNS watches Ingress hosts and writes A/CNAME records at your DNS provider — no manual DNS edits.
ingress-nginx.ingress.enabled: true with host: shortener.<your-domain>.helm upgrade --install.Ready.https://shortener.<your-domain> — the browser warns (“staging issuer”) but TLS works.kind + cloud-provider-kindgo install sigs.k8s.io/cloud-provider-kind@latest
sudo cloud-provider-kind &
cloud-provider-kind gives the LoadBalancer Service an IP on the host loopback so you can curl ingress-nginx locally. ExternalDNS won’t make sense locally (no real DNS) — for the lab we either skip ExternalDNS on kind or use a 127.0.0.1 host with /etc/hosts.
Once staging works end-to-end, copy the ClusterIssuer to letsencrypt-prod (swap the server URL) and switch the Ingress annotation to letsencrypt-prod. Delete the old staging cert Secret; cert-manager re-issues from prod within ~60 seconds.