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 }