blob: 5fcc5b52b6487bb1a72f09834f101e80a6e91ec3 [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"
40 "k8s.io/client-go/tools/record"
Serge Bazanski77cb6c52020-12-19 00:09:22 +010041
Serge Bazanski31370b02021-01-07 16:31:14 +010042 "source.monogon.dev/metropolis/pkg/supervisor"
Lorenz Brunb682ba52020-07-08 14:51:36 +020043)
44
45type Service struct {
Serge Bazanski216fe7b2021-05-21 18:36:16 +020046 // Traffic in ClusterCIDR is assumed to be originated inside the cluster
47 // and will not be SNATed
Lorenz Brunb682ba52020-07-08 14:51:36 +020048 ClusterCIDR net.IPNet
49 // A Kubernetes ClientSet with read access to endpoints and services
50 ClientSet kubernetes.Interface
51}
52
53func (s *Service) Run(ctx context.Context) error {
54 var ipv4ClusterCIDR string
55 var ipv6ClusterCIDR string
56 if s.ClusterCIDR.IP.To4() == nil && s.ClusterCIDR.IP.To16() != nil {
57 ipv6ClusterCIDR = s.ClusterCIDR.String()
58 } else if s.ClusterCIDR.IP.To4() != nil {
59 ipv4ClusterCIDR = s.ClusterCIDR.String()
60 } else {
61 return errors.New("invalid ClusterCIDR")
62 }
63 nfti, err := nftables.InitNFTables(ipv4ClusterCIDR, ipv6ClusterCIDR)
64 if err != nil {
65 return fmt.Errorf("failed to initialize nftables with error: %w", err)
66 }
67
68 // Create event recorder to report events into K8s
69 hostname, err := os.Hostname()
70 if err != nil {
71 return fmt.Errorf("failed to get local host name with error: %w", err)
72 }
73 eventBroadcaster := record.NewBroadcaster()
74 recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "nfproxy", Host: hostname})
75
76 // Create new proxy controller with endpoint slices enabled
77 // https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/
78 nfproxy := proxy.NewProxy(nfti, hostname, recorder, true)
79
80 // Create special informer which doesn't track headless services
81 noHeadlessEndpoints, err := labels.NewRequirement(v1.IsHeadlessService, selection.DoesNotExist, nil)
82 if err != nil {
83 return fmt.Errorf("failed to create Requirement for noHeadlessEndpoints: %w", err)
84 }
85 labelSelector := labels.NewSelector()
86 labelSelector = labelSelector.Add(*noHeadlessEndpoints)
87
88 kubeInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(s.ClientSet, time.Minute*5,
89 kubeinformers.WithTweakListOptions(func(options *metav1.ListOptions) {
90 options.LabelSelector = labelSelector.String()
91 }))
92
93 svcController := controller.NewServiceController(nfproxy, s.ClientSet, kubeInformerFactory.Core().V1().Services())
94 ep := controller.NewEndpointSliceController(nfproxy, s.ClientSet, kubeInformerFactory.Discovery().V1beta1().EndpointSlices())
95 kubeInformerFactory.Start(ctx.Done())
96
97 if err = svcController.Start(ctx.Done()); err != nil {
98 return fmt.Errorf("error running Service controller: %w", err)
99 }
100 if err = ep.Start(ctx.Done()); err != nil {
101 return fmt.Errorf("error running endpoint controller: %w", err)
102 }
103 supervisor.Signal(ctx, supervisor.SignalHealthy)
104 supervisor.Signal(ctx, supervisor.SignalDone)
105 return nil
106}