Tools Used in Kubernetes · k9s

k9s

Every question kubectl answers, it answers once — you type a command, the server replies, the terminal goes still, and if you want to know whether anything changed you type the same command again. k9s exists to remove that gap. It's a terminal UI that opens a live, continuously-refreshing table onto whatever resource you're looking at, lets you jump between resource types by typing a couple of characters instead of a full command line, and turns describe/logs/exec/edit/delete — the five things you do to a Pod fifty times a day — into single keystrokes fired at whatever row your cursor is sitting on. It is not a separate control plane, a caching proxy, or a privilege of its own: underneath, it opens the identical watch connections and issues the identical authenticated requests kubectl would, against the same kube-apiserver, subject to the same RBAC. This page covers how it's built, the screen you're looking at, the config files you'll actually hand-edit, the keybindings worth committing to muscle memory, where it bites, and how it stacks up against Lens and the Kubernetes Dashboard.

☺ Explain it like I'm 10

kubectl is like calling a friend on the phone every time you want to know what's in the fridge — you ask, they check, they tell you, then the call ends and you know nothing new until you call again. k9s is like standing in front of the fridge with the door open: you can see everything at once, it updates itself the instant something changes, and you can just reach in and grab something instead of describing what you want over the phone. But it's still the same fridge, with the same lock on it — if you're not allowed to touch the top shelf on the phone, standing in front of the open door doesn't change that. Being able to see something and being allowed to touch it are still two different rules, no matter how you're looking at the fridge.

🐰🦊Your hosts for this topic: Remy the Rabbit & Foxy — Remy already drills raw kubectl speed everywhere else in this course, and k9s is exactly that reflex given a live screen to run on; Foxy brings the other half, because a faster look around a cluster is worth the most in the first sixty seconds after something already looks wrong.

What k9s actually is: a watch, not a shortcut around anything

☺ Like you're 10: It's not a secret back door into the cluster — it's the same front door kubectl uses, just left open on your screen instead of closed after every question.

The Kubernetes API & the controller pattern already established that every built-in controller works the same way: open a long-lived GET ?watch=true connection against the objects it cares about, keep a local cache in sync as events stream in, and act on the diff. k9s runs the identical pattern, just in service of a human instead of a reconcile loop — it opens watches (via client-go, the same Go library controllers are built on) against whatever resource list is on screen, and every row updates the instant the API server emits an event for it, with no polling loop and no re-typing kubectl get. That's the entire trick behind "live" — it isn't refreshing faster, it's being told the moment something changes, the same signal a controller reconciles on.

Two consequences follow directly from that, and both matter more than they sound like they should. First, k9s needs nothing installed in the cluster — no Deployment, no Service, no extra attack surface running alongside your workloads, unlike the Kubernetes Dashboard covered later on this page. It reads your kubeconfig exactly like kubectl does and talks straight to kube-apiserver from your own machine. Second, every action you trigger from a row — e to edit, Ctrl-D to delete, s to shell in — becomes a perfectly normal, individually authenticated API request, evaluated against RBAC and admission control exactly like a kubectl invocation would be. k9s has no elevated identity of its own to fall back on; if your RoleBinding doesn't grant delete on Pods, pressing Ctrl-D in k9s fails the exact same way kubectl delete pod would, just inside a UI instead of a shell prompt.

kubectl one request, then done k9s open watch, live table Kubernetes Dashboard runs inside the cluster kube-apiserver RBAC + admission — identical for all three etcd

The screen, oriented

☺ Like you're 10: Four zones, always in the same place — where you are, what's in front of you, what you typed, and what your fingers can do about it right now.

Launch k9s with no arguments and it opens on the Pod list for your kubeconfig's current context and namespace — the same defaults a bare kubectl get pods would use. From there, every view on screen is built from the same four regions:

That last point is worth sitting with: k9s's keybindings are context-sensitive by design, not one universal cheat sheet. Press ? from any view and it prints the exact, current keybinding list for that resource type — the single most reliable reference, because it can't drift out of date the way a memorized list can once you customize hotkeys.yaml (below) or upgrade versions.

Command mode: how you actually move around

☺ Like you're 10: Instead of clicking through menus, you type a short word and hit Enter — like a search bar that teleports you.

Press :, type a resource name or its short alias, hit Enter, and you're looking at that resource type across the current namespace — no re-running a whole kubectl get line, no re-typing a namespace flag you already set. k9s discovers these names from the same API server discovery endpoint kubectl's own short names come from, so most of what you already type after kubectl get works unmodified after :.

:po            jump to Pods              :svc          jump to Services
:deploy        jump to Deployments       :ns           jump to Namespaces (pick one to switch into)
:cm            jump to ConfigMaps        :ctx          jump to Contexts (pick one to switch into)
:sa            jump to ServiceAccounts   :events        cluster events, newest first
:role          Roles in this namespace   :clusterrole  cluster-scoped Roles
:pulse         cluster health dashboard: resource counts, error rates, sparklines
:xray deploy   ownership tree for every Deployment — Deployment → ReplicaSet → Pods, live
:alias         list every resource alias k9s currently knows, including any you added

:xray is worth calling out on its own: it draws the same ownerReferences tree that kubectl's tree krew plugin prints as static text — except live, expandable, and colored by health, so a ReplicaSet stuck at zero-of-three ready Pods is visually obvious rather than something you have to notice in a column of numbers. Once you're inside any list, / opens an inline filter (substring or regex) that narrows the visible rows without leaving the view, Esc clears the filter or steps back one level of the crumb trail, and Enter drills into whatever's selected — Enter on a Deployment shows its Pods, Enter on a Pod shows its containers, one level at a time, exactly mirroring the ownership hierarchy the object model page describes.

Config you actually write

☺ Like you're 10: k9s reads its own small stack of YAML files the way the cluster reads yours — a handful of settings files that shape how the tool behaves, sitting in a folder on your own machine.

k9s keeps its configuration under $XDG_CONFIG_HOME/k9s/ (typically ~/.config/k9s/ on macOS and Linux; older versions used ~/.k9s/ directly). Five files there do almost all of the customizing you'll ever want:

# ~/.config/k9s/config.yaml — top-level behavior
k9s:
  refreshRate: 2          # seconds between UI refresh ticks (not the watch itself — that's already live)
  readOnly: false         # true disables edit/delete/scale/kill actions cluster-wide, client-side
  noExitOnCtrlC: true     # require :quit or q, so a stray Ctrl-C doesn't drop you out mid-session
  ui:
    enableMouse: false
    skin: dracula          # references a file under skins/dracula.yaml
  logger:
    tail: 200
    sinceSeconds: 300
    fullScreenLogs: false
# ~/.config/k9s/plugins.yaml — shell out to any command, bound to a key, scoped to a resource type
plugins:
  all-logs:
    shortCut: Shift-L
    description: "Logs, all containers, follow"
    scopes: ["pods"]
    command: kubectl
    background: false
    args: ["logs", "-f", "$NAME", "-n", "$NAMESPACE", "--context", "$CONTEXT", "--all-containers"]

# ~/.config/k9s/hotkeys.yaml — jump straight to a saved view with one keystroke
hotKeys:
  shift-0:
    shortCut: Shift-0
    description: "Pods in kube-system"
    command: pods
    namespace: kube-system

# ~/.config/k9s/aliases.yaml — short names of your own, layered on top of the API server's own
alias:
  cko: apps/v1/deployments

Plugins are the one most worth adopting deliberately: a plugin's args gets $NAME, $NAMESPACE, and $CONTEXT substituted from whatever row is selected before the command runs, so a single keystroke can fire off a real, correctly-scoped kubectl invocation (or any other CLI — stern, a curl against a debug sidecar, your own script) without you ever typing the resource name and namespace by hand. It's the same idea as kubectl's own krew plugins, one layer up: instead of extending the CLI itself, you're binding the CLI's own commands to keys inside the UI that already knows which row you're pointing at.

Core keybindings for day-to-day work

☺ Like you're 10: A handful of single letters cover almost everything you'll do to a resource, all day, every day.

d         describe the selected resource                y        view its YAML (read-only)
e         edit it in $EDITOR, same as `kubectl edit`     l        tail logs (Pods/containers)
s         shell in (`exec -it`, Pods/containers)         Ctrl-D   delete, with a confirm prompt
Shift-F   port-forward from the selected Pod             Enter    drill into what's selected
/         filter the current view                        Esc      clear filter, or back one level
:         open the command line                           ?        context-sensitive help for this view
0-9       jump to a favorite/recent namespace              0        (on the namespace picker) all namespaces
# a realistic five minutes: chase down one Deployment's Pods, tail logs, shell in, back out
$ k9s -n checkout --context prod        # launch straight into the namespace and context you need
:deploy⏎                                 # jump to Deployments
/checkout-web⏎                           # filter to the one that matters
⏎                                        # drill in — now looking at that Deployment's Pods
l                                        # tail logs on the selected Pod
Esc  s                                   # back out of logs, shell into the same Pod
Esc  Esc                                 # back out to the Pod list, then the Deployment list
:xray deploy⏎                            # confirm the ReplicaSet and Pods actually match desired state
◆ Key idea

Nothing above is a k9s-only capability — every one of those keys fires a request kubectl already knows how to make. What k9s buys you is eliminating the retyping: the resource name, the namespace, and the context are already sitting in the row your cursor is on, so the keystroke is the whole cost. That's the entire value proposition in one sentence, and it's also exactly why k9s can never do anything RBAC wouldn't already let a well-aimed kubectl command do.

Gotchas and failure modes

☺ Like you're 10: Most k9s mistakes look like the tool being smart, when really it's just doing exactly what you told it, fast, in a namespace or cluster you didn't mean to touch.

Ctrl-D is one keystroke away from every row, all the time. The confirm prompt is real, but it's still one accidental keypress plus a reflexive "y" away from a delete — the same speed that makes k9s valuable for reads makes a mistaken write faster too. If you regularly work in k9s against a bastion box other people also use, set readOnly: true in config.yaml or launch with k9s --readonly; it's a client-side guard against your own fat-fingering, not a security boundary — the RBAC gate from earlier on this page is still the only thing that actually stops an unauthorized action, and someone with an unmodified config still deletes exactly what RBAC lets them delete.

Namespace scope hides as much as it shows. k9s opens on one namespace at a time by default, same as kubectl without -A. A cross-namespace problem — a NetworkPolicy blocking traffic between two namespaces, a ResourceQuota fight — can look like nothing's wrong from inside just one of them. Press 0 on the namespace picker (or launch with -A) before concluding a namespace is clean.

A live watch can still go stale. k9s reconnects its watches automatically far more often than a bare kubectl get -w left running would, but a real network partition or an API server restart mid-rolling-upgrade can still leave a view showing a connection-lost banner rather than silently going quiet — kubectl's own page covers the identical underlying watch-reconnection gap; the fix is the same instinct either way: trust a banner or an explicit reconnect over an assumption that "it would say something if it were stale."

Custom hotkeys and plugins can drift from what you remember. Once hotkeys.yaml or plugins.yaml has been edited — by you, six months ago, or by whoever set up the machine you're borrowing — the keybindings on screen are no longer the defaults documented anywhere online. ? from the current view is the one source of truth that can't be stale, because it's printing exactly what's configured on this machine right now.

⚠ Not available on exam day — verify officially before you rely on it

k9s is a practice-speed and production-operations tool, not an exam-day one. CKA, CKAD, and CKS are performance-based exams delivered in a locked-down, browser-based remote terminal, and the officially provided environment ships a shell with kubectl and a text editor (vim/nano) — candidates cannot install or bring their own additional tooling into that terminal. Build your muscle memory in k9s during study if it helps you move faster, but drill the equivalent raw kubectl commands too, since those are what the proctored terminal actually gives you. Exact permitted tooling, documentation access, and proctoring rules change and are enforced strictly — this course is an independent, unofficial study resource, not affiliated with the CNCF or Linux Foundation, so confirm current exam-environment rules in your Linux Foundation candidate handbook before you sit, not from a study guide.

k9s vs. the alternatives

☺ Like you're 10: Every other option here is a different window onto the same cluster — none of them replace kubectl, and none of them get you past a permission kubectl wouldn't already grant you.

OptionModelBest whenCosts you
kubectlScriptable CLI, one HTTPS request per invocationAutomation, CI, precise one-off commands, anything that has to be reproducible in a pipeline — and the only option actually present in a proctored exam terminalNo persistent view; every question is a fresh command, retyped namespace and all
k9sTerminal UI, live watches, same client-go/API path as kubectlFast interactive triage across many resources — filtering, drilling, logs and exec without retyping selectors each timeNot scriptable; still one human driving it, one cluster/context at a time; nothing installed remotely to help teammates
Lens / OpenLensDesktop GUI, graphical resource browser, built-in metrics and terminal panesNewcomers, or anyone who wants graphs and a resource tree without memorizing a single flag or keyA heavier client to install and keep patched per machine; less naturally scriptable or shareable over SSH than a terminal tool
Kubernetes DashboardWeb UI, deployed as a workload running inside the cluster itselfA shared, browser-based view for people who won't install a CLI or a desktop app at allOne more thing running in-cluster with its own RBAC surface and ingress exposure to secure carefully — the one option on this table that isn't "nothing extra to run"

The honest framing is the same one kubectl's own page lands on for Helm and Kustomize: this isn't "kubectl or k9s." k9s has no independent execution path — it is kubectl's own reach, wearing a faster interface, watching the same objects the observability page already told you to instrument, through the same RBAC gate the security page already told you not to trust a UI to bypass.

🎬 At the Pod Squad
🦊

Foxy: Something in checkout is flapping — I can smell it before the dashboard even loads.

🐰

Remy the Rabbit: On it — k9s -n checkout, filter for the Deployment, drill in... three Pods, two Ready, one restarting every ninety seconds.

🦊

Foxy: Don't exec in yet. Pull d for describe first — I want the events, not a guess.

🐰

Remy the Rabbit: OOMKilled. Memory limit's set way under what this build actually needs.

👺

Gizmo the Gremlin: Easy — just hit Ctrl-D on the limit and remove it entirely. No limit, no more OOMKills, ever. 🤑

🦥

Sol the Sloth: That's not fixing it, that's hiding it from the scheduler and letting it eat the node instead. Give me the actual usage numbers and I'll set a real limit.

🐢

Timmy the Turtle: And whatever you set — you'll only be able to set it because your RoleBinding already allows patching this Deployment. k9s didn't grant you anything new to get here.

🐰

Remy the Rabbit: Fair. e to edit, bump the limit to what Sol says, watch :xray deploy until the ReplicaSet settles. Still faster than four separate kubectl lines.

🐢 Timmy's checkpoint

1. What underlying mechanism does k9s use to keep its resource tables "live," and which existing controllers in this course already use that same mechanism? 2. Why can k9s run with zero components installed inside the cluster, unlike the Kubernetes Dashboard? 3. If your RoleBinding doesn't grant delete on Pods, what happens when you press Ctrl-D on a Pod row in k9s? 4. Name the four regions of the k9s screen and what each one shows. 5. What does :xray deploy draw, and which existing kubectl krew plugin draws the conceptually identical thing as static text? 6. Name three of the five config files under ~/.config/k9s/ and what each one is for. 7. Why can't exam-day muscle memory rely on k9s specifically?

Check your answers
  1. Kubernetes watches (a long-lived GET ?watch=true), via client-go — the identical mechanism every built-in controller (ReplicaSet's, Deployment's, and any operator's) uses to react to changes without polling, as covered in the API & controller pattern page.
  2. Because k9s talks to kube-apiserver directly from your own machine using your kubeconfig, the same way kubectl does — it needs no Deployment, Service, or in-cluster component of its own, unlike the Dashboard, which runs as a workload inside the cluster and so carries its own RBAC surface and exposure to secure.
  3. The delete request fails the same way kubectl delete pod would — k9s has no privilege beyond what your identity's RBAC bindings already grant, so a UI keystroke doesn't bypass authorization any more than a CLI command does.
  4. The header (context/cluster/user and cluster-wide resource gauges), the crumb trail (how you navigated to the current view), the resource table (the live, filterable, sortable rows), and the command line/hotkey footer (: to jump resources, plus the keys that apply to whatever resource type is currently on screen).
  5. The ownership tree built from ownerReferences — Deployment → ReplicaSet → Pods — drawn live and expandable; kubectl's tree krew plugin (covered on the kubectl page) draws the conceptually identical tree, just as static text from a single command.
  6. config.yaml (general behavior — refresh rate, read-only mode, skin), plugins.yaml (bind a key to any shell command, scoped to a resource type, with $NAME/$NAMESPACE/$CONTEXT substituted in), hotkeys.yaml (jump straight to a saved view with one keystroke), aliases.yaml (short names of your own layered on the API server's own), and views.yaml (per-resource saved sort column/order).
  7. Because the officially provided CKA/CKAD/CKS remote exam terminal ships only kubectl and a text editor — candidates cannot install additional tooling into it, so any speed built purely on k9s-specific keystrokes doesn't transfer; the underlying kubectl commands it's shelling out to are what the exam terminal actually gives you, and that's exactly what still needs to be fast on its own.