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, ¢ralOpts, 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 }