| Tim Windelschmidt | 6d33a43 | 2025-02-04 14:34:25 +0100 | [diff] [blame] | 1 | // Copyright The Monogon Project Authors. |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 2 | // SPDX-License-Identifier: Apache-2.0 |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 3 | |
| Serge Bazanski | 216fe7b | 2021-05-21 18:36:16 +0200 | [diff] [blame] | 4 | // Package clusternet implements a WireGuard-based overlay network for |
| 5 | // Kubernetes. It relies on controller-manager's IPAM to assign IP ranges to |
| 6 | // nodes and on Kubernetes' Node objects to distribute the Node IPs and public |
| 7 | // keys. |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 8 | // |
| Serge Bazanski | 216fe7b | 2021-05-21 18:36:16 +0200 | [diff] [blame] | 9 | // It sets up a single WireGuard network interface and routes the entire |
| 10 | // ClusterCIDR into that network interface, relying on WireGuard's AllowedIPs |
| 11 | // mechanism to look up the correct peer node to send the traffic to. This |
| 12 | // means that the routing table doesn't change and doesn't have to be |
| 13 | // separately managed. When clusternet is started it annotates its WireGuard |
| 14 | // public key onto its node object. |
| 15 | // For each node object that's created or updated on the K8s apiserver it |
| 16 | // checks if a public key annotation is set and if yes a peer with that public |
| 17 | // key, its InternalIP as endpoint and the CIDR for that node as AllowedIPs is |
| 18 | // created. |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 19 | package clusternet |
| 20 | |
| 21 | import ( |
| 22 | "context" |
| Serge Bazanski | 7920852 | 2023-03-28 20:14:58 +0200 | [diff] [blame] | 23 | "net/netip" |
| 24 | "time" |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 25 | |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 26 | corev1 "k8s.io/api/core/v1" |
| Serge Bazanski | 7920852 | 2023-03-28 20:14:58 +0200 | [diff] [blame] | 27 | "k8s.io/apimachinery/pkg/fields" |
| Serge Bazanski | 77cb6c5 | 2020-12-19 00:09:22 +0100 | [diff] [blame] | 28 | "k8s.io/client-go/kubernetes" |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 29 | "k8s.io/client-go/tools/cache" |
| 30 | |
| Serge Bazanski | 3c5d063 | 2024-09-12 10:49:12 +0000 | [diff] [blame] | 31 | "source.monogon.dev/go/logging" |
| Lorenz Brun | cb76c84 | 2025-08-11 12:54:28 +0200 | [diff] [blame^] | 32 | "source.monogon.dev/metropolis/node/core/network/ipam" |
| Tim Windelschmidt | 9f21f53 | 2024-05-07 15:14:20 +0200 | [diff] [blame] | 33 | "source.monogon.dev/osbase/event" |
| Tim Windelschmidt | 9f21f53 | 2024-05-07 15:14:20 +0200 | [diff] [blame] | 34 | "source.monogon.dev/osbase/supervisor" |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 35 | ) |
| 36 | |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 37 | type Service struct { |
| Serge Bazanski | 7920852 | 2023-03-28 20:14:58 +0200 | [diff] [blame] | 38 | NodeName string |
| 39 | Kubernetes kubernetes.Interface |
| Lorenz Brun | cb76c84 | 2025-08-11 12:54:28 +0200 | [diff] [blame^] | 40 | Prefixes event.Value[*ipam.Prefixes] |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 41 | |
| Serge Bazanski | 3c5d063 | 2024-09-12 10:49:12 +0000 | [diff] [blame] | 42 | logger logging.Leveled |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 43 | } |
| 44 | |
| Serge Bazanski | 7920852 | 2023-03-28 20:14:58 +0200 | [diff] [blame] | 45 | // ensureNode is called any time the node that this Service is running on gets |
| 46 | // updated. It uses this data to update this node's prefixes in the Curator. |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 47 | func (s *Service) ensureNode(newNode *corev1.Node) error { |
| Serge Bazanski | 7920852 | 2023-03-28 20:14:58 +0200 | [diff] [blame] | 48 | if newNode.Name != s.NodeName { |
| 49 | // We only care about our own node |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 50 | return nil |
| 51 | } |
| Serge Bazanski | 7920852 | 2023-03-28 20:14:58 +0200 | [diff] [blame] | 52 | |
| Lorenz Brun | cb76c84 | 2025-08-11 12:54:28 +0200 | [diff] [blame^] | 53 | var prefixes ipam.Prefixes |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 54 | for _, podNetStr := range newNode.Spec.PodCIDRs { |
| Serge Bazanski | 7920852 | 2023-03-28 20:14:58 +0200 | [diff] [blame] | 55 | prefix, err := netip.ParsePrefix(podNetStr) |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 56 | if err != nil { |
| Serge Bazanski | c735967 | 2020-10-30 16:38:57 +0100 | [diff] [blame] | 57 | s.logger.Warningf("Node %s PodCIDR failed to parse, ignored: %v", newNode.Name, err) |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 58 | continue |
| 59 | } |
| Serge Bazanski | 7920852 | 2023-03-28 20:14:58 +0200 | [diff] [blame] | 60 | prefixes = append(prefixes, prefix) |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 61 | } |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 62 | |
| Serge Bazanski | 7920852 | 2023-03-28 20:14:58 +0200 | [diff] [blame] | 63 | s.logger.V(1).Infof("Updating locally originated prefixes: %+v", prefixes) |
| 64 | s.Prefixes.Set(&prefixes) |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 65 | return nil |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 66 | } |
| 67 | |
| 68 | // Run runs the ClusterNet service. See package description for what it does. |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 69 | func (s *Service) Run(ctx context.Context) error { |
| 70 | logger := supervisor.Logger(ctx) |
| Lorenz Brun | ca24cfa | 2020-08-18 13:49:37 +0200 | [diff] [blame] | 71 | s.logger = logger |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 72 | |
| Serge Bazanski | 7920852 | 2023-03-28 20:14:58 +0200 | [diff] [blame] | 73 | // Make a 'shared' informer. It's shared by name, but we don't actually share it |
| 74 | // - instead we have to use it as the standard Informer API does not support |
| 75 | // error handling. And we want to use a dedicated informer because we want to |
| 76 | // only watch our own node. |
| 77 | lw := cache.NewListWatchFromClient( |
| 78 | s.Kubernetes.CoreV1().RESTClient(), |
| 79 | "nodes", "", |
| 80 | fields.OneTermEqualSelector("metadata.name", s.NodeName), |
| 81 | ) |
| 82 | ni := cache.NewSharedInformer(lw, &corev1.Node{}, time.Second*5) |
| 83 | ni.AddEventHandler(cache.ResourceEventHandlerFuncs{ |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 84 | AddFunc: func(new interface{}) { |
| 85 | newNode, ok := new.(*corev1.Node) |
| 86 | if !ok { |
| Serge Bazanski | c735967 | 2020-10-30 16:38:57 +0100 | [diff] [blame] | 87 | logger.Errorf("Received non-node item %+v in node event handler", new) |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 88 | return |
| 89 | } |
| 90 | if err := s.ensureNode(newNode); err != nil { |
| Serge Bazanski | c735967 | 2020-10-30 16:38:57 +0100 | [diff] [blame] | 91 | logger.Warningf("Failed to sync node: %v", err) |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 92 | } |
| 93 | }, |
| 94 | UpdateFunc: func(old, new interface{}) { |
| 95 | newNode, ok := new.(*corev1.Node) |
| 96 | if !ok { |
| Serge Bazanski | c735967 | 2020-10-30 16:38:57 +0100 | [diff] [blame] | 97 | logger.Errorf("Received non-node item %+v in node event handler", new) |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 98 | return |
| 99 | } |
| 100 | if err := s.ensureNode(newNode); err != nil { |
| Serge Bazanski | c735967 | 2020-10-30 16:38:57 +0100 | [diff] [blame] | 101 | logger.Warningf("Failed to sync node: %v", err) |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 102 | } |
| 103 | }, |
| Serge Bazanski | 7920852 | 2023-03-28 20:14:58 +0200 | [diff] [blame] | 104 | }) |
| 105 | ni.SetWatchErrorHandler(func(_ *cache.Reflector, err error) { |
| 106 | supervisor.Logger(ctx).Errorf("node informer watch error: %v", err) |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 107 | }) |
| 108 | |
| 109 | supervisor.Signal(ctx, supervisor.SignalHealthy) |
| Serge Bazanski | 7920852 | 2023-03-28 20:14:58 +0200 | [diff] [blame] | 110 | ni.Run(ctx.Done()) |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 111 | return ctx.Err() |
| Lorenz Brun | f042e6f | 2020-06-24 16:46:09 +0200 | [diff] [blame] | 112 | } |