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 }