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