Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🚢 Container Practice

A hands-on repository covering Docker and Kubernetes — built from real practice including all the mistakes, fixes, and lessons learned along the way.

Environment

Component Version
Docker Desktop 29.2.0
minikube v1.38.1
kubectl v1.34.1
Helm 3.x
OS Windows 11 + PowerShell

Starting the cluster

# With NetworkPolicy support (recommended)
minikube start --driver=docker --cni=calico

# Enable addons
minikube addons enable ingress
minikube addons enable metrics-server

Windows tip: use curl.exe instead of curl (which is an alias for Invoke-WebRequest).
Direct cluster IP 192.168.58.2 is not reachable from the host — use kubectl port-forward.
minikube tunnel only works with LoadBalancer services, not NodePort.


Repository Structure

container-practice/
├── build/          # Docker images
├── kubernetes/     # Kubernetes manifests
├── helm/           # Helm + RBAC
├── hpa-demo/       # Horizontal Pod Autoscaler
└── argocd/         # GitOps with ArgoCD
└── eks-wordpress-lab/ # AWS + EKS practice
└── gke-flask-app/     # GCP + GKE practice

build/ — Docker

Source files for a simple Python application and Dockerfiles.

app.py              # Flask application
Dockerfile          # Final multi-stage image
Dockerfile_prev     # Previous version (for comparison)
requirements.txt    # Python dependencies
docker build -t my-app:1.0 .
docker run -p 5000:5000 my-app:1.0

kubernetes/ — Kubernetes Manifests

Apply everything

kubectl create namespace dev
kubectl apply -f kubernetes/ -n dev

Files

File Description Topic
deployment.yml nginx Deployment, 3 replicas, RollingUpdate strategy Foundations
deployment-recreate.yml Deployment with Recreate strategy Workloads
service.yml ClusterIP Service for nginx Foundations
service-nodeport.yml NodePort Service (port 30080) Networking
ingress.yml Ingress: myapp.local/nginx and /app2 Networking
networkpolicy.yaml Allow traffic only from ingress-nginx namespace (requires Calico!) Networking
deployment-app2.yml httpd:2.4 for the second Ingress path Networking
configmap.yml ConfigMap with APP_ENV, DB_HOST, nginx.conf Configuration
secret.yml Secret with DB_PASSWORD and API_KEY (base64) Configuration
deployment-with-config.yml Deployment with env vars from ConfigMap/Secret Configuration
deployment-with-volume.yaml Deployment with volume mount (live reload!) Configuration
postgres-pvc.yaml PersistentVolumeClaim 1Gi for PostgreSQL Storage
postgres-secret.yaml Secret with PostgreSQL password Storage
postgres-deployment.yaml PostgreSQL Deployment with PVC Storage
job.yaml Job: db-migration (one-off task) Workloads
cronjob.yaml CronJob: backup every 2 minutes Workloads
daemonset.yaml DaemonSet log-collector with hostPath Workloads
statefulset.yaml StatefulSet postgres-ss, 2 replicas, headless service Workloads
my-first-app.yml First Pod (nginx) Foundations
my-first-service.yml First Service Foundations
my-config.yml Additional ConfigMap Configuration
my-ingress.yml Additional Ingress Networking
serviceaccount.yaml ServiceAccount for RBAC practice RBAC

⚠️ Demo secrets: the values in secret.yml and postgres-secret.yaml are example/demo credentials for learning only — they are not real and must not be reused. Never commit real secrets to git; use a secrets manager (e.g. AWS Secrets Manager, Sealed Secrets, External Secrets).

Key commands

# Rolling Update
kubectl set image deployment nginx-deployment nginx=nginx:1.27 -n dev
kubectl rollout status deployment nginx-deployment -n dev
kubectl rollout history deployment nginx-deployment -n dev
kubectl rollout undo deployment nginx-deployment -n dev

# Scaling
kubectl scale deployment nginx-deployment --replicas=5 -n dev

# Test Ingress (Windows)
kubectl port-forward -n ingress-nginx service/ingress-nginx-controller 8080:80
curl.exe -H "Host: myapp.local" http://localhost:8080/nginx

# Live ConfigMap update (PowerShell)
$patch = '{"data":{"APP_ENV":"staging"}}'
kubectl patch configmap nginx-config -n dev --type merge -p $patch
# The file updates inside the pod within ~60s — no restart needed!

# StatefulSet DNS
# postgres-ss-0.postgres-headless.dev.svc.cluster.local
# postgres-ss-1.postgres-headless.dev.svc.cluster.local

Note: NetworkPolicy only works with Calico CNI.
With the default bridge CNI, objects are created but rules are not enforced.


helm/ — Helm + RBAC

Helm practice

cd helm/

# Install bitnami nginx
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install my-nginx bitnami/nginx --namespace helm-demo --set service.type=ClusterIP

# Custom chart (in my-app/ directory)
helm install my-release ./my-app --namespace helm-demo
helm upgrade my-release ./my-app --set replicaCount=5
helm rollback my-release 1 -n helm-demo
helm history my-release -n helm-demo

Gotcha: helm create generates extra files (httproute.yaml, hpa.yaml, serviceaccount.yaml).
Delete them or add all required sections to values.yaml — otherwise helm lint will fail.

RBAC files

File Description
serviceaccount.yaml ServiceAccount app-service-account in namespace rbac-demo
role.yaml Role pod-reader: get/list/watch pods, get/list deployments
rolebinding.yaml RoleBinding: SA → Role (scoped to namespace rbac-demo)
pod-with-sa.yaml Pod api-client for testing permissions from inside the cluster
clusterrole-monitoring.yaml ClusterRole monitoring-role: read-only on nodes/pods/services/deployments
kubectl create namespace rbac-demo
kubectl apply -f serviceaccount.yaml
kubectl apply -f role.yaml
kubectl apply -f rolebinding.yaml

# Test permissions
kubectl auth can-i get pods --as=system:serviceaccount:rbac-demo:app-service-account -n rbac-demo
# → yes

kubectl auth can-i delete pods --as=system:serviceaccount:rbac-demo:app-service-account -n rbac-demo
# → no

kubectl auth can-i get pods --as=system:serviceaccount:rbac-demo:app-service-account -n helm-demo
# → no (until ClusterRoleBinding is created)

# ClusterRole — access across all namespaces
kubectl apply -f clusterrole-monitoring.yaml
kubectl create clusterrolebinding monitoring-binding `
  --clusterrole=monitoring-role `
  --serviceaccount=rbac-demo:app-service-account

kubectl auth can-i get pods --as=system:serviceaccount:rbac-demo:app-service-account -n helm-demo
# → yes

hpa-demo/ — Horizontal Pod Autoscaler

deployment.yaml     # php-apache with resource requests (required for HPA!)
hpa.yaml            # HPA: min=1, max=10, target CPU=50%
kubectl create namespace hpa-demo
kubectl apply -f deployment.yaml -n hpa-demo
kubectl apply -f hpa.yaml -n hpa-demo

# Watch HPA in real time
kubectl get hpa -n hpa-demo -w

# Generate load
kubectl run load-generator --image=busybox --restart=Never -n hpa-demo `
  -- /bin/sh -c "while true; do wget -q -O- http://php-apache.hpa-demo.svc.cluster.local; done"

Observed behavior

cpu: 0%/50%    replicas=1   ← idle baseline
cpu: 113%/50%  replicas=1   ← load starts
cpu: 113%/50%  replicas=3   ← scale up (~15s)
cpu: 104%/50%  replicas=6   ← scale up continues
cpu: 65%/50%   replicas=7   ← stabilizing near target
cpu: 0%/50%    replicas=7   ← load removed, cooldown begins
cpu: 0%/50%    replicas=3   ← scale down step 1
cpu: 0%/50%    replicas=1   ← back to minReplicas ✅

Formula: desiredReplicas = currentReplicas × (currentCPU / targetCPU)

Scale up is near-instant.
Scale down has a ~5 minute cooldown window — protection against flapping.
resources.requests is mandatory — HPA does not work without it.


argocd/ — GitOps with ArgoCD

argocd-app.yaml         # Application: argoproj/argocd-example-apps (guestbook)
argocd-nginx-app.yaml   # Application: own repo (nginx-gitops)

Install ArgoCD

kubectl create namespace argocd
kubectl apply -n argocd -f `
  https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Get admin password (PowerShell)
kubectl get secret argocd-initial-admin-secret -n argocd `
  -o jsonpath='{.data.password}' | `
  % { [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($_)) }

# Open UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# → https://localhost:8080  (admin / <password above>)

Connect your own repo

# Edit argocd-nginx-app.yaml — set repoURL to your repository
kubectl apply -f argocd-nginx-app.yaml
kubectl get application nginx-gitops -n argocd -w

GitOps workflow

# ❌ Don't do this — ArgoCD will revert it!
kubectl scale deployment nginx-gitops -n gitops-demo --replicas=0

# ✅ Correct way — change via Git
# Edit deployment.yaml: replicas: 2 → replicas: 4
git add .
git commit -m "scale: nginx 2 → 4 replicas"
git push
# ArgoCD detects the change (~3 min) and applies automatically

selfHeal — cluster drift protection

# Test: manually kill all pods
kubectl scale deployment nginx-gitops -n gitops-demo --replicas=0

# ArgoCD restores INSTANTLY (Watch API, not polling!)
kubectl get pods -n gitops-demo -w
# → pods recreated in ~2 seconds

Sync status lifecycle

Unknown      Healthy     ← ArgoCD reading the repo
OutOfSync    Missing     ← diff detected: resources absent in cluster
Synced       Progressing ← manifests applied, pods starting
Synced       Healthy     ← everything running ✅

Polling every 3 min — detects new commits in Git.
Watch API (instant) — protects against manual changes in the cluster.


Monitoring — Prometheus + Grafana

Not stored in this repo — deployed via Helm.

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

helm install prometheus-stack prometheus-community/kube-prometheus-stack `
  --namespace monitoring `
  --set grafana.adminPassword=admin123 `
  --set prometheus.prometheusSpec.retention=24h `
  --set alertmanager.enabled=false

# Grafana UI
kubectl port-forward -n monitoring service/prometheus-stack-grafana 3000:80
# → http://localhost:3000  (admin / admin123)

# Prometheus UI
kubectl port-forward -n monitoring `
  service/prometheus-stack-kube-prom-prometheus 9090:9090
# → http://localhost:9090

Key PromQL queries

# CPU by pod (minikube: cpu="total" filter is required!)
sum by (pod) (
  rate(container_cpu_usage_seconds_total{
    namespace="load-test", cpu="total", container!=""
  }[2m])
)

# Container memory usage
container_memory_working_set_bytes{namespace="dev", container!=""}

# Running pods per namespace
count by (namespace) (kube_pod_info)

# Container restart count
kube_pod_container_status_restarts_total

Empty result in PromQL?
Check: 1) the Service exists, 2) load is actually running (kubectl top pods),
3) wait 2 minutes after starting load, 4) add cpu="total" filter on minikube.


Quick Reference

# Cluster overview
kubectl get all -A
kubectl top nodes
kubectl top pods -n <namespace>
kubectl get events -n <namespace> --sort-by='.lastTimestamp'

# Diagnostics
kubectl describe pod <pod> -n <namespace>
kubectl logs <pod> -n <namespace> -f
kubectl exec -it <pod> -n <namespace> -- bash

# Cleanup
kubectl delete namespace <namespace>   # deletes everything inside!
helm uninstall <release> -n <namespace>

Topics Covered

Topic Key Objects
Foundations Pod, Namespace, Deployment, ReplicaSet, Service, Labels
Workloads RollingUpdate, Recreate, Rollback, Revision History
Networking ClusterIP, NodePort, Ingress, NetworkPolicy (Calico)
Storage emptyDir, PVC/PV, StatefulSet, Job, CronJob, DaemonSet
Configuration ConfigMap, Secret, env vars vs volume mount
Helm Chart, Release, Values, Upgrade, Rollback
RBAC Role, ClusterRole, RoleBinding, ServiceAccount
Monitoring Prometheus, Grafana, PromQL, PrometheusRule
GitOps ArgoCD, Application, selfHeal, automated sync
Autoscaling HPA, scale up/down, cooldown, flapping protection

About

A hands-on repository covering Docker and Kubernetes — built from real practice including all the mistakes, fixes, and lessons learned along the way.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages