blob: 637be7bbc9fde39f65c1c118ec7b8017f45d196f [file] [log] [blame]
Lorenz Brunb682ba52020-07-08 14:51:36 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
Serge Bazanski216fe7b2021-05-21 18:36:16 +020017// Package nfproxy is a Kubernetes Service IP proxy based exclusively on the
18// Linux nftables interface. It uses netfilter's NAT capabilities to accept
19// traffic on service IPs and DNAT it to the respective endpoint.
Lorenz Brunb682ba52020-07-08 14:51:36 +020020package nfproxy
21
22import (
23 "context"
24 "errors"
25 "fmt"
26 "net"
27 "os"
28 "time"
29
Lorenz Brunb682ba52020-07-08 14:51:36 +020030 "github.com/sbezverk/nfproxy/pkg/controller"
31 "github.com/sbezverk/nfproxy/pkg/nftables"
32 "github.com/sbezverk/nfproxy/pkg/proxy"
33 v1 "k8s.io/api/core/v1"
34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35 "k8s.io/apimachinery/pkg/labels"
36 "k8s.io/apimachinery/pkg/selection"
37 kubeinformers "k8s.io/client-go/informers"
38 "k8s.io/client-go/kubernetes"
39 "k8s.io/client-go/kubernetes/scheme"
Serge Bazanski6d6ed312023-03-27 11:04:14 +020040 "k8s.io/client-go/tools/cache"
Lorenz Brunb682ba52020-07-08 14:51:36 +020041 "k8s.io/client-go/tools/record"
Serge Bazanski77cb6c52020-12-19 00:09:22 +010042
Serge Bazanski31370b02021-01-07 16:31:14 +010043 "source.monogon.dev/metropolis/pkg/supervisor"
Lorenz Brunb682ba52020-07-08 14:51:36 +020044)
45
46type Service struct {
Serge Bazanski216fe7b2021-05-21 18:36:16 +020047 // Traffic in ClusterCIDR is assumed to be originated inside the cluster
48 // and will not be SNATed
Lorenz Brunb682ba52020-07-08 14:51:36 +020049 ClusterCIDR net.IPNet
50 // A Kubernetes ClientSet with read access to endpoints and services
51 ClientSet kubernetes.Interface
52}
53
54func (s *Service) Run(ctx context.Context) error {
55 var ipv4ClusterCIDR string
56 var ipv6ClusterCIDR string
57 if s.ClusterCIDR.IP.To4() == nil && s.ClusterCIDR.IP.To16() != nil {
58 ipv6ClusterCIDR = s.ClusterCIDR.String()
59 } else if s.ClusterCIDR.IP.To4() != nil {
60 ipv4ClusterCIDR = s.ClusterCIDR.String()
61 } else {
62 return errors.New("invalid ClusterCIDR")
63 }
64 nfti, err := nftables.InitNFTables(ipv4ClusterCIDR, ipv6ClusterCIDR)
65 if err != nil {
66 return fmt.Errorf("failed to initialize nftables with error: %w", err)
67 }
68
69 // Create event recorder to report events into K8s
70 hostname, err := os.Hostname()
71 if err != nil {
72 return fmt.Errorf("failed to get local host name with error: %w", err)
73 }
74 eventBroadcaster := record.NewBroadcaster()
75 recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "nfproxy", Host: hostname})
76
77 // Create new proxy controller with endpoint slices enabled
78 // https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/
79 nfproxy := proxy.NewProxy(nfti, hostname, recorder, true)
80
81 // Create special informer which doesn't track headless services
82 noHeadlessEndpoints, err := labels.NewRequirement(v1.IsHeadlessService, selection.DoesNotExist, nil)
83 if err != nil {
84 return fmt.Errorf("failed to create Requirement for noHeadlessEndpoints: %w", err)
85 }
86 labelSelector := labels.NewSelector()
87 labelSelector = labelSelector.Add(*noHeadlessEndpoints)
88
89 kubeInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(s.ClientSet, time.Minute*5,
90 kubeinformers.WithTweakListOptions(func(options *metav1.ListOptions) {
91 options.LabelSelector = labelSelector.String()
92 }))
93
Lorenz Brune6e570a2023-11-28 19:23:19 +010094 endpointSlicesInformer := kubeInformerFactory.Discovery().V1().EndpointSlices()
Serge Bazanski6d6ed312023-03-27 11:04:14 +020095 endpointSlicesInformer.Informer().SetWatchErrorHandler(func(_ *cache.Reflector, err error) {
96 supervisor.Logger(ctx).Errorf("endpoint slices watch error: %v", err)
97 })
98 servicesInformer := kubeInformerFactory.Core().V1().Services()
99 servicesInformer.Informer().SetWatchErrorHandler(func(_ *cache.Reflector, err error) {
100 supervisor.Logger(ctx).Errorf("service informer watch error: %v", err)
101 })
102
103 svcController := controller.NewServiceController(nfproxy, s.ClientSet, servicesInformer)
104 ep := controller.NewEndpointSliceController(nfproxy, s.ClientSet, endpointSlicesInformer)
Lorenz Brunb682ba52020-07-08 14:51:36 +0200105 kubeInformerFactory.Start(ctx.Done())
106
107 if err = svcController.Start(ctx.Done()); err != nil {
108 return fmt.Errorf("error running Service controller: %w", err)
109 }
110 if err = ep.Start(ctx.Done()); err != nil {
111 return fmt.Errorf("error running endpoint controller: %w", err)
112 }
113 supervisor.Signal(ctx, supervisor.SignalHealthy)
114 supervisor.Signal(ctx, supervisor.SignalDone)
115 return nil
116}