Beginner

Kubernetes for Developers: Core Concepts

Kubernetes from a developer’s view — pods, deployments, services, storage, ConfigMaps and Secrets.

Table of Contents

  1. Course Overview
  2. Kubernetes from a Developer’s Perspective
  3. Pods
  4. Deployments
  5. Services
  6. Storage
  7. ConfigMaps and Secrets
  8. Putting It All Together
  9. Reference Tables
  10. Final Summary

1. Course Overview

This course covers the core concepts of Kubernetes from a developer’s perspective. It does not cover cluster administration concepts, but focuses on what every developer needs to know to:

  • Understand Kubernetes architecture
  • Create and manage Pods, Deployments, and Services
  • Manage configuration with ConfigMaps and Secrets
  • Understand Storage options
  • Deploy a complete application in a Kubernetes cluster

2. Kubernetes from a Developer’s Perspective

What is Kubernetes?

Kubernetes (abbreviated K8s) is an open-source system for automating deployment, scaling, and management of containerized applications. It acts like a GPS: you tell it the desired state (e.g.: 5 replicas), and it automatically navigates to that state from the current state.

Analogy: If Docker Compose manages containers locally, Kubernetes orchestrates them in production at scale with auto-healing, automatic scaling, and zero-downtime deployments.

Big Picture Architecture

graph TB
    subgraph "Control Plane (Master Node)"
        API["API Server"]
        CM["Controller Manager"]
        SCHED["Scheduler"]
        ETCD["etcd (State Store)"]
    end

    subgraph "Worker Node 1"
        K1["kubelet"]
        P1["Pod A\n(Container)"]
        P2["Pod B\n(Container)"]
        KP1["kube-proxy"]
    end

    subgraph "Worker Node 2"
        K2["kubelet"]
        P3["Pod C\n(Container)"]
        KP2["kube-proxy"]
    end

    DEV["Developer\nkubectl"] -->|"kubectl apply"| API
    API --> CM
    API --> SCHED
    API --> ETCD
    CM --> K1
    CM --> K2
    SCHED --> K1
    SCHED --> K2
    K1 --> P1
    K1 --> P2
    K2 --> P3

Key Components:

ComponentRole
API ServerSingle entry point for all operations (REST API)
Controller ManagerMonitors state and takes corrective actions
SchedulerDecides which node to place a new Pod on
etcdKey-value database storing all cluster state
kubeletAgent on each node, manages local Pods
kube-proxyManages network rules on each node

Benefits and Developer Use Cases

Why learn Kubernetes as a developer?

  1. Local production emulation — Test your app in an environment identical to production
  2. Zero-downtime deployments — Deploy without service interruption
  3. Auto-scaling — Kubernetes automatically manages the number of replicas
  4. Self-healing — If a Pod goes down, it is automatically recreated
  5. CI/CD pipeline — Native integration with DevOps tools
  6. Consistent environments — “It works on my machine” resolved for good

Running Kubernetes Locally

OptionDescriptionAdvantages
Docker DesktopSingle checkbox in settingsSimplest, ideal for beginners
MinikubeLightweight VM simulating a clusterFlexible, supports multiple drivers
kind (Kubernetes in Docker)Cluster in Docker containersAllows scaling worker nodes
kubeadmFull installationFor administrators, out of dev scope

Recommendation: Docker Desktop is the simplest choice to start. One master node and one worker node, sufficient for all concepts in this course.

Getting Started with kubectl

kubectl is the CLI tool for interacting with the Kubernetes cluster via the API Server.

# Check version
kubectl version

# Cluster info (DNS, etc.)
kubectl cluster-info

# List all resources
kubectl get all

# Quickly launch a Pod (imperative approach)
kubectl run web-server --image=nginx:alpine

# List Pods
kubectl get pods

# Port-forward to access a Pod from outside
kubectl port-forward pod/web-server 8080:80

# Handy alias (configure in your shell)
# alias k=kubectl

3. Pods

Pod Core Concepts

A Pod is the smallest deployable unit in Kubernetes. It is the execution environment for one or more containers.

Key points:

  • A Pod = one or more containers that share: IP, memory, volumes
  • In general: one container per Pod (best practice)
  • A Pod has a cluster IP address (internal to the cluster only)
  • Pods are ephemeral — they die and are replaced, never resurrected
  • Scaling is done horizontally (multiple identical Pods)
graph LR
    subgraph "Pod"
        C1["Container A\n(main app)"]
        C2["Container B\n(sidecar)"]
        IP["IP Address\nshared"]
        VOL["Volume\nshared"]
    end
    C1 <--> VOL
    C2 <--> VOL
    C1 <--> IP
    C2 <--> IP

Creating a Pod

Imperative approach (kubectl run):

# Create a Pod quickly
kubectl run web-server --image=nginx:alpine

# Verify
kubectl get pods

# Access from outside (port-forward)
kubectl port-forward pod/web-server 8080:80

# Delete
kubectl delete pod web-server

List and inspect:

kubectl get pods
kubectl get pods -o wide          # Display IPs and nodes
kubectl describe pod web-server   # Full details + events
kubectl get pod web-server -o yaml  # Full YAML output

YAML Fundamentals

YAML (YAML Ain’t Markup Language) is the declarative language used to define all Kubernetes resources. Indentation with spaces (never tabs) determines the structure.

# Map (key: value)
firstName: dan

# Complex map
address:
  street: 123 Main St
  city: Montreal

# List (sequence)
colors:
  - red
  - blue
  - green

# List of maps
containers:
  - name: nginx
    image: nginx:alpine
  - name: redis
    image: redis:alpine

Structure of a Kubernetes manifest:

FieldDescription
apiVersionAPI version (e.g. v1, apps/v1)
kindResource type (Pod, Deployment, Service…)
metadataName, labels, annotations
specDesired specification of the resource

Defining a Pod with YAML

# nginx.pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: web-server
  labels:
    app: web-server
    rel: stable
spec:
  containers:
    - name: web-server
      image: nginx:alpine
      ports:
        - containerPort: 80
      resources:
        limits:
          memory: "128Mi"
          cpu: "200m"
        requests:
          memory: "64Mi"
          cpu: "100m"

Commands with YAML:

# Create (with saved config for future apply)
kubectl create -f nginx.pod.yml --save-config

# Apply (create or update)
kubectl apply -f nginx.pod.yml

# Delete
kubectl delete -f nginx.pod.yml
# or
kubectl delete pod web-server

# Run a command in a Pod
kubectl exec web-server -it -- sh

# Get logs
kubectl logs web-server
kubectl logs web-server -f   # Streaming

Health Probes

Probes allow Kubernetes to know the health status of a container. This is a critical aspect for the developer as only they know their application’s behavior.

flowchart TD
    START["Pod started"] --> READY["Readiness Probe\n(is the Pod ready\nto receive traffic?)"]
    READY -->|"Success"| TRAFFIC["Traffic sent\nto the Pod"]
    READY -->|"Failure"| WAIT["Pod excluded from\nload balancing"]
    TRAFFIC --> LIVE["Liveness Probe\n(is the Pod\nstill alive?)"]
    LIVE -->|"Success"| TRAFFIC
    LIVE -->|"Failure"| RESTART["Container restarted\nautomatically"]
    RESTART --> READY

Probe types:

TypeMechanismUsage
httpGetHTTP GET request to an endpointWeb apps, REST APIs
tcpSocketTCP connection on a portDatabases, TCP services
execExecute a command in the containerCustom health scripts

Complete example with probes:

apiVersion: v1
kind: Pod
metadata:
  name: web-server
spec:
  containers:
    - name: web-server
      image: nginx:alpine
      ports:
        - containerPort: 80
      livenessProbe:
        httpGet:
          path: /index.html
          port: 80
        initialDelaySeconds: 15   # Wait before 1st check
        periodSeconds: 10          # Check frequency
        timeoutSeconds: 2          # Timeout per check
        failureThreshold: 3        # Number of failures before restart
      readinessProbe:
        httpGet:
          path: /index.html
          port: 80
        initialDelaySeconds: 3
        periodSeconds: 5

Important Parameters:

ParameterDescriptionDefault
initialDelaySecondsDelay before the first probe (startup time)0
periodSecondsCheck frequency10
timeoutSecondsRequest timeout1
failureThresholdNumber of consecutive failures before action3
successThresholdNumber of successes to become “healthy” again1

4. Deployments

Deployment Core Concepts

A Deployment is a high-level resource that manages Pods via ReplicaSets. It is the recommended method for deploying applications.

graph TB
    DEP["Deployment"] -->|"manages"| RS["ReplicaSet"]
    RS -->|"ensures N replicas"| P1["Pod 1"]
    RS -->|"ensures N replicas"| P2["Pod 2"]
    RS -->|"ensures N replicas"| P3["Pod 3"]

    style DEP fill:#4a90d9,color:#fff
    style RS fill:#7bc67e,color:#fff
    style P1 fill:#f5a623,color:#fff
    style P2 fill:#f5a623,color:#fff
    style P3 fill:#f5a623,color:#fff

What a Deployment provides:

  • Self-healing: if a Pod dies, ReplicaSet automatically creates a new one
  • Scaling: easily increase/decrease the number of replicas
  • Rolling updates: update without downtime
  • Rollback: return to a previous version

Creating a Deployment

# nginx.deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
  labels:
    app: web-server
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web-server          # Must match template label
  minReadySeconds: 10          # Wait 10s after startup before routing traffic
  template:
    metadata:
      labels:
        app: web-server        # Pod label (must match selector)
    spec:
      containers:
        - name: web-server
          image: nginx:alpine
          ports:
            - containerPort: 80
          resources:
            limits:
              memory: "128Mi"
              cpu: "200m"
            requests:
              memory: "64Mi"
              cpu: "100m"
          livenessProbe:
            httpGet:
              path: /index.html
              port: 80
            initialDelaySeconds: 15
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /index.html
              port: 80
            initialDelaySeconds: 3
            periodSeconds: 5

Deployment Commands:

# Create or update
kubectl apply -f nginx.deployment.yml

# List deployments
kubectl get deployments
kubectl get deployments --show-labels
kubectl get deployments -l app=web-server   # Filter by label

# Scale
kubectl scale deployment web-server --replicas=5

# View rollout
kubectl rollout status deployment web-server

# Revision history
kubectl rollout history deployment web-server

# Rollback
kubectl rollout undo deployment web-server

# Delete
kubectl delete deployment web-server
# or
kubectl delete -f nginx.deployment.yml

Deployment Options and Zero Downtime

Rolling Update (default): Kubernetes progressively replaces old Pods with new ones, without service interruption.

sequenceDiagram
    participant K as Kubernetes
    participant OldPods as Pods v1.0 (x3)
    participant NewPods as Pods v2.0

    K->>NewPods: Create Pod v2.0 #1
    NewPods-->>K: Ready
    K->>OldPods: Delete Pod v1.0 #1
    K->>NewPods: Create Pod v2.0 #2
    NewPods-->>K: Ready
    K->>OldPods: Delete Pod v1.0 #2
    K->>NewPods: Create Pod v2.0 #3
    NewPods-->>K: Ready
    K->>OldPods: Delete Pod v1.0 #3
    Note over K,NewPods: Migration complete, zero downtime

Available deployment strategies:

StrategyDescriptionUse Case
Rolling UpdateProgressive replacement (default)Most cases
Blue-GreenTwo parallel environments, instant switchCritical regression testing
CanarySmall % of traffic to new versionProgressive validation
RecreateFull stop then restartDB schema migrations

Updating the image:

# Update the container image in a deployment
kubectl set image deployment/web-server web-server=nginx:1.15.9-alpine

# Or modify the YAML and apply
kubectl apply -f nginx.deployment.yml

5. Services

Service Core Concepts

Pods are ephemeral and their IP addresses change constantly. A Service provides a stable IP address and a fixed DNS name to access a group of Pods.

graph LR
    CLIENT["Client\n(external or internal)"]
    SVC["Service\nStable IP + DNS"]
    P1["Pod 1\n10.1.0.5"]
    P2["Pod 2\n10.1.0.6"]
    P3["Pod 3\n10.1.0.7"]

    CLIENT --> SVC
    SVC -->|"load balance"| P1
    SVC -->|"load balance"| P2
    SVC -->|"load balance"| P3

    style SVC fill:#4a90d9,color:#fff

Service Role:

  • Abstraction of Pod IPs (which change)
  • Load balancing between replicas
  • Service discovery via DNS (e.g. my-service.default.svc.cluster.local)
  • Association to Pods via labels/selectors

Request flow from external to a Pod:

flowchart LR
    EXT["Internet\n(HTTP request)"] --> LB["LoadBalancer Service\nExternal IP"]
    LB --> NP["NodePort\n(port 30000-32767)"]
    NP --> SVC["ClusterIP Service\n(internal)"]
    SVC --> P1["Pod 1"]
    SVC --> P2["Pod 2"]

Service Types

graph TB
    subgraph "ClusterIP (default)"
        CL_SVC["Service\nClusterIP"] -->|"internal only"| POD_CL["Pods"]
    end
    subgraph "NodePort"
        NP_NODE["Node IP:30080"] --> NP_SVC["Service\nNodePort"] --> POD_NP["Pods"]
    end
    subgraph "LoadBalancer"
        LB_EXT["External IP\n(cloud provider)"] --> LB_SVC["Service\nLoadBalancer"] --> POD_LB["Pods"]
    end
    subgraph "ExternalName"
        EN_SVC["Service\nExternalName"] -->|"DNS CNAME"| EXT_DNS["api.external.com"]
    end

Creating a Service with YAML

ClusterIP (internal communication):

# nginx-clusterip.service.yml
apiVersion: v1
kind: Service
metadata:
  name: nginx-clusterip
  labels:
    app: nginx
spec:
  type: ClusterIP        # Optional, this is the default
  selector:
    app: web-server      # Selects Pods with this label
  ports:
    - port: 80           # Service port (accessible within the cluster)
      targetPort: 80     # Container port in the Pod

NodePort (access from outside the cluster):

# nginx-nodeport.service.yml
apiVersion: v1
kind: Service
metadata:
  name: nginx-nodeport
spec:
  type: NodePort
  selector:
    app: web-server
  ports:
    - port: 80
      targetPort: 80
      nodePort: 31000    # External port (30000-32767), optional (auto if omitted)

LoadBalancer (cloud provider):

# nginx-loadbalancer.service.yml
apiVersion: v1
kind: Service
metadata:
  name: nginx-loadbalancer
spec:
  type: LoadBalancer
  selector:
    app: web-server
  ports:
    - port: 80
      targetPort: 80

Service Commands:

# Apply
kubectl apply -f nginx-clusterip.service.yml

# List
kubectl get services
kubectl get svc

# Details
kubectl describe service nginx-clusterip

# Test communication between Pods
kubectl exec <pod-name> -- curl http://nginx-clusterip

# Port-forward (quick local test)
kubectl port-forward service/nginx-clusterip 8080:80

# Delete
kubectl delete service nginx-clusterip
kubectl delete -f nginx-clusterip.service.yml

6. Storage

Volumes

A Volume is a storage space attached to a Pod. Unlike a container’s filesystem, a volume can survive container restarts.

Common volume types:

TypeScopeUse Case
emptyDirPod lifetimeData sharing between containers in the same Pod
hostPathNodeAccess to the node’s filesystem (security caution)
nfsNetworkShared network storage
configMapClusterInject config as files
secretClusterInject secrets as files
persistentVolumeClaimClusterDecoupled persistent storage

Example with emptyDir (inter-container sharing):

apiVersion: v1
kind: Pod
metadata:
  name: nginx-with-sidecar
spec:
  volumes:
    - name: html             # Volume name
      emptyDir: {}           # Tied to Pod lifecycle

  containers:
    - name: nginx
      image: nginx:alpine
      volumeMounts:
        - name: html
          mountPath: /usr/share/nginx/html   # Mount the volume
          readOnly: true

    - name: updater
      image: alpine
      volumeMounts:
        - name: html
          mountPath: /html                   # Same volume, different path
      command: ["/bin/sh", "-c"]
      args:
        - while true; do
            date > /html/index.html;
            sleep 10;
          done

PersistentVolumes and PersistentVolumeClaims

flowchart LR
    ADMIN["Administrator"] -->|"creates"| PV["PersistentVolume\n(PV)\n— physical storage —"]
    PV -->|"bound by storageClass\nor manually"| PVC["PersistentVolumeClaim\n(PVC)\n— storage request —"]
    PVC -->|"mounted in"| POD["Pod"]
    PV -->|"actual storage"| CLOUD["Cloud Storage\n(Azure File, AWS EBS, GCP PD...)"]

    style PV fill:#7bc67e,color:#fff
    style PVC fill:#4a90d9,color:#fff
    style POD fill:#f5a623,color:#fff

PersistentVolume (defined by admin):

apiVersion: v1
kind: PersistentVolume
metadata:
  name: mongo-pv
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce          # Only one Pod can write
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  hostPath:
    path: /tmp/data/db

PersistentVolumeClaim (used by the developer):

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongo-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: local-storage
  resources:
    requests:
      storage: 1Gi

Using the PVC in a Pod/Deployment:

spec:
  volumes:
    - name: mongo-storage
      persistentVolumeClaim:
        claimName: mongo-pvc     # Reference to the PVC

  containers:
    - name: mongodb
      image: mongo
      volumeMounts:
        - name: mongo-storage
          mountPath: /data/db    # Path in the container

StorageClasses

A StorageClass is a template that enables dynamic provisioning of PersistentVolumes. The administrator creates the StorageClass, developers make PVCs that reference it — the PV is created automatically.

# StorageClass for local static provisioning
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain

7. ConfigMaps and Secrets

ConfigMaps — Concepts and Creation

A ConfigMap stores configuration data as key-value pairs and injects them into Pods (environment variables or mounted files).

Advantage: Configuration is decoupled from the Docker image → the same image can run in dev, staging, and prod with different configs.

3 ways to create a ConfigMap:

# 1. Via YAML manifest
kubectl apply -f app-settings.configmap.yml

# 2. From a config file
kubectl create configmap app-settings --from-file=game.config

# 3. From an environment file (.env)
kubectl create configmap app-settings --from-env-file=settings.env

# 4. Literal values
kubectl create configmap app-settings \
  --from-literal=enemies=aliens \
  --from-literal=lives=3

YAML Manifest:

# app-settings.configmap.yml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-settings
data:
  enemies: aliens
  lives: "3"
  enemies.cheat: "true"
  enemies.cheat.level: noGodMode
  # Full config file as a value
  game.properties: |
    enemies=aliens
    lives=3
    enemies.cheat=true

Using a ConfigMap in a Pod

graph LR
    CM["ConfigMap\napp-settings\n• enemies=aliens\n• lives=3"] -->|"envFrom"| ENV["Environment variables\nin the container\nENEMIES=aliens\nLIVES=3"]
    CM -->|"volume mount"| FILE["Mounted files\n/etc/config/enemies\n/etc/config/lives"]

    style CM fill:#4a90d9,color:#fff

Injection via environment variables:

# In the container spec
spec:
  containers:
    - name: app
      image: my-app:latest
      env:
        # Inject a specific key
        - name: ENEMIES
          valueFrom:
            configMapKeyRef:
              name: app-settings      # ConfigMap name
              key: enemies            # Key in the ConfigMap

      # Or inject ALL keys from the ConfigMap
      envFrom:
        - configMapRef:
            name: app-settings

Injection via volume (files):

spec:
  volumes:
    - name: config-volume
      configMap:
        name: app-settings           # ConfigMap name

  containers:
    - name: app
      image: my-app:latest
      volumeMounts:
        - name: config-volume
          mountPath: /etc/config     # Each key becomes a file
          readOnly: true

Reading a ConfigMap file in Node.js:

const fs = require('fs');
// The key "enemies.cheat.level" becomes the file /etc/config/enemies.cheat.level
const data = fs.readFileSync('/etc/config/enemies.cheat.level', 'utf8');
console.log(data); // "noGodMode"

// Environment variable
console.log(process.env.ENEMIES); // "aliens"

Secrets — Concepts and Creation

A Secret stores sensitive data (passwords, tokens, certificates) encoded in Base64. It is similar to a ConfigMap but with additional protection mechanisms.

⚠️ Important: Base64 is not encryption. Secrets must be protected by RBAC access control. Never commit them in source control.

Best practices:

  • Enable encryption at rest in etcd (administrator)
  • Use RBAC to limit access
  • Consider solutions like HashiCorp Vault for production
  • Never put Secrets in source control

Create a Secret:

# Literal values
kubectl create secret generic db-passwords \
  --from-literal=db-password=my_database_pass \
  --from-literal=db-root-password=root_db_pass

# From files (e.g. SSH keys)
kubectl create secret generic ssh-keys \
  --from-file=ssh-privatekey=~/.ssh/id_rsa \
  --from-file=ssh-publickey=~/.ssh/id_rsa.pub

# TLS certificate
kubectl create secret tls my-tls-secret \
  --cert=path/to/cert.pem \
  --key=path/to/key.pem

YAML Manifest (Base64 values):

apiVersion: v1
kind: Secret
metadata:
  name: db-passwords
type: Opaque
data:
  # echo -n "my_database_pass" | base64
  db-password: bXlfZGF0YWJhc2VfcGFzcw==
  db-root-password: cm9vdF9kYl9wYXNz

Using a Secret in a Pod

graph LR
    SEC["Secret\ndb-passwords\n• db-password (Base64)\n• db-root-password (Base64)"] -->|"secretKeyRef"| ENV["Env variable\nDATABASE_PASSWORD=\n(automatically decoded)"]
    SEC -->|"volume mount"| FILE["Mounted file\n/etc/secrets/db-password\n(automatically decoded)"]

    style SEC fill:#e74c3c,color:#fff

Via environment variables:

spec:
  containers:
    - name: app
      image: my-app:latest
      env:
        - name: DATABASE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-passwords        # Secret name
              key: db-password          # Key in the Secret

      # Or all keys from the Secret
      envFrom:
        - secretRef:
            name: db-passwords

Via volume (files):

spec:
  volumes:
    - name: secrets-volume
      secret:
        secretName: db-passwords

  containers:
    - name: app
      image: my-app:latest
      volumeMounts:
        - name: secrets-volume
          mountPath: /etc/secrets
          readOnly: true

8. Putting It All Together

Demo Application Architecture

The demo application combines all concepts: nginx (reverse proxy), Node.js (API), MongoDB (database), Redis (cache).

graph TB
    subgraph "Kubernetes Cluster"
        subgraph "Ingress / Service LoadBalancer"
            LB["Service: LoadBalancer\nPort 80"]
        end

        subgraph "Frontend Layer"
            NGINX_SVC["Service: ClusterIP\nnginx-service"]
            NGINX_POD["Deployment: nginx\nReverse Proxy\nPort 80"]
        end

        subgraph "API Layer"
            NODE_SVC["Service: ClusterIP\nnode-service"]
            NODE_POD["Deployment: node-app\nNode.js API\nPort 3000"]
        end

        subgraph "Data Layer"
            MONGO_SVC["Service: ClusterIP\nmongo-service"]
            MONGO_POD["StatefulSet: mongodb\nPort 27017"]
            REDIS_SVC["Service: ClusterIP\nredis-service"]
            REDIS_POD["Deployment: redis\nPort 6379"]
        end

        subgraph "Configuration"
            CM["ConfigMap\napp-settings"]
            SEC["Secret\ndb-passwords"]
            PV["PersistentVolume\nmongo-pv"]
            PVC["PersistentVolumeClaim\nmongo-pvc"]
        end

        LB --> NGINX_SVC --> NGINX_POD
        NGINX_POD -->|"proxy /api"| NODE_SVC --> NODE_POD
        NODE_POD --> MONGO_SVC --> MONGO_POD
        NODE_POD --> REDIS_SVC --> REDIS_POD
        CM -->|"envFrom"| NODE_POD
        CM -->|"envFrom"| MONGO_POD
        SEC -->|"secretKeyRef"| MONGO_POD
        PVC --> MONGO_POD
        PV --> PVC
    end

    USER["User\n(Browser)"] --> LB

MongoDB manifest with ConfigMap + Secret + PVC:

# mongo.deployment.yml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: mongo-settings
data:
  MONGODB_DBNAME: codeWithDan
  MONGODB_ROLE: readWrite
  MONGO_INITDB_ROOT_USERNAME: admin

---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mongo-pv
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: local-storage
  hostPath:
    path: /tmp/data/db

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongo-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: local-storage
  resources:
    requests:
      storage: 1Gi

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mongodb
spec:
  selector:
    matchLabels:
      app: mongodb
  serviceName: "mongodb"
  replicas: 1
  template:
    metadata:
      labels:
        app: mongodb
    spec:
      volumes:
        - name: mongo-storage
          persistentVolumeClaim:
            claimName: mongo-pvc
      containers:
        - name: mongodb
          image: mongo
          ports:
            - containerPort: 27017
          envFrom:
            - configMapRef:
                name: mongo-settings
          env:
            - name: MONGO_INITDB_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-passwords
                  key: db-password
          volumeMounts:
            - name: mongo-storage
              mountPath: /data/db

---
apiVersion: v1
kind: Service
metadata:
  name: mongo-service
spec:
  selector:
    app: mongodb
  ports:
    - port: 27017
      targetPort: 27017

Deploy all manifests in one command:

# Deploy all files in a folder
kubectl apply -f k8s/

# Or specific files
kubectl apply -f mongo.deployment.yml -f node.deployment.yml -f nginx.deployment.yml

# Verify everything is running
kubectl get all

Troubleshooting Techniques

# ─── Logs ───────────────────────────────────────────────────────────────
kubectl logs <pod-name>
kubectl logs <pod-name> -c <container-name>   # Specific container
kubectl logs <pod-name> -p                    # Previous Pod (crashed)
kubectl logs <pod-name> -f                    # Real-time streaming

# ─── Describe (events + state) ──────────────────────────────────────────
kubectl describe pod <pod-name>
kubectl describe deployment <deployment-name>
kubectl describe service <service-name>

# ─── YAML output (actual cluster state) ─────────────────────────────────
kubectl get pod <pod-name> -o yaml
kubectl get deployment <deployment-name> -o yaml

# ─── Run commands in a Pod ───────────────────────────────────────────────
kubectl exec <pod-name> -it -- sh             # Interactive shell
kubectl exec <pod-name> -- curl http://service-name  # Test connectivity

# ─── Quick access for testing ────────────────────────────────────────────
kubectl port-forward pod/<pod-name> 8080:80
kubectl port-forward deployment/<name> 8080:80
kubectl port-forward service/<name> 8080:80

Common errors and solutions:

ErrorProbable CauseSolution
CrashLoopBackOffContainer crashes on startupkubectl logs <pod> -p to see the error
CreateContainerConfigErrorMissing Secret or ConfigMapVerify the Secret/CM exists
PendingInsufficient resources, or non-schedulable nodekubectl describe pod → Events
ImagePullBackOffDocker image not foundCheck image name/tag and credentials
OOMKilledInsufficient memoryIncrease resources.limits.memory

9. Reference Tables

Essential kubectl Commands

