Skip to content

kubectl-ate: dial ateapi directly over the port-forward stream#539

Draft
Yiqing Wang (YQ-Wang) wants to merge 1 commit into
agent-substrate:mainfrom
YQ-Wang:feat/issue-26-portforward-reset
Draft

kubectl-ate: dial ateapi directly over the port-forward stream#539
Yiqing Wang (YQ-Wang) wants to merge 1 commit into
agent-substrate:mainfrom
YQ-Wang:feat/issue-26-portforward-reset

Conversation

@YQ-Wang

@YQ-Wang Yiqing Wang (YQ-Wang) commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Relates to #26 and #540. Opening this as a draft to ask whether the change is wanted before polishing it.

  • Tests pass
  • Appropriate changes to documentation are included in the PR — no user-facing change; the CLI README's description of a background port-forward tunnel stays accurate

Summary

kubectl ate reaches ateapi through client-go's PortForwarder, which binds a localhost TCP listener, accepts a connection from our own gRPC client, and copies bytes between that connection and the Kubernetes port-forward stream. This exposes the port-forward stream directly as gRPC's net.Conn, so the listener and the copy goroutines go away.

  • dial ateapi directly over Kubernetes port-forward streams instead of a localhost TCP bridge
  • honor cancellation during SPDY negotiation and stream setup
  • close direct stream connections on setup and remote-stream errors, with lifecycle tests

That buys three things: no localhost listener bound for the lifetime of each command, gRPC able to redial transparently where the current single tunnel is a permanent failure if it drops, and one less copy hop per RPC. It also removes the reset behind #26 at its source, since that copy loop is what observes the RST — but #540 keeps the message away from users regardless, so this should stand or fall as a transport change.

The adapter follows Kubernetes' own direct-stream pattern, which is not importable: https://github.com/kubernetes/kubernetes/blob/v1.36.0/test/e2e/framework/pod/dial.go

Questions I would like input on

  1. Is this worth roughly 200 lines of production code here, given that the user-visible symptom is already handled?
  2. Would this be better pursued upstream as a client-go API that exposes a pod port-forward stream as a net.Conn? Anyone wanting gRPC over port-forward reimplements this today.
  3. If it stays here, internal/e2e/portforward.go is a second consumer that could share it.

Not ready for a detailed review

watchErrors reports through slog.Error, and since kubectl-ate configures no handler that lands on the user's terminal as 2026/07/25 02:41:27 ERROR Port-forward connection failed remotePort=443 error="...". That is the same kind of message as #26, and #540 does not suppress it because #540 replaces utilruntime.ErrorHandlers rather than touching slog. Closing the connection and letting gRPC report the transport error is the better shape.

Beyond that: the deadline methods are no-ops that grpc-go's teardown relies on (grpc/grpc-go#8425), Close evaluates Stream.Close() before streamConn.Close() without the Reset() that upstream uses to discard unsent data, RemoveStreams is never called, the now-unused cancel field can go, and moving dial failures from client construction to the first RPC needs a UX check for cases like an unready api pod or an RBAC denial.

Validation

go test -race -count=100 ./internal/ateclient, go test ./cmd/kubectl-ate/..., go mod tidy -diff and make verify pass. Coverage is fake-based.

The transport has not been exercised against a live cluster at volume yet; that and an e2e assertion that a successful command leaves stderr clean belong on the list above. Note that the reset itself is timing-dependent and 1,500 CLI invocations on macOS/kind produced none, so a load test has to run on Linux to say anything.

Moving port-forward transport to WebSockets would not eliminate this reset class: kubernetes/kubectl#1620 (comment)

@YQ-Wang Yiqing Wang (YQ-Wang) changed the title Fix spurious kubectl-ate port-forward reset errors kubectl-ate: dial ateapi directly over the port-forward stream Jul 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant