k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kubemark/app/hollow_node.go (about)

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package app
    18  
    19  import (
    20  	"context"
    21  	goflag "flag"
    22  	"fmt"
    23  	"time"
    24  
    25  	"github.com/spf13/cobra"
    26  	"github.com/spf13/pflag"
    27  	"go.opentelemetry.io/otel/trace/noop"
    28  	internalapi "k8s.io/cri-api/pkg/apis"
    29  	"k8s.io/klog/v2"
    30  
    31  	v1 "k8s.io/api/core/v1"
    32  	"k8s.io/apimachinery/pkg/api/resource"
    33  	"k8s.io/apimachinery/pkg/types"
    34  	"k8s.io/apimachinery/pkg/util/sets"
    35  	clientset "k8s.io/client-go/kubernetes"
    36  	restclient "k8s.io/client-go/rest"
    37  	"k8s.io/client-go/tools/clientcmd"
    38  	"k8s.io/client-go/tools/events"
    39  	cliflag "k8s.io/component-base/cli/flag"
    40  	_ "k8s.io/component-base/metrics/prometheus/restclient" // for client metric registration
    41  	_ "k8s.io/component-base/metrics/prometheus/version"    // for version metric registration
    42  	"k8s.io/component-base/version"
    43  	"k8s.io/component-base/version/verflag"
    44  	remote "k8s.io/cri-client/pkg"
    45  	fakeremote "k8s.io/cri-client/pkg/fake"
    46  	"k8s.io/kubernetes/pkg/api/legacyscheme"
    47  	"k8s.io/kubernetes/pkg/cluster/ports"
    48  	cadvisortest "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing"
    49  	"k8s.io/kubernetes/pkg/kubelet/certificate/bootstrap"
    50  	"k8s.io/kubernetes/pkg/kubelet/cm"
    51  	"k8s.io/kubernetes/pkg/kubemark"
    52  	kubemarkproxy "k8s.io/kubernetes/pkg/proxy/kubemark"
    53  	utilflag "k8s.io/kubernetes/pkg/util/flag"
    54  )
    55  
    56  type hollowNodeConfig struct {
    57  	KubeconfigPath          string
    58  	BootstrapKubeconfigPath string
    59  	CertDirectory           string
    60  	KubeletPort             int
    61  	KubeletReadOnlyPort     int
    62  	Morph                   string
    63  	NodeName                string
    64  	ServerPort              int
    65  	ContentType             string
    66  	QPS                     float32
    67  	Burst                   int
    68  	NodeLabels              map[string]string
    69  	RegisterWithTaints      []v1.Taint
    70  	MaxPods                 int
    71  	ExtendedResources       map[string]string
    72  	UseHostImageService     bool
    73  
    74  	// Deprecated config; remove these with the corresponding flags
    75  	UseRealProxier       bool
    76  	ProxierSyncPeriod    time.Duration
    77  	ProxierMinSyncPeriod time.Duration
    78  }
    79  
    80  const (
    81  	maxPods     = 110
    82  	podsPerCore = 0
    83  )
    84  
    85  // TODO(#45650): Refactor hollow-node into hollow-kubelet and hollow-proxy
    86  // and make the config driven.
    87  var knownMorphs = sets.NewString("kubelet", "proxy")
    88  
    89  func (c *hollowNodeConfig) addFlags(fs *pflag.FlagSet) {
    90  	fs.StringVar(&c.KubeconfigPath, "kubeconfig", "/kubeconfig/kubeconfig", "Path to kubeconfig file.")
    91  	fs.StringVar(&c.BootstrapKubeconfigPath, "bootstrap-kubeconfig", "", "Path to bootstrap kubeconfig file.")
    92  	fs.StringVar(&c.CertDirectory, "cert-dir", "/etc/srv/", "Path to cert directory for bootstraping.")
    93  	fs.IntVar(&c.KubeletPort, "kubelet-port", ports.KubeletPort, "Port on which HollowKubelet should be listening.")
    94  	fs.IntVar(&c.KubeletReadOnlyPort, "kubelet-read-only-port", ports.KubeletReadOnlyPort, "Read-only port on which Kubelet is listening.")
    95  	fs.StringVar(&c.NodeName, "name", "fake-node", "Name of this Hollow Node.")
    96  	fs.IntVar(&c.ServerPort, "api-server-port", 443, "Port on which API server is listening.")
    97  	fs.StringVar(&c.Morph, "morph", "", fmt.Sprintf("Specifies into which Hollow component this binary should morph. Allowed values: %v", knownMorphs.List()))
    98  	fs.StringVar(&c.ContentType, "kube-api-content-type", "application/vnd.kubernetes.protobuf", "ContentType of requests sent to apiserver.")
    99  	fs.Float32Var(&c.QPS, "kube-api-qps", 10, "QPS indicates the maximum QPS to the apiserver.")
   100  	fs.IntVar(&c.Burst, "kube-api-burst", 20, "Burst indicates maximum burst for throttle to the apiserver.")
   101  
   102  	bindableNodeLabels := cliflag.ConfigurationMap(c.NodeLabels)
   103  	fs.Var(&bindableNodeLabels, "node-labels", "Additional node labels")
   104  	fs.Var(utilflag.RegisterWithTaintsVar{Value: &c.RegisterWithTaints}, "register-with-taints", "Register the node with the given list of taints (comma separated \"<key>=<value>:<effect>\"). No-op if register-node is false.")
   105  	fs.IntVar(&c.MaxPods, "max-pods", maxPods, "Number of pods that can run on this Kubelet.")
   106  	bindableExtendedResources := cliflag.ConfigurationMap(c.ExtendedResources)
   107  	fs.Var(&bindableExtendedResources, "extended-resources", "Register the node with extended resources (comma separated \"<name>=<quantity>\")")
   108  	fs.BoolVar(&c.UseHostImageService, "use-host-image-service", true, "Set to true if the hollow-kubelet should use the host image service. If set to false the fake image service will be used")
   109  
   110  	fs.BoolVar(&c.UseRealProxier, "use-real-proxier", true, "Has no effect.")
   111  	_ = fs.MarkDeprecated("use-real-proxier", "This flag is deprecated and will be removed in a future release.")
   112  	fs.DurationVar(&c.ProxierSyncPeriod, "proxier-sync-period", 30*time.Second, "Has no effect.")
   113  	_ = fs.MarkDeprecated("proxier-sync-period", "This flag is deprecated and will be removed in a future release.")
   114  	fs.DurationVar(&c.ProxierMinSyncPeriod, "proxier-min-sync-period", 0, "Has no effect.")
   115  	_ = fs.MarkDeprecated("proxier-min-sync-period", "This flag is deprecated and will be removed in a future release.")
   116  }
   117  
   118  func (c *hollowNodeConfig) createClientConfigFromFile() (*restclient.Config, error) {
   119  	clientConfig, err := clientcmd.LoadFromFile(c.KubeconfigPath)
   120  	if err != nil {
   121  		return nil, fmt.Errorf("error while loading kubeconfig from file %v: %v", c.KubeconfigPath, err)
   122  	}
   123  	config, err := clientcmd.NewDefaultClientConfig(*clientConfig, &clientcmd.ConfigOverrides{}).ClientConfig()
   124  	if err != nil {
   125  		return nil, fmt.Errorf("error while creating kubeconfig: %v", err)
   126  	}
   127  	config.ContentType = c.ContentType
   128  	config.QPS = c.QPS
   129  	config.Burst = c.Burst
   130  	return config, nil
   131  }
   132  
   133  func (c *hollowNodeConfig) bootstrapClientConfig() error {
   134  	if c.BootstrapKubeconfigPath != "" {
   135  		return bootstrap.LoadClientCert(context.TODO(), c.KubeconfigPath, c.BootstrapKubeconfigPath, c.CertDirectory, types.NodeName(c.NodeName))
   136  	}
   137  	return nil
   138  }
   139  
   140  func (c *hollowNodeConfig) createHollowKubeletOptions() *kubemark.HollowKubeletOptions {
   141  	return &kubemark.HollowKubeletOptions{
   142  		NodeName:            c.NodeName,
   143  		KubeletPort:         c.KubeletPort,
   144  		KubeletReadOnlyPort: c.KubeletReadOnlyPort,
   145  		MaxPods:             c.MaxPods,
   146  		PodsPerCore:         podsPerCore,
   147  		NodeLabels:          c.NodeLabels,
   148  		RegisterWithTaints:  c.RegisterWithTaints,
   149  	}
   150  }
   151  
   152  // NewHollowNodeCommand creates a *cobra.Command object with default parameters
   153  func NewHollowNodeCommand() *cobra.Command {
   154  	s := &hollowNodeConfig{
   155  		NodeLabels:        make(map[string]string),
   156  		ExtendedResources: make(map[string]string),
   157  	}
   158  
   159  	cmd := &cobra.Command{
   160  		Use:  "kubemark",
   161  		Long: "kubemark",
   162  		RunE: func(cmd *cobra.Command, args []string) error {
   163  			verflag.PrintAndExitIfRequested()
   164  			cliflag.PrintFlags(cmd.Flags())
   165  			return run(cmd.Context(), s)
   166  		},
   167  		Args: func(cmd *cobra.Command, args []string) error {
   168  			for _, arg := range args {
   169  				if len(arg) > 0 {
   170  					return fmt.Errorf("%q does not take any arguments, got %q", cmd.CommandPath(), args)
   171  				}
   172  			}
   173  			return nil
   174  		},
   175  	}
   176  
   177  	fs := cmd.Flags()
   178  	fs.AddGoFlagSet(goflag.CommandLine) // for flags like --docker-only
   179  	s.addFlags(fs)
   180  
   181  	return cmd
   182  }
   183  
   184  func run(ctx context.Context, config *hollowNodeConfig) error {
   185  	// To help debugging, immediately log version and print flags.
   186  	klog.Infof("Version: %+v", version.Get())
   187  
   188  	if !knownMorphs.Has(config.Morph) {
   189  		return fmt.Errorf("Unknown morph: %v. allowed values: %v", config.Morph, knownMorphs.List())
   190  	}
   191  
   192  	// create a client to communicate with API server.
   193  	err := config.bootstrapClientConfig()
   194  	if err != nil {
   195  		return fmt.Errorf("Failed to bootstrap, error: %w. Exiting", err)
   196  	}
   197  	clientConfig, err := config.createClientConfigFromFile()
   198  	if err != nil {
   199  		return fmt.Errorf("Failed to create a ClientConfig, error: %w. Exiting", err)
   200  	}
   201  
   202  	if config.Morph == "kubelet" {
   203  		clientConfig.UserAgent = "hollow-kubelet"
   204  		client, err := clientset.NewForConfig(clientConfig)
   205  		if err != nil {
   206  			return fmt.Errorf("Failed to create a ClientSet, error: %w. Exiting", err)
   207  		}
   208  
   209  		f, c := kubemark.GetHollowKubeletConfig(config.createHollowKubeletOptions())
   210  
   211  		heartbeatClientConfig := *clientConfig
   212  		heartbeatClientConfig.Timeout = c.NodeStatusUpdateFrequency.Duration
   213  		// The timeout is the minimum of the lease duration and status update frequency
   214  		leaseTimeout := time.Duration(c.NodeLeaseDurationSeconds) * time.Second
   215  		if heartbeatClientConfig.Timeout > leaseTimeout {
   216  			heartbeatClientConfig.Timeout = leaseTimeout
   217  		}
   218  
   219  		heartbeatClientConfig.QPS = float32(-1)
   220  		heartbeatClient, err := clientset.NewForConfig(&heartbeatClientConfig)
   221  		if err != nil {
   222  			return fmt.Errorf("Failed to create a ClientSet, error: %w. Exiting", err)
   223  		}
   224  
   225  		cadvisorInterface := &cadvisortest.Fake{
   226  			NodeName: config.NodeName,
   227  		}
   228  
   229  		var containerManager cm.ContainerManager
   230  		if config.ExtendedResources != nil {
   231  			extendedResources := v1.ResourceList{}
   232  			for k, v := range config.ExtendedResources {
   233  				extendedResources[v1.ResourceName(k)] = resource.MustParse(v)
   234  			}
   235  
   236  			containerManager = cm.NewStubContainerManagerWithDevicePluginResource(extendedResources)
   237  		} else {
   238  			containerManager = cm.NewStubContainerManager()
   239  		}
   240  
   241  		endpoint, err := fakeremote.GenerateEndpoint()
   242  		if err != nil {
   243  			return fmt.Errorf("Failed to generate fake endpoint, error: %w", err)
   244  		}
   245  		fakeRemoteRuntime := fakeremote.NewFakeRemoteRuntime()
   246  		if err = fakeRemoteRuntime.Start(endpoint); err != nil {
   247  			return fmt.Errorf("Failed to start fake runtime, error: %w", err)
   248  		}
   249  		defer fakeRemoteRuntime.Stop()
   250  		logger := klog.Background()
   251  		runtimeService, err := remote.NewRemoteRuntimeService(endpoint, 15*time.Second, noop.NewTracerProvider(), &logger)
   252  		if err != nil {
   253  			return fmt.Errorf("Failed to init runtime service, error: %w", err)
   254  		}
   255  
   256  		var imageService internalapi.ImageManagerService = fakeRemoteRuntime.ImageService
   257  		if config.UseHostImageService {
   258  			imageService, err = remote.NewRemoteImageService(c.ImageServiceEndpoint, 15*time.Second, noop.NewTracerProvider(), &logger)
   259  			if err != nil {
   260  				return fmt.Errorf("Failed to init image service, error: %w", err)
   261  			}
   262  		}
   263  
   264  		hollowKubelet := kubemark.NewHollowKubelet(
   265  			f, c,
   266  			client,
   267  			heartbeatClient,
   268  			cadvisorInterface,
   269  			imageService,
   270  			runtimeService,
   271  			containerManager,
   272  		)
   273  		hollowKubelet.Run(ctx)
   274  	}
   275  
   276  	if config.Morph == "proxy" {
   277  		clientConfig.UserAgent = "hollow-proxy"
   278  
   279  		client, err := clientset.NewForConfig(clientConfig)
   280  		if err != nil {
   281  			return fmt.Errorf("Failed to create API Server client, error: %w", err)
   282  		}
   283  		eventBroadcaster := events.NewBroadcaster(&events.EventSinkImpl{Interface: client.EventsV1()})
   284  		recorder := eventBroadcaster.NewRecorder(legacyscheme.Scheme, "kube-proxy")
   285  
   286  		hollowProxy := kubemarkproxy.NewHollowProxy(
   287  			config.NodeName,
   288  			client,
   289  			client.CoreV1(),
   290  			eventBroadcaster,
   291  			recorder,
   292  		)
   293  		return hollowProxy.Run()
   294  	}
   295  
   296  	return nil
   297  }