2019-01-12 04:58:27 +00:00
/ *
Copyright 2016 The Kubernetes Authors .
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 clusterinfo
import (
2020-03-26 21:07:15 +00:00
"context"
2019-01-12 04:58:27 +00:00
"fmt"
"io"
"os"
"path"
"time"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
2019-04-07 17:07:55 +00:00
"k8s.io/cli-runtime/pkg/printers"
2019-01-12 04:58:27 +00:00
appsv1client "k8s.io/client-go/kubernetes/typed/apps/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
2019-09-27 21:51:53 +00:00
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
2019-01-12 04:58:27 +00:00
)
const (
defaultPodLogsTimeout = 20 * time . Second
timeout = 5 * time . Minute
)
type ClusterInfoDumpOptions struct {
PrintFlags * genericclioptions . PrintFlags
PrintObj printers . ResourcePrinterFunc
OutputDir string
AllNamespaces bool
Namespaces [ ] string
Timeout time . Duration
AppsClient appsv1client . AppsV1Interface
CoreClient corev1client . CoreV1Interface
Namespace string
RESTClientGetter genericclioptions . RESTClientGetter
LogsForObject polymorphichelpers . LogsForObjectFunc
genericclioptions . IOStreams
}
func NewCmdClusterInfoDump ( f cmdutil . Factory , ioStreams genericclioptions . IOStreams ) * cobra . Command {
o := & ClusterInfoDumpOptions {
PrintFlags : genericclioptions . NewPrintFlags ( "" ) . WithTypeSetter ( scheme . Scheme ) . WithDefaultOutput ( "json" ) ,
IOStreams : ioStreams ,
}
cmd := & cobra . Command {
Use : "dump" ,
Short : i18n . T ( "Dump lots of relevant info for debugging and diagnosis" ) ,
Long : dumpLong ,
Example : dumpExample ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
cmdutil . CheckErr ( o . Complete ( f , cmd ) )
cmdutil . CheckErr ( o . Run ( ) )
} ,
}
o . PrintFlags . AddFlags ( cmd )
cmd . Flags ( ) . StringVar ( & o . OutputDir , "output-directory" , o . OutputDir , i18n . T ( "Where to output the files. If empty or '-' uses stdout, otherwise creates a directory hierarchy in that directory" ) )
cmd . Flags ( ) . StringSliceVar ( & o . Namespaces , "namespaces" , o . Namespaces , "A comma separated list of namespaces to dump." )
2019-04-07 17:07:55 +00:00
cmd . Flags ( ) . BoolVarP ( & o . AllNamespaces , "all-namespaces" , "A" , o . AllNamespaces , "If true, dump all namespaces. If true, --namespaces is ignored." )
2019-01-12 04:58:27 +00:00
cmdutil . AddPodRunningTimeoutFlag ( cmd , defaultPodLogsTimeout )
return cmd
}
var (
dumpLong = templates . LongDesc ( i18n . T ( `
Dumps cluster info out suitable for debugging and diagnosing cluster problems . By default , dumps everything to
stdout . You can optionally specify a directory with -- output - directory . If you specify a directory , kubernetes will
build a set of files in that directory . By default only dumps things in the ' kube - system ' namespace , but you can
switch to a different namespace with the -- namespaces flag , or specify -- all - namespaces to dump all namespaces .
The command also dumps the logs of all of the pods in the cluster , these logs are dumped into different directories
based on namespace and pod name . ` ) )
dumpExample = templates . Examples ( i18n . T ( `
# Dump current cluster state to stdout
kubectl cluster - info dump
# Dump current cluster state to / path / to / cluster - state
kubectl cluster - info dump -- output - directory = / path / to / cluster - state
# Dump all namespaces to stdout
kubectl cluster - info dump -- all - namespaces
# Dump a set of namespaces to / path / to / cluster - state
kubectl cluster - info dump -- namespaces default , kube - system -- output - directory = / path / to / cluster - state ` ) )
)
2020-03-26 21:07:15 +00:00
func setupOutputWriter ( dir string , defaultWriter io . Writer , filename string , fileExtension string ) io . Writer {
2019-01-12 04:58:27 +00:00
if len ( dir ) == 0 || dir == "-" {
return defaultWriter
}
2020-03-26 21:07:15 +00:00
fullFile := path . Join ( dir , filename ) + fileExtension
2019-01-12 04:58:27 +00:00
parent := path . Dir ( fullFile )
cmdutil . CheckErr ( os . MkdirAll ( parent , 0755 ) )
2020-03-26 21:07:15 +00:00
file , err := os . Create ( fullFile )
2019-01-12 04:58:27 +00:00
cmdutil . CheckErr ( err )
return file
}
func ( o * ClusterInfoDumpOptions ) Complete ( f cmdutil . Factory , cmd * cobra . Command ) error {
printer , err := o . PrintFlags . ToPrinter ( )
if err != nil {
return err
}
o . PrintObj = printer . PrintObj
config , err := f . ToRESTConfig ( )
if err != nil {
return err
}
o . CoreClient , err = corev1client . NewForConfig ( config )
if err != nil {
return err
}
o . AppsClient , err = appsv1client . NewForConfig ( config )
if err != nil {
return err
}
o . Timeout , err = cmdutil . GetPodRunningTimeoutFlag ( cmd )
if err != nil {
return err
}
o . Namespace , _ , err = f . ToRawKubeConfigLoader ( ) . Namespace ( )
if err != nil {
return err
}
// TODO this should eventually just be the completed kubeconfigflag struct
o . RESTClientGetter = f
o . LogsForObject = polymorphichelpers . LogsForObjectFn
return nil
}
func ( o * ClusterInfoDumpOptions ) Run ( ) error {
2020-03-26 21:07:15 +00:00
nodes , err := o . CoreClient . Nodes ( ) . List ( context . TODO ( ) , metav1 . ListOptions { } )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
2020-03-26 21:07:15 +00:00
fileExtension := ".txt"
if o . PrintFlags . OutputFormat != nil {
switch * o . PrintFlags . OutputFormat {
case "json" :
fileExtension = ".json"
case "yaml" :
fileExtension = ".yaml"
}
}
if err := o . PrintObj ( nodes , setupOutputWriter ( o . OutputDir , o . Out , "nodes" , fileExtension ) ) ; err != nil {
2019-01-12 04:58:27 +00:00
return err
}
var namespaces [ ] string
if o . AllNamespaces {
2020-03-26 21:07:15 +00:00
namespaceList , err := o . CoreClient . Namespaces ( ) . List ( context . TODO ( ) , metav1 . ListOptions { } )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
for ix := range namespaceList . Items {
namespaces = append ( namespaces , namespaceList . Items [ ix ] . Name )
}
} else {
if len ( o . Namespaces ) == 0 {
namespaces = [ ] string {
metav1 . NamespaceSystem ,
o . Namespace ,
}
}
}
for _ , namespace := range namespaces {
// TODO: this is repetitive in the extreme. Use reflection or
// something to make this a for loop.
2020-03-26 21:07:15 +00:00
events , err := o . CoreClient . Events ( namespace ) . List ( context . TODO ( ) , metav1 . ListOptions { } )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
2020-03-26 21:07:15 +00:00
if err := o . PrintObj ( events , setupOutputWriter ( o . OutputDir , o . Out , path . Join ( namespace , "events" ) , fileExtension ) ) ; err != nil {
2019-01-12 04:58:27 +00:00
return err
}
2020-03-26 21:07:15 +00:00
rcs , err := o . CoreClient . ReplicationControllers ( namespace ) . List ( context . TODO ( ) , metav1 . ListOptions { } )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
2020-03-26 21:07:15 +00:00
if err := o . PrintObj ( rcs , setupOutputWriter ( o . OutputDir , o . Out , path . Join ( namespace , "replication-controllers" ) , fileExtension ) ) ; err != nil {
2019-01-12 04:58:27 +00:00
return err
}
2020-03-26 21:07:15 +00:00
svcs , err := o . CoreClient . Services ( namespace ) . List ( context . TODO ( ) , metav1 . ListOptions { } )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
2020-03-26 21:07:15 +00:00
if err := o . PrintObj ( svcs , setupOutputWriter ( o . OutputDir , o . Out , path . Join ( namespace , "services" ) , fileExtension ) ) ; err != nil {
2019-01-12 04:58:27 +00:00
return err
}
2020-03-26 21:07:15 +00:00
sets , err := o . AppsClient . DaemonSets ( namespace ) . List ( context . TODO ( ) , metav1 . ListOptions { } )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
2020-03-26 21:07:15 +00:00
if err := o . PrintObj ( sets , setupOutputWriter ( o . OutputDir , o . Out , path . Join ( namespace , "daemonsets" ) , fileExtension ) ) ; err != nil {
2019-01-12 04:58:27 +00:00
return err
}
2020-03-26 21:07:15 +00:00
deps , err := o . AppsClient . Deployments ( namespace ) . List ( context . TODO ( ) , metav1 . ListOptions { } )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
2020-03-26 21:07:15 +00:00
if err := o . PrintObj ( deps , setupOutputWriter ( o . OutputDir , o . Out , path . Join ( namespace , "deployments" ) , fileExtension ) ) ; err != nil {
2019-01-12 04:58:27 +00:00
return err
}
2020-03-26 21:07:15 +00:00
rps , err := o . AppsClient . ReplicaSets ( namespace ) . List ( context . TODO ( ) , metav1 . ListOptions { } )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
2020-03-26 21:07:15 +00:00
if err := o . PrintObj ( rps , setupOutputWriter ( o . OutputDir , o . Out , path . Join ( namespace , "replicasets" ) , fileExtension ) ) ; err != nil {
2019-01-12 04:58:27 +00:00
return err
}
2020-03-26 21:07:15 +00:00
pods , err := o . CoreClient . Pods ( namespace ) . List ( context . TODO ( ) , metav1 . ListOptions { } )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
2020-03-26 21:07:15 +00:00
if err := o . PrintObj ( pods , setupOutputWriter ( o . OutputDir , o . Out , path . Join ( namespace , "pods" ) , fileExtension ) ) ; err != nil {
2019-01-12 04:58:27 +00:00
return err
}
printContainer := func ( writer io . Writer , container corev1 . Container , pod * corev1 . Pod ) {
writer . Write ( [ ] byte ( fmt . Sprintf ( "==== START logs for container %s of pod %s/%s ====\n" , container . Name , pod . Namespace , pod . Name ) ) )
defer writer . Write ( [ ] byte ( fmt . Sprintf ( "==== END logs for container %s of pod %s/%s ====\n" , container . Name , pod . Namespace , pod . Name ) ) )
requests , err := o . LogsForObject ( o . RESTClientGetter , pod , & corev1 . PodLogOptions { Container : container . Name } , timeout , false )
if err != nil {
// Print error and return.
writer . Write ( [ ] byte ( fmt . Sprintf ( "Create log request error: %s\n" , err . Error ( ) ) ) )
return
}
for _ , request := range requests {
2020-03-26 21:07:15 +00:00
data , err := request . DoRaw ( context . TODO ( ) )
2019-01-12 04:58:27 +00:00
if err != nil {
// Print error and return.
writer . Write ( [ ] byte ( fmt . Sprintf ( "Request log error: %s\n" , err . Error ( ) ) ) )
return
}
writer . Write ( data )
}
}
for ix := range pods . Items {
pod := & pods . Items [ ix ]
2020-03-26 21:07:15 +00:00
initcontainers := pod . Spec . InitContainers
2019-01-12 04:58:27 +00:00
containers := pod . Spec . Containers
2020-03-26 21:07:15 +00:00
writer := setupOutputWriter ( o . OutputDir , o . Out , path . Join ( namespace , pod . Name , "logs" ) , ".txt" )
2019-01-12 04:58:27 +00:00
2020-03-26 21:07:15 +00:00
for i := range initcontainers {
printContainer ( writer , initcontainers [ i ] , pod )
}
2019-01-12 04:58:27 +00:00
for i := range containers {
printContainer ( writer , containers [ i ] , pod )
}
}
}
dest := o . OutputDir
2020-03-26 21:07:15 +00:00
if len ( dest ) > 0 && dest != "-" {
2019-01-12 04:58:27 +00:00
fmt . Fprintf ( o . Out , "Cluster info dumped to %s\n" , dest )
}
return nil
}