1
16
17 package clusterinfo
18
19 import (
20 "context"
21 "fmt"
22 "io"
23 "os"
24 "path"
25 "time"
26
27 "github.com/spf13/cobra"
28
29 corev1 "k8s.io/api/core/v1"
30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 "k8s.io/cli-runtime/pkg/genericclioptions"
32 "k8s.io/cli-runtime/pkg/genericiooptions"
33 "k8s.io/cli-runtime/pkg/printers"
34 appsv1client "k8s.io/client-go/kubernetes/typed/apps/v1"
35 corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
36 cmdutil "k8s.io/kubectl/pkg/cmd/util"
37 "k8s.io/kubectl/pkg/polymorphichelpers"
38 "k8s.io/kubectl/pkg/scheme"
39 "k8s.io/kubectl/pkg/util/i18n"
40 "k8s.io/kubectl/pkg/util/templates"
41 )
42
43 const (
44 defaultPodLogsTimeout = 20 * time.Second
45 timeout = 5 * time.Minute
46 )
47
48 type ClusterInfoDumpOptions struct {
49 PrintFlags *genericclioptions.PrintFlags
50 PrintObj printers.ResourcePrinterFunc
51
52 OutputDir string
53 AllNamespaces bool
54 Namespaces []string
55
56 Timeout time.Duration
57 AppsClient appsv1client.AppsV1Interface
58 CoreClient corev1client.CoreV1Interface
59 Namespace string
60 RESTClientGetter genericclioptions.RESTClientGetter
61 LogsForObject polymorphichelpers.LogsForObjectFunc
62
63 genericiooptions.IOStreams
64 }
65
66 func NewCmdClusterInfoDump(restClientGetter genericclioptions.RESTClientGetter, ioStreams genericiooptions.IOStreams) *cobra.Command {
67 o := &ClusterInfoDumpOptions{
68 PrintFlags: genericclioptions.NewPrintFlags("").WithTypeSetter(scheme.Scheme).WithDefaultOutput("json"),
69
70 IOStreams: ioStreams,
71 }
72
73 cmd := &cobra.Command{
74 Use: "dump",
75 Short: i18n.T("Dump relevant information for debugging and diagnosis"),
76 Long: dumpLong,
77 Example: dumpExample,
78 Run: func(cmd *cobra.Command, args []string) {
79 cmdutil.CheckErr(o.Complete(restClientGetter, cmd))
80 cmdutil.CheckErr(o.Run())
81 },
82 }
83
84 o.PrintFlags.AddFlags(cmd)
85
86 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"))
87 cmd.Flags().StringSliceVar(&o.Namespaces, "namespaces", o.Namespaces, "A comma separated list of namespaces to dump.")
88 cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If true, dump all namespaces. If true, --namespaces is ignored.")
89 cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodLogsTimeout)
90 return cmd
91 }
92
93 var (
94 dumpLong = templates.LongDesc(i18n.T(`
95 Dump cluster information out suitable for debugging and diagnosing cluster problems. By default, dumps everything to
96 stdout. You can optionally specify a directory with --output-directory. If you specify a directory, Kubernetes will
97 build a set of files in that directory. By default, only dumps things in the current namespace and 'kube-system' namespace, but you can
98 switch to a different namespace with the --namespaces flag, or specify --all-namespaces to dump all namespaces.
99
100 The command also dumps the logs of all of the pods in the cluster; these logs are dumped into different directories
101 based on namespace and pod name.`))
102
103 dumpExample = templates.Examples(i18n.T(`
104 # Dump current cluster state to stdout
105 kubectl cluster-info dump
106
107 # Dump current cluster state to /path/to/cluster-state
108 kubectl cluster-info dump --output-directory=/path/to/cluster-state
109
110 # Dump all namespaces to stdout
111 kubectl cluster-info dump --all-namespaces
112
113 # Dump a set of namespaces to /path/to/cluster-state
114 kubectl cluster-info dump --namespaces default,kube-system --output-directory=/path/to/cluster-state`))
115 )
116
117 func setupOutputWriter(dir string, defaultWriter io.Writer, filename string, fileExtension string) io.Writer {
118 if len(dir) == 0 || dir == "-" {
119 return defaultWriter
120 }
121 fullFile := path.Join(dir, filename) + fileExtension
122 parent := path.Dir(fullFile)
123 cmdutil.CheckErr(os.MkdirAll(parent, 0755))
124
125 file, err := os.Create(fullFile)
126 cmdutil.CheckErr(err)
127 return file
128 }
129
130 func (o *ClusterInfoDumpOptions) Complete(restClientGetter genericclioptions.RESTClientGetter, cmd *cobra.Command) error {
131 printer, err := o.PrintFlags.ToPrinter()
132 if err != nil {
133 return err
134 }
135
136 o.PrintObj = printer.PrintObj
137
138 config, err := restClientGetter.ToRESTConfig()
139 if err != nil {
140 return err
141 }
142
143 o.CoreClient, err = corev1client.NewForConfig(config)
144 if err != nil {
145 return err
146 }
147
148 o.AppsClient, err = appsv1client.NewForConfig(config)
149 if err != nil {
150 return err
151 }
152
153 o.Timeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd)
154 if err != nil {
155 return err
156 }
157
158 o.Namespace, _, err = restClientGetter.ToRawKubeConfigLoader().Namespace()
159 if err != nil {
160 return err
161 }
162
163 o.RESTClientGetter = restClientGetter
164 o.LogsForObject = polymorphichelpers.LogsForObjectFn
165
166 return nil
167 }
168
169 func (o *ClusterInfoDumpOptions) Run() error {
170 nodes, err := o.CoreClient.Nodes().List(context.TODO(), metav1.ListOptions{})
171 if err != nil {
172 return err
173 }
174
175 fileExtension := ".txt"
176 if o.PrintFlags.OutputFormat != nil {
177 switch *o.PrintFlags.OutputFormat {
178 case "json":
179 fileExtension = ".json"
180 case "yaml":
181 fileExtension = ".yaml"
182 }
183 }
184
185 if err := o.PrintObj(nodes, setupOutputWriter(o.OutputDir, o.Out, "nodes", fileExtension)); err != nil {
186 return err
187 }
188
189 var namespaces []string
190 if o.AllNamespaces {
191 namespaceList, err := o.CoreClient.Namespaces().List(context.TODO(), metav1.ListOptions{})
192 if err != nil {
193 return err
194 }
195 for ix := range namespaceList.Items {
196 namespaces = append(namespaces, namespaceList.Items[ix].Name)
197 }
198 } else {
199 if len(o.Namespaces) == 0 {
200 namespaces = []string{
201 metav1.NamespaceSystem,
202 o.Namespace,
203 }
204 } else {
205 namespaces = o.Namespaces
206 }
207 }
208 for _, namespace := range namespaces {
209
210
211 events, err := o.CoreClient.Events(namespace).List(context.TODO(), metav1.ListOptions{})
212 if err != nil {
213 return err
214 }
215 if err := o.PrintObj(events, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "events"), fileExtension)); err != nil {
216 return err
217 }
218
219 rcs, err := o.CoreClient.ReplicationControllers(namespace).List(context.TODO(), metav1.ListOptions{})
220 if err != nil {
221 return err
222 }
223 if err := o.PrintObj(rcs, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "replication-controllers"), fileExtension)); err != nil {
224 return err
225 }
226
227 svcs, err := o.CoreClient.Services(namespace).List(context.TODO(), metav1.ListOptions{})
228 if err != nil {
229 return err
230 }
231 if err := o.PrintObj(svcs, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "services"), fileExtension)); err != nil {
232 return err
233 }
234
235 sets, err := o.AppsClient.DaemonSets(namespace).List(context.TODO(), metav1.ListOptions{})
236 if err != nil {
237 return err
238 }
239 if err := o.PrintObj(sets, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "daemonsets"), fileExtension)); err != nil {
240 return err
241 }
242
243 deps, err := o.AppsClient.Deployments(namespace).List(context.TODO(), metav1.ListOptions{})
244 if err != nil {
245 return err
246 }
247 if err := o.PrintObj(deps, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "deployments"), fileExtension)); err != nil {
248 return err
249 }
250
251 rps, err := o.AppsClient.ReplicaSets(namespace).List(context.TODO(), metav1.ListOptions{})
252 if err != nil {
253 return err
254 }
255 if err := o.PrintObj(rps, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "replicasets"), fileExtension)); err != nil {
256 return err
257 }
258
259 pods, err := o.CoreClient.Pods(namespace).List(context.TODO(), metav1.ListOptions{})
260 if err != nil {
261 return err
262 }
263
264 if err := o.PrintObj(pods, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "pods"), fileExtension)); err != nil {
265 return err
266 }
267
268 printContainer := func(writer io.Writer, container corev1.Container, pod *corev1.Pod) {
269 writer.Write([]byte(fmt.Sprintf("==== START logs for container %s of pod %s/%s ====\n", container.Name, pod.Namespace, pod.Name)))
270 defer writer.Write([]byte(fmt.Sprintf("==== END logs for container %s of pod %s/%s ====\n", container.Name, pod.Namespace, pod.Name)))
271
272 requests, err := o.LogsForObject(o.RESTClientGetter, pod, &corev1.PodLogOptions{Container: container.Name}, timeout, false)
273 if err != nil {
274
275 writer.Write([]byte(fmt.Sprintf("Create log request error: %s\n", err.Error())))
276 return
277 }
278
279 for _, request := range requests {
280 data, err := request.DoRaw(context.TODO())
281 if err != nil {
282
283 writer.Write([]byte(fmt.Sprintf("Request log error: %s\n", err.Error())))
284 return
285 }
286 writer.Write(data)
287 }
288 }
289
290 for ix := range pods.Items {
291 pod := &pods.Items[ix]
292 initcontainers := pod.Spec.InitContainers
293 containers := pod.Spec.Containers
294 writer := setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, pod.Name, "logs"), ".txt")
295
296 for i := range initcontainers {
297 printContainer(writer, initcontainers[i], pod)
298 }
299 for i := range containers {
300 printContainer(writer, containers[i], pod)
301 }
302 }
303 }
304
305 dest := o.OutputDir
306 if len(dest) > 0 && dest != "-" {
307 fmt.Fprintf(o.Out, "Cluster info dumped to %s\n", dest)
308 }
309 return nil
310 }
311
View as plain text