Zum Inhalt springen

ExternalDNS mit STACKIT DNS verwenden

ExternalDNS ist ein Add-on für Kubernetes, das die Verwaltung von Domain-Name-System-Einträgen (DNS-Einträgen) für Kubernetes-Dienste durch die Nutzung verschiedener DNS-Anbieter automatisiert. Während Kubernetes DNS-Einträge traditionell intern verwaltet, erweitert ExternalDNS diese Funktionalität, indem es die Verantwortung für die Verwaltung der DNS-Einträge an einen externen DNS-Anbieter wie STACKIT überträgt. Folglich ermöglicht der STACKIT-Webhook die Verwaltung Ihrer STACKIT-Domains innerhalb Ihres Kubernetes-Clusters mithilfe von ExternalDNS.

  • Kompatibilität der Kubernetes-Cluster-Version: Um ExternalDNS bereitzustellen, stellen Sie sicher, dass Sie einen Kubernetes-Cluster mit den Versionen 1.24 bis 1.26 betreiben. Obwohl andere Versionen kompatibel sein könnten, wurden diese Versionen für optimale Leistung und Stabilität validiert.
  • Cluster-Typ – SKE: Diese Anleitung enthält spezifische Anweisungen für das Deployment auf einem SKE-Cluster. Alternative Cluster-Konfigurationen können abweichende Implementierungsschritte erfordern.
  • Erforderliche Zugangsdaten: Ein dediziertes Servicekonto und ein entsprechender Authentifizierungstoken sind für den Bereitstellungsprozess erforderlich. ExternalDNS wird programmgesteuert Einträge in der DNS-Zone erstellen und löschen, wofür das Servicekonto Berechtigungen auf Projektmitgliedsebene benötigt.
  • Mechanismus zur Anwendungsbereitstellung: Diese Dokumentation setzt die Verwendung eines SKE-Clusters voraus. Daher können die Methoden zur Bereitstellung von Anwendungen über LoadBalancer- oder NodePort-Dienste je nach Ihrer Cluster-Konfiguration variieren.

Durch die Einhaltung dieser Voraussetzungen sind Sie besser auf die folgenden Schritte in diesem Tutorial vorbereitet.

Beginnen Sie mit dem Abrufen Ihres STACKIT-Authentifizierungstokens. Diesen finden Sie im Servicekonto innerhalb des Projekts, in dem Sie die DNS-Zone und die Einträge erstellen möchten. Hinweis: ExternalDNS wird automatisch Einträge aus Ihren Kubernetes-Ingresses oder -Diensten innerhalb dieser angegebenen Zone erstellen.

Terminal-Fenster
export AUTHENTICATION_TOKEN="ey..."

Erstellen Sie eine DNS-Zone. Dies kann entweder über unsere Benutzeroberfläche (UI) oder die REST-API erfolgen. In dieser Anleitung verwenden wir die STACKIT DNS API.

Terminal-Fenster
export PROJECTID="4ec710a8-fe7c-42d3-aca4-84d9ff69a14e"
export DNS_ZONE="example.runs.onstackit.cloud"
curl --location "https://dns.api.stackit.cloud/v1/projects/$PROJECTID/zones" \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $AUTHENTICATION_TOKEN" \
--data '{
"name": "external-dns",
"dnsName": "'"$DNS_ZONE"'"
}'

Sobald die Zone über die API erstellt wurde, erhalten Sie eine Antwort mit einer eindeutigen Kennung für die Zone, der sogenannten zone.id. Diese ID ist für zukünftige API-Aufrufe im Zusammenhang mit dieser Zone erforderlich.

Terminal-Fenster
export ZONEID="1035d922-7a90-4a20-ba89-308ed9eb0cff"

Erstellen Sie ein Kubernetes-Secret unter Verwendung des oben genannten Authentifizierungstokens. Dieses Secret unterstützt unseren Webhook bei der Authentifizierung an unserer API.

Terminal-Fenster
kubectl create ns external-dns && kubectl create secret generic external-dns-stackit-webhook --from-literal=auth-token=$AUTHENTICATION_TOKEN -n external-dns

Nachdem die Vorbereitungen abgeschlossen sind, können Sie ExternalDNS mit unserem Webhook bereitstellen:

Terminal-Fenster
kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
namespace: external-dns
labels:
app.kubernetes.io/name: external-dns
app.kubernetes.io/instance: external-dns
imagePullSecrets:
- name: docker-secret
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
labels:
app.kubernetes.io/name: external-dns
app.kubernetes.io/instance: external-dns
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list","watch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["services","endpoints"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
labels:
app.kubernetes.io/name: external-dns
app.kubernetes.io/instance: external-dns
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: external-dns
---
apiVersion: v1
kind: Service
metadata:
name: external-dns
namespace: external-dns
labels:
app.kubernetes.io/name: external-dns
app.kubernetes.io/instance: external-dns
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: external-dns
app.kubernetes.io/instance: external-dns
ports:
- name: http
port: 7979
targetPort: http
protocol: TCP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
namespace: external-dns
labels:
app.kubernetes.io/name: external-dns
app.kubernetes.io/instance: external-dns
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: external-dns
app.kubernetes.io/instance: external-dns
strategy:
type: Recreate
template:
metadata:
labels:
app.kubernetes.io/name: external-dns
app.kubernetes.io/instance: external-dns
spec:
serviceAccountName: external-dns
securityContext:
fsGroup: 65534
containers:
- name: external-dns
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65534
image: registry.k8s.io/external-dns/external-dns:v0.14.0
imagePullPolicy: IfNotPresent
args:
- --log-level=info
- --log-format=text
- --interval=1m
- --source=service
- --source=ingress
- --policy=sync # set it upsert-only if you don't want it to delete records
- --txt-owner-id=my-testcluster # has to be uniq for each cluster if multiple external-dns managing one zone
- --provider=webhook
ports:
- name: http
protocol: TCP
containerPort: 7979
livenessProbe:
failureThreshold: 2
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
readinessProbe:
failureThreshold: 6
httpGet:
path: /healthz
port: http
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
- name: webhook
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65534
image: ghcr.io/stackitcloud/external-dns-stackit-webhook:v0.2.0
imagePullPolicy: IfNotPresent
args:
- --project-id=$PROJECTID
- --domain-filter=$DNS_ZONE
ports:
- name: http
protocol: TCP
containerPort: 8888
livenessProbe:
failureThreshold: 2
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
readinessProbe:
failureThreshold: 6
httpGet:
path: /healthz
port: http
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
env:
- name: AUTH_TOKEN
valueFrom:
secretKeyRef:
name: external-dns-stackit-webhook
key: auth-token
EOF

Beachten Sie, dass das Setzen eines Domain-Filters nicht zwingend ist. Es kann jedoch von Vorteil sein, wenn Ihr Cluster über mehrere mit unterschiedlichen Zonen verknüpfte Datensätze verfügt.

Um zu überprüfen, ob ExternalDNS betriebsbereit ist, verwenden Sie den unten angegebenen Befehl:

Terminal-Fenster
kubectl get pods -n external-dns

Sie erhalten die folgende Ausgabe:

NAME READY STATUS RESTARTS AGE
external-dns-7fb65c6899-lqcsp 2/2 Running 0 18s

Stellen Sie einen Reverse-Proxy bereit, wie zum Beispiel den Nginx Ingress Controller:

Terminal-Fenster
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
kubectl create ns ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx --namespace ingress-nginx

Stellen Sie als praktisches Beispiel eine Testanwendung auf Ihrem Kubernetes-Cluster bereit und machen Sie diese über eine Ingress-Ressource zugänglich. Dadurch veranschaulichen Sie die Fähigkeit von ExternalDNS, Einträge in der zuvor erstellten Zone zu generieren.

Terminal-Fenster
kubectl apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
name: example-app
---
apiVersion: v1
kind: ConfigMap
metadata:
name: example-app
namespace: example-app
data:
index.html: |
<!DOCTYPE html>
<html>
<head>
<title>ExampleApp</title>
</head>
<body>
<header>
<h1 id="Hello World">Hello World</h1>
</header>
</body>
</html>
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-app
namespace: example-app
spec:
replicas: 1
selector:
matchLabels:
app: example-app
template:
metadata:
labels:
app: example-app
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: config-volume
mountPath: /usr/share/nginx/html
volumes:
- name: config-volume
configMap:
name: example-app
---
apiVersion: v1
kind: Service
metadata:
name: example-app
namespace: example-app
spec:
selector:
app: example-app
ports:
- protocol: TCP
port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: example-app
annotations:
ingress.kubernetes.io/rewrite-target: /
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: "app.example.runs.onstackit.cloud"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-app
port:
number: 80
EOF

Um die Funktionalität der Anwendung zu überprüfen:

Terminal-Fenster
kubectl get pods -n example-app

Sie erhalten die folgende Ausgabe:

NAME READY STATUS RESTARTS AGE
example-app-588974765d-r6shs 1/1 Running 0 23s

Lassen Sie einen kurzen Zeitraum verstreichen und bestätigen Sie dann, dass ExternalDNS die Einträge für unsere Beispielanwendung generiert hat:

Terminal-Fenster
curl --location --globoff "https://dns.api.stackit.cloud/v1/projects/$PROJECTID/zones/$ZONEID/rrsets" \
--header "Authorization: Bearer $AUTHENTICATION_TOKEN" | \
jq '.rrSets[] | {name, type, records}'
{
"name": "a-app.example.runs.onstackit.cloud.",
"type": "TXT",
"records": [
{
"content": "\"heritage=external-dns,external-dns/owner=default,external-dns/resource=ingress/example-app/app-ingress\"",
"id": "a316420b-4c0b-4aad-b089-5af5a361f0f9"
}
]
}
{
"name": "app.example.runs.onstackit.cloud.",
"type": "TXT",
"records": [
{
"content": "\"heritage=external-dns,external-dns/owner=default,external-dns/resource=ingress/example-app/app-ingress\"",
"id": "cacd591c-d62c-4a61-99e4-7a70ca642cf6"
}
]
}
{
"name": "app.example.runs.onstackit.cloud.",
"type": "A",
"records": [
{
"content": "45.129.45.243",
"id": "06a9c311-1a13-4835-a694-589897db5008"
}
]
}
{
"name": "example.runs.onstackit.cloud.",
"type": "SOA",
"records": [
{
"content": "ns1.stackit.cloud hostmaster.stackit.cloud. 2023090500 3600 600 1209600 60",
"id": "0268092d-63cd-45b2-ad39-364f026cb0a2"
}
]
}
{
"name": "example.runs.onstackit.cloud.",
"type": "NS",
"records": [
{
"content": "ns1.stackit.cloud.",
"id": "49ea254f-c8c5-4db0-952a-ee9a47bd8f04"
},
{
"content": "ns2.stackit.cloud.",
"id": "d8a5c03d-3048-49e1-a2ff-2534307be56e"
}
]
}

Testen Sie den Zugriff auf Ihre Website mit curl:

Terminal-Fenster
curl http://app.example.runs.onstackit.cloud
<!DOCTYPE html>
<html>
<head>
<title>ExampleApp</title>
</head>
<body>
<header>
<h1 id="Hello World">Hello World</h1>
</header>
</body>
</html>

Wie Sie vielleicht bemerkt haben, ist das aktuelle Setup nicht abgesichert. Für eine erhöhte Sicherheit sollten Sie die Bereitstellung des Cert-Managers und dessen Integration mit STACKIT DNS in Betracht ziehen. Eine Anleitung zur Durchführung finden Sie unter STACKIT DNS für DNS01 verwenden, um als DNS01-ACME-Issuer mit Cert-Manager zu fungieren.