Meet kubectl

The remote control for your cluster.

Beginner10 min · lesson 4 of 24
In plain terms
kubectl talks to the API server. You do not SSH to nodes to start apps.

kubectl (most people say "kube-control," a few say "kube-cuttle") is the CLI for a Kubernetes cluster. A cluster is a group of nodes that run your apps. You do not SSH to those nodes to start workloads. You run kubectl on your laptop; it sends a request to the cluster and prints the reply. Nearly everything in this course goes through kubectl, so a small set of verbs is the foundation.

Where your commands actually go

kubectl does not start processes on worker nodes itself. Every command is an API request. The API server is the only entrance to the control plane. (API means application programming interface: the doorway one program uses to talk to another.) kubectl talks only to that server. You ask, and the answer comes back to your screen. That one rule explains a lot of how kubectl behaves.

kubectl is your remote control
1You type a kubectlcommandget, describe, logs, apply, or…2It reaches the APIserverthe one doorway into the…3The cluster actslists, creates, or changes…4Results return toyouprinted in your terminal
kubectl sends every request to the API server and shows you the result. It never touches the worker machines directly, which is why the same commands work no matter which cluster you point at.

The five commands you'll use every day

You can get surprisingly far with five verbs. get lists things ("show me the pods"). describe prints the full details of one thing, including a running list of recent events. logs shows what a container printed out while it ran. apply creates or updates things from a file. delete removes them. After the verb you name a resource type, like pods or nodes or deployments, and often a specific name after that. Learn those five and you can handle most of a normal day.

Start simple. Ask the cluster which machines it's built from:

terminal
kubectl get nodes
output
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane 9d v1.31.0
kind-worker Ready <none> 9d v1.31.0
kind-worker2 Ready <none> 9d v1.31.0

Now list what is running. A Pod is the smallest thing Kubernetes runs, usually one container wrapped so the cluster can schedule and watch it. By default kubectl shows one namespace, a named slice of the cluster. Add -o wide for extra columns, such as which node a Pod landed on and its IP. Add -A to list every namespace at once:

terminal
kubectl get pods -o wide
output
NAME READY STATUS RESTARTS AGE IP NODE
web-7c9fd8b6f5-2xk9q 1/1 Running 0 12m 10.244.1.7 kind-worker
web-7c9fd8b6f5-lp8vt 1/1 Running 0 12m 10.244.2.3 kind-worker2

You'll mistype one of these before long, and it helps to have already seen what that looks like so it doesn't throw you. Ask for a resource type that doesn't exist, and kubectl stops you with this:

terminal
kubectl get pdos
output
error: the server doesn't have a resource type "pdos"

Ask for a Pod that simply isn't there, and the message reads differently:

terminal
kubectl get pod web-old
output
Error from server (NotFound): pods "web-old" not found

Read the two apart. The first says the kind of thing you asked for, pdos, isn't a word Kubernetes knows, so it's nearly always a spelling slip (pods, not pdos). The second is the opposite: pods is a real kind, the cluster understood you and went looking, and there just isn't a Pod named web-old. One error means "I don't know that word"; the other means "I know the word, but that thing isn't here." That second line, Error from server, is the API server answering you back through the same front door from earlier. Telling those two apart is half of learning to read Kubernetes errors, and you'll meet both in your first hour.

When a Pod is genuinely stuck rather than just misspelled, describe is the command that tells you why. It prints a lot of detail. Scroll down to the Events section at the bottom, which reads in plain sentences:

terminal
kubectl describe pod web-7c9fd8b6f5-2xk9q
output
Name: web-7c9fd8b6f5-2xk9q
Namespace: default
Node: kind-worker/172.18.0.3
Status: Running
Containers:
web:
Image: nginx:1.27
State: Running
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 12m default-scheduler Successfully assigned default/web-7c9fd8b6f5-2xk9q to kind-worker
Normal Pulled 12m kubelet Successfully pulled image "nginx:1.27"
Normal Started 12m kubelet Started container web

Those Events are gold. Each line names who did what. The scheduler (the part of the control plane that picks which node each Pod should run on) placed the Pod, and then the kubelet on that node pulled the image and started the container. (The kubelet is the small agent Kubernetes runs on every node to start containers and watch over them.) When a Pod is stuck, this is where the reason turns up, in words you can actually read, long before you need to open the logs.

Two ways to tell kubectl what you want

