Skip to content

LinuxPod: add stdin/stdout/stderr attach to an already-running process (PTY re-attach) #735

@SCKelemen

Description

@SCKelemen

Problem

The Containerization framework provides LinuxPod.execInContainer(_:processID:configure:)
to spawn a new process inside a running container, but exposes no mechanism
to re-attach an interactive PTY (stdin + stdout + stderr) to the primary
process
that was started with startContainer(). Once the initial stdout/stderr
streams are consumed at start time, there is no way to reconnect.

This means the Kubernetes CRI Attach RPC — which connects a client's terminal
to the container's existing entrypoint process — cannot be implemented on
the pod-shared path.

Use case

kubectl attach -it <pod> expects the CRI runtime to open a bidirectional
byte stream to the container's primary process. On the pod-shared path
(podvmd), the CRI runtime dispatches a JSON-RPC pod.attach call to the
daemon. The daemon must then re-attach to the process that was started at
container boot.

Without this primitive, the only option is to exec a new shell, which is
semantically different (new PID, new process group, does not share the
entrypoint's stdin history or signal handling).

Reproducer

In our podvmd daemon (tools/podvmd/Sources/podvmd/PodRegistry.swift,
line ~449):

// MARK: - pod.attach
//
// Apple Containerization framework (as of 0.12.x) does not expose a
// re-attach primitive for the primary container process.

func attach(_ params: PodAttachParams) async throws -> PodAttachResult {
    _ = try podEntry(params.handle)
    throw PodVMError(
        .methodNotFound,
        "pod.attach not supported by Apple Containerization framework on this host"
    )
}

The Go CRI layer (darwin-cri) calls this method and receives JSON-RPC error
code -32601 (method not found), which it maps to gRPC codes.Unimplemented:

Attach: pod-shared path: pod.attach returned method_not_found
        (-32601: pod.attach not supported by Apple Containerization
        framework on this host)

A kubectl attach -it against a pod-shared pod produces:

error: unable to upgrade connection: container attach not supported

Proposed API shape

Option A — method on LinuxPod:

/// Attach stdin/stdout/stderr to an already-running container process.
/// Returns when the client disconnects or the process exits.
func attach(
    containerID: String,
    processID: String?,       // nil = primary process
    stdin: (any ReaderStream)?,
    stdout: (any Writer)?,
    stderr: (any Writer)?,
    tty: Bool
) async throws

Option B — method on LinuxProcess:

/// Re-open the stdio streams for an existing process.
func openTTY() async throws -> (stdin: FileHandle, stdout: FileHandle)

Either shape would unblock the CRI Attach path.

Workarounds we've tried

  1. Exec a new /bin/sh — not semantically equivalent; creates a new
    process rather than attaching to the existing one.
  2. Capture stdout/stderr at start time into files, then tail — provides
    read-only output but no stdin, and misses data written before the tail
    starts.
  3. Hold the initial Writer references open — works for log streaming
    but does not provide a way to inject stdin after the fact, and the
    Writer protocol is unidirectional.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions