istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/cmd/pilot-agent/app/cmd.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 app
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net"
    21  	"net/netip"
    22  	"strings"
    23  
    24  	"github.com/spf13/cobra"
    25  
    26  	"istio.io/api/annotation"
    27  	meshconfig "istio.io/api/mesh/v1alpha1"
    28  	"istio.io/istio/pilot/cmd/pilot-agent/config"
    29  	"istio.io/istio/pilot/cmd/pilot-agent/options"
    30  	"istio.io/istio/pilot/cmd/pilot-agent/status"
    31  	"istio.io/istio/pilot/pkg/util/network"
    32  	"istio.io/istio/pkg/bootstrap"
    33  	"istio.io/istio/pkg/cmd"
    34  	"istio.io/istio/pkg/collateral"
    35  	"istio.io/istio/pkg/config/constants"
    36  	"istio.io/istio/pkg/envoy"
    37  	istioagent "istio.io/istio/pkg/istio-agent"
    38  	"istio.io/istio/pkg/log"
    39  	"istio.io/istio/pkg/model"
    40  	"istio.io/istio/pkg/slices"
    41  	"istio.io/istio/pkg/util/protomarshal"
    42  	"istio.io/istio/pkg/util/sets"
    43  	"istio.io/istio/pkg/version"
    44  	cleaniptables "istio.io/istio/tools/istio-clean-iptables/pkg/cmd"
    45  	iptables "istio.io/istio/tools/istio-iptables/pkg/cmd"
    46  	iptableslog "istio.io/istio/tools/istio-iptables/pkg/log"
    47  )
    48  
    49  const (
    50  	localHostIPv4 = "127.0.0.1"
    51  	localHostIPv6 = "::1"
    52  )
    53  
    54  var (
    55  	loggingOptions = log.DefaultOptions()
    56  	proxyArgs      options.ProxyArgs
    57  )
    58  
    59  func NewRootCommand(sds istioagent.SDSServiceFactory) *cobra.Command {
    60  	rootCmd := &cobra.Command{
    61  		Use:          "pilot-agent",
    62  		Short:        "Istio Pilot agent.",
    63  		Long:         "Istio Pilot agent runs in the sidecar or gateway container and bootstraps Envoy.",
    64  		SilenceUsage: true,
    65  		FParseErrWhitelist: cobra.FParseErrWhitelist{
    66  			// Allow unknown flags for backward-compatibility.
    67  			UnknownFlags: true,
    68  		},
    69  	}
    70  
    71  	// Attach the Istio logging options to the command.
    72  	loggingOptions.AttachCobraFlags(rootCmd)
    73  
    74  	cmd.AddFlags(rootCmd)
    75  
    76  	proxyCmd := newProxyCommand(sds)
    77  	addFlags(proxyCmd)
    78  	rootCmd.AddCommand(proxyCmd)
    79  	rootCmd.AddCommand(requestCmd)
    80  	rootCmd.AddCommand(waitCmd)
    81  	rootCmd.AddCommand(version.CobraCommand())
    82  	rootCmd.AddCommand(iptables.GetCommand(loggingOptions))
    83  	rootCmd.AddCommand(cleaniptables.GetCommand(loggingOptions))
    84  
    85  	rootCmd.AddCommand(collateral.CobraCommand(rootCmd, collateral.Metadata{
    86  		Title:   "Istio Pilot Agent",
    87  		Section: "pilot-agent CLI",
    88  		Manual:  "Istio Pilot Agent",
    89  	}))
    90  
    91  	return rootCmd
    92  }
    93  
    94  func newProxyCommand(sds istioagent.SDSServiceFactory) *cobra.Command {
    95  	return &cobra.Command{
    96  		Use:   "proxy",
    97  		Short: "XDS proxy agent",
    98  		FParseErrWhitelist: cobra.FParseErrWhitelist{
    99  			// Allow unknown flags for backward-compatibility.
   100  			UnknownFlags: true,
   101  		},
   102  		PersistentPreRunE: configureLogging,
   103  		RunE: func(c *cobra.Command, args []string) error {
   104  			cmd.PrintFlags(c.Flags())
   105  			log.Infof("Version %s", version.Info.String())
   106  
   107  			raiseLimits()
   108  
   109  			err := initProxy(args)
   110  			if err != nil {
   111  				return err
   112  			}
   113  			proxyConfig, err := config.ConstructProxyConfig(proxyArgs.MeshConfigFile, proxyArgs.ServiceCluster, options.ProxyConfigEnv, proxyArgs.Concurrency)
   114  			if err != nil {
   115  				return fmt.Errorf("failed to get proxy config: %v", err)
   116  			}
   117  			if out, err := protomarshal.ToYAML(proxyConfig); err != nil {
   118  				log.Infof("Failed to serialize to YAML: %v", err)
   119  			} else {
   120  				log.Infof("Effective config: %s", out)
   121  			}
   122  
   123  			secOpts, err := options.NewSecurityOptions(proxyConfig, proxyArgs.StsPort, proxyArgs.TokenManagerPlugin)
   124  			if err != nil {
   125  				return err
   126  			}
   127  
   128  			// If we are using a custom template file (for control plane proxy, for example), configure this.
   129  			if proxyArgs.TemplateFile != "" && proxyConfig.CustomConfigFile == "" {
   130  				proxyConfig.ProxyBootstrapTemplatePath = proxyArgs.TemplateFile
   131  			}
   132  
   133  			envoyOptions := envoy.ProxyConfig{
   134  				LogLevel:          proxyArgs.ProxyLogLevel,
   135  				ComponentLogLevel: proxyArgs.ProxyComponentLogLevel,
   136  				LogAsJSON:         loggingOptions.JSONEncoding,
   137  				NodeIPs:           proxyArgs.IPAddresses,
   138  				Sidecar:           proxyArgs.Type == model.SidecarProxy,
   139  				OutlierLogPath:    proxyArgs.OutlierLogPath,
   140  			}
   141  			agentOptions := options.NewAgentOptions(&proxyArgs, proxyConfig, sds)
   142  			agent := istioagent.NewAgent(proxyConfig, agentOptions, secOpts, envoyOptions)
   143  			ctx, cancel := context.WithCancel(context.Background())
   144  			defer cancel()
   145  			defer agent.Close()
   146  
   147  			// If a status port was provided, start handling status probes.
   148  			if proxyConfig.StatusPort > 0 {
   149  				if err := initStatusServer(ctx, proxyConfig,
   150  					agentOptions.EnvoyPrometheusPort, proxyArgs.EnableProfiling, agent, cancel); err != nil {
   151  					return err
   152  				}
   153  			}
   154  
   155  			go iptableslog.ReadNFLOGSocket(ctx)
   156  
   157  			// On SIGINT or SIGTERM, cancel the context, triggering a graceful shutdown
   158  			go cmd.WaitSignalFunc(cancel)
   159  
   160  			// Start in process SDS, dns server, xds proxy, and Envoy.
   161  			wait, err := agent.Run(ctx)
   162  			if err != nil {
   163  				return err
   164  			}
   165  			wait()
   166  			return nil
   167  		},
   168  	}
   169  }
   170  
   171  func addFlags(proxyCmd *cobra.Command) {
   172  	proxyArgs = options.NewProxyArgs()
   173  	proxyCmd.PersistentFlags().StringVar(&proxyArgs.DNSDomain, "domain", "",
   174  		"DNS domain suffix. If not provided uses ${POD_NAMESPACE}.svc.cluster.local")
   175  	proxyCmd.PersistentFlags().StringVar(&proxyArgs.MeshConfigFile, "meshConfig", "./etc/istio/config/mesh",
   176  		"File name for Istio mesh configuration. If not specified, a default mesh will be used. This may be overridden by "+
   177  			"PROXY_CONFIG environment variable or proxy.istio.io/config annotation.")
   178  	proxyCmd.PersistentFlags().IntVar(&proxyArgs.StsPort, "stsPort", 0,
   179  		"HTTP Port on which to serve Security Token Service (STS). If zero, STS service will not be provided.")
   180  	proxyCmd.PersistentFlags().StringVar(&proxyArgs.TokenManagerPlugin, "tokenManagerPlugin", "",
   181  		"Token provider specific plugin name.")
   182  	// DEPRECATED. Flags for proxy configuration
   183  	proxyCmd.PersistentFlags().StringVar(&proxyArgs.ServiceCluster, "serviceCluster", constants.ServiceClusterName, "Service cluster")
   184  	// Log levels are provided by the library https://github.com/gabime/spdlog, used by Envoy.
   185  	proxyCmd.PersistentFlags().StringVar(&proxyArgs.ProxyLogLevel, "proxyLogLevel", "warning,misc:error",
   186  		fmt.Sprintf("The log level used to start the Envoy proxy (choose from {%s, %s, %s, %s, %s, %s, %s})."+
   187  			"Level may also include one or more scopes, such as 'info,misc:error,upstream:debug'",
   188  			"trace", "debug", "info", "warning", "error", "critical", "off"))
   189  	proxyCmd.PersistentFlags().IntVar(&proxyArgs.Concurrency, "concurrency", 0, "number of worker threads to run")
   190  	// See https://www.envoyproxy.io/docs/envoy/latest/operations/cli#cmdoption-component-log-level
   191  	proxyCmd.PersistentFlags().StringVar(&proxyArgs.ProxyComponentLogLevel, "proxyComponentLogLevel", "",
   192  		"The component log level used to start the Envoy proxy. Deprecated, use proxyLogLevel instead")
   193  	proxyCmd.PersistentFlags().StringVar(&proxyArgs.TemplateFile, "templateFile", "",
   194  		"Go template bootstrap config")
   195  	proxyCmd.PersistentFlags().StringVar(&proxyArgs.OutlierLogPath, "outlierLogPath", "",
   196  		"The log path for outlier detection")
   197  	proxyCmd.PersistentFlags().BoolVar(&proxyArgs.EnableProfiling, "profiling", true,
   198  		"Enable profiling via web interface host:port/debug/pprof/.")
   199  }
   200  
   201  func initStatusServer(
   202  	ctx context.Context,
   203  	proxyConfig *meshconfig.ProxyConfig,
   204  	envoyPrometheusPort int,
   205  	enableProfiling bool,
   206  	agent *istioagent.Agent,
   207  	shutdown context.CancelFunc,
   208  ) error {
   209  	o := options.NewStatusServerOptions(proxyArgs.IsIPv6(), proxyArgs.Type, proxyConfig, agent)
   210  	o.EnvoyPrometheusPort = envoyPrometheusPort
   211  	o.EnableProfiling = enableProfiling
   212  	o.Context = ctx
   213  	o.Shutdown = shutdown
   214  	statusServer, err := status.NewServer(*o)
   215  	if err != nil {
   216  		return err
   217  	}
   218  	go statusServer.Run(ctx)
   219  	return nil
   220  }
   221  
   222  func getDNSDomain(podNamespace, domain string) string {
   223  	if len(domain) == 0 {
   224  		domain = podNamespace + ".svc." + constants.DefaultClusterLocalDomain
   225  	}
   226  	return domain
   227  }
   228  
   229  func configureLogging(_ *cobra.Command, _ []string) error {
   230  	if err := log.Configure(loggingOptions); err != nil {
   231  		return err
   232  	}
   233  	return nil
   234  }
   235  
   236  func initProxy(args []string) error {
   237  	proxyArgs.Type = model.SidecarProxy
   238  	if len(args) > 0 {
   239  		proxyArgs.Type = model.NodeType(args[0])
   240  		if !model.IsApplicationNodeType(proxyArgs.Type) {
   241  			return fmt.Errorf("Invalid proxy Type: " + string(proxyArgs.Type))
   242  		}
   243  	}
   244  
   245  	podIP, _ := netip.ParseAddr(options.InstanceIPVar.Get()) // protobuf encoding of IP_ADDRESS type
   246  	if podIP.IsValid() {
   247  		// The first one must be the pod ip as we pick the first ip as pod ip in istiod.
   248  		proxyArgs.IPAddresses = []string{podIP.String()}
   249  	}
   250  
   251  	// Obtain all the IPs from the node
   252  	proxyAddrs := make([]string, 0)
   253  	if ipAddrs, ok := network.GetPrivateIPs(context.Background()); ok {
   254  		proxyAddrs = append(proxyAddrs, ipAddrs...)
   255  	}
   256  
   257  	// No IP addresses provided, append 127.0.0.1 for ipv4 and ::1 for ipv6
   258  	if len(proxyAddrs) == 0 {
   259  		proxyAddrs = append(proxyAddrs, localHostIPv4, localHostIPv6)
   260  	}
   261  
   262  	// Get exclusions from traffic.sidecar.istio.io/excludeInterfaces
   263  	excludeAddrs := getExcludeInterfaces()
   264  	excludeAddrs.InsertAll(proxyArgs.IPAddresses...) // prevent duplicate IPs
   265  	proxyAddrs = slices.FilterInPlace(proxyAddrs, func(s string) bool {
   266  		return !excludeAddrs.Contains(s)
   267  	})
   268  
   269  	proxyArgs.IPAddresses = append(proxyArgs.IPAddresses, proxyAddrs...)
   270  	log.Debugf("proxy IPAddresses: %v", proxyArgs.IPAddresses)
   271  
   272  	// After IP addresses are set, let us discover IPMode.
   273  	proxyArgs.DiscoverIPMode()
   274  
   275  	// Extract pod variables.
   276  	proxyArgs.ID = proxyArgs.PodName + "." + proxyArgs.PodNamespace
   277  
   278  	// If not set, set a default based on platform - podNamespace.svc.cluster.local for
   279  	// K8S
   280  	proxyArgs.DNSDomain = getDNSDomain(proxyArgs.PodNamespace, proxyArgs.DNSDomain)
   281  	log.WithLabels("ips", proxyArgs.IPAddresses, "type", proxyArgs.Type, "id", proxyArgs.ID, "domain", proxyArgs.DNSDomain).Info("Proxy role")
   282  
   283  	return nil
   284  }
   285  
   286  func getExcludeInterfaces() sets.String {
   287  	excludeAddrs := sets.New[string]()
   288  
   289  	// Get list of excluded interfaces from pod annotation
   290  	// TODO: Discuss other input methods such as env, flag (ssuvasanth)
   291  	annotations, err := bootstrap.ReadPodAnnotations("")
   292  	if err != nil {
   293  		log.Debugf("Reading podInfoAnnotations file to get excludeInterfaces was unsuccessful. Continuing without exclusions. msg: %v", err)
   294  		return excludeAddrs
   295  	}
   296  	value, ok := annotations[annotation.SidecarTrafficExcludeInterfaces.Name]
   297  	if !ok {
   298  		log.Debugf("%s annotation is not present", annotation.SidecarTrafficExcludeInterfaces.Name)
   299  		return excludeAddrs
   300  	}
   301  	exclusions := strings.Split(value, ",")
   302  
   303  	// Find IP addr of excluded interfaces and add to a map for instant lookup
   304  	for _, ifaceName := range exclusions {
   305  		iface, err := net.InterfaceByName(ifaceName)
   306  		if err != nil {
   307  			log.Warnf("Unable to get interface %s: %v", ifaceName, err)
   308  			continue
   309  		}
   310  		addrs, err := iface.Addrs()
   311  		if err != nil {
   312  			log.Warnf("Unable to get IP addr(s) of interface %s: %v", ifaceName, err)
   313  			continue
   314  		}
   315  
   316  		for _, addr := range addrs {
   317  			// Get IP only
   318  			var ip net.IP
   319  			switch v := addr.(type) {
   320  			case *net.IPNet:
   321  				ip = v.IP
   322  			case *net.IPAddr:
   323  				ip = v.IP
   324  			default:
   325  				continue
   326  			}
   327  
   328  			// handling ipv4 wrapping in ipv6
   329  			ipAddr, okay := netip.AddrFromSlice(ip)
   330  			if !okay {
   331  				continue
   332  			}
   333  			unwrapAddr := ipAddr.Unmap()
   334  			if !unwrapAddr.IsValid() || unwrapAddr.IsLoopback() || unwrapAddr.IsLinkLocalUnicast() || unwrapAddr.IsLinkLocalMulticast() || unwrapAddr.IsUnspecified() {
   335  				continue
   336  			}
   337  
   338  			// Add to map
   339  			excludeAddrs.Insert(unwrapAddr.String())
   340  		}
   341  	}
   342  
   343  	log.Infof("Exclude IPs %v based on %s annotation", excludeAddrs, annotation.SidecarTrafficExcludeInterfaces.Name)
   344  	return excludeAddrs
   345  }
   346  
   347  func raiseLimits() {
   348  	limit, err := RaiseFileLimits()
   349  	if err != nil {
   350  		log.Warnf("failed setting file limit: %v", err)
   351  	} else {
   352  		log.Infof("Set max file descriptors (ulimit -n) to: %d", limit)
   353  	}
   354  }