Table of Contents
- Course Overview
- Kubernetes from a Developer’s Perspective
- Pods
- Deployments
- Services
- Storage
- ConfigMaps and Secrets
- Putting It All Together
- Reference Tables
- 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:
| Component | Role |
|---|---|
| API Server | Single entry point for all operations (REST API) |
| Controller Manager | Monitors state and takes corrective actions |
| Scheduler | Decides which node to place a new Pod on |
| etcd | Key-value database storing all cluster state |
| kubelet | Agent on each node, manages local Pods |
| kube-proxy | Manages network rules on each node |
Benefits and Developer Use Cases
Why learn Kubernetes as a developer?
- Local production emulation — Test your app in an environment identical to production
- Zero-downtime deployments — Deploy without service interruption
- Auto-scaling — Kubernetes automatically manages the number of replicas
- Self-healing — If a Pod goes down, it is automatically recreated
- CI/CD pipeline — Native integration with DevOps tools
- Consistent environments — “It works on my machine” resolved for good
Running Kubernetes Locally
| Option | Description | Advantages |
|---|---|---|
| Docker Desktop | Single checkbox in settings | Simplest, ideal for beginners |
| Minikube | Lightweight VM simulating a cluster | Flexible, supports multiple drivers |
| kind (Kubernetes in Docker) | Cluster in Docker containers | Allows scaling worker nodes |
| kubeadm | Full installation | For 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:
| Field | Description |
|---|---|
apiVersion | API version (e.g. v1, apps/v1) |
kind | Resource type (Pod, Deployment, Service…) |
metadata | Name, labels, annotations |
spec | Desired 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:
| Type | Mechanism | Usage |
|---|---|---|
httpGet | HTTP GET request to an endpoint | Web apps, REST APIs |
tcpSocket | TCP connection on a port | Databases, TCP services |
exec | Execute a command in the container | Custom 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:
| Parameter | Description | Default |
|---|---|---|
initialDelaySeconds | Delay before the first probe (startup time) | 0 |
periodSeconds | Check frequency | 10 |
timeoutSeconds | Request timeout | 1 |
failureThreshold | Number of consecutive failures before action | 3 |
successThreshold | Number of successes to become “healthy” again | 1 |
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:
| Strategy | Description | Use Case |
|---|---|---|
| Rolling Update | Progressive replacement (default) | Most cases |
| Blue-Green | Two parallel environments, instant switch | Critical regression testing |
| Canary | Small % of traffic to new version | Progressive validation |
| Recreate | Full stop then restart | DB 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:
| Type | Scope | Use Case |
|---|---|---|
emptyDir | Pod lifetime | Data sharing between containers in the same Pod |
hostPath | Node | Access to the node’s filesystem (security caution) |
nfs | Network | Shared network storage |
configMap | Cluster | Inject config as files |
secret | Cluster | Inject secrets as files |
persistentVolumeClaim | Cluster | Decoupled 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:
| Error | Probable Cause | Solution |
|---|---|---|
CrashLoopBackOff | Container crashes on startup | kubectl logs <pod> -p to see the error |
CreateContainerConfigError | Missing Secret or ConfigMap | Verify the Secret/CM exists |
Pending | Insufficient resources, or non-schedulable node | kubectl describe pod → Events |
ImagePullBackOff | Docker image not found | Check image name/tag and credentials |
OOMKilled | Insufficient memory | Increase resources.limits.memory |
9. Reference Tables
Essential kubectl Commands
| Command | Description |
|---|---|
kubectl version | kubectl and cluster version |
kubectl cluster-info | Cluster information |
kubectl get all | All resources in the current namespace |
kubectl get pods | List Pods |
kubectl get pods -o wide | Pods with IP and node |
kubectl get pods --watch | Watch changes in real-time |
kubectl get deployments | List Deployments |
kubectl get services | List Services |
kubectl get configmaps | List ConfigMaps |
kubectl get secrets | List Secrets |
kubectl get pv | List PersistentVolumes |
kubectl get pvc | List 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> -f | Log streaming |
kubectl exec <pod> -it -- sh | Interactive shell in a Pod |
kubectl port-forward <pod> 8080:80 | Expose a port locally |
kubectl scale deployment <name> --replicas=5 | Scale 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
| Type | Accessible from | IP provided | Use Case |
|---|---|---|---|
| ClusterIP | Within the cluster only | Stable internal IP | Inter-Pod communication |
| NodePort | Node IP + static port (30000-32767) | Node IP | Testing, dev, access without LB |
| LoadBalancer | Anywhere (via public IP) | External IP (cloud) | Production applications |
| ExternalName | Within the cluster | DNS alias (CNAME) | Proxy to external service |
ConfigMap and Secret Sources
| Creation Method | Command | Result in Kubernetes |
|---|---|---|
| YAML manifest | kubectl apply -f cm.yml | Explicit keys in data: |
| Config file | --from-file=game.config | Filename = key, content = value |
| .env file | --from-env-file=.env | Each KEY=VALUE line → separate key |
| Literal value | --from-literal=key=value | Simple key |
| Pod Injection Method | YAML Mechanism | Access in Code |
|---|---|---|
| Env var (specific key) | valueFrom.configMapKeyRef | process.env.KEY |
| All env vars | envFrom.configMapRef | process.env.KEY |
| Mounted file (volume) | volumes.configMap + volumeMounts | fs.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:
- Write the application → Dockerfile →
docker build - Create YAML manifests: Deployment + Service + ConfigMap + Secret
- Apply:
kubectl apply -f k8s/ - Verify:
kubectl get all,kubectl describe pod <name> - Test locally:
kubectl port-forward service/<name> 8080:80 - Debug if needed:
kubectl logs <pod>,kubectl exec <pod> -it -- sh - Update: modify the YAML or
kubectl set image→kubectl apply - 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