Module 00 · Orientation · 30 min · You are: Platform and product
Orientation: what a platform team owns in Kubernetes
In this module
- You learn: What Kubernetes does (you declare what should run, controllers make it so), which parts of a shared cluster the platform team owns and which a product team owns, and what a namespace is.
- You do: Tell the course your GitHub name, check your tools, create your repo
k8s-platform, create a three-node cluster on your laptop, and declare the first namespace from a file. - You end with: A cluster
kind-k8swith threeReadynodes. Your repo on GitHub holdscluster/kind.yamlandtenants/checkout/namespace.yaml. The namespacecheckout-devexists and is empty.
Most Kubernetes courses teach the objects one by one, as if you were alone on the cluster. A platform team is never alone: several teams share the cluster, and the work is deciding what each team may do, how much it may use, and what happens to its pods when a node goes away. You build that from the first module: a repo that says what should exist, a cluster that you only change from that repo, and one folder per team. You play the platform team and team-checkout, a product team.
Open this module in the interactive course to track progress.
Concepts
- Declare, then reconcile
- You send the API server an object that says what you want. A controller compares it with what exists and closes the gap, again and again. Nothing is "run"; things are made true.
- Control plane and nodes
- The control plane (API server, etcd, scheduler, controllers) decides. Nodes run a kubelet that starts containers. On kind every node is a Docker container on your laptop.
- Namespace
- A named slice of the cluster. Names, quotas, permissions and network rules attach to it. It is the unit a platform team hands to a product team.
- Two owners
tenants/<team>/is the platform team's: the namespace and the rules around it.apps/<team>/is the product team's: what runs inside.- You are the reconciler
- In this course a change is a commit followed by
kubectl apply, which you run. Argo CD later does that step for you.
Who owns what in a shared cluster
| Object | Folder | Owner | Module |
|---|---|---|---|
| Namespace, Pod Security labels | tenants/<team>/ | Platform team | 00, 06 |
| ResourceQuota, LimitRange | tenants/<team>/ | Platform team | 03 |
| Role, RoleBinding, ServiceAccount | tenants/<team>/ | Platform team | 05 |
| NetworkPolicy | tenants/<team>/ | Platform team | 06 |
| Deployment, Service, Ingress, ConfigMap, PodDisruptionBudget | apps/<team>/ | Product team | 01 to 04 |
| A Secret's value | Neither folder | Product team | 02 |
| Nodes, ingress controller, network plugin | cluster/ | Platform team | 00, 01 |
Kubernetes does not care about folders. The folder is the ownership line people agree on.
Files to know
Where you are heading, in two minutes. These files from the reference repo describe the finished setup. Click Quick look on each: it opens the real file right here, with numbered circles on the lines that matter.
Open them in your editor or on GitHub; the bullets say what to look at.
README.md(reference, your repo): The map of the reference repo. Read the tables, skip the prose.cluster/kind.yaml(reference, your repo): The course cluster.tenants/checkout/namespace.yaml(reference, your repo): Where you are heading: the team label and the security level. Today you write only the first label.- The team label, then the Pod Security labels you add in module 06 L6–13
Steps
1. Tell the course who you are
Your GitHub user (or organisation) and a folder for your repo and the reference clone. Every command and link uses them. They stay in your browser.
Set your GitHub user and workspace folder in the interactive course or the form at the top of the notes index; these notes then show your values.
2. Check your tools
Paste this into a terminal. Each tool reports ok (with its version), MISSING (with where to get it) or PROBLEM (installed but not usable yet, like Docker not running or gh not logged in). Install what is missing your usual way, then run it again until it says All set. Versions: kind 0.33 or newer (it runs the Kubernetes 1.37 node image, and its built-in network plugin enforces the network rules of module 06), kubectl 1.36 to 1.38, Docker with 4 GB of memory.
check() { # tool, works-check, version, problem, where-to-get-it, optional
if ! command -v "$1" >/dev/null 2>&1; then
if [ -n "$6" ]; then printf ' optional %-22s not installed (%s)\n' "$1" "$5"
else printf ' MISSING %-22s install it: %s\n' "$1" "$5"; bad=$((bad+1)); fi
elif ! eval "$2" >/dev/null 2>&1; then printf ' PROBLEM %-22s %s\n' "$1" "$4"; [ -n "$6" ] || bad=$((bad+1))
else printf ' ok %-22s %s\n' "$1" "$(eval "$3" 2>&1 | head -n 1)"; fi
}
bad=0
check 'git' 'git --version' 'git --version' 'installed, but it does not run' 'https://git-scm.com/downloads'
check 'docker' 'docker info' "docker version --format '{{.Server.Version}}'" 'installed, but the Docker daemon is not running: start Docker Desktop (or the docker service)' 'https://docs.docker.com/get-docker/'
check 'kind' 'kind version' 'kind version' 'installed, but it does not run' 'https://kind.sigs.k8s.io/docs/user/quick-start/#installation'
check 'kubectl' 'kubectl version --client' 'kubectl version --client' 'installed, but it does not run' 'https://kubernetes.io/docs/tasks/tools/'
check 'gh' 'gh auth status' 'gh --version' 'installed, but not logged in: run gh auth login' 'https://cli.github.com/'
check 'curl' 'curl --version' 'curl --version' 'installed, but it does not run' 'your package manager (it ships with macOS and most Linux distributions)'
if [ "$bad" -eq 0 ]; then echo "All set: every tool is ready."; else echo "$bad tool(s) need attention. Install or fix them, then run this check again."; fi
3. Clone the reference repo
The finished implementation. You copy from it and compare with it; you never push to it.
mkdir -p ~/platformengr && git clone -q https://github.com/platformengr/kubernetes-platform.git ~/platformengr/kubernetes-platform
Check that it arrived. You should see cluster, tenants, apps, labs and docs:
ls ~/platformengr/kubernetes-platform
4. Create your repo
Your repo of manifests: what should exist in the cluster, one folder per owner. Public, so the lab checks on this site can read it.
cd ~/platformengr && gh repo create YOUR_GITHUB_USER/k8s-platform --public --clone
Go into it and make the folders: cluster for the platform team's cluster files, tenants/checkout for the rules around team-checkout, apps/checkout for what team-checkout runs, and docs.
cd ~/platformengr/k8s-platform && git switch -C main && mkdir -p cluster tenants/checkout apps/checkout docs
5. Create the cluster
Copy the cluster file into your repo, then let kind build the cluster from it. It takes one to two minutes and pulls the node image the first time.
cp ~/platformengr/kubernetes-platform/cluster/kind.yaml ~/platformengr/k8s-platform/cluster/
Create the cluster. Three nodes: one control plane and two workers, each a Docker container.
kind create cluster --config ~/platformengr/k8s-platform/cluster/kind.yaml
kind pointed kubectl at the new cluster. Expect kind-k8s: before you change anything on any cluster, this is the line to check.
kubectl config current-context
Expect three rows with STATUS Ready: k8s-control-plane with ROLES control-plane, and two workers.
kubectl get nodes
A kind node is a container. Expect the same three names:
docker ps --format '{{.Names}}'
6. Find four things in the cluster
Kubernetes runs its own parts as pods in the kube-system namespace. List them with the node each one runs on (-o wide adds the NODE column).
kubectl get pods -n kube-system -o wide
1. kube-apiserver-k8s-control-plane. The only door into the cluster. kubectl, the controllers and the kubelets all talk to it, and to nothing else.
2. etcd-k8s-control-plane. The database where every object is stored. Only the API server talks to it.
3. coredns-..., twice. The cluster's DNS: it turns names like checkout.checkout-dev into addresses. You will need it in module 06.
4. kindnet-..., once per node (three). The pod network. It gives every pod an address and, from module 06, enforces your network rules.
7. Declare the first namespace
team-checkout gets a namespace. The platform team writes it, so it goes under tenants/. The label team: checkout says who it belongs to; later rules select on it.
Create ~/platformengr/k8s-platform/tenants/checkout/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: checkout-dev
labels:
team: checkout
Ask the API server what it would do, without doing it. Expect namespace/checkout-dev created (server dry run): the file is valid and you are allowed.
cd ~/platformengr/k8s-platform && kubectl apply --dry-run=server -f tenants/checkout/namespace.yaml
Create it. kubectl apply sends the file to the API server, which stores the object. Expect namespace/checkout-dev created.
kubectl apply -f tenants/checkout/namespace.yaml
Read its labels. Expect your team=checkout and one you did not write, kubernetes.io/metadata.name=checkout-dev: every namespace gets it, and network rules use it in module 06.
kubectl get namespace checkout-dev --show-labels
8. Commit
Git now says what the cluster has. Push it, so it is on GitHub.
cd ~/platformengr/k8s-platform && git add -A && git commit -qm "Cluster and the checkout-dev namespace" && git push -qu origin main
Simplified for the course
kind runs every node as a container on one machine. A real cluster has machines in several zones, a managed or highly available control plane, and nodes that are replaced, not repaired.
You are cluster-admin and you apply by hand. In a team, nobody applies by hand: a pull request is merged and a controller applies it. That is the Argo CD course.
One namespace per team, called
checkout-dev. Real teams get one per environment (checkout-dev,checkout-prod), usually on different clusters.Versions are pinned: the node image in
cluster/kind.yaml(Kubernetes 1.37.0), the ingress controller incluster/traefik.yaml, the app image in each Deployment. A bump is one commit that changes all of them together.
Break it on purpose: A node stops answering
Your laptop slept, Docker restarted, or (in a real cluster) a machine died. One worker is gone and nobody told Kubernetes in advance.
You practise: reading node status, and finding where a kind node really lives.
1. Break it
docker stop k8s-worker2
2. What you see
Run kubectl get nodes now, and again after a minute. Expected: three nodes Ready. You get: k8s-worker2 turns NotReady after about 40 to 60 seconds. Nothing told you: there is no error, only a status.
Look at kubectl describe node k8s-worker2, under Conditions.
3. Fix it yourself
Bring the node back to Ready without recreating the cluster. Done when kubectl get nodes shows three Ready nodes.
Hint 1
The conditions say Kubelet stopped posting node status. The kubelet runs on the node. Where does a kind node run?
Hint 2
docker ps -a --format '{{.Names}} {{.Status}}': one of the three is Exited.
The fix
docker start k8s-worker2
Give the kubelet half a minute to report in, then expect three Ready nodes:
kubectl get nodes
After you fixed it: the five questions
- What failed?
- A node: its kubelet stopped reporting to the API server.
- How was it detected?
- The node controller marks a node
NotReadywhen heartbeats stop. Nobody is paged by that on its own; you saw it because you looked. - Who responds?
- The platform team: it owns the nodes.
- How is it corrected?
- Start the node (here), or replace it (in a real cluster).
- How is it prevented?
- You cannot stop a node from dying. You make it not matter: more than one replica, spread over nodes (module 04), and an alert on
NotReadynodes.
Quiz
- What does
kubectl apply -f namespace.yamlactually do?- It sends the object to the API server, which stores it; controllers then make the cluster match
- It logs in to a node over the network and runs the commands needed to create the namespace there
- It copies the file into the cluster, where every kubelet reads it the next time the node restarts
- Why does this course put namespaces under
tenants/and Deployments underapps/?- Because kubectl applies folders in alphabetical order, and
apps/has to be applied beforetenants/ - Because they have different owners: the platform team sets the rules around a team, the team decides what runs inside
- Because Kubernetes only accepts Namespace objects from a folder whose name is
tenants
- Because kubectl applies folders in alphabetical order, and
- A node shows
NotReady. What does that tell you?- Every pod on that node has crashed and has already been deleted by the scheduler
- The node is being upgraded on purpose and will come back by itself when it is done
- The control plane has stopped hearing from that node's kubelet
Answers
- It sends the object to the API server, which stores it; controllers then make the cluster match kubectl only talks to the API server; everything else is controllers reacting.
- Because they have different owners: the platform team sets the rules around a team, the team decides what runs inside The folder is the ownership line; Kubernetes does not care about folders at all.
- The control plane has stopped hearing from that node's kubelet
NotReadyis about heartbeats, not about the pods.
Field note
The first thing I check on any cluster I am asked to look at is kubectl config current-context. The second is whether what runs there is written down anywhere. If it is not, that is the real problem, whatever the ticket says.