Skip to content

ExternalDNS

ExternalDNS keeps records in your Frostmoln Managed DNS zones in sync with Kubernetes Ingress, Service, and Gateway API resources — from any Kubernetes cluster. When you expose a workload, its hostname's records are created, updated, and cleaned up automatically; you never edit DNS by hand.

Frostmoln ships a webhook provider that runs as a sidecar in the external-dns pod and translates ExternalDNS's plans into calls against the Frostmoln DNS API.

Pilot — DNS access is granted per tenant

ExternalDNS builds on Managed DNS, which is a pilot feature enabled per tenant. If the dns entitlement isn't on your tenant yet, see Managed DNS for how to request access.

Prerequisites

  • A Frostmoln DNS zone for the domain you want records in — see Managed DNS to create one.
  • The dns entitlement on your tenant (the same pilot access as Managed DNS).
  • A tenant API key (fmk_...) held by a user in that tenant. Create one in the portal under Settings → API Keys, or with the CLI — see fm account api-key.

How it works

  • Records — the webhook lists your tenant's zones and recordsets and returns them to ExternalDNS as endpoints (one recordset = one (name, type) with all its values).
  • Apply — creates and updates go through the DNS API's idempotent, collection-level upsert; deletes resolve the recordset by its stable ID.
  • Domain filter — by default the webhook fetches your tenant's zone names at startup and manages only those. Create a new zone → restart the pod so it is picked up (or set DOMAIN_FILTER explicitly).
  • Ownership — use ExternalDNS's default TXT registry (--registry=txt with --txt-owner-id=<cluster-id>) so a record is only ever changed by the cluster that created it. TXT recordsets round-trip through the API unmodified.

Install

On Frostmoln Managed Kubernetes, ExternalDNS with the Frostmoln webhook is a one-click cluster add-on. Select external-dns when you create the cluster (portal → Add-ons, or addons: ["external-dns"] on the API / Terraform resource) and the platform installs everything for you — the external-dns namespace, RBAC, the ExternalDNS controller, and the Frostmoln webhook sidecar, with --txt-owner-id already set to your cluster's id. The webhook image is provided by the platform and preloaded on the cluster nodes; you never build, pull, or reference it yourself.

The only step left to you is the API-key Secret (next section): until it exists the webhook container waits in CreateContainerConfigError. Create your DNS zone first (or restart the external-dns Deployment after creating it), and make sure your tenant holds the dns entitlement — without it the webhook logs 403 FEATURE_NOT_ENABLED.

Selected at cluster creation

The add-on is chosen when the cluster is created. To add ExternalDNS to an existing cluster, recreate it with the add-on selected — day-2 addition is planned. ExternalDNS with the Frostmoln webhook is currently available on Frostmoln Managed Kubernetes only.

Managed Kubernetes: enable the addon

On Frostmoln Managed Kubernetes the easiest path is the external-dns cluster addon: select it when creating the cluster (portal → Add-ons, or addons: ["external-dns"] on the API / Terraform resource) and the platform installs everything in this guide — the external-dns namespace, RBAC, and the Deployment, with --txt-owner-id already set to the cluster's id.

The only manual step is the API-key Secret (next section): until it exists the webhook container waits in CreateContainerConfigError. Create your DNS zone first (or restart the external-dns Deployment after creating it), and make sure your tenant holds the dns entitlement — without it the webhook logs 403 FEATURE_NOT_ENABLED.

Cluster create only

The addon can only be selected when the cluster is created. On an existing cluster, apply the manifest under Deploy instead.

Store the API key

Put the tenant API key in a Secret the webhook can read:

bash
kubectl create namespace external-dns   # skip if the add-on already created it
kubectl create secret generic frostmoln-dns \
  --namespace external-dns \
  --from-literal=api-key=fmk_...

Deploy

On Managed Kubernetes with the addon enabled you can skip this section — only the Secret above is needed.

The manifest below runs external-dns with the Frostmoln webhook as a sidecar, plus the ServiceAccount and RBAC external-dns needs. Replace <cluster-id> with a value unique to this cluster — it stamps the TXT ownership records so several clusters can safely share a zone. The pod declares the full restricted Pod Security contract (Managed Kubernetes enforces it cluster-wide; the explicit runAsUser matters because the upstream external-dns image runs as root).

On Frostmoln Managed Kubernetes the webhook image is preloaded on your nodes, and it lives in a Frostmoln-internal registry your cluster cannot reach — so <webhook-image> is not yours to choose. It must be a ref that is already on the nodes; any other value leaves the sidecar in ImagePullBackOff. Read it off the cluster and paste it in verbatim:

bash
kubectl get nodes -o json \
  | jq -r '.items[].status.images[]?.names[]?' \
  | grep 'external-dns-frostmoln-webhook:' | sort -u

More than one ref means your node pool spans two node-image generations: use the one present on every node, or roll the pool so they match. No output at all means the image is not listed on a busy node — try one node at a time (kubectl get node <name> -o json), or ask support.

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns
  namespace: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: external-dns
rules:
  - apiGroups: ['']
    resources: ['services', 'endpoints', 'pods', 'nodes']
    verbs: ['get', 'watch', 'list']
  - apiGroups: ['discovery.k8s.io']
    resources: ['endpointslices']
    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
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: external-dns
subjects:
  - kind: ServiceAccount
    name: external-dns
    namespace: external-dns
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: external-dns
  namespace: external-dns
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: external-dns
  template:
    metadata:
      labels:
        app: external-dns
    spec:
      serviceAccountName: external-dns
      securityContext:
        runAsNonRoot: true
        runAsUser: 65532
        runAsGroup: 65532
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: external-dns
          image: registry.k8s.io/external-dns/external-dns:v0.21.0
          args:
            - --source=ingress
            - --source=service
            - --provider=webhook
            - --registry=txt
            - --txt-owner-id=<cluster-id>
          securityContext:
            readOnlyRootFilesystem: true
            allowPrivilegeEscalation: false
            capabilities:
              drop: [ALL]
        - name: frostmoln-webhook
          image: <webhook-image>
          imagePullPolicy: IfNotPresent
          env:
            - name: FROSTMOLN_API_KEY
              valueFrom:
                secretKeyRef:
                  name: frostmoln-dns
                  key: api-key
          ports:
            - containerPort: 8080
              name: http
          livenessProbe:
            httpGet:
              path: /healthz
              port: http
          readinessProbe:
            httpGet:
              path: /healthz
              port: http
          securityContext:
            readOnlyRootFilesystem: true
            allowPrivilegeEscalation: false
            capabilities:
              drop: [ALL]

Apply it:

bash
kubectl apply -f external-dns.yaml

The webhook API listens on the pod loopback only — it is unauthenticated by contract and refuses any other bind address — and FROSTMOLN_API_URL must be https. Both are enforced at startup.

Configuration

The add-on sets sensible defaults. To tune the webhook sidecar, edit the external-dns Deployment and set these environment variables:

VariableDefaultDescription
FROSTMOLN_API_KEY— (required)Tenant API key (fmk_...); the tenant must hold the dns entitlement
FROSTMOLN_API_KEY_FILERead the key from a mounted file instead, to keep it out of the pod env
FROSTMOLN_API_URLhttps://api.frostmoln.cloud/apiCustomer API base URL (must be https)
FROSTMOLN_TENANT_IDresolved from the keyTenant override
DOMAIN_FILTERyour zone namesComma-separated domain filter override
ZONE_CACHE_TTL1mHow long the zone catalog is cached
DRY_RUNfalseLog the changes ExternalDNS would make without applying them

Notes and caveats

  • Public zones reject private and loopback targets. An A/AAAA record in a public zone must point at a routable address — a load balancer or a public IP, not a cluster-internal IP. For internal names, use a private (vpcId) zone.
  • Delegate the zone before you expect public resolution. A zone answers on the internet only once your domain is delegated to its name servers — see Managed DNS → Delegate your domain.
  • New zones need a webhook restart. The domain filter is fetched once at startup, so restart the external-dns pod after creating a zone you want ExternalDNS to manage.
  • TXT ownership is supported natively — keep --registry=txt with a stable --txt-owner-id so multiple clusters can safely share a zone.