istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/internaldebug/internal-debug.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 internaldebug
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"io"
    21  	"strings"
    22  
    23  	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    24  	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    25  	"github.com/spf13/cobra"
    26  
    27  	"istio.io/istio/istioctl/pkg/cli"
    28  	"istio.io/istio/istioctl/pkg/clioptions"
    29  	"istio.io/istio/istioctl/pkg/multixds"
    30  	"istio.io/istio/istioctl/pkg/util"
    31  	v3 "istio.io/istio/pilot/pkg/xds/v3"
    32  	"istio.io/istio/pkg/kube"
    33  )
    34  
    35  func HandlerForRetrieveDebugList(kubeClient kube.CLIClient,
    36  	centralOpts clioptions.CentralControlPlaneOptions,
    37  	writer io.Writer,
    38  	istioNamespace string,
    39  ) (map[string]*discovery.DiscoveryResponse, error) {
    40  	var namespace, serviceAccount string
    41  	xdsRequest := discovery.DiscoveryRequest{
    42  		ResourceNames: []string{"list"},
    43  		Node: &core.Node{
    44  			Id: "debug~0.0.0.0~istioctl~cluster.local",
    45  		},
    46  		TypeUrl: v3.DebugType,
    47  	}
    48  	xdsResponses, respErr := multixds.AllRequestAndProcessXds(&xdsRequest, centralOpts, istioNamespace,
    49  		namespace, serviceAccount, kubeClient, multixds.DefaultOptions)
    50  	if respErr != nil {
    51  		return xdsResponses, respErr
    52  	}
    53  	_, _ = fmt.Fprint(writer, "error: according to below command list, please check all supported internal debug commands\n")
    54  	return xdsResponses, nil
    55  }
    56  
    57  func HandlerForDebugErrors(kubeClient kube.CLIClient,
    58  	centralOpts *clioptions.CentralControlPlaneOptions,
    59  	writer io.Writer,
    60  	istioNamespace string,
    61  	xdsResponses map[string]*discovery.DiscoveryResponse,
    62  ) (map[string]*discovery.DiscoveryResponse, error) {
    63  	for _, response := range xdsResponses {
    64  		for _, resource := range response.Resources {
    65  			eString := string(resource.Value)
    66  			switch {
    67  			case strings.Contains(eString, "You must provide a proxyID in the query string"):
    68  				return nil, fmt.Errorf(" You must provide a proxyID in the query string, e.g. [%s]",
    69  					"edsz?proxyID=istio-ingressgateway")
    70  
    71  			case strings.Contains(eString, "404 page not found"):
    72  				return HandlerForRetrieveDebugList(kubeClient, *centralOpts, writer, istioNamespace)
    73  
    74  			case strings.Contains(eString, "querystring parameter 'resource' is required"):
    75  				return nil, fmt.Errorf("querystring parameter 'resource' is required, e.g. [%s]",
    76  					"config_distribution?resource=VirtualService/default/bookinfo")
    77  			}
    78  		}
    79  	}
    80  	return nil, nil
    81  }
    82  
    83  func DebugCommand(ctx cli.Context) *cobra.Command {
    84  	var opts clioptions.ControlPlaneOptions
    85  	var centralOpts clioptions.CentralControlPlaneOptions
    86  
    87  	debugCommand := &cobra.Command{
    88  		Use:   "internal-debug [<type>/]<name>[.<namespace>]",
    89  		Short: "Retrieves the debug information of istio",
    90  		Long: `
    91  Retrieves the debug information from Istiod or Pods in the mesh using the service account from the pod if --cert-dir is empty.
    92  By default it will use the default serviceAccount from (istio-system) namespace if the pod is not specified.
    93  `,
    94  		Example: `  # Retrieve sync status for all Envoys in a mesh
    95    istioctl x internal-debug syncz
    96  
    97    # Retrieve sync diff for a single Envoy and Istiod
    98    istioctl x internal-debug syncz istio-egressgateway-59585c5b9c-ndc59.istio-system
    99  
   100    # SECURITY OPTIONS
   101  
   102    # Retrieve syncz debug information directly from the control plane, using token security
   103    # (This is the usual way to get the debug information with an out-of-cluster control plane.)
   104    istioctl x internal-debug syncz --xds-address istio.cloudprovider.example.com:15012
   105  
   106    # Retrieve syncz debug information via Kubernetes config, using token security
   107    # (This is the usual way to get the debug information with an in-cluster control plane.)
   108    istioctl x internal-debug syncz
   109  
   110    # Retrieve syncz debug information directly from the control plane, using RSA certificate security
   111    # (Certificates must be obtained before this step.  The --cert-dir flag lets istioctl bypass the Kubernetes API server.)
   112    istioctl x internal-debug syncz --xds-address istio.example.com:15012 --cert-dir ~/.istio-certs
   113  
   114    # Retrieve syncz information via XDS from specific control plane in multi-control plane in-cluster configuration
   115    # (Select a specific control plane in an in-cluster canary Istio configuration.)
   116    istioctl x internal-debug syncz --xds-label istio.io/rev=default
   117  `,
   118  		RunE: func(c *cobra.Command, args []string) error {
   119  			kubeClient, err := ctx.CLIClientWithRevision(opts.Revision)
   120  			if err != nil {
   121  				return err
   122  			}
   123  			if len(args) == 0 {
   124  				return util.CommandParseError{
   125  					Err: fmt.Errorf("debug type is required"),
   126  				}
   127  			}
   128  			var xdsRequest discovery.DiscoveryRequest
   129  			var namespace, serviceAccount string
   130  
   131  			xdsRequest = discovery.DiscoveryRequest{
   132  				ResourceNames: []string{args[0]},
   133  				Node: &core.Node{
   134  					Id: "debug~0.0.0.0~istioctl~cluster.local",
   135  				},
   136  				TypeUrl: v3.DebugType,
   137  			}
   138  
   139  			xdsResponses, err := multixds.MultiRequestAndProcessXds(internalDebugAllIstiod, &xdsRequest, centralOpts, ctx.IstioNamespace(),
   140  				namespace, serviceAccount, kubeClient, multixds.DefaultOptions)
   141  			if err != nil {
   142  				return err
   143  			}
   144  			sw := DebugWriter{
   145  				Writer:                 c.OutOrStdout(),
   146  				InternalDebugAllIstiod: internalDebugAllIstiod,
   147  			}
   148  			newResponse, err := HandlerForDebugErrors(kubeClient, &centralOpts, c.OutOrStdout(), ctx.IstioNamespace(), xdsResponses)
   149  			if err != nil {
   150  				return err
   151  			}
   152  			if newResponse != nil {
   153  				return sw.PrintAll(newResponse)
   154  			}
   155  
   156  			return sw.PrintAll(xdsResponses)
   157  		},
   158  	}
   159  
   160  	opts.AttachControlPlaneFlags(debugCommand)
   161  	centralOpts.AttachControlPlaneFlags(debugCommand)
   162  	debugCommand.Long += "\n\n" + util.ExperimentalMsg
   163  	debugCommand.PersistentFlags().BoolVar(&internalDebugAllIstiod, "all", false,
   164  		"Send the same request to all instances of Istiod. Only applicable for in-cluster deployment.")
   165  	return debugCommand
   166  }
   167  
   168  var internalDebugAllIstiod bool
   169  
   170  type DebugWriter struct {
   171  	Writer                 io.Writer
   172  	Namespace              string
   173  	InternalDebugAllIstiod bool
   174  }
   175  
   176  func (s *DebugWriter) PrintAll(drs map[string]*discovery.DiscoveryResponse) error {
   177  	// Gather the statuses before printing so they may be sorted
   178  	mappedResp := map[string]string{}
   179  	for id, dr := range drs {
   180  		for _, resource := range dr.Resources {
   181  			if s.InternalDebugAllIstiod {
   182  				mappedResp[id] = string(resource.Value) + "\n"
   183  			} else {
   184  				_, _ = s.Writer.Write(resource.Value)
   185  				_, _ = s.Writer.Write([]byte("\n"))
   186  			}
   187  		}
   188  	}
   189  	if len(mappedResp) > 0 {
   190  		mresp, err := json.MarshalIndent(mappedResp, "", "  ")
   191  		if err != nil {
   192  			return err
   193  		}
   194  		_, _ = s.Writer.Write(mresp)
   195  		_, _ = s.Writer.Write([]byte("\n"))
   196  	}
   197  
   198  	return nil
   199  }