Zum Inhalt springen

Eine App mit Ingress und TLS veröffentlichen

Nach Ein Image mit kubectl deployen war die Demo-App nur innerhalb des Clusters erreichbar. Diese Anleitung stellt sie unter deiner eigenen Domain ins Internet, mit gültigem HTTPS-Zertifikat: ein Ingress-Controller davor, cert-manager, der Zertifikate von Let’s Encrypt holt, und ein Ingress-Objekt pro App.

Ein PaaSbox-Cluster hat den Ingress-Controller schon und bietet cert-manager als Add-on an: Folge Auf einem PaaSbox-Cluster. Auf jedem anderen Cluster folgst du Auf jedem anderen Cluster.

  • Die laufende Demo-App aus Ein Image mit kubectl deployen (Namespace demo, Service web auf Port 80) oder einen beliebigen Service, den du veröffentlichen willst.
  • Eine Domain, die dir gehört, samt Zugriff auf ihre DNS-Einträge. Diese Anleitung verwendet demo.example.com; setz überall deine eigene ein.

Traefik, der Ingress-Controller, den k3s mitbringt, läuft auf jedem PaaSbox-Cluster und antwortet auf den Ports 80 und 443 des Servers; die Firewall des Clusters lässt beide von überall herein. Seine IngressClass ist traefik. Installiere keinen zweiten Ingress-Controller wie ingress-nginx: Traefik hält beide Ports, und der Cluster legt keine Hetzner Load Balancer an, ein zweiter Controller bekommt also weder das eine noch das andere.

Läuft auf dem Cluster das Add-on PaaSbox Platform (im Labor gebaut, noch nicht veröffentlicht), bringt es sein eigenes cert-manager mit, und das cert-manager-Add-on kann nicht daneben an sein. Dort fordert die SaaSApplication einer App ihr Zertifikat selbst an: siehe Schritt 2 des Lernpfads, Deine erste App deployen.

Setz auf dem Reiter Add-ons des Clusters, im Abschnitt cert-manager, den Haken bei On und füll aus:

  • ACME e-mail: Let’s Encrypt legt das Konto unter dieser Adresse an und schickt Ablaufwarnungen dorthin.
  • Default issuer: Lass production stehen. staging stellt Zertifikate aus, denen Browser nicht vertrauen, ohne die Ratenlimits von production; damit probierst du ein Setup aus.
  • Challenge: Lass http01 stehen. Let’s Encrypt holt ein Token von deiner Domain über Port 80, durch Traefik, und sonst ist nichts nötig.

Wähle Save. Das Add-on installiert cert-manager und zwei ClusterIssuer, letsencrypt-staging und letsencrypt-production. In einem Labor am 2026-10-10 waren sie 21 Sekunden später bereit:

Terminal-Fenster
kubectl get clusterissuers

Leg bei deinem DNS-Anbieter einen A-Eintrag für deinen Hostnamen an, der auf die öffentliche IPv4-Adresse des Servers zeigt. Es ist die Adresse, auf die der API-Name des Clusters auflöst, und die Hetzner-Konsole zeigt sie beim Server:

demo.example.com. 300 IN A <the server's IPv4 address>

Prüfe, dass er auflöst, bevor du weitermachst; vorher kann das Zertifikat nicht ausgestellt werden:

Terminal-Fenster
dig +short demo.example.com

Der API-Name des Clusters ist für kubectl da, nicht für deine App: Gib der App einen eigenen Namen.

Speichere das Folgende als ingress.yaml. Die Annotation nennt den Issuer, ingressClassName nennt Traefik:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: demo
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
spec:
ingressClassName: traefik
tls:
- hosts: [demo.example.com]
secretName: web-tls
rules:
- host: demo.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port: { number: 80 }

Statt einen Issuer zu nennen, nimmt die Annotation kubernetes.io/tls-acme: "true" den Default issuer des Add-ons.

Terminal-Fenster
kubectl apply -f ingress.yaml
kubectl -n demo get certificate -w

READY wird True, sobald Let’s Encrypt das Token geprüft hat. Im Labor wurde ein Staging-Zertifikat in 31 Sekunden über Traefik ausgestellt.

Terminal-Fenster
curl https://demo.example.com
curl -vI https://demo.example.com 2>&1 | grep -E 'subject|issuer'

Jede weitere App ist ein weiterer Ingress: derselbe Traefik, derselbe Issuer, ein Manifest pro Hostname.

Zum Aufräumen löschst du den Ingress und den DNS-Eintrag. Schaltest du das Add-on aus, verschwinden cert-manager und die ClusterIssuer; die Zertifikate und ihre Secrets bleiben.

Diese Schritte installieren einen Ingress-Controller und cert-manager mit Helm. Schritt 1 setzt einen Cluster bei Hetzner voraus, dessen Cloud Controller für Services vom Typ LoadBalancer Load Balancer anlegt. Neben kubectl brauchst du helm.

Der Ingress-Controller nimmt allen HTTP(S)-Traffic in den Cluster entgegen und leitet ihn an deine Services weiter. Sein Service hat den Typ LoadBalancer, darum legt der Cloud Controller bei der Installation einen Hetzner Load Balancer in deinem Projekt an:

Terminal-Fenster
helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx --create-namespace \
--set controller.service.annotations."load-balancer\.hetzner\.cloud/location"=nbg1

Setz die Location-Annotation auf den Standort deines Clusters (hier nbg1). Warte auf die öffentliche IP des Load Balancers:

Terminal-Fenster
kubectl -n ingress-nginx get svc ingress-nginx-controller -w

Nach etwa einer Minute füllt sich die Spalte EXTERNAL-IP. Dein DNS-Eintrag zeigt darauf.

Der Load Balancer steht auf deiner Hetzner-Rechnung. Er liegt in deinem Hetzner-Projekt, und Hetzner rechnet ihn direkt mit dir ab. Ein Load Balancer bedient alle Apps hinter dem Ingress-Controller. Deinstallierst du den Controller (siehe Aufräumen), wird er gelöscht und die Kosten enden.

2. cert-manager und einen Let’s-Encrypt-Issuer installieren

Abschnitt betitelt „2. cert-manager und einen Let’s-Encrypt-Issuer installieren“
Terminal-Fenster
helm upgrade --install cert-manager cert-manager \
--repo https://charts.jetstack.io \
--namespace cert-manager --create-namespace \
--set crds.enabled=true

Dann bekommt er ein Konto bei Let’s Encrypt. Speichere das Folgende als cluster-issuer.yaml und ersetze die E-Mail-Adresse durch deine eigene:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: you@example.com
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
ingressClassName: nginx
Terminal-Fenster
kubectl apply -f cluster-issuer.yaml

Das nutzt die HTTP-01-Challenge: Let’s Encrypt holt ein Token per einfachem HTTP über deinen Ingress. DNS-Zugangsdaten brauchst du nicht, aber die Domain muss zuerst auf den Load Balancer auflösen.

Leg einen A-Eintrag für deinen Hostnamen an, der auf die EXTERNAL-IP aus Schritt 1 zeigt, und prüfe ihn mit dig +short demo.example.com.

Nimm das Manifest aus Schritt 3 des PaaSbox-Abschnitts mit zwei Änderungen: die Annotation cert-manager.io/cluster-issuer: letsencrypt-prod und ingressClassName: nginx. Wende es an und sieh dem Zertifikat zu:

Terminal-Fenster
kubectl apply -f ingress.yaml
kubectl -n demo get certificate -w

READY wird True, meist innerhalb von ein bis zwei Minuten. Prüfe mit curl https://demo.example.com.

Terminal-Fenster
kubectl delete -f ingress.yaml
helm -n cert-manager uninstall cert-manager
helm -n ingress-nginx uninstall ingress-nginx # deletes the Hetzner load balancer
kubectl delete namespace ingress-nginx cert-manager

Entferne auch den DNS-Eintrag.

Fast immer liegt es an DNS: Der Eintrag löst öffentlich noch nicht auf (frische Einträge und Negative Caching können ein paar Minuten dauern), oder er zeigt auf die falsche Adresse. kubectl -n demo describe certificaterequest und die Logs von cert-manager sagen, welche Prüfung fehlgeschlagen ist. Korrigiere den Eintrag, und die Ausstellung läuft von selbst weiter.