Skip to content

Commit c20aa76

Browse files
authored
Merge pull request kubernetes#125333 from ardaguclu/kep-4292-beta
KEP-4292: Preparations to promote custom profiling in kubectl debug
2 parents 5a99930 + 890ae1e commit c20aa76

File tree

4 files changed

+166
-77
lines changed

4 files changed

+166
-77
lines changed

staging/src/k8s.io/kubectl/pkg/cmd/debug/debug.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import (
5656
"k8s.io/kubectl/pkg/util/templates"
5757
"k8s.io/kubectl/pkg/util/term"
5858
"k8s.io/utils/ptr"
59+
"sigs.k8s.io/yaml"
5960
)
6061

6162
var (
@@ -211,8 +212,8 @@ func (o *DebugOptions) AddFlags(cmd *cobra.Command) {
211212
cmd.Flags().StringVar(&o.TargetContainer, "target", "", i18n.T("When using an ephemeral container, target processes in this container name."))
212213
cmd.Flags().BoolVarP(&o.TTY, "tty", "t", o.TTY, i18n.T("Allocate a TTY for the debugging container."))
213214
cmd.Flags().StringVar(&o.Profile, "profile", ProfileLegacy, i18n.T(`Options are "legacy", "general", "baseline", "netadmin", "restricted" or "sysadmin".`))
214-
if cmdutil.DebugCustomProfile.IsEnabled() {
215-
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."))
215+
if !cmdutil.DebugCustomProfile.IsDisabled() {
216+
cmd.Flags().StringVar(&o.CustomProfileFile, "custom", o.CustomProfileFile, i18n.T("Path to a JSON or YAML file containing a partial container spec to customize built-in debug profiles."))
216217
}
217218
}
218219

@@ -293,7 +294,10 @@ func (o *DebugOptions) Complete(restClientGetter genericclioptions.RESTClientGet
293294

294295
err = json.Unmarshal(customProfileBytes, &o.CustomProfile)
295296
if err != nil {
296-
return fmt.Errorf("%s does not contain a valid container spec: %w", o.CustomProfileFile, err)
297+
err = yaml.Unmarshal(customProfileBytes, &o.CustomProfile)
298+
if err != nil {
299+
return fmt.Errorf("%s does not contain a valid container spec: %w", o.CustomProfileFile, err)
300+
}
297301
}
298302
}
299303

staging/src/k8s.io/kubectl/pkg/cmd/debug/debug_test.go

Lines changed: 66 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ import (
2222
"testing"
2323
"time"
2424

25-
cmdutil "k8s.io/kubectl/pkg/cmd/util"
26-
2725
"github.com/google/go-cmp/cmp"
2826
"github.com/google/go-cmp/cmp/cmpopts"
2927
"github.com/spf13/cobra"
@@ -2088,31 +2086,29 @@ func TestGenerateNodeDebugPodCustomProfile(t *testing.T) {
20882086
} {
20892087

20902088
t.Run(tc.name, func(t *testing.T) {
2091-
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.DebugCustomProfile}, t, func(t *testing.T) {
2092-
var err error
2093-
kflags := KeepFlags{
2094-
Labels: tc.opts.KeepLabels,
2095-
Annotations: tc.opts.KeepAnnotations,
2096-
Liveness: tc.opts.KeepLiveness,
2097-
Readiness: tc.opts.KeepReadiness,
2098-
Startup: tc.opts.KeepStartup,
2099-
InitContainers: tc.opts.KeepInitContainers,
2100-
}
2101-
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
2102-
if err != nil {
2103-
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
2104-
}
2105-
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
2089+
var err error
2090+
kflags := KeepFlags{
2091+
Labels: tc.opts.KeepLabels,
2092+
Annotations: tc.opts.KeepAnnotations,
2093+
Liveness: tc.opts.KeepLiveness,
2094+
Readiness: tc.opts.KeepReadiness,
2095+
Startup: tc.opts.KeepStartup,
2096+
InitContainers: tc.opts.KeepInitContainers,
2097+
}
2098+
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
2099+
if err != nil {
2100+
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
2101+
}
2102+
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
21062103

2107-
pod, err := tc.opts.generateNodeDebugPod(tc.node)
2108-
if err != nil {
2109-
t.Fatalf("Fail to generate node debug pod: %v", err)
2110-
}
2111-
tc.expected.Name = pod.Name
2112-
if diff := cmp.Diff(tc.expected, pod); diff != "" {
2113-
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
2114-
}
2115-
})
2104+
pod, err := tc.opts.generateNodeDebugPod(tc.node)
2105+
if err != nil {
2106+
t.Fatalf("Fail to generate node debug pod: %v", err)
2107+
}
2108+
tc.expected.Name = pod.Name
2109+
if diff := cmp.Diff(tc.expected, pod); diff != "" {
2110+
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
2111+
}
21162112
})
21172113
}
21182114
}
@@ -2296,31 +2292,29 @@ func TestGenerateCopyDebugPodCustomProfile(t *testing.T) {
22962292
} {
22972293

22982294
t.Run(tc.name, func(t *testing.T) {
2299-
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.DebugCustomProfile}, t, func(t *testing.T) {
2300-
var err error
2301-
kflags := KeepFlags{
2302-
Labels: tc.opts.KeepLabels,
2303-
Annotations: tc.opts.KeepAnnotations,
2304-
Liveness: tc.opts.KeepLiveness,
2305-
Readiness: tc.opts.KeepReadiness,
2306-
Startup: tc.opts.KeepStartup,
2307-
InitContainers: tc.opts.KeepInitContainers,
2308-
}
2309-
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
2310-
if err != nil {
2311-
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
2312-
}
2313-
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
2295+
var err error
2296+
kflags := KeepFlags{
2297+
Labels: tc.opts.KeepLabels,
2298+
Annotations: tc.opts.KeepAnnotations,
2299+
Liveness: tc.opts.KeepLiveness,
2300+
Readiness: tc.opts.KeepReadiness,
2301+
Startup: tc.opts.KeepStartup,
2302+
InitContainers: tc.opts.KeepInitContainers,
2303+
}
2304+
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
2305+
if err != nil {
2306+
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
2307+
}
2308+
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
23142309

2315-
pod, dc, err := tc.opts.generatePodCopyWithDebugContainer(tc.copyPod)
2316-
if err != nil {
2317-
t.Fatalf("Fail to generate node debug pod: %v", err)
2318-
}
2319-
tc.expected.Spec.Containers[0].Name = dc
2320-
if diff := cmp.Diff(tc.expected, pod); diff != "" {
2321-
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
2322-
}
2323-
})
2310+
pod, dc, err := tc.opts.generatePodCopyWithDebugContainer(tc.copyPod)
2311+
if err != nil {
2312+
t.Fatalf("Fail to generate node debug pod: %v", err)
2313+
}
2314+
tc.expected.Spec.Containers[0].Name = dc
2315+
if diff := cmp.Diff(tc.expected, pod); diff != "" {
2316+
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
2317+
}
23242318
})
23252319
}
23262320
}
@@ -2510,31 +2504,29 @@ func TestGenerateEphemeralDebugPodCustomProfile(t *testing.T) {
25102504
} {
25112505

25122506
t.Run(tc.name, func(t *testing.T) {
2513-
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.DebugCustomProfile}, t, func(t *testing.T) {
2514-
var err error
2515-
kflags := KeepFlags{
2516-
Labels: tc.opts.KeepLabels,
2517-
Annotations: tc.opts.KeepAnnotations,
2518-
Liveness: tc.opts.KeepLiveness,
2519-
Readiness: tc.opts.KeepReadiness,
2520-
Startup: tc.opts.KeepStartup,
2521-
InitContainers: tc.opts.KeepInitContainers,
2522-
}
2523-
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
2524-
if err != nil {
2525-
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
2526-
}
2527-
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
2507+
var err error
2508+
kflags := KeepFlags{
2509+
Labels: tc.opts.KeepLabels,
2510+
Annotations: tc.opts.KeepAnnotations,
2511+
Liveness: tc.opts.KeepLiveness,
2512+
Readiness: tc.opts.KeepReadiness,
2513+
Startup: tc.opts.KeepStartup,
2514+
InitContainers: tc.opts.KeepInitContainers,
2515+
}
2516+
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
2517+
if err != nil {
2518+
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
2519+
}
2520+
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
25282521

2529-
pod, ec, err := tc.opts.generateDebugContainer(tc.copyPod)
2530-
if err != nil {
2531-
t.Fatalf("Fail to generate node debug pod: %v", err)
2532-
}
2533-
tc.expected.Spec.EphemeralContainers[0].Name = ec.Name
2534-
if diff := cmp.Diff(tc.expected, pod); diff != "" {
2535-
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
2536-
}
2537-
})
2522+
pod, ec, err := tc.opts.generateDebugContainer(tc.copyPod)
2523+
if err != nil {
2524+
t.Fatalf("Fail to generate node debug pod: %v", err)
2525+
}
2526+
tc.expected.Spec.EphemeralContainers[0].Name = ec.Name
2527+
if diff := cmp.Diff(tc.expected, pod); diff != "" {
2528+
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
2529+
}
25382530
})
25392531
}
25402532
}

