Modulo 05
Docker crea ed esegue un container. Kubernetes lo orchestra: gestisce centinaia o migliaia di container distribuiti su più server, li riavvia da solo e li scala secondo il carico.
Kubernetes, spesso abbreviato in K8s, è una piattaforma open source per orchestrare i container. Tra le sue funzionalità principali: distribuzione automatica dei container, bilanciamento del carico, riavvio automatico in caso di errore, scalabilità automatica e aggiornamenti senza interruzioni del servizio.
In questo modulo useremo Minikube, una versione di Kubernetes pensata per lo sviluppo locale.
mkdir kubernetes-demo cd kubernetes-demo npm init -y npm install express
kubectl è il programma che permette di comunicare con Kubernetes. Su macOS:
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/amd64/kubectl"
Verifica dell'installazione:
kubectl version --client
Minikube crea un cluster Kubernetes direttamente sul computer. Su macOS Apple Silicon:
curl -LO https://github.com/kubernetes/minikube/releases/latest/download/minikube-darwin-arm64 sudo install minikube-darwin-arm64 /usr/local/bin/minikube
minikube version
Creiamo index.js:
import express from "express";
const app = express();
const PORT = process.env.PORT || 3000;
app.get("/", (req, res) => {
res.json({
message: "Hello from a container!",
service: "hello-node",
pod: process.env.POD_NAME || "unknown",
time: new Date().toISOString()
});
});
app.get("/readyz", (req, res) => {
res.status(200).send("ready");
});
app.get("/healthz", (req, res) => {
res.status(200).send("ok");
});
app.listen(PORT, () => {
console.log(`Example app listening on port ${PORT}`);
});
/ restituisce un JSON con messaggio, nome del servizio, nome del Pod e orario./readyz dice a Kubernetes se l'applicazione è pronta a ricevere richieste./healthz dice se il container è ancora funzionante: se risponde con errore, Kubernetes riavvia automaticamente il Pod.In package.json cambiamo "type": "commonjs" in "type": "module" e aggiungiamo gli script:
"scripts": {
"dev": "node --watch index.js",
"start": "node index.js"
}
FROM node:18-alpine AS base WORKDIR /app COPY package.json package-lock.json* ./ RUN npm ci --omit=dev COPY . . USER node EXPOSE 3000 ENV NODE_ENV=production CMD ["npm","start"]
npm ci --omit=dev installa solo le dipendenze di produzione, USER node evita di eseguire il container come amministratore.
Creiamo docker-compose.yml:
services:
api:
build:
context: .
dockerfile: Dockerfile
container_name: node-api
ports:
- "3000:3000"
environment:
NODE_ENV: production
volumes:
- .:/app
- /app/node_modules
command: npm run dev
Aggiungiamo .gitignore e .dockerignore:
# .gitignore node_modules # .dockerignore node_modules npm-debug.log Dockerfile .dockerignore .git .gitignore
Avviamo tutto con:
docker compose up --build
Aprendo http://localhost:3000 vediamo il JSON restituito dal server.
Nella cartella k8s, creiamo deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubernetes-demo-api
spec:
replicas: 2
selector:
matchLabels:
app: kubernetes-demo-api
template:
metadata:
labels:
app: kubernetes-demo-api
spec:
containers:
- name: kubernetes-demo-api
image: tuoutente/kubernetes-demo-api:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
readinessProbe:
httpGet:
path: /readyz
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 10
periodSeconds: 20
Il Deployment dice a Kubernetes come creare i Pod: replicas: 2 crea due Pod identici, image indica l'immagine Docker da usare, resources definisce quanta CPU e memoria assegnare, readinessProbe e livenessProbe usano gli endpoint /readyz e /healthz per sapere se il Pod è pronto o ancora vivo.
Creiamo service.yaml:
apiVersion: v1
kind: Service
metadata:
name: kubernetes-demo-api-service
spec:
selector:
app: kubernetes-demo-api
ports:
- protocol: TCP
port: 3000
targetPort: 3000
type: NodePort
Il Service permette ai Pod di comunicare con l'esterno. NodePort espone automaticamente l'applicazione su una porta del nodo Kubernetes.
minikube start kubectl get nodes kubectl cluster-info
kubectl apply -f k8s/deployment.yaml kubectl apply -f k8s/service.yaml
kubectl get pods -w kubectl get services
minikube service kubernetes-demo-api-service
Apre automaticamente il browser sull'applicazione in esecuzione nel cluster.
minikube stop
Creiamo deploy.sh:
#!/bin/bash set -e NAME="kubernetes-demo-api" USERNAME="tuoutente" IMAGE="$USERNAME/$NAME:latest" echo "Building Docker image..." docker build -t $IMAGE . echo "Pushing image to Docker Hub..." docker push $IMAGE echo "Applying Kubernetes manifests..." kubectl apply -f k8s/deployment.yaml kubectl apply -f k8s/service.yaml echo "Getting Pods..." kubectl get pods echo "Getting Services..." kubectl get services echo "Deployment completed successfully."
In package.json aggiungiamo:
"deploy": "sh deploy.sh"
Da qui in poi basta:
npm run deploy
Installare kubectl e Minikube, creare un'applicazione Express pronta per il cloud, containerizzarla con Docker, orchestrarla con Deployment e Service Kubernetes, gestire Pod e servizi, e automatizzare l'intero deploy con uno script Bash. Con Kubernetes un'applicazione diventa scalabile, resiliente e gestibile in modo dichiarativo — il livello sopra Docker in una pipeline DevOps completa.