-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[d8] Add d8 collect-debug-info #81
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/* | ||
Copyright 2024 Flant JSC | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package collect_debug_info | ||
|
||
import ( | ||
"fmt" | ||
"github.com/deckhouse/deckhouse-cli/internal/platform/cmd/collect-debug-info/debugTar" | ||
"github.com/deckhouse/deckhouse-cli/internal/utilk8s" | ||
"github.com/spf13/cobra" | ||
"golang.org/x/term" | ||
"k8s.io/kubectl/pkg/util/templates" | ||
"os" | ||
) | ||
|
||
var collectDebugInfoCmdLong = templates.LongDesc(` | ||
Collect debug info from Deckhouse Kubernetes Platform. | ||
|
||
© Flant JSC 2025`) | ||
|
||
func NewCommand() *cobra.Command { | ||
collectDebugInfoCmd := &cobra.Command{ | ||
Use: "collect-debug-info", | ||
Short: "Collect debug info.", | ||
Long: collectDebugInfoCmdLong, | ||
SilenceErrors: true, | ||
SilenceUsage: true, | ||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
var err error | ||
if term.IsTerminal(int(os.Stdout.Fd())) { | ||
return fmt.Errorf("Please provide output tar.gz to dump debug logs, ex. \"> dump-logs.tar.gz\"") | ||
} | ||
return err | ||
}, | ||
RunE: collectDebugInfo, | ||
} | ||
return collectDebugInfoCmd | ||
} | ||
|
||
func collectDebugInfo(cmd *cobra.Command, _ []string) error { | ||
kubeconfigPath, err := cmd.Flags().GetString("kubeconfig") | ||
if err != nil { | ||
return fmt.Errorf("Failed to setup Kubernetes client: %w", err) | ||
} | ||
|
||
config, kubeCl, err := utilk8s.SetupK8sClientSet(kubeconfigPath) | ||
if err != nil { | ||
return fmt.Errorf("Failed to setup Kubernetes client: %w", err) | ||
} | ||
|
||
err = debugTar.Tarball(config, kubeCl) | ||
if err != nil { | ||
return fmt.Errorf("Error collecting debug info: %w", err) | ||
} | ||
return err | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
package debugTar | ||
|
||
import ( | ||
"archive/tar" | ||
"bytes" | ||
"compress/gzip" | ||
"context" | ||
"fmt" | ||
"github.com/deckhouse/deckhouse-cli/internal/platform/cmd/operatepod" | ||
"golang.org/x/term" | ||
"k8s.io/client-go/kubernetes" | ||
"k8s.io/client-go/rest" | ||
"k8s.io/client-go/tools/remotecommand" | ||
"os" | ||
"strings" | ||
) | ||
|
||
type Command struct { | ||
Cmd string | ||
Args []string | ||
File string | ||
} | ||
|
||
func Tarball(config *rest.Config, kubeCl kubernetes.Interface) error { | ||
const ( | ||
labelSelector = "leader=true" | ||
namespace = "d8-system" | ||
containerName = "deckhouse" | ||
) | ||
|
||
debugCommands := []Command{ | ||
{ | ||
File: "queue.txt", | ||
Cmd: "deckhouse-controller", | ||
Args: []string{"queue", "list"}, | ||
}, | ||
{ | ||
File: "global-values.json", | ||
Cmd: "bash", | ||
Args: []string{"-c", `deckhouse-controller global values -o json | jq '.internal.modules.kubeRBACProxyCA = "REDACTED" | .modulesImages.registry.dockercfg = "REDACTED"'`}, | ||
}, | ||
{ | ||
File: "deckhouse-enabled-modules.json", | ||
Cmd: "bash", | ||
Args: []string{"-c", "kubectl get modules -o json | jq '.items[]'"}, | ||
}, | ||
{ | ||
File: "events.json", | ||
Cmd: "kubectl", | ||
Args: []string{"get", "events", "--sort-by=.metadata.creationTimestamp", "-A", "-o", "json"}, | ||
}, | ||
{ | ||
File: "d8-all.json", | ||
Cmd: "bash", | ||
Args: []string{"-c", `for ns in $(kubectl get ns -o go-template='{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}{{"kube-system"}}' -l heritage=deckhouse); do kubectl -n $ns get all -o json; done | jq -s '[.[].items[]]'`}, | ||
}, | ||
{ | ||
File: "node-groups.json", | ||
Cmd: "kubectl", | ||
Args: []string{"get", "nodegroups", "-A", "-o", "json"}, | ||
}, | ||
{ | ||
File: "nodes.json", | ||
Cmd: "kubectl", | ||
Args: []string{"get", "nodes", "-A", "-o", "json"}, | ||
}, | ||
{ | ||
File: "machines.json", | ||
Cmd: "kubectl", | ||
Args: []string{"get", "machines.machine.sapcloud.io", "-A", "-o", "json"}, | ||
}, | ||
{ | ||
File: "instances.json", | ||
Cmd: "kubectl", | ||
Args: []string{"get", "instances.deckhouse.io", "-o", "json"}, | ||
}, | ||
{ | ||
File: "staticinstances.json", | ||
Cmd: "kubectl", | ||
Args: []string{"get", "staticinstances.deckhouse.io", "-o", "json"}, | ||
}, | ||
{ | ||
File: "deckhouse-version.json", | ||
Cmd: "bash", | ||
Args: []string{"-c", "jq -s add <(kubectl -n d8-system get deployment deckhouse -o json | jq -r '.metadata.annotations | {\"core.deckhouse.io/edition\",\"core.deckhouse.io/version\"}') <(kubectl -n d8-system get deployment deckhouse -o json | jq -r '.spec.template.spec.containers[] | select(.name == \"deckhouse\") | {image}')"}, | ||
}, | ||
{ | ||
File: "deckhouse-releases.json", | ||
Cmd: "kubectl", | ||
Args: []string{"get", "deckhousereleases", "-o", "json"}, | ||
}, | ||
{ | ||
File: "deckhouse-logs.json", | ||
Cmd: "kubectl", | ||
Args: []string{"logs", "deploy/deckhouse", "--tail", "3000"}, | ||
}, | ||
{ | ||
File: "mcm-logs.txt", | ||
Cmd: "kubectl", | ||
Args: []string{"-n", "d8-cloud-instance-manager", "logs", "-l", "app=machine-controller-manager", "--tail", "3000", "-c", "controller"}, | ||
}, | ||
{ | ||
File: "ccm-logs.txt", | ||
Cmd: "kubectl", | ||
Args: []string{"-n", "d8-cloud-provider", "logs", "-l", "app=cloud-controller-manager", "--tail", "3000"}, | ||
}, | ||
{ | ||
File: "cluster-autoscaler-logs.txt", | ||
Cmd: "kubectl", | ||
Args: []string{"-n", "d8-cloud-instance-manager", "logs", "-l", "app=cluster-autoscaler", "--tail", "3000", "-c", "cluster-autoscaler"}, | ||
}, | ||
{ | ||
File: "vpa-admission-controller-logs.txt", | ||
Cmd: "kubectl", | ||
Args: []string{"-n", "kube-system", "logs", "-l", "app=vpa-admission-controller", "--tail", "3000", "-c", "admission-controller"}, | ||
}, | ||
{ | ||
File: "vpa-recommender-logs.txt", | ||
Cmd: "kubectl", | ||
Args: []string{"-n", "kube-system", "logs", "-l", "app=vpa-recommender", "--tail", "3000", "-c", "recommender"}, | ||
}, | ||
{ | ||
File: "vpa-updater-logs.txt", | ||
Cmd: "kubectl", | ||
Args: []string{"-n", "kube-system", "logs", "-l", "app=vpa-updater", "--tail", "3000", "-c", "updater"}, | ||
}, | ||
{ | ||
File: "prometheus-logs.txt", | ||
Cmd: "kubectl", | ||
Args: []string{"-n", "d8-monitoring", "logs", "-l", "prometheus=main", "--tail", "3000", "-c", "prometheus"}, | ||
}, | ||
{ | ||
File: "terraform-check.json", | ||
Cmd: "bash", | ||
Args: []string{"-c", `kubectl exec deploy/terraform-state-exporter -- dhctl terraform check --logger-type json -o json | jq -c '.terraform_plan[]?.variables.providerClusterConfiguration.value.provider = "REDACTED"'`}, | ||
}, | ||
{ | ||
File: "alerts.json", | ||
Cmd: "bash", | ||
Args: []string{"-c", `kubectl get clusteralerts.deckhouse.io -o json | jq '.items[]'`}, | ||
}, | ||
{ | ||
File: "pods.txt", | ||
Cmd: "bash", | ||
Args: []string{"-c", `kubectl get pod -A -owide | grep -Pv '\s+([1-9]+[\d]*)\/\1\s+' | grep -v 'Completed\|Evicted' | grep -E "^(d8-|kube-system)"`}, | ||
}, | ||
{ | ||
File: "cluster-authorization-rules.json", | ||
Cmd: "kubectl", | ||
Args: []string{"get", "clusterauthorizationrules", "-o", "json"}, | ||
}, | ||
{ | ||
File: "authorization-rules.json", | ||
Cmd: "kubectl", | ||
Args: []string{"get", "authorizationrules", "-o", "json"}, | ||
}, | ||
{ | ||
File: "module-configs.json", | ||
Cmd: "kubectl", | ||
Args: []string{"get", "moduleconfig", "-o", "json"}, | ||
}, | ||
} | ||
|
||
podName, err := operatepod.GetDeckhousePod(kubeCl, namespace, labelSelector) | ||
|
||
if term.IsTerminal(int(os.Stdout.Fd())) { | ||
return fmt.Errorf("Please provide output tar.gz to dump debug logs, ex. \"> dump-logs.tar.gz\"") | ||
} | ||
Comment on lines
+166
to
+168
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be done early, there is no point in reaching to the kube-apiserver if we know that we are not going to output to file and throw error anyway There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed, moved it to PreRunE There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems that you forgot to remove it here |
||
|
||
var stdout, stderr bytes.Buffer | ||
gzipWriter := gzip.NewWriter(os.Stdout) | ||
defer gzipWriter.Close() | ||
tarWriter := tar.NewWriter(gzipWriter) | ||
defer tarWriter.Close() | ||
|
||
for _, cmd := range debugCommands { | ||
fullCommand := append([]string{cmd.Cmd}, cmd.Args...) | ||
executor, err := operatepod.ExecInPod(config, kubeCl, fullCommand, podName, namespace, containerName) | ||
if err = executor.StreamWithContext( | ||
context.Background(), | ||
remotecommand.StreamOptions{ | ||
Stdout: &stdout, | ||
Stderr: &stderr, | ||
}); err != nil { | ||
fmt.Fprintf(os.Stderr, strings.Join(fullCommand, " ")) | ||
fmt.Fprintf(os.Stderr, stderr.String()) | ||
} | ||
|
||
err = cmd.Writer(tarWriter, stdout.Bytes()) | ||
if err != nil { | ||
return fmt.Errorf("failed to update the %w", err) | ||
} | ||
stdout.Reset() | ||
|
||
} | ||
return err | ||
} | ||
|
||
func (c *Command) Writer(tarWriter *tar.Writer, fileContent []byte) error { | ||
header := &tar.Header{ | ||
Name: c.File, | ||
Mode: 0o600, | ||
Size: int64(len(fileContent)), | ||
} | ||
|
||
if err := tarWriter.WriteHeader(header); err != nil { | ||
return fmt.Errorf("write tar header: %v", err) | ||
} | ||
|
||
if _, err := tarWriter.Write(fileContent); err != nil { | ||
return fmt.Errorf("copy content: %v", err) | ||
} | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var err error
is not required, return nil explicitly. Doing it in this way may cause nasty bugs that are hard to debug.https://go.dev/doc/faq#nil_error