docs: add Kubernetes CKS study notes

This commit is contained in:
2026-08-04 23:18:45 +02:00
parent a6ee7a2b07
commit 91a1849009
57 changed files with 8313 additions and 0 deletions

View 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:`.

View 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).

View 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}'`.

View 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
(`3000032767`), 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.

View 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 (`3000032767`), 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`.

View 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`.

View 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:`.

View 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.

View 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.

View 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`.

View 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).

View 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.

View 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`

View 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`.