Skip to content

math280h/greydns

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

25 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

GreyDNS 🌐

A lightweight Kubernetes controller for automated DNS management through service annotations

GreyDNS enables development teams to manage their DNS records directly through Kubernetes service annotations, working seamlessly with centrally managed ingress patterns.

Disclaimer: GreyDNS is not meant to replace ExternalDNS for a lot of use cases. It's designed to be a solution to a specific problem that Platform Engineers may run into when trying to empower development teams to manage their own DNS records while maintaining a central point of control for ingress.

Go Version License

flowchart LR
    dev([Developer]) -->|kubectl apply| svc["Service or Ingress<br/>greydns.io/* annotations"]
    subgraph k8s[Kubernetes cluster]
        svc -->|informer event| ctrl[greydns controller]
        cm[(ConfigMap<br/>greydns-config)] --> ctrl
        sec[(Secret<br/>greydns-secret)] --> ctrl
    end
    ctrl -->|Create / Update / Delete| prov[Active DNS provider<br/>e.g. Cloudflare]
    prov --> dns[(Public DNS record<br/>owned by greydns)]
Loading

πŸš€ Features

  • Annotation-Driven: Create and manage DNS records using simple Kubernetes Service or Ingress annotations
  • Ingress-Aware: Automatically creates one record per spec.rules[].host on annotated Ingresses
  • Central Ingress: Works with centrally managed ingress controllers
  • Real-time Updates: Automatically syncs DNS records when annotations change
  • Lightweight: Minimal resource footprint with efficient caching
  • Observable: Prometheus metrics exposed on /metrics alongside the health probes on port 8080
  • Hot-reloadable Config: Edit greydns-config and the controller picks up generic keys (TTL, record-type, ingress-destination, allowed-overrides) without a pod restart

πŸ“¦ Supported DNS Providers

GreyDNS uses a pluggable provider system. Exactly one provider is active at a time, selected via the provider key in the ConfigMap.

  • CloudFlare

Coming Soon

  • Route 53
  • Google Cloud DNS Support
  • Azure DNS Support

See docs/adding-a-provider.md for how to add a new provider in a single package.

πŸ“‹ Prerequisites

  • Kubernetes cluster (1.19+)
  • kubectl configured to access your cluster

CloudFlare

  • CloudFlare API token with the following permissions:
    • Zone: Read
    • DNS: Edit

πŸ› οΈ Installation

  1. Deploy GreyDNS using kubectl:

    kubectl apply -f https://raw.githubusercontent.com/math280h/greydns/refs/heads/main/deployment.yaml
  2. Create the required ConfigMap. Generic keys live at the top level; provider-specific keys are namespaced by the provider name (e.g. cloudflare.proxy-enabled), so switching providers is purely a config change:

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: greydns-config
      namespace: default
    data:
      provider: "cloudflare"
      record-type: "A"
      record-ttl: "60"
      cache-refresh-seconds: "60"
      ingress-destination: "YOUR_INGRESS_IP"
      # Cloudflare-specific
      cloudflare.proxy-enabled: "true"
  3. Create the provider secret. The secret is always named greydns-secret; keys are provider-specific:

    # Cloudflare
    kubectl create secret generic greydns-secret \
      --from-literal=cloudflare-token=YOUR_API_TOKEN

πŸ“ Usage

Add annotations to your Kubernetes service:

apiVersion: v1
kind: Service
metadata:
  name: my-service
  annotations:
    greydns.io/dns: "true"
    greydns.io/domain: "api.example.com,api-v2.example.com"
    greydns.io/zone: "example.com"
    # Optional per-Service overrides:
    greydns.io/ttl: "300"
    greydns.io/record-type: "AAAA"
    greydns.io/cloudflare-proxied: "true"
spec:
  # ... rest of service spec

Service annotations

Annotation Required Description
greydns.io/dns Yes Must be "true" for greydns to manage this Service.
greydns.io/zone Yes Managed DNS zone name (must match a provider-visible zone).
greydns.io/domain Yes Fully-qualified record name(s). Comma-separated list is accepted; one DNS record is created per entry, all in the same zone.
greydns.io/ttl No Positive integer seconds. Overrides the controller-wide record-ttl.
greydns.io/record-type No One of the provider's supported record types. Overrides record-type.
greydns.io/<provider>-<key> No Provider-scoped override, e.g. greydns.io/cloudflare-proxied.

Invalid override values fall back to the controller default and surface as an InvalidAnnotation event on the Service. Per-Service overrides can be restricted cluster-wide via the allowed-overrides ConfigMap key; overrides not on the allowlist are ignored with the same event.

Ingress support

greydns watches networking.k8s.io/v1/Ingress objects alongside Services. Adding the same greydns.io/* annotations to an Ingress makes greydns create one record per unique spec.rules[].host entry.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  annotations:
    greydns.io/dns: "true"
    greydns.io/zone: "example.com"
    # Optional per-resource overrides:
    greydns.io/ttl: "300"
    greydns.io/cloudflare-proxied: "true"
spec:
  rules:
    - host: app.example.com
    - host: api.example.com

greydns.io/domain is ignored on Ingresses; the host list comes from spec.rules. An Ingress and a Service sharing namespace/name own disjoint records (the owner reference includes the Kubernetes kind), and greydns emits DuplicateDomain when an Ingress host is already owned by another resource.

Leader election

Every greydns pod participates in a coordination.k8s.io/Lease named greydns-leader in the pod's namespace; only the lease holder runs the reconciler and refresh loop, so replicas > 1 is safe. Followers stay alive on the same /healthz and /readyz probes and take over within LeaseDuration (15s) if the leader disappears. The lease is released cleanly on graceful shutdown so rolling updates hand off in under a second.

Duplicate Records

GreyDNS will automatically deduplicate records based on the namespace and service name. If you create two records at the same time it's first come first serve.

GreyDNS will create an event on the service if it detects a record that is already owned by another service.

Duplicate Record

Admission-time annotation validation (optional)

admission-policy.yaml ships a ValidatingAdmissionPolicy that rejects malformed values on the core greydns.io/* annotations (dns, zone, domain, ttl, record-type) at kubectl apply time, so typos surface immediately instead of landing as InvalidAnnotation events at reconcile. Provider-scoped keys like greydns.io/cloudflare-proxied are passed through untouched. It's optional and independent of the controller; apply it alongside the deployment if you want upfront feedback:

kubectl apply -f admission-policy.yaml

Requires Kubernetes 1.30+ (GA admissionregistration.k8s.io/v1). The policy rejects:

  • greydns.io/dns not in "true" / "false"
  • greydns.io/ttl not a positive integer
  • greydns.io/record-type not in A / AAAA / CNAME
  • greydns.io/zone that isn't a valid DNS name
  • greydns.io/domain with any CSV entry that isn't a valid DNS name

Objects without any greydns.io/* annotation skip the policy entirely via a match condition, so the overhead for unrelated Services and Ingresses is a single CEL check.

πŸ” Configuration

Generic keys

Universal DNS concepts live at the top level so they're not duplicated across providers.

Config Key Description Required Hot-reloadable
provider Active provider name (today: cloudflare) Yes No (restart)
record-type Default record type (A, AAAA, CNAME, ...). Overridable per Service via greydns.io/record-type. Yes Yes
record-ttl Default TTL in seconds. Overridable per Service via greydns.io/ttl. Yes Yes
cache-refresh-seconds Cache refresh interval Yes No (restart)
ingress-destination Ingress controller IP address or hostname Yes Yes
allowed-overrides Allowlist of per-Service override suffixes (CSV). Missing key or * allows every override; explicit empty string denies every override; otherwise only the listed suffixes (e.g. ttl,record-type,cloudflare-proxied) are honoured. Enforced in-process, so Service editors can't bypass it. No (default allow all) Yes

Editing greydns-config triggers a ConfigMap informer event; the controller re-parses, swaps its in-memory snapshot, and enqueues every watched Service and Ingress for re-reconcile so existing records converge on the new values within seconds (no pod restart, no per-resource bounce). Invalid values are logged and rejected; the previous snapshot stays in force until the config is fixed. Provider credentials and the provider key still require a pod restart.

Cloudflare keys (provider: cloudflare)

Config Key Description Required
cloudflare.proxy-enabled Default proxy setting (true/false). Overridable per Service via greydns.io/cloudflare-proxied. No (default false)

Secret keys: cloudflare-token.

πŸ“Š Metrics

Prometheus metrics are served on GET /metrics on the same port (8080) as the health probes.

Metric Type Labels Description
greydns_reconciles_total counter outcome Reconciliations grouped by outcome: created, updated, noop, duplicate_domain, invalid_annotation, error.
greydns_provider_calls_total counter provider, operation, outcome Provider API calls; operation is one of list_zones, list_owned, create, update, delete; outcome is success or error.
greydns_provider_call_duration_seconds histogram provider, operation Provider API call latency.
greydns_cache_records gauge - Number of DNS records currently in the controller cache.
greydns_retry_queue_depth gauge - Number of failed deletes pending retry.
greydns_cache_refresh_duration_seconds histogram - Background cache-refresh latency.
greydns_cache_refresh_last_success_timestamp_seconds gauge - Unix timestamp of the last successful cache refresh.
greydns_workqueue_depth gauge - Items currently waiting in the reconcile workqueue.
greydns_workqueue_adds_total counter - Items added to the workqueue since startup.
greydns_workqueue_retries_total counter - Reconciles that failed and were requeued with backoff.

πŸ†˜ Troubleshooting

Common failure modes (RBAC errors, DuplicateDomain, zone not managed, stuck deletes, leader lease contention) are catalogued in docs/troubleshooting.md.

πŸ€” Why Not ExternalDNS?

While ExternalDNS is a powerful tool for DNS automation in Kubernetes, GreyDNS takes a different approach:

  • Seamlessly integrates with centrally managed ingress controllers
  • No CRDs required - uses native Kubernetes annotations
  • Reduced complexity compared to ExternalDNS

πŸ’‘ Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.


Named after Greyson, my rubber duck (who happens to be a cat) 🐱

About

GreyDNS enables development teams to manage their DNS records directly through Kubernetes service annotations, working seamlessly with centrally managed ingress patterns.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors