discovery

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: GPL-3.0 Imports: 13 Imported by: 0

README

discovery

Name

discovery - DNS-based service discovery with pluggable sources.

Description

discovery provides DNS-based service discovery for CoreDNS. It maintains an in-memory store of services and their instances, populated by pluggable sources (Podman, QEMU, etc.). The plugin serves A and SRV DNS records for the configured zone, allowing clients (such as Caddy) to discover service instances via standard DNS queries.

The plugin follows the List + Watch pattern (similar to the kubernetes plugin): sources perform an initial list of available services, then watch for changes in real-time, keeping the in-memory store synchronized.

Status: Core framework (store, handler, source interface, setup) is implemented and tested. The Podman source is the next planned implementation. QEMU source is planned after that.

Syntax

discovery ZONE {
    ttl SECONDS
    fallthrough
    source NAME {
        # source-specific configuration
    }
}
  • ttl — TTL for DNS responses in seconds. Default: 30. Maximum: 3600.
  • fallthrough — If a query matches the zone but no record is found, pass the request to the next plugin instead of returning NXDOMAIN.
  • source — Configures a discovery source. Can be specified multiple times. Each source maintains its own discovery loop and feeds into the shared store.

DNS Convention

The plugin serves the following record types for the configured zone:

A Records
<service>.<namespace>.<zone>              → IPs of all instances
<instance-id>.<service>.<namespace>.<zone> → IP of a specific instance
SRV Records (RFC 2782)
_<service>._<proto>.<namespace>.<zone>    → priority weight port <instance-id>.<service>.<namespace>.<zone>
Example
open-webui.default.svc.desaules.in.                    A      10.88.0.5
_open-webui._tcp.default.svc.desaules.in.              SRV    10 100 8080 a1b2c3.open-webui.default.svc.desaules.in.
a1b2c3.open-webui.default.svc.desaules.in.             A      10.88.0.5

Sources

podman

Discovers Podman containers via the Podman REST API (Unix socket). Uses the List + Watch pattern: initial container list followed by real-time event streaming.

Container labels:

Label Required Description
discovery.enable Yes Must be true to enable discovery
discovery.service Yes Service name
discovery.port Yes Service port
discovery.protocol No tcp (default) or udp
discovery.namespace No Namespace (default: default)

Configuration:

source podman {
    socket /run/podman/podman.sock     # Podman API socket
    host_ip 10.10.10.1                  # IP for host-networked containers
    refresh 30s                         # Resync interval (fallback)
    label discovery.enable=true         # Container label filter
}
qemu (planned)

Discovers QEMU virtual machines via QMP (QEMU Machine Protocol). Uses guest agent for IP discovery.

Examples

svc.desaules.in {
    discovery svc.desaules.in {
        ttl 30
        source podman {
            socket /run/podman/podman.sock
            host_ip 10.10.10.1
        }
    }
    cache 30
    loadbalance
    errors
    forward . /etc/resolv.conf
}

Adding New Sources

A source is a Go type implementing the Source interface:

type Source interface {
    Name() string
    ParseConfig(c *caddy.Controller) error
    Run(ctx context.Context, store *Store) error
}

To register a new source, create a file source_<name>.go with:

func init() {
    RegisterSource("<name>", func() Source { return &MySource{} })
}

The source's Run method should populate the store via store.Register and store.Deregister, and block until ctx is cancelled.

Integration with CoreDNS

This plugin is an external CoreDNS plugin. It is compiled into CoreDNS via a separate integration repo that uses Method 2 (external Go program importing CoreDNS as a library). No fork of CoreDNS is required.

Pre-built Docker image: ghcr.io/tdesaules/coredns:latest

See Also

Documentation

Overview

Package discovery implements a CoreDNS plugin for DNS-based service discovery.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RegisterSource

func RegisterSource(name string, factory SourceFactory)

RegisterSource registers a source factory. Called from init() in each source_*.go file.

func RegisteredSources

func RegisteredSources() []string

RegisteredSources returns the names of all registered sources.

Types

type Handler

type Handler struct {
	Next  plugin.Handler
	Store *Store
	Zone  string
	TTL   uint32
	Fall  fall.F
}

Handler implements plugin.Handler and serves DNS records from the in-memory store.

func (*Handler) Name

func (h *Handler) Name() string

Name implements plugin.Handler.

func (*Handler) ServeDNS

func (h *Handler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error)

ServeDNS implements plugin.Handler. It parses the query name, looks up the store, and returns A or SRV records. If no match is found, it falls through to the next plugin.

type Instance

type Instance struct {
	ID       string `json:"id"`
	Address  string `json:"address"`
	Port     int    `json:"port"`
	Protocol string `json:"protocol"`
	Priority int    `json:"priority"`
	Weight   int    `json:"weight"`
	Source   string `json:"source"`
}

Instance represents a single instance of a discovered service.

type Service

type Service struct {
	Name      string               `json:"name"`
	Namespace string               `json:"namespace"`
	Instances map[string]*Instance `json:"instances"`
}

Service represents a service with its instances.

type Source

type Source interface {
	// Name returns the source name (e.g., "podman", "qemu").
	Name() string

	// ParseConfig parses source-specific configuration from the Corefile.
	// The controller cursor is positioned after the "{" token.
	// ParseConfig should consume tokens using c.Next() until it
	// encounters the closing "}" of the source sub-block.
	ParseConfig(c *caddy.Controller) error

	// Run starts the discovery loop. It populates the store and keeps
	// it updated. Blocks until ctx is cancelled.
	Run(ctx context.Context, store *Store) error
}

Source is a discovery source that populates the store. Each source (podman, qemu, etc.) implements this interface and registers itself via RegisterSource in its init() function.

type SourceFactory

type SourceFactory func() Source

SourceFactory creates a new Source instance.

func GetSource

func GetSource(name string) (SourceFactory, bool)

GetSource returns a source factory by name.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store is a thread-safe in-memory store for discovered services.

func NewStore

func NewStore() *Store

NewStore creates a new Store.

func (*Store) Deregister

func (s *Store) Deregister(svcName, namespace, instanceID string)

Deregister removes an instance from a service.

func (*Store) DeregisterBySource

func (s *Store) DeregisterBySource(source string)

DeregisterBySource removes all instances discovered by a specific source.

func (*Store) GetInstance

func (s *Store) GetInstance(svcName, namespace, instanceID string) (*Instance, bool)

GetInstance returns a specific instance of a service.

func (*Store) GetInstances

func (s *Store) GetInstances(svcName, namespace string) []*Instance

GetInstances returns all instances of a service.

func (*Store) GetService

func (s *Store) GetService(svcName, namespace string) (*Service, bool)

GetService returns a service and all its instances.

func (*Store) ListServices

func (s *Store) ListServices() []*Service

ListServices returns all services in the store.

func (*Store) Register

func (s *Store) Register(svcName, namespace string, inst *Instance) error

Register adds or updates an instance of a service in the store.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL