[istio] Add interactive istioctl debug via d8 network istio - #454
[istio] Add interactive istioctl debug via d8 network istio#454skurbatov wants to merge 5 commits into
Conversation
Signed-off-by: Sergey Kurbatov <sergey.kurbatov@flant.com>
Signed-off-by: Sergey Kurbatov <sergey.kurbatov@flant.com>
Glitchy-Sheep
left a comment
There was a problem hiding this comment.
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.
| 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) |
There was a problem hiding this comment.
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.
| switch pod.Status.Phase { | ||
| case corev1.PodRunning: | ||
| return true, nil | ||
| case corev1.PodFailed, corev1.PodSucceeded: |
There was a problem hiding this comment.
The one-shot form istio -- command from Use does not survive the poll-then-attach design:
- a fast command (
istioctl version) drives the pod toSucceededwithin the 1s poll interval;waitForPodRunningreturns 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
StreamWithContextreturns nil even when the command failed and d8 exits 0 (root.goalready mapsExitCode()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.
| } | ||
|
|
||
| ns, _, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( | ||
| &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath}, |
There was a problem hiding this comment.
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".
| } | ||
|
|
||
| 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 { |
There was a problem hiding this comment.
The fixed pod name plus unconditional "leftover" deletion makes every run destructive to a concurrent session:
- a second run force-deletes a live
Runningpod someone is attached to; - the first session's deferred delete-by-name (no UID precondition) then kills the second session's fresh pod;
createOrUpdateRoleBindingoverwritesSubjects, so a run with a different--namespaceagainst the same--target-namespacesilently 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.
| return nil | ||
| } | ||
|
|
||
| func istioctlDebugRules() []rbacv1.PolicyRule { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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{{ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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>
Summary
d8 network istiostarts 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 withd8 k.Problem
podsget/list,pods/portforwardcreate), thend8 k run --rm -itwith--overridesfor the SAd8-system/debug-containeron every run--rmcleanup were easy to get wrong; none of this lived underd8 networkFix
d8 network istionext tocni-migrationd8-system/debug-container(data.image) unless--imageis setistioctl-debugin--namespace, and Role/RoleBindingistioctl-debugin--target-namespace(defaults to--namespace)istioctl-debug(restartPolicy: Never, stdin/TTY, default commandbash), waits until Running, attaches, deletes the pod on exit; RBAC is left in place for reuseErrImagePull/ImagePullBackOff/CrashLoopBackOffBefore / After
Before: apply three RBAC manifests,
IMG="$(d8 k -n d8-system get cm debug-container -o jsonpath='{.data.image}')", thend8 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 namespaceTestEnsureRBACIsIdempotentAndUpdatesRules— second apply restores Role rules if they were clearedTestResolveDebugImage—--imageoverride, ConfigMap lookup, missing CM / emptyimagekeyTestBuildDebugPod— SA, automount token, Never restart, stdin/TTYTestCreateDebugPodReplacesLeftover— leftover pod is replacedTestWaitForPodRunning/TestWaitForPodRunningImagePullError— Running vs terminal wait reasonTestNewCommandFlags—namespace,target-namespace,image,kubeconfig,contextNotes
Interactive attach is not covered by the fake clientset tests; end-to-end attach against a live cluster was not run in this environment.