-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Send HostMetadata to BPF KubeProxy #11817
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a88351d
9cd0f0c
31c3951
911257d
96173ef
d9fb94f
db4f05e
a0a4dcd
8c3bc7a
4770c54
f934bb4
6ef65d7
9283a6c
84ad309
807debe
535993a
9e08163
c51769b
3438016
513138f
507e26f
5830ba3
6a22591
234f5c8
6328346
a618e89
e6dd427
25bac63
c4ec5d2
784c1c5
562fec4
02f82d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ import ( | |
| "github.com/projectcalico/calico/felix/bpf/maps" | ||
| "github.com/projectcalico/calico/felix/bpf/routes" | ||
| "github.com/projectcalico/calico/felix/ip" | ||
| "github.com/projectcalico/calico/felix/proto" | ||
| ) | ||
|
|
||
| // KubeProxy is a wrapper of Proxy that deals with higher level issue like | ||
|
|
@@ -34,6 +35,15 @@ type KubeProxy struct { | |
| proxy ProxyFrontend | ||
| syncer DPSyncer | ||
|
|
||
| // pendingHostMetadataUpdates contains HostMetadataV4V6Update and HostMetadataV4V6Removes | ||
| // that we're batching up to send. Only accessed from the int-dataplane goroutine. | ||
| // Keyed by hostname (node name). | ||
| pendingHostMetadataUpdates map[string]any | ||
| // hostMetadataUpdates is a size-1 channel - allows for one non-blocking write, | ||
| // and repeated updates get merged into older unconsumed ones. | ||
| hostMetadataUpdates chan map[string]any | ||
| inSyncWithIntDataplane bool | ||
|
|
||
| ipFamily int | ||
| hostIPUpdates chan []net.IP | ||
| stopOnce sync.Once | ||
|
|
@@ -73,6 +83,9 @@ func StartKubeProxy(k8s kubernetes.Interface, hostname string, | |
| opts: opts, | ||
| rt: NewRTCache(), | ||
|
|
||
| hostMetadataUpdates: make(chan map[string]any, 1), | ||
| pendingHostMetadataUpdates: make(map[string]any), | ||
|
|
||
| hostIPUpdates: make(chan []net.IP, 1), | ||
| exiting: make(chan struct{}), | ||
| } | ||
|
|
@@ -104,13 +117,21 @@ func (kp *KubeProxy) Stop() { | |
| defer kp.lock.Unlock() | ||
|
|
||
| close(kp.exiting) | ||
| close(kp.hostMetadataUpdates) | ||
| close(kp.hostIPUpdates) | ||
| kp.proxy.Stop() | ||
| kp.wg.Wait() | ||
| }) | ||
| } | ||
|
|
||
| func (kp *KubeProxy) run(hostIPs []net.IP) error { | ||
| func (kp *KubeProxy) setProxyHostMetadata(hostMetadata map[string]*proto.HostMetadataV4V6Update) { | ||
| kp.lock.Lock() | ||
| defer kp.lock.Unlock() | ||
|
|
||
| kp.proxy.SetHostMetadata(hostMetadata, true) | ||
| } | ||
|
|
||
| func (kp *KubeProxy) run(hostIPs []net.IP, hostMetadata map[string]*proto.HostMetadataV4V6Update) error { | ||
|
|
||
| ips := make([]net.IP, 0, len(hostIPs)) | ||
| for _, ip := range hostIPs { | ||
|
|
@@ -141,6 +162,8 @@ func (kp *KubeProxy) run(hostIPs []net.IP) error { | |
| } | ||
|
|
||
| kp.proxy.SetHostIPs(hostIPs) | ||
| // Don't bother invoking a resync within SetHostMetadata; we will be syncing a fresh syncer right after. | ||
| kp.proxy.SetHostMetadata(hostMetadata, false) | ||
| kp.proxy.SetSyncer(syncer) | ||
|
|
||
| log.Infof("kube-proxy v%d node info updated, hostname=%q hostIPs=%+v", kp.ipFamily, kp.hostname, hostIPs) | ||
|
|
@@ -173,26 +196,43 @@ func (kp *KubeProxy) start() error { | |
| kp.syncer = syncer | ||
| kp.lock.Unlock() | ||
|
|
||
| // wait for the initial update | ||
| // Wait for the initial update. | ||
| hostIPs := <-kp.hostIPUpdates | ||
|
|
||
| err = kp.run(hostIPs) | ||
| hostMetadata := make(map[string]*proto.HostMetadataV4V6Update) | ||
| // Block until we go in-sync and get the first batch of hostmetadata | ||
| // updates, to avoid a flap after a Felix restart. In practice, this | ||
| // recv should happen very soon after receiving the host IPs above. | ||
| hostMetadataUpdates := <-kp.hostMetadataUpdates | ||
| mergeHostMetadataV4V6Updates(hostMetadata, hostMetadataUpdates) | ||
|
|
||
| err = kp.run(hostIPs, hostMetadata) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| kp.wg.Go(func() { | ||
| for { | ||
| var ok bool | ||
| select { | ||
| case hostIPs, ok := <-kp.hostIPUpdates: | ||
| case hostIPs, ok = <-kp.hostIPUpdates: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this now redundant because it is also covered by the hostMetadata updates (idk 100%)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll need to check on that - these host IP updates were ultimately coming from interface updates IIRC, and I'm not sure if the same word is being used differently between that and HostMetadata updates 🤔 |
||
| if !ok { | ||
| log.Error("kube-proxy: hostIPUpdates closed") | ||
| return | ||
| } | ||
| err = kp.run(hostIPs) | ||
| err = kp.run(hostIPs, hostMetadata) | ||
| if err != nil { | ||
| log.Panic("kube-proxy failed to resync after host IPs update") | ||
| } | ||
|
|
||
| case hostMetadataUpdates, ok = <-kp.hostMetadataUpdates: | ||
| if !ok { | ||
| log.Error("kube-proxy: hostMetadataUpdates closed") | ||
| return | ||
| } | ||
| mergeHostMetadataV4V6Updates(hostMetadata, hostMetadataUpdates) | ||
| kp.setProxyHostMetadata(hostMetadata) | ||
|
|
||
| case <-kp.exiting: | ||
| log.Info("kube-proxy: exiting") | ||
| return | ||
|
|
@@ -221,6 +261,92 @@ func (kp *KubeProxy) OnHostIPsUpdate(IPs []net.IP) { | |
| log.Debugf("kube-proxy OnHostIPsUpdate: %+v", IPs) | ||
| } | ||
|
|
||
| // OnUpdate implements the manager interface. | ||
| // Writes updates to pending updates map - overwrites repeated updates for the same key. | ||
| func (kp *KubeProxy) OnUpdate(msg any) { | ||
| hostname := "" | ||
| switch update := msg.(type) { | ||
| case *proto.HostMetadataV4V6Update: | ||
| hostname = update.Hostname | ||
| log.WithField("msg", update).Debugf("kube-proxy OnUpdate: host metadata update") | ||
| case *proto.HostMetadataV4V6Remove: | ||
| hostname = update.Hostname | ||
| log.WithField("msg", update).Debugf("kube-proxy OnUpdate: host metadata remove") | ||
| default: | ||
| return | ||
| } | ||
|
|
||
| if hostname == "" { | ||
| log.WithField("msg", msg).Warn("kube-proxy OnUpdate: got host metadata update with empty hostname") | ||
| return | ||
| } | ||
|
|
||
| kp.pendingHostMetadataUpdates[hostname] = msg | ||
| } | ||
|
|
||
| // CompleteDeferredWork implements the manager interface. | ||
| // Avoids blocking the thread by draining & merging older updates on the channel before sending. | ||
| func (kp *KubeProxy) CompleteDeferredWork() error { | ||
| // If not in-sync with felix, we allow sending an empty update | ||
| // to signal to the KP loop that it can start looping. | ||
| if len(kp.pendingHostMetadataUpdates) == 0 && kp.inSyncWithIntDataplane { | ||
| log.Debug("No pending host metadata updates to process") | ||
| return nil | ||
| } | ||
|
|
||
| // Drain any pre-existing msg first and merge. | ||
| updates := kp.pollHostMetadataV4V6UpdatesNonBlocking() | ||
| if updates == nil { | ||
| updates = make(map[string]any) | ||
| } | ||
|
|
||
| // Overwrite any pre-existing updates for a given key. | ||
| // Always send 'Removes' instead of just deleting updates of the same key (since downstream may need to see a remove). | ||
| for k, v := range kp.pendingHostMetadataUpdates { | ||
| updates[k] = v | ||
| log.WithField("nodeName", k).Debug("Queueing new host metadata update") | ||
| // ... And clear the pending updates after processing. | ||
| delete(kp.pendingHostMetadataUpdates, k) | ||
| } | ||
|
|
||
| // Send the merged updates back down the channel. | ||
| log.Debug("Queueing new hostmetadata for main loop") | ||
| kp.hostMetadataUpdates <- updates | ||
|
||
| log.Debug("Successfully queued new hostmetadata") | ||
| kp.inSyncWithIntDataplane = true | ||
| return nil | ||
| } | ||
|
|
||
| // pollHostMetadataV4V6UpdatesNonBlocking tries to read a pending host metadata update on the update channel. | ||
| // Returns nil immediately, if nothing can be received from the updates channel. | ||
| func (kp *KubeProxy) pollHostMetadataV4V6UpdatesNonBlocking() map[string]any { | ||
| select { | ||
| case upd := <-kp.hostMetadataUpdates: | ||
| return upd | ||
| default: | ||
| return nil | ||
| } | ||
| } | ||
|
|
||
| // mergeHostMetadataV4V6Updates merges the existing host metadata updates with the latest updates: | ||
| // - A 'remove' in latest deletes the corresponding key in 'existing'. | ||
| // - An 'update' in latest overwrites the corresponding key in 'existing'. | ||
| // - If 'latest' is nil, does nothing. 'existing' must be non-nil. | ||
| func mergeHostMetadataV4V6Updates(existing map[string]*proto.HostMetadataV4V6Update, latest map[string]any) { | ||
| if latest == nil { | ||
| return | ||
| } | ||
|
|
||
| for k, v := range latest { | ||
| switch update := v.(type) { | ||
| case *proto.HostMetadataV4V6Update: | ||
| existing[k] = update | ||
| case *proto.HostMetadataV4V6Remove: | ||
| delete(existing, k) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // OnRouteUpdate should be used to update the internal state of routing tables | ||
| func (kp *KubeProxy) OnRouteUpdate(k routes.KeyInterface, v routes.ValueInterface) { | ||
| log.WithFields(log.Fields{"key": k, "value": v}).Debug("kube-proxy: OnRouteUpdate") | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feels like a void operation. Merging with empty is whatever you got, can't you just assign it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of those structures can contain removes aswell as updates, while the other only contains updates