feat(scenarios): add 10 new Kubernetes Basics scenarios

Signed-off-by: Abhinav Sinha <[email protected]>
This commit is contained in:
Abhinav Sinha
2026-06-20 23:54:52 +05:30
parent 6c3843cb90
commit d42aa779b1
11 changed files with 526 additions and 1 deletions
+11 -1
View File
@@ -36,6 +36,16 @@
"annotate-resource-basics",
"init-container-task-basics",
"restart-policy-mcq",
"kubectl-output-format-mcq"
"kubectl-output-format-mcq",
"kubectl-explain-basics",
"kubectl-patch-basics",
"field-selector-basics",
"sort-by-basics",
"kubectl-diff-basics",
"node-cordon-basics",
"static-pods-mcq",
"deployment-strategy-mcq",
"pod-node-assignment-mcq",
"pod-env-vars-inline"
]
}
@@ -0,0 +1,44 @@
{
"id": "deployment-strategy-mcq",
"title": "Deployment Update Strategies",
"category": "Workloads",
"difficulty": "Medium",
"type": "mcq",
"weight": 3,
"description": "## Deployment Update Strategies\n\nA production Deployment is being updated. The team requires that **at least 3 pods are always available** during the rollout, and at most **5 pods can exist at any one time** (the Deployment has 4 replicas).\n\nWhich `strategy` configuration achieves this?\n\n```yaml\nspec:\n replicas: 4\n strategy:\n type: RollingUpdate\n rollingUpdate:\n maxUnavailable: ???\n maxSurge: ???\n```",
"options": [
{
"id": "a",
"text": "`maxUnavailable: 1`, `maxSurge: 1` — at most 1 pod unavailable (3 always up), at most 5 pods total"
},
{
"id": "b",
"text": "`maxUnavailable: 0`, `maxSurge: 0` — ensures zero disruption with no extra pods"
},
{
"id": "c",
"text": "`maxUnavailable: 2`, `maxSurge: 2` — allows 2 pods down (2 available) and 6 total pods"
},
{
"id": "d",
"text": "`maxUnavailable: 4`, `maxSurge: 1` — replaces all pods at once before adding new ones"
}
],
"correct_option": "a",
"explanation": "With 4 replicas, `maxUnavailable: 1` means at most 1 pod can be unavailable → minimum 3 pods always running. `maxSurge: 1` means at most 1 extra pod can be created → maximum 5 pods total (4+1). This satisfies both constraints. Option B (`maxUnavailable: 0, maxSurge: 0`) is invalid — at least one of them must be non-zero. Option C allows only 2 available pods (violates the minimum-3 requirement). Option D would take all existing pods offline before any new ones are ready.",
"hints": [
{
"title": "Understanding maxUnavailable and maxSurge",
"body": "maxUnavailable: how many pods can be unavailable during an update (can be a count or %). maxSurge: how many extra pods above the desired count can exist simultaneously.",
"command": "kubectl explain deployment.spec.strategy.rollingUpdate"
},
{
"title": "Check the current strategy on a deployment",
"body": "Use jsonpath to inspect the rolling update config of any existing deployment.",
"command": "kubectl get deployment <name> -o jsonpath='{.spec.strategy.rollingUpdate}'"
}
],
"setup_commands": [],
"default_namespace": "default",
"teardown_commands": []
}
+60
View File
@@ -0,0 +1,60 @@
{
"id": "field-selector-basics",
"title": "Filtering Resources with Field Selectors",
"category": "Core Concepts",
"difficulty": "Easy",
"type": "task",
"weight": 4,
"description": "## Filtering Resources with Field Selectors\n\nField selectors let you filter Kubernetes resources by the **value of specific object fields** — similar to how label selectors work but targeting any field in the resource spec or status.\n\nCommon use cases:\n- Find all pods in a specific phase: `kubectl get pods --field-selector=status.phase=Running`\n- Find resources in a specific namespace: `kubectl get pods --field-selector=metadata.namespace=kube-system`\n\n**Your task:**\n\nSeveral pods have been created for you — some Running, some in a Failed state.\n\n1. Use a field selector to list only the **Running** pods and count them. Save the count to `/tmp/running-count.txt`\n2. Use a field selector to list all pods **not** in the `default` namespace (filter by `metadata.namespace!=default`)\n\n```bash\n# Count running pods:\nkubectl get pods --field-selector=status.phase=Running --no-headers | wc -l\n```",
"hints": [
{
"title": "Field selector syntax",
"body": "Use `--field-selector=field.path=value` to filter. Multiple conditions are comma-separated. Supported operators: `=`, `==`, `!=`.",
"command": "kubectl get pods --field-selector=status.phase=Running"
},
{
"title": "Save running pod count to a file",
"body": "Pipe the result through wc -l and redirect to the file.",
"command": "kubectl get pods --field-selector=status.phase=Running --no-headers | wc -l | tr -d ' ' > /tmp/running-count.txt"
}
],
"setup_commands": [
{
"command": "kubectl run alpha --image=nginx:alpine 2>/dev/null || true"
},
{
"command": "kubectl run beta --image=nginx:alpine 2>/dev/null || true"
},
{
"command": "kubectl run crash-pod --image=busybox:1.36 --restart=Never -- /bin/false 2>/dev/null || true"
},
{
"command": "kubectl rollout status deployment/alpha --timeout=30s 2>/dev/null || true"
}
],
"validation": {
"commands": [
{
"description": "File /tmp/running-count.txt exists and has a numeric value",
"command": "cat /tmp/running-count.txt",
"expected_output": "^[0-9]+$",
"match": "regex"
},
{
"description": "The saved count matches the actual number of Running pods",
"command": "LIVE=$(kubectl get pods --field-selector=status.phase=Running --no-headers 2>/dev/null | wc -l | tr -d ' '); SAVED=$(cat /tmp/running-count.txt 2>/dev/null | tr -d ' '); [ \"$LIVE\" = \"$SAVED\" ] && echo 'match' || echo 'mismatch'",
"expected_output": "match",
"match": "exact"
}
]
},
"default_namespace": "default",
"teardown_commands": [
{
"command": "kubectl delete pod alpha beta crash-pod --ignore-not-found --grace-period=0 --force"
},
{
"command": "rm -f /tmp/running-count.txt"
}
]
}
+65
View File
@@ -0,0 +1,65 @@
{
"id": "kubectl-diff-basics",
"title": "Previewing Changes with kubectl diff",
"category": "Core Concepts",
"difficulty": "Medium",
"type": "task",
"weight": 4,
"description": "## Previewing Changes with `kubectl diff`\n\n`kubectl diff` compares a local manifest file against the **live state** of a resource in the cluster, showing exactly what would change if you applied it — without actually making any changes. This is a safe way to review updates before rolling them out.\n\n**Your task:**\n\nA Deployment named `diffme` is running with 1 replica and image `nginx:1.24`.\n\n1. Write a updated manifest for the same Deployment with **3 replicas** and image `nginx:1.25` to `/tmp/diffme-updated.yaml`\n2. Run `kubectl diff` against it to preview the changes\n3. Then **apply** the updated manifest to make the changes live\n\n```bash\n# Preview changes:\nkubectl diff -f /tmp/diffme-updated.yaml\n\n# Apply changes:\nkubectl apply -f /tmp/diffme-updated.yaml\n```",
"hints": [
{
"title": "Write the updated manifest",
"body": "Create a YAML file with the updated replicas and image values.",
"command": "cat <<EOF > /tmp/diffme-updated.yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: diffme\nspec:\n replicas: 3\n selector:\n matchLabels:\n app: diffme\n template:\n metadata:\n labels:\n app: diffme\n spec:\n containers:\n - name: app\n image: nginx:1.25\nEOF"
},
{
"title": "Run kubectl diff",
"body": "kubectl diff exits with code 1 if there are differences (expected), code 0 if nothing changed.",
"command": "kubectl diff -f /tmp/diffme-updated.yaml; echo \"Exit code: $?\""
},
{
"title": "Apply the manifest",
"body": "Once you have reviewed the diff output, apply the manifest to update the live cluster state.",
"command": "kubectl apply -f /tmp/diffme-updated.yaml"
}
],
"setup_commands": [
{
"command": "kubectl create deployment diffme --image=nginx:1.24 --replicas=1 2>/dev/null || true"
},
{
"command": "kubectl rollout status deployment/diffme --timeout=60s"
}
],
"validation": {
"commands": [
{
"description": "Deployment 'diffme' has 3 replicas",
"command": "kubectl get deployment diffme -o jsonpath='{.spec.replicas}'",
"expected_output": "3",
"match": "exact"
},
{
"description": "Deployment uses nginx:1.25 image",
"command": "kubectl get deployment diffme -o jsonpath='{.spec.template.spec.containers[0].image}'",
"expected_output": "nginx:1.25",
"match": "exact"
},
{
"description": "Manifest file exists at /tmp/diffme-updated.yaml",
"command": "test -f /tmp/diffme-updated.yaml && echo 'exists'",
"expected_output": "exists",
"match": "exact"
}
]
},
"default_namespace": "default",
"teardown_commands": [
{
"command": "kubectl delete deployment diffme --ignore-not-found"
},
{
"command": "rm -f /tmp/diffme-updated.yaml"
}
]
}
@@ -0,0 +1,50 @@
{
"id": "kubectl-explain-basics",
"title": "Exploring the API with kubectl explain",
"category": "Core Concepts",
"difficulty": "Easy",
"type": "task",
"weight": 3,
"description": "## Exploring the API with `kubectl explain`\n\n`kubectl explain` is a built-in reference tool that describes the fields of any Kubernetes resource — directly from the live API server. It is invaluable during exams when you need to recall the exact field name or understand what a field accepts.\n\n**Your task:**\n\nA ConfigMap has been created for you. Use `kubectl explain` to answer the following, then:\n\n1. Create a Pod named `explain-demo` using image `nginx:alpine`\n2. Set the pod's `spec.terminationGracePeriodSeconds` to **5** (use `kubectl explain pod.spec.terminationGracePeriodSeconds` to understand the field)\n\n```bash\n# Explore the pod spec:\nkubectl explain pod.spec\n\n# Inspect a specific field:\nkubectl explain pod.spec.terminationGracePeriodSeconds\n```",
"hints": [
{
"title": "kubectl explain syntax",
"body": "Use dot-notation to drill into nested fields. For example: `kubectl explain pod.spec.containers.resources`.",
"command": "kubectl explain pod.spec.terminationGracePeriodSeconds"
},
{
"title": "Create the pod with the field set",
"body": "Use a heredoc to pipe a YAML manifest with terminationGracePeriodSeconds set to 5 directly to kubectl apply.",
"command": "cat <<EOF | kubectl apply -f -\napiVersion: v1\nkind: Pod\nmetadata:\n name: explain-demo\nspec:\n terminationGracePeriodSeconds: 5\n containers:\n - name: app\n image: nginx:alpine\nEOF"
}
],
"setup_commands": [],
"validation": {
"commands": [
{
"description": "Pod 'explain-demo' exists",
"command": "kubectl get pod explain-demo -o jsonpath='{.metadata.name}'",
"expected_output": "explain-demo",
"match": "exact"
},
{
"description": "Pod uses nginx:alpine image",
"command": "kubectl get pod explain-demo -o jsonpath='{.spec.containers[0].image}'",
"expected_output": "nginx:alpine",
"match": "exact"
},
{
"description": "terminationGracePeriodSeconds is 5",
"command": "kubectl get pod explain-demo -o jsonpath='{.spec.terminationGracePeriodSeconds}'",
"expected_output": "5",
"match": "exact"
}
]
},
"default_namespace": "default",
"teardown_commands": [
{
"command": "kubectl delete pod explain-demo --ignore-not-found --grace-period=0 --force"
}
]
}
+51
View File
@@ -0,0 +1,51 @@
{
"id": "kubectl-patch-basics",
"title": "Patching Resources with kubectl patch",
"category": "Core Concepts",
"difficulty": "Medium",
"type": "task",
"weight": 5,
"description": "## Patching Resources with `kubectl patch`\n\n`kubectl patch` lets you surgically update specific fields of a live resource without editing the full manifest. It supports three patch strategies: `merge`, `json`, and `strategic`.\n\n**Your task:**\n\nA Deployment named `patchme` is running in the `default` namespace with 1 replica.\n\n1. Use `kubectl patch` to update its replica count to **3**\n2. Use `kubectl patch` to add a label `env=staging` to the Deployment's **pod template** (`.spec.template.metadata.labels`)\n\n```bash\n# Verify:\nkubectl get deploy patchme -o jsonpath='{.spec.replicas}'\nkubectl get deploy patchme -o jsonpath='{.spec.template.metadata.labels}' \n```",
"hints": [
{
"title": "Patch replicas using merge patch",
"body": "Use `--type=merge` with a JSON snippet targeting the field you want to change.",
"command": "kubectl patch deployment patchme --type=merge -p '{\"spec\":{\"replicas\":3}}'"
},
{
"title": "Patch pod template labels",
"body": "To add labels to the pod template, target spec.template.metadata.labels in your patch body.",
"command": "kubectl patch deployment patchme --type=merge -p '{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"env\":\"staging\"}}}}}'"
}
],
"setup_commands": [
{
"command": "kubectl create deployment patchme --image=nginx:alpine --replicas=1 2>/dev/null || true"
},
{
"command": "kubectl rollout status deployment/patchme --timeout=60s"
}
],
"validation": {
"commands": [
{
"description": "Deployment 'patchme' has 3 replicas",
"command": "kubectl get deployment patchme -o jsonpath='{.spec.replicas}'",
"expected_output": "3",
"match": "exact"
},
{
"description": "Pod template has label env=staging",
"command": "kubectl get deployment patchme -o jsonpath='{.spec.template.metadata.labels.env}'",
"expected_output": "staging",
"match": "exact"
}
]
},
"default_namespace": "default",
"teardown_commands": [
{
"command": "kubectl delete deployment patchme --ignore-not-found"
}
]
}
+44
View File
@@ -0,0 +1,44 @@
{
"id": "node-cordon-basics",
"title": "Cordoning and Uncordoning a Node",
"category": "Cluster Management",
"difficulty": "Easy",
"type": "task",
"weight": 4,
"description": "## Cordoning and Uncordoning a Node\n\n`kubectl cordon` marks a node as **unschedulable** — the node continues running existing workloads but the scheduler will not place new pods on it. This is useful before maintenance windows.\n\n`kubectl uncordon` reverses the cordon, making the node schedulable again.\n\n**Your task:**\n\n1. Find the name of the cluster node using `kubectl get nodes`\n2. **Cordon** the node\n3. Verify the node shows `SchedulingDisabled` in its status\n4. **Uncordon** the node to restore it to normal\n\n```bash\n# Check node status:\nkubectl get nodes\n\n# Cordon:\nkubectl cordon <node-name>\n\n# Uncordon:\nkubectl uncordon <node-name>\n```",
"hints": [
{
"title": "Find the node name",
"body": "In this single-node cluster, there is exactly one node. Use kubectl get nodes to find its name.",
"command": "kubectl get nodes -o jsonpath='{.items[0].metadata.name}'"
},
{
"title": "Cordon and uncordon using a variable",
"body": "Store the node name in a variable for convenience, then cordon and uncordon it.",
"command": "NODE=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}') && kubectl cordon $NODE && kubectl uncordon $NODE"
}
],
"setup_commands": [],
"validation": {
"commands": [
{
"description": "Node is schedulable (not cordoned)",
"command": "kubectl get nodes -o jsonpath='{.items[0].spec.unschedulable}' 2>/dev/null || echo 'false'",
"expected_output": "false",
"match": "contains"
},
{
"description": "Node is in Ready state",
"command": "kubectl get nodes -o jsonpath='{.items[0].status.conditions[?(@.type==\"Ready\")].status}'",
"expected_output": "True",
"match": "exact"
}
]
},
"default_namespace": "default",
"teardown_commands": [
{
"command": "kubectl uncordon $(kubectl get nodes -o jsonpath='{.items[0].metadata.name}') 2>/dev/null || true"
}
]
}
+50
View File
@@ -0,0 +1,50 @@
{
"id": "pod-env-vars-inline",
"title": "Pod with Inline Environment Variables",
"category": "Configuration",
"difficulty": "Easy",
"type": "task",
"weight": 4,
"description": "## Pod with Inline Environment Variables\n\nContainers can receive configuration through environment variables from three sources:\n1. **Inline literals** — hardcoded directly in the pod spec (`env[].value`)\n2. **ConfigMap** — loaded from a ConfigMap key\n3. **Secret** — loaded from a Secret key\n\nThis scenario focuses on source **#1**: setting environment variables directly in the pod spec.\n\n**Your task:**\n\nCreate a Pod named `env-demo` using image `busybox:1.36` with command `sleep 3600` and the following environment variables set **inline** (not from a ConfigMap or Secret):\n\n| Name | Value |\n|---|---|\n| `APP_COLOR` | `blue` |\n| `APP_MODE` | `production` |\n\n```bash\n# Verify the env vars are set inside the container:\nkubectl exec env-demo -- env | grep APP_\n```",
"hints": [
{
"title": "Setting env vars with kubectl run",
"body": "The quickest way: use `kubectl run` with one `--env` flag per variable.",
"command": "kubectl run env-demo --image=busybox:1.36 --command --env=APP_COLOR=blue --env=APP_MODE=production -- sleep 3600"
},
{
"title": "Setting env vars in a manifest",
"body": "In a YAML manifest, use the `env` array under the container spec. Each entry has a `name` and `value` field.",
"command": "cat <<EOF | kubectl apply -f -\napiVersion: v1\nkind: Pod\nmetadata:\n name: env-demo\nspec:\n containers:\n - name: app\n image: busybox:1.36\n command: [\"sleep\", \"3600\"]\n env:\n - name: APP_COLOR\n value: \"blue\"\n - name: APP_MODE\n value: \"production\"\nEOF"
}
],
"setup_commands": [],
"validation": {
"commands": [
{
"description": "Pod 'env-demo' is Running",
"command": "kubectl get pod env-demo -o jsonpath='{.status.phase}'",
"expected_output": "Running",
"match": "exact"
},
{
"description": "APP_COLOR is set to 'blue'",
"command": "kubectl get pod env-demo -o jsonpath='{.spec.containers[0].env[?(@.name==\"APP_COLOR\")].value}'",
"expected_output": "blue",
"match": "exact"
},
{
"description": "APP_MODE is set to 'production'",
"command": "kubectl get pod env-demo -o jsonpath='{.spec.containers[0].env[?(@.name==\"APP_MODE\")].value}'",
"expected_output": "production",
"match": "exact"
}
]
},
"default_namespace": "default",
"teardown_commands": [
{
"command": "kubectl delete pod env-demo --ignore-not-found --grace-period=0 --force"
}
]
}
@@ -0,0 +1,44 @@
{
"id": "pod-node-assignment-mcq",
"title": "Assigning Pods to Nodes",
"category": "Scheduling",
"difficulty": "Medium",
"type": "mcq",
"weight": 3,
"description": "## Assigning Pods to Nodes\n\nA data-processing Pod **must** run on a specific node named `worker-gpu` that has GPU resources. You want to ensure the Pod is **always** scheduled on that exact node — regardless of node labels.\n\nWhich field in the Pod spec achieves this most directly?",
"options": [
{
"id": "a",
"text": "`spec.nodeSelector: {kubernetes.io/hostname: worker-gpu}` — schedules on nodes matching the label"
},
{
"id": "b",
"text": "`spec.nodeName: worker-gpu` — directly assigns the Pod to the named node, bypassing the scheduler"
},
{
"id": "c",
"text": "`spec.affinity.nodeAffinity` with `requiredDuringSchedulingIgnoredDuringExecution` targeting the node name"
},
{
"id": "d",
"text": "`spec.tolerations` with a toleration matching the node's taint"
}
],
"correct_option": "b",
"explanation": "`spec.nodeName` is the most direct method — the Pod is **directly bound** to the named node, bypassing the Kubernetes scheduler entirely. The pod will only run on that node and will stay in `Pending` if that node is unavailable. `nodeSelector` requires the node to have a matching label (e.g., `kubernetes.io/hostname` is auto-assigned, so option A would also work, but is less direct). `nodeAffinity` is more flexible and preferred for production. `tolerations` allow pods to be scheduled on tainted nodes but do not restrict them to a specific node.",
"hints": [
{
"title": "nodeName vs nodeSelector",
"body": "nodeName bypasses the scheduler and pins the pod to a named node. nodeSelector uses labels for a more flexible approach. For production, prefer nodeAffinity.",
"command": "kubectl explain pod.spec.nodeName"
},
{
"title": "Get the node's auto-assigned hostname label",
"body": "The kubernetes.io/hostname label is automatically set on every node and matches the node name.",
"command": "kubectl get nodes --show-labels"
}
],
"setup_commands": [],
"default_namespace": "default",
"teardown_commands": []
}
+63
View File
@@ -0,0 +1,63 @@
{
"id": "sort-by-basics",
"title": "Sorting kubectl Output",
"category": "Core Concepts",
"difficulty": "Easy",
"type": "task",
"weight": 3,
"description": "## Sorting `kubectl` Output\n\nThe `--sort-by` flag on `kubectl get` allows you to sort output by any JSONPath expression — extremely useful for finding the newest pod, the heaviest resource consumer, or the oldest event.\n\n**Your task:**\n\nSeveral pods have been created for you. Complete the following:\n\n1. List all pods in the `default` namespace **sorted by their creation timestamp** (oldest first) and save the output to `/tmp/pods-sorted.txt`\n2. List all pods sorted by name and save to `/tmp/pods-by-name.txt`\n\n```bash\n# Sort by creation time:\nkubectl get pods --sort-by=.metadata.creationTimestamp\n\n# Sort by name:\nkubectl get pods --sort-by=.metadata.name\n```",
"hints": [
{
"title": "--sort-by flag syntax",
"body": "Pass any JSONPath expression to --sort-by. The expression must point to a comparable field (string, number, or timestamp).",
"command": "kubectl get pods --sort-by=.metadata.creationTimestamp"
},
{
"title": "Save output to a file",
"body": "Redirect kubectl output using the > operator.",
"command": "kubectl get pods --sort-by=.metadata.creationTimestamp > /tmp/pods-sorted.txt && kubectl get pods --sort-by=.metadata.name > /tmp/pods-by-name.txt"
}
],
"setup_commands": [
{
"command": "kubectl run sort-pod-c --image=nginx:alpine 2>/dev/null || true && sleep 1"
},
{
"command": "kubectl run sort-pod-b --image=nginx:alpine 2>/dev/null || true && sleep 0.5"
},
{
"command": "kubectl run sort-pod-a --image=nginx:alpine 2>/dev/null || true && sleep 0.5"
}
],
"validation": {
"commands": [
{
"description": "File /tmp/pods-sorted.txt exists and contains pod output",
"command": "cat /tmp/pods-sorted.txt",
"expected_output": "sort-pod",
"match": "contains"
},
{
"description": "File /tmp/pods-by-name.txt exists and contains pod output",
"command": "cat /tmp/pods-by-name.txt",
"expected_output": "sort-pod",
"match": "contains"
},
{
"description": "pods-by-name.txt is sorted alphabetically (sort-pod-a appears before sort-pod-c)",
"command": "grep -n 'sort-pod-a' /tmp/pods-by-name.txt | cut -d: -f1",
"expected_output": "^[0-9]+$",
"match": "regex"
}
]
},
"default_namespace": "default",
"teardown_commands": [
{
"command": "kubectl delete pod sort-pod-a sort-pod-b sort-pod-c --ignore-not-found --grace-period=0 --force"
},
{
"command": "rm -f /tmp/pods-sorted.txt /tmp/pods-by-name.txt"
}
]
}
+44
View File
@@ -0,0 +1,44 @@
{
"id": "static-pods-mcq",
"title": "Static Pods in Kubernetes",
"category": "Core Concepts",
"difficulty": "Medium",
"type": "mcq",
"weight": 3,
"description": "## Static Pods in Kubernetes\n\nA cluster administrator wants to run a monitoring agent on every node without relying on the Kubernetes scheduler or API server. The agent must start automatically even if the API server is unavailable.\n\nWhich approach should they use, and where should the manifest be placed?",
"options": [
{
"id": "a",
"text": "Create a DaemonSet — the scheduler places one pod per node automatically"
},
{
"id": "b",
"text": "Create a Static Pod by placing a manifest in the kubelet's static pod directory (typically `/etc/kubernetes/manifests/`)"
},
{
"id": "c",
"text": "Use a CronJob with `--concurrencyPolicy=Forbid` to schedule one pod per node"
},
{
"id": "d",
"text": "Annotate a Deployment with `node-placement: static` to pin it to every node"
}
],
"correct_option": "b",
"explanation": "Static Pods are managed directly by the kubelet daemon on a specific node, **without the API server scheduling them**. The kubelet watches a directory (e.g., `/etc/kubernetes/manifests/`) and automatically creates any pods defined there — even if the API server is down. They are ideal for bootstrapping control-plane components (etcd, kube-apiserver, etc.) and for workloads that must survive API server failures. DaemonSets are managed by the scheduler/API server and won't work if the API server is unavailable. There is no `node-placement: static` annotation.",
"hints": [
{
"title": "What makes a pod 'static'?",
"body": "Static pods are defined as YAML files on the node's filesystem. The kubelet monitors the staticPodPath directory and reconciles the pods itself — no scheduler, no API server required.",
"command": "ls /etc/kubernetes/manifests/ 2>/dev/null || echo 'Static pod dir may differ per distro'"
},
{
"title": "Identifying static pods in the cluster",
"body": "Static pods always have the node name appended to their pod name (e.g., `kube-apiserver-controlplane`). You can also look at the pod's ownerReferences — static pods have no ownerReference.",
"command": "kubectl get pods -n kube-system"
}
],
"setup_commands": [],
"default_namespace": "default",
"teardown_commands": []
}