Skip to content

[istio] Add interactive istioctl debug via d8 network istio - #454

Open
skurbatov wants to merge 5 commits into
mainfrom
istio_debug
Open

[istio] Add interactive istioctl debug via d8 network istio#454
skurbatov wants to merge 5 commits into
mainfrom
istio_debug

Conversation

@skurbatov

Copy link
Copy Markdown

Summary

d8 network istio starts an interactive istioctl debug session: it applies the namespaced RBAC, pulls the platform debug image, and attaches to a one-shot pod — the same workflow operators previously ran by hand with d8 k.

Problem

  • Debugging Istio required applying a ServiceAccount in the debug namespace plus a Role/RoleBinding in the target namespace (pods get/list, pods/portforward create), then d8 k run --rm -it with --overrides for the SA
  • The debug image had to be read from ConfigMap d8-system/debug-container on every run
  • The two namespaces, leftover pods, and --rm cleanup were easy to get wrong; none of this lived under d8 network

Fix

  • New leaf command d8 network istio next to cni-migration
  • Resolves the image from d8-system/debug-container (data.image) unless --image is set
  • Idempotently creates ServiceAccount istioctl-debug in --namespace, and Role/RoleBinding istioctl-debug in --target-namespace (defaults to --namespace)
  • Runs pod istioctl-debug (restartPolicy: Never, stdin/TTY, default command bash), waits until Running, attaches, deletes the pod on exit; RBAC is left in place for reuse
  • Replaces a leftover pod of the same name; fails fast on ErrImagePull / ImagePullBackOff / CrashLoopBackOff
  • Resets SIGINT/SIGTERM so the first Ctrl-C ends the attach session instead of being swallowed by the d8 root handler

Before / After

Before: apply three RBAC manifests, IMG="$(d8 k -n d8-system get cm debug-container -o jsonpath='{.data.image}')", then d8 k run istioctl-debug --rm -it --overrides=... -- bash.

After: d8 network istio -n <debug-namespace> --target-namespace <target-namespace> — same SA/Role/RoleBinding and the same debug pod, without the manual kubectl steps.

Tests

  • TestEnsureRBACCreatesObjects — SA in the debug namespace, Role rules and RoleBinding subject/ref in the target namespace
  • TestEnsureRBACIsIdempotentAndUpdatesRules — second apply restores Role rules if they were cleared
  • TestResolveDebugImage--image override, ConfigMap lookup, missing CM / empty image key
  • TestBuildDebugPod — SA, automount token, Never restart, stdin/TTY
  • TestCreateDebugPodReplacesLeftover — leftover pod is replaced
  • TestWaitForPodRunning / TestWaitForPodRunningImagePullError — Running vs terminal wait reason
  • TestNewCommandFlagsnamespace, target-namespace, image, kubeconfig, context

Notes

Interactive attach is not covered by the fake clientset tests; end-to-end attach against a live cluster was not run in this environment.

@skurbatov skurbatov self-assigned this Aug 24, 2026
@skurbatov skurbatov added the enhancement New feature or request label Aug 24, 2026
@skurbatov
skurbatov requested a review from ldmonster as a code owner August 24, 2026 14:58
@skurbatov
skurbatov marked this pull request as draft August 24, 2026 14:59
Signed-off-by: Sergey Kurbatov <sergey.kurbatov@flant.com>
Signed-off-by: Sergey Kurbatov <sergey.kurbatov@flant.com>
@skurbatov
skurbatov marked this pull request as ready for review August 24, 2026 16:15
@skurbatov
skurbatov requested a review from apolovov August 24, 2026 16:16
apolovov
apolovov previously approved these changes Aug 27, 2026

@Glitchy-Sheep Glitchy-Sheep left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new d8 network istio command. 6 inline comments below: the first four are confirmed by mechanism (signal handling vs cleanup, one-shot mode, KUBECONFIG list, concurrent sessions), the last two depend on the cluster setup (RBAC scope for istioctl, image pull / PSS) and are worth checking on a real DKP cluster.

Comment thread internal/network/istio/istio.go Outdated
RunE: func(cmd *cobra.Command, args []string) error {
// Match kubectl: first SIGINT/SIGTERM must stop the attach session
// instead of being swallowed by the d8 root graceful handler.
signal.Reset(syscall.SIGINT, syscall.SIGTERM)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

signal.Reset disables the root graceful handler - the only thing that cancels cmd.Context() on SIGINT/SIGTERM. With the default signal action restored, Ctrl+C kills the process on the spot: the deferred deletePod never runs and the debug pod (with its SA token mounted) leaks. Ctrl+C during the up-to-2-minute image pull wait is the easiest way to hit this.

The kubectl.go code this mirrors has no cleanup defer, so the reset is harmless there. Here the whole path is ctx-aware: without the reset, cancel aborts the wait/stream and the defer (built on context.Background() for exactly this) deletes the pod. Suggest dropping the reset.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

switch pod.Status.Phase {
case corev1.PodRunning:
return true, nil
case corev1.PodFailed, corev1.PodSucceeded:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The one-shot form istio -- command from Use does not survive the poll-then-attach design:

  • a fast command (istioctl version) drives the pod to Succeeded within the 1s poll interval; waitForPodRunning returns that as an error, and the deferred delete removes the pod before its logs can be read - the output is lost;
  • even when attach wins the race, everything printed before the stream opened is not replayed;
  • attach, unlike exec, carries no exit status, so StreamWithContext returns nil even when the command failed and d8 exits 0 (root.go already maps ExitCode() errors to the process exit code).

kubectl run --attach falls back to printing container logs for exactly this race. If one-shot is out of scope for now, dropping [-- command ...] from Use may be the cheaper fix.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment thread internal/network/istio/istio.go Outdated
}

ns, _, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kubeconfigNamespace puts the raw flag value into ExplicitPath, but the flag default is $KUBECONFIG verbatim, which may be a colon-separated list. SetupK8sClientSet splits it (clientset.go:53), this function does not: with KUBECONFIG=~/.kube/config:~/.kube/other the client builds fine, then the command dies with "no such file or directory" on the literal path config:other.

transport.KubeconfigNamespace (internal/snapshot/transport/kubeconfig.go) already implements the correct loading rules and can be reused here. It also makes the ns == "" fallback unnecessary: clientcmd already returns "default".

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment thread internal/network/istio/debug.go Outdated
}

func createDebugPod(ctx context.Context, kube kubernetes.Interface, namespace, image string, command []string) (*corev1.Pod, error) {
if existing, err := kube.CoreV1().Pods(namespace).Get(ctx, resourceName, metav1.GetOptions{}); err == nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fixed pod name plus unconditional "leftover" deletion makes every run destructive to a concurrent session:

  • a second run force-deletes a live Running pod someone is attached to;
  • the first session's deferred delete-by-name (no UID precondition) then kills the second session's fresh pod;
  • createOrUpdateRoleBinding overwrites Subjects, so a run with a different --namespace against the same --target-namespace silently revokes the other session's RBAC mid-flight.

Suggest GenerateName per run with the app.kubernetes.io/managed-by label as the ownership marker, and garbage-collecting only terminal-phase pods.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment thread internal/network/istio/debug.go Outdated
return nil
}

func istioctlDebugRules() []rbacv1.PolicyRule {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These rules cover only the target namespace, but istioctl's core commands need the control plane: istioctl proxy-status discovers istiod pods and port-forwards to them in d8-istio, which this RBAC never grants -> Forbidden. proxy-config against pods in the target namespace will work; if proxy-status / xds diagnostics are part of the intended scope, one --target-namespace cannot cover both. Worth checking on a real cluster before merge.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve set up the minimum RBAC grants; we’ll add more as needed.

ServiceAccountName: resourceName,
AutomountServiceAccountToken: ptr.To(true),
RestartPolicy: corev1.RestartPolicyNever,
Containers: []corev1.Container{{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pod spec has no imagePullSecrets, while the default image from d8-system/debug-container lives in the private Deckhouse registry whose pull secret exists only in d8-* namespaces. Unless nodes carry containerd auth for that registry, d8 network istio -n <user-ns> without --image ends in ImagePullBackOff. Related: there is no securityContext either, so a namespace enforcing the restricted PSS rejects the pod at admission. Both are worth a check on a real DKP cluster.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, we are accepting this problem. In the future, we will consider how to fix it.

Signed-off-by: Sergey Kurbatov <sergey.kurbatov@flant.com>
Signed-off-by: Sergey Kurbatov <sergey.kurbatov@flant.com>
Signed-off-by: Sergey Kurbatov <sergey.kurbatov@flant.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants