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)