mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 20:24:09 +00:00
Merge pull request #90094 from aylei/aylei/debug-by-copy
Add debug by copy support for kubectl alpha debug command
This commit is contained in:
commit
8d7aaa5e7b
@ -32,6 +32,7 @@ go_library(
|
|||||||
"//vendor/github.com/docker/distribution/reference:go_default_library",
|
"//vendor/github.com/docker/distribution/reference:go_default_library",
|
||||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||||
"//vendor/k8s.io/klog/v2:go_default_library",
|
"//vendor/k8s.io/klog/v2:go_default_library",
|
||||||
|
"//vendor/k8s.io/utils/pointer:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -55,7 +56,9 @@ go_test(
|
|||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
deps = [
|
deps = [
|
||||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
|
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
|
||||||
"//vendor/github.com/google/go-cmp/cmp:go_default_library",
|
"//vendor/github.com/google/go-cmp/cmp:go_default_library",
|
||||||
|
"//vendor/k8s.io/utils/pointer:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
@ -37,7 +38,6 @@ import (
|
|||||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
watchtools "k8s.io/client-go/tools/watch"
|
watchtools "k8s.io/client-go/tools/watch"
|
||||||
"k8s.io/klog/v2"
|
|
||||||
"k8s.io/kubectl/pkg/cmd/attach"
|
"k8s.io/kubectl/pkg/cmd/attach"
|
||||||
"k8s.io/kubectl/pkg/cmd/exec"
|
"k8s.io/kubectl/pkg/cmd/exec"
|
||||||
"k8s.io/kubectl/pkg/cmd/logs"
|
"k8s.io/kubectl/pkg/cmd/logs"
|
||||||
@ -47,6 +47,7 @@ import (
|
|||||||
"k8s.io/kubectl/pkg/util/i18n"
|
"k8s.io/kubectl/pkg/util/i18n"
|
||||||
"k8s.io/kubectl/pkg/util/interrupt"
|
"k8s.io/kubectl/pkg/util/interrupt"
|
||||||
"k8s.io/kubectl/pkg/util/templates"
|
"k8s.io/kubectl/pkg/util/templates"
|
||||||
|
"k8s.io/utils/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -59,26 +60,39 @@ var (
|
|||||||
|
|
||||||
# Create a debug container named debugger using a custom automated debugging image.
|
# Create a debug container named debugger using a custom automated debugging image.
|
||||||
# (requires the EphemeralContainers feature to be enabled in the cluster)
|
# (requires the EphemeralContainers feature to be enabled in the cluster)
|
||||||
kubectl alpha debug --image=myproj/debug-tools -c debugger mypod`))
|
kubectl alpha debug --image=myproj/debug-tools -c debugger mypod
|
||||||
|
|
||||||
|
# Create a debug container as a copy of the original Pod and attach to it
|
||||||
|
kubectl alpha debug mypod -it --image=busybox --copy-to=my-debugger
|
||||||
|
|
||||||
|
# Create a copy of mypod named my-debugger with my-container's image changed to busybox
|
||||||
|
kubectl alpha debug mypod --image=busybox --container=my-container --copy-to=my-debugger -- sleep 1d
|
||||||
|
`))
|
||||||
)
|
)
|
||||||
|
|
||||||
var nameSuffixFunc = utilrand.String
|
var nameSuffixFunc = utilrand.String
|
||||||
|
|
||||||
// DebugOptions holds the options for an invocation of kubectl debug.
|
// DebugOptions holds the options for an invocation of kubectl debug.
|
||||||
type DebugOptions struct {
|
type DebugOptions struct {
|
||||||
Args []string
|
Args []string
|
||||||
ArgsOnly bool
|
ArgsOnly bool
|
||||||
Attach bool
|
Attach bool
|
||||||
Container string
|
Container string
|
||||||
Env []corev1.EnvVar
|
CopyTo string
|
||||||
Image string
|
Replace bool
|
||||||
Interactive bool
|
Env []corev1.EnvVar
|
||||||
Namespace string
|
Image string
|
||||||
TargetNames []string
|
Interactive bool
|
||||||
PullPolicy corev1.PullPolicy
|
Namespace string
|
||||||
Quiet bool
|
TargetNames []string
|
||||||
Target string
|
PullPolicy corev1.PullPolicy
|
||||||
TTY bool
|
Quiet bool
|
||||||
|
SameNode bool
|
||||||
|
ShareProcesses bool
|
||||||
|
Target string
|
||||||
|
TTY bool
|
||||||
|
|
||||||
|
shareProcessedChanged bool
|
||||||
|
|
||||||
builder *resource.Builder
|
builder *resource.Builder
|
||||||
podClient corev1client.PodsGetter
|
podClient corev1client.PodsGetter
|
||||||
@ -89,9 +103,10 @@ type DebugOptions struct {
|
|||||||
// NewDebugOptions returns a DebugOptions initialized with default values.
|
// NewDebugOptions returns a DebugOptions initialized with default values.
|
||||||
func NewDebugOptions(streams genericclioptions.IOStreams) *DebugOptions {
|
func NewDebugOptions(streams genericclioptions.IOStreams) *DebugOptions {
|
||||||
return &DebugOptions{
|
return &DebugOptions{
|
||||||
Args: []string{},
|
Args: []string{},
|
||||||
IOStreams: streams,
|
IOStreams: streams,
|
||||||
TargetNames: []string{},
|
TargetNames: []string{},
|
||||||
|
ShareProcesses: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,12 +135,16 @@ func addDebugFlags(cmd *cobra.Command, opt *DebugOptions) {
|
|||||||
cmd.Flags().BoolVar(&opt.ArgsOnly, "arguments-only", opt.ArgsOnly, i18n.T("If specified, everything after -- will be passed to the new container as Args instead of Command."))
|
cmd.Flags().BoolVar(&opt.ArgsOnly, "arguments-only", opt.ArgsOnly, i18n.T("If specified, everything after -- will be passed to the new container as Args instead of Command."))
|
||||||
cmd.Flags().BoolVar(&opt.Attach, "attach", opt.Attach, i18n.T("If true, wait for the Pod to start running, and then attach to the Pod as if 'kubectl attach ...' were called. Default false, unless '-i/--stdin' is set, in which case the default is true."))
|
cmd.Flags().BoolVar(&opt.Attach, "attach", opt.Attach, i18n.T("If true, wait for the Pod to start running, and then attach to the Pod as if 'kubectl attach ...' were called. Default false, unless '-i/--stdin' is set, in which case the default is true."))
|
||||||
cmd.Flags().StringVarP(&opt.Container, "container", "c", opt.Container, i18n.T("Container name to use for debug container."))
|
cmd.Flags().StringVarP(&opt.Container, "container", "c", opt.Container, i18n.T("Container name to use for debug container."))
|
||||||
|
cmd.Flags().StringVar(&opt.CopyTo, "copy-to", opt.CopyTo, i18n.T("Create a copy of the target Pod with this name."))
|
||||||
|
cmd.Flags().BoolVar(&opt.Replace, "replace", opt.Replace, i18n.T("When used with '--copy-to', delete the original Pod"))
|
||||||
cmd.Flags().StringToString("env", nil, i18n.T("Environment variables to set in the container."))
|
cmd.Flags().StringToString("env", nil, i18n.T("Environment variables to set in the container."))
|
||||||
cmd.Flags().StringVar(&opt.Image, "image", opt.Image, i18n.T("Container image to use for debug container."))
|
cmd.Flags().StringVar(&opt.Image, "image", opt.Image, i18n.T("Container image to use for debug container."))
|
||||||
cmd.MarkFlagRequired("image")
|
cmd.MarkFlagRequired("image")
|
||||||
cmd.Flags().String("image-pull-policy", string(corev1.PullIfNotPresent), i18n.T("The image pull policy for the container."))
|
cmd.Flags().String("image-pull-policy", string(corev1.PullIfNotPresent), i18n.T("The image pull policy for the container."))
|
||||||
cmd.Flags().BoolVarP(&opt.Interactive, "stdin", "i", opt.Interactive, i18n.T("Keep stdin open on the container(s) in the pod, even if nothing is attached."))
|
cmd.Flags().BoolVarP(&opt.Interactive, "stdin", "i", opt.Interactive, i18n.T("Keep stdin open on the container(s) in the pod, even if nothing is attached."))
|
||||||
cmd.Flags().BoolVar(&opt.Quiet, "quiet", opt.Quiet, i18n.T("If true, suppress prompt messages."))
|
cmd.Flags().BoolVar(&opt.Quiet, "quiet", opt.Quiet, i18n.T("If true, suppress prompt messages."))
|
||||||
|
cmd.Flags().BoolVar(&opt.SameNode, "same-node", opt.SameNode, i18n.T("Schedule the copy of target Pod on the same node."))
|
||||||
|
cmd.Flags().BoolVar(&opt.ShareProcesses, "share-processes", opt.ShareProcesses, i18n.T("When used with '--copy-to', enable process namespace sharing in the copy."))
|
||||||
cmd.Flags().StringVar(&opt.Target, "target", "", i18n.T("Target processes in this container name."))
|
cmd.Flags().StringVar(&opt.Target, "target", "", i18n.T("Target processes in this container name."))
|
||||||
cmd.Flags().BoolVarP(&opt.TTY, "tty", "t", opt.TTY, i18n.T("Allocated a TTY for each container in the pod."))
|
cmd.Flags().BoolVarP(&opt.TTY, "tty", "t", opt.TTY, i18n.T("Allocated a TTY for each container in the pod."))
|
||||||
}
|
}
|
||||||
@ -173,6 +192,9 @@ func (o *DebugOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
|
|||||||
}
|
}
|
||||||
o.podClient = clientset.CoreV1()
|
o.podClient = clientset.CoreV1()
|
||||||
|
|
||||||
|
// Share processes
|
||||||
|
o.shareProcessedChanged = cmd.Flags().Changed("share-processes")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,9 +295,17 @@ func (o *DebugOptions) Run(f cmdutil.Factory, cmd *cobra.Command) error {
|
|||||||
|
|
||||||
// visitPod handles debugging for pod targets by (depending on options):
|
// visitPod handles debugging for pod targets by (depending on options):
|
||||||
// 1. Creating an ephemeral debug container in an existing pod, OR
|
// 1. Creating an ephemeral debug container in an existing pod, OR
|
||||||
// 2. Making a copy of pod with certain attributes changed (NOT YET IMPLEMENTED)
|
// 2. Making a copy of pod with certain attributes changed
|
||||||
// visitPod returns a pod and debug container name for subsequent attach, if applicable.
|
// visitPod returns a pod and debug container name for subsequent attach, if applicable.
|
||||||
func (o *DebugOptions) visitPod(ctx context.Context, pod *corev1.Pod) (*corev1.Pod, string, error) {
|
func (o *DebugOptions) visitPod(ctx context.Context, pod *corev1.Pod) (*corev1.Pod, string, error) {
|
||||||
|
if len(o.CopyTo) > 0 {
|
||||||
|
return o.debugByCopy(ctx, pod)
|
||||||
|
}
|
||||||
|
return o.debugByEphemeralContainer(ctx, pod)
|
||||||
|
}
|
||||||
|
|
||||||
|
// debugByEphemeralContainer runs an EphemeralContainer in the target Pod for use as a debug container
|
||||||
|
func (o *DebugOptions) debugByEphemeralContainer(ctx context.Context, pod *corev1.Pod) (*corev1.Pod, string, error) {
|
||||||
pods := o.podClient.Pods(pod.Namespace)
|
pods := o.podClient.Pods(pod.Namespace)
|
||||||
ec, err := pods.GetEphemeralContainers(ctx, pod.Name, metav1.GetOptions{})
|
ec, err := pods.GetEphemeralContainers(ctx, pod.Name, metav1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -298,34 +328,26 @@ func (o *DebugOptions) visitPod(ctx context.Context, pod *corev1.Pod) (*corev1.P
|
|||||||
return pod, debugContainer.Name, nil
|
return pod, debugContainer.Name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func containerNames(pod *corev1.Pod) map[string]bool {
|
// debugByCopy runs a copy of the target Pod with a debug container added or an original container modified
|
||||||
names := map[string]bool{}
|
func (o *DebugOptions) debugByCopy(ctx context.Context, pod *corev1.Pod) (*corev1.Pod, string, error) {
|
||||||
for _, c := range pod.Spec.Containers {
|
copied, dc := o.generatePodCopyWithDebugContainer(pod)
|
||||||
names[c.Name] = true
|
copied, err := o.podClient.Pods(copied.Namespace).Create(ctx, copied, metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
}
|
}
|
||||||
for _, c := range pod.Spec.InitContainers {
|
if o.Replace {
|
||||||
names[c.Name] = true
|
err := o.podClient.Pods(pod.Namespace).Delete(ctx, pod.Name, *metav1.NewDeleteOptions(0))
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, c := range pod.Spec.EphemeralContainers {
|
return copied, dc, nil
|
||||||
names[c.Name] = true
|
|
||||||
}
|
|
||||||
return names
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateDebugContainer returns an EphemeralContainer suitable for use as a debug container
|
// generateDebugContainer returns an EphemeralContainer suitable for use as a debug container
|
||||||
// in the given pod.
|
// in the given pod.
|
||||||
func (o *DebugOptions) generateDebugContainer(pod *corev1.Pod) *corev1.EphemeralContainer {
|
func (o *DebugOptions) generateDebugContainer(pod *corev1.Pod) *corev1.EphemeralContainer {
|
||||||
name := o.Container
|
name := o.computeDebugContainerName(pod)
|
||||||
if len(name) == 0 {
|
|
||||||
cn, existing := "", containerNames(pod)
|
|
||||||
for len(cn) == 0 || existing[cn] {
|
|
||||||
cn = fmt.Sprintf("debugger-%s", nameSuffixFunc(5))
|
|
||||||
}
|
|
||||||
if !o.Quiet {
|
|
||||||
fmt.Fprintf(o.ErrOut, "Defaulting debug container name to %s.\n", cn)
|
|
||||||
}
|
|
||||||
name = cn
|
|
||||||
}
|
|
||||||
|
|
||||||
ec := &corev1.EphemeralContainer{
|
ec := &corev1.EphemeralContainer{
|
||||||
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
|
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
|
||||||
@ -349,8 +371,87 @@ func (o *DebugOptions) generateDebugContainer(pod *corev1.Pod) *corev1.Ephemeral
|
|||||||
return ec
|
return ec
|
||||||
}
|
}
|
||||||
|
|
||||||
// waitForEphemeralContainer watches the given pod until the ephemeralContainer is running
|
// generatePodCopy takes a Pod and returns a copy and the debug container name of that copy
|
||||||
func waitForEphemeralContainer(ctx context.Context, podClient corev1client.PodsGetter, ns, podName, ephemeralContainerName string) (*corev1.Pod, error) {
|
func (o *DebugOptions) generatePodCopyWithDebugContainer(pod *corev1.Pod) (*corev1.Pod, string) {
|
||||||
|
copied := &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: o.CopyTo,
|
||||||
|
Namespace: pod.Namespace,
|
||||||
|
Annotations: pod.Annotations,
|
||||||
|
},
|
||||||
|
Spec: *pod.Spec.DeepCopy(),
|
||||||
|
}
|
||||||
|
// change ShareProcessNamespace configuration only when commanded explicitly
|
||||||
|
if o.shareProcessedChanged {
|
||||||
|
copied.Spec.ShareProcessNamespace = pointer.BoolPtr(o.ShareProcesses)
|
||||||
|
}
|
||||||
|
if !o.SameNode {
|
||||||
|
copied.Spec.NodeName = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
containerByName := containerNameToRef(copied)
|
||||||
|
c, containerExists := containerByName[o.Container]
|
||||||
|
// Add a new container if the specified container does not exist
|
||||||
|
if !containerExists {
|
||||||
|
name := o.computeDebugContainerName(copied)
|
||||||
|
c = &corev1.Container{Name: name}
|
||||||
|
// envs are customizable when adding new container
|
||||||
|
c.Env = o.Env
|
||||||
|
}
|
||||||
|
c.Image = o.Image
|
||||||
|
c.ImagePullPolicy = o.PullPolicy
|
||||||
|
c.Stdin = o.Interactive
|
||||||
|
c.TerminationMessagePolicy = corev1.TerminationMessageReadFile
|
||||||
|
c.TTY = o.TTY
|
||||||
|
if o.ArgsOnly {
|
||||||
|
c.Args = o.Args
|
||||||
|
} else {
|
||||||
|
c.Command = o.Args
|
||||||
|
c.Args = nil
|
||||||
|
}
|
||||||
|
if !containerExists {
|
||||||
|
copied.Spec.Containers = append(copied.Spec.Containers, *c)
|
||||||
|
}
|
||||||
|
return copied, c.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *DebugOptions) computeDebugContainerName(pod *corev1.Pod) string {
|
||||||
|
if len(o.Container) > 0 {
|
||||||
|
return o.Container
|
||||||
|
}
|
||||||
|
name := o.Container
|
||||||
|
if len(name) == 0 {
|
||||||
|
cn, containerByName := "", containerNameToRef(pod)
|
||||||
|
for len(cn) == 0 || (containerByName[cn] != nil) {
|
||||||
|
cn = fmt.Sprintf("debugger-%s", nameSuffixFunc(5))
|
||||||
|
}
|
||||||
|
if !o.Quiet {
|
||||||
|
fmt.Fprintf(o.ErrOut, "Defaulting debug container name to %s.\n", cn)
|
||||||
|
}
|
||||||
|
name = cn
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func containerNameToRef(pod *corev1.Pod) map[string]*corev1.Container {
|
||||||
|
names := map[string]*corev1.Container{}
|
||||||
|
for i := range pod.Spec.Containers {
|
||||||
|
ref := &pod.Spec.Containers[i]
|
||||||
|
names[ref.Name] = ref
|
||||||
|
}
|
||||||
|
for i := range pod.Spec.InitContainers {
|
||||||
|
ref := &pod.Spec.Containers[i]
|
||||||
|
names[ref.Name] = ref
|
||||||
|
}
|
||||||
|
for i := range pod.Spec.EphemeralContainers {
|
||||||
|
ref := &pod.Spec.Containers[i]
|
||||||
|
names[ref.Name] = ref
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForContainer watches the given pod until the container is running
|
||||||
|
func waitForContainer(ctx context.Context, podClient corev1client.PodsGetter, ns, podName, containerName string) (*corev1.Pod, error) {
|
||||||
// TODO: expose the timeout
|
// TODO: expose the timeout
|
||||||
ctx, cancel := watchtools.ContextWithOptionalTimeout(ctx, 0*time.Second)
|
ctx, cancel := watchtools.ContextWithOptionalTimeout(ctx, 0*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@ -381,17 +482,14 @@ func waitForEphemeralContainer(ctx context.Context, podClient corev1client.PodsG
|
|||||||
return false, fmt.Errorf("watch did not return a pod: %v", ev.Object)
|
return false, fmt.Errorf("watch did not return a pod: %v", ev.Object)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range p.Status.EphemeralContainerStatuses {
|
s := getContainerStatusByName(p, containerName)
|
||||||
if s.Name != ephemeralContainerName {
|
if s == nil {
|
||||||
continue
|
return false, nil
|
||||||
}
|
}
|
||||||
|
klog.V(2).Infof("debug container status is %v", s)
|
||||||
klog.V(2).Infof("debug container status is %v", s)
|
if s.State.Running != nil || s.State.Terminated != nil {
|
||||||
if s.State.Running != nil || s.State.Terminated != nil {
|
return true, nil
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
})
|
})
|
||||||
if ev != nil {
|
if ev != nil {
|
||||||
@ -403,9 +501,8 @@ func waitForEphemeralContainer(ctx context.Context, podClient corev1client.PodsG
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(verb): handle other types of containers
|
func handleAttachPod(ctx context.Context, f cmdutil.Factory, podClient corev1client.PodsGetter, ns, podName, containerName string, opts *attach.AttachOptions) error {
|
||||||
func handleAttachPod(ctx context.Context, f cmdutil.Factory, podClient corev1client.PodsGetter, ns, podName, ephemeralContainerName string, opts *attach.AttachOptions) error {
|
pod, err := waitForContainer(ctx, podClient, ns, podName, containerName)
|
||||||
pod, err := waitForEphemeralContainer(ctx, podClient, ns, podName, ephemeralContainerName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -413,16 +510,15 @@ func handleAttachPod(ctx context.Context, f cmdutil.Factory, podClient corev1cli
|
|||||||
opts.Namespace = ns
|
opts.Namespace = ns
|
||||||
opts.Pod = pod
|
opts.Pod = pod
|
||||||
opts.PodName = podName
|
opts.PodName = podName
|
||||||
opts.ContainerName = ephemeralContainerName
|
opts.ContainerName = containerName
|
||||||
if opts.AttachFunc == nil {
|
if opts.AttachFunc == nil {
|
||||||
opts.AttachFunc = attach.DefaultAttachFunc
|
opts.AttachFunc = attach.DefaultAttachFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
var status *corev1.ContainerStatus
|
status := getContainerStatusByName(pod, containerName)
|
||||||
for i := range pod.Status.EphemeralContainerStatuses {
|
if status == nil {
|
||||||
if pod.Status.EphemeralContainerStatuses[i].Name == ephemeralContainerName {
|
// impossible path
|
||||||
status = &pod.Status.EphemeralContainerStatuses[i]
|
return fmt.Errorf("Error get container status of %s: %+v", containerName, err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if status.State.Terminated != nil {
|
if status.State.Terminated != nil {
|
||||||
klog.V(1).Info("Ephemeral container terminated, falling back to logs")
|
klog.V(1).Info("Ephemeral container terminated, falling back to logs")
|
||||||
@ -436,6 +532,18 @@ func handleAttachPod(ctx context.Context, f cmdutil.Factory, podClient corev1cli
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getContainerStatusByName(pod *corev1.Pod, containerName string) *corev1.ContainerStatus {
|
||||||
|
allContainerStatus := [][]corev1.ContainerStatus{pod.Status.InitContainerStatuses, pod.Status.ContainerStatuses, pod.Status.EphemeralContainerStatuses}
|
||||||
|
for _, statusSlice := range allContainerStatus {
|
||||||
|
for i := range statusSlice {
|
||||||
|
if statusSlice[i].Name == containerName {
|
||||||
|
return &statusSlice[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// logOpts logs output from opts to the pods log.
|
// logOpts logs output from opts to the pods log.
|
||||||
func logOpts(restClientGetter genericclioptions.RESTClientGetter, pod *corev1.Pod, opts *attach.AttachOptions) error {
|
func logOpts(restClientGetter genericclioptions.RESTClientGetter, pod *corev1.Pod, opts *attach.AttachOptions) error {
|
||||||
ctrName, err := opts.GetContainerName(pod)
|
ctrName, err := opts.GetContainerName(pod)
|
||||||
|
@ -19,10 +19,13 @@ package debug
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
|
"k8s.io/utils/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGenerateDebugContainer(t *testing.T) {
|
func TestGenerateDebugContainer(t *testing.T) {
|
||||||
@ -164,3 +167,481 @@ func TestGenerateDebugContainer(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
|
||||||
|
defer func(old func(int) string) { nameSuffixFunc = old }(nameSuffixFunc)
|
||||||
|
var suffixCounter int
|
||||||
|
nameSuffixFunc = func(int) string {
|
||||||
|
suffixCounter++
|
||||||
|
return fmt.Sprint(suffixCounter)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
opts *DebugOptions
|
||||||
|
pod *corev1.Pod
|
||||||
|
expected *corev1.Pod
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic",
|
||||||
|
opts: &DebugOptions{
|
||||||
|
CopyTo: "debugger",
|
||||||
|
Container: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
PullPolicy: corev1.PullIfNotPresent,
|
||||||
|
},
|
||||||
|
pod: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "target",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "debugger",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NodeName: "node-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "debugger",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||||
|
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same node",
|
||||||
|
opts: &DebugOptions{
|
||||||
|
CopyTo: "debugger",
|
||||||
|
Container: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
PullPolicy: corev1.PullIfNotPresent,
|
||||||
|
SameNode: true,
|
||||||
|
},
|
||||||
|
pod: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "target",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "debugger",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NodeName: "node-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "debugger",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||||
|
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NodeName: "node-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metadata stripping",
|
||||||
|
opts: &DebugOptions{
|
||||||
|
CopyTo: "debugger",
|
||||||
|
Container: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
PullPolicy: corev1.PullIfNotPresent,
|
||||||
|
},
|
||||||
|
pod: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "target",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"app": "business",
|
||||||
|
},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"test": "test",
|
||||||
|
},
|
||||||
|
ResourceVersion: "1",
|
||||||
|
CreationTimestamp: metav1.Time{time.Now()},
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "debugger",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "debugger",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"test": "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||||
|
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add a debug container",
|
||||||
|
opts: &DebugOptions{
|
||||||
|
CopyTo: "debugger",
|
||||||
|
Container: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
PullPolicy: corev1.PullIfNotPresent,
|
||||||
|
},
|
||||||
|
pod: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "target",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "business",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "debugger",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "business",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||||
|
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "customize envs",
|
||||||
|
opts: &DebugOptions{
|
||||||
|
CopyTo: "debugger",
|
||||||
|
Container: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
PullPolicy: corev1.PullIfNotPresent,
|
||||||
|
Env: []corev1.EnvVar{{
|
||||||
|
Name: "TEST",
|
||||||
|
Value: "test",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
pod: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "target",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "business",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "debugger",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "business",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||||
|
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||||
|
Env: []corev1.EnvVar{{
|
||||||
|
Name: "TEST",
|
||||||
|
Value: "test",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "debug args as container command",
|
||||||
|
opts: &DebugOptions{
|
||||||
|
CopyTo: "debugger",
|
||||||
|
Container: "debugger",
|
||||||
|
Args: []string{"/bin/echo", "one", "two", "three"},
|
||||||
|
Image: "busybox",
|
||||||
|
PullPolicy: corev1.PullIfNotPresent,
|
||||||
|
},
|
||||||
|
pod: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "target",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "business",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "debugger",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "business",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
Command: []string{"/bin/echo", "one", "two", "three"},
|
||||||
|
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||||
|
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "debug args as container command",
|
||||||
|
opts: &DebugOptions{
|
||||||
|
CopyTo: "debugger",
|
||||||
|
Container: "debugger",
|
||||||
|
Args: []string{"one", "two", "three"},
|
||||||
|
ArgsOnly: true,
|
||||||
|
Image: "busybox",
|
||||||
|
PullPolicy: corev1.PullIfNotPresent,
|
||||||
|
},
|
||||||
|
pod: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "target",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "business",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "debugger",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "business",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
Args: []string{"one", "two", "three"},
|
||||||
|
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||||
|
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "modify existing command to debug args",
|
||||||
|
opts: &DebugOptions{
|
||||||
|
CopyTo: "debugger",
|
||||||
|
Container: "debugger",
|
||||||
|
Args: []string{"sleep", "1d"},
|
||||||
|
Image: "busybox",
|
||||||
|
PullPolicy: corev1.PullIfNotPresent,
|
||||||
|
},
|
||||||
|
pod: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "target",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "debugger",
|
||||||
|
Command: []string{"echo"},
|
||||||
|
Args: []string{"one", "two", "three"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "debugger",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
Command: []string{"sleep", "1d"},
|
||||||
|
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||||
|
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "random name",
|
||||||
|
opts: &DebugOptions{
|
||||||
|
CopyTo: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
PullPolicy: corev1.PullIfNotPresent,
|
||||||
|
},
|
||||||
|
pod: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "target",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "business",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "debugger",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "business",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "debugger-1",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||||
|
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "random name collision",
|
||||||
|
opts: &DebugOptions{
|
||||||
|
CopyTo: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
PullPolicy: corev1.PullIfNotPresent,
|
||||||
|
},
|
||||||
|
pod: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "target",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "debugger-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "debugger",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "debugger-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "debugger-2",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||||
|
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "shared process namespace",
|
||||||
|
opts: &DebugOptions{
|
||||||
|
CopyTo: "debugger",
|
||||||
|
Container: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
PullPolicy: corev1.PullIfNotPresent,
|
||||||
|
ShareProcesses: true,
|
||||||
|
shareProcessedChanged: true,
|
||||||
|
},
|
||||||
|
pod: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "target",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "debugger",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NodeName: "node-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "debugger",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||||
|
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ShareProcessNamespace: pointer.BoolPtr(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
tc.opts.IOStreams = genericclioptions.NewTestIOStreamsDiscard()
|
||||||
|
suffixCounter = 0
|
||||||
|
|
||||||
|
if tc.pod == nil {
|
||||||
|
tc.pod = &corev1.Pod{}
|
||||||
|
}
|
||||||
|
pod, _ := tc.opts.generatePodCopyWithDebugContainer(tc.pod)
|
||||||
|
if diff := cmp.Diff(tc.expected, pod); diff != "" {
|
||||||
|
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user