mirror of
https://github.com/percona/percona-toolkit.git
synced 2025-09-12 06:00:14 +00:00
PT-1865 Clean-up
This commit is contained in:
@@ -1,6 +1,42 @@
|
|||||||
#Debug collector tool
|
#Debug collector tool
|
||||||
|
|
||||||
Collects all necessary data for debugging from a cluster and creates a tar file with it
|
Collects data for debugging from a k8s/opeshift cluster and creates a tar.gz archive with it.
|
||||||
|
Archive name will be "cluster-dump.tar.gz" and it will be saved in the same location where you will run tool
|
||||||
|
|
||||||
Installed and configured kubectl is needed for work
|
###Data that will be collected
|
||||||
|
"pods",
|
||||||
|
"replicasets",
|
||||||
|
"deployments",
|
||||||
|
"statefulsets",
|
||||||
|
"replicationcontrollers",
|
||||||
|
"events",
|
||||||
|
"configmaps",
|
||||||
|
"secrets",
|
||||||
|
"cronjobs",
|
||||||
|
"jobs",
|
||||||
|
"podsecuritypolicies",
|
||||||
|
"poddisruptionbudgets",
|
||||||
|
"perconaxtradbbackups",
|
||||||
|
"perconaxtradbclusterbackups",
|
||||||
|
"perconaxtradbclusterrestores",
|
||||||
|
"perconaxtradbclusters",
|
||||||
|
"clusterrolebindings",
|
||||||
|
"clusterroles",
|
||||||
|
"rolebindings",
|
||||||
|
"roles",
|
||||||
|
"storageclasses",
|
||||||
|
"persistentvolumeclaims",
|
||||||
|
"persistentvolumes",
|
||||||
|
"modes",
|
||||||
|
"pxc/psmdb" (depend on 'resource' flag)
|
||||||
|
|
||||||
|
|
||||||
|
###Usage
|
||||||
|
Tool accept 3 flags:
|
||||||
|
1) resource: name of required custom resource ("pxc" is default)
|
||||||
|
2) namespace: namespace from where you need to collect data. If empty data will be collected from all namespaces
|
||||||
|
3) cluster: name for exact pxc/psmdb cluster. If empty tool will collect all "resource"
|
||||||
|
|
||||||
|
###Requirements
|
||||||
|
Installed and configured 'kubectl' is needed for work
|
||||||
|
|
||||||
|
@@ -20,16 +20,12 @@ type Dumper struct {
|
|||||||
resources []string
|
resources []string
|
||||||
namespace string
|
namespace string
|
||||||
location string
|
location string
|
||||||
Errors string
|
errors string
|
||||||
mode int64
|
mode int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// New return new Dumper object
|
// New return new Dumper object
|
||||||
func New(location, namespace, resource string) Dumper {
|
func New(location, namespace, resource string) Dumper {
|
||||||
directory := "cluster-dump"
|
|
||||||
if len(location) > 0 {
|
|
||||||
directory = location + "/cluster-dump"
|
|
||||||
}
|
|
||||||
resources := []string{
|
resources := []string{
|
||||||
"pods",
|
"pods",
|
||||||
"replicasets",
|
"replicasets",
|
||||||
@@ -61,7 +57,7 @@ func New(location, namespace, resource string) Dumper {
|
|||||||
return Dumper{
|
return Dumper{
|
||||||
cmd: "kubectl",
|
cmd: "kubectl",
|
||||||
resources: resources,
|
resources: resources,
|
||||||
location: directory,
|
location: "cluster-dump",
|
||||||
mode: int64(0777),
|
mode: int64(0777),
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
}
|
}
|
||||||
@@ -77,27 +73,32 @@ type namespaces struct {
|
|||||||
|
|
||||||
// DumpCluster create dump of a cluster in Dumper.location
|
// DumpCluster create dump of a cluster in Dumper.location
|
||||||
func (d *Dumper) DumpCluster() error {
|
func (d *Dumper) DumpCluster() error {
|
||||||
tarFile, err := os.Create(d.location + ".tar.gz")
|
file, err := os.Create(d.location + ".tar.gz")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "create tar file")
|
return errors.Wrap(err, "create tar file")
|
||||||
}
|
}
|
||||||
|
|
||||||
zr := gzip.NewWriter(tarFile)
|
zr := gzip.NewWriter(file)
|
||||||
tw := tar.NewWriter(zr)
|
tw := tar.NewWriter(zr)
|
||||||
defer func() {
|
defer func() {
|
||||||
|
err = addToArchive(d.location+"/errors.txt", d.mode, []byte(d.errors), tw)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error: add errors.txt to archive:", err)
|
||||||
|
}
|
||||||
|
|
||||||
err = tw.Close()
|
err = tw.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicln("close tar writer", err)
|
log.Println("close tar writer", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = zr.Close()
|
err = zr.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicln("close gzip writer", err)
|
log.Println("close gzip writer", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = tarFile.Close()
|
err = file.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicln("close tar file", err)
|
log.Println("close file", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -112,13 +113,13 @@ func (d *Dumper) DumpCluster() error {
|
|||||||
args := []string{"get", "namespaces", "-o", "json"}
|
args := []string{"get", "namespaces", "-o", "json"}
|
||||||
output, err := d.runCmd(args...)
|
output, err := d.runCmd(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.saveCommandError(err.Error(), args...)
|
d.logError(err.Error(), args...)
|
||||||
return errors.Wrap(err, "get namespaces")
|
return errors.Wrap(err, "get namespaces")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(output, &nss)
|
err = json.Unmarshal(output, &nss)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.saveCommandError(err.Error(), "unmarshal namespaces")
|
d.logError(err.Error(), "unmarshal namespaces")
|
||||||
return errors.Wrap(err, "unmarshal namespaces")
|
return errors.Wrap(err, "unmarshal namespaces")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,15 +128,15 @@ func (d *Dumper) DumpCluster() error {
|
|||||||
args := []string{"get", "pods", "-o", "json", "--namespace", ns.Name}
|
args := []string{"get", "pods", "-o", "json", "--namespace", ns.Name}
|
||||||
output, err := d.runCmd(args...)
|
output, err := d.runCmd(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.saveCommandError(err.Error(), args...)
|
d.logError(err.Error(), args...)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var pods k8sPods
|
var pods k8sPods
|
||||||
err = json.Unmarshal(output, &pods)
|
err = json.Unmarshal(output, &pods)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.saveCommandError(err.Error(), "unmarshal pods from namespace", ns.Name)
|
d.logError(err.Error(), "unmarshal pods from namespace", ns.Name)
|
||||||
log.Println(errors.Wrap(err, "unmarshal pods"))
|
log.Printf("Error: unmarshal pods in namespace %s: %v", ns.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pod := range pods.Items {
|
for _, pod := range pods.Items {
|
||||||
@@ -143,36 +144,31 @@ func (d *Dumper) DumpCluster() error {
|
|||||||
args := []string{"logs", pod.Name, "--namespace", ns.Name, "--all-containers"}
|
args := []string{"logs", pod.Name, "--namespace", ns.Name, "--all-containers"}
|
||||||
output, err = d.runCmd(args...)
|
output, err = d.runCmd(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("logs pod %s namespace %s: %v", pod.Name, ns.Name, err)
|
d.logError(err.Error(), args...)
|
||||||
d.saveCommandError(err.Error(), args...)
|
err = addToArchive(location, d.mode, []byte(err.Error()), tw)
|
||||||
err = createArchive(location, d.mode, []byte(err.Error()), tw)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("create archive with err: %v", err)
|
log.Printf("Error: create archive with logs for pod %s in namespace %s: %v", pod.Name, ns.Name, err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err = createArchive(location, d.mode, output, tw)
|
err = addToArchive(location, d.mode, output, tw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("create archive for pod %s: %v", pod.Name, err)
|
d.logError(err.Error(), "create archive for pod "+pod.Name)
|
||||||
|
log.Printf("Error: create archive for pod %s: %v", pod.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, resource := range d.resources {
|
for _, resource := range d.resources {
|
||||||
err = d.getResource(resource, ns.Name, tw)
|
err = d.getResource(resource, ns.Name, tw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(errors.Wrapf(err, "get %s resource", resource))
|
log.Printf("Error: get %s resource: %v", resource, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = createArchive(d.location+"/errors.txt", d.mode, []byte(d.Errors), tw)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(errors.Wrap(err, "write errors"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.getResource("nodes", "", tw)
|
err = d.getResource("nodes", "", tw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(errors.Wrapf(err, "get nodes"))
|
return errors.Wrapf(err, "get nodes")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -202,26 +198,26 @@ func (d *Dumper) getResource(name, namespace string, tw *tar.Writer) error {
|
|||||||
location += "/" + name + ".yaml"
|
location += "/" + name + ".yaml"
|
||||||
output, err := d.runCmd(args...)
|
output, err := d.runCmd(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.saveCommandError(err.Error(), args...)
|
d.logError(err.Error(), args...)
|
||||||
log.Printf("get resource %s namespace %s: %v", name, namespace, err)
|
log.Printf("Error: get resource %s in namespace %s: %v", name, namespace, err)
|
||||||
return createArchive(location, d.mode, []byte(err.Error()), tw)
|
return addToArchive(location, d.mode, []byte(err.Error()), tw)
|
||||||
}
|
}
|
||||||
|
|
||||||
return createArchive(location, d.mode, output, tw)
|
return addToArchive(location, d.mode, output, tw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dumper) saveCommandError(err string, args ...string) {
|
func (d *Dumper) logError(err string, args ...string) {
|
||||||
d.Errors += d.cmd + " " + strings.Join(args, " ") + ": " + err + "\n"
|
d.errors += d.cmd + " " + strings.Join(args, " ") + ": " + err + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
func createArchive(location string, mode int64, content []byte, tw *tar.Writer) error {
|
func addToArchive(location string, mode int64, content []byte, tw *tar.Writer) error {
|
||||||
hdr := &tar.Header{
|
hdr := &tar.Header{
|
||||||
Name: location,
|
Name: location,
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Size: int64(len(content)),
|
Size: int64(len(content)),
|
||||||
}
|
}
|
||||||
if err := tw.WriteHeader(hdr); err != nil {
|
if err := tw.WriteHeader(hdr); err != nil {
|
||||||
return errors.Wrap(err, "write header")
|
return errors.Wrapf(err, "write header to %s", location)
|
||||||
}
|
}
|
||||||
if _, err := tw.Write(content); err != nil {
|
if _, err := tw.Write(content); err != nil {
|
||||||
return errors.Wrapf(err, "write content to %s", location)
|
return errors.Wrapf(err, "write content to %s", location)
|
||||||
|
@@ -13,23 +13,23 @@ func main() {
|
|||||||
resource := ""
|
resource := ""
|
||||||
clusterName := ""
|
clusterName := ""
|
||||||
|
|
||||||
flag.StringVar(&namespace, "namespace", "", "Namespace for dumping. If empty will dump all namespaces")
|
flag.StringVar(&namespace, "namespace", "", "Namespace for collecting data. If empty data will be collected from all namespaces")
|
||||||
flag.StringVar(&resource, "resource", "pxc", "Resource name. Default value - 'pxc'")
|
flag.StringVar(&resource, "resource", "pxc", "Resource name. Default value - 'pxc'")
|
||||||
flag.StringVar(&clusterName, "cluster", "", "Cluster name")
|
flag.StringVar(&clusterName, "cluster", "", "Cluster name")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if len(clusterName) > 0 {
|
if len(clusterName) > 0 {
|
||||||
resource = resource + "/" + clusterName
|
resource += "/" + clusterName
|
||||||
}
|
}
|
||||||
|
|
||||||
d := dumper.New("", namespace, resource)
|
d := dumper.New("", namespace, resource)
|
||||||
log.Println("Start dump cluster")
|
log.Println("Start collecting cluster data")
|
||||||
|
|
||||||
err := d.DumpCluster()
|
err := d.DumpCluster()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println("Error:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Cluster dump ready")
|
log.Println("Done")
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user