kubernetes

package
v1.8.2 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: MIT Imports: 25 Imported by: 0

README

Kubernetes DNS Middleware for SDNS

Kubernetes DNS middleware for SDNS. Resolves cluster-domain names (services, pods, SRV, PTR) directly from a sharded in-memory registry populated by Kubernetes informers. Each affected name's dns.RR slices are pre-built on every mutation, so ResolveQuery is a single sharded map lookup with zero allocations.

This middleware does not cache DNS responses, and the chain order in gen.go places kubernetes before the cache middleware so the cache layer doesn't see these answers either. That is by design: registry lookups are already O(1), and only the dns.Msg setup + wire packing in ServeDNS cost allocations on the hot path. If you are debugging stale answers, the source of truth is the registry — the upstream cache is not involved.

Features

DNS resolution
  1. Service DNS

    • service.namespace.svc.cluster.local → ClusterIP
    • Headless services return all ready endpoint IPs
    • ExternalName services return CNAME records
    • Full IPv4 / IPv6 / dual-stack
  2. Pod DNS

    • pod-ip.namespace.pod.cluster.local → Pod IP
    • IPv4: 10-244-1-1.namespace.pod.cluster.local
    • IPv6: 2001-db8--1.namespace.pod.cluster.local
    • StatefulSet pods: pod-name.service.namespace.svc.cluster.local
  3. SRV records

    • _port._protocol.service.namespace.svc.cluster.local
    • TCP / UDP / SCTP
  4. PTR records (reverse DNS)

    • IPv4: 1.0.96.10.in-addr.arpa → service / pod
    • IPv6: …ip6.arpa → service / pod
    • O(1) reverse-IP index for services
  5. Kubernetes API integration

    • Watches Services, EndpointSlices, and Pods
    • In-cluster config or external kubeconfig
    • Demo data fallback for local testing
Registry

The registry is 256-way sharded:

  • serviceShards keyed by namespace/name
  • podShards keyed by IP
  • endpointShards keyed by namespace/service
  • podByName keyed by namespace/name (StatefulSet lookups, public accessor)
  • serviceByIP keyed by ClusterIP string (PTR fast path)

Reads and writes against different shards never contend. Per-shard RWMutexes serialise reads against any concurrent write to the same shard.

File structure

  • kubernetes.go — middleware entry: New, ServeDNS, Stats, demo seed
  • registry.go — sharded Registry: query resolution + accessors
  • client.go — Kubernetes API client (informers for Services, EndpointSlices, Pods)
  • types.goService, Pod, Endpoint, Port
  • ipv6_utils.go — IPv6 parsing helpers
  • constants.go — TTLs, network octets, etc.
  • test_helpers.go — mock ResponseWriter for tests

Configuration

[kubernetes]
enabled = true
cluster_domain = "cluster.local"  # default

# kubeconfig = "/path/to/kubeconfig"  # optional, falls back to in-cluster
# demo = true                         # populate synthetic data for local testing

[kubernetes.ttl]
service = 30
pod     = 30
srv     = 30
ptr     = 30

The legacy killer_mode flag is accepted for backward compatibility but has no effect — the middleware always uses the sharded registry. Remove it from your config; SDNS logs a deprecation warning if it is set to true.

Query examples

# Service
dig @localhost service-name.namespace.svc.cluster.local

# Pod by IP
dig @localhost 10-244-1-1.namespace.pod.cluster.local

# SRV
dig @localhost _http._tcp.service-name.namespace.svc.cluster.local SRV

# Reverse
dig @localhost -x 10.96.0.1

# IPv6 service
dig @localhost service-name.namespace.svc.cluster.local AAAA

Limitations

  • Node DNS queries not implemented (rarely used in practice).
  • Search domains must be configured in SDNS, not extracted from pods.
  • EndpointSlices only — legacy Endpoints are not consumed.

RBAC

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: sdns-kubernetes-dns
rules:
- apiGroups: [""]
  resources: ["services", "pods"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["discovery.k8s.io"]
  resources: ["endpointslices"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: sdns-kubernetes-dns
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: sdns-kubernetes-dns
subjects:
- kind: ServiceAccount
  name: sdns
  namespace: sdns-system

Stats

Kubernetes.Stats() returns:

  • queries, answered, errors, write_errors
  • registry: per-registry counters (services, pods, endpoints, endpoint_sets, queries, hits, hit_rate_pct, shards)

Troubleshooting

No Kubernetes connection. Verify kubeconfig path, in-cluster pod identity, and RBAC permissions for Services / Pods / EndpointSlices.

Queries not resolving. Ensure cluster_domain matches the cluster's actual domain (kubectl get cm -n kube-system coredns -o yaml shows the answer if you're migrating from CoreDNS). Check that informers have synced — the middleware passes through to the next handler until at least one informer has populated the registry.

Cache behaviour. This middleware does not cache responses, and the cache middleware sits below it in the chain (see gen.go) so it never sees Kubernetes answers either. There is no DNS-message cache in this path. Stale answers can therefore only come from stale informer state — check Stats()["registry"] and the Kubernetes API directly, not the cache middleware.

Documentation

Overview

Package kubernetes - Kubernetes API client

Package kubernetes - Common constants for Kubernetes middleware

Package kubernetes provides a Kubernetes DNS middleware for SDNS. It answers cluster-domain queries (services, pods, SRV, PTR) from a sharded in-memory registry populated by Kubernetes informers. ResolveQuery is a single sharded map lookup plus a slice-header copy — zero allocations per query.

Package kubernetes - DNS types

Index

Constants

View Source
const (
	// Cache sizes and limits
	CacheMaxEntries      = 10000 // Maximum number of entries in zero-alloc cache
	CacheIndexSize       = 16384 // Must be power of 2 for fast modulo
	CacheLockStripes     = 256   // Number of lock stripes for sharding
	CacheMaxWireSize     = 4096  // Maximum wire format DNS message size (EDNS0 support)
	CacheLinearProbeSize = 16    // Maximum linear probe attempts for collision handling

	// Cache cleanup and expiry
	CacheCleanupInterval = 10 * time.Second
	CacheDefaultTTL      = 30 // Default TTL in seconds
)

Cache configuration constants

View Source
const (
	RegistryServiceShards = 256 // Number of shards for services
	RegistryPodShards     = 256 // Number of shards for pods
)

Sharding constants for registry

View Source
const (
	PredictorBufferSize     = 1024 // Size of circular buffer for recent queries
	PredictorMaxPredictions = 10   // Maximum predictions in pool
	PredictorMaxResults     = 5    // Maximum predictions to return
	PredictorThresholdDiv   = 10   // Threshold divisor (>10% probability)
	PredictorTrainInterval  = 30 * time.Second
)

Predictor constants

View Source
const (
	IPv4AddressSize = 4  // Size of IPv4 address in bytes
	IPv6AddressSize = 16 // Size of IPv6 address in bytes
)

Network constants

View Source
const (
	DNSTypeA    = 1  // A record type
	DNSTypeAAAA = 28 // AAAA record type
)

DNS query type constants (for ML predictor)

View Source
const (
	FNVOffsetBasis = 14695981039346656037 // FNV-1a offset basis
	FNVPrime       = 1099511628211        // FNV-1a prime
	HashMultiplier = 31                   // Simple hash multiplier
)

Hash constants

View Source
const (
	SRVPriority = 0   // Default SRV priority
	SRVWeight   = 100 // Default SRV weight for single entry
	SRVWeight1  = 1   // Alternative SRV weight
)

SRV record constants

View Source
const (
	IPv4LastOctetIndex = 3  // Index of last octet in IPv4 address
	IPv6LastByteIndex  = 15 // Index of last byte in IPv6 address
)

IP byte positions

View Source
const (
	WireMessageIDOffset = 0 // Offset of message ID in DNS wire format
	WireMessageIDSize   = 2 // Size of message ID in bytes
)

Wire format constants

View Source
const (
	BenchmarkServiceStart = 1      // Starting index for benchmark services
	NetworkOctet10        = 10     // First octet for test IPs (10.x.x.x)
	NetworkOctet96        = 96     // Second octet for test IPs (10.96.x.x)
	NetworkOctet244       = 244    // Third octet for test pod IPs (10.244.x.x)
	IPv6TestPrefix        = 0xfe80 // IPv6 test prefix (fe80::)
)

Benchmark and test constants

View Source
const (
	PortHTTPS = 443 // HTTPS port
	PortDNS   = 53  // DNS port
)

Port numbers for test services

View Source
const (
	DefaultServiceTTL = uint32(30)
	DefaultPodTTL     = uint32(30)
	DefaultSRVTTL     = uint32(30)
	DefaultPTRTTL     = uint32(30)
)

DNS TTL values (configurable)

View Source
const (
	ClientStopTimeout = 5 * time.Second // Timeout for client stop operation
)

Client timeout constants

View Source
const (
	DemoServiceCount = 10 // Number of demo services to create
)

Service population constants (for demo/test data)

View Source
const (
	PercentageMultiplier = 100
)

Registry statistics percentage calculation

View Source
const (
	StatsLogInterval = 30 * time.Second // Interval for logging statistics
)

Performance monitoring constants

Variables

This section is empty.

Functions

func FormatPodIP

func FormatPodIP(ip net.IP) string

FormatPodIP formats an IP for pod DNS name IPv4: 10.244.1.1 -> 10-244-1-1 IPv6: 2001:db8::1 -> 2001-db8--1

func FormatReverseIP

func FormatReverseIP(ip net.IP) string

FormatReverseIP formats an IP for reverse DNS IPv4: 10.96.0.1 -> 1.0.96.10.in-addr.arpa IPv6: 2001:db8::1 -> 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa

func ParsePodIP

func ParsePodIP(podPart string) net.IP

ParsePodIP parses both IPv4 and IPv6 pod query formats IPv4: 10-244-1-1.namespace.pod.cluster.local IPv6: 2001-db8--1.namespace.pod.cluster.local or

2001-0db8-0000-0000-0000-0000-0000-0001.namespace.pod.cluster.local

func ParseReverseIP

func ParseReverseIP(labels []string) (net.IP, bool)

ParseReverseIP parses both IPv4 and IPv6 reverse queries IPv4: 1.0.96.10.in-addr.arpa -> 10.96.0.1 IPv6: 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa

Types

type Client

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

Client connects to the Kubernetes API.

func NewClient

func NewClient(kubeconfig string, registry *Registry) (*Client, error)

NewClient creates a new Kubernetes client wired to registry.

func (*Client) Rebuilds added in v1.6.6

func (c *Client) Rebuilds() uint64

Rebuilds returns the total number of per-service rebuilds.

func (*Client) Run

func (c *Client) Run(ctx context.Context) error

Run starts watching Kubernetes resources.

func (*Client) Stop

func (c *Client) Stop()

Stop stops the client and waits for cleanup.

func (*Client) Synced added in v1.6.4

func (c *Client) Synced() bool

Synced reports whether the informer caches have populated the registry at least once.

type Endpoint

type Endpoint struct {
	Addresses []string   // Dual-stack: [IPv4, IPv6] addresses
	Hostname  string     // Optional hostname
	Ready     bool       // Is endpoint ready
	TargetRef *ObjectRef // Reference to pod
}

Endpoint represents a service endpoint

func (*Endpoint) GetIPv4

func (e *Endpoint) GetIPv4() string

GetIPv4 returns IPv4 address from endpoint addresses

func (*Endpoint) GetIPv6

func (e *Endpoint) GetIPv6() string

GetIPv6 returns IPv6 address from endpoint addresses

type Kubernetes

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

Kubernetes is the DNS middleware that answers cluster-domain queries.

func New

func New(cfg *config.Config) *Kubernetes

New creates a new Kubernetes DNS middleware.

func (*Kubernetes) Name

func (k *Kubernetes) Name() string

Name returns the middleware name.

func (*Kubernetes) ServeDNS

func (k *Kubernetes) ServeDNS(ctx context.Context, ch *middleware.Chain)

ServeDNS handles DNS queries. The default stub has no registry and passes a wire-born request through with one check; a configured cluster needs the decoded question and materializes.

func (*Kubernetes) Stats

func (k *Kubernetes) Stats() map[string]any

Stats returns runtime statistics.

type ObjectRef

type ObjectRef struct {
	Kind      string
	Name      string
	Namespace string
}

ObjectRef references another object

type Pod

type Pod struct {
	Name      string
	Namespace string
	IPs       []string // Dual-stack: [IPv4, IPv6] addresses
	Hostname  string   // Pod hostname
	Subdomain string   // For StatefulSet DNS
}

Pod represents a Kubernetes pod

func (*Pod) GetIPv4

func (p *Pod) GetIPv4() string

GetIPv4 returns the IPv4 address from pod IPs

func (*Pod) GetIPv6

func (p *Pod) GetIPv6() string

GetIPv6 returns the IPv6 address from pod IPs

type Port

type Port struct {
	Name     string
	Port     int
	Protocol string // TCP, UDP
}

Port represents a service port

type Registry

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

Registry is a 256-way sharded store of services, pods, and endpoints with pre-built DNS answers per FQDN.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates an empty registry with all shards initialised.

func (*Registry) AddPod

func (r *Registry) AddPod(pod *Pod)

AddPod adds or updates a pod.

func (*Registry) AddService

func (r *Registry) AddService(svc *Service)

func (*Registry) ApplyEndpointSlice added in v1.6.6

func (r *Registry) ApplyEndpointSlice(svcName, namespace, sliceName string, eps []Endpoint) bool

ApplyEndpointSlice records sliceName's contribution to a headless service. The answer cache is NOT refreshed here — call MaterialiseHeadless once the burst of Apply/Remove calls has settled. Returns true when state actually changed.

When a real slice arrives for a service whose state holds a synthetic-slice contribution, the synthetic is retracted first; otherwise the two would double-count.

func (*Registry) DeletePod

func (r *Registry) DeletePod(name, namespace string)

DeletePod removes a pod from every shard it was indexed in.

func (*Registry) DeleteService

func (r *Registry) DeleteService(name, namespace string)

DeleteService removes a service and every piece of state derived from it. The endpoint shard is wiped unconditionally so that endpoints-before-Service ordering can't leave stale endpoints for a later AddService to pick up.

func (*Registry) GetEndpoints

func (r *Registry) GetEndpoints(service, namespace string) []Endpoint

func (*Registry) GetPodByIP

func (r *Registry) GetPodByIP(ip string) *Pod

func (*Registry) GetPodByName

func (r *Registry) GetPodByName(name, namespace string) *Pod

func (*Registry) GetService

func (r *Registry) GetService(name, namespace string) *Service

func (*Registry) GetServiceByIP

func (r *Registry) GetServiceByIP(ip []byte) *Service

GetServiceByIP returns the service whose ClusterIPs contain ip (IPv4 or IPv6 byte slice).

func (*Registry) MaterialiseHeadless added in v1.6.6

func (r *Registry) MaterialiseHeadless(svcName, namespace string)

MaterialiseHeadless rebuilds the answer cache from the headless state. Worker callers debounce so a flurry of Apply/Remove calls amortises to one materialise per debounce window.

func (*Registry) RemoveEndpointSlice added in v1.6.6

func (r *Registry) RemoveEndpointSlice(svcName, namespace, sliceName string) bool

RemoveEndpointSlice retracts sliceName's contribution. Returns true when state changed.

func (*Registry) ResolveQuery added in v1.6.6

func (r *Registry) ResolveQuery(qname string, qtype uint16) (answer, extra []dns.RR, ok bool)

ResolveQuery resolves a DNS query against the registry. Returns ok=false for unknown names; ok=true with a nil/empty answer means authoritative NOERROR/NODATA. SRV queries return A/AAAA glue in extra.

func (*Registry) SetClusterDomain added in v1.6.6

func (r *Registry) SetClusterDomain(domain string)

SetClusterDomain configures the cluster suffix used for suffix matching and PTR/SRV target construction. The input is lowercased and stripped of any trailing dot.

func (*Registry) SetEndpoints

func (r *Registry) SetEndpoints(service, namespace string, endpoints []Endpoint)

SetEndpoints replaces a service's endpoint set. Headless services route through the per-slice incremental state.

func (*Registry) SetTTLs added in v1.6.6

func (r *Registry) SetTTLs(service, pod, srv, ptr uint32)

SetTTLs sets custom TTL values; 0 keeps the default.

func (*Registry) Stats

func (r *Registry) Stats() map[string]int64

Stats returns counters describing the registry's contents and traffic since process start.

type Response

type Response struct {
	Answer []dns.RR
	Extra  []dns.RR
	Rcode  int
}

Response holds DNS query results

type Service

type Service struct {
	Name         string
	Namespace    string
	Type         string   // ClusterIP, NodePort, LoadBalancer, ExternalName
	ClusterIPs   [][]byte // Dual-stack: [IPv4, IPv6] addresses
	IPFamilies   []string // ["IPv4", "IPv6"] or ["IPv6", "IPv4"]
	ExternalName string   // For ExternalName type
	Headless     bool     // True if ClusterIP is None
	Ports        []Port
}

Service represents a Kubernetes service

func (*Service) GetIPv4

func (s *Service) GetIPv4() []byte

GetIPv4 returns the IPv4 address if available

func (*Service) GetIPv6

func (s *Service) GetIPv6() []byte

GetIPv6 returns the IPv6 address if available

Jump to

Keyboard shortcuts

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