1
16
17 package debug
18
19 import (
20 "context"
21 "encoding/json"
22 "fmt"
23 "os"
24 "time"
25
26 "github.com/distribution/reference"
27 "github.com/spf13/cobra"
28 "k8s.io/klog/v2"
29 "k8s.io/utils/pointer"
30
31 corev1 "k8s.io/api/core/v1"
32 "k8s.io/apimachinery/pkg/api/errors"
33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34 "k8s.io/apimachinery/pkg/fields"
35 "k8s.io/apimachinery/pkg/runtime"
36 "k8s.io/apimachinery/pkg/runtime/schema"
37 "k8s.io/apimachinery/pkg/types"
38 utilrand "k8s.io/apimachinery/pkg/util/rand"
39 "k8s.io/apimachinery/pkg/util/strategicpatch"
40 "k8s.io/apimachinery/pkg/watch"
41 "k8s.io/cli-runtime/pkg/genericclioptions"
42 "k8s.io/cli-runtime/pkg/genericiooptions"
43 "k8s.io/cli-runtime/pkg/printers"
44 "k8s.io/cli-runtime/pkg/resource"
45 "k8s.io/client-go/kubernetes"
46 corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
47 "k8s.io/client-go/tools/cache"
48 watchtools "k8s.io/client-go/tools/watch"
49 "k8s.io/kubectl/pkg/cmd/attach"
50 "k8s.io/kubectl/pkg/cmd/exec"
51 "k8s.io/kubectl/pkg/cmd/logs"
52 cmdutil "k8s.io/kubectl/pkg/cmd/util"
53 "k8s.io/kubectl/pkg/polymorphichelpers"
54 "k8s.io/kubectl/pkg/scheme"
55 "k8s.io/kubectl/pkg/util/i18n"
56 "k8s.io/kubectl/pkg/util/interrupt"
57 "k8s.io/kubectl/pkg/util/templates"
58 "k8s.io/kubectl/pkg/util/term"
59 )
60
61 var (
62 debugLong = templates.LongDesc(i18n.T(`
63 Debug cluster resources using interactive debugging containers.
64
65 'debug' provides automation for common debugging tasks for cluster objects identified by
66 resource and name. Pods will be used by default if no resource is specified.
67
68 The action taken by 'debug' varies depending on what resource is specified. Supported
69 actions include:
70
71 * Workload: Create a copy of an existing pod with certain attributes changed,
72 for example changing the image tag to a new version.
73 * Workload: Add an ephemeral container to an already running pod, for example to add
74 debugging utilities without restarting the pod.
75 * Node: Create a new pod that runs in the node's host namespaces and can access
76 the node's filesystem.
77 `))
78
79 debugExample = templates.Examples(i18n.T(`
80 # Create an interactive debugging session in pod mypod and immediately attach to it.
81 kubectl debug mypod -it --image=busybox
82
83 # Create an interactive debugging session for the pod in the file pod.yaml and immediately attach to it.
84 # (requires the EphemeralContainers feature to be enabled in the cluster)
85 kubectl debug -f pod.yaml -it --image=busybox
86
87 # Create a debug container named debugger using a custom automated debugging image.
88 kubectl debug --image=myproj/debug-tools -c debugger mypod
89
90 # Create a copy of mypod adding a debug container and attach to it
91 kubectl debug mypod -it --image=busybox --copy-to=my-debugger
92
93 # Create a copy of mypod changing the command of mycontainer
94 kubectl debug mypod -it --copy-to=my-debugger --container=mycontainer -- sh
95
96 # Create a copy of mypod changing all container images to busybox
97 kubectl debug mypod --copy-to=my-debugger --set-image=*=busybox
98
99 # Create a copy of mypod adding a debug container and changing container images
100 kubectl debug mypod -it --copy-to=my-debugger --image=debian --set-image=app=app:debug,sidecar=sidecar:debug
101
102 # Create an interactive debugging session on a node and immediately attach to it.
103 # The container will run in the host namespaces and the host's filesystem will be mounted at /host
104 kubectl debug node/mynode -it --image=busybox
105 `))
106 )
107
108 var nameSuffixFunc = utilrand.String
109
110 type DebugAttachFunc func(ctx context.Context, restClientGetter genericclioptions.RESTClientGetter, cmdPath string, ns, podName, containerName string) error
111
112
113 type DebugOptions struct {
114 Args []string
115 ArgsOnly bool
116 Attach bool
117 AttachFunc DebugAttachFunc
118 Container string
119 CopyTo string
120 Replace bool
121 Env []corev1.EnvVar
122 Image string
123 Interactive bool
124 Namespace string
125 TargetNames []string
126 PullPolicy corev1.PullPolicy
127 Quiet bool
128 SameNode bool
129 SetImages map[string]string
130 ShareProcesses bool
131 TargetContainer string
132 TTY bool
133 Profile string
134 CustomProfileFile string
135 CustomProfile *corev1.Container
136 Applier ProfileApplier
137
138 explicitNamespace bool
139 attachChanged bool
140 shareProcessedChanged bool
141
142 podClient corev1client.CoreV1Interface
143
144 Builder *resource.Builder
145 genericiooptions.IOStreams
146 WarningPrinter *printers.WarningPrinter
147
148 resource.FilenameOptions
149 }
150
151
152 func NewDebugOptions(streams genericiooptions.IOStreams) *DebugOptions {
153 return &DebugOptions{
154 Args: []string{},
155 IOStreams: streams,
156 TargetNames: []string{},
157 ShareProcesses: true,
158 }
159 }
160
161
162 func NewCmdDebug(restClientGetter genericclioptions.RESTClientGetter, streams genericiooptions.IOStreams) *cobra.Command {
163 o := NewDebugOptions(streams)
164
165 cmd := &cobra.Command{
166 Use: "debug (POD | TYPE[[.VERSION].GROUP]/NAME) [ -- COMMAND [args...] ]",
167 DisableFlagsInUseLine: true,
168 Short: i18n.T("Create debugging sessions for troubleshooting workloads and nodes"),
169 Long: debugLong,
170 Example: debugExample,
171 Run: func(cmd *cobra.Command, args []string) {
172 cmdutil.CheckErr(o.Complete(restClientGetter, cmd, args))
173 cmdutil.CheckErr(o.Validate())
174 cmdutil.CheckErr(o.Run(restClientGetter, cmd))
175 },
176 }
177
178 o.AddFlags(cmd)
179 return cmd
180 }
181
182 func (o *DebugOptions) AddFlags(cmd *cobra.Command) {
183 cmdutil.AddJsonFilenameFlag(cmd.Flags(), &o.FilenameOptions.Filenames, "identifying the resource to debug")
184
185 cmd.Flags().BoolVar(&o.ArgsOnly, "arguments-only", o.ArgsOnly, i18n.T("If specified, everything after -- will be passed to the new container as Args instead of Command."))
186 cmd.Flags().BoolVar(&o.Attach, "attach", o.Attach, i18n.T("If true, wait for the container to start running, and then attach as if 'kubectl attach ...' were called. Default false, unless '-i/--stdin' is set, in which case the default is true."))
187 cmd.Flags().StringVarP(&o.Container, "container", "c", o.Container, i18n.T("Container name to use for debug container."))
188 cmd.Flags().StringVar(&o.CopyTo, "copy-to", o.CopyTo, i18n.T("Create a copy of the target Pod with this name."))
189 cmd.Flags().BoolVar(&o.Replace, "replace", o.Replace, i18n.T("When used with '--copy-to', delete the original Pod."))
190 cmd.Flags().StringToString("env", nil, i18n.T("Environment variables to set in the container."))
191 cmd.Flags().StringVar(&o.Image, "image", o.Image, i18n.T("Container image to use for debug container."))
192 cmd.Flags().StringToStringVar(&o.SetImages, "set-image", o.SetImages, i18n.T("When used with '--copy-to', a list of name=image pairs for changing container images, similar to how 'kubectl set image' works."))
193 cmd.Flags().String("image-pull-policy", "", i18n.T("The image pull policy for the container. If left empty, this value will not be specified by the client and defaulted by the server."))
194 cmd.Flags().BoolVarP(&o.Interactive, "stdin", "i", o.Interactive, i18n.T("Keep stdin open on the container(s) in the pod, even if nothing is attached."))
195 cmd.Flags().BoolVarP(&o.Quiet, "quiet", "q", o.Quiet, i18n.T("If true, suppress informational messages."))
196 cmd.Flags().BoolVar(&o.SameNode, "same-node", o.SameNode, i18n.T("When used with '--copy-to', schedule the copy of target Pod on the same node."))
197 cmd.Flags().BoolVar(&o.ShareProcesses, "share-processes", o.ShareProcesses, i18n.T("When used with '--copy-to', enable process namespace sharing in the copy."))
198 cmd.Flags().StringVar(&o.TargetContainer, "target", "", i18n.T("When using an ephemeral container, target processes in this container name."))
199 cmd.Flags().BoolVarP(&o.TTY, "tty", "t", o.TTY, i18n.T("Allocate a TTY for the debugging container."))
200 cmd.Flags().StringVar(&o.Profile, "profile", ProfileLegacy, i18n.T(`Options are "legacy", "general", "baseline", "netadmin", "restricted" or "sysadmin".`))
201 if cmdutil.DebugCustomProfile.IsEnabled() {
202 cmd.Flags().StringVar(&o.CustomProfileFile, "custom", o.CustomProfileFile, i18n.T("Path to a JSON file containing a partial container spec to customize built-in debug profiles."))
203 }
204 }
205
206
207 func (o *DebugOptions) Complete(restClientGetter genericclioptions.RESTClientGetter, cmd *cobra.Command, args []string) error {
208 var err error
209
210 o.PullPolicy = corev1.PullPolicy(cmdutil.GetFlagString(cmd, "image-pull-policy"))
211
212
213 argsLen := cmd.ArgsLenAtDash()
214 o.TargetNames = args
215
216 if argsLen >= 0 && len(args) > argsLen {
217 o.TargetNames, o.Args = args[:argsLen], args[argsLen:]
218 }
219
220
221 attachFlag := cmd.Flags().Lookup("attach")
222 if !attachFlag.Changed && o.Interactive {
223 o.Attach = true
224 }
225
226
227
228
229
230
231 if o.AttachFunc == nil {
232 o.AttachFunc = o.handleAttachPod
233 }
234
235
236 envStrings, err := cmd.Flags().GetStringToString("env")
237 if err != nil {
238 return fmt.Errorf("internal error getting env flag: %v", err)
239 }
240 for k, v := range envStrings {
241 o.Env = append(o.Env, corev1.EnvVar{Name: k, Value: v})
242 }
243
244
245 o.Namespace, o.explicitNamespace, err = restClientGetter.ToRawKubeConfigLoader().Namespace()
246 if err != nil {
247 return err
248 }
249
250
251 o.attachChanged = cmd.Flags().Changed("attach")
252 o.shareProcessedChanged = cmd.Flags().Changed("share-processes")
253
254
255 if o.WarningPrinter == nil {
256 o.WarningPrinter = printers.NewWarningPrinter(o.ErrOut, printers.WarningPrinterOptions{Color: term.AllowsColorOutput(o.ErrOut)})
257 }
258
259 if o.Applier == nil {
260 applier, err := NewProfileApplier(o.Profile)
261 if err != nil {
262 return err
263 }
264 o.Applier = applier
265 }
266
267 if o.CustomProfileFile != "" {
268 customProfileBytes, err := os.ReadFile(o.CustomProfileFile)
269 if err != nil {
270 return fmt.Errorf("must pass a container spec json file for custom profile: %w", err)
271 }
272
273 err = json.Unmarshal(customProfileBytes, &o.CustomProfile)
274 if err != nil {
275 return fmt.Errorf("%s does not contain a valid container spec: %w", o.CustomProfileFile, err)
276 }
277 }
278
279 clientConfig, err := restClientGetter.ToRESTConfig()
280 if err != nil {
281 return err
282 }
283
284 client, err := kubernetes.NewForConfig(clientConfig)
285 if err != nil {
286 return err
287 }
288
289 o.podClient = client.CoreV1()
290
291 o.Builder = resource.NewBuilder(restClientGetter)
292
293 return nil
294 }
295
296
297 func (o *DebugOptions) Validate() error {
298
299 if o.Attach && o.attachChanged && len(o.Image) == 0 && len(o.Container) == 0 {
300 return fmt.Errorf("you must specify --container or create a new container using --image in order to attach.")
301 }
302
303
304 if len(o.CopyTo) > 0 {
305 if len(o.Image) == 0 && len(o.SetImages) == 0 && len(o.Args) == 0 {
306 return fmt.Errorf("you must specify --image, --set-image or command arguments.")
307 }
308 if len(o.Args) > 0 && len(o.Container) == 0 && len(o.Image) == 0 {
309 return fmt.Errorf("you must specify an existing container or a new image when specifying args.")
310 }
311 } else {
312
313 switch {
314 case o.Replace:
315 return fmt.Errorf("--replace may only be used with --copy-to.")
316 case o.SameNode:
317 return fmt.Errorf("--same-node may only be used with --copy-to.")
318 case len(o.SetImages) > 0:
319 return fmt.Errorf("--set-image may only be used with --copy-to.")
320 case len(o.Image) == 0:
321 return fmt.Errorf("you must specify --image when not using --copy-to.")
322 }
323 }
324
325
326 if len(o.Image) > 0 && !reference.ReferenceRegexp.MatchString(o.Image) {
327 return fmt.Errorf("invalid image name %q: %v", o.Image, reference.ErrReferenceInvalidFormat)
328 }
329
330
331 if len(o.TargetNames) == 0 && len(o.FilenameOptions.Filenames) == 0 {
332 return fmt.Errorf("NAME or filename is required for debug")
333 }
334
335
336 switch o.PullPolicy {
337 case corev1.PullAlways, corev1.PullIfNotPresent, corev1.PullNever, "":
338
339 default:
340 return fmt.Errorf("invalid image pull policy: %s", o.PullPolicy)
341 }
342
343
344 for name, image := range o.SetImages {
345 if !reference.ReferenceRegexp.MatchString(image) {
346 return fmt.Errorf("invalid image name %q for container %q: %v", image, name, reference.ErrReferenceInvalidFormat)
347 }
348 }
349
350
351 if len(o.TargetContainer) > 0 {
352 if len(o.CopyTo) > 0 {
353 return fmt.Errorf("--target is incompatible with --copy-to. Use --share-processes instead.")
354 }
355 if !o.Quiet {
356 fmt.Fprintf(o.Out, "Targeting container %q. If you don't see processes from this container it may be because the container runtime doesn't support this feature.\n", o.TargetContainer)
357
358 }
359 }
360
361
362 if o.TTY && !o.Interactive {
363 return fmt.Errorf("-i/--stdin is required for containers with -t/--tty=true")
364 }
365
366
367 if o.WarningPrinter == nil {
368 return fmt.Errorf("WarningPrinter can not be used without initialization")
369 }
370
371 if o.CustomProfile != nil {
372 if o.CustomProfile.Name != "" || len(o.CustomProfile.Command) > 0 || o.CustomProfile.Image != "" || o.CustomProfile.Lifecycle != nil || len(o.CustomProfile.VolumeDevices) > 0 {
373 return fmt.Errorf("name, command, image, lifecycle and volume devices are not modifiable via custom profile")
374 }
375 }
376
377 return nil
378 }
379
380
381 func (o *DebugOptions) Run(restClientGetter genericclioptions.RESTClientGetter, cmd *cobra.Command) error {
382 ctx := context.Background()
383
384 r := o.Builder.
385 WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
386 FilenameParam(o.explicitNamespace, &o.FilenameOptions).
387 NamespaceParam(o.Namespace).DefaultNamespace().ResourceNames("pods", o.TargetNames...).
388 Do()
389 if err := r.Err(); err != nil {
390 return err
391 }
392
393 err := r.Visit(func(info *resource.Info, err error) error {
394 if err != nil {
395
396 return err
397 }
398
399 var (
400 debugPod *corev1.Pod
401 containerName string
402 visitErr error
403 )
404 switch obj := info.Object.(type) {
405 case *corev1.Node:
406 debugPod, containerName, visitErr = o.visitNode(ctx, obj)
407 case *corev1.Pod:
408 debugPod, containerName, visitErr = o.visitPod(ctx, obj)
409 default:
410 visitErr = fmt.Errorf("%q not supported by debug", info.Mapping.GroupVersionKind)
411 }
412 if visitErr != nil {
413 return visitErr
414 }
415
416 if o.Attach && len(containerName) > 0 && o.AttachFunc != nil {
417 if err := o.AttachFunc(ctx, restClientGetter, cmd.Parent().CommandPath(), debugPod.Namespace, debugPod.Name, containerName); err != nil {
418 return err
419 }
420 }
421
422 return nil
423 })
424
425 return err
426 }
427
428
429
430 func (o *DebugOptions) visitNode(ctx context.Context, node *corev1.Node) (*corev1.Pod, string, error) {
431 pods := o.podClient.Pods(o.Namespace)
432 debugPod, err := o.generateNodeDebugPod(node)
433 if err != nil {
434 return nil, "", err
435 }
436 newPod, err := pods.Create(ctx, debugPod, metav1.CreateOptions{})
437 if err != nil {
438 return nil, "", err
439 }
440
441 return newPod, newPod.Spec.Containers[0].Name, nil
442 }
443
444
445
446
447
448
449 func (o *DebugOptions) visitPod(ctx context.Context, pod *corev1.Pod) (*corev1.Pod, string, error) {
450 if len(o.CopyTo) > 0 {
451 return o.debugByCopy(ctx, pod)
452 }
453 return o.debugByEphemeralContainer(ctx, pod)
454 }
455
456
457 func (o *DebugOptions) debugByEphemeralContainer(ctx context.Context, pod *corev1.Pod) (*corev1.Pod, string, error) {
458 klog.V(2).Infof("existing ephemeral containers: %v", pod.Spec.EphemeralContainers)
459 podJS, err := json.Marshal(pod)
460 if err != nil {
461 return nil, "", fmt.Errorf("error creating JSON for pod: %v", err)
462 }
463
464 debugPod, debugContainer, err := o.generateDebugContainer(pod)
465 if err != nil {
466 return nil, "", err
467 }
468 klog.V(2).Infof("new ephemeral container: %#v", debugContainer)
469
470 debugJS, err := json.Marshal(debugPod)
471 if err != nil {
472 return nil, "", fmt.Errorf("error creating JSON for debug container: %v", err)
473 }
474
475 patch, err := strategicpatch.CreateTwoWayMergePatch(podJS, debugJS, pod)
476 if err != nil {
477 return nil, "", fmt.Errorf("error creating patch to add debug container: %v", err)
478 }
479 klog.V(2).Infof("generated strategic merge patch for debug container: %s", patch)
480
481 pods := o.podClient.Pods(pod.Namespace)
482 result, err := pods.Patch(ctx, pod.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}, "ephemeralcontainers")
483 if err != nil {
484
485
486 if serr, ok := err.(*errors.StatusError); ok && serr.Status().Reason == metav1.StatusReasonNotFound && serr.ErrStatus.Details.Name == "" {
487 return nil, "", fmt.Errorf("ephemeral containers are disabled for this cluster (error from server: %q)", err)
488 }
489
490 return nil, "", err
491 }
492
493 return result, debugContainer.Name, nil
494 }
495
496
497
498 func (o *DebugOptions) applyCustomProfile(debugPod *corev1.Pod, containerName string) error {
499 o.CustomProfile.Name = containerName
500 customJS, err := json.Marshal(o.CustomProfile)
501 if err != nil {
502 return fmt.Errorf("unable to marshall custom profile: %w", err)
503 }
504
505 var index int
506 found := false
507 for i, val := range debugPod.Spec.Containers {
508 if val.Name == containerName {
509 index = i
510 found = true
511 break
512 }
513 }
514
515 if !found {
516 return fmt.Errorf("unable to find the %s container in the pod %s", containerName, debugPod.Name)
517 }
518
519 var debugContainerJS []byte
520 debugContainerJS, err = json.Marshal(debugPod.Spec.Containers[index])
521 if err != nil {
522 return fmt.Errorf("unable to marshall container: %w", err)
523 }
524
525 patchedContainer, err := strategicpatch.StrategicMergePatch(debugContainerJS, customJS, corev1.Container{})
526 if err != nil {
527 return fmt.Errorf("error creating three way patch to add debug container: %w", err)
528 }
529
530 err = json.Unmarshal(patchedContainer, &debugPod.Spec.Containers[index])
531 if err != nil {
532 return fmt.Errorf("unable to unmarshall patched container to container: %w", err)
533 }
534
535 return nil
536 }
537
538
539
540 func (o *DebugOptions) applyCustomProfileEphemeral(debugPod *corev1.Pod, containerName string) error {
541 o.CustomProfile.Name = containerName
542 customJS, err := json.Marshal(o.CustomProfile)
543 if err != nil {
544 return fmt.Errorf("unable to marshall custom profile: %w", err)
545 }
546
547 var index int
548 found := false
549 for i, val := range debugPod.Spec.EphemeralContainers {
550 if val.Name == containerName {
551 index = i
552 found = true
553 break
554 }
555 }
556
557 if !found {
558 return fmt.Errorf("unable to find the %s ephemeral container in the pod %s", containerName, debugPod.Name)
559 }
560
561 var debugContainerJS []byte
562 debugContainerJS, err = json.Marshal(debugPod.Spec.EphemeralContainers[index])
563 if err != nil {
564 return fmt.Errorf("unable to marshall ephemeral container:%w", err)
565 }
566
567 patchedContainer, err := strategicpatch.StrategicMergePatch(debugContainerJS, customJS, corev1.Container{})
568 if err != nil {
569 return fmt.Errorf("error creating three way patch to add debug container: %w", err)
570 }
571
572 err = json.Unmarshal(patchedContainer, &debugPod.Spec.EphemeralContainers[index])
573 if err != nil {
574 return fmt.Errorf("unable to unmarshall patched container to ephemeral container: %w", err)
575 }
576
577 return nil
578 }
579
580
581 func (o *DebugOptions) debugByCopy(ctx context.Context, pod *corev1.Pod) (*corev1.Pod, string, error) {
582 copied, dc, err := o.generatePodCopyWithDebugContainer(pod)
583 if err != nil {
584 return nil, "", err
585 }
586 created, err := o.podClient.Pods(copied.Namespace).Create(ctx, copied, metav1.CreateOptions{})
587 if err != nil {
588 return nil, "", err
589 }
590 if o.Replace {
591 err := o.podClient.Pods(pod.Namespace).Delete(ctx, pod.Name, *metav1.NewDeleteOptions(0))
592 if err != nil {
593 return nil, "", err
594 }
595 }
596 return created, dc, nil
597 }
598
599
600
601 func (o *DebugOptions) generateDebugContainer(pod *corev1.Pod) (*corev1.Pod, *corev1.EphemeralContainer, error) {
602 name := o.computeDebugContainerName(pod)
603 ec := &corev1.EphemeralContainer{
604 EphemeralContainerCommon: corev1.EphemeralContainerCommon{
605 Name: name,
606 Env: o.Env,
607 Image: o.Image,
608 ImagePullPolicy: o.PullPolicy,
609 Stdin: o.Interactive,
610 TerminationMessagePolicy: corev1.TerminationMessageReadFile,
611 TTY: o.TTY,
612 },
613 TargetContainerName: o.TargetContainer,
614 }
615
616 if o.ArgsOnly {
617 ec.Args = o.Args
618 } else {
619 ec.Command = o.Args
620 }
621
622 copied := pod.DeepCopy()
623 copied.Spec.EphemeralContainers = append(copied.Spec.EphemeralContainers, *ec)
624 if err := o.Applier.Apply(copied, name, copied); err != nil {
625 return nil, nil, err
626 }
627
628 if o.CustomProfile != nil {
629 err := o.applyCustomProfileEphemeral(copied, ec.Name)
630 if err != nil {
631 return nil, nil, err
632 }
633 }
634
635 ec = &copied.Spec.EphemeralContainers[len(copied.Spec.EphemeralContainers)-1]
636
637 return copied, ec, nil
638 }
639
640
641
642 func (o *DebugOptions) generateNodeDebugPod(node *corev1.Node) (*corev1.Pod, error) {
643 cn := "debugger"
644
645
646 if len(o.Container) > 0 {
647 cn = o.Container
648 }
649
650
651
652
653 pn := fmt.Sprintf("node-debugger-%s-%s", node.Name, nameSuffixFunc(5))
654 if !o.Quiet {
655 fmt.Fprintf(o.Out, "Creating debugging pod %s with container %s on node %s.\n", pn, cn, node.Name)
656 }
657
658 p := &corev1.Pod{
659 ObjectMeta: metav1.ObjectMeta{
660 Name: pn,
661 },
662 Spec: corev1.PodSpec{
663 Containers: []corev1.Container{
664 {
665 Name: cn,
666 Env: o.Env,
667 Image: o.Image,
668 ImagePullPolicy: o.PullPolicy,
669 Stdin: o.Interactive,
670 TerminationMessagePolicy: corev1.TerminationMessageReadFile,
671 TTY: o.TTY,
672 },
673 },
674 NodeName: node.Name,
675 RestartPolicy: corev1.RestartPolicyNever,
676 Tolerations: []corev1.Toleration{
677 {
678 Operator: corev1.TolerationOpExists,
679 },
680 },
681 },
682 }
683
684 if o.ArgsOnly {
685 p.Spec.Containers[0].Args = o.Args
686 } else {
687 p.Spec.Containers[0].Command = o.Args
688 }
689
690 if err := o.Applier.Apply(p, cn, node); err != nil {
691 return nil, err
692 }
693
694 if o.CustomProfile != nil {
695 err := o.applyCustomProfile(p, cn)
696 if err != nil {
697 return nil, err
698 }
699 }
700
701 return p, nil
702 }
703
704
705 func (o *DebugOptions) generatePodCopyWithDebugContainer(pod *corev1.Pod) (*corev1.Pod, string, error) {
706 copied := &corev1.Pod{
707 ObjectMeta: metav1.ObjectMeta{
708 Name: o.CopyTo,
709 Namespace: pod.Namespace,
710 Annotations: pod.Annotations,
711 },
712 Spec: *pod.Spec.DeepCopy(),
713 }
714
715 copied.Spec.EphemeralContainers = nil
716
717 if o.shareProcessedChanged {
718 copied.Spec.ShareProcessNamespace = pointer.Bool(o.ShareProcesses)
719 }
720 if !o.SameNode {
721 copied.Spec.NodeName = ""
722 }
723
724
725 for i, c := range copied.Spec.Containers {
726 override := o.SetImages["*"]
727 if img, ok := o.SetImages[c.Name]; ok {
728 override = img
729 }
730 if len(override) > 0 {
731 copied.Spec.Containers[i].Image = override
732 }
733 }
734
735 name, containerByName := o.Container, containerNameToRef(copied)
736
737 c, ok := containerByName[name]
738 if !ok {
739
740 if len(o.Image) == 0 {
741 if len(o.SetImages) > 0 {
742
743 return copied, "", nil
744 }
745 return nil, "", fmt.Errorf("you must specify image when creating new container")
746 }
747
748 if len(name) == 0 {
749 name = o.computeDebugContainerName(copied)
750 }
751 copied.Spec.Containers = append(copied.Spec.Containers, corev1.Container{
752 Name: name,
753 TerminationMessagePolicy: corev1.TerminationMessageReadFile,
754 })
755 c = &copied.Spec.Containers[len(copied.Spec.Containers)-1]
756 }
757
758 if len(o.Args) > 0 {
759 if o.ArgsOnly {
760 c.Args = o.Args
761 } else {
762 c.Command = o.Args
763 c.Args = nil
764 }
765 }
766 if len(o.Env) > 0 {
767 c.Env = o.Env
768 }
769 if len(o.Image) > 0 {
770 c.Image = o.Image
771 }
772 if len(o.PullPolicy) > 0 {
773 c.ImagePullPolicy = o.PullPolicy
774 }
775 c.Stdin = o.Interactive
776 c.TTY = o.TTY
777
778 err := o.Applier.Apply(copied, c.Name, pod)
779 if err != nil {
780 return nil, "", err
781 }
782
783 if o.CustomProfile != nil {
784 err = o.applyCustomProfile(copied, name)
785 if err != nil {
786 return nil, "", err
787 }
788 }
789
790 return copied, name, nil
791 }
792
793 func (o *DebugOptions) computeDebugContainerName(pod *corev1.Pod) string {
794 if len(o.Container) > 0 {
795 return o.Container
796 }
797
798 cn, containerByName := "", containerNameToRef(pod)
799 for len(cn) == 0 || (containerByName[cn] != nil) {
800 cn = fmt.Sprintf("debugger-%s", nameSuffixFunc(5))
801 }
802 if !o.Quiet {
803 fmt.Fprintf(o.Out, "Defaulting debug container name to %s.\n", cn)
804 }
805 return cn
806 }
807
808 func containerNameToRef(pod *corev1.Pod) map[string]*corev1.Container {
809 names := map[string]*corev1.Container{}
810 for i := range pod.Spec.Containers {
811 ref := &pod.Spec.Containers[i]
812 names[ref.Name] = ref
813 }
814 for i := range pod.Spec.InitContainers {
815 ref := &pod.Spec.InitContainers[i]
816 names[ref.Name] = ref
817 }
818 for i := range pod.Spec.EphemeralContainers {
819 ref := (*corev1.Container)(&pod.Spec.EphemeralContainers[i].EphemeralContainerCommon)
820 names[ref.Name] = ref
821 }
822 return names
823 }
824
825
826 func (o *DebugOptions) waitForContainer(ctx context.Context, ns, podName, containerName string) (*corev1.Pod, error) {
827
828 ctx, cancel := watchtools.ContextWithOptionalTimeout(ctx, 0*time.Second)
829 defer cancel()
830
831 fieldSelector := fields.OneTermEqualSelector("metadata.name", podName).String()
832 lw := &cache.ListWatch{
833 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
834 options.FieldSelector = fieldSelector
835 return o.podClient.Pods(ns).List(ctx, options)
836 },
837 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
838 options.FieldSelector = fieldSelector
839 return o.podClient.Pods(ns).Watch(ctx, options)
840 },
841 }
842
843 intr := interrupt.New(nil, cancel)
844 var result *corev1.Pod
845 err := intr.Run(func() error {
846 ev, err := watchtools.UntilWithSync(ctx, lw, &corev1.Pod{}, nil, func(ev watch.Event) (bool, error) {
847 klog.V(2).Infof("watch received event %q with object %T", ev.Type, ev.Object)
848 switch ev.Type {
849 case watch.Deleted:
850 return false, errors.NewNotFound(schema.GroupResource{Resource: "pods"}, "")
851 }
852
853 p, ok := ev.Object.(*corev1.Pod)
854 if !ok {
855 return false, fmt.Errorf("watch did not return a pod: %v", ev.Object)
856 }
857
858 s := getContainerStatusByName(p, containerName)
859 if s == nil {
860 return false, nil
861 }
862 klog.V(2).Infof("debug container status is %v", s)
863 if s.State.Running != nil || s.State.Terminated != nil {
864 return true, nil
865 }
866 if !o.Quiet && s.State.Waiting != nil && s.State.Waiting.Message != "" {
867 o.WarningPrinter.Print(fmt.Sprintf("container %s: %s", containerName, s.State.Waiting.Message))
868 }
869 return false, nil
870 })
871 if ev != nil {
872 result = ev.Object.(*corev1.Pod)
873 }
874 return err
875 })
876
877 return result, err
878 }
879
880 func (o *DebugOptions) handleAttachPod(ctx context.Context, restClientGetter genericclioptions.RESTClientGetter, cmdPath string, ns, podName, containerName string) error {
881 opts := &attach.AttachOptions{
882 StreamOptions: exec.StreamOptions{
883 IOStreams: o.IOStreams,
884 Stdin: o.Interactive,
885 TTY: o.TTY,
886 Quiet: o.Quiet,
887 },
888 CommandName: cmdPath + " attach",
889
890 Attach: &attach.DefaultRemoteAttach{},
891 }
892 config, err := restClientGetter.ToRESTConfig()
893 if err != nil {
894 return err
895 }
896 opts.Config = config
897 opts.AttachFunc = attach.DefaultAttachFunc
898
899 pod, err := o.waitForContainer(ctx, ns, podName, containerName)
900 if err != nil {
901 return err
902 }
903
904 opts.Namespace = ns
905 opts.Pod = pod
906 opts.PodName = podName
907 opts.ContainerName = containerName
908 if opts.AttachFunc == nil {
909 opts.AttachFunc = attach.DefaultAttachFunc
910 }
911
912 status := getContainerStatusByName(pod, containerName)
913 if status == nil {
914
915 return fmt.Errorf("error getting container status of container name %q: %+v", containerName, err)
916 }
917 if status.State.Terminated != nil {
918 klog.V(1).Info("Ephemeral container terminated, falling back to logs")
919 return logOpts(restClientGetter, pod, opts)
920 }
921
922 if err := opts.Run(); err != nil {
923 fmt.Fprintf(opts.ErrOut, "warning: couldn't attach to pod/%s, falling back to streaming logs: %v\n", podName, err)
924 return logOpts(restClientGetter, pod, opts)
925 }
926 return nil
927 }
928
929 func getContainerStatusByName(pod *corev1.Pod, containerName string) *corev1.ContainerStatus {
930 allContainerStatus := [][]corev1.ContainerStatus{pod.Status.InitContainerStatuses, pod.Status.ContainerStatuses, pod.Status.EphemeralContainerStatuses}
931 for _, statusSlice := range allContainerStatus {
932 for i := range statusSlice {
933 if statusSlice[i].Name == containerName {
934 return &statusSlice[i]
935 }
936 }
937 }
938 return nil
939 }
940
941
942 func logOpts(restClientGetter genericclioptions.RESTClientGetter, pod *corev1.Pod, opts *attach.AttachOptions) error {
943 ctrName, err := opts.GetContainerName(pod)
944 if err != nil {
945 return err
946 }
947
948 requests, err := polymorphichelpers.LogsForObjectFn(restClientGetter, pod, &corev1.PodLogOptions{Container: ctrName}, opts.GetPodTimeout, false)
949 if err != nil {
950 return err
951 }
952 for _, request := range requests {
953 if err := logs.DefaultConsumeRequest(request, opts.Out); err != nil {
954 return err
955 }
956 }
957
958 return nil
959 }
960
View as plain text