diff --git a/cmd/exec/exec.go b/cmd/exec/exec.go index 754df51..f70a0f6 100644 --- a/cmd/exec/exec.go +++ b/cmd/exec/exec.go @@ -55,7 +55,10 @@ const ( cdebug exec --namespace=myns -it pod/mypod # Start a shell in a Kubernetes pod's container: - cdebug exec -it pod/mypod/mycontainer` + cdebug exec -it pod/mypod/mycontainer + + # Start a shell on a Kubernetes node: + cdebug exec -it node/mynode` ) var ( @@ -123,7 +126,7 @@ func NewCommand(cli cliutil.CLI) *cobra.Command { if sep := strings.Index(opts.target, "://"); sep != -1 { opts.schema = opts.target[:sep+3] opts.target = opts.target[sep+3:] - } else if strings.HasPrefix(opts.target, "pod/") || strings.HasPrefix(opts.target, "pods/") { + } else if strings.HasPrefix(opts.target, "pod/") || strings.HasPrefix(opts.target, "pods/") || strings.HasPrefix(opts.target, "node/") || strings.HasPrefix(opts.target, "nodes/") { opts.schema = schemaKubeLong } else { opts.schema = schemaDocker @@ -353,7 +356,7 @@ func debuggerEntrypoint( cli, simpleEntrypoint, map[string]any{ - "PID": targetPID, + "TARGET_PID": targetPID, "Cmd": func() string { if len(cmd) == 0 { return "sh" diff --git a/cmd/exec/exec_kubernetes.go b/cmd/exec/exec_kubernetes.go index 8103dd0..0f51c46 100644 --- a/cmd/exec/exec_kubernetes.go +++ b/cmd/exec/exec_kubernetes.go @@ -66,6 +66,51 @@ func runDebuggerKubernetes(ctx context.Context, cli cliutil.CLI, opts *options) namespace = "default" } + var ( + podName string + debuggerName string + ephemeral bool + ) + if strings.HasPrefix(opts.target, "node/") || strings.HasPrefix(opts.target, "nodes/") { + podName, debuggerName, err = runNodeDebugger(ctx, cli, opts, namespace, client) + } else { + podName, debuggerName, err = runPodDebugger(ctx, cli, opts, namespace, client) + ephemeral = true + } + + if err != nil { + return fmt.Errorf("error creating debugger: %v", err) + } + + if opts.detach { + attachCmd := []string{"kubectl", "attach", "-n", namespace, "-c", debuggerName} + if opts.stdin { + attachCmd = append(attachCmd, "-i") + } + if opts.tty { + attachCmd = append(attachCmd, "-t") + } + attachCmd = append(attachCmd, podName) + + cli.PrintAux("Debugger container %q started in the background.\n", debuggerName) + cli.PrintAux("Use %#q if you need to attach to it.\n", strings.Join(attachCmd, " ")) + return nil + } + + return attachPodDebugger( + ctx, + cli, + opts, + config, + client, + namespace, + podName, + debuggerName, + ephemeral, + ) +} + +func runPodDebugger(ctx context.Context, cli cliutil.CLI, opts *options, namespace string, client kubernetes.Interface) (string, string, error) { var ( podName string targetName string @@ -84,7 +129,7 @@ func runDebuggerKubernetes(ctx context.Context, cli cliutil.CLI, opts *options) Pods(namespace). Get(ctx, podName, metav1.GetOptions{}) if err != nil { - return fmt.Errorf("error getting target pod: %v", err) + return "", "", fmt.Errorf("error getting target pod: %v", err) } runID := uuid.ShortID() @@ -94,7 +139,7 @@ func runDebuggerKubernetes(ctx context.Context, cli cliutil.CLI, opts *options) cli.PrintAux("Starting debugger container...\n") useChroot := isRootUser(opts.user) && !isReadOnlyRootFS(pod, targetName) && !runsAsNonRoot(pod, targetName) - if err := runPodDebugger( + if err := addEphemeralDebugger( ctx, cli, opts, @@ -104,37 +149,82 @@ func runDebuggerKubernetes(ctx context.Context, cli cliutil.CLI, opts *options) debuggerName, debuggerEntrypoint(cli, runID, 1, opts.image, opts.cmd, useChroot), ); err != nil { - return fmt.Errorf("error adding debugger container: %v", err) + return "", "", fmt.Errorf("error adding debugger container: %v", err) } - if opts.detach { - attachCmd := []string{"kubectl", "attach", "-n", namespace, "-c", debuggerName} - if opts.stdin { - attachCmd = append(attachCmd, "-i") - } - if opts.tty { - attachCmd = append(attachCmd, "-t") - } - attachCmd = append(attachCmd, podName) + return podName, debuggerName, nil +} - cli.PrintAux("Debugger container %q started in the background.\n", debuggerName) - cli.PrintAux("Use %#q if you need to attach to it.\n", strings.Join(attachCmd, " ")) - return nil +func runNodeDebugger(ctx context.Context, cli cliutil.CLI, opts *options, namespace string, client kubernetes.Interface) (string, string, error) { + opts.target = strings.TrimPrefix(opts.target, "node/") + opts.target = strings.TrimPrefix(opts.target, "nodes/") + + runID := uuid.ShortID() + podName := fmt.Sprintf("cdebug-%s-%s", opts.target, runID) + debuggerName := "cdebug" + volumeName := "host-root" + + p := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Namespace: namespace, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: debuggerName, + Image: opts.image, + ImagePullPolicy: corev1.PullIfNotPresent, + Command: []string{"sh", "-c", debuggerEntrypoint(cli, runID, 1, opts.image, opts.cmd, false)}, + Stdin: opts.stdin, + TTY: opts.tty, + // Env: TODO... + // VolumeDevices: TODO... + SecurityContext: &corev1.SecurityContext{ + Privileged: &opts.privileged, + RunAsUser: uidPtr(opts.user), + RunAsGroup: gidPtr(opts.user), + }, + TerminationMessagePolicy: corev1.TerminationMessageReadFile, + VolumeMounts: []corev1.VolumeMount{ + { + Name: volumeName, + MountPath: "/host", + }, + }, + }, + }, + // Share host's network, PID and IPC with debug pod + HostNetwork: true, + HostPID: true, + HostIPC: true, + + NodeName: opts.target, + RestartPolicy: corev1.RestartPolicyNever, + Tolerations: []corev1.Toleration{ + { + Operator: corev1.TolerationOpExists, + }, + }, + Volumes: []corev1.Volume{ + { + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{Path: "/"}, + }, + }, + }, + }, } - return attachPodDebugger( - ctx, - cli, - opts, - config, - client, - namespace, - podName, - debuggerName, - ) + if _, err := client.CoreV1().Pods(namespace).Create(ctx, p, metav1.CreateOptions{}); err != nil { + return "", "", fmt.Errorf("error creating debug pod: %v", err) + } + + return podName, debuggerName, nil } -func runPodDebugger( +func addEphemeralDebugger( ctx context.Context, cli cliutil.CLI, opts *options, @@ -316,6 +406,7 @@ func attachPodDebugger( ns string, podName string, debuggerName string, + ephemeral bool, ) error { cli.PrintAux("Waiting for debugger container...\n") pod, err := waitForContainer(ctx, client, ns, podName, debuggerName, true) @@ -343,7 +434,13 @@ func attachPodDebugger( status.State.Terminated.ExitCode) } - debuggerContainer := ephemeralContainerByName(pod, debuggerName) + var debuggerContainer *corev1.Container + if ephemeral { + debuggerContainer = ephemeralContainerByName(pod, debuggerName) + } else { + debuggerContainer = containerByName(pod, debuggerName) + + } if debuggerContainer == nil { return fmt.Errorf("cannot find debugger container %q in pod %q", debuggerName, podName) } @@ -545,10 +642,11 @@ func containerByName(pod *corev1.Pod, containerName string) *corev1.Container { return nil } -func ephemeralContainerByName(pod *corev1.Pod, containerName string) *corev1.EphemeralContainer { +func ephemeralContainerByName(pod *corev1.Pod, containerName string) *corev1.Container { for i := range pod.Spec.EphemeralContainers { if pod.Spec.EphemeralContainers[i].Name == containerName { - return &pod.Spec.EphemeralContainers[i] + c := corev1.Container(pod.Spec.EphemeralContainers[i].EphemeralContainerCommon) + return &c } } return nil