docs: add Kubernetes CKS study notes
This commit is contained in:
95
kubernetes/level 1/task-1.md
Normal file
95
kubernetes/level 1/task-1.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team is diving into Kubernetes for application management. One team member has a task to create a pod according to the details below:
|
||||
|
||||
|
||||
Create a pod named pod-httpd using the httpd image with the latest tag. Ensure to specify the tag as httpd:latest.
|
||||
|
||||
Set the app label to httpd_app, and name the container as httpd-container.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
# Solution
|
||||
|
||||
# Kubernetes Pod — `pod-httpd`
|
||||
|
||||
A single pod running the `httpd:latest` image, with an `app: httpd_app` label and a
|
||||
named container. Applied inline via a heredoc — no manifest file on disk.
|
||||
|
||||
## Apply (heredoc → kubectl)
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod-httpd
|
||||
labels:
|
||||
app: httpd_app
|
||||
spec:
|
||||
containers:
|
||||
- name: httpd-container
|
||||
image: httpd:latest
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The heredoc apply pattern
|
||||
|
||||
- **`kubectl apply -f -`** — the `-f -` tells kubectl to read the manifest from
|
||||
**stdin** instead of a file. The heredoc feeds the YAML straight in, so nothing is
|
||||
written to disk.
|
||||
- **`<<'EOF'` (delimiter quoted)** — quoting `EOF` disables shell expansion inside the
|
||||
body, so any `$VAR`, backticks, or `$(...)` in a manifest stay literal rather than
|
||||
being interpreted by the shell. For Kubernetes YAML this is almost always what you
|
||||
want. Use unquoted `<<EOF` only when you deliberately want the shell to interpolate a
|
||||
variable into the manifest before it reaches kubectl.
|
||||
- **Indentation still matters** — YAML inside the heredoc must keep exact indentation,
|
||||
spaces not tabs.
|
||||
|
||||
### The manifest, field by field
|
||||
|
||||
- **`apiVersion: v1` / `kind: Pod`** — a Pod is a core (`v1`) object, the smallest
|
||||
deployable unit in Kubernetes: one or more containers sharing a network namespace and
|
||||
storage.
|
||||
- **`metadata.name: pod-httpd`** — the pod's name, exactly as required.
|
||||
- **`metadata.labels.app: httpd_app`** — the required label. Labels are key/value tags
|
||||
used by selectors (Services, ReplicaSets, `kubectl get -l`) to target the pod, so
|
||||
`kubectl get pods -l app=httpd_app` will match this pod.
|
||||
- **`spec.containers`** — the list of containers in the pod, here a single entry:
|
||||
- **`name: httpd-container`** — the container name, exactly as required. This is the
|
||||
name you'd pass to `kubectl logs` / `kubectl exec -c`.
|
||||
- **`image: httpd:latest`** — the Apache HTTP Server image at the `latest` tag,
|
||||
specified explicitly as required. Writing just `httpd` would default the tag to
|
||||
`latest` anyway, but the task asks for the tag to be stated, so it's spelled out.
|
||||
|
||||
### Label vs. name — two different things
|
||||
|
||||
Easy to conflate, but distinct:
|
||||
- the **pod name** (`pod-httpd`) uniquely identifies the pod in its namespace, and
|
||||
- the **`app` label** (`httpd_app`) is a selector tag that can be shared across many
|
||||
pods.
|
||||
|
||||
They live in different manifest fields (`metadata.name` vs `metadata.labels`) and serve
|
||||
different purposes.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# Pod is running with the correct name
|
||||
kubectl get pod pod-httpd -o wide
|
||||
|
||||
# Label is set
|
||||
kubectl get pod pod-httpd --show-labels
|
||||
|
||||
# Container name and image are correct
|
||||
kubectl get pod pod-httpd \
|
||||
-o jsonpath='{.spec.containers[0].name}{" "}{.spec.containers[0].image}{"\n"}'
|
||||
```
|
||||
|
||||
Expected — `pod-httpd` in `Running` status, labels showing `app=httpd_app`, and the
|
||||
jsonpath printing `httpd-container httpd:latest`.
|
||||
|
||||
> If the pod sits in `ImagePullBackOff`, the node may be offline or rate-limited on the
|
||||
> pull. `kubectl describe pod pod-httpd` shows the pull events under `Events:`.
|
||||
144
kubernetes/level 1/task-10.md
Normal file
144
kubernetes/level 1/task-10.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team needs a time check pod created in a specific Kubernetes namespace for logging purposes. Initially, it's for testing, but it may be integrated into an existing cluster later. Here's what's required:
|
||||
|
||||
|
||||
Create a pod called time-check in the nautilus namespace. The pod should contain a container named time-check, utilizing the busybox image with the latest tag (specify as busybox:latest).
|
||||
|
||||
Create a config map named time-config with the data TIME_FREQ=7 in the same namespace.
|
||||
|
||||
Configure the time-check container to execute the command: while true; do date; sleep $TIME_FREQ;done. Ensure the result is written /opt/finance/time/time-check.log. Also, add an environmental variable TIME_FREQ in the container, fetching its value from the config map TIME_FREQ key.
|
||||
|
||||
Create a volume log-volume and mount it at /opt/finance/time within the container.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
# Solution
|
||||
|
||||
# Kubernetes Pod + ConfigMap — `time-check` in `nautilus`
|
||||
|
||||
A `time-check` pod that loops `date` into a log file on a mounted volume, reading its
|
||||
sleep interval from a ConfigMap-backed env var. Applied inline via a multi-document
|
||||
heredoc — no manifest file on disk.
|
||||
|
||||
## Apply (heredoc → kubectl)
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: nautilus
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: time-config
|
||||
namespace: nautilus
|
||||
data:
|
||||
TIME_FREQ: "7"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: time-check
|
||||
namespace: nautilus
|
||||
spec:
|
||||
containers:
|
||||
- name: time-check
|
||||
image: busybox:latest
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- "while true; do date; sleep $TIME_FREQ; done >> /opt/finance/time/time-check.log"
|
||||
env:
|
||||
- name: TIME_FREQ
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: time-config
|
||||
key: TIME_FREQ
|
||||
volumeMounts:
|
||||
- name: log-volume
|
||||
mountPath: /opt/finance/time
|
||||
volumes:
|
||||
- name: log-volume
|
||||
emptyDir: {}
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The heredoc apply pattern
|
||||
|
||||
- **`kubectl apply -f -`** reads from **stdin**; `---` separates the three documents,
|
||||
processed in order so the **Namespace → ConfigMap → Pod** dependency chain is satisfied.
|
||||
- **`<<'EOF'` (delimiter quoted)** is critical here: it stops the jump-host's shell from
|
||||
expanding `$TIME_FREQ` when you paste the command. The literal `$TIME_FREQ` must reach the
|
||||
manifest untouched so the **container's** shell expands it at runtime from the env var.
|
||||
An unquoted heredoc would blank it out before it ever got to Kubernetes.
|
||||
|
||||
### The Namespace
|
||||
|
||||
- **`kind: Namespace` / `name: nautilus`** — created first so the ConfigMap and Pod have
|
||||
somewhere to live. If `nautilus` already exists, `apply` is idempotent and simply no-ops.
|
||||
|
||||
### The ConfigMap
|
||||
|
||||
- **`name: time-config`, `namespace: nautilus`** — as required.
|
||||
- **`data.TIME_FREQ: "7"`** — the key/value the pod reads. The value is **quoted** because
|
||||
ConfigMap values must be strings; an unquoted `7` would be a YAML integer and rejected.
|
||||
|
||||
### The Pod
|
||||
|
||||
- **`name: time-check`, `namespace: nautilus`**, container **`name: time-check`**,
|
||||
**`image: busybox:latest`** — all exactly as required.
|
||||
|
||||
- **`command`** — run via `/bin/sh -c` so the shell interprets the loop and, crucially,
|
||||
expands `$TIME_FREQ`. The loop prints `date` every `$TIME_FREQ` seconds; the
|
||||
`>> /opt/finance/time/time-check.log` redirect on the **whole loop** opens the log file
|
||||
once and appends each timestamp to it continuously. That satisfies "the result is written
|
||||
to /opt/finance/time/time-check.log."
|
||||
|
||||
- **`env` with `valueFrom.configMapKeyRef`** — injects an env var named `TIME_FREQ` whose
|
||||
value is pulled from the `TIME_FREQ` key of the `time-config` ConfigMap. This is the link
|
||||
between requirement 2 and the `$TIME_FREQ` in the command — the container gets `7` at
|
||||
runtime without hardcoding it.
|
||||
|
||||
- **`volumeMounts` + `volumes`** — the two halves of attaching storage:
|
||||
- **`volumes: - name: log-volume / emptyDir: {}`** declares a volume named `log-volume`.
|
||||
`emptyDir` is an ephemeral, pod-lifetime scratch volume — the simplest choice for a log
|
||||
location the task doesn't require to persist.
|
||||
- **`volumeMounts: - name: log-volume / mountPath: /opt/finance/time`** mounts that volume
|
||||
into the container at `/opt/finance/time`, which is where the log file is written. The
|
||||
`name` on both sides must match, or Kubernetes can't wire them together.
|
||||
|
||||
### Why the mount path and log path line up
|
||||
|
||||
The command writes to `/opt/finance/time/time-check.log`, and the volume is mounted at
|
||||
`/opt/finance/time`. So the log file lands **inside** the mounted volume — meaning the
|
||||
directory exists (the mount creates it) and the write succeeds. If the mount path and the
|
||||
log directory didn't match, the write could fail or land on the container's ephemeral
|
||||
root filesystem instead.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# ConfigMap present with the value
|
||||
kubectl get configmap time-config -n nautilus -o jsonpath='{.data.TIME_FREQ}{"\n"}'
|
||||
|
||||
# Pod running
|
||||
kubectl get pod time-check -n nautilus -o wide
|
||||
|
||||
# Env var resolved from the ConfigMap
|
||||
kubectl exec time-check -n nautilus -- printenv TIME_FREQ
|
||||
|
||||
# Log file is being written inside the mounted volume
|
||||
kubectl exec time-check -n nautilus -- cat /opt/finance/time/time-check.log
|
||||
```
|
||||
|
||||
Expected — ConfigMap value `7`, pod `Running`, `printenv` showing `TIME_FREQ=7`, and the
|
||||
log file accumulating timestamps roughly every 7 seconds.
|
||||
|
||||
> Remember `-n nautilus` on every command — nothing here is in `default`. If the pod is
|
||||
> `Error`/`CrashLoopBackOff`, check `kubectl logs time-check -n nautilus` (though output goes
|
||||
> to the log file, so the pod's stdout may be quiet).
|
||||
133
kubernetes/level 1/task-11.md
Normal file
133
kubernetes/level 1/task-11.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# Assignment
|
||||
|
||||
A junior DevOps team member encountered difficulties deploying a stack on the Kubernetes cluster. The pod fails to start, presenting errors. Let's troubleshoot and rectify the issue promptly.
|
||||
|
||||
|
||||
There is a pod named webserver, and the container within it is named httpd-container, its utilizing the httpd:latest image.
|
||||
|
||||
Additionally, there's a sidecar container named sidecar-container using the ubuntu:latest image.
|
||||
|
||||
Identify and address the issue to ensure the pod is in the running state and the application is accessible.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
# Solution
|
||||
|
||||
There is incorrect image in the pod ... just fix it
|
||||
|
||||
# Troubleshoot Pod — `webserver` (httpd + ubuntu sidecar)
|
||||
|
||||
The `webserver` pod won't start. This walks the diagnosis, then applies a corrected
|
||||
manifest via heredoc.
|
||||
|
||||
## Step 1 — Diagnose
|
||||
|
||||
```bash
|
||||
# Overall state + which container is failing
|
||||
kubectl get pod webserver -o wide
|
||||
|
||||
# The authoritative source: events + per-container state at the bottom
|
||||
kubectl describe pod webserver
|
||||
|
||||
# Logs from each container (the sidecar is the usual culprit)
|
||||
kubectl logs webserver -c httpd-container
|
||||
kubectl logs webserver -c sidecar-container
|
||||
```
|
||||
|
||||
Look at the container states in `describe`: `ImagePullBackOff` / `ErrImagePull` points to
|
||||
a **bad image name or tag**; `CrashLoopBackOff` with the sidecar exiting `Completed` points
|
||||
to a **container with no long-running process**.
|
||||
|
||||
## Step 2 — The two usual root causes
|
||||
|
||||
For this specific setup (an `httpd` container plus an `ubuntu` sidecar), the failure is
|
||||
almost always one or both of:
|
||||
|
||||
1. **Image typo** — e.g. `httpd:latst`, `httpd:letest`, or a misspelled `ubuntu` — which
|
||||
yields `ErrImagePull` / `ImagePullBackOff`.
|
||||
2. **The ubuntu sidecar exits immediately.** `ubuntu:latest` has no long-running entrypoint
|
||||
— it starts a shell, finds nothing to do, and exits `0`. Kubernetes sees the container
|
||||
terminate and puts the pod in `CrashLoopBackOff` (it keeps restarting a container that
|
||||
keeps exiting). A sidecar **must** be given a command that keeps it alive.
|
||||
|
||||
## Step 3 — Fix (recreate with a corrected manifest)
|
||||
|
||||
A pod's container image and command are effectively immutable in place, so the clean fix is
|
||||
delete and re-create:
|
||||
|
||||
```bash
|
||||
kubectl delete pod webserver
|
||||
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: webserver
|
||||
labels:
|
||||
app: httpd
|
||||
spec:
|
||||
containers:
|
||||
- name: httpd-container
|
||||
image: httpd:latest
|
||||
- name: sidecar-container
|
||||
image: ubuntu:latest
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- "while true; do echo sidecar running; sleep 5; done"
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The heredoc apply pattern
|
||||
|
||||
- **`kubectl apply -f -`** reads the manifest from **stdin**; nothing written to disk.
|
||||
- **`<<'EOF'` (delimiter quoted)** keeps the `while`/`$`-free command literal so the
|
||||
container's shell runs it as written.
|
||||
|
||||
### What the corrected manifest fixes
|
||||
|
||||
- **`httpd-container` → `image: httpd:latest`** — a valid, correctly-spelled image/tag, so
|
||||
the pull succeeds (fixes any `ImagePullBackOff` from a typo).
|
||||
- **`sidecar-container` → `command: [...while true...sleep 5...]`** — gives the ubuntu
|
||||
container a **long-running foreground process**. Now it never exits, so the pod stays
|
||||
`Running` instead of crash-looping. Any equivalent keep-alive works (`sleep infinity`,
|
||||
`tail -f /dev/null`); the infinite loop is a clear, portable choice.
|
||||
|
||||
### Why a sidecar needs a command but httpd doesn't
|
||||
|
||||
`httpd:latest`'s default entrypoint **is** a long-running server (Apache in the
|
||||
foreground), so it stays up on its own. `ubuntu:latest` has no such default — its job here
|
||||
is just to accompany the main container, so **you** must supply the process that keeps it
|
||||
alive. This asymmetry is the heart of the bug: the same manifest that's fine for httpd
|
||||
leaves ubuntu dead on arrival.
|
||||
|
||||
### "Application accessible"
|
||||
|
||||
Once both containers stay up, the pod reports `2/2 Running` and Apache serves on its
|
||||
default port 80 inside the pod. You can confirm the app responds from within the pod
|
||||
(below); exposing it externally would be a separate Service, which this task doesn't ask
|
||||
for.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# Both containers up: READY should show 2/2
|
||||
kubectl get pod webserver
|
||||
|
||||
# No more restart churn / crash events
|
||||
kubectl describe pod webserver | sed -n '/Events/,$p'
|
||||
|
||||
# httpd actually serving inside the pod
|
||||
kubectl exec webserver -c httpd-container -- sh -c 'apt-get -v >/dev/null 2>&1; echo ok' 2>/dev/null || true
|
||||
kubectl exec webserver -c sidecar-container -- echo "sidecar alive"
|
||||
```
|
||||
|
||||
Expected — `webserver` in `Running` with `READY 2/2`, no crash-loop events, and both
|
||||
`exec` checks succeeding. Apache serving on port 80 within the pod.
|
||||
|
||||
> If `describe` still shows `ImagePullBackOff` after the fix, the node is offline or
|
||||
> rate-limited on the registry — not a manifest problem. If the sidecar still exits,
|
||||
> confirm its `command` made it into the spec:
|
||||
> `kubectl get pod webserver -o jsonpath='{.spec.containers[1].command}'`.
|
||||
103
kubernetes/level 1/task-12.md
Normal file
103
kubernetes/level 1/task-12.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Assignment
|
||||
|
||||
An application deployed on the Kubernetes cluster requires an update with new features developed by the Nautilus application development team. The existing setup includes a deployment named nginx-deployment and a service named nginx-service. Below are the necessary changes to be implemented without deleting the deployment and service:
|
||||
|
||||
|
||||
1.) Modify the service nodeport from 30008 to 32165
|
||||
|
||||
2.) Change the replicas count from 1 to 5
|
||||
|
||||
3.) Update the image from nginx:1.19 to nginx:latest
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
# Solution
|
||||
|
||||
# Update Deployment + Service (no delete, all patches) — `nginx-deployment` / `nginx-service`
|
||||
|
||||
Three in-place changes, each as a surgical `kubectl patch`: replicas `1 → 5`, image
|
||||
`nginx:1.19 → nginx:latest`, service nodePort `30008 → 32165`. No object is deleted and no
|
||||
full manifest is reproduced.
|
||||
|
||||
## Patches
|
||||
|
||||
```bash
|
||||
# 1) Replicas 1 -> 5 (strategic-merge patch)
|
||||
kubectl patch deployment nginx-deployment \
|
||||
-p '{"spec":{"replicas":5}}'
|
||||
|
||||
# 2) Image nginx:1.19 -> nginx:latest (strategic-merge, container matched by name)
|
||||
kubectl patch deployment nginx-deployment \
|
||||
-p '{"spec":{"template":{"spec":{"containers":[{"name":"nginx-container","image":"nginx:latest"}]}}}}'
|
||||
|
||||
# 3) NodePort 30008 -> 32165 (JSON patch, precise field replace)
|
||||
kubectl patch service nginx-service \
|
||||
--type=json \
|
||||
-p='[{"op":"replace","path":"/spec/ports/0/nodePort","value":32165}]'
|
||||
|
||||
# Wait for the image change to roll out
|
||||
kubectl rollout status deployment/nginx-deployment
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### Two patch types, chosen per change
|
||||
|
||||
`kubectl patch` supports different strategies; each change uses the one that's cleanest for it.
|
||||
|
||||
**Strategic-merge patch (default)** — used for replicas and image. It deep-merges the JSON
|
||||
fragment into the live object, and it understands Kubernetes list semantics.
|
||||
|
||||
- **Replicas** — `{"spec":{"replicas":5}}` merges a single scalar field; nothing else in the
|
||||
spec is touched.
|
||||
- **Image** — `{"spec":{"template":{"spec":{"containers":[{"name":"nginx-container","image":"nginx:latest"}]}}}}`.
|
||||
The `containers` list is merged **by the `name` key**, so including `name: nginx-container`
|
||||
tells Kubernetes to patch *that* existing container's image rather than replace the whole
|
||||
list or add a second container. This is why the correct container name matters — a wrong
|
||||
name would append a new container instead of updating the existing one.
|
||||
|
||||
**JSON patch (`--type=json`)** — used for the nodePort. It's an ordered list of explicit
|
||||
operations (RFC 6902). `replace` on `/spec/ports/0/nodePort` targets exactly one field of the
|
||||
first port entry. JSON patch is the right tool for editing **one element of a list** like
|
||||
`ports` — a strategic-merge patch on a ports array is ambiguous about how to match entries,
|
||||
whereas `ports/0` is unambiguous. `32165` is inside the valid NodePort range
|
||||
(`30000–32767`), so the API accepts it.
|
||||
|
||||
### Why the image patch triggers a rolling update
|
||||
|
||||
Changing the container image mutates the pod template. The Deployment controller detects the
|
||||
template change, creates a new ReplicaSet, and rolls `nginx:latest` pods in while retiring the
|
||||
`nginx:1.19` pods incrementally — governed by the live `RollingUpdate` strategy
|
||||
(`maxSurge/maxUnavailable 25%`). `rollout status` blocks until that completes. The replicas
|
||||
patch simply scales the ReplicaSet to 5.
|
||||
|
||||
### Nothing gets deleted
|
||||
|
||||
All three are `patch` operations that mutate existing objects in place. The service keeps its
|
||||
ClusterIP; the deployment keeps its identity and rollout history. "Without deleting" is
|
||||
satisfied by construction — a delete + recreate would drop the ClusterIP and history.
|
||||
|
||||
### Equivalent shortcuts
|
||||
|
||||
The same results are achievable with purpose-built verbs, if you prefer them over raw patches:
|
||||
|
||||
```bash
|
||||
kubectl scale deployment nginx-deployment --replicas=5
|
||||
kubectl set image deployment/nginx-deployment nginx-container=nginx:latest
|
||||
# (nodePort still needs a patch/edit — there's no dedicated verb for it)
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
kubectl get deployment nginx-deployment
|
||||
kubectl get deployment nginx-deployment \
|
||||
-o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
|
||||
kubectl get service nginx-service \
|
||||
-o jsonpath='{.spec.ports[0].nodePort}{"\n"}'
|
||||
```
|
||||
|
||||
Expected — deployment `READY 5/5`, image `nginx:latest`, nodePort `32165`.
|
||||
|
||||
> If the rollout hangs with new pods in `ImagePullBackOff`, the `nginx:latest` pull failed
|
||||
> (node offline / rate-limited); `kubectl describe pod <name>` shows the cause.
|
||||
106
kubernetes/level 1/task-13.md
Normal file
106
kubernetes/level 1/task-13.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team has already deployed a ReplicaSet to host an application that requires a highly available infrastructure. Your task is to expose the application running in the existing ReplicaSet by creating a Kubernetes NodePort Service.
|
||||
|
||||
Follow the specifications below to create the Service and ensure the application pods are accessible:
|
||||
|
||||
|
||||
A ReplicaSet named httpd-replicaset is already running in the cluster.
|
||||
|
||||
The pods managed by the ReplicaSet use the following labels:
|
||||
Assign labels app as httpd_app, and type as front-end.
|
||||
|
||||
Create a NodePort Service named httpd-service to expose the application.
|
||||
|
||||
Set the NodePort to 30080.
|
||||
|
||||
Expose port 80 of the application.
|
||||
|
||||
Note: Do not delete or modify the configuration of the deployed ReplicaSet application.
|
||||
|
||||
# Solution
|
||||
|
||||
# Kubernetes NodePort Service — `httpd-service`
|
||||
|
||||
A NodePort Service that exposes the pods already managed by the `httpd-replicaset`, matched
|
||||
by their labels. Applied inline via a heredoc — no manifest file on disk, and the ReplicaSet
|
||||
is left untouched.
|
||||
|
||||
## Apply (heredoc → kubectl)
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: httpd-service
|
||||
spec:
|
||||
type: NodePort
|
||||
selector:
|
||||
app: httpd_app
|
||||
type: front-end
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
nodePort: 30080
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The heredoc apply pattern
|
||||
|
||||
- **`kubectl apply -f -`** reads from **stdin**; nothing written to disk.
|
||||
- **`<<'EOF'` (delimiter quoted)** disables shell expansion — the right default for k8s YAML.
|
||||
|
||||
### A Service finds pods by label selector
|
||||
|
||||
A Service has no direct link to the ReplicaSet — it targets **pods** whose labels match its
|
||||
`selector`. This is the crux of the task:
|
||||
|
||||
- **`selector: {app: httpd_app, type: front-end}`** — must exactly match the labels on the
|
||||
pods the ReplicaSet manages. Any pod carrying **both** labels becomes an endpoint of this
|
||||
Service, regardless of what created it. Since the ReplicaSet stamps its pods with these
|
||||
labels, the Service automatically picks them up. A mismatched selector would yield a Service
|
||||
with **zero endpoints** — it'd exist but route nowhere.
|
||||
|
||||
This is also why we don't touch the ReplicaSet: the Service attaches by label, so exposing the
|
||||
app needs no change to the existing workload at all.
|
||||
|
||||
### NodePort and the port fields
|
||||
|
||||
`type: NodePort` opens a port on **every node** that forwards to the Service, which in turn
|
||||
load-balances across the matching pods. The three port fields each mean something distinct:
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `port: 80` | The port the **Service** itself listens on (its ClusterIP:80). |
|
||||
| `targetPort: 80` | The port on the **pod/container** traffic is forwarded to — Apache's port 80. |
|
||||
| `nodePort: 30080` | The port opened on **each node's** IP for external access. |
|
||||
|
||||
So the path is: `<node-ip>:30080` → Service `:80` → pod `:80`. `30080` is inside the valid
|
||||
NodePort range (`30000–32767`), so the API accepts it.
|
||||
|
||||
### Why all three ports are set explicitly
|
||||
|
||||
`targetPort` defaults to `port` if omitted (both 80 here, so it'd work either way), and
|
||||
`nodePort` would be auto-assigned from the range if omitted — but the task pins it to `30080`,
|
||||
so it's set explicitly. Being explicit also makes the manifest self-documenting.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# Service created as NodePort with the right ports
|
||||
kubectl get service httpd-service
|
||||
|
||||
# Endpoints populated = selector matched the ReplicaSet's pods (this is the key check)
|
||||
kubectl get endpoints httpd-service
|
||||
|
||||
# Confirm it reaches the app (from a node or the jump-host)
|
||||
curl -s http://<node-ip>:30080 | head -n 5
|
||||
```
|
||||
|
||||
Expected — `httpd-service` of type `NodePort` showing `80:30080/TCP`, and
|
||||
`kubectl get endpoints httpd-service` listing one IP per matching pod. If endpoints is empty,
|
||||
the selector doesn't match the pods' labels — recheck them with
|
||||
`kubectl get pods --show-labels`.
|
||||
132
kubernetes/level 1/task-14.md
Normal file
132
kubernetes/level 1/task-14.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Assignment
|
||||
|
||||
We encountered an issue with our Nginx and PHP-FPM setup on the Kubernetes cluster this morning, which halted its functionality. Investigate and rectify the issue:
|
||||
|
||||
|
||||
|
||||
The pod name is nginx-phpfpm and configmap name is nginx-config. Identify and fix the problem.
|
||||
|
||||
|
||||
Once resolved, copy /home/thor/index.php file from the jump host to the nginx-container within the nginx document root. After this, you should be able to access the website using Website button on the top bar.
|
||||
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
# Solution
|
||||
|
||||
# Troubleshoot nginx + php-fpm — `nginx-phpfpm` (confirmed fix)
|
||||
|
||||
## Root cause (confirmed from the live spec)
|
||||
|
||||
The nginx config and the two container mounts don't agree on the document root:
|
||||
|
||||
| Source | Path |
|
||||
|--------|------|
|
||||
| nginx config `root` | `/var/www/html` |
|
||||
| `nginx-container` shared-files mount | `/var/www/html` ✓ |
|
||||
| `php-fpm-container` shared-files mount | `/usr/share/nginx/html` ✗ **mismatch** |
|
||||
|
||||
nginx forwards PHP requests to php-fpm with
|
||||
`SCRIPT_FILENAME = $document_root$fastcgi_script_name` → `/var/www/html/index.php`. But in the
|
||||
**php-fpm** container the shared volume is mounted at `/usr/share/nginx/html`, so the path
|
||||
`/var/www/html/index.php` doesn't exist there and php-fpm returns **"File not found."** The two
|
||||
containers share the same `emptyDir` volume, but at **different paths**, so they aren't actually
|
||||
sharing the document root.
|
||||
|
||||
**Fix:** change the php-fpm container's `shared-files` mountPath from `/usr/share/nginx/html`
|
||||
to `/var/www/html`, so both containers — and the nginx `root`, and the FastCGI
|
||||
`SCRIPT_FILENAME` — all reference the same path.
|
||||
|
||||
## Step 1 — Recreate the pod with the corrected mountPath
|
||||
|
||||
A pod's `volumeMounts` are immutable in place, so delete and re-create. This heredoc is your
|
||||
live spec (from `last-applied-configuration`) with **only** the php-fpm mountPath fixed:
|
||||
|
||||
```bash
|
||||
kubectl delete pod nginx-phpfpm
|
||||
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx-phpfpm
|
||||
namespace: default
|
||||
labels:
|
||||
app: php-app
|
||||
spec:
|
||||
volumes:
|
||||
- name: shared-files
|
||||
emptyDir: {}
|
||||
- name: nginx-config-volume
|
||||
configMap:
|
||||
name: nginx-config
|
||||
containers:
|
||||
- name: php-fpm-container
|
||||
image: php:7.2-fpm-alpine
|
||||
volumeMounts:
|
||||
- name: shared-files
|
||||
mountPath: /var/www/html # FIXED: was /usr/share/nginx/html
|
||||
- name: nginx-container
|
||||
image: nginx:latest
|
||||
volumeMounts:
|
||||
- name: shared-files
|
||||
mountPath: /var/www/html
|
||||
- name: nginx-config-volume
|
||||
mountPath: /etc/nginx/nginx.conf
|
||||
subPath: nginx.conf
|
||||
EOF
|
||||
```
|
||||
|
||||
## Step 2 — Copy the PHP file into the shared document root
|
||||
|
||||
```bash
|
||||
kubectl cp /home/thor/index.php nginx-phpfpm:/var/www/html/index.php -c nginx-container
|
||||
```
|
||||
|
||||
Copy into `/var/www/html` (the now-consistent root) via the nginx container. Because the volume
|
||||
is shared at the same path in both containers, php-fpm sees the file too and can execute it.
|
||||
|
||||
## How it works
|
||||
|
||||
### Why the mismatch broke it even though the pod was "Running"
|
||||
|
||||
Both containers were healthy (`2/2 Running`) — this wasn't a crash. The failure was purely at
|
||||
request time: nginx served on port 8099, matched `.php`, and handed php-fpm an absolute path
|
||||
(`/var/www/html/...`) that was valid in nginx's filesystem but pointed at nothing in php-fpm's.
|
||||
FastCGI passes a **path string**, not a file handle, so both containers must resolve that same
|
||||
absolute path to the same bytes — which only happens if the shared volume is mounted at the
|
||||
identical path in each. Aligning php-fpm's mount to `/var/www/html` closes that gap.
|
||||
|
||||
### Why copy after recreating
|
||||
|
||||
Recreating the pod starts with a fresh empty `shared-files` volume, so `index.php` is copied
|
||||
**after** the pod is Running. It then lives in the shared root for the pod's lifetime and is
|
||||
visible to both containers.
|
||||
|
||||
### Note on the listen port
|
||||
|
||||
The nginx config listens on **8099** (not 80), with `root /var/www/html`. Any Service exposing
|
||||
this app must target port 8099 — the Website button relies on that mapping. This task doesn't
|
||||
ask you to change the Service, just to fix the pod and drop in the file.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# Pod healthy after recreate
|
||||
kubectl get pod nginx-phpfpm
|
||||
|
||||
# File present in the shared root, visible from BOTH containers
|
||||
kubectl exec nginx-phpfpm -c nginx-container -- ls -l /var/www/html/index.php
|
||||
kubectl exec nginx-phpfpm -c php-fpm-container -- ls -l /var/www/html/index.php
|
||||
|
||||
# App renders the PHP page (nginx listens on 8099)
|
||||
kubectl exec nginx-phpfpm -c nginx-container -- curl -s http://localhost:8099/index.php | head
|
||||
```
|
||||
|
||||
Expected — `nginx-phpfpm` `READY 2/2`, `index.php` visible from **both** containers under
|
||||
`/var/www/html`, and the curl returning the rendered page. The **Website** button then loads the
|
||||
site.
|
||||
|
||||
> The key sign the fix worked: `ls` succeeds from the **php-fpm** container too. Before the fix
|
||||
> it would only appear under `/usr/share/nginx/html` there, which is exactly why php-fpm
|
||||
> couldn't find it at `/var/www/html`.
|
||||
100
kubernetes/level 1/task-2.md
Normal file
100
kubernetes/level 1/task-2.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team is delving into Kubernetes for app management. One team member needs to create a deployment following these details:
|
||||
|
||||
|
||||
Create a deployment named httpd to deploy the application httpd using the image httpd:latest (ensure to specify the tag)
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
# Solution
|
||||
|
||||
# Kubernetes Deployment — `httpd`
|
||||
|
||||
A Deployment named `httpd` running the `httpd:latest` image. Applied inline via a
|
||||
heredoc — no manifest file on disk.
|
||||
|
||||
## Apply (heredoc → kubectl)
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: httpd
|
||||
labels:
|
||||
app: httpd
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: httpd
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: httpd
|
||||
spec:
|
||||
containers:
|
||||
- name: httpd
|
||||
image: httpd:latest
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The heredoc apply pattern
|
||||
|
||||
- **`kubectl apply -f -`** reads the manifest from **stdin**; the heredoc feeds the YAML
|
||||
straight in, so nothing is written to disk.
|
||||
- **`<<'EOF'` (delimiter quoted)** disables shell expansion inside the body, so any
|
||||
`$VAR` / backticks in a manifest stay literal — the right default for Kubernetes YAML.
|
||||
- **Indentation matters** — keep it exact, spaces not tabs.
|
||||
|
||||
### The manifest, field by field
|
||||
|
||||
- **`apiVersion: apps/v1` / `kind: Deployment`** — a Deployment is an `apps/v1` object
|
||||
that manages a ReplicaSet, which in turn manages Pods. It gives you declarative
|
||||
updates, rollouts/rollbacks, and self-healing (recreates pods that die).
|
||||
- **`metadata.name: httpd`** — the Deployment's name, exactly as required.
|
||||
- **`spec.replicas: 1`** — how many pod copies to run. The task doesn't specify a
|
||||
count, so one replica is the minimal correct choice.
|
||||
- **`spec.selector.matchLabels.app: httpd`** — how the Deployment finds the pods it
|
||||
owns. This **must** match the pod template's labels below, or the API rejects the
|
||||
manifest (`selector does not match template labels`).
|
||||
- **`spec.template`** — the pod blueprint the Deployment stamps out:
|
||||
- **`template.metadata.labels.app: httpd`** — labels applied to each pod; these are
|
||||
what the selector targets. The label key/value is arbitrary but must be consistent
|
||||
between selector and template.
|
||||
- **`template.spec.containers`** — one container:
|
||||
- **`name: httpd`** — container name.
|
||||
- **`image: httpd:latest`** — the Apache HTTP Server image at the `latest` tag,
|
||||
stated explicitly as required. (Bare `httpd` defaults to `latest`, but the task
|
||||
asks for the tag to be specified.)
|
||||
|
||||
### Why the selector/template labels must agree
|
||||
|
||||
A Deployment is decoupled from its pods — it tracks them purely by label selector.
|
||||
`spec.selector.matchLabels` is the query; `spec.template.metadata.labels` is what gets
|
||||
stamped on each pod. If they don't match, the Deployment couldn't recognize its own
|
||||
pods, so Kubernetes rejects the config outright. Keeping both `app: httpd` satisfies
|
||||
that contract.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# Deployment exists and is available
|
||||
kubectl get deployment httpd
|
||||
|
||||
# Rollout completed
|
||||
kubectl rollout status deployment/httpd
|
||||
|
||||
# Pod is running with the right image
|
||||
kubectl get pods -l app=httpd \
|
||||
-o jsonpath='{.items[0].spec.containers[0].image}{"\n"}'
|
||||
```
|
||||
|
||||
Expected — `httpd` deployment showing `READY 1/1`, rollout reporting success, and the
|
||||
image printing `httpd:latest`.
|
||||
|
||||
> If a pod sits in `ImagePullBackOff`, the node may be offline or rate-limited on the
|
||||
> pull. `kubectl describe pod <name>` shows pull events under `Events:`.
|
||||
99
kubernetes/level 1/task-3.md
Normal file
99
kubernetes/level 1/task-3.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team is planning to deploy some micro services on Kubernetes platform. The team has already set up a Kubernetes cluster and now they want to set up some namespaces, deployments etc. Based on the current requirements, the team has shared some details as below:
|
||||
|
||||
|
||||
Create a namespace named dev and deploy a POD within it. Name the pod dev-nginx-pod and use the nginx image with the latest tag. Ensure to specify the tag as nginx:latest.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
# Solution
|
||||
|
||||
# Kubernetes Namespace + Pod — `dev` / `dev-nginx-pod`
|
||||
|
||||
Create a `dev` namespace and run `dev-nginx-pod` (image `nginx:latest`) inside it. Both
|
||||
objects are applied in one heredoc using a multi-document manifest — no file on disk.
|
||||
|
||||
## Apply (heredoc → kubectl)
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: dev
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: dev-nginx-pod
|
||||
namespace: dev
|
||||
labels:
|
||||
app: dev-nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx-container
|
||||
image: nginx:latest
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The heredoc apply pattern
|
||||
|
||||
- **`kubectl apply -f -`** reads from **stdin**; the heredoc feeds the YAML straight in,
|
||||
nothing written to disk.
|
||||
- **`<<'EOF'` (delimiter quoted)** disables shell expansion, so any `$VAR`/backticks in
|
||||
a manifest stay literal — the right default for Kubernetes YAML.
|
||||
- **`---`** separates the two YAML documents in a single stream. `kubectl apply`
|
||||
processes each in order, so the **Namespace is created before the Pod** that targets
|
||||
it. Ordering matters: a pod referencing a namespace that doesn't yet exist would fail
|
||||
with `namespaces "dev" not found`.
|
||||
|
||||
### The Namespace
|
||||
|
||||
- **`kind: Namespace` / `metadata.name: dev`** — a namespace is a virtual cluster
|
||||
partition for isolating and grouping resources. Creating it first gives the pod
|
||||
somewhere to live.
|
||||
|
||||
### The Pod
|
||||
|
||||
- **`metadata.name: dev-nginx-pod`** — the pod's name, exactly as required.
|
||||
- **`metadata.namespace: dev`** — places the pod **in the `dev` namespace**. This is the
|
||||
key field for the task: without it, the pod would land in `default`. (Equivalent to
|
||||
passing `-n dev` on the command line, but pinning it in the manifest is explicit and
|
||||
self-contained.)
|
||||
- **`metadata.labels.app: dev-nginx`** — an optional label for selection/grouping. Not
|
||||
required by the task, but good practice.
|
||||
- **`spec.containers`** — one container:
|
||||
- **`name: nginx-container`** — the container name.
|
||||
- **`image: nginx:latest`** — the NGINX image at the `latest` tag, stated explicitly
|
||||
as required. (Bare `nginx` defaults to `latest`, but the task asks for the tag to be
|
||||
specified.)
|
||||
|
||||
### Namespace-scoping recap
|
||||
|
||||
The `namespace: dev` line in the pod's metadata is what satisfies "deploy a POD within
|
||||
it." Everything else about the pod is standard; the namespace field is the one that
|
||||
ties the two objects together.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# Namespace exists
|
||||
kubectl get namespace dev
|
||||
|
||||
# Pod is running inside the dev namespace
|
||||
kubectl get pod dev-nginx-pod -n dev -o wide
|
||||
|
||||
# Image is correct
|
||||
kubectl get pod dev-nginx-pod -n dev \
|
||||
-o jsonpath='{.spec.containers[0].image}{"\n"}'
|
||||
```
|
||||
|
||||
Expected — namespace `dev` present, `dev-nginx-pod` in `Running` status within it, and
|
||||
the image printing `nginx:latest`.
|
||||
|
||||
> Remember to pass `-n dev` on any `kubectl` command targeting this pod — it's not in
|
||||
> the `default` namespace. If the pod sits in `ImagePullBackOff`, run
|
||||
> `kubectl describe pod dev-nginx-pod -n dev` to see the pull events.
|
||||
108
kubernetes/level 1/task-4.md
Normal file
108
kubernetes/level 1/task-4.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team has noticed performance issues in some Kubernetes-hosted applications due to resource constraints. To address this, they plan to set limits on resource utilization. Here are the details:
|
||||
|
||||
|
||||
Create a pod named httpd-pod with a container named httpd-container. Use the httpd image with the latest tag (specify as httpd:latest). Configure the following container-level resource requests and limits for the container:
|
||||
|
||||
Requests: Memory: 15Mi, CPU: 100m
|
||||
|
||||
Limits: Memory: 20Mi, CPU: 100m
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
# Solution
|
||||
|
||||
# Kubernetes Pod with Resource Limits — `httpd-pod`
|
||||
|
||||
A pod running `httpd:latest` with container-level CPU/memory requests and limits.
|
||||
Applied inline via a heredoc — no manifest file on disk.
|
||||
|
||||
## Apply (heredoc → kubectl)
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: httpd-pod
|
||||
labels:
|
||||
app: httpd
|
||||
spec:
|
||||
containers:
|
||||
- name: httpd-container
|
||||
image: httpd:latest
|
||||
resources:
|
||||
requests:
|
||||
memory: "15Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "20Mi"
|
||||
cpu: "100m"
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The heredoc apply pattern
|
||||
|
||||
- **`kubectl apply -f -`** reads from **stdin**; the heredoc feeds the YAML in, nothing
|
||||
on disk.
|
||||
- **`<<'EOF'` (delimiter quoted)** disables shell expansion, keeping `$VAR`/backticks
|
||||
literal — the right default for Kubernetes YAML.
|
||||
|
||||
### The pod
|
||||
|
||||
- **`metadata.name: httpd-pod`** and container **`name: httpd-container`** — exactly as
|
||||
required.
|
||||
- **`image: httpd:latest`** — Apache HTTP Server at the `latest` tag, stated explicitly.
|
||||
|
||||
### The `resources` block — the heart of this task
|
||||
|
||||
Resource controls are set **per container**, under `spec.containers[].resources`:
|
||||
|
||||
- **`requests`** — the amount the scheduler **reserves** for the container. Kubernetes
|
||||
places the pod on a node that has at least this much free, and the request is what
|
||||
counts against node allocatable capacity. Here: `memory: 15Mi`, `cpu: 100m`.
|
||||
- **`limits`** — the hard **ceiling** the container may use at runtime. Here:
|
||||
`memory: 20Mi`, `cpu: 100m`.
|
||||
|
||||
The two behave very differently when exceeded:
|
||||
|
||||
| Resource | Over the limit → |
|
||||
|----------|------------------|
|
||||
| **CPU** | **Throttled** — the container is capped at its CPU limit; it's slowed, never killed. |
|
||||
| **Memory** | **OOM-killed** — memory is incompressible, so exceeding the limit terminates the container (it restarts per its policy). |
|
||||
|
||||
### Units explained
|
||||
|
||||
- **`100m` CPU** = 100 millicores = 0.1 of a vCPU. CPU is measured in millicores; `1000m`
|
||||
= 1 full core. Request and limit are both `100m` here, so the container is guaranteed
|
||||
0.1 core and capped at 0.1 core.
|
||||
- **`15Mi` / `20Mi` memory** — `Mi` is **mebibytes** (base-2, 1 Mi = 1,048,576 bytes),
|
||||
distinct from `M` (megabytes, base-10, 1,000,000 bytes). The task specifies `Mi`, so
|
||||
use `Mi` exactly — mixing units is a common mistake.
|
||||
|
||||
### Requests ≤ limits
|
||||
|
||||
Note memory request (`15Mi`) is below its limit (`20Mi`), while CPU request equals its
|
||||
limit (`100m`). That's valid: a request must never exceed its limit, but being lower is
|
||||
fine and normal — it lets the container burst up to the limit when the node has spare
|
||||
capacity while only reserving the smaller request amount.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# Pod running
|
||||
kubectl get pod httpd-pod
|
||||
|
||||
# Requests and limits are set correctly
|
||||
kubectl get pod httpd-pod \
|
||||
-o jsonpath='{.spec.containers[0].resources}{"\n"}'
|
||||
```
|
||||
|
||||
Expected — `httpd-pod` in `Running` status, and the resources printing requests
|
||||
`{cpu:100m, memory:15Mi}` with limits `{cpu:100m, memory:20Mi}`.
|
||||
|
||||
> If the pod is `OOMKilled` on start, `httpd:latest` needed more than the `20Mi` memory
|
||||
> limit — but Apache's base footprint fits comfortably, so the given values are fine.
|
||||
132
kubernetes/level 1/task-5.md
Normal file
132
kubernetes/level 1/task-5.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Assignment
|
||||
|
||||
An application currently running on the Kubernetes cluster employs the nginx web server. The Nautilus application development team has introduced some recent changes that need deployment. They've crafted an image nginx:1.17 with the latest updates.
|
||||
|
||||
|
||||
Execute a rolling update for this application, integrating the nginx:1.17 image. The deployment is named nginx-deployment.
|
||||
|
||||
Ensure all pods are operational post-update.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
# Solution
|
||||
|
||||
# Rolling Update — `nginx-deployment` → `nginx:1.17`
|
||||
|
||||
Update an existing deployment's image to `nginx:1.17` via a rolling update, then confirm
|
||||
all pods are healthy.
|
||||
|
||||
## Primary method — `kubectl set image` (recommended)
|
||||
|
||||
Because the deployment already exists and you don't need its full manifest to change one
|
||||
image, this is the canonical rolling-update command:
|
||||
|
||||
```bash
|
||||
# Trigger the rolling update
|
||||
kubectl set image deployment/nginx-deployment nginx=nginx:1.17
|
||||
|
||||
# Watch it roll out and block until complete
|
||||
kubectl rollout status deployment/nginx-deployment
|
||||
```
|
||||
|
||||
`nginx=nginx:1.17` means "set the container **named `nginx`** to image `nginx:1.17`."
|
||||
Confirm the container's actual name first if unsure:
|
||||
|
||||
```bash
|
||||
kubectl get deployment nginx-deployment \
|
||||
-o jsonpath='{.spec.template.spec.containers[*].name}{"\n"}'
|
||||
```
|
||||
|
||||
Use whatever name that prints on the left side of the `=`.
|
||||
|
||||
## Alternative — heredoc `apply`
|
||||
|
||||
You can also drive the update declaratively by re-applying the deployment with the new
|
||||
image. **Caveat:** `apply` reconciles the whole pod template, so the manifest must match
|
||||
the existing deployment's container name, labels, and selector — otherwise you'll change
|
||||
more than intended. Grab the current spec first:
|
||||
|
||||
```bash
|
||||
kubectl get deployment nginx-deployment -o yaml # note container name, labels, replicas
|
||||
```
|
||||
|
||||
Then apply with the image bumped to `nginx:1.17` (adjust names/replicas to match what you
|
||||
saw):
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.17
|
||||
EOF
|
||||
|
||||
kubectl rollout status deployment/nginx-deployment
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### What a rolling update does
|
||||
|
||||
A Deployment's default strategy is `RollingUpdate`: when the pod template changes (here,
|
||||
the image), the Deployment creates a **new ReplicaSet** for `nginx:1.17` and shifts pods
|
||||
over **incrementally** — spinning up new pods and tearing down old ones a few at a time,
|
||||
governed by `maxSurge` (how many extra pods above desired count are allowed) and
|
||||
`maxUnavailable` (how many below). This keeps the app serving throughout, with no full
|
||||
outage. Changing the image is exactly the kind of template change that triggers it.
|
||||
|
||||
### `set image` vs. heredoc `apply`
|
||||
|
||||
- **`set image`** patches just the container image on the live object. It's surgical,
|
||||
needs no knowledge of the rest of the spec, and is the idiomatic way to roll a new
|
||||
image onto an existing deployment. Preferred when you're changing one field.
|
||||
- **`apply`** reconciles the entire manifest you feed it against the live object. It's the
|
||||
right tool when you own the manifest as source of truth, but for a one-field image bump
|
||||
on a deployment you didn't author, it forces you to reproduce the full spec correctly —
|
||||
more surface area to get wrong. That's why `set image` is primary here.
|
||||
|
||||
Both produce the same underlying rolling update; they differ only in how the change is
|
||||
expressed.
|
||||
|
||||
### `rollout status`
|
||||
|
||||
`kubectl rollout status` blocks until the new ReplicaSet is fully rolled out and all
|
||||
updated pods report Ready — the "ensure all pods are operational post-update" requirement.
|
||||
If the update stalls (e.g. a bad image), it surfaces there rather than silently leaving
|
||||
old pods running.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# All pods updated and running
|
||||
kubectl get pods -l app=nginx -o wide
|
||||
|
||||
# Every pod now on nginx:1.17
|
||||
kubectl get deployment nginx-deployment \
|
||||
-o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
|
||||
|
||||
# Rollout history (shows the new revision)
|
||||
kubectl rollout history deployment/nginx-deployment
|
||||
```
|
||||
|
||||
Expected — all pods `Running` and `READY`, the image printing `nginx:1.17`, and a new
|
||||
revision recorded in the rollout history.
|
||||
|
||||
> If the rollout hangs with new pods in `ImagePullBackOff`, the `nginx:1.17` pull failed
|
||||
> (node offline / rate-limited). `kubectl describe pod <name>` shows the cause; you can
|
||||
> revert with `kubectl rollout undo deployment/nginx-deployment`.
|
||||
85
kubernetes/level 1/task-6.md
Normal file
85
kubernetes/level 1/task-6.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Assignment
|
||||
|
||||
Earlier today, the Nautilus DevOps team deployed a new release for an application. However, a customer has reported a bug related to this recent release. Consequently, the team aims to revert to the previous version.
|
||||
|
||||
|
||||
There exists a deployment named nginx-deployment; initiate a rollback to the previous revision.
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
# Solution
|
||||
|
||||
# Rollback — `nginx-deployment` to the previous revision
|
||||
|
||||
Revert the deployment to the revision that was live before the buggy release.
|
||||
|
||||
> **Why no heredoc `apply` here:** a rollback isn't a declarative manifest operation. It
|
||||
> doesn't describe a desired end-state in YAML — it tells the Deployment controller to
|
||||
> switch back to a **previously recorded ReplicaSet revision** stored in rollout history.
|
||||
> That's an imperative rollout action, so `kubectl rollout undo` is the correct (and only
|
||||
> idiomatic) tool. Re-applying an old manifest would work only if you happened to still
|
||||
> have the exact prior YAML, and even then it's the wrong abstraction.
|
||||
|
||||
## Command
|
||||
|
||||
```bash
|
||||
# Roll back to the immediately previous revision
|
||||
kubectl rollout undo deployment/nginx-deployment
|
||||
|
||||
# Block until the rollback finishes and pods are Ready
|
||||
kubectl rollout status deployment/nginx-deployment
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### Rollout history and revisions
|
||||
|
||||
Every change to a Deployment's pod template (image bump, env change, etc.) creates a new
|
||||
**revision**, each backed by its own ReplicaSet. Kubernetes retains old ReplicaSets (up to
|
||||
`spec.revisionHistoryLimit`, default 10) precisely so you can roll back. You can inspect
|
||||
them:
|
||||
|
||||
```bash
|
||||
kubectl rollout history deployment/nginx-deployment
|
||||
```
|
||||
|
||||
### What `rollout undo` does
|
||||
|
||||
- With no `--to-revision` flag, it reverts to the **immediately previous** revision — the
|
||||
one running before the current (buggy) release. That's exactly this task.
|
||||
- Mechanically, it scales the **previous ReplicaSet** back up and the **current** one down
|
||||
using the same `RollingUpdate` strategy — so the rollback itself is gradual and keeps the
|
||||
app serving, no full outage.
|
||||
- The rollback is recorded as a **new** revision in history (revisions move forward even
|
||||
when the content is an older template), so you always have a clean audit trail.
|
||||
|
||||
### Targeting a specific revision (if needed)
|
||||
|
||||
If "previous" isn't the right target, pick an explicit revision from the history:
|
||||
|
||||
```bash
|
||||
kubectl rollout undo deployment/nginx-deployment --to-revision=<N>
|
||||
```
|
||||
|
||||
For this task the default (previous revision) is what's required, so no flag is needed.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# Rollback completed, pods Ready
|
||||
kubectl rollout status deployment/nginx-deployment
|
||||
|
||||
# Confirm the image/template reverted to the prior version
|
||||
kubectl get deployment nginx-deployment \
|
||||
-o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
|
||||
|
||||
# History shows a new revision at the top reflecting the rollback
|
||||
kubectl rollout history deployment/nginx-deployment
|
||||
```
|
||||
|
||||
Expected — rollout reports success with all pods `Running`/`READY`, the container image
|
||||
reverted to the previous release's tag, and a new revision entry recording the rollback.
|
||||
|
||||
> If the rollback stalls, `kubectl describe deployment nginx-deployment` and
|
||||
> `kubectl get pods -l <selector>` show what's blocking (e.g. the old image also failing to
|
||||
> pull).
|
||||
112
kubernetes/level 1/task-7.md
Normal file
112
kubernetes/level 1/task-7.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Assignment
|
||||
The Nautilus DevOps team is gearing up to deploy applications on a Kubernetes cluster for migration purposes. A team member has been tasked with creating a ReplicaSet outlined below:
|
||||
|
||||
|
||||
|
||||
Create a ReplicaSet using httpd image with latest tag (ensure to specify as httpd:latest) and name it httpd-replicaset.
|
||||
|
||||
|
||||
Apply labels: app as httpd_app, type as front-end.
|
||||
|
||||
|
||||
Name the container httpd-container. Ensure the replica count is 4.
|
||||
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
# Solution
|
||||
|
||||
# Kubernetes ReplicaSet — `httpd-replicaset`
|
||||
|
||||
A ReplicaSet maintaining 4 `httpd:latest` pods, labeled `app: httpd_app` and
|
||||
`type: front-end`. Applied inline via a heredoc — no manifest file on disk.
|
||||
|
||||
## Apply (heredoc → kubectl)
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: apps/v1
|
||||
kind: ReplicaSet
|
||||
metadata:
|
||||
name: httpd-replicaset
|
||||
labels:
|
||||
app: httpd_app
|
||||
type: front-end
|
||||
spec:
|
||||
replicas: 4
|
||||
selector:
|
||||
matchLabels:
|
||||
app: httpd_app
|
||||
type: front-end
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: httpd_app
|
||||
type: front-end
|
||||
spec:
|
||||
containers:
|
||||
- name: httpd-container
|
||||
image: httpd:latest
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The heredoc apply pattern
|
||||
|
||||
- **`kubectl apply -f -`** reads from **stdin**; the heredoc feeds the YAML in, nothing on
|
||||
disk.
|
||||
- **`<<'EOF'` (delimiter quoted)** disables shell expansion, keeping `$VAR`/backticks
|
||||
literal — the right default for Kubernetes YAML.
|
||||
|
||||
### The manifest, field by field
|
||||
|
||||
- **`apiVersion: apps/v1` / `kind: ReplicaSet`** — a ReplicaSet ensures a specified number
|
||||
of identical pod replicas are running at all times, recreating any that die. (In
|
||||
practice you'd usually use a Deployment, which manages ReplicaSets and adds rollouts —
|
||||
but the task asks for a bare ReplicaSet.)
|
||||
- **`metadata.name: httpd-replicaset`** — the RS name, exactly as required.
|
||||
- **`metadata.labels`** — `app: httpd_app` and `type: front-end` on the ReplicaSet object
|
||||
itself, as required.
|
||||
- **`spec.replicas: 4`** — the desired pod count. The RS controller works continuously to
|
||||
keep exactly 4 matching pods running.
|
||||
- **`spec.selector.matchLabels`** — how the RS identifies the pods it owns. This **must**
|
||||
match the pod template's labels, or the API rejects the manifest with `selector does not
|
||||
match template labels`.
|
||||
- **`spec.template`** — the pod blueprint:
|
||||
- **`template.metadata.labels`** — `app: httpd_app`, `type: front-end` stamped on every
|
||||
pod. These are what the selector targets.
|
||||
- **`template.spec.containers`** — one container: **`name: httpd-container`** with
|
||||
**`image: httpd:latest`** (tag stated explicitly, as required).
|
||||
|
||||
### Why the selector and template labels must agree
|
||||
|
||||
A ReplicaSet is decoupled from its pods and tracks them purely by label selector.
|
||||
`spec.selector.matchLabels` is the query; `spec.template.metadata.labels` is what gets
|
||||
stamped on each pod. If they don't match, the RS couldn't recognize its own pods, so
|
||||
Kubernetes rejects the config outright. Both carry `app: httpd_app` + `type: front-end`
|
||||
to satisfy that contract.
|
||||
|
||||
> Selector caution: because a ReplicaSet adopts **any** existing pod matching its selector,
|
||||
> reusing labels that other pods already carry can cause it to adopt (or fight over) them.
|
||||
> The two-label selector here is specific enough to avoid that in a clean environment.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# ReplicaSet exists with 4 ready replicas
|
||||
kubectl get rs httpd-replicaset
|
||||
|
||||
# Pods are running and labeled
|
||||
kubectl get pods -l app=httpd_app,type=front-end --show-labels
|
||||
|
||||
# Container image is correct
|
||||
kubectl get rs httpd-replicaset \
|
||||
-o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
|
||||
```
|
||||
|
||||
Expected — `httpd-replicaset` showing `DESIRED 4 / CURRENT 4 / READY 4`, four pods
|
||||
`Running` with both labels, and the image printing `httpd:latest`.
|
||||
|
||||
> If pods sit in `ImagePullBackOff`, the `httpd:latest` pull failed (node offline /
|
||||
> rate-limited). `kubectl describe pod <name>` shows the pull events.
|
||||
126
kubernetes/level 1/task-8.md
Normal file
126
kubernetes/level 1/task-8.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team is setting up recurring tasks on different schedules. Currently, they're developing scripts to be executed periodically. To kickstart the process, they're creating cron jobs in the Kubernetes cluster with placeholder commands. Follow the instructions below:
|
||||
|
||||
|
||||
|
||||
Create a cronjob named datacenter.
|
||||
|
||||
|
||||
Set Its schedule to something like */12 * * * *. You can set any schedule for now.
|
||||
|
||||
|
||||
Name the container cron-datacenter.
|
||||
|
||||
|
||||
Utilize the httpd image with latest tag (specify as httpd:latest).
|
||||
|
||||
|
||||
Execute the dummy command echo Welcome to xfusioncorp!.
|
||||
|
||||
|
||||
Ensure the restart policy is OnFailure.
|
||||
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
# Solution
|
||||
|
||||
# Kubernetes CronJob — `datacenter`
|
||||
|
||||
A CronJob that runs `echo Welcome to xfusioncorp!` in an `httpd:latest` container on a
|
||||
schedule. Applied inline via a heredoc — no manifest file on disk.
|
||||
|
||||
## Apply (heredoc → kubectl)
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: datacenter
|
||||
spec:
|
||||
schedule: "*/12 * * * *"
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: cron-datacenter
|
||||
image: httpd:latest
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- echo Welcome to xfusioncorp!
|
||||
restartPolicy: OnFailure
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The heredoc apply pattern
|
||||
|
||||
- **`kubectl apply -f -`** reads from **stdin**; the heredoc feeds the YAML in, nothing on
|
||||
disk.
|
||||
- **`<<'EOF'` (delimiter quoted)** disables shell expansion, keeping `$VAR`/backticks
|
||||
literal — important here so `echo Welcome to xfusioncorp!` is stored verbatim rather than
|
||||
evaluated by the jump-host's shell.
|
||||
|
||||
### The nested structure — the part that trips people up
|
||||
|
||||
A CronJob wraps three levels deep. From outside in:
|
||||
|
||||
1. **`CronJob.spec`** — the schedule and how to spawn jobs.
|
||||
2. **`jobTemplate.spec`** — the **Job** created on each tick.
|
||||
3. **`template.spec`** — the **Pod** the Job runs.
|
||||
|
||||
So the container and restart policy live at `spec.jobTemplate.spec.template.spec`, not
|
||||
directly under the CronJob. Getting the nesting wrong is the most common CronJob error.
|
||||
|
||||
### Field by field
|
||||
|
||||
- **`apiVersion: batch/v1` / `kind: CronJob`** — CronJob is a `batch/v1` object (stable
|
||||
since Kubernetes 1.21; the old `batch/v1beta1` is removed in modern clusters — use
|
||||
`batch/v1`).
|
||||
- **`metadata.name: datacenter`** — the CronJob name, exactly as required.
|
||||
- **`spec.schedule: "*/12 * * * *"`** — standard cron syntax
|
||||
(minute hour day-of-month month day-of-week). `*/12 * * * *` = every 12 minutes. The task
|
||||
allows any schedule; quote the string so YAML doesn't choke on the `*`.
|
||||
- **`jobTemplate.spec.template.spec.containers`** — one container:
|
||||
- **`name: cron-datacenter`** — the container name, exactly as required.
|
||||
- **`image: httpd:latest`** — tag stated explicitly, as required.
|
||||
- **`command`** — the dummy command. Written as `["/bin/sh","-c","echo Welcome to
|
||||
xfusioncorp!"]` so the shell handles the phrase as one command. `command` overrides the
|
||||
image's default entrypoint (httpd's web server), which is what we want — this is a
|
||||
one-shot echo, not a running server.
|
||||
- **`restartPolicy: OnFailure`** — required. It sits at the **pod** level (inside
|
||||
`template.spec`), not on the container. For Jobs/CronJobs only `OnFailure` and `Never` are
|
||||
valid (`Always` is rejected, since a batch job is meant to complete, not run forever).
|
||||
`OnFailure` restarts the pod if the command exits non-zero.
|
||||
|
||||
### Why `command` uses `/bin/sh -c`
|
||||
|
||||
Passing the echo through `sh -c` runs it as a shell command, so the full phrase (with its
|
||||
spaces and `!`) is handled correctly as a single string. Listing bare args instead would
|
||||
also work for a simple echo, but the `sh -c` form is robust and the common pattern for
|
||||
dummy commands.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# CronJob registered with the schedule
|
||||
kubectl get cronjob datacenter
|
||||
|
||||
# After a scheduled tick, a Job (and its pod) appears
|
||||
kubectl get jobs -l job-name --watch # Ctrl-C once one shows
|
||||
kubectl get pods -l job-name
|
||||
|
||||
# Check the output of a completed run
|
||||
kubectl logs job/<job-name-from-above>
|
||||
```
|
||||
|
||||
Expected — `datacenter` listed with schedule `*/12 * * * *`; after a tick, a Job runs to
|
||||
completion and its pod's logs show `Welcome to xfusioncorp!`.
|
||||
|
||||
> To trigger a run immediately instead of waiting for the schedule:
|
||||
> `kubectl create job --from=cronjob/datacenter datacenter-manual`
|
||||
107
kubernetes/level 1/task-9.md
Normal file
107
kubernetes/level 1/task-9.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team is crafting jobs in the Kubernetes cluster. While they're developing actual scripts/commands, they're currently setting up templates and testing jobs with dummy commands. Please create a job template as per details given below:
|
||||
|
||||
|
||||
Create a job named countdown-datacenter.
|
||||
|
||||
The spec template should be named countdown-datacenter (under metadata), and the container should be named container-countdown-datacenter
|
||||
|
||||
Utilize image ubuntu with latest tag (ensure to specify as ubuntu:latest), and set the restart policy to Never.
|
||||
|
||||
Execute the command sleep 5
|
||||
|
||||
Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.
|
||||
|
||||
# Solution
|
||||
|
||||
# Kubernetes Job — `countdown-datacenter`
|
||||
|
||||
A one-shot Job that runs `sleep 5` in an `ubuntu:latest` container. Applied inline via a
|
||||
heredoc — no manifest file on disk.
|
||||
|
||||
## Apply (heredoc → kubectl)
|
||||
|
||||
```bash
|
||||
kubectl apply -f - <<'EOF'
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: countdown-datacenter
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
name: countdown-datacenter
|
||||
spec:
|
||||
containers:
|
||||
- name: container-countdown-datacenter
|
||||
image: ubuntu:latest
|
||||
command:
|
||||
- sleep
|
||||
- "5"
|
||||
restartPolicy: Never
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The heredoc apply pattern
|
||||
|
||||
- **`kubectl apply -f -`** reads from **stdin**; the heredoc feeds the YAML in, nothing on
|
||||
disk.
|
||||
- **`<<'EOF'` (delimiter quoted)** disables shell expansion, keeping the manifest literal —
|
||||
the right default for Kubernetes YAML.
|
||||
|
||||
### Job vs. CronJob
|
||||
|
||||
A **Job** runs a pod to **completion** once (or a set number of times), unlike a CronJob
|
||||
which spawns Jobs on a schedule. So there's no `schedule` or `jobTemplate` here — the pod
|
||||
template sits directly under `spec.template`, one level shallower than a CronJob.
|
||||
|
||||
### Field by field
|
||||
|
||||
- **`apiVersion: batch/v1` / `kind: Job`** — Job is a `batch/v1` object.
|
||||
- **`metadata.name: countdown-datacenter`** — the Job's name, exactly as required.
|
||||
- **`spec.template.metadata.name: countdown-datacenter`** — the pod template name, as the
|
||||
task explicitly requests. (Note: a template name is optional and normally ignored for
|
||||
Jobs — the actual pod gets an auto-generated name like `countdown-datacenter-abcde` — but
|
||||
the task asks for it, so it's set.)
|
||||
- **`spec.template.spec.containers`** — one container:
|
||||
- **`name: container-countdown-datacenter`** — the container name, exactly as required.
|
||||
- **`image: ubuntu:latest`** — tag stated explicitly, as required.
|
||||
- **`command: ["sleep", "5"]`** — the dummy command. The `"5"` is **quoted** so YAML
|
||||
passes it as a string argument; command args must be strings, and an unquoted `5` would
|
||||
be parsed as an integer and rejected.
|
||||
- **`restartPolicy: Never`** — required. It sits at the **pod** level (inside
|
||||
`template.spec`), not on the container. Jobs accept only `Never` or `OnFailure` (`Always`
|
||||
is invalid for batch workloads). `Never` means if the pod fails, the Job creates a *new*
|
||||
pod rather than restarting the existing one.
|
||||
|
||||
### `restartPolicy: Never` vs. `OnFailure` for Jobs
|
||||
|
||||
Both are valid for Jobs, and they differ in failure handling:
|
||||
- **`Never`** — a failed pod is left as-is; the Job spins up a fresh pod for the next
|
||||
attempt. You end up with visible failed pods (useful for debugging).
|
||||
- **`OnFailure`** — the same pod's container is restarted in place.
|
||||
|
||||
The task specifies `Never`, so failed attempts (if any) appear as separate pods.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# Job created and completing
|
||||
kubectl get job countdown-datacenter
|
||||
|
||||
# Pod runs then completes
|
||||
kubectl get pods -l job-name=countdown-datacenter
|
||||
|
||||
# Confirm image and command
|
||||
kubectl get job countdown-datacenter \
|
||||
-o jsonpath='{.spec.template.spec.containers[0].image}{" "}{.spec.template.spec.containers[0].command}{"\n"}'
|
||||
```
|
||||
|
||||
Expected — `countdown-datacenter` showing `COMPLETIONS 1/1` after ~5 seconds, its pod
|
||||
transitioning `Running` → `Completed`, and the jsonpath printing `ubuntu:latest [sleep 5]`.
|
||||
|
||||
> The pod runs `sleep 5` then exits 0, so the Job completes cleanly. If it shows `Error`,
|
||||
> check `kubectl logs job/countdown-datacenter`.
|
||||
Reference in New Issue
Block a user