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 ¶
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.
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
- Variables
- func BuildOwnerLabelValue(record *v1alpha1.DNSRecord) string
- func GetOwnerFromLabel(probe *v1alpha1.DNSHealthCheckProbe) string
- type BaseDNSRecordReconciler
- type DNSProbeReconciler
- type DNSRecord
- func (s *DNSRecord) ClearStatusCondition(conditionType string)
- func (s *DNSRecord) GetDNSRecord() *v1alpha1.DNSRecord
- func (s *DNSRecord) GetEndpoints() []*externaldns.Endpoint
- func (s *DNSRecord) GetGroup() types.Group
- func (s *DNSRecord) GetOwnerID() string
- func (s *DNSRecord) GetSpec() *v1alpha1.DNSRecordSpec
- func (s *DNSRecord) GetStatus() *v1alpha1.DNSRecordStatus
- func (s *DNSRecord) GetStatusCondition(conditionType string) *metav1.Condition
- func (s *DNSRecord) GetZoneDomainName() string
- func (s *DNSRecord) GetZoneID() string
- func (s *DNSRecord) SetStatusActiveGroups(groups types.Groups)
- func (s *DNSRecord) SetStatusCondition(conditionType string, status metav1.ConditionStatus, reason, message string)
- func (s *DNSRecord) SetStatusConditions(_ bool)
- func (s *DNSRecord) SetStatusDomainOwners(owners []string)
- func (s *DNSRecord) SetStatusEndpoints(endpoints []*externaldns.Endpoint)
- func (s *DNSRecord) SetStatusGroup(group types.Group)
- func (s *DNSRecord) SetStatusObservedGeneration(observedGeneration int64)
- func (s *DNSRecord) SetStatusOwnerID(id string)
- func (s *DNSRecord) SetStatusZoneDomainName(domainName string)
- func (s *DNSRecord) SetStatusZoneID(id string)
- type DNSRecordAccessor
- type DNSRecordReconciler
- func (r *DNSRecordReconciler) DeleteHealthChecks(ctx context.Context, dnsRecord *v1alpha1.DNSRecord) error
- func (r *DNSRecordReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)
- func (r *DNSRecordReconciler) ReconcileHealthChecks(ctx context.Context, dnsRecord *v1alpha1.DNSRecord, allowInsecureCerts bool) error
- func (r *DNSRecordReconciler) SetupWithManager(mgr ctrl.Manager, maxRequeue, validForDuration, minRequeue time.Duration, ...) error
- type DefaultTXTResolver
- type GroupAdapter
- type RemoteDNSRecord
- func (s *RemoteDNSRecord) ClearStatusCondition(conditionType string)
- func (s *RemoteDNSRecord) GetDNSRecord() *v1alpha1.DNSRecord
- func (s *RemoteDNSRecord) GetEndpoints() []*externaldns.Endpoint
- func (s *RemoteDNSRecord) GetGroup() types.Group
- func (s *RemoteDNSRecord) GetOwnerID() string
- func (s *RemoteDNSRecord) GetSpec() *v1alpha1.DNSRecordSpec
- func (s *RemoteDNSRecord) GetStatus() *v1alpha1.DNSRecordStatus
- func (s *RemoteDNSRecord) GetStatusCondition(conditionType string) *metav1.Condition
- func (s *RemoteDNSRecord) GetZoneDomainName() string
- func (s *RemoteDNSRecord) GetZoneID() string
- func (s *RemoteDNSRecord) HasDNSZoneAssigned() bool
- func (s *RemoteDNSRecord) SetStatusActiveGroups(groups types.Groups)
- func (s *RemoteDNSRecord) SetStatusCondition(conditionType string, status metav1.ConditionStatus, reason, message string)
- func (s *RemoteDNSRecord) SetStatusConditions(_ bool)
- func (s *RemoteDNSRecord) SetStatusDomainOwners(owners []string)
- func (s *RemoteDNSRecord) SetStatusEndpoints(endpoints []*externaldns.Endpoint)
- func (s *RemoteDNSRecord) SetStatusGroup(_ types.Group)
- func (s *RemoteDNSRecord) SetStatusObservedGeneration(observedGeneration int64)
- func (s *RemoteDNSRecord) SetStatusOwnerID(_ string)
- func (s *RemoteDNSRecord) SetStatusZoneDomainName(domainName string)
- func (s *RemoteDNSRecord) SetStatusZoneID(id string)
- type RemoteDNSRecordReconciler
- type TXTResolver
Constants ¶
const ( ProbeOwnerLabel = "kuadrant.io/health-probes-owner" DNSHealthCheckFinalizer = "kuadrant.io/dns-health-check-probe" )
const ( DNSRecordFinalizer = "kuadrant.io/dns-record" DelegationRolePrimary = "primary" DelegationRoleSecondary = "secondary" )
const ( ActiveGroupsTXTRecordName = "kuadrant-active-groups" TXTRecordKeysSeparator = ";" TXTRecordGroupKey = "groups" )
Variables ¶
var (
ErrInvalidHeader = fmt.Errorf("invalid header format")
)
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
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) 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
func (*DNSRecord) ClearStatusCondition ¶ added in v0.16.0
func (*DNSRecord) GetDNSRecord ¶ added in v0.16.0
func (*DNSRecord) GetEndpoints ¶ added in v0.16.0
func (s *DNSRecord) GetEndpoints() []*externaldns.Endpoint
func (*DNSRecord) GetOwnerID ¶ added in v0.16.0
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 (*DNSRecord) GetZoneDomainName ¶ added in v0.16.0
func (*DNSRecord) SetStatusActiveGroups ¶ added in v0.16.0
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 (*DNSRecord) SetStatusDomainOwners ¶ added in v0.16.0
func (*DNSRecord) SetStatusEndpoints ¶ added in v0.16.0
func (s *DNSRecord) SetStatusEndpoints(endpoints []*externaldns.Endpoint)
func (*DNSRecord) SetStatusGroup ¶ added in v0.16.0
func (*DNSRecord) SetStatusObservedGeneration ¶ added in v0.16.0
func (*DNSRecord) SetStatusOwnerID ¶ added in v0.16.0
func (*DNSRecord) SetStatusZoneDomainName ¶ added in v0.16.0
func (*DNSRecord) SetStatusZoneID ¶ added in v0.16.0
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) ReconcileHealthChecks ¶ added in v0.2.0
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:
- Try each provided nameserver in sequence until one returns results
- Automatically adds port 53 if not specified in the nameserver address
- Falls back to system DNS resolver if all custom nameservers fail
- 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 (r *RemoteDNSRecordReconciler) Reconcile(ctx context.Context, req mcreconcile.Request) (ctrl.Result, error)
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.