Skip to content

ateom-gvisor: attribute runsc output for the pause container - #2330

Merged
Jeff Luo (JeffLuoo) merged 6 commits into
agent-substrate:mainfrom
cloudonly:pause-container-log-pipe
Oct 14, 2026
Merged

Jeff Luo (JeffLuoo) merged 6 commits into
agent-substrate:mainfrom
cloudonly:pause-container-log-pipe

Conversation

@cloudonly

@cloudonly Luomu (cloudonly) commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

The runsc create, start, and restore commands for the pause container previously wrote their stdout/stderr to the shared worker pod log without actor attribution. RunWorkload and RestoreWorkload now forward that CLI output and the pause container's stdio through the existing actor log envelope, labeled with the actor and ate.actor.container.name="_pause". This covers cold startup and VOLUMES/MEMORY restore. JSON fields are preserved, and reserved ate.* fields cannot override platform attribution.

kubectl ate logs actors excludes _pause by default, keeping the pause container's runsc startup banners and host paths out of the default application log view. Application container logs and actor lifecycle records remain visible. Pass -c _pause to inspect the pause container's runsc diagnostics. The attributed records remain in the worker pod log and centralized logging backends.

Restore requests reject unsupported snapshot fidelity values before acquiring actor locks or setting up networking and log pipes. Both workload paths call StartJSONLogPipe directly, consistent with application containers. The RPC closes its pipe writer when it returns; the forwarding goroutine does not retain the RPC context.

This change attributes runsc CLI/pause stdio output. With runsc debug logging disabled, sentry and gofer stdio is directed to /dev/null; this change does not enable that logging. --alsologtostderr uses glog text by default. Tests cover that text format and runsc JSON diagnostics using msg, level, and time.

Addresses the pause-container invocation portion of #2271 and the default log display concern raised in review. One-shot runsc helpers, sentry panic capture, and the micro-VM implementation remain separate work. No new registry attributes are introduced.

Validation after merging main f03327bd:

  • Linux/amd64, Go 1.27.2: full go test -race -count=1 ./... passed for 152 packages, with PostgreSQL/envtest integration enabled by CI=true.
  • Pause attribution, fs checkpoint/restore arguments, fidelity rejection, and CLI source/container filtering regressions passed 20 repetitions with the race detector.
  • All 15 repository verification scripts passed. The pinned golangci-lint v2.13.2 used Go 1.27.1 and a 15-minute timeout after its Go 1.27.2 export-data incompatibility was confirmed. The earlier package-loading timeouts are retained in the validation record.

Both VOLUMES and MEMORY restore paths retain main's fs restore arguments while forwarding pause output through the actor log pipe. The checkout remained clean.

Verification ran as an unprivileged user. Root-gated mount tests and real gVisor E2E were not run for this head.

AI assistance was used to resolve the merge conflicts and run verification.

Comment thread cmd/ateom-gvisor/main.go Outdated
Comment on lines +519 to +523
// startPauseLogPipe starts the log pipe for the pause container's sandbox
// runtime (sentry and gofer) output, so its lines reach the pod log tagged
// with the actor and the pause container's runsc container name. The caller
// owns the pipe and must Close it when the workload RPC returns; the
// subprocess keeps its inherited write end for its lifetime.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This pipe does not receive sentry or gofer output. In runsc (runsc/sandbox/sandbox.go and runsc/container/container.go), when -debug is off, both the sentry and gofer processes have their Stdin/Stdout/Stderr set to nil (/dev/null). The pipe passed to cmdCreate, cmdStart, and cmdRestore only receives:

  1. The runsc CLI process's own --alsologtostderr output during create/start/restore, and
  2. The /pause container process's stdio (donated via --stdio-fds), which produces no output.

Please update this comment, docs/observability.md:15, and the PR description so they describe the runsc CLI / pause container output rather than sentry and gofer output.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated in 98d9a78. The source comments, observability documentation, and PR title/description now describe the runsc CLI's create/start/restore output and the pause container's stdio. They also clarify that sentry and gofer stdio remains directed to /dev/null when debug logging is disabled. This PR does not enable those logs.

Comment thread cmd/ateom-gvisor/main.go Outdated
Comment on lines +917 to +921
pw, err := s.startPauseLogPipe(attribution)
if err != nil {
return nil, fmt.Errorf("while starting pause log pipe: %w", err)
}
defer pw.Close()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

startPauseLogPipe opens the pipe and spawns the reader goroutine before switch req.GetScope() validates the scope. Moving it after the scope check (or into the valid cases) avoids creating the pipe and goroutine on an unsupported scope.

Also, since the application container loop below calls s.actorLogger.StartJSONLogPipe(attribution, ac.GetName()) directly, consider calling s.actorLogger.StartJSONLogPipe(attribution, ocispec.PauseContainer) inline in RunWorkload and RestoreWorkload instead of adding the one-line startPauseLogPipe wrapper.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated in 98d9a78. RestoreWorkload now validates the snapshot scope after path validation and before acquiring actor locks, configuring networking, or opening log pipes. TestRestoreWorkloadRejectsUnsupportedScope covers scopes 0 and 99 with an otherwise uninitialized service, so reaching those dependencies would fail the test. I also removed startPauseLogPipe; RunWorkload and RestoreWorkload call StartJSONLogPipe directly, consistent with the application container loop.

Comment thread cmd/ateom-gvisor/main_test.go Outdated
if err != nil {
t.Fatalf("startPauseLogPipe: %v", err)
}
const jsonLine = `{"severity":"warning","message":"gofer: I/O error","ate.actor.name":"forged"}`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: runsc's JSON log emitter (pkg/log/json.go) and util.Errorf (runsc/cmd/util/util.go) use the keys msg, level, and time rather than message and severity (and --alsologtostderr uses -debug-log-format, which defaults to glog text unless -debug-log-format=json is set). Using runsc's actual JSON keys (msg/level) here would make the test input match real runsc output.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated in 98d9a78. TestPauseContainerLogAttribution now uses msg, level, and time, including the lowercase warning level, and verifies that all three fields survive forwarding while forged ate.* attribution cannot override the actor labels. The plain-text fixture uses a glog-style runsc banner, and the documentation notes that --alsologtostderr defaults to glog text. This regression test passed 20 repetitions with the race detector.

Comment thread cmd/ateom-gvisor/main.go Outdated
// owns the pipe and must Close it when the workload RPC returns; the
// subprocess keeps its inherited write end for its lifetime.
func (s *AteomService) startPauseLogPipe(attribution resources.ActorAttribution) (io.WriteCloser, error) {
return s.actorLogger.StartJSONLogPipe(attribution, ocispec.PauseContainer)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Because logLineFilter.matches in cmd/kubectl-ate/internal/cmd/actor.go:550 matches all containers when -c is not set, tagging _pause with ate.actor.container.name="_pause" means kubectl ate logs actors <name> now prints the runsc create and runsc start/restore --alsologtostderr banners (including host paths in Args: and conf.Log()) alongside the workload's logs.

Should kubectl ate logs exclude ocispec.PauseContainer by default unless -c _pause is explicitly requested (tracked also in #2212 item 3)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Implemented in 98d9a78. The default log filter now excludes ocispec.PauseContainer. Passing -c _pause shows its diagnostics; application logs and actor lifecycle records remain visible by default. The CLI help and observability documentation explain this behavior. The attributed _pause records remain available in worker pod logs and centralized logging.

Tests cover default filtering, explicit _pause selection, application-container selection, and lifecycle records. The CLI regression tests passed 20 repetitions with the race detector. After rebasing onto main, both binaries compiled and full make verify passed all 145 Go test packages and 14 verification scripts. These checks ran as an unprivileged user; root-gated tests and real gVisor E2E were not run. This change handles the pause-container portion of the concern; it does not separate application-container runsc output.

@cloudonly Luomu (cloudonly) changed the title ateom-gvisor: attribute the pause container's sandbox runtime output ateom-gvisor: attribute runsc output for the pause container Oct 9, 2026
@cloudonly
Luomu (cloudonly) force-pushed the pause-container-log-pipe branch from f741aa9 to 98d9a78 Compare October 9, 2026 00:28
The pause container's sentry and gofer output went to the worker pod log
raw, via os.Stdout, so on multi-actor workers its lines carried no actor
identity and were invisible to `kubectl ate logs`, which only keeps
lines whose labels name an actor. RunWorkload and RestoreWorkload now
give the pause container its own actor-log pipe, like the application
containers already had: every line is wrapped in the labeled envelope
under the pause container's real runsc container name, "_pause", runsc
JSON diagnostics pass their fields through, and subprocess-originated
records still cannot forge the reserved ate.* attribution.
Attribute pause-container runsc CLI output accurately, validate restore scopes before acquiring resources, and call the log pipe API directly. Preserve runsc JSON fields and test default and explicit pause-container filtering without hiding actor lifecycle events.
@cloudonly
Luomu (cloudonly) force-pushed the pause-container-log-pipe branch from 98d9a78 to 7955607 Compare October 9, 2026 06:52
# Conflicts:
#	cmd/kubectl-ate/internal/cmd/actor.go
#	cmd/kubectl-ate/internal/cmd/actor_test.go
#	docs/observability.md
…est-race-fix

# Conflicts:
#	cmd/ateom-gvisor/main.go
@JeffLuoo
Jeff Luo (JeffLuoo) added this pull request to the merge queue Oct 13, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to no response for status checks Oct 13, 2026
# Conflicts:
#	cmd/kubectl-ate/internal/cmd/actor.go
@JeffLuoo
Jeff Luo (JeffLuoo) added this pull request to the merge queue Oct 14, 2026
Merged via the queue into agent-substrate:main with commit e826281 Oct 14, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/gvisor area/observability kind/bug Something isn't working / bugfixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants