DEV Community

cloud-sky-ops
cloud-sky-ops

Posted on

Post 0/10 — Foundations: Zero to Base Setup

I’m a DevOps engineer and in this blog I’ll get you from zero to a working local cluster, deploy an app with raw YAML, then switch to Helm—plus the mental models and commands you’ll reuse daily.


Executive Summary

  • Stand up a local Kubernetes cluster (kind/minikube) and verify it with kubectl.
  • Cement a mental model of Pods → ReplicaSets → Deployments → Services → Controllers.
  • Apply clean YAML (Deployment + Service), then Helm-ify it with a minimal chart.
  • Learn the 12 commands I actually use day-to-day (kubectl + Helm).
  • Practice pitfall recovery (images won’t pull, pending pods, bad Services, context issues).

Prereqs

  • minikube
  • kind
  • Docker running (required by kind & often by minikube).
  • kubectl (v1.28+ recommended)
  • helm

Here’s the updated Install Hints section starting numbering from 1 instead of 0:


Install Hints (macOS, Linux, Windows)

1. Docker (required for kind & often for minikube)

  • macOS (with Homebrew):
 brew install --cask docker open /Applications/Docker.app 
Enter fullscreen mode Exit fullscreen mode

Tip: Ensure Docker Desktop is running before creating clusters.

  • Linux (Debian/Ubuntu):
 sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg lsb-release sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # Add your user to docker group (log out/in after) sudo usermod -aG docker $USER 
Enter fullscreen mode Exit fullscreen mode
  • Windows (PowerShell as Admin):
 choco install docker-desktop 
Enter fullscreen mode Exit fullscreen mode

Tip: After install, restart and launch Docker Desktop.


2. kubectl

  • macOS:
 brew install kubectl 
Enter fullscreen mode Exit fullscreen mode
  • Linux (Debian/Ubuntu):
 sudo apt-get update && sudo apt-get install -y apt-transport-https gnupg curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /usr/share/keyrings/kubernetes-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo apt-get update sudo apt-get install -y kubectl 
Enter fullscreen mode Exit fullscreen mode
  • Windows (PowerShell as Admin):
 choco install kubernetes-cli 
Enter fullscreen mode Exit fullscreen mode

3. kind (Kubernetes in Docker)

  • macOS:
 brew install kind 
Enter fullscreen mode Exit fullscreen mode
  • Linux:
 curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.22.0/kind-linux-amd64 chmod +x ./kind sudo mv ./kind /usr/local/bin/kind 
Enter fullscreen mode Exit fullscreen mode
  • Windows (PowerShell as Admin):
 choco install kind 
Enter fullscreen mode Exit fullscreen mode

4. minikube

  • macOS:
 brew install minikube 
Enter fullscreen mode Exit fullscreen mode
  • Linux:
 curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 sudo install minikube-linux-amd64 /usr/local/bin/minikube 
Enter fullscreen mode Exit fullscreen mode
  • Windows (PowerShell as Admin):
 choco install minikube 
Enter fullscreen mode Exit fullscreen mode

5. Helm

  • macOS:
 brew install helm 
Enter fullscreen mode Exit fullscreen mode
  • Linux:
 curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash 
Enter fullscreen mode Exit fullscreen mode
  • Windows (PowerShell as Admin):
 choco install kubernetes-helm 
Enter fullscreen mode Exit fullscreen mode

Verify Installations

kubectl version --client kind version minikube version helm version docker --version 
Enter fullscreen mode Exit fullscreen mode

Concepts & Skills

1) Kubernetes Mental Model

Definition: Kubernetes reconciles desired state (your specs) to actual state (running Pods) via controllers. It explains everything: you ask for N replicas, deployments manage ReplicaSets which manage Pods; Services route to healthy Pod endpoints.

Best practices:

  • Treat Pods as ephemeral; deploy via Deployments, not bare Pods.
  • Scale & roll out via Deployments; don’t hand-edit Pods.
  • Expose traffic via Services; keep labels/selectors consistent.
  • Let controllers do the work; think “declare, don’t script.”

Commands you’ll use:

kubectl get deploy,rs,pods,svc -A kubectl describe deploy <name> kubectl rollout status deploy/<name> kubectl scale deploy/<name> --replicas=3 
Enter fullscreen mode Exit fullscreen mode

Before → After

# Before: single Pod (fragile) apiVersion: v1 kind: Pod metadata: { name: hello } spec: { containers: [{ name: app, image: nginx:1.25 }] } # After: Deployment + Service (managed, scalable) apiVersion: apps/v1 kind: Deployment metadata: { name: hello } spec: replicas: 2 selector: { matchLabels: { app: hello } } template: metadata: { labels: { app: hello } } spec: containers: [{ name: app, image: nginx:1.25 }] --- apiVersion: v1 kind: Service metadata: { name: hello } spec: type: ClusterIP selector: { app: hello } ports: [{ port: 80, targetPort: 80 }] 
Enter fullscreen mode Exit fullscreen mode

Decision cues (when to use):

  • Deployment for stateless apps, rolling updates.
  • StatefulSet for ordered/identity-bound Pods (DBs).
  • DaemonSet for per-node agents.
  • Job/CronJob for finite/recurring work.

2) YAML Hygiene

Definition: Kubernetes resources are structured YAML: apiVersion, kind, metadata, spec. 90% of “Why won’t it work?” is indentation, wrong apiVersion, or misplaced fields.

Best practices:

  • Keep a stable skeleton: apiVersion/kind/metadata/spec.
  • Use 2 spaces; never tabs.
  • Verify with kubectl explain and kubectl apply --dry-run=client -f.
  • Prefer labels (e.g., app: hello) for selectors; avoid ad-hoc names.
  • Pin images (e.g., nginx:1.25) to avoid surprise upgrades.

Helpful commands:

kubectl explain deployment.spec --recursive | less kubectl apply --dry-run=client -f hello.yaml 
Enter fullscreen mode Exit fullscreen mode

Before → After

# Before: wrong apiVersion, mixed tabs apiVersion: apps/v2 kind: Deployment metadata: name: hello # <-- tab! spec: {} # After: correct and clean apiVersion: apps/v1 kind: Deployment metadata: name: hello spec: replicas: 1 selector: { matchLabels: { app: hello } } template: metadata: { labels: { app: hello } } spec: containers: [{ name: app, image: nginx:1.25 }] 
Enter fullscreen mode Exit fullscreen mode

Decision cues:

  • Use kubectl explain if you’re guessing a field.
  • Use --dry-run for fast validation before real apply.

3) kubectl Basics

Definition: kubectl is the CLI to talk to the API server using your current context/namespace. Wrong context/namespace is the #1 cause of “it’s not found.”

Best practices:

  • Set a default namespace per context.
  • Use wide output and labels in get.
  • Rely on describe and events to debug.
  • Use -o yaml to see server-filled fields.
  • Use kubeconfig contexts; don’t point prod by accident.

Commands:

kubectl config get-contexts kubectl config set-context --current --namespace=dev kubectl get pods -o wide kubectl describe pod <pod> kubectl get events --sort-by=.lastTimestamp 
Enter fullscreen mode Exit fullscreen mode

Before → After

# Before: implicit default namespace (surprise!) kubectl get pods # After: explicit context & namespace kubectl config set-context --current --namespace=dev kubectl get pods -n dev 
Enter fullscreen mode Exit fullscreen mode

Decision cues:

  • If a resource “disappears,” check namespace.
  • If a command “hangs,” check context/cluster.

4) Local Cluster: kind or minikube

Definition: kind runs Kubernetes in Docker containers; minikube runs a local Kubernetes VM/container. Fast, reproducible clusters for development and demos.

Best practices:

  • Use kind for simple, Docker-backed clusters.
  • Use minikube for addons/ingress and driver flexibility.
  • Name clusters per project (kind create cluster --name demo).
  • Export kubeconfig only for the current shell (avoid prod collisions).

Commands:

# kind kind create cluster --name k90 kubectl cluster-info kubectl get nodes # minikube minikube start minikube status kubectl get nodes 
Enter fullscreen mode Exit fullscreen mode

Before → After

# Before: ad-hoc envs, “works on my machine” docker run -p 8080:80 nginx # After: real cluster parity locally kind create cluster --name k90 kubectl apply -f k8s/hello.yaml 
Enter fullscreen mode Exit fullscreen mode

Decision cues:

  • kind if you already live in Docker land & want speed.
  • minikube if you need addons and drivers (HyperKit, Docker, etc.).

5) Helm Basics

Definition: Helm is Kubernetes’ package manager: it templatizes YAML, versioned as charts, and then installed as releases. Stop copy-pasting YAML; manage environments, values, upgrades, and rollbacks cleanly.

Best practices:

  • Keep charts minimal; prefer a small set of templates.
  • Parameterize only what changes across envs.
  • Validate with helm lint and render with helm template.
  • Track releases with helm list and rollback confidently.

Commands:

helm create hello helm lint hello helm template hello helm install hello ./hello -n dev --create-namespace helm upgrade hello ./hello -f values-dev.yaml helm rollback hello 1 
Enter fullscreen mode Exit fullscreen mode

Before → After

# Before: multiple hand-maintained YAML files per env kubectl apply -f dev/hello.yaml kubectl apply -f prod/hello.yaml # After: one chart, many values helm install hello ./hello -f values-dev.yaml helm upgrade hello ./hello -f values-prod.yaml 
Enter fullscreen mode Exit fullscreen mode

Decision cues:

  • Use raw YAML to learn and for tiny one-offs.
  • Use Helm once you need env variants, upgrades, teams, reuse.

Diagrams

Architecture (request → Service → Pods)

Diagram-1

Control Plane Path (sequence)

Diagram-2


Hands-on Mini-Lab (20–30 min)

Goal: Create a cluster, deploy “hello” with raw YAML, then with Helm; compare manifests.

1) Create a local cluster

kind create cluster --name k90 kubectl cluster-info kubectl get nodes kubectl config set-context --current --namespace=dev 
Enter fullscreen mode Exit fullscreen mode

(macOS: if bash errors, run in zsh or install newer bash with Homebrew.)

2) Raw YAML: Deployment + Service

Create k8s/hello.yaml:

apiVersion: apps/v1 kind: Deployment metadata: name: hello labels: { app: hello } spec: replicas: 2 selector: { matchLabels: { app: hello } } template: metadata: { labels: { app: hello } } spec: containers: - name: app image: nginx:1.25 ports: [{ containerPort: 80 }] --- apiVersion: v1 kind: Service metadata: name: hello labels: { app: hello } spec: type: ClusterIP selector: { app: hello } ports: - name: http port: 80 targetPort: 80 
Enter fullscreen mode Exit fullscreen mode

Apply and verify:

kubectl apply -f k8s/hello.yaml kubectl rollout status deploy/hello kubectl get svc hello -o wide kubectl get pods -l app=hello -o wide 
Enter fullscreen mode Exit fullscreen mode

Port-forward to test:

kubectl port-forward svc/hello 8080:80 # open http://localhost:8080 
Enter fullscreen mode Exit fullscreen mode

3) Helm-ify it

Create a chart and trim it down:

helm create hello-chart # Keep only templates/deployment.yaml and templates/service.yaml; delete extras like hpa, serviceaccount, tests. 
Enter fullscreen mode Exit fullscreen mode

hello-chart/values.yaml (minimal):

replicaCount: 2 image: repository: nginx tag: "1.25" pullPolicy: IfNotPresent service: type: ClusterIP port: 80 labels: app: hello 
Enter fullscreen mode Exit fullscreen mode

hello-chart/templates/deployment.yaml:

apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "hello-chart.fullname" . }} labels: {{- include "hello-chart.labels" . | nindent 4 }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app: {{ .Values.labels.app }} template: metadata: labels: app: {{ .Values.labels.app }} {{- include "hello-chart.labels" . | nindent 8 }} spec: containers: - name: app image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - containerPort: 80 
Enter fullscreen mode Exit fullscreen mode

hello-chart/templates/service.yaml:

apiVersion: v1 kind: Service metadata: name: {{ include "hello-chart.fullname" . }} labels: {{- include "hello-chart.labels" . | nindent 4 }} spec: type: {{ .Values.service.type }} selector: app: {{ .Values.labels.app }} ports: - name: http port: {{ .Values.service.port }} targetPort: 80 
Enter fullscreen mode Exit fullscreen mode

Render & compare with your raw YAML:

helm template hello ./hello-chart > rendered.yaml diff -u k8s/hello.yaml rendered.yaml || true 
Enter fullscreen mode Exit fullscreen mode

Install and test:

helm install hello ./hello-chart -n dev --create-namespace kubectl get all -l app=hello kubectl port-forward svc/hello 8080:80 
Enter fullscreen mode Exit fullscreen mode

Upgrade and rollback:

# Bump replicas via values helm upgrade hello ./hello-chart --set replicaCount=3 helm history hello helm rollback hello 1 # back to first revision 
Enter fullscreen mode Exit fullscreen mode

Cheatsheet Table (Top 12)

Command What it does
kubectl config get-contexts List kubeconfig contexts; know where you’re pointed.
kubectl config set-context --current --namespace=dev Set default namespace for current context.
kubectl get pods -o wide Show Pods with node/IP; quick health snapshot.
kubectl describe pod/<name> Deep dive into events, containers, reasons for failures.
kubectl get events --sort-by=.lastTimestamp Recent cluster events for fast debugging.
kubectl apply -f file.yaml Declaratively create/update resources.
kubectl rollout status deploy/<name> Watch rollout until success/fail.
kubectl logs deploy/<name> -f Stream app logs from all Pods in the Deployment.
kubectl port-forward svc/<name> 8080:80 Access ClusterIP services from localhost.
helm template <rel> <chart> Render manifests locally (no cluster changes).
helm install <rel> <chart> -f values.yaml Install a chart as a named release.
helm upgrade --install <rel> <chart> Idempotent deploy; create or upgrade in one.

Pitfalls & Recovery

  • ImagePullBackOff / ErrImagePull: Repository or tag wrong, or no registry creds.
    Fix: kubectl describe pod, verify image:; try docker pull locally; add imagePullSecret if private.

  • Pods stuck Pending: No schedulable nodes, resource requests too high, or PVC issues.
    Fix: kubectl describe pod; check kubectl get nodes; reduce resources.requests; with minikube, minikube addons enable storage.

  • Service not routing: selector doesn’t match Pod labels or targetPort mismatch.
    Fix: Compare spec.selector to pod.metadata.labels; align targetPort with containerPort.

  • Wrong context/namespace: Resources “missing.”
    Fix: kubectl config current-context; kubectl get ns; set proper namespace.

  • Helm upgrade fails (immutable fields): Some fields (e.g., spec.clusterIP) can’t change in-place.
    Fix: helm diff to see changes; for Services, preserve clusterIP; otherwise helm uninstall and re-install.

  • RBAC forbidden: In restricted clusters, applies fail.
    Fix: Ask for right Role/RoleBinding; test with kubectl auth can-i.

  • Tabs/indentation in YAML: Parsing errors or ignored fields.
    Fix: Convert tabs to spaces; validate with kubectl apply --dry-run=client -f.


Quick Bash (≥4) Scriptlets — Point‑wise Explanation

Below is a line‑by‑line breakdown of the helper script that creates or deletes a local kind cluster and sets a default namespace.

#!/usr/bin/env bash set -euo pipefail CLUSTER=${1:-k90} case "${2:-up}" in up) kind create cluster --name "$CLUSTER" kubectl config set-context --current --namespace=dev ;; down) kind delete cluster --name "$CLUSTER" ;; esac 
Enter fullscreen mode Exit fullscreen mode

What each line does

  1. #!/usr/bin/env bash
    Shebang. Asks the OS to execute this file with the first bash found in your PATH (portable across systems).

  2. set -euo pipefail
    Enables strict mode:

  • -e: exit immediately if any command exits with non‑zero status.
  • -u: error if using an unset variable (catch typos/assumptions).
  • -o pipefail: a pipeline fails if any command fails (not just the last).
  1. CLUSTER=${1:-k90}
    Positional argument \$1 is the cluster name; if omitted, default to k90.
    Examples: ./cluster.sh ⇒ name k90; ./cluster.sh demo ⇒ name demo.

  2. case "${2:-up}" in
    Dispatches on the action provided in \$2; defaults to up if not given.
    Usage examples:

  • ./cluster.shup (default)
  • ./cluster.sh demoup on cluster demo
  • ./cluster.sh demo downdown on cluster demo
  1. up) block
  • kind create cluster --name "$CLUSTER" creates a Docker‑backed Kubernetes cluster named CLUSTER.
  • kubectl config set-context --current --namespace=dev sets the default namespace for the current kubeconfig context to dev, so you don’t need -n dev for every command.
  1. down) block
  • kind delete cluster --name "$CLUSTER" removes the cluster and its Docker containers cleanly.
  1. ;; and esac ;; ends each case arm; esac closes the case statement.

Why this script is useful

  • Idempotent lifecycle: One command to bring the cluster up or tear it down.
  • Safer defaults: Strict mode prevents partial/hidden failures.
  • Less typing: Sets your working namespace so kubectl get pods “just works.”
  • Portable: env shebang finds the right bash; easily adapted for zsh.

Common tweaks you might add

  • Wait for node readiness:
 kubectl wait --for=condition=Ready nodes --all --timeout=120s 
Enter fullscreen mode Exit fullscreen mode
  • Install an ingress addon (minikube):
 minikube addons enable ingress 
Enter fullscreen mode Exit fullscreen mode
  • Switch context safely (guard rails):
 kubectl config current-context | grep -q kind- || { echo "Refusing to run outside a kind context" >&2; exit 1; } 
Enter fullscreen mode Exit fullscreen mode
  • Parameterize namespace:
 NS=${NS:-dev} kubectl config set-context --current --namespace="$NS" 
Enter fullscreen mode Exit fullscreen mode

macOS / Linux / Windows notes

  • macOS: If /bin/bash is 3.2, run with zsh (#!/bin/zsh) or brew install bash and use /usr/local/bin/bash (or /opt/homebrew/bin/bash on Apple Silicon).
  • Linux: Ensure your user can access Docker without sudo (usermod -aG docker $USER, then re‑login).
  • Windows: Run the script from WSL2 (Ubuntu) for the best experience with kind/minikube; ensure Docker Desktop has the WSL2 backend enabled.

Quick usage recap

# Bring up default cluster (k90) and set namespace dev ./cluster.sh # Bring up a named cluster ./cluster.sh demo up # Tear it down ./cluster.sh demo down 
Enter fullscreen mode Exit fullscreen mode

You’re ready. Save this page, keep the YAML/Helm snippets handy, and start iterating.


Wrap-up & Next Steps

You now have:

  • A cluster you can spin up/down quickly.
  • A clean Deployment + Service in raw YAML.
  • A minimal Helm chart with values and releases.
  • A mental model to reason about controllers and desired state.

Post 1 (coming up):

  • Ingress vs. NodePort vs. LoadBalancer with local ingress addons.
  • Rolling updates & health probes (readiness/liveness/startup).
  • Config & Secrets (env vars, mounted files, externalized values).
  • Resource requests/limits & HPA basics.
  • Helm strategies: values layering, env directories, helmfile preview.

Top comments (0)