CommandDescription
kubectl versionkubectl and cluster version
kubectl cluster-infoCluster information
kubectl get allAll resources in the current namespace
kubectl get podsList Pods
kubectl get pods -o widePods with IP and node
kubectl get pods --watchWatch changes in real-time
kubectl get deploymentsList Deployments
kubectl get servicesList Services
kubectl get configmapsList ConfigMaps
kubectl get secretsList Secrets
kubectl get pvList PersistentVolumes
kubectl get pvcList PersistentVolumeClaims
kubectl apply -f <file.yml>Create or update a resource
kubectl create -f <file.yml>Create a resource (error if exists)
kubectl delete -f <file.yml>Delete resources from a manifest
kubectl delete pod <name>Delete a specific Pod
kubectl describe pod <name>Details + events of a Pod
kubectl logs <pod>Pod logs
kubectl logs <pod> -fLog streaming
kubectl exec <pod> -it -- shInteractive shell in a Pod
kubectl port-forward <pod> 8080:80Expose a port locally
kubectl scale deployment <name> --replicas=5Scale a Deployment
kubectl rollout status deployment/<name>Rolling update status
kubectl rollout undo deployment/<name>Rollback a Deployment
kubectl set image deployment/<name> <container>=<image>Update the image

Service Types

TypeAccessible fromIP providedUse Case
ClusterIPWithin the cluster onlyStable internal IPInter-Pod communication
NodePortNode IP + static port (30000-32767)Node IPTesting, dev, access without LB
LoadBalancerAnywhere (via public IP)External IP (cloud)Production applications
ExternalNameWithin the clusterDNS alias (CNAME)Proxy to external service

ConfigMap and Secret Sources

Creation MethodCommandResult in Kubernetes
YAML manifestkubectl apply -f cm.ymlExplicit keys in data:
Config file--from-file=game.configFilename = key, content = value
.env file--from-env-file=.envEach KEY=VALUE line → separate key
Literal value--from-literal=key=valueSimple key
Pod Injection MethodYAML MechanismAccess in Code
Env var (specific key)valueFrom.configMapKeyRefprocess.env.KEY
All env varsenvFrom.configMapRefprocess.env.KEY
Mounted file (volume)volumes.configMap + volumeMountsfs.readFileSync('/etc/config/key')

10. Final Summary

mindmap
  root((Kubernetes\nfor Developers))
    Pods
      Smallest unit
      One container per Pod
      Health Probes
        Liveness Probe
        Readiness Probe
      kubectl run / apply
    Deployments
      Wraps ReplicaSet
      Self-healing
      Zero-downtime rolling updates
      Scale replicas
      Rollback
    Services
      Pod IP abstraction
      ClusterIP - internal
      NodePort - external access
      LoadBalancer - cloud
      ExternalName - DNS alias
    Storage
      emptyDir - ephemeral
      hostPath - node filesystem
      PersistentVolume - PV
      PersistentVolumeClaim - PVC
      StorageClass - dynamic template
    ConfigMaps
      Configuration data
      Env var injection
      File volume injection
    Secrets
      Sensitive data Base64
      RBAC required
      Env var injection
      File volume injection

Kubernetes Resource Hierarchy:

Cluster
└── Namespace (default, kube-system...)
    ├── Deployment
    │   └── ReplicaSet
    │       └── Pod
    │           └── Container(s)
    ├── Service (ClusterIP / NodePort / LoadBalancer)
    ├── ConfigMap
    ├── Secret
    ├── PersistentVolume (cluster-wide)
    └── PersistentVolumeClaim (namespace)

Typical Developer Workflow:

  1. Write the application → Dockerfile → docker build
  2. Create YAML manifests: Deployment + Service + ConfigMap + Secret
  3. Apply: kubectl apply -f k8s/
  4. Verify: kubectl get all, kubectl describe pod <name>
  5. Test locally: kubectl port-forward service/<name> 8080:80
  6. Debug if needed: kubectl logs <pod>, kubectl exec <pod> -it -- sh
  7. Update: modify the YAML or kubectl set imagekubectl apply
  8. Rollback if issues: kubectl rollout undo deployment/<name>

Course completed — Kubernetes for Developers: Core Concepts | Dan Wahlin


Search Terms

kubernetes · developers · core · concepts · containers · pod · service · deployment · yaml · architecture · configmap · configmaps · creation · developer · kubectl · secret · secrets · types

Interested in this course?

Contact us to book it or get a custom training plan for your team.