test/cmd/debug.sh

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,3 +567,95 @@ run_kubectl_debug_netadmin_node_tests() {
567567
set +o nounset
568568
set +o errexit
569569
}
570+
571+
run_kubectl_debug_custom_profile_tests() {
572+
set -o nounset
573+
set -o errexit
574+
575+
create_and_use_new_namespace
576+
kube::log::status "Testing kubectl debug custom profile"
577+
578+
### Pod Troubleshooting by ephemeral containers with netadmin profile
579+
# Pre-Condition: Pod "nginx" is created
580+
kubectl run target-debug "--image=${IMAGE_NGINX:?}" "${kube_flags[@]:?}"
581+
kube::test::get_object_assert pod "{{range.items}}{{${id_field:?}}}:{{end}}" 'target-debug:'
582+
583+
cat > "${TMPDIR:-/tmp}"/custom_profile.json << EOF
584+
{
585+
"env": [
586+
{
587+
"name": "ENV_VAR1",
588+
"value": "value1"
589+
},
590+
{
591+
"name": "ENV_VAR2",
592+
"value": "value2"
593+
}
594+
]
595+
}
596+
EOF
597+
598+
cat > "${TMPDIR:-/tmp}"/custom_profile.yaml << EOF
599+
env:
600+
- name: ENV_VAR3
601+
value: value3
602+
- name: ENV_VAR4
603+
value: value4
604+
EOF
605+
606+
# Command: add a new debug container with general profile
607+
output_message=$(kubectl debug target-debug -it --image=busybox --attach=false -c debug-container --profile=general --custom="${TMPDIR:-/tmp}"/custom_profile.json "${kube_flags[@]:?}")
608+
609+
# Post-Conditions
610+
kube::test::get_object_assert pod/target-debug '{{range.spec.ephemeralContainers}}{{.name}}:{{end}}' 'debug-container:'
611+
kube::test::get_object_assert pod/target-debug '{{((index (index .spec.ephemeralContainers 0).env 0)).name}}' 'ENV_VAR1'
612+
kube::test::get_object_assert pod/target-debug '{{((index (index .spec.ephemeralContainers 0).env 0)).value}}' 'value1'
613+
kube::test::get_object_assert pod/target-debug '{{((index (index .spec.ephemeralContainers 0).env 1)).name}}' 'ENV_VAR2'
614+
kube::test::get_object_assert pod/target-debug '{{((index (index .spec.ephemeralContainers 0).env 1)).value}}' 'value2'
615+
616+
# Command: add a new debug container with general profile
617+
kubectl debug target-debug -it --image=busybox --attach=false -c debug-container-2 --profile=general --custom="${TMPDIR:-/tmp}"/custom_profile.yaml "${kube_flags[@]:?}"
618+
619+
# Post-Conditions
620+
kube::test::get_object_assert pod/target-debug '{{range.spec.ephemeralContainers}}{{.name}}:{{end}}' 'debug-container:debug-container-2:'
621+
kube::test::get_object_assert pod/target-debug '{{((index (index .spec.ephemeralContainers 1).env 0)).name}}' 'ENV_VAR3'
622+
kube::test::get_object_assert pod/target-debug '{{((index (index .spec.ephemeralContainers 1).env 0)).value}}' 'value3'
623+
kube::test::get_object_assert pod/target-debug '{{((index (index .spec.ephemeralContainers 1).env 1)).name}}' 'ENV_VAR4'
624+
kube::test::get_object_assert pod/target-debug '{{((index (index .spec.ephemeralContainers 1).env 1)).value}}' 'value4'
625+
626+
# Command: create a copy of target with a new debug container
627+
kubectl debug target-debug -it --copy-to=target-copy --image=busybox --container=debug-container-3 --attach=false --profile=general --custom="${TMPDIR:-/tmp}"/custom_profile.json "${kube_flags[@]:?}"
628+
# Post-Conditions
629+
kube::test::get_object_assert pod/target-copy '{{range.spec.containers}}{{.name}}:{{end}}' 'target-debug:debug-container-3:'
630+
kube::test::get_object_assert pod/target-copy '{{((index (index .spec.containers 1).env 0)).name}}' 'ENV_VAR1'
631+
kube::test::get_object_assert pod/target-copy '{{((index (index .spec.containers 1).env 0)).value}}' 'value1'
632+
kube::test::get_object_assert pod/target-copy '{{((index (index .spec.containers 1).env 1)).name}}' 'ENV_VAR2'
633+
kube::test::get_object_assert pod/target-copy '{{((index (index .spec.containers 1).env 1)).value}}' 'value2'
634+
635+
# Clean up
636+
kubectl delete pod target-copy "${kube_flags[@]:?}"
637+
kubectl delete pod target-debug "${kube_flags[@]:?}"
638+
639+
### Debug node with custom profile
640+
# Pre-Condition: node exists
641+
kube::test::get_object_assert nodes "{{range.items}}{{${id_field:?}}}:{{end}}" '127.0.0.1:'
642+
# Command: create a new node debugger pod
643+
output_message=$(kubectl debug --profile general node/127.0.0.1 --image=busybox --custom="${TMPDIR:-/tmp}"/custom_profile.yaml --attach=false "${kube_flags[@]:?}" -- true)
644+
# Post-Conditions
645+
kube::test::get_object_assert pod "{{(len .items)}}" '1'
646+
debugger=$(kubectl get pod -o go-template="{{(index .items 0)${id_field:?}}}")
647+
kube::test::if_has_string "${output_message:?}" "${debugger:?}"
648+
kube::test::get_object_assert "pod/${debugger:?}" "{{${image_field:?}}}" 'busybox'
649+
kube::test::get_object_assert "pod/${debugger:?}" '{{.spec.nodeName}}' '127.0.0.1'
650+
kube::test::get_object_assert "pod/${debugger:?}" '{{((index (index .spec.containers 0).env 0)).name}}' 'ENV_VAR3'
651+
kube::test::get_object_assert "pod/${debugger:?}" '{{((index (index .spec.containers 0).env 0)).value}}' 'value3'
652+
kube::test::get_object_assert "pod/${debugger:?}" '{{((index (index .spec.containers 0).env 1)).name}}' 'ENV_VAR4'
653+
kube::test::get_object_assert "pod/${debugger:?}" '{{((index (index .spec.containers 0).env 1)).value}}' 'value4'
654+
# Clean up
655+
# pod.spec.nodeName is set by kubectl debug node which causes the delete to hang,
656+
# presumably waiting for a kubelet that's not present. Force the delete.
657+
kubectl delete --force pod "${debugger:?}" "${kube_flags[@]:?}"
658+
659+
set +o nounset
660+
set +o errexit
661+
}

test/cmd/legacy-script.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,7 @@ runTests() {
10461046
record_command run_kubectl_debug_baseline_tests
10471047
record_command run_kubectl_debug_restricted_tests
10481048
record_command run_kubectl_debug_netadmin_tests
1049+
record_command run_kubectl_debug_custom_profile_tests
10491050
fi
10501051
if kube::test::if_supports_resource "${nodes}" ; then
10511052
record_command run_kubectl_debug_node_tests

0 commit comments

Comments
 (0)