istio.io/istio@v0.0.0-20240520182934-d79c90f27776/cni/pkg/cmd/root.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cmd
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"os"
    22  	"strings"
    23  
    24  	"github.com/spf13/cobra"
    25  	"github.com/spf13/viper"
    26  
    27  	"istio.io/istio/cni/pkg/config"
    28  	"istio.io/istio/cni/pkg/constants"
    29  	"istio.io/istio/cni/pkg/install"
    30  	udsLog "istio.io/istio/cni/pkg/log"
    31  	"istio.io/istio/cni/pkg/monitoring"
    32  	"istio.io/istio/cni/pkg/nodeagent"
    33  	"istio.io/istio/cni/pkg/repair"
    34  	"istio.io/istio/pkg/collateral"
    35  	"istio.io/istio/pkg/ctrlz"
    36  	"istio.io/istio/pkg/env"
    37  	"istio.io/istio/pkg/log"
    38  	"istio.io/istio/pkg/version"
    39  	iptables "istio.io/istio/tools/istio-iptables/pkg/constants"
    40  )
    41  
    42  var (
    43  	logOptions   = log.DefaultOptions()
    44  	ctrlzOptions = func() *ctrlz.Options {
    45  		o := ctrlz.DefaultOptions()
    46  		o.EnablePprof = true
    47  		return o
    48  	}()
    49  )
    50  
    51  var rootCmd = &cobra.Command{
    52  	Use:          "install-cni",
    53  	Short:        "Install and configure Istio CNI plugin on a node, detect and repair pod which is broken by race condition.",
    54  	SilenceUsage: true,
    55  	PreRunE: func(c *cobra.Command, args []string) error {
    56  		if err := log.Configure(logOptions); err != nil {
    57  			log.Errorf("Failed to configure log %v", err)
    58  		}
    59  		return nil
    60  	},
    61  	RunE: func(c *cobra.Command, args []string) (err error) {
    62  		ctx := c.Context()
    63  
    64  		// Start controlz server
    65  		_, _ = ctrlz.Run(ctrlzOptions, nil)
    66  
    67  		var cfg *config.Config
    68  		if cfg, err = constructConfig(); err != nil {
    69  			return
    70  		}
    71  		log.Infof("CNI install configuration: \n%+v", cfg.InstallConfig)
    72  		log.Infof("CNI race repair configuration: \n%+v", cfg.RepairConfig)
    73  
    74  		// Start metrics server
    75  		monitoring.SetupMonitoring(cfg.InstallConfig.MonitoringPort, "/metrics", ctx.Done())
    76  
    77  		// Start UDS log server
    78  		udsLogger := udsLog.NewUDSLogger()
    79  		if err = udsLogger.StartUDSLogServer(cfg.InstallConfig.LogUDSAddress, ctx.Done()); err != nil {
    80  			log.Errorf("Failed to start up UDS Log Server: %v", err)
    81  			return
    82  		}
    83  
    84  		// Creates a basic health endpoint server that reports health status
    85  		// based on atomic flag, as set by installer
    86  		// TODO nodeagent watch server should affect this too, and drop atomic flag
    87  		installDaemonReady, watchServerReady := nodeagent.StartHealthServer()
    88  
    89  		if cfg.InstallConfig.AmbientEnabled {
    90  			// Start ambient controller
    91  
    92  			// node agent will spawn a goroutine and watch the K8S API for events,
    93  			// as well as listen for messages from the CNI binary.
    94  			log.Info("Starting ambient node agent with inpod redirect mode")
    95  			ambientAgent, err := nodeagent.NewServer(ctx, watchServerReady, cfg.InstallConfig.CNIEventAddress,
    96  				nodeagent.AmbientArgs{
    97  					SystemNamespace: nodeagent.SystemNamespace,
    98  					Revision:        nodeagent.Revision,
    99  					ServerSocket:    cfg.InstallConfig.ZtunnelUDSAddress,
   100  					DNSCapture:      cfg.InstallConfig.AmbientDNSCapture,
   101  					EnableIPv6:      cfg.InstallConfig.AmbientIPv6,
   102  				})
   103  			if err != nil {
   104  				return fmt.Errorf("failed to create ambient nodeagent service: %v", err)
   105  			}
   106  
   107  			ambientAgent.Start()
   108  			defer ambientAgent.Stop()
   109  
   110  			log.Info("Ambient node agent started, starting installer...")
   111  
   112  		} else {
   113  			// Ambient not enabled, so this readiness flag is no-op'd
   114  			watchServerReady.Store(true)
   115  		}
   116  
   117  		installer := install.NewInstaller(&cfg.InstallConfig, installDaemonReady)
   118  
   119  		repair.StartRepair(ctx, cfg.RepairConfig)
   120  
   121  		log.Info("Installer created, watching node CNI dir")
   122  		// installer.Run() will block indefinitely, and attempt to permanently "keep"
   123  		// the CNI binary installed.
   124  		if err = installer.Run(ctx); err != nil {
   125  			if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
   126  				log.Infof("installer complete: %v", err)
   127  				// Error was caused by interrupt/termination signal
   128  				err = nil
   129  			} else {
   130  				log.Errorf("installer failed: %v", err)
   131  			}
   132  		}
   133  
   134  		if cleanErr := installer.Cleanup(); cleanErr != nil {
   135  			if err != nil {
   136  				err = fmt.Errorf("%s: %w", cleanErr.Error(), err)
   137  			} else {
   138  				err = cleanErr
   139  			}
   140  		}
   141  
   142  		return
   143  	},
   144  }
   145  
   146  // GetCommand returns the main cobra.Command object for this application
   147  func GetCommand() *cobra.Command {
   148  	return rootCmd
   149  }
   150  
   151  func init() {
   152  	viper.AutomaticEnv()
   153  	viper.AllowEmptyEnv(true)
   154  	viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
   155  	logOptions.AttachCobraFlags(rootCmd)
   156  	ctrlzOptions.AttachCobraFlags(rootCmd)
   157  
   158  	rootCmd.AddCommand(version.CobraCommand())
   159  	rootCmd.AddCommand(collateral.CobraCommand(rootCmd, collateral.Metadata{
   160  		Title:   "Istio CNI Plugin Installer",
   161  		Section: "install-cni CLI",
   162  		Manual:  "Istio CNI Plugin Installer",
   163  	}))
   164  
   165  	registerStringParameter(constants.CNINetDir, "/etc/cni/net.d", "Directory on the host where CNI network plugins are installed")
   166  	registerStringParameter(constants.CNIConfName, "", "Name of the CNI configuration file")
   167  	registerBooleanParameter(constants.ChainedCNIPlugin, true, "Whether to install CNI plugin as a chained or standalone")
   168  	registerStringParameter(constants.CNINetworkConfig, "", "CNI configuration template as a string")
   169  	registerStringParameter(constants.LogLevel, "warn", "Fallback value for log level in CNI config file, if not specified in helm template")
   170  
   171  	// Not configurable in CNI helm charts
   172  	registerStringParameter(constants.MountedCNINetDir, "/host/etc/cni/net.d", "Directory on the container where CNI networks are installed")
   173  	registerStringParameter(constants.CNINetworkConfigFile, "", "CNI config template as a file")
   174  	registerStringParameter(constants.KubeconfigFilename, "ZZZ-istio-cni-kubeconfig",
   175  		"Name of the kubeconfig file which CNI plugin will use when interacting with API server")
   176  	registerIntegerParameter(constants.KubeconfigMode, constants.DefaultKubeconfigMode, "File mode of the kubeconfig file")
   177  	registerStringParameter(constants.KubeCAFile, "", "CA file for kubeconfig. Defaults to the same as install-cni pod")
   178  	registerBooleanParameter(constants.SkipTLSVerify, false, "Whether to use insecure TLS in kubeconfig file")
   179  	registerIntegerParameter(constants.MonitoringPort, 15014, "HTTP port to serve prometheus metrics")
   180  	registerStringParameter(constants.LogUDSAddress, "/var/run/istio-cni/log.sock", "The UDS server address which CNI plugin will copy log output to")
   181  	registerStringParameter(constants.CNIEventAddress, "/var/run/istio-cni/pluginevent.sock",
   182  		"The UDS server address which CNI plugin will forward ambient pod creation events to")
   183  	registerStringParameter(constants.ZtunnelUDSAddress, "/var/run/ztunnel/ztunnel.sock", "The UDS server address which ztunnel will connect to")
   184  	registerBooleanParameter(constants.AmbientEnabled, false, "Whether ambient controller is enabled")
   185  	// Repair
   186  	registerBooleanParameter(constants.RepairEnabled, true, "Whether to enable race condition repair or not")
   187  	registerBooleanParameter(constants.RepairDeletePods, false, "Controller will delete pods when detecting pod broken by race condition")
   188  	registerBooleanParameter(constants.RepairLabelPods, false, "Controller will label pods when detecting pod broken by race condition")
   189  	registerStringParameter(constants.RepairLabelKey, "cni.istio.io/uninitialized",
   190  		"The key portion of the label which will be set by the race repair if label pods is true")
   191  	registerStringParameter(constants.RepairLabelValue, "true",
   192  		"The value portion of the label which will be set by the race repair if label pods is true")
   193  	registerStringParameter(constants.RepairNodeName, "", "The name of the managed node (will manage all nodes if unset)")
   194  	registerStringParameter(constants.RepairSidecarAnnotation, "sidecar.istio.io/status",
   195  		"An annotation key that indicates this pod contains an istio sidecar. All pods without this annotation will be ignored."+
   196  			"The value of the annotation is ignored.")
   197  	registerStringParameter(constants.RepairInitContainerName, "istio-validation",
   198  		"The name of the istio init container (will crash-loop if CNI is not configured for the pod)")
   199  	registerStringParameter(constants.RepairInitTerminationMsg, "",
   200  		"The expected termination message for the init container when crash-looping because of CNI misconfiguration")
   201  	registerIntegerParameter(constants.RepairInitExitCode, iptables.ValidationErrorCode,
   202  		"Expected exit code for the init container when crash-looping because of CNI misconfiguration")
   203  	registerStringParameter(constants.RepairLabelSelectors, "",
   204  		"A set of label selectors in label=value format that will be added to the pod list filters")
   205  	registerStringParameter(constants.RepairFieldSelectors, "",
   206  		"A set of field selectors in label=value format that will be added to the pod list filters")
   207  }
   208  
   209  func registerStringParameter(name, value, usage string) {
   210  	rootCmd.Flags().String(name, value, usage)
   211  	registerEnvironment(name, value, usage)
   212  }
   213  
   214  func registerIntegerParameter(name string, value int, usage string) {
   215  	rootCmd.Flags().Int(name, value, usage)
   216  	registerEnvironment(name, value, usage)
   217  }
   218  
   219  func registerBooleanParameter(name string, value bool, usage string) {
   220  	rootCmd.Flags().Bool(name, value, usage)
   221  	registerEnvironment(name, value, usage)
   222  }
   223  
   224  func registerEnvironment[T env.Parseable](name string, defaultValue T, usage string) {
   225  	envName := strings.Replace(strings.ToUpper(name), "-", "_", -1)
   226  	// Note: we do not rely on istio env package to retrieve configuration. We relies on viper.
   227  	// This is just to make sure the reference doc tool can generate doc with these vars as env variable at istio.io.
   228  	env.Register(envName, defaultValue, usage)
   229  	bindViper(name)
   230  }
   231  
   232  func bindViper(name string) {
   233  	if err := viper.BindPFlag(name, rootCmd.Flags().Lookup(name)); err != nil {
   234  		log.Error(err)
   235  		os.Exit(1)
   236  	}
   237  }
   238  
   239  func constructConfig() (*config.Config, error) {
   240  	installCfg := config.InstallConfig{
   241  		CNINetDir:        viper.GetString(constants.CNINetDir),
   242  		MountedCNINetDir: viper.GetString(constants.MountedCNINetDir),
   243  		CNIConfName:      viper.GetString(constants.CNIConfName),
   244  		ChainedCNIPlugin: viper.GetBool(constants.ChainedCNIPlugin),
   245  
   246  		LogLevel:              viper.GetString(constants.LogLevel),
   247  		KubeconfigFilename:    viper.GetString(constants.KubeconfigFilename),
   248  		KubeconfigMode:        viper.GetInt(constants.KubeconfigMode),
   249  		KubeCAFile:            viper.GetString(constants.KubeCAFile),
   250  		SkipTLSVerify:         viper.GetBool(constants.SkipTLSVerify),
   251  		K8sServiceProtocol:    os.Getenv("KUBERNETES_SERVICE_PROTOCOL"),
   252  		K8sServiceHost:        os.Getenv("KUBERNETES_SERVICE_HOST"),
   253  		K8sServicePort:        os.Getenv("KUBERNETES_SERVICE_PORT"),
   254  		K8sNodeName:           os.Getenv("KUBERNETES_NODE_NAME"),
   255  		K8sServiceAccountPath: constants.ServiceAccountPath,
   256  
   257  		CNIBinSourceDir:  constants.CNIBinDir,
   258  		CNIBinTargetDirs: []string{constants.HostCNIBinDir},
   259  		MonitoringPort:   viper.GetInt(constants.MonitoringPort),
   260  		LogUDSAddress:    viper.GetString(constants.LogUDSAddress),
   261  		CNIEventAddress:  viper.GetString(constants.CNIEventAddress),
   262  
   263  		ExcludeNamespaces: viper.GetString(constants.ExcludeNamespaces),
   264  		ZtunnelUDSAddress: viper.GetString(constants.ZtunnelUDSAddress),
   265  
   266  		AmbientEnabled:    viper.GetBool(constants.AmbientEnabled),
   267  		AmbientDNSCapture: viper.GetBool(constants.AmbientDNSCapture),
   268  		AmbientIPv6:       viper.GetBool(constants.AmbientIPv6),
   269  	}
   270  
   271  	if len(installCfg.K8sNodeName) == 0 {
   272  		var err error
   273  		installCfg.K8sNodeName, err = os.Hostname()
   274  		if err != nil {
   275  			return nil, err
   276  		}
   277  	}
   278  
   279  	repairCfg := config.RepairConfig{
   280  		Enabled:            viper.GetBool(constants.RepairEnabled),
   281  		RepairPods:         viper.GetBool(constants.RepairRepairPods),
   282  		DeletePods:         viper.GetBool(constants.RepairDeletePods),
   283  		LabelPods:          viper.GetBool(constants.RepairLabelPods),
   284  		LabelKey:           viper.GetString(constants.RepairLabelKey),
   285  		LabelValue:         viper.GetString(constants.RepairLabelValue),
   286  		NodeName:           viper.GetString(constants.RepairNodeName),
   287  		SidecarAnnotation:  viper.GetString(constants.RepairSidecarAnnotation),
   288  		InitContainerName:  viper.GetString(constants.RepairInitContainerName),
   289  		InitTerminationMsg: viper.GetString(constants.RepairInitTerminationMsg),
   290  		InitExitCode:       viper.GetInt(constants.RepairInitExitCode),
   291  		LabelSelectors:     viper.GetString(constants.RepairLabelSelectors),
   292  		FieldSelectors:     viper.GetString(constants.RepairFieldSelectors),
   293  	}
   294  
   295  	return &config.Config{InstallConfig: installCfg, RepairConfig: repairCfg}, nil
   296  }