controller

package
v0.16.1 Latest Latest
Warning

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

Go to latest
Published: Feb 18, 2026 License: Apache-2.0 Imports: 45 Imported by: 0

Documentation

Overview

Package controller implements DNS failover groups functionality for the DNS operator.

DNS Failover Groups Overview

DNS Groups enable active-passive failover for DNS records across multiple clusters. Each cluster can be assigned to a named group (e.g., "primary", "secondary"), and only the records from the currently "active" groups are published to DNS.

How It Works

  1. Group Assignment: Each DNSRecord controller is started with a group identifier (via --group flag or GROUP environment variable). Records managed by that controller inherit the group assignment.

  2. Active Groups Declaration: The set of currently active groups is stored as a special TXT record in DNS at: kuadrant-active-groups.<zone-domain-name> Format: "groups=group1&&group2;version=1"

3. Group Filtering: Before publishing, each controller:

  • Queries the active groups TXT record

  • Compares its own group against the active groups list

  • Only publishes if its group is active OR if it's ungrouped

    4. Inactive Group Cleanup: When a group becomes active, it unpublishes DNS records from inactive groups to ensure clean failover without stale data.

Example Scenario

Setup:

  • Cluster A (group="us-east") has DNSRecord pointing to 1.2.3.4
  • Cluster B (group="us-west") has DNSRecord pointing to 5.6.7.8
  • Cluster C (ungrouped) has DNSRecord pointing to 9.9.9.9

When active groups = ["us-east"]:

  • Published targets: 1.2.3.4, 9.9.9.9
  • Cluster B sees it's inactive and skips publishing

When active groups switch to ["us-west"]:

  • Cluster B becomes active and publishes 5.6.7.8
  • Cluster A sees it's inactive and stops publishing
  • Cluster B also unpublishes stale 1.2.3.4 records
  • Published targets: 5.6.7.8, 9.9.9.9

Ungrouped Records

Records without a group assignment (group="") are always considered active and published alongside whichever groups are currently active. This allows for baseline traffic routing or shared infrastructure records.

Key Components

- TXTResolver: Interface for looking up the active groups TXT record from DNS - GroupAdapter: Wraps DNSRecordAccessor to add group-aware IsActive() behavior - unpublishInactiveGroups(): Cleans up DNS records from inactive groups - getActiveGroups(): Queries DNS for the current active groups list

Index

Constants

View Source
const (
	ProbeOwnerLabel         = "kuadrant.io/health-probes-owner"
	DNSHealthCheckFinalizer = "kuadrant.io/dns-health-check-probe"
)
View Source
const (
	DNSRecordFinalizer = "kuadrant.io/dns-record"

	DelegationRolePrimary   = "primary"
	DelegationRoleSecondary = "secondary"
)
View Source
const (
	ActiveGroupsTXTRecordName = "kuadrant-active-groups"
	TXTRecordKeysSeparator    = ";"
	TXTRecordGroupKey         = "groups"
)

Variables

View Source
var (
	ErrInvalidHeader = fmt.Errorf("invalid header format")
)
View Source
var (
	// InactiveGroupRequeueTime determines how frequently inactive group records
	// are reconciled to check if they've become active
	InactiveGroupRequeueTime = time.Second * 15
)

Functions

func BuildOwnerLabelValue added in v0.7.0

func BuildOwnerLabelValue(record *v1alpha1.DNSRecord) string

BuildOwnerLabelValue ensures label value does not exceed the 63 char limit It uses the name of the record, if the resulting string longer than 63 chars, it will use UIDHash of the record

func GetOwnerFromLabel added in v0.10.0

func GetOwnerFromLabel(probe *v1alpha1.DNSHealthCheckProbe) string

GetOwnerFromLabel returns a name or UID of probe owner A reverse to BuildOwnerLabelValue

Types

type BaseDNSRecordReconciler added in v0.16.0

type BaseDNSRecordReconciler struct {
	Scheme          *runtime.Scheme
	ProviderFactory provider.Factory
	DelegationRole  string
	Group           types.Group
	TXTResolver     TXTResolver
}

func (*BaseDNSRecordReconciler) IsPrimary added in v0.16.0

func (r *BaseDNSRecordReconciler) IsPrimary() bool

func (*BaseDNSRecordReconciler) IsSecondary added in v0.16.0

func (r *BaseDNSRecordReconciler) IsSecondary() bool

type DNSProbeReconciler added in v0.7.0

