Exposing services with Ingress and automatic TLS via cert-manager
Quick answer
A plain Kubernetes Service gives a set of pods a stable internal address. An Ingress, backed by an ingress controller such as nginx-ingress, adds HTTP(S) routing in front of that: host and path rules, and a single external entry point for many services. Layer cert-manager on top and it requests, renews, and attaches Let's Encrypt TLS certificates to that Ingress automatically, so you're not managing certificates by hand.
This is standard, open-source Kubernetes tooling, not specific to any provider. It works the same way regardless of which infrastructure your cluster runs on, see Self-managed Kubernetes on Worldstream: choosing your infrastructure if you haven't picked that yet.
Service vs. Ingress
A Service gives a stable virtual IP and DNS name to a set of pods selected by label, and load-balances traffic across them. That's enough for internal, pod-to-pod traffic, or for a LoadBalancer-type Service exposing one application externally. It doesn't give you HTTP-aware routing: no host-based rules, no path-based rules, and no shared entry point for multiple applications.
An Ingress resource sits a layer above that. It describes routing rules, "requests for shop.example.com go to the shop Service, requests for api.example.com/v1 go to the api Service", and an ingress controller running in the cluster (nginx-ingress is the most common, but Traefik and others exist too) reads those rules and does the actual routing. One ingress controller, and typically one external load balancer or public IP, can front many applications this way.
A basic Ingress manifest
This routes traffic for shop.example.com to a Service called shop-service on port 80.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop-ingress
namespace: shop
spec:
ingressClassName: nginx
rules:
- host: shop.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: shop-service
port:
number: 80At this point the Ingress serves plain HTTP. Getting HTTPS working means either attaching a certificate manually or, more sustainably, letting cert-manager handle issuance and renewal.
Adding cert-manager for automatic TLS
cert-manager is a Kubernetes add-on that watches for certificate requests and talks to a certificate authority, most commonly Let's Encrypt, to issue and renew them without manual intervention. It's installed once into the cluster, then configured with an Issuer or ClusterIssuer that describes which CA to use and how to prove domain ownership.
A ClusterIssuer using Let's Encrypt's HTTP-01 challenge, which proves domain ownership by serving a token over plain HTTP through the ingress controller, looks like this:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: platform-team@example.com
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
ingressClassName: nginxWith that ClusterIssuer in place, annotate the Ingress and add a tls block naming the secret cert-manager should create and keep up to date. cert-manager watches for this annotation, requests the certificate, and stores it in the named secret, renewing it automatically before it expires.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop-ingress
namespace: shop
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- shop.example.com
secretName: shop-tls
rules:
- host: shop.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: shop-service
port:
number: 80From here, cert-manager and the ingress controller do the ongoing work between them: the controller routes and terminates TLS, cert-manager keeps the certificate behind it valid. Check the status of a certificate at any point with kubectl get certificate -n shop and kubectl describe certificate shop-tls -n shop, the latter is the quickest way to see why an issuance attempt failed if the secret isn't appearing.