Logs, exec and pod files
Four ways into a running container: read its logs, get a shell in it, forward a port out of it, and — if you turn it on — browse its filesystem. All four are more privileged than looking at YAML, and the security model reflects that.
|
Exec and the whole pod-file family require the admin login even in open-mode. Open-mode makes
Logs and port-forward listing follow the ordinary rule: readable in open-mode, and starting or stopping a forward requires the login. See Security and identity. |
1. Pod logs
Logs open in the dock — a resizable panel across the bottom of the window with a tab per
session. Any tab can be popped out into a floating window with ⧉ and docked again; the stream
survives the move, because the session’s DOM is relocated rather than rebuilt.
Reach them from a row’s ⋮ menu or the detail drawer:
-
Logs on a Pod — one container, or all of that pod’s containers at once
-
Logs (all pods) on a Deployment, StatefulSet, DaemonSet, ReplicaSet or Job — every pod behind the workload, in one stream
Both require no login in open-mode.
1.1. Multi-source streaming
GET /api/v1/clusters/{id}/logs/stream multiplexes many containers onto one SSE stream. Each
source is one container of one pod, identified as namespace/pod/container.
Sources are resolved either from a pod’s containers (init containers can be included, and are
listed last) or from a workload’s spec.selector.matchLabels, with the pods sorted by name so a
given replica keeps the same colour and label across reconnects.
-
Up to 24 sources. Past that the stream still works on the first 24 and says so — Following N of M matching containers — the rest are not being streamed — rather than quietly dropping the rest.
-
Each source gets a colour and a text prefix. Colour is never the only cue. In workload scope the shared pod-name prefix is stripped so replicas are distinguishable as text.
-
Ordering is best-effort and declared as such. Lines are accumulated and timestamp-sorted within a short window before being sent; the stream tells the client that this is the contract rather than implying a total ordering across pods that does not exist.
-
History comes first: each source’s tail snapshot (200 lines by default) is taken before its follow opens, so you do not get live lines interleaved ahead of the backlog.
1.2. New pods join a stream already running
During a rollout the pods you started watching go quiet and new ones appear — which is precisely when someone is watching. kweblens re-resolves the workload’s pods every 4 seconds and attaches readers to whatever is new, so replicas created mid-rollout join the stream in flight.
That is polling, and deliberately so: a new pod is not readable the moment it appears, so a watch would still need the retry loop that polling already is, and would additionally own watch re-establishment.
A source that fails is reported per-source (the rest keep streaming) and reported again when it recovers. Sources that ended are re-attached by a later refresh.
1.3. Reading the stream
The log pane offers a container selector, a text filter applied at render so it re-reveals
lines already received, wrap, pause, copy, and a source legend where clicking a source
toggles it and double-clicking isolates it. It follows the tail unless you scroll up, and tells you
when it is paused at a scroll position.
1.4. Refusals are not log lines
fabric8 returns an API-server refusal as log content: asking for the log of a container that
has not started answers HTTP 400 with a Status body, and neither getLog() nor watchLog()
throws — both hand you that JSON as though the application had printed it.
kweblens recognises both wire forms (an API-server Status object, and the kubelet’s prose for
garbage-collected previous-run logs) and reports them as errors against the source. Without that,
every pod a rollout created contributed a fabricated line attributed to your application.
1.5. Other log endpoints
-
GET …/pods/{ns}/{pod}/log?container=&tailLines=— a plain-text snapshot, 200 lines by default -
GET …/pods/{ns}/{pod}/log/stream?container=— single-container SSE follow -
GET …/pods/{ns}/{pod}/log/previous?container=&tailLines=— the previous container instance’s log, 500 lines by default. This is the crash-loop view: what the container said before it died -
GET …/logs/sources— which sources a given scope resolves to, and whether the set was truncated
The SPA drives the multi-source endpoint; the per-pod endpoints remain for scripting and the MCP tool surface.
|
Every log stream carries the 15-second SSE keepalive. It is load-bearing here: an |
2. Exec into a container
Shell on a pod opens a terminal session in the dock — xterm.js in the browser, a WebSocket to
/ws/exec, and the Kubernetes exec API at the far end. Attach to Pod uses the same socket in
attach mode, which connects to the container’s existing main process rather than starting a new
one.
The handshake always requires authentication, in both security modes. Because a browser cannot attach Basic auth to a WebSocket handshake, the SPA’s sign-in establishes a session cookie that the socket then rides — which is why the authenticated context is persisted to the HTTP session and not only to the request.
Every exec and attach is audited against the pod.
Two limits worth knowing:
-
The shell is
sh. Not configurable, and there is nobashfallback. A container without a shell cannot be exec’d into. -
The terminal does not resize the remote PTY. The browser refits xterm locally on resize, but no resize message is sent, so a full-screen program’s idea of the window size is whatever it started with.
Exec runs with a TTY and stderr merged into stdout. The separate one-shot exec path used by the file browser deliberately runs without a TTY, because a PTY merges the two streams and performs newline translation, which corrupts binary payloads.
3. Port-forward
Port Forward on a Service opens a dialog; forwards are then listed and stopped from the
Port Forwards view, which refreshes every few seconds and shows each forward’s status as Active,
Closed or Failed.
-
GET /api/v1/clusters/{id}/port-forwards— list -
POST /api/v1/clusters/{id}/port-forwards— start{kind, namespace, name, remotePort, localPort} -
POST /api/v1/clusters/{id}/port-forwards/{id}/stop— stop
Starting and stopping are audited and require the login.
Targets are Pod and Service. Not Deployment — the API refuses any other kind by name rather
than guessing. A Service target is resolved to a concrete pod: the requested port is matched
against the Service’s ports, a Running pod is preferred among those the selector matches, and a
named targetPort is resolved against that pod’s declared container ports. Each failure says
which step failed — no selector, no pods, no such port, or a named port the pod does not declare.
That resolution is why the dialog warns that a Service port may land on a different number: the
forward goes to the pod’s targetPort, and the list shows remotePort → podPort when they differ.
Leave the local port blank and the OS picks one, which is then reported back. Forwards are server-side and outlive the tab that started them — that is intended, which is also why they have no heartbeat: unlike an SSE stream, a port-forward is a named resource you can come back and stop.
Because the listening socket is bound by kweblens rather than by the cluster, closing a cluster’s client is not enough to release it. Removing or re-pointing a cluster stops that cluster’s forwards, since otherwise there would be no screen left on which to list them and they would hold the port until the process restarted.
4. The pod file browser
Browse, view, download, edit, upload and delete files inside a container, over the same Kubernetes exec API the terminal uses. The UI is the pod detail drawer’s Files tab.
4.1. Off by default, on purpose
|
A container’s disk holds things no other kweblens view exposes: projected Secret volumes, and the
service-account token under |
4.2. Configuration
| Property | Default | Effect |
|---|---|---|
|
|
Turns the feature on at all. Everything below is inert until it is |
|
|
|
|
|
Largest file that may be viewed or downloaded. |
|
|
Largest payload that may be written into a container. |
|
|
Largest number of directory entries in one listing. |
|
|
How long a single in-container command may run. |
|
empty |
Absolute path prefixes to confine the browser to. Empty means the whole container filesystem. |
The first five have KWEBLENS_FILES_* environment variables in the shipped configuration.
Oversized files are refused, not truncated — a silently truncated copy that you then saved would replace the whole file with a fragment.
4.3. Confinement with allowed-roots
When roots are set, the check runs twice: once on the requested path after normalisation, and
again on the path the container itself resolves it to (readlink -f). So a symlink inside a root
that points outside it is refused (path-outside-roots), and a path the container cannot resolve
at all is refused too (unresolvable-path).
It fails closed. A path that cannot be checked is not allowed.
Paths must be absolute, are length-limited, and a .. that would climb above / is rejected
rather than clamped. Paths are never spliced into a shell string — they are passed as arguments —
so quoting is not a defence that could be got wrong.
/api/v1/about reports the roots to the browser (only when authenticated), so the Files tab opens
inside a root instead of on /, and a breadcrumb crumb outside the roots is shown disabled and
labelled as such rather than silently missing.
4.4. What the Files tab offers
The tab appears on Pods only, and only when the feature is not known to be disabled. Signed out, it shows an explanation and a sign-in button — it does not fire a request that would 401.
-
A container selector (init containers listed last and marked)
-
A breadcrumb trail,
UpandRefresh -
Name, Type, Size, Mode, Owner and Modified columns; symlinks render as
name → target -
Download on any file
-
Edit only when the file is editable and writes are enabled
-
Upload, likewise only when writes are enabled
-
Delete, with confirmation, only when writes are enabled
The write capabilities come from /api/v1/about (podFiles.enabled, .writable,
.maxWriteBytes), so Edit and Upload are offered only where a write can actually succeed, and the
UI still learns from the first real listing if it is talking to an older server.
A binary file is not opened in an editor at all — saving it there would corrupt it. A truncated read is shown read-only for the same reason.
Upload takes one file at a time, by picker or drag-and-drop, and replacing an existing name asks first. A dropped folder, or several files at once, is refused with an explanation and nothing is sent. Oversize uploads are refused before the file is read.
4.5. How it works, and what it needs from the container
Every operation is one short-lived one-shot sh command, bounded by command-timeout. Listings
are parsed from NUL-delimited fields rather than by scraping ls -la, whose columns differ between
busybox, toybox and GNU coreutils and which is ambiguous for filenames containing spaces or
newlines.
A container with no shell — distroless, scratch — reports that plainly instead of showing an empty tree.
Two safety properties of the write path:
-
A write goes to a temporary file, is verified by byte count, and is then redirected over the target — a redirect rather than a move, so the file keeps its inode, owner and mode.
-
The script reads exactly the expected number of bytes from stdin. The Kubernetes exec API gives the client no way to signal end-of-input, so a script that read to EOF would hang for the whole
command-timeout, return a 504 — and then land anyway once the connection dropped, having already reported failure for a write that happened. Anything new piped into a container must bound its own read of stdin for the same reason.
Deleting is restricted to regular files and symlinks; a directory is refused. rm -rf driven from
a browser is not a mistake anyone recovers from.
4.6. Auditing
Every content read, download, write and delete is audited with its path. Directory listings are not — drowning the bounded audit ring in them would hide the reads that matter.
Failures come back as stable codes rather than raw shell noise: file-not-found,
not-a-directory, is-a-directory, file-not-readable, file-not-writable, file-too-large,
path-outside-roots, unresolvable-path, no-shell, container-not-running,
container-command-timeout, and the two gate refusals files-disabled and files-read-only. The
browser has an explanation for each, and a test fails the build if the server grows a code the UI
cannot explain.
5. Related
-
Security and identity — why these surfaces are authenticated in both modes
-
Configuration — every
kweblens.*property -
Browsing resources — the drawer these tabs live in