type DNSProbeReconciler struct {
	client.Client
	Scheme       *runtime.Scheme
	ProbeManager *probes.ProbeManager
}

DNSProbeReconciler reconciles a DNSRecord object

func (*DNSProbeReconciler) Reconcile added in v0.7.0

func (r *DNSProbeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)

func (*DNSProbeReconciler) SetupWithManager added in v0.7.0

func (r *DNSProbeReconciler) SetupWithManager(mgr ctrl.Manager, maxRequeue, validForDuration, minRequeue time.Duration) error

SetupWithManager sets up the controller with the Manager.

type DNSRecord added in v0.16.0

type DNSRecord struct {
	*v1alpha1.DNSRecord
}

func (*DNSRecord) ClearStatusCondition added in v0.16.0

func (s *DNSRecord) ClearStatusCondition(conditionType string)

func (*DNSRecord) GetDNSRecord added in v0.16.0

func (s *DNSRecord) GetDNSRecord() *v1alpha1.DNSRecord

func (*DNSRecord) GetEndpoints added in v0.16.0

func (s *DNSRecord) GetEndpoints() []*externaldns.Endpoint

func (*DNSRecord) GetGroup added in v0.16.0

func (s *DNSRecord) GetGroup() types.Group

func (*DNSRecord) GetOwnerID added in v0.16.0

func (s *DNSRecord) GetOwnerID() string

func (*DNSRecord) GetSpec added in v0.16.0

func (s *DNSRecord) GetSpec() *v1alpha1.DNSRecordSpec

func (*DNSRecord) GetStatus added in v0.16.0

func (s *DNSRecord) GetStatus() *v1alpha1.DNSRecordStatus

func (*DNSRecord) GetStatusCondition added in v0.16.0

func (s *DNSRecord) GetStatusCondition(conditionType string) *metav1.Condition

func (*DNSRecord) GetZoneDomainName added in v0.16.0

func (s *DNSRecord) GetZoneDomainName() string

func (*DNSRecord) GetZoneID added in v0.16.0

func (s *DNSRecord) GetZoneID() string

func (*DNSRecord) SetStatusActiveGroups added in v0.16.0

func (s *DNSRecord) SetStatusActiveGroups(groups types.Groups)

func (*DNSRecord) SetStatusCondition added in v0.16.0

func (s *DNSRecord) SetStatusCondition(conditionType string, status metav1.ConditionStatus, reason, message string)

func (*DNSRecord) SetStatusConditions added in v0.16.0

func (s *DNSRecord) SetStatusConditions(_ bool)

func (*DNSRecord) SetStatusDomainOwners added in v0.16.0

func (s *DNSRecord) SetStatusDomainOwners(owners []string)

func (*DNSRecord) SetStatusEndpoints added in v0.16.0

func (s *DNSRecord) SetStatusEndpoints(endpoints []*externaldns.Endpoint)

func (*DNSRecord) SetStatusGroup added in v0.16.0

func (s *DNSRecord) SetStatusGroup(group types.Group)

func (*DNSRecord) SetStatusObservedGeneration added in v0.16.0

func (s *DNSRecord) SetStatusObservedGeneration(observedGeneration int64)

func (*DNSRecord) SetStatusOwnerID added in v0.16.0

func (s *DNSRecord) SetStatusOwnerID(id string)

func (*DNSRecord) SetStatusZoneDomainName added in v0.16.0

func (s *DNSRecord) SetStatusZoneDomainName(domainName string)

func (*DNSRecord) SetStatusZoneID added in v0.16.0

func (s *DNSRecord) SetStatusZoneID(id string)

type DNSRecordAccessor added in v0.15.0

type DNSRecordAccessor interface {
	v1alpha1.ProviderAccessor
	GetDNSRecord() *v1alpha1.DNSRecord
	GetOwnerID() string
	GetGroup() types.Group
	GetRootHost() string
	GetZoneDomainName() string
	GetZoneID() string
	GetEndpoints() []*externaldns.Endpoint
	GetSpec() *v1alpha1.DNSRecordSpec
	GetStatus() *v1alpha1.DNSRecordStatus
	SetStatusConditions(hadChanges bool)
	SetStatusCondition(conditionType string, status metav1.ConditionStatus, reason, message string)
	ClearStatusCondition(conditionType string)
	GetStatusCondition(conditionType string) *metav1.Condition
	SetStatusOwnerID(id string)
	SetStatusZoneID(id string)
	SetStatusZoneDomainName(domainName string)
	SetStatusDomainOwners(owners []string)
	SetStatusEndpoints(endpoints []*externaldns.Endpoint)
	SetStatusObservedGeneration(observedGeneration int64)
	SetStatusGroup(types.Group)
	SetStatusActiveGroups(types.Groups)
	HasOwnerIDAssigned() bool
	HasDNSZoneAssigned() bool
	HasProviderSecretAssigned() bool
	IsDeleting() bool
	IsActive() bool
}

type DNSRecordReconciler

type DNSRecordReconciler struct {
	BaseDNSRecordReconciler

	client.Client
}

DNSRecordReconciler reconciles a DNSRecord object on a local cluster.

func (*DNSRecordReconciler) DeleteHealthChecks added in v0.7.0

func (r *DNSRecordReconciler) DeleteHealthChecks(ctx context.Context, dnsRecord *v1alpha1.DNSRecord) error

DeleteHealthChecks deletes all v1alpha1.DNSHealthCheckProbe that have ProbeOwnerLabel of passed in DNSRecord

func (*DNSRecordReconciler) Reconcile

func (r *DNSRecordReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)

func (*DNSRecordReconciler) ReconcileHealthChecks added in v0.2.0

func (r *DNSRecordReconciler) ReconcileHealthChecks(ctx context.Context, dnsRecord *v1alpha1.DNSRecord, allowInsecureCerts bool) error

func (*DNSRecordReconciler) SetupWithManager

func (r *DNSRecordReconciler) SetupWithManager(mgr ctrl.Manager, maxRequeue, validForDuration, minRequeue time.Duration, healthProbesEnabled, allowInsecureHealthCert bool) error

SetupWithManager sets up the controller with the Manager.

type DefaultTXTResolver added in v0.16.0

type DefaultTXTResolver struct{}

DefaultTXTResolver is the default implementation that uses net.LookupTXT. It supports custom nameserver configuration for querying specific DNS servers.

func (*DefaultTXTResolver) LookupTXT added in v0.16.0

func (d *DefaultTXTResolver) LookupTXT(ctx context.Context, host string, nameservers []string) ([]string, error)

LookupTXT queries TXT records for the given host using custom nameservers if provided. This is used to query the active groups TXT record, which may be hosted on specific DNS servers (e.g., CoreDNS instances in local development).

Nameserver Resolution Strategy:

  1. Try each provided nameserver in sequence until one returns results
  2. Automatically adds port 53 if not specified in the nameserver address
  3. Falls back to system DNS resolver if all custom nameservers fail
  4. Uses a 3-second timeout per nameserver to avoid hanging on unreachable servers

type GroupAdapter added in v0.16.0

type GroupAdapter struct {
	DNSRecordAccessor
	// contains filtered or unexported fields
}

GroupAdapter wraps a DNSRecordAccessor to provide group-aware behavior. It determines whether a record should be published based on whether its group is in the list of currently active groups.

func (*GroupAdapter) GetActiveGroups added in v0.16.0

func (s *GroupAdapter) GetActiveGroups() types.Groups

func (*GroupAdapter) IsActive added in v0.16.0

func (s *GroupAdapter) IsActive() bool

IsActive determines if this record should be published based on group membership. Returns true if:

  • The record is ungrouped (group=""), OR
  • The record's group is in the active groups list

Returns false if:

  • The record has a group assignment that is NOT in the active groups list

func (*GroupAdapter) SetStatusConditions added in v0.16.0

func (s *GroupAdapter) SetStatusConditions(hadChanges bool)

SetStatusConditions updates the DNSRecord status conditions including the Active condition. The Active condition indicates whether this record's group is currently active:

  • ConditionTrue: Record is in an active group and will be published
  • ConditionFalse: Record is in an inactive group and will not be published
  • Condition removed: Record is ungrouped (always active, no condition needed)

type RemoteDNSRecord added in v0.15.0

type RemoteDNSRecord struct {
	*v1alpha1.DNSRecord
	ClusterID string
	// contains filtered or unexported fields
}

func (*RemoteDNSRecord) ClearStatusCondition added in v0.16.0

func (s *RemoteDNSRecord) ClearStatusCondition(conditionType string)

func (*RemoteDNSRecord) GetDNSRecord added in v0.15.0

func (s *RemoteDNSRecord) GetDNSRecord() *v1alpha1.DNSRecord

func (*RemoteDNSRecord) GetEndpoints added in v0.16.0

func (s *RemoteDNSRecord) GetEndpoints() []*externaldns.Endpoint

func (*RemoteDNSRecord) GetGroup added in v0.16.0

func (s *RemoteDNSRecord) GetGroup() types.Group

func (*RemoteDNSRecord) GetOwnerID added in v0.15.0

func (s *RemoteDNSRecord) GetOwnerID() string

func (*RemoteDNSRecord) GetSpec added in v0.15.0

func (s *RemoteDNSRecord) GetSpec() *v1alpha1.DNSRecordSpec

func (*RemoteDNSRecord) GetStatus added in v0.15.0

func (s *RemoteDNSRecord) GetStatus() *v1alpha1.DNSRecordStatus

GetStatus returns the status set for the current cluster ID. If none is set an empty DNSRecordStatus is returned.

func (*RemoteDNSRecord) GetStatusCondition added in v0.16.0

func (s *RemoteDNSRecord) GetStatusCondition(conditionType string) *metav1.Condition

func (*RemoteDNSRecord) GetZoneDomainName added in v0.15.0

func (s *RemoteDNSRecord) GetZoneDomainName() string

func (*RemoteDNSRecord) GetZoneID added in v0.15.0

func (s *RemoteDNSRecord) GetZoneID() string

func (*RemoteDNSRecord) HasDNSZoneAssigned added in v0.15.0

func (s *RemoteDNSRecord) HasDNSZoneAssigned() bool

func (*RemoteDNSRecord) SetStatusActiveGroups added in v0.16.0

func (s *RemoteDNSRecord) SetStatusActiveGroups(groups types.Groups)

func (*RemoteDNSRecord) SetStatusCondition added in v0.15.0

func (s *RemoteDNSRecord) SetStatusCondition(conditionType string, status metav1.ConditionStatus, reason, message string)

func (*RemoteDNSRecord) SetStatusConditions added in v0.16.0

func (s *RemoteDNSRecord) SetStatusConditions(_ bool)

func (*RemoteDNSRecord) SetStatusDomainOwners added in v0.15.0

func (s *RemoteDNSRecord) SetStatusDomainOwners(owners []string)

func (*RemoteDNSRecord) SetStatusEndpoints added in v0.15.0

func (s *RemoteDNSRecord) SetStatusEndpoints(endpoints []*externaldns.Endpoint)

func (*RemoteDNSRecord) SetStatusGroup added in v0.16.0

func (s *RemoteDNSRecord) SetStatusGroup(_ types.Group)

func (*RemoteDNSRecord) SetStatusObservedGeneration added in v0.15.0

func (s *RemoteDNSRecord) SetStatusObservedGeneration(observedGeneration int64)

func (*RemoteDNSRecord) SetStatusOwnerID added in v0.16.0

func (s *RemoteDNSRecord) SetStatusOwnerID(_ string)

func (*RemoteDNSRecord) SetStatusZoneDomainName added in v0.15.0

func (s *RemoteDNSRecord) SetStatusZoneDomainName(domainName string)

func (*RemoteDNSRecord) SetStatusZoneID added in v0.15.0

func (s *RemoteDNSRecord) SetStatusZoneID(id string)

type RemoteDNSRecordReconciler added in v0.15.0

type RemoteDNSRecordReconciler struct {
	BaseDNSRecordReconciler

	RemoteClusterCollector *metrics.RemoteClusterCollector
	// contains filtered or unexported fields
}

RemoteDNSRecordReconciler reconciles a DNSRecord object on a remote cluster.

func (*RemoteDNSRecordReconciler) Reconcile added in v0.15.0

func (*RemoteDNSRecordReconciler) SetupWithManager added in v0.15.0

func (r *RemoteDNSRecordReconciler) SetupWithManager(mgr mcmanager.Manager, maxRequeue time.Duration, skipNameValidation bool) error

SetupWithManager sets up the controller with the Manager.

type TXTResolver added in v0.16.0

type TXTResolver interface {
	// LookupTXT queries the specified host for TXT records.
	// If nameservers are provided, queries are directed to those servers.
	// If nameservers is empty or all fail, falls back to default DNS resolution.
	LookupTXT(ctx context.Context, host string, nameservers []string) ([]string, error)
}

TXTResolver is an interface for resolving TXT DNS records. This abstraction allows for mocking in tests and custom resolution logic.

Jump to

Keyboard shortcuts

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