Hallo zusammen! Nachdem wir in den vorherigen Posts über die Installation von k3s und die sichere Verwaltung von Secrets gesprochen haben, widmen wir uns heute der Automatisierung der DNS- und Zertifikatsverwaltung in unserem Heim-Kubernetes-Cluster. Mit Hilfe von ExternalDNS, Cert-Manager und Let’s Encrypt, alles gesteuert durch GitOps und FluxCD, schaffen wir eine robuste und automatisierte Infrastruktur für unsere Dienste.
Warum ExternalDNS, Cert-Manager und Let’s Encrypt?
ExternalDNS synchronisiert automatisch entdeckte DNS-Einträge mit einer Vielzahl von DNS-Providern, basierend auf den Ressourcen in unserem Kubernetes-Cluster. Dies erleichtert die DNS-Verwaltung erheblich, indem es sicherstellt, dass DNS-Einträge immer aktuell sind, ohne dass manuelle Eingriffe nötig sind.
Cert-Manager automatisiert das Management und die Erneuerung von SSL-Zertifikaten innerhalb von Kubernetes. Es nutzt Let’s Encrypt, um kostenlose und vertrauenswürdige Zertifikate bereitzustellen, die automatisch erneuert werden, um die Sicherheit zu gewährleisten.
Let’s Encrypt ist ein kostenloser, automatisierter und offener Zertifikataussteller, der es uns ermöglicht, SSL-Zertifikate zu erhalten, die von den meisten Browsern als vertrauenswürdig angesehen werden, ohne Kosten oder manuelle Prozesse.
Installation und Konfiguration
Hinweis: Die Anlage von Namespaces, Helmrepositories und Kustomizations habe ich in den vorherigen Beiträgen gezeigt. Diese Komponenten sehen sich immer sehr ähnlich. Um die Beiträge hier kürzer zu halten, werde ich die Definitionen hier nicht zeigen.
ExternalDNS
- Einrichten von ExternalDNS: Zuerst fügen wir die ExternalDNS-Konfiguration zu unserem Git-Repository hinzu
.
Dieses HelmRelease stellt ExternalDNS so ein, dass es mit dem spezifizierten DNS-Provider netcup interagiert und DNS-Einträge für die Domainexample.com
verwaltet. Die Einträge werden als TXT-Records selbst im DNS verwaltet (die sogenannte Registry). Um Konflikte bei cnames zu vermeiden, muss ein Prefix für die Registry-Records verwendet werden.
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: external-dns
namespace: external-dns
spec:
chart:
spec:
chart: external-dns
version: 7.3.2
sourceRef:
kind: HelmRepository
name: bitnami
namespace: default
interval: 15m
timeout: 5m
releaseName: external-dns
values: # paste icontents of upstream values.yaml below, indented 4 spaces
image:
registry: docker.io
repository: bitnami/external-dns
#tag: 0.14.1-debian-12-r2
digest: ""
pullSecrets: []
revisionHistoryLimit: 10
sources:
- crd
# - service
- ingress
# - contour-httpproxy
provider: webhook
initContainers: []
dnsConfig: {}
sidecars:
- name: netcup-webhook
image: ghcr.io/mrueg/external-dns-netcup-webhook:latest
imagePullPolicy: Always
args:
- --log-level=debug
- --domain-filter=example.com
- --netcup-customer-id=<customer id>
env:
- name: NETCUP_API_KEY
valueFrom:
secretKeyRef:
key: NETCUP_API_KEY
name: netcup-credentials
- name: NETCUP_API_PASSWORD
valueFrom:
secretKeyRef:
key: NETCUP_API_PASSWORD
name: netcup-credentials
## @param registry Registry method to use (options: txt, aws-sd, dynamodb, noop)
## ref: https://github.com/kubernetes-sigs/external-dns/blob/master/docs/proposal/registry.md
##
registry: "txt"
## @param txtPrefix When using the TXT registry, a prefix for ownership records that avoids collision with CNAME entries (optional)<CNAME record> (Mutual exclusive with txt-suffix)
##
txtPrefix: "k3s."
## @param txtSuffix When using the TXT registry, a suffix for ownership records that avoids collision with CNAME entries (optional)<CNAME record>.suffix (Mutual exclusive with txt-prefix)
##
...
Code-Sprache: HTML, XML (xml)
Cert-Manager
- Installation von Cert-Manager mit FluxCD: Füge die die Cert-Manager Konfiguration zu deinem Repository hinzu.
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: cert-manager
namespace: cert-manager
spec:
chart:
spec:
chart: cert-manager
version: v1.14.x
sourceRef:
kind: HelmRepository
name: jetstack
namespace: default
interval: 15m
timeout: 5m
releaseName: cert-manager
values:
# füge die Inhalte der default values.yaml hier ein.
# installCRDs sollte auf true gesetzt werden, um die
# Custom Resource Definitions auf diesem Weg zu installieren
Let’s Encrypt ClusterIssuer
- Konfigurieren eines Let’s Encrypt ClusterIssuer: Dieser ClusterIssuer wird verwendet, um Zertifikate von Let’s Encrypt anzufordern und automatisch zu erneuern. Um angeforderte Zertifikate verifizieren zu können, gibt es verschiedene Methoden. Die bekanntesten sind die http-01-challenge und die dns-01-challenge. Ich habe mich hier für die dns-01-challenge entschieden. Das erspart mir, Port 80 in meinem Heimetz freigeben zu müssen. Voraussetzung für die Challenge ist, dass es einen Provider für den DNS-Anbieter gibt. Ich nutze netcup, wofür es einen Webhook gibt.
HelmRelease für den Webhook:
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: cert-manager-webhook-netcup
namespace: cert-manager
spec:
chart:
spec:
chart: cert-manager-webhook-netcup
version: 1.0.x
sourceRef:
kind: HelmRepository
name: cert-manager-webhook-netcup
namespace: default
interval: 15m
timeout: 5m
releaseName: cert-manager-webhook-netcup
values: # paste icontents of upstream values.yaml below, indented 4 spaces
# The GroupName here is used to identify your company or business unit that
# created this webhook.
# For example, this may be "acme.mycompany.com".
# This name will need to be referenced in each Issuer's `webhook` stanza to
# inform cert-manager of where to send ChallengePayload resources in order to
# solve the DNS01 challenge.
# This group name should be **unique**, hence using your own company's domain
# here is recommended.
groupName: com.netcup.webhook
certManager:
namespace: cert-manager
serviceAccountName: cert-manager
image:
repository: ghcr.io/aellwein/cert-manager-webhook-netcup
# set version here for upcoming release
# tag: 1.0.22
# sha hash can be used to specify image version, instead of tag
hash: ""
pullPolicy: IfNotPresent
nameOverride: ""
fullnameOverride: ""
service:
type: ClusterIP
port: 443
resources:
{}
nodeSelector: {}
tolerations: []
affinity: {}
Der Clusterissuer sorgt dafür, dass Zertifikate clusterweit ausgestellt werden können.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
email: <mail adress>
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- selector:
dnsZones:
- "<your dns zone>"
dns01:
webhook:
groupName: com.netcup.webhook
solverName: netcup
config:
secretRef: <name of the secret containing your provider credentials>
secretNamespace: cert-manager
Code-Sprache: HTML, XML (xml)
Nun müssen wir noch ein Zertifikat erzeugen lassen. Bis das Zertifikat einsatzbereit ist kann es erfahrungsgemäß bis zu einer Stunde dauern. In diesem Beispiel wird ein Wildcardzertifikat angelegt. Die reflector-Annotationen sollen dafür sorgen, dass das erzeugte Zertifikat in andere Namespaces kopiert werden kann. Dazu später mehr.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: letsencrypt-wildcard-cert-prod
namespace: letsencrypt-wildcard-cert
spec:
# secretName doesn't have to match the certificate name, but it may as well, for simplicity!
secretName: letsencrypt-wildcard-cert-prod
secretTemplate:
annotations:
reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true"
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- "my.example.com"
- "*.my.example.com"
Fortgeschrittene DNS- und Zertifikatsstrategien
Nutzung von Wildcard-Domains
Die Verwendung von Wildcard-Domains ist eine effektive Strategie, um den Bedarf an ständig neuen DNS-Einträgen zu minimieren. Durch die Konfiguration eines Wildcard-Zertifikats für eine Hauptdomain (z.B. *.example.com
) können viele Subdomains ohne zusätzliche DNS-Konfigurationen abgedeckt werden. Dies vereinfacht die Verwaltung erheblich, da nicht für jede neue Subdomain ein separater DNS-Eintrag erforderlich ist.
Dynamische DNS-Einträge mit ExternalDNS
Für Szenarien, in denen spezifische oder zahlreiche Subdomains benötigt werden, bietet ExternalDNS die Möglichkeit, DNS-Einträge dynamisch zu erzeugen. Dies kann auf zwei Arten geschehen:
- CRD-Objekte: Indem Custom Resource Definitions (CRDs) verwendet werden, die speziell für DNS-Einträge in ExternalDNS definiert sind.
- Annotations in Ingress-Objekten: Durch das Hinzufügen spezifischer Annotations wie
external-dns.alpha.kubernetes.io/hostname
undexternal-dns.alpha.kubernetes.io/target
zu Ingress-Objekten. Dies ermöglicht es ExternalDNS, entsprechende DNS-Einträge automatisch zu erstellen und zu aktualisieren, basierend auf den Ingress-Konfigurationen.
Verwaltung von Let’s Encrypt Zertifikaten über Namespaces
Die Zertifikate von Let’s Encrypt, einschließlich der Wildcard-Zertifikate, müssen im richtigen Kubernetes Namespace platziert werden, um von den entsprechenden Services genutzt werden zu können. Da das Wildcard-Zertifikat typischerweise nur in einem Namespace vorhanden ist, nutze ich Kubernetes Reflector zur Synchronisation:
Kubernetes Reflector ist ein Tool, das Secrets und ConfigMaps zwischen verschiedenen Namespaces repliziert. Dies ist besonders nützlich, um das Wildcard-Zertifikat über mehrere Namespaces hinweg verfügbar zu machen, ohne es manuell in jedem Namespace neu erstellen zu müssen. Hier ein Beispiel, wie ein solches Setup konfiguriert sein könnte:
<code>apiVersion: v1
kind: Secret
metadata:
name: wildcard-cert
annotations:
reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true"
data:
tls.crt: <base64-encoded-cert>
tls.key: <base64-encoded-key>
</code>
Code-Sprache: JavaScript (javascript)
Fazit
Mit der erfolgreichen Integration von ExternalDNS und Cert-Manager in unser GitOps-Workflow und der Nutzung von Let’s Encrypt über DNS-Challenges haben wir eine vollautomatische Lösung für das Management von DNS und SSL-Zertifikaten geschaffen. Durch die Verwendung von Wildcard-Zertifikaten und die sorgfältige Planung der Zertifikatserneuerungen minimieren wir die Anzahl der notwendigen Anfragen und vereinfachen die Verwaltung. Dies stellt sicher, dass unsere Services sicher, erreichbar und gut geschützt sind.
Weitere Blogposts aus der k3s-Reihe
- Einführung in meinen Kubernetes-Heimcluster: Warum und Wie?
- Der Aufbau meines Kubernetes-Heimclusters: Die Wahl der Hardware
- Initialisierung meines Heim-Kubernetes-Clusters: k3s mit Ansible
- GitOps mit FluxCD für meinen Heim-Kubernetes-Cluster
- Sichere Verwaltung von Kubernetes Secrets mit Sealed Secrets
- Ingress und Load Balancer in Kubernetes: Traefik und MetalLB
- Storage, Backup und Restore in meinem Kubernetes-Cluster
Ein Gedanke zu „Automatisierte DNS- und SSL-Zertifikatsverwaltung in Kubernetes“