Module 1: Why Kubernetes And The Continuity Challenge
5. Hands-on: your first cluster
Description
With kind and kubectl installed (lesson 4), this lesson creates the cluster that's going to carry the rest of this guide: andes-cargo-cluster, under the exact name aws-serverless-and-containers-guide left documented on ECS and never ran. Everything you see in this lesson really ran to write it — the cluster creation, the control plane confirmation, and the final node listing, all executed against real Docker at the time of writing this lesson, with no paid-plan limit or AWS account involved whatsoever.
Connection to the module
This is the lesson that turns the name andes-cargo-cluster from a documented promise (the previous guide) into a real cluster. Lesson 6 explains what just got created under the hood; lesson 7 loads the inherited image inside this same cluster.
Step 1 — Define the cluster: kind-config.yaml
kind create cluster, with no arguments, creates a single-node cluster (which acts as both control plane and the place where your workloads run). This guide instead uses an explicit configuration file from the start — one control plane and two worker nodes — for a concrete pedagogical reason: lesson 6 explains the difference between the control plane and the nodes, and that distinction is much easier to understand when there are separate nodes to observe, not one container doing both jobs at once.
# kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: andes-cargo-cluster
nodes:
- role: control-plane
- role: worker
- role: worker
Three fields deserve explanation before you use this file:
kind: Cluster— this isn't the name of thekindtool (even though it matches) — it's the standardkindfield of any manifest in the Kubernetes family, which tells the system what type of object this document describes. You're going to see this same field, with different values (Pod,Deployment,Service), in every manifest in the modules that follow.name: andes-cargo-cluster— the name you decided to reuse from the previous guide (lesson 3 of this module).kinduses this value as the prefix for every Docker container it creates for this cluster.nodes:— the explicit list of what "machines" you want inside the cluster. Onecontrol-planerole and twoworkerroles — each entry in this list becomes, literally, a separate Docker container.
Step 2 — Create the cluster
kind create cluster --config kind-config.yaml --name andes-cargo-cluster
Notice that
--name andes-cargo-clusteris passed both on the command line and inside the file (name: andes-cargo-cluster) — when both match, as here, there's no ambiguity. If you ever use akind-config.yamlwithout thenamefield, the--nameflag is required; if the file does include it, you can omit the flag andkinduses the file's value.
What to expect (literal, executed — the total time varies depending on your machine and connection, but the sequence of steps is always the same):
Creating cluster "andes-cargo-cluster" ...
• Ensuring node image (kindest/node:v1.36.1) 🖼 ...
✓ Ensuring node image (kindest/node:v1.36.1) 🖼
• Preparing nodes 📦 📦 📦 ...
✓ Preparing nodes 📦 📦 📦
• Writing configuration 📜 ...
✓ Writing configuration 📜
• Starting control-plane 🕹️ ...
✓ Starting control-plane 🕹️
• Installing CNI 🔌 ...
✓ Installing CNI 🔌
• Installing StorageClass 💾 ...
✓ Installing StorageClass 💾
• Joining worker nodes 🚜 ...
✓ Joining worker nodes 🚜
Set kubectl context to "kind-andes-cargo-cluster"
You can now use your cluster with:
kubectl cluster-info --context kind-andes-cargo-cluster
Every line with a ✓ is a complete piece of Kubernetes starting up: the kindest/node:v1.36.1 image — the same Kubernetes version number (v1.36.1) you confirmed with kubectl version --client in the previous lesson, not a coincidence — gets pulled or reused from your Docker cache; the three containers get prepared (one per declared node); the CNI gets installed (the networking plugin that lets Pods talk to each other — you come back to this in depth in Module 4); a default StorageClass gets installed; and the two worker nodes join the control plane. The last line is the most important one to read carefully: kind already modified your ~/.kube/config file, adding a new context named kind-andes-cargo-cluster — the exact piece kubectl was missing in lesson 4 to stop failing with "connection refused."
Step 3 — Confirm the control plane
kubectl cluster-info --context kind-andes-cargo-cluster
What to expect (the exact port — 56493 in this case — is randomly assigned by Docker each time the cluster is created; marked as variable. The rest is literal):
Kubernetes control plane is running at https://127.0.0.1:56493
CoreDNS is running at https://127.0.0.1:56493/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
Two lines, two separate confirmations: the control plane (kube-apiserver, the entry point to the whole cluster — you go deeper on this in lesson 6) is up and responding at https://127.0.0.1:<port>; and CoreDNS — the cluster's internal name server, the piece that makes it possible for one Pod to find another by name instead of by IP address, something you're going to use without thinking about it starting in Module 3 — is running too.
Step 4 — Confirm the nodes
Right after creating the cluster, it's normal for the nodes to not be ready yet — the CNI needs a few more seconds to finish configuring on each one:
kubectl get nodes
What to expect (immediately after creating the cluster — note the NotReady status, normal in the first few seconds):
NAME STATUS ROLES AGE VERSION
andes-cargo-cluster-control-plane NotReady control-plane 20s v1.36.1
andes-cargo-cluster-worker NotReady <none> 5s v1.36.1
andes-cargo-cluster-worker2 NotReady <none> 5s v1.36.1
Wait a few more seconds and run the same command again:
kubectl get nodes
What to expect (literal, executed, about 20-30 seconds later — all three nodes at Ready; AGE advances with the clock, marked as variable; NAME, ROLES, and VERSION are literal):
NAME STATUS ROLES AGE VERSION
andes-cargo-cluster-control-plane Ready control-plane 29s v1.36.1
andes-cargo-cluster-worker Ready <none> 14s v1.36.1
andes-cargo-cluster-worker2 Ready <none> 14s v1.36.1
Three nodes, three names built from the name you declared in kind-config.yaml: andes-cargo-cluster-control-plane, andes-cargo-cluster-worker, andes-cargo-cluster-worker2. That naming pattern — cluster prefix, plus role — is literal and predictable, unlike the Pod names you're going to see starting in Module 2, which do carry a randomly generated suffix.
Confirm, from Docker's side, that these are real containers
docker ps --filter "name=andes-cargo-cluster" --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
What to expect (the exact "Up" time is variable; the rest, literal):
NAMES IMAGE STATUS
andes-cargo-cluster-worker2 kindest/node:v1.36.1 Up About a minute
andes-cargo-cluster-worker kindest/node:v1.36.1 Up About a minute
andes-cargo-cluster-control-plane kindest/node:v1.36.1 Up About a minute
This isn't a curiosity — it's this guide's central point of honesty, and you're going to pick it up in depth in lesson 6: every "node" kubectl get nodes showed you as part of a Kubernetes cluster is, literally, a Docker container running on your machine, visible with the same docker ps you already know from docker-essentials-guide and from the previous guide.
Analogy: the scale model of the stadium
Think of this kind cluster as a scale model of a soccer stadium, built to rehearse before the real match. It's not a cardboard simulation — it's made of the same construction materials (concrete, steel) as the big stadium, just at a size that fits on a work table. Every piece you're going to install on top of this cluster for the rest of the guide — the playing field (the Pods), the access tunnels (Ingress), the security system at the gates (admission control) — behaves exactly the same way it would in the full production stadium (EKS, Module 7). The difference between the model and the real stadium isn't in the materials — it's in who built and maintains the structure around it: here, you built it yourself with kind create cluster; on EKS, AWS administers that part for you, with the cost and control implications Module 7 explains in depth.
Deep dive: what "context" means in kubectl
kubectl cluster-info --context kind-andes-cargo-cluster used an argument worth understanding before moving on: a context, in kubectl, is a named combination of three things — which cluster to connect to, with which user/credentials, and in which default namespace to work. kind automatically creates a context named kind-<cluster-name> every time you create a new cluster, and leaves it as the active context — that's why, from now on, you can omit --context in most of this guide's commands, and kubectl will assume you want to talk to andes-cargo-cluster. You can confirm your active context at any time:
kubectl config current-context
What to expect:
kind-andes-cargo-cluster
This context mechanism is exactly what's going to let you, later in your career, have both this local kind cluster and a real EKS cluster configured in the same ~/.kube/config, and switch between them with kubectl config use-context without reinstalling anything.
Common mistakes
Running kind create cluster a second time under the same name, without noticing (workflow). What happens: someone, after having already created andes-cargo-cluster in an earlier attempt, runs this lesson's same command again, and kind responds with an error saying a cluster with that name already exists. Why it happens: it's easy to lose track of which clusters you already have created, especially if you came back to this lesson after a break. How to spot it: kind's error message explicitly mentions node(s) already exist for a cluster with the name "andes-cargo-cluster". How to fix it: first confirm with kind get clusters which clusters already exist; if andes-cargo-cluster is already in the list, you don't need to create it again — just continue with this lesson's Step 3 to confirm it's still healthy.
Interpreting NotReady right after creating the cluster as a failure (conceptual, the most common one specific to this lesson). What happens: someone runs kubectl get nodes the exact instant kind create cluster finishes, sees STATUS: NotReady on all three nodes, and assumes something went wrong. Why it happens: "NotReady" sounds like an error state, not a normal transitional one. How to spot it: if the status is still NotReady after more than a full minute, then there is a real problem to investigate (check docker ps to confirm the three containers are still running); if only a few seconds have passed, it's exactly the expected behavior. How to fix it: this lesson shows it explicitly — the CNI needs a few seconds to finish configuring on each node before it reports Ready. Wait and run kubectl get nodes again.
Not noticing that kind create cluster changed kubectl's active context, and getting confused if you already had another cluster configured (configuration, relevant for anyone arriving with prior Kubernetes experience). What happens: someone who already had another cluster (for example, one from an earlier personal project) configured in their ~/.kube/config, runs this lesson's commands, and is later surprised that their kubectl commands in another project now point to andes-cargo-cluster instead of the cluster they expected. Why it happens: kind create cluster leaves the new context as the active one by default — convenient behavior for this guide, but something that can surprise anyone with multiple clusters. How to spot it: kubectl config current-context shows kind-andes-cargo-cluster when you expected another name. How to fix it: use kubectl config get-contexts to see all available contexts, and kubectl config use-context <name> to switch between them — nothing was deleted, only which context is active changed.
Exercises
Exercise 1 — Explain kind-config.yaml without looking at it. Without going back to Step 1, write from memory this file's three main fields and what each one controls.
See solution
kind: Cluster — declares what type of object the file describes (a complete cluster, not an individual node or any other resource type). name: andes-cargo-cluster — the cluster's name, used as the prefix for every Docker container kind creates. nodes: — the explicit list of nodes you want, with their role (control-plane or worker); each entry in this list becomes a separate Docker container.
Exercise 2 — Predict the number of containers. If you changed kind-config.yaml to have one control-plane and four workers, how many Docker containers would you expect to see with docker ps --filter "name=andes-cargo-cluster" after creating the cluster?
See solution
Five — one for each entry declared in the nodes: list (one control-plane plus four worker), because each node declared in the configuration file becomes, literally, an independent Docker container. This is exactly the point lesson 6 develops in depth: "a Kubernetes node" and "a Docker container" are, in kind, the same thing seen from two different layers.
Exercise 3 — Diagnose an unexpected context. A coworker runs kubectl get pods after following this lesson, and gets an error about a cluster it doesn't recognize, with a name that isn't kind-andes-cargo-cluster. Which command would you use first to diagnose the problem, and which command would fix it?
See solution
First, kubectl config current-context — to confirm which context is active right now. If the result isn't kind-andes-cargo-cluster, the coworker probably has another cluster configured that stayed active (for example, if they created another kind cluster after this one, or if they already had another project's context active). The command that fixes it is kubectl config use-context kind-andes-cargo-cluster, which switches the active context without deleting any existing configuration for other clusters.
Summary and next step
In this lesson you really created your first Kubernetes cluster: andes-cargo-cluster, with one control plane and two worker nodes, running Kubernetes v1.36.1 inside three separate Docker containers on your own machine. You confirmed the control plane with kubectl cluster-info, confirmed all three nodes in Ready state with kubectl get nodes, and saw, from Docker's side, that every "node" is literally one more container in your docker ps. This is, literally, the cluster aws-serverless-and-containers-guide promised and could never run — it now exists.
Before moving on you should be able to: explain kind-config.yaml's three main fields; distinguish a normal transitional NotReady state from a real problem; and use kubectl config current-context/use-context to diagnose which cluster kubectl is talking to at a given moment.
Lesson 6 opens up this cluster: what the control plane actually is, what etcd does, and why every component you just created runs as one more Docker container, with no additional magic.
Resources
- kind — Quick Start — the official guide, including the exact syntax for
kind create cluster --config. - kind — Configuration — complete reference for the
kind-config.yamlformat, including thecontrol-planeandworkerroles. - Kubernetes —
kubectlCheat Sheet — official command reference, includingcluster-infoandconfig current-context. - Kubernetes — Configure Access to Multiple Clusters — the official documentation on contexts, the piece that resolves this lesson's third common mistake.