There are two styles of kubectl command, and the difference matters later. Imperative gives an order for right now: kubectl run, kubectl create, kubectl scale. That is fine for learning and throwaway experiments. Declarative writes the desired state in a file and uses kubectl apply -f; the cluster makes reality match the file. Real projects run that way because a file can be saved, reviewed, and kept in version control (Git records every change), so there is one agreed record of what the cluster should look like.

That file is written in YAML, a plain-text format for settings that's meant to be easy for people to read. A complete Kubernetes file always has four parts: apiVersion (which version of the Kubernetes API to speak), kind (what type of object this is), metadata (its name and any labels, which are simple tags you attach so you can find things later), and spec (what you actually want). Here's a whole, applyable file that describes one Pod:

web-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: web
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.27
ports:
- containerPort: 80

Apply it, and kubectl tells you plainly what changed:

terminal
kubectl apply -f web-pod.yaml
output
pod/web created

From here you'd run kubectl get pods again to watch it come up, and if it misbehaves, kubectl describe pod web to see why. Same five verbs, over and over. That loop, apply then check then fix, is most of what working with Kubernetes feels like day to day.

kubectl is a remote control aimed at the API server using a kubeconfig. Contexts pick cluster + user + namespace defaults. Most "wrong cluster" outages are a context mistake, not a mysterious bug. Make a habit of printing the current context before destructive commands.

Imperative commands (kubectl run, kubectl create) are fine for learning and break-glass. Declarative apply of YAML is what you want for anything that should survive the next engineer. Prefer apply for shared environments rather than a pile of one-off imperative edits nobody can replay.

On-call, the trio get / describe / logs covers most first-pass triage. Events on describe often explain a failure faster than staring at STATUS alone.

Try this

Confirm who you are talking to (context), list namespaces, and practice get + describe on a short-lived pod.

terminal
$ kubectl config current-context
kind-kind
$ kubectl get ns
NAME STATUS AGE
default Active 12d
kube-system Active 12d
kube-public Active 12d
$ kubectl run tip --image=busybox:1.36 --restart=Never --command -- sleep 120
pod/tip created
$ kubectl get pod tip
NAME READY STATUS RESTARTS AGE
tip 1/1 Running 0 5s
$ kubectl describe pod tip | Select-String -Pattern 'Status:|Node:|Events:' -Context 0,8
Status: Running
Node: kind-worker/172.18.0.3
Events:
Normal Scheduled ... Successfully assigned default/tip ...
$ kubectl delete pod tip
pod "tip" deleted

Takeaway

Always know your context, prefer declarative apply for lasting objects, and triage with get → describe → logs. kubectl is how you read the API the controllers already trust.

Quick check
01You run kubectl get pod web-old and get back: Error from server (NotFound): pods "web-old" not found. What is Kubernetes actually telling you?
Incorrect — That's the other error. A misspelled type gives "the server doesn't have a resource type" instead. Here pod is spelled fine and Kubernetes understood it.
Correct — The cluster understood the request, looked, and found nothing by that name. Check the name with kubectl get pods, or add -A to look across namespaces.
Incorrect — A dead cluster gives a connection error instead, like "The connection to the server was refused." Here the server clearly answered, it just had nothing to return.
Incorrect — A config problem shows up as an auth or connection failure, not a clean NotFound. Getting a NotFound back actually proves the connection is working.
02The lesson describes two styles: the imperative style (kubectl run, kubectl create) and the declarative style (kubectl apply -f a file). Why do real projects prefer the declarative style?
Incorrect — every kubectl command, apply included, goes through the API server; speed is not the reason.
Incorrect — the imperative kubectl run also creates Pods; the difference is how the intent is recorded, not whether it works.
Correct — a declarative file is like handing a driver an address instead of turn-by-turn directions; it can be shared with teammates and re-applied exactly.
Incorrect — that describes the imperative style; the declarative style works precisely by writing the state into a file.
03Two web Pods are Running, and you need to see, in a single command, which node each one landed on and the IP address it was given. Which command do you run?
Correct — -o wide adds extra columns to the listing, including the node each Pod landed on and its IP address.
Incorrect — the plain listing shows name, ready, status, restarts, and age, but not the node or the IP address.
Incorrect — logs prints what a container wrote out; it does not report which node a Pod is on or its IP address.
Incorrect — -A lists Pods across all namespaces but still does not add the node or IP columns; that is what -o wide is for.
Always know which cluster you're pointing at
kubectl talks to whatever cluster your current settings point at, and it's easy to fire a command at the wrong one, like deleting something in production when you thought you were on a test cluster. Before you run anything destructive, run kubectl config current-context to confirm where you're aimed. That one habit saves real pain the moment you're juggling more than one cluster.

Related