diff --git a/controllers/clusterwidenetworkpolicy_controller.go b/controllers/clusterwidenetworkpolicy_controller.go index 3fba5316..40cb2daa 100644 --- a/controllers/clusterwidenetworkpolicy_controller.go +++ b/controllers/clusterwidenetworkpolicy_controller.go @@ -43,9 +43,10 @@ type ClusterwideNetworkPolicyReconciler struct { Ctx context.Context Recorder record.EventRecorder - Interval time.Duration - DnsProxy *dns.DNSProxy - SkipDNS bool + Interval time.Duration + FQDNStateSyncInterval time.Duration + DnsProxy *dns.DNSProxy + SkipDNS bool } // SetupWithManager configures this controller to run in schedule @@ -145,7 +146,7 @@ func (r *ClusterwideNetworkPolicyReconciler) manageDNSProxy( if enableDNS && r.DnsProxy == nil { r.Log.Info("DNS Proxy is initialized") - if r.DnsProxy, err = dns.NewDNSProxy(r.Ctx, f.Spec.DNSServerAddress, f.Spec.DNSPort, r.ShootClient, ctrl.Log.WithName("DNS proxy")); err != nil { + if r.DnsProxy, err = dns.NewDNSProxy(r.Ctx, f.Spec.DNSServerAddress, f.Spec.DNSPort, r.FQDNStateSyncInterval, r.ShootClient, ctrl.Log.WithName("DNS proxy")); err != nil { return fmt.Errorf("failed to init DNS proxy: %w", err) } go r.DnsProxy.Run() diff --git a/main.go b/main.go index 6f359da8..8bb61141 100644 --- a/main.go +++ b/main.go @@ -56,14 +56,15 @@ func init() { func main() { var ( - logLevel string - isVersion bool - metricsAddr string - enableIDS bool - enableSignatureCheck bool - hostsFile string - firewallName string - kubeconfigPath = os.Getenv("KUBECONFIG") + logLevel string + isVersion bool + metricsAddr string + enableIDS bool + enableSignatureCheck bool + hostsFile string + firewallName string + fqdnStateSyncInterval time.Duration + kubeconfigPath = os.Getenv("KUBECONFIG") ) flag.StringVar(&logLevel, "log-level", "info", "the log level of the controller") @@ -73,6 +74,7 @@ func main() { flag.StringVar(&hostsFile, "hosts-file", "/etc/hosts", "The hosts file to manipulate for the droptailer.") flag.BoolVar(&enableSignatureCheck, "enable-signature-check", true, "Set this to false to ignore signature checking.") flag.StringVar(&firewallName, "firewall-name", "", "the name of the firewall resource in the seed cluster to reconcile (defaults to hostname)") + flag.DurationVar(&fqdnStateSyncInterval, "fqdnstate-sync-interval", 10*time.Second, "minimum interval between fqdn state configmap syncs") if _, err := os.Stat(seedKubeconfigPath); err == nil || os.IsExist(err) { // controller-runtime registered this flag already, so we can use it @@ -265,13 +267,14 @@ func main() { // ClusterwideNetworkPolicy Reconciler if err = (&controllers.ClusterwideNetworkPolicyReconciler{ - SeedClient: seedMgr.GetClient(), - ShootClient: shootMgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("ClusterwideNetworkPolicy"), - Ctx: ctx, - Recorder: shootMgr.GetEventRecorderFor("FirewallController"), // nolint:staticcheck - FirewallName: firewallName, - SeedNamespace: seedNamespace, + SeedClient: seedMgr.GetClient(), + ShootClient: shootMgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("ClusterwideNetworkPolicy"), + Ctx: ctx, + Recorder: shootMgr.GetEventRecorderFor("FirewallController"), // nolint:staticcheck + FirewallName: firewallName, + SeedNamespace: seedNamespace, + FQDNStateSyncInterval: fqdnStateSyncInterval, }).SetupWithManager(shootMgr); err != nil { l.Error("unable to create clusterwidenetworkpolicy controller", "error", err) panic(err) diff --git a/pkg/dns/dnscache.go b/pkg/dns/dnscache.go index 48035ace..ab72c3f0 100644 --- a/pkg/dns/dnscache.go +++ b/pkg/dns/dnscache.go @@ -115,26 +115,29 @@ type cacheEntry struct { type DNSCache struct { sync.RWMutex - log logr.Logger - fqdnToEntry map[string]cacheEntry - setNames map[string]struct{} - dnsServerAddr string - shootClient client.Client - ctx context.Context - ipv4Enabled bool - ipv6Enabled bool + log logr.Logger + fqdnToEntry map[string]cacheEntry + setNames map[string]struct{} + dnsServerAddr string + shootClient client.Client + ctx context.Context + ipv4Enabled bool + ipv6Enabled bool + fqdnStateSyncInterval time.Duration + stateDirty bool } -func newDNSCache(ctx context.Context, dns string, ipv4Enabled, ipv6Enabled bool, shootClient client.Client, log logr.Logger) (*DNSCache, error) { +func newDNSCache(ctx context.Context, dns string, ipv4Enabled, ipv6Enabled bool, fqdnStateSyncInterval time.Duration, shootClient client.Client, log logr.Logger) (*DNSCache, error) { c := DNSCache{ - log: log, - fqdnToEntry: map[string]cacheEntry{}, - setNames: map[string]struct{}{}, - dnsServerAddr: dns, - shootClient: shootClient, - ctx: ctx, - ipv4Enabled: ipv4Enabled, - ipv6Enabled: ipv6Enabled, + log: log, + fqdnToEntry: map[string]cacheEntry{}, + setNames: map[string]struct{}{}, + dnsServerAddr: dns, + shootClient: shootClient, + ctx: ctx, + ipv4Enabled: ipv4Enabled, + ipv6Enabled: ipv6Enabled, + fqdnStateSyncInterval: fqdnStateSyncInterval, } nn := types.NamespacedName{Name: fqdnStateConfigmapName, Namespace: fqdnStateNamespace} @@ -150,20 +153,19 @@ func newDNSCache(ctx context.Context, dns string, ipv4Enabled, ipv6Enabled bool, c.log.Error(err, "error reading fqndstate configmap") return nil, err } - if scm.Data == nil { + if apierrors.IsNotFound(err) || scm.Data == nil { c.log.V(4).Info("DEBUG fqdnstate cm not found or contains no data", "cm", scm) - return &c, nil - - } - if scm.Data[fqdnStateConfigmapKey] == "" { + } else if scm.Data[fqdnStateConfigmapKey] == "" { c.log.Error(fmt.Errorf("error reading fqdnstate configmap, ignoring content"), "fqdnstate configmap does not contain the right key", "configmap", scm, "key", fqdnStateConfigmapKey) - return &c, nil - } - c.log.V(4).Info("DEBUG state stored in fqdnstate cm, trying to unmarshal", fqdnStateConfigmapKey, scm.Data[fqdnStateConfigmapKey]) - err = yaml.UnmarshalStrict([]byte(scm.Data[fqdnStateConfigmapKey]), &c.fqdnToEntry) - if err != nil { - c.log.Info("could not unmarshal state from fqdnstate configmap, ignoring content.", "error", err) + } else { + c.log.V(4).Info("DEBUG state stored in fqdnstate cm, trying to unmarshal", fqdnStateConfigmapKey, scm.Data[fqdnStateConfigmapKey]) + err = yaml.UnmarshalStrict([]byte(scm.Data[fqdnStateConfigmapKey]), &c.fqdnToEntry) + if err != nil { + c.log.Info("could not unmarshal state from fqdnstate configmap, ignoring content.", "error", err) + } } + + c.startFQDNStateSyncLoop() return &c, nil } @@ -215,6 +217,8 @@ func (c *DNSCache) writeStateToConfigmap() error { return err } + c.markStateSynced() + return nil } @@ -227,12 +231,12 @@ func (c *DNSCache) writeStateToConfigmap() error { } debugLog.Info("DEBUG updated cm", "old data", cm.Data, "new data", data) + c.markStateSynced() return nil } debugLog.Info("DEBUG no need to update cm, already up to date") - return nil } @@ -461,9 +465,7 @@ func (c *DNSCache) Update(lookupTime time.Time, qname string, msg *dnsgo.Msg, fq } if ipEntriesUpdated { - if err := c.writeStateToConfigmap(); err != nil { - c.log.V(4).Info("DEBUG could not write updated DNS cache to state configmap", "configmap", fqdnStateConfigmapName, "namespace", fqdnStateNamespace, "error", err) - } + c.markStateDirty() } return found, nil @@ -596,3 +598,48 @@ func createRenderIPSetFromIPEntry(version IPVersion, entry *iPEntry) RenderIPSet Version: version, } } + +func (c *DNSCache) startFQDNStateSyncLoop() { + if c.fqdnStateSyncInterval <= 0 { + c.log.Info("fqdnstate sync interval is set to 0 or negative, skipping state sync loop") + return + } + c.log.Info("starting fqdnstate sync loop", "interval", c.fqdnStateSyncInterval) + + ticker := time.NewTicker(c.fqdnStateSyncInterval) + go func() { + defer ticker.Stop() + for { + select { + case <-c.ctx.Done(): + return + case <-ticker.C: + if !c.isStateDirty() { + continue + } + if err := c.writeStateToConfigmap(); err != nil { + c.log.V(4).Info("DEBUG periodic sync of fqdnstate configmap failed", "configmap", fqdnStateConfigmapName, "namespace", fqdnStateNamespace, "error", err) + } + } + } + }() +} + +func (c *DNSCache) markStateDirty() { + c.Lock() + c.stateDirty = true + c.Unlock() +} + +func (c *DNSCache) markStateSynced() { + c.Lock() + c.stateDirty = false + c.Unlock() +} + +func (c *DNSCache) isStateDirty() bool { + c.RLock() + defer c.RUnlock() + + return c.stateDirty +} diff --git a/pkg/dns/dnsproxy.go b/pkg/dns/dnsproxy.go index b9a16212..e6482dd6 100644 --- a/pkg/dns/dnsproxy.go +++ b/pkg/dns/dnsproxy.go @@ -39,7 +39,7 @@ type DNSProxy struct { handler DNSHandler } -func NewDNSProxy(ctx context.Context, dns string, port *uint, shootClient client.Client, log logr.Logger) (*DNSProxy, error) { +func NewDNSProxy(ctx context.Context, dns string, port *uint, fqdnStateSyncInterval time.Duration, shootClient client.Client, log logr.Logger) (*DNSProxy, error) { if dns == "" { dns = defaultDNSServerAddr } @@ -59,7 +59,7 @@ func NewDNSProxy(ctx context.Context, dns string, port *uint, shootClient client } backgroundCtx, cancel := context.WithCancel(ctx) - cache, err := newDNSCache(backgroundCtx, dns, true, false, shootClient, log.WithName("DNS cache")) + cache, err := newDNSCache(backgroundCtx, dns, true, false, fqdnStateSyncInterval, shootClient, log.WithName("DNS cache")) if err != nil { cancel() return nil, err