Introduzione
Non vi è definizione più autorevole di Kubernetes di quella degli stessi produttori, su kubernetes.io:
Kubernetes è un sistema open-source per automatizzare la distribuzione, la scala, e la gestione di applicativi mantenuti in contenitori.
Kubernetes raggruppa i contenitori che costituiscono un applicativo in unità logiche per una facile scoperta e gestione. Kubernetes è costruito su 15 anni di esperienza di Google nell'esecuzione di carichi di lavoro, in combinazione con idee e pratiche avanzate da parte della comunità di sviluppatori.
Il presente corso fornisce un'introduzione alla tecnologia Kubernetes. I suoi scopi sono:
- familiarizzare con l'architettura e la struttura di Kubernetes
- usare Kubernetes in cluster locali sul proprio PC
- considerare alcuni principali componenti di Kubernetes
- esemplificare lo sviluppo di semplici applicativi da distribuire tramite Kubernetes con numerosi esercizi
Licenza
La presente documentazione è distribuita secondo i termini della GNU Free Documentation License Versione 1.3, 3 novembre 200t8.
Ne è garantita assoluta libertà di copia, ridistribuzione e modifica, sia a scopi commerciali che non.
L'autore detiene il credito per il lavoro originale ma nè credito nè responsabilità per le modifiche.
Qualsiasi lavoro derivato deve essere conforme a questa stessa licenza.
Il testo pieno della licenza è a:
https://www.gnu.org/licenses/fdl.txt
Introduzione e Docker
Per poter distribuire un applicativo in ambiente Kubernetes occorre imserirlo in immagini, da cui possano essere istanziati contenitori.
La tecnologia di contenitori di gran lunga più usata è quella Docker.
Docker verrà usato in questo corso per:
- generare containers da immagini Docker già disponibili
- containerizzare e distribuire nostri applicativi
- costruire uno stesso cluster Kubernetes
L'installazione, familiarità ed uso di Docker è quindi un prerequisito per questo corso.
E' da notare che non è richiesta un'eccessiva profondità di conoscenza di Docker. Gli argomenti di cui ci occuperemo saranno:
- gestione di immagini, contenitori e reti Docker
- generazione di immagini con Dockerfile
- uso di Docker Compose per l'orchestrazione locale di contenitori Docker
Microservizi
Gli applicativi distribuiti con Kubernetes sono ottimamente strutturati in microservizi.
Perchè i Microservizi
Un tipico applicativo di e-commerce può avere:
- Front-end web
- Servizio di catalogo
- Carrello della spesa
- Servizio di autenticazione
- Servizio di logging
- Storaggio persistente
In una architettura a microservizi ogni funzione è in un suo contenitore separato - microservizio
Ciascun tipo di contenitore è replicato a seconda del carico
Ciascun microservizio è sviluppato indipendentemente dagli altri, con tecnologie varie
Diventano molto importanti le interfacce
Stili di Applicativi
Applicativo Monolitico
Applicativo a Microservizi
Un microservizio è uno stile architettonico per sviluppare un singolo applicativo come serie di piccoli servizi
- ciascuno come processo separato
- in comunicazione tra loro, spesso con API basate su
- HTTP: REST - Representational State Transfer
- RPC - Remote Procedure Calls.
- deploy indipendente anche su hardware diversi
La modularizzazione dei programmi è presente da lungo periodo, ma, attenzione alla differenza:
- librerie - moduli riutilizzabili linkati nello spazio di indirizzamento di un singolo processo monolitico
- servizi - componenti fuori dallo spazio di indirizzamento del processo client che li usa
I servizi sono:
- più lenti delle librerie
- mantenibili e sostituibili individualmente
- sviluppabili da team separati
I servizi dipendono da una precisa definizione delle Interfacce che, una volta progettate e implementate, sono difficilmente cambiabili.
Serie di qualità positive:
- componentizzazione
- vicinanza ai problemi business
- prodotti non progetti
- approccio bottom-up
- sevizi intelligenti e canali di comunicazione stupidi
- amministrazione decentralizzata
- gestione dati decentralizzata
- automazione di infrastruttura
- progettati per la tolleranza al fallimento
- design evolutivo
Architettare Microservizi
Stile di programmazione di applicativi distribuiti.
Non sono direttamente collegati a Docker ma la containerizzazione dei microservizi è un valore aggiunto.
Strutturazione in microservizi modulari da i vantaggi:
- indipendenza di ciascun modulo dagli altri moduli
- facile sostituibilità
- deploy indipendente
- uso di un protocollo di comunicazione agnostico come HTTP
- applicativi intelligenti e canali di comunicazione stupidi
- interscambio dati come messaggi
- ciascuno può essere realizzato con linguaggi di programmazione differente
- sono importanti le interfacce
- organizzati in modo vicino al dominio business affrontato
- favoriscono la scalabilità
Architettura di Docker
Docker
- Ambiente di isolamento delle risorse necessarie ad un applicativo all'interno di un contenitore
- Da circa 2013, Solomon Hykes
- Contributo di Red Hat
- Richiede Linux a 64 bit Kernel 2.6+
- Licenza Apache (open)
- Scritto in Go
- Virtualizzazione entro il sistema operativo
- Direzione: software indipendente dal sistema operativo
Docker è scritto nel linguaggio di programmazione Go. Molti dei suoi comportamenti sono dovuti al linguaggio Go.
E’ distribuito da docker.com o docker.io
Ne esistono due versioni:
- enterprise - a sottoscrizione di licenza
- community - gratuito ed Open Source
Docker si basa su features architettoniche di Linux:
- cgroups - supporto kernel all’isolamento processi
- Union File System - FS composto da layers
La docker.com supporta il prodotto solo su Linux. Il Linux di riferimento è Ubuntu.
E’ in fase di sviluppo un Linux di riferimento specifico per Docker indipendente dalle distribuzioni correnti
Cgroups
Un cgroup (control group) è una feature che limita, traccia ed isola l’uso di risorse di una collezione di processi.
- I Cgroups sono una parte integrale del kernel Linux
- Forniscono l’implementazione nel system space dei contenitori dello user space
Union File System
Un contenitore è l'istanza run-time di un'immagine.
Un'immagine è la combinazione di un certo numero, variabile a seconda della specifica immagine, di strati software (Layers), integrati all'interno di uno Union File System.
Componenti di Docker
- Dockerfile: specifica dei componenti necessari. File testuale di specifiche
- Image: risultato della compilazione del docker file. Serie di layers in un repository locale o remoto
- Container: istanza di realizzazione di una image
Più containers della stessa image sono istanze separate e indipendenti.
Client-Server
- docker - client, Command Line Interface. Interfaccia con l’utente tramite comandi shell
- dockerd - high-level server, interagisce con il client. Implementa il comportamento di Docker
- containerd - low-level server, gestisce i container. Usato anche con contenitori non Docker, p.es. Kubernetes
Collegamento Locale
Il collegamento tra client e server avviene tramite un socket. In locale (default) questo è un socket Unix.
Tipi di Contenitori
- Process container - l’ambiente di esecuzione di un processo principale e possibilmente altri processi
- Network container - fornisce connettività, indirizzamento e risoluzione nomi ai contenitori di processo
- Data container - mantiene i dati in volumi e li persiste quando i contenitori di processo non sono attivi
Tutti i contenitori sono mantenuti in un repository locale..
Installazione di Docker
Tipi di Installazione
Sistemi Operativi
Linux
Ubuntu e simili [deb], RedHat e simili (CentOS) [RPM]
- Su hardware diretto (Docker Engine)
- Su Macchina Virtuale Linux (Docker Engine)
- VirtualBox (preferito)
- KVM
- Con Docker Desktop
Windows
- Su Macchina Virtuale Linux (Docker Engine)
- VirtualBox (preferito)
- VMWare
- Linux on Windows with WSL2
- Con Docker Desktop
Docker Desktop
Nuova offerta Docker
Basato su Macchina Virtuale Linux
Dashboard:
- Soluzione per principianti
- Interfaccia grafica Dashboard comune
- Molte meno opzioni che da linea di comando
- Non ancora performante o stabile. Promesse di migliorie
NB: Non si possono avere simultaneamente attivi Docker Engine e Docker Desktop
Su Linux, prima di attivare Docker Desktop occorre disabilitare Docker Engine:
sudo systemctl stop docker
sudo systemctl disable docker.service
sudo systemctl disable docker.socket
Installare Docker su Ubuntu
Su Hardware o su VM VirtualBox
Requisiti:
- Linux recente 64 bit
- Linux Mint (derivato da Ubuntu Focal 20.04 LTS)
- 4 GB RAM
- 100+ GB HD
- Connessione a Internet
Docker Engine
Docker Engine è la componente principale
Installare Docker Community Edition:
sudo apt update
sudo apt install docker.io
Docker Compose
Docker Compose è uno strumento per l’orchestrazione di più contenitori sulla stessa macchina host
Installare Docker Compose su Ubuntu:
sudo curl -SL https://github.com/docker/compose/releases/download/v2.23.3/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose version
Esiste anche una versione pacchettizzata di release, installabile con:
sudo apt install docker-compose
Ma è una versione relativamente vecchia, e sconsigliata.
Nuova Versione di Docker Compose
Il Docker Compose originale è una utility sviluppata da un gruppo indipendente da Docker.io
- scritta in Python
- comando con trattino separatore
docker-compose comando [opzioni] [argomenti]
Docker.io ha di recente aggiunto la sua versione di Docker Compose scritta in Go e parte del comando docker
- comando senza trattino separatore
docker compose comando [opzioni] [argomenti]
- La sintassi non è cambiata
- Usano entrambi il file
docker-compose.yml
Docker Compose è un plugin scaricabile dal repository software di Docker.io
Procedura shell docker-compose.sh
:
#! /bin/bash
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$UBUNTU_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
# Install the Docker Compose plugin:
sudo apt install docker-compose-plugin
Renderla eseguibile e lanciarla.
Dopo l'Installazione di Docker
Attivare il servizio:
sudo systemctl start docker
Abilitare il servizio ad ogni reboot:
sudo systemctl enable docker
Configurare l’utente corrente:
sudo usermod -aG docker $USER
Reboot del sistema. In teoria un relogin è sufficiente, ma la VM Ubuntu vuole proprio un reboot.
Test:
docker info
Gestione del Server Docker in Linux
Il server è gestito come servizio standard di Linux e si può controllare con:
sudo systemctl
azione
docker
ove azione
può essere:
status
- stato del servizio e processi attivistart
- far partire il serviziostop
- fermare il servizioenable
- configurare lo start automatico al bootdisable
- disabilitare lo start automatico al boot
Due componenti del server:
docker.service
- il serviziodocker.socket
- il socket Unix di collegamento
Il Client docker
E’ un applicativo da linea di comando (CLI) che:
- interagisce con l’utente
- invia i comandi al server per l’esecuzione
Nella versione di Docker più moderna i comandi hanno la struttura:
docker
ambiente comando
Alcuni comandi storici non hanno bisogno della specifica dell’ambiente e sono del formato:
docker
comando
Per avere un veloce aiuto sui comandi disponibili:
docker
Per avere un veloce aiuto sui comandi disponibili in un certo ambiente:
docker
ambiente
docker
ambiente
--help
- da anche le opzioni
Ambienti
Ogni ambiente permette la gestione di un diverso aspetto di Docker:
config
- configurazioni del cluster Swarmcontainer
– contenitori di processoimage
- immagininetwork
– contenitori di retinode
- nodi del cluster Swarmplugin
- pluginssecret
- segreti del cluster Swarmservice
- servizi offerti dal cluster Swarmswarm
- il cluster Swarmsystem
- aspetti generali del sistematrust
- fiducia delle immaginivolume
- contenitori di dati
Molti comandi legacy non richiedono la specifica dell’ambiente
Manualistica
Docker è dotato di ricca manualistica di riferimento.
Il nome della pagina di manuale mappa il comando docker dato:
docker
ambiente comando
ha manualistica con
man docker-
ambiente-comando
Esempi:
man docker
man docker-run
man docker-network-create
Nota: Alpine Linux
La release Linux di riferimento per Docker è Ubuntu
- Questa è anche l’immagine di base su cui molte immagini di applicativi sono costruite
- Ma: richiede molti MB di disco
Un’alternativa recente per lo sviluppo immagini di applicativi è Alpine Linux
- Molto ridotto nel footprint
- Richiede pochissimi MB di disco
Attenzioni:
- Alpine ha comandi di amministrazione diversi
- I comandi di base sono ottenuti con
busybox
- L’ambiente runtime è MUSL e non GLIBC
- Questo necessita di attenzioni quando si sviluppano eseguibili compilati dai contenitori
Fondamenti di Kubernetes
Vengono qui esaminate le principali caratteristiche dell'ambiente Kubernetes e i suoi oggetti principali.
Gli esercizi ed esempi sono condotti con l'emulatore di cluster Minikube.
Origini, evoluzione e scopo di Kubernetes
Kubernetes
Normalmente abbreviato in k8s. Pronuncia come in Kates.
Sistema aperto per automatizzare il deployment, la scalabilità e la gestione di applicativi in container.
https://kubernetes.io/
- Originato da Google
- Gestito dalla Cloud Native Computing Foundation
- Scritto in Go
- Tecnologia complementaria a Docker
Google è basato su Kubernetes
- Google genera miliardi di contenitori la settimana
- search, Gmail, GFS, ecc.
Kubernetes c’era prima di Docker
- Progetti originari: Borg e Omega (Star Trek)
- Originariamente chiamato Seven of Nine (donna Borg salvata da USS Voyager)
- Per questo ha sette lati
- Il nome viene dal greco per timoniere
Macchine Virtuali e Contenitori
Macchine Virtuali
Virtualizzazione dell'Hardware
- Più sistemi operativi diversi
- Più flessibile
- Più maturo
Contenitori
Virtualizzazione dell'Ambiente Operativo
- Più efficienza
- Meno manutenzione di sistema
- Deployment molto più veloce
- Migliaia di immagini con documentazione
Storia:
- 1980-2000: Applicativi su hardware nativo
- 2000-2015: Applicativi in macchine virtuali
- 2015-: Applicativi in contenitori nel cloud
Cloud Native
Progettati per i requisiti di funzionamento nel cloud:
- unattended
- auto-scaling
- self-healing
- rolling updates e rollbacks senza downtime
Gli applicativi cloud native possono anche funzionare su un cluster locale. Infatti di solito vengono prima sviluppati in locale.
Alcuni fornitori di servizi cloud che offrono soluzioni Kubernetes a pagamento sono:
- GCP - Google Kubernetes Engine (GKE).
- Microsoft Azure - Azure Kubernetes Service (AKS).
- Amazon - Amazon Elastic Kubernetes Service (Amazon EKS).
- Red Hat - OpenShift Container Platform (OCP)
Ogni diversa offerta ha metodi differenti di connessione e autenticazione, tools di gestione del cluster e regolamenti comportamentali.
Una volta istanziato un cluster Kubernetes però, l'uso del comando di gestione kubectl
è lo stesso per tutte le piattaforme.
Orchestratore
Sistema che compie il deployment e la gestione di applicazioni in un cluster, e risponde dinamicamente ai cambiamenti.
Le applicazioni sono attive in contenitori.
Lo scopo potenziale di Kubernetes è globale.
Il servizio è solitamente fornito da un cloud provider.
Tipi di Contenitori
K8s accetta vari tipi di contenitori.
Dalla release 1.20 devono essere conformi all’interfaccia Container Runtime Interface (CRI).
Nuove versioni di Docker forniscono containerd
, conforme a questa interfaccia
La versione più vecchia di Docker è deprecata
Vi sono altri ambienti di container oltre a Docker, ma molto meno diffusi
Note
Un contenitore non ha il Kernel del Sistema Operativo, ma usa quello della macchina ospite.
Ci sono contenitori Linux e Windows:
- Linux ammette solo contenitori Linux
- Windows ammette contenitori
- Windows, su Pro e Server, non su Home
- Linux, con lo strato Windows Subsystem for Linux (WSL)
- Non si possono avere simultaneamente sulla stessa macchina ospite
- I contenitori Windows sono molto più grossi dei contenitori Linux e non ne hanno tutte le possibilità
Docker e Kubernetes sono tecnologie native Linux. Sviluppare applicativi in contenitori Windows è cercarsi dei problemi.
Alternative a Kubernetes
Kubernetes nel 2023 è di gran lunga la soluzione più gettonata per la gestione di applicativi in ambiente cloud, ma non l'unica. Alcuna altre offerte open source sono:
- Docker Swarm - l'alternativa più popolare, adatta quando l'applicativo già usa tecnologie Docker: Docker Engine, Docker Compose, Docker Hub
- Marathon - creato dalla Università di California, Berkeley, per l'ambiente DC/OS di Mesosphere e Apache Mesos.
- Nomad - semplice piattaforma di orchestrazione contenitori della Hashicorp
Cluster Kubernetes
Struttura di un Cluster
Kubernetes è un orchestratore di applicativi in container, preferibilmente strutturati in microservizi:
- Molte parti piccole e indipendenti
- Cooperano per implementare un applicativo complesso
- Stesso concetto di Docker-compose ma in grande
K8s gestisce clusters che comprendono:
- masters - uno o più
- si occupano della gestione - control plane
- nodi - uno o più - workers
- si occupano dell’esecuzione
- comunicano col master
Masters e nodi sono istanze Linux
- su hardware nativo
- su macchine virtuali
- nel cloud
Componenti del Master
Il master ha quattro componenti principali:
- API Server
- Frontend col mondo
- API REST che comunica in JSON
- Cluster Store
- Configurazioni e stato del cluster
- Basato sul gestore di chiavi etcd
- Controller
- Monitor delle operazioni
- Transizioni da current state a desidered state
- Scheduler
- Assegna i workload ai nodi
- Si occupa delle performance
Componenti di un Nodo
- Kubelet
- Comunica col Master
- Container Engine
- Gestisce i contenitori
- Network Proxy
- Gestisce le comunicazioni di rete del nodo
Aspetti d’Uso
Due aspetti di Kubernetes:
-
Cluster - creazione e gestione
- Prerequisito per ogni operazione
- Vari ambienti per vari sistemi operativi host
-
Kubernetes - interazione col cluster
- Quando il cluster è attivo e raggiungibile
- Comando client principale stile CLI: kubectl
- anche utilities GUI, ma
kubectl
è più completo e performante
- anche utilities GUI, ma
- Possibili plugins e utilities aggiuntive
Topologia di Cluster Kubernetes
Rete Locale
In una rete locale
- Su hardware dedicato, PC o Blades
- Richiede installazione complessa e manutenzione
Cloud
In un cloud
- Soluzione tipica in produzione
- Richiede sottoscrizione ad un Cloud Provider
- Costi a metratura di utilizzo
Macchine Virtuali
Su un singolo computer im macchine virtuali
- Per esercizi e sviluppo applicativi
- Limitato
- Richiede notevoli risorse di CPU e RAM
- Statico e non scalabile
Contenitori Docker
Su un singolo computer im macchine virtuali
- Più leggero di macchine virtuali
- Limitato
- Richiede organizzazione dello strato Docker
Emulatori di cluster per PC
Cluster su Singolo PC
- Minikube
- Il più diffuso, favorito da PC Windows
- Un singolo nodo, con control-plane e worker
- Richiede 2 CPU, 4+ GB RAM
- Molti add-ons
- MicroK8s
- Molto pesante sulle risorse del PC
- Un nodo per ogni Macchina Virtuale
- K3s, K0s
- Leggeri ma molto di base
- Disponibili anche in docker
- KinD
- Kubernetes in Docker
- Più nodi e anche più clusters
- 1 CPU e meno peso sulle risorse
- Meno completo degli altri
Minikube
Struttura di Minikube
Installazione ed uso di Minikube
Installare kubectl
Installazione in Linux
Scaricare e installare:
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s \
https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
chmod +x ./kubectl
sudo mv ./kubectl /usr/local/bin/kubectl
sudo chown root:root /usr/local/bin/kubectl
Controllare:
kubectl version
Più leggibile:
kubectl version --output=yaml
Installare Minikube
Download del programma:
wget https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
chmod +x minikube-linux-amd64
sudo mv minikube-linux-amd64 /usr/local/bin/minikube
sudo chown root:root /usr/local/bin/minikube
Test:
minikube version
Aggiusta automaticamente l’ambiente per accedere al cluster dalla shell.
Altri Requisiti Software
Sono necessari per qualche comando di Minikube.
Installare il pacchetto conntrack
, che gestisce connessioni stateful:
sudo apt update
sudo apt install conntrack
Intallare il pacchetto cri-dockerd
, che inerfaccia il server a dasso livello di Docker a Kubernetes:
wget https://github.com/Mirantis/cri-dockerd
chmod +x cri-dockerd
sudo mv ./cri-dockerd /usr/local/bin/cri-dockerd
sudo chown root:root /usr/local/bin/cri-dockerd
Lancio di Minikube
minikube start
😄 minikube v1.27.1 on Linuxmint 20.3
✨ Automatically selected the docker driver. Other choices: virtualbox, ssh, none
📌 Using Docker driver with root privileges
👍 Starting control plane node minikube in cluster minikube
🚜 Pulling base image ...
💾 Downloading Kubernetes v1.25.2 preload ...
> preloaded-images-k8s-v18-v1...: 385.41 MiB / 385.41 MiB 100.00% 231.25
> gcr.io/k8s-minikube/kicbase: 387.11 MiB / 387.11 MiB 100.00% 228.11 KiB
> gcr.io/k8s-minikube/kicbase: 0 B [_____________________] ?% ? p/s 14m32s
🔥 Creating docker container (CPUs=2, Memory=2200MB) ...
🐳 Preparing Kubernetes v1.25.2 on Docker 20.10.18 ...
▪ Generating certificates and keys ...
▪ Booting up control plane ...
▪ Configuring RBAC rules ...
🔎 Verifying Kubernetes components...
▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟 Enabled addons: default-storageclass, storage-provisioner
🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
La prima volta può impiegare molto tempo, poichè scarica dalla rete delle immagini Docker.
Si può prescaricare l’immagine, e selezionarne una versione specifica:
docker pull gcr.io/k8s-minikube/kicbase:v0.0.35
Lanciare minikube con l’immagine scaricata:
minikube start --base-image='gcr.io/k8s-minikube/kicbase:v0.0.35'
Immagine docker dopo l’installazione:
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
gcr.io/k8s-minikube/kicbase v0.0.26 b0c9ec980b3d 8 days ago 1.08GB
Tutto minikube è in un’unica immagine molto grossa
Vedere la configurazione di kubectl
:
kubectl config view
Informazioni sul cluster:
kubectl cluster-info
Kubernetes master is running at https://192.168.49.2:8443
KubeDNS is running at https://192.168.49.2:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
Nodi attivi:
kubectl get nodes
NAME STATUS ROLES AGE VERSION
minikube Ready master 46m v1.19.2
List dei pods:
kubectl get pod
No resources found in default namespace.
SSH alla shell minikube:
minikube ssh
Fermare Minikube
minikube stop
✋ Stopping node "minikube" ...
🛑 Powering off "minikube" via SSH ...
🛑 1 node stopped.
minikube stop
non cancella il container minikube
Rimuovere Minikube
Cancellare l’intero cluster:
minikube delete
N.B. Non farlo fino al termine di tutte le operazioni
Comandi Minikube e Add-ons
Requisiti di Minikube
Hardware
- CPU con almeno 2 cores
- almeno 2 GB di RAM libera
- almeno 20 GB di spazio disco
Minikube Drivers
Minikube ha bisogno di un driver runtime. I driver sono riconosciuti di default e sono selezionati con preferenza che varia col sistema operativo sottostante:
- Linux
- Docker
- KVM2
- VirtualBox
- Windows
- Hyper-V
- Docker
- VirtuaBox
Gestione del Cluster Minikube
Il default di Minikube è di gestire un cluster chiamato minikube
con un nodo, sia con la funzionalità di control-plane che di worker.
minikube start
- fa partire il clusterminikube stop
- ferma il cluster, ma non toglie il contenitore docker che lo implementaminikube pause
- mette in pausa, non ha impatto sugli applicativi già deployed, ma non accetta comandiminikube unpause
- toglie dalla pausaminikube delete
- ferma il cluster e rimuove il contenitore docker che lo implementa
Cluster Multinodo
E' possibile generare un cluster multinodo, p.es. con tre nodi, con il comando:
minikube start -n 3
Il primo nodo, minikube
, sarà il control-plane. Gli altri due nodi, minikube-m02
e minikube-m03
, saranno workers.
Si può aggiungere un nodo al cluster esistente col comando:
minikube add node
Sarà un worker. Non è possibile al momento aggiungere un control-plane.
Ogni nuovo nodo aggiunto corriaponde a un contenitore Docker sulla macchina host, se Docker è il driver di Minikube (default).
Aggiungere nodi appesantisce la richiesta di risorse dalla macchina ospite: memoria e carico di sistema. Si può andare presto in modalità swapping con disastrosa riduzione delle prestazioni.
Si può fermare un nodo col comando, per esempio:
minikube node stop minikube-m02
Eventuali pod attivi su tale nodo sono terminati e ricreati su un altro nodo. Gli utenti che sono collegati a tali pod possono avere un'interruzione se il servizio è stateful, ma alla prossima richiesta di accesso al servizio verranno serviti dal nuovo pod sull'altro nodo.
Per far ripartire un nodo fermo, per esempio:
minikube node start minikube-m02
Per rimuovere un nodo, per esempio:
minikube node delete minikube-m02
Per listare i nodi e i loro indirizzi IP dare il comando:
minikube node list
La lista dei nodi disponibili si trova anche con:
kubectl get nodes
Lo stato del cluster, con i dettagli per ciascun nodo, si ottiene con:
minikube status
Principali oggetti di Kubernetes
Componenti Kubernetes
Pod
- Unità atomica di Deployment
- Sandbox con namespace, cgroups, ecc.
- Tutte le applicazioni sono dentro contenitori
- Ogni contenitore esegue in un pod
Due modelli:
- single-container pod - Un pod con un singolo contenitore
- multi-container pod - Un pod con più contenitori
- Vi è un contenitore principale
- Vi sono altri helper containers o supporting containers
- P.es. logger
I Pod:
- aumentano le capacità dei container
- etichette, annotazioni, policies di restart, scheduling, regole di affinità, controllo di terminazione, sicurezza, limiti alle risorse
- assistono nella schedulazione
- nella stessa regione e zona nel cloud o datacenter: co-scheduling e co-locating
- permettono la condivisione di risorse
- filesystems, stack di rete, memoria, volumi dati
Multicontainer Pods
Tutti i container in un pod condividono lo stesso ambiente, incluso l’indirizzo IP
Comunicano tra loro tramite localhost
Patterns
Funzioni del supporting container:
- Sidecar
- Task secondario: update, logging, ...
- Adapter
- Trattamento dei dati in input e/o output
- Ambassador
- Gestione di connettività a sistemi esterni
- Init
- Azioni da eseguire prima del container principale
Scalare i Pod
Aggiungere nuovi pod, non containers in un pod
I Pod sono effimeri
- Se un pod muore per qualsiasi motivo, un altro viene creato nel cluster
- Non avrà lo stesso indirizzo IP del precedente
- Un container non può contare sull’esistenza continuativa degli indirizzi IP degli altri
Servizi
- Endpoints di rete persistenti e affidabili per i pod
- Include il servizio DNS
- Un oggetto di K8s
Associare i Pod ai Servizi
- Ottenuto tramite un insieme di Labels
- Associati quando i pod hanno lo stesso sottoinsieme dei serrvizi
ReplicaSet
- Tipicamente contenuto in un Deployment
- Oggetto K8s, wrapper di pod
- Compie la gestione di un numero di pod
- Si assicura della presenza continua del numero di istanze desiderato
Deployment
- Oggetto di prima classe
- Specificato da Manifest
- YAML
- JSON
- Inviato via REST allo API Server
- Molte features
- Rollouts
- Rollbacks
Ingress
- Interfaccia esterna al cluster
- IP esterno
- Usato per HTTP
- Contatta i servizi interni
Modalità imperativa e dichiarativa
Modalità di Interazione
Per l’interazione tra il client kubectl e il cluster k8s sono possibili due modalità.
Imperativa
- L’utente da una serie di comandi con kubectl
- K8s esegue ciascun comando immediatamente
La modalità imperativa ha una serie di svantaggi:
- non mantiene una storia delle previe configurazioni poichè i comandi operano direttamente su oggetti attivi del cluster
- non si può adattare a sistemi di revisione del codice
- non fornisce templati standard per la creazione di nuovi oggetti
Dichiarativa
- L’utente scrive un file di specifiche in YAML, con la sintassi appropriata
- Il comando
kubectl
invia il file a k8s - I processi del master node interpretano il file ed eseguono autonomamente le operazioni necessarie
Gestione di Pods
Creare un Pod in Modo Imperativo
Se non eseguito:
minikube start
Creare un pod db col database mongo:
kubectl run db --image mongo:3.3
Controllo:
kubectl get pods
NAME READY STATUS RESTARTS AGE
db 0/1 ContainerCreating 0 64s
Ci vuole del tempo perchè venga scaricata l’immagine di mongo.
Controllare a intervalli regolari con:
kubectl get events | grep -i db
Conferma che c’è un contenitore attivo col database mongo:
eval $(minikube docker-env)
Ora le variabili d’ambiente del docker dello host puntano a minikube.
docker container ls -f ancestor=mongo:3.3
Risettare l’ambiente:
eval $(minikube docker-env -u)
Le variabili d’ambiente del docker dello host puntano di nuovo allo host.
Rimuovere il pod:
kubectl delete pod db
Verificare:
kubectl get pods
Non lo trova.
Possibile problema
Lo scarico di qualche immagine troppo grossa può andare in timeout. L’evento dice:
Failed to pull image "mongo:3.3": rpc error: code = Unknown desc = context deadline exceeded
In tal caso viene ritentato più volte con un tempo di timeout via via crescente. Può impiegare comunque molto tempo.
Workaround, scaricare l’immagine a mano:
minikube ssh docker pull mongo:3.3
Attendere lo scarico dell’immagine.
L’immagine è scaricata nel contesto di Minikube, non nello host a monte.
Poi proseguire con:
kubectl run db --image mongo:3.3
Creare un Pod in Modo Dichiarativo
Creare una directory e il file di specifiche YAML:
mkdir pod
vim pod/db.yml
apiVersion: v1
kind: Pod
metadata:
name: db
labels:
type: db
vendor: MongoLabs
spec:
containers:
- name: db
image: mongo:3.3
command: ["mongod"]
args: ["--rest", "--httpinterface"]
Sottomettere la richiesta:
kubectl create -f pod/db.yml
Verificare e attendere il pull dell’immagine:
kubectl get pods
kubectl get events
Con più informazioni:
kubectl get pods -o wide
In altri formati:
kubectl get pods -o json
kubectl get pods -o yaml
Dettagli su un singolo pod:
kubectl describe pod db
Qualora vi fossero più pods attivi nel cluster, si può limitare la visualizzazione a pods aventi certe etichette, per esempio:
kubectl get pods -l vendor=MongoLabs
Si possono mostrare tutti i pods con le loro etichette con:
kubectl get pods --show-labels
Interazione col Pod
Aprire una shell nel contenitore del pod:
kubectl exec -it db -- sh
- Se il contenitore abbia una shell, e quale sia il suo nome, dipende dall’immagine usata
Lanciare un comando nel contenitore del pod:
kubectl exec -it db -- ps wax
Per pod multicontainer indicare il nome del container:
kubectl exec -it --container=
nomecontainer nomepod
-- sh
I container ausiliari sono nel rapporto ottenuto con:
kubectl describe pod
nomepod
Vedere il log:
kubectl logs db [-f]
Kill e Rimozione di Pod
Kill del container in un pod:
kubectl exec db -- pkill mongod
e dare immediatamente:
kubectl get pods
NAME READY STATUS RESTARTS AGE
db 0/1 Completed 1 (31s ago) 2m57s
Ma dopo qualche secondo:
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE
db 1/1 Running 1 (21s ago) 3m18s
Quando il container di un pod è distrutto, immediatamente viene ricreato un altro container.
Rimuovere definitivamente il pod:
kubectl delete -f pod/db.yml
Rimuove tutte le risorse descritte dal file di specifiche
Note
Ambiente del Cluster e di Default
Nell’ambiente di default:
docker images
Nell’ambiente del cluster:
eval $(minikube docker-env)
docker images
- Ci sono più immagini nel cluster che nell’ambiente di default.
- Verranno rimosse alla rimozione del cluster
- Alcune immagini sono visibili in entrambi gli ambienti
- Non verranno rimosse alla rimozione del cluster
Tornare all’ambiente di default:
eval $(minikube docker-env -u)
Sequenza di Eventi
Durante la creazione del pod:
- Il client Kubernetes (
kubectl
) invia una richiesta allo API Server di creazione di un pod come definito nel file di specificapod/db.yml
- Lo Scheduler monitorizza lo API Server e scopre che vi è un pod non assegnato
- Lo Scheduler decide quale nodo assegnare al pod ed invia tale informazione allo API Server
- Anche Kubelet del nodo assegnato monitorizza lo API Server e scopre che gli è stato assegnato il pod
- Kubelet invia una richiesta a Docker per la creazione dei container che formano il pod. In questo caso è un container singolo con l’immagine mongo
- Kubelet notifica lo API Server che il pod è stato creato con successo
Pod con Container Multipli
Il file di specifiche è pod/go-demo-2.yml
:
apiVersion: v1
kind: Pod
metadata:
name: go-demo-2
labels:
type: stack
spec:
containers:
- name: db
image: mongo:3.3
- name: api
image: vfarcic/go-demo-2
env:
- name: DB
value: localhost
Creazione del pod:
kubectl create -f pod/go-demo-2.yml
e dopo qualche secondo:
kubectl get -f pod/go-demo-2.yml
NAME READY STATUS RESTARTS AGE
go-demo-2 2/2 Running 0 104s
Dall’output di:
kubectl get -f pod/go-demo-2.yml -o json
si vedono i nomi dei container: db
, api
Output con filtro:
kubectl get -f pod/go-demo-2.yml \
-o jsonpath="{.spec.containers[*].name}"; \
echo
Comando in un container specifico:
kubectl exec -c db go-demo-2 -- ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
mongodb 1 1.0 1.5 273132 61004 ? Ssl 10:59 0:07 mongod
root 33 0.0 0.0 17504 2064 ? Rs 11:12 0:00 ps aux
kubectl exec -c api go-demo-2 -- ps aux
PID USER TIME COMMAND
1 root 0:00 go-demo
12 root 0:00 ps aux
L’output dipende dal sistema operativo del container.
Logs di un container specifico:
kubectl logs go-demo-2 -c db
Rimuovere il pod:
kubectl delete -f pod/go-demo-2.yml
Architettura dei Pod
Ciclo di Vita dei Pod
Stati del Ciclo di Vita
Un contenitore ha tre stati del ciclo di vita:
- waiting - sta ancora compiendo dei passi necessari alla creazione del container
- running - in piena funzione
- terminated - il contenitore ha completato con successo o è fallito per qualche motivo
Gli stati indicati si susseguono nel ciclo di vita.
Fasi del Ciclo di Vita
E' il campo Status
bel rapporto di
kubectl describe pod
nomepod
.
- Pending - accettato, ma non tutti i contenitori sono pronti
- Running - in funzione o in ripartenza
- Succeeded - container terminati con successo, non ripartiranno
- Failed - uno o più contenitori sono falliti
- Unknown - stato non determinato, perchè il nodo non è raggiungibile
Condizioni del Pod
- PodScheduled - schedulato su un nodo
- ContainersReady - i contenitori del pod sono attivi
- Initialized - tutte le attività di inizializzazione sono terminate
- Ready - proto ad accettare collegamenti
Restart di un Pod
In caso di fallimento di un pod, ne può venire creato un altro uguale, a seconda del settaggio restartPolicy
delle spec
:
Always
- sempre, anche se lo stato di uscita del contenitore è zero, che indica successo (default)OnFailure
- solo se lo stato di uscita del container è diverso da zero, indicante fallimentoNever
- mai
Il restart di un pod non avviene quando quel pod è creato standalone, ma solo se fa parte di un workload come ReplicaSet, Seployment, StatefulSet, DaemonSet, Job e CronJob.
Hooks del Ciclo di Vita
Ve ne sono due:
PostStart
- eseguito prima dello stato runningPrestop
- eseguito prima dello stato terminated
Il seguente esempio ne illustra l'uso.
Preparare il file di specifiche:
mkdir -p ~/pod
cd ~/pod
vim prepost.yml
apiVersion: v1
kind: Pod
metadata:
name: ha-nginx-pod-hooks
labels:
app: ha-nginx-p-h
spec:
containers:
- name: ha-nginx-c-1
image: nginx:1.10.1
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo postStart is executing > /tmp/msg"]
preStop:
exec:
command: ["/bin/sh", "-c", "echo preStop is executing > /tmp/msg"]
Creare il pod:
kubectl apply -f prepost.yml
Attendere lo stato di Running.
Connettersi al pod:
kubectl exec -it ha-nginx-pod-hooks -- bash
Verificare che postStart
è stato eseguito:
cat /tmp/msg
Uscire e cancellare il pod:
exit
kubectl delete -f prepost.yml
preStop
è stato eseguito. Purtroppo il file /tmp/msg
è stato perso col pod. E' pensabile però che il pod scriva ad un volume persistente.
postStart
e preStop
non generano logging.
Contenitori Init
In un pod multicontainer, uno o più possono essere indicati come contenitori Init.
Questi eseguiranno prima del pod principale, che non andrà nello stato Running finchè tutti gli InitContainers non avranno terminato.
Il seguente esempio ne illustra il funzionamento.
Preparare il file di specifiche:
mkdir -p ~/pod
cd ~/pod
vim init.yml
apiVersion: v1
kind: Pod
metadata:
name: ha-nginx-pod-init
labels:
app: ha-nginx-p-i
spec:
containers:
- name: ha-nginx-c-1
image: nginx:1.10.1
ports:
- containerPort: 80
initContainers:
- name: ha-init-c-1
image: busybox:1.28
command: ['sh', '-c', 'echo ha-init-c-1 initContainers is running!']
Creare il pod:
kubectl apply -f init.yml
Notare la situazione in veloce evoluzione:
jenny@fray:~/pod$ kubectl apply -f init.yml
pod/ha-nginx-pod-init created
jenny@fray:~/pod$ kubectl get pod
NAME READY STATUS RESTARTS AGE
ha-nginx-pod-init 0/1 Init:0/1 0 10s
jenny@fray:~/pod$ kubectl get pod
NAME READY STATUS RESTARTS AGE
ha-nginx-pod-init 0/1 Init:0/1 0 18s
jenny@fray:~/pod$ kubectl get pod
NAME READY STATUS RESTARTS AGE
ha-nginx-pod-init 0/1 PodInitializing 0 28s
jenny@fray:~/pod$ kubectl get pod
NAME READY STATUS RESTARTS AGE
ha-nginx-pod-init 1/1 Running 0 32s
Dapprima lo InitContainer per due volte non è pronto, poi è in esecuzione nello stato PodInitializing
. Quindi lo InitContainer termina e il contenitore principale va nello stato Running
.
Si può ispezionare il log dello InitContainer:
kubectl logs ha-nginx-pod-init -c ha-init-c-1
Al termine, eliminare il pod:
kubectl delete -f init.yml
Replicaset e Deployment
Creazione di Pod
In modalità dichiarativa.
Creare una directory per l’esercizio:
mkdir -p ~/kube/hello
cd ~/kube/hello
File di specifiche per il pod:
vim pod.yml
apiVersion: v1
kind: Pod
metadata:
name: hello-pod
labels:
zone: prod
version: v1
spec:
containers:
- name: hello-ctr
image: nigelpoulton/k8sbook:latest
ports:
- containerPort: 8080
Creazione del pod:
kubectl create -f pod.yml
IL pronto torna subito ma il comando per completare impiegherà tempo, poichè vi è un’immagine da scaricare.
Ispezione del comando in esecuzione:
kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-pod 0/1 ContainerCreating 0 52s
Ispezione più dettagliata:
kubectl describe pods hello-pod
Dopo qualche tempo:
kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-pod 1/1 Running 0 7m29s
Creare un ReplicaSet
File di specifiche:
vim rs.yml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: web-rs
spec:
replicas: 3
selector:
matchLabels:
app: hello-world
template:
metadata:
labels:
app: hello-world
spec:
containers:
- name: hello-ctr
image: nigelpoulton/k8sbook:latest
ports:
- containerPort: 8080
Creare il ReplicaSet:
kubectl create -f rs.yml
replicaset.apps/web-rs created
Notare il nome dell'oggetto, composto di due parti separate da ‘/’
Si può citare il nome completo nei comandi.
Per i replicasets l’alias di 'replicaset.apps' è anche ‘rs’.
Listare il ReplicaSet:
kubectl get replicaset.apps/web-rs
kubectl get rs/web-rs
Listare tutti i pods e le loro labels:
kubectl get pods --show-labels
Scala di un ReplicaSet
Nel file di configurazione rs.yml
, cambiamo il parametro replicas da 3 a 5.
Sottoponiamo il cambiamento:
kubectl apply -f rs.yml
Otteniamo un warning:
Warning: resource replicasets/web-rs is missing the
kubectl.kubernetes.io/last-applied-configuration annotation
which is required by kubectl apply. kubectl apply should only
be used on resources created declaratively by either
kubectl create --save-config or kubectl apply.
The missing annotation will be patched automatically.
Il warning compare solo la prima volta.
Verificare:
kubectl get rs/web-rs
kubectl get pods --show-labels
Nota:
- Se si adotta il formato dichiarativo occorre astenersi dall’uso di comandi imperativi sugli oggetti creati
- Potrebbe causare disallineamenti
Servizi e loro tipi
Modo imperativo
Comando:
kubectl expose rs web-rs \
--name=hello-svc \
--target-port=8080 \
--type=NodePort
service/hello-svc exposed
Tipi di servizio:
- ClusterIP - Indirizzo IP stabile nel cluster. Non visibile dall’esterno
- NodePort - Visibile dall’esterno del cluster, a livello nodi
- LoadBalancer - Visibile dall'esterno del cluster, fornisce un bilanciamento di carico sui nodi
Descrizione del servizio:
kubectl describe svc hello-svc
Rimuovere il servizio:
kubectl delete service hello-svc
Modo dichiarativo
File di specifiche:
vim svc.yml
apiVersion: v1
kind: Service
metadata:
name: hello-svc
labels:
app: hello-world
spec:
type: NodePort
ports:
- port: 8080
nodePort: 30001
protocol: TCP
selector:
app: hello-world
Creazione del servizio:
kubectl create -f svc.yml
Ispezione del servizio:
kubectl get svc hello-svc
kubectl describe svc hello-svc
Descrizione degli endpoints:
kubectl get ep hello-svc
Rimozione del servizio:
kubectl delete -f svc.yml
Rimozione del replicaset:
kubectl delete -f rs.yml
Gestione di Minikube
Rilanciare Minikube: warmstart
Dopo uno stop ma col container ancora presente:
minikube start
😄 minikube v1.27.1 on Linuxmint 20.3
✨ Using the docker driver based on existing profile
👍 Starting control plane node minikube in cluster minikube
🚜 Pulling base image ...
🔄 Restarting existing docker container for "minikube" ...
🐳 Preparing Kubernetes v1.25.2 on Docker 20.10.18 ...
🔎 Verifying Kubernetes components...
▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟 Enabled addons: storage-provisioner, default-storageclass
🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
Pochi minuti.
Le immagini di prima ci sono ancora:
minikube ssh
docker images
Rilanciare Minikube: coldstart
Fermare minikube e rimuovere il container:
minikube stop
docker rm minikube
Rilanciare minikube:
minikube start
....
🚜 Pulling base image ...
🤷 docker "minikube" container is missing, will recreate.
🔥 Creating docker container (CPUs=2, Memory=2200MB) ...
🐳 Preparing Kubernetes v1.25.2 on Docker 20.10.18 ...
...
Qualche minuto in più.
Le immagini di prima non ci sono più:
minikube ssh
docker images
L'emulatore di cluster Minikube non ha intrinseca persistenza dei suoi contenuti una volta che rimuoviamo il contenitore docker che lo implementa.
Kubernetes in Docker (KinD)
KinD è uno strumento per cluster Kubernetes locali usando come nodi dei contenitori Docker.
E' progettato per il testing ma può essere usato per lo sviluppo in locale.
Kind consiste di:
- Pacchetti Go che implementano la creazione di cluster, il build di immagini, ecc.
- Un’interfaccia a linea di comando (
kind
) costruita su tali pacchetti - Immagini Docker per il run di systemd, Kubernetes, ecc.
Kind compie il bootstrap dei nodi con kubeadm
.
Cluster KinD
Installiamo kind:
curl -Lo ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.14.0/kind-linux-amd64 && \
chmod +x ./kind && \
sudo mv ./kind /usr/local/bin/kind
Editare il file kind.yml
:
vim kind.yml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
Scarico dell'immagine del cluster Kubernetes per Kind:
docker pull kindest/node:v1.24.0
E' una immagine relativamente grossa, impiega del tempo.
Lancio del cluster:
kind create cluster --config kind.yml --image kindest/node:v1.24.0
Creating cluster "kind" ...
✓ Ensuring node image (kindest/node:v1.24.0) 🖼
✓ Preparing nodes 📦 📦 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
✓ Joining worker nodes 🚜
Set kubectl context to "kind-kind"
Impiega qualche minuto. Il cluster è ora pronto.
Informazioni sul cluster:
kubectl cluster-info --context kind-kind
Kubernetes control plane is running at https://127.0.0.1:44391
CoreDNS is running at https://127.0.0.1:44391/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
Listare i nodi:
kubectl get nodes
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane 7m31s v1.24.0
kind-worker Ready <none> 6m22s v1.24.0
kind-worker2 Ready <none> 6m22s v1.24.0
Affinchè tutti i nodi siano pronti può richiedere qualche minuto.
Uso del Cluster Kind
Creazione di Pod
Specifichiamo un single-container pod, editando il file di manifest scripts/pod.yml
:
mkdir -p ~/scripts
vim ~/scripts/pod.yml
kind: Pod
apiVersion: v1
metadata:
name: hello-pod
labels:
zone: prod
version: v1
spec:
containers:
- name: hello-ctr
image: nigelpoulton/k8sbook:1.0
ports:
- containerPort: 8080
Creazione del pod:
kubectl apply -f scripts/pod.yml
Impiega un po' di tempo poichè deve scaricare un'immagine dalla rete.
Check:
kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-pod 1/1 Running 0 6m53s
Si può monitorare il progresso degli eventi del pod con:
kubectl describe pod hello-pod
Il control-plane decide quale nodo avrà il pod. L’immagine per il container del pod è un pull dal registry principale Docker Hub.
Ispezione del pod
Verifica delle proprietà del pod:
kubectl get pods hello-pod -o yaml
- spec è il desired state
- status è il current state
- col tempo devono convergere
Descrivere il pod:
kubectl describe pods hello-pod
- Include gli eventi del pod
Pod logs:
kubectl logs hello-pod
- Nessuno nel nostro caso
Eseguire un comando nel pod:
kubectl exec hello-pod -- ps aux
PID USER TIME COMMAND
1 root 0:00 node ./app.js
20 root 0:00 ps aux
Connettersi ad un pod
Run di una shell in un pod:
kubectl exec -it hello-pod -- sh
Entro il pod ciò che è possibile fare dipende dal software installato.
Check dello hostname:
env | grep HOSTNAME
HOSTNAME=hello-pod
- Il nome di un pod è il suo hostname DNS
Uscire dal pod:
exit
Rimuovere il pod tramite il suo manifest:
kubectl delete -f scripts/pod.yml
La rimozione di un pod è un'azione relativamente lenta, anche se il messaggio compare subito.
Pulizia Globale
Listare i cluster:
kind get clusters
kind
Rimuovere il cluster:
kind delete cluster --name kind
Deleting cluster "kind" ...
Repository di Immagini in Locale
Scenario
- Sviluppiamo un applicativo
- Lo inseriamo in un'immagine
- Manteniamo l'immagine in un registry locale
- Lanciamo un pod che carica l'immagine dal registry locale
Quando si crea un pod, l’immagine del suo container è normalmente scaricata del registry del Docker Hub.
Vogliamo creare un’immagine locale, per motivi di confidenzialità, indipendenza ed economia.
Il registry locale deve essere visibile al cluster kubernetes.
- Servirà un cluster che lo permetta, tramite un opportuno plugin
- Occorrerà configurazione del cluster:
- nella costruzione
- a runtime
Sviluppo di Applicativo
Il programma di applicativo demo è un semplicissimo web server, scritto in linguaggio Go.
Ascoterà sulla porta 8888
e, quando contattato, invierà la stringa Hello, 世界
- Ciao Mondo in qualche lingua orientale.
Scaffolding
Prepariamo la locazione in cui sviluppare l'applicativo:
mkdir -p ~/demo/hello
cd ~/demo/hello
Programma
Si chiama main.go
:
vim main.go
package main
import (
"fmt"
"log"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, 世界")
}
func main() {
http.HandleFunc("/", handler)
fmt.Println("Running demo app. Press Ctrl+C to exit...")
log.Fatal(http.ListenAndServe(":8888", nil))
}
Inserimento Applicativo in Immagine
Non è necessario installare il software del compilatore Go. Si può far compilare il programma ad un contenitore, che in seguito viene scartato.
Usiamo un build a due fasi, con un Chained Dockerfile;
- viene compilato l'eseguibile
- l'eseguibile è inserito in un'immagine vuota
Il Dockerfile
è:
vim Dockerfile
FROM golang:1.17-alpine AS build
WORKDIR /src/
COPY main.go go.* /src/
ENV GO111MODULE="off"
RUN CGO_ENABLED=0 go build -o /bin/demo
FROM scratch
COPY --from=build /bin/demo /bin/demo
EXPOSE 8888
ENTRYPOINT ["/bin/demo"]
Ogni eseguibile compilato staticamente in Go include un mini sistema operativo che fornisce le funzionalità di scheduling e gestione della memoria.
Per questo nella seconda fase del build non c'è bisogno di partire da un sistema operativo di base e si può usare l'immagine di base
scratch
(vuota).
E' la direttiva di compilazione
CGO_ENABLED=0
che disabilita il link con le librerie dinamiche di sistema e produce un eseguibile linkato staticamente.
I linguaggio Go ha molti notevoli vantaggi quando si vuole produrre software da inserire in immagini Docker.
Build dell'Immagine
Si costruisce l'immagine myhello:
docker build -t myhello .
Verificarne l'avvenuta creazione con:
docker images
Nota
Il messaggio del comando docker build
avverte che build
è deprecato e verrà presto sostituito da buildx
.
Questo è un plugin di docker.
Per installarlo:
curl -L "https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-amd64" -o docker-buildx
mkdir -p ~/.docker/cli-plugins
mv docker-buildx ~/.docker/cli-plugins
chmod +x ~/.docker/cli-plugins/docker-buildx
docker buildx version
Il comando di build diventa allora:
docker buildx build -t myhello .
Registry Locale
Docker Hub offre l'immagine registry per un registry locale.
Occorre quindi un piccolo refactoring del progetto.
Refactoring del Progetto
Occorrono le seguenti attività:
- istanziare un contenitore docker per il registry
- creare il cluster con una opportuna patch che gli consenta di vedere il registry
- agganciare il registry alla rete del cluster
- creare un ConfigMap che documenti la presenza del registry
Il tutto è convenientemente raccolto in una procedura shell, copiata dalla documentazione del sito Kind, e leggermente modificata per il nostro progetto.
vim ~/registry.sh
#!/bin/sh
set -o errexit
# crea il contenitore del registry se non esiste
reg_name='kind-registry'
reg_port='5000'
if [ "$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" != 'true' ]; then
docker run \
-d --restart=always -p "127.0.0.1:${reg_port}:5000" --name "${reg_name}" \
-v $PWD/registry:/var/lib/registry registry:2
fi
# crea un cluster con il registry locale abilitato in containerd
cat <<EOF | kind create cluster --image kindest/node:v1.24.0 --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:${reg_port}"]
endpoint = ["http://${reg_name}:5000"]
nodes:
- role: control-plane
- role: worker
- role: worker
EOF
# connette il registry alla rete del cluster se non connesso
if [ "$(docker inspect -f='{{json .NetworkSettings.Networks.kind}}' "${reg_name}")" = 'null' ]; then
docker network connect "kind" "${reg_name}"
fi
# Documenta il local registry
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: local-registry-hosting
namespace: kube-public
data:
localRegistryHosting.v1: |
host: "localhost:${reg_port}"
help: "https://kind.sigs.k8s.io/docs/user/local-registry/"
EOF
Renderla eseguibile e lanciarla:
chmod +x ~/registry.sh
~/registry.sh
Check del cluster:
kubectl cluster-info --context kind-try
kubectl get nodes
Nostra immagine nel Registry Locale
Ora che abbiamo un registry locale, vi inseriamo l'immagine che abbiamo preparato, myhello
.
Il riferimento al registry che contiene un'immagine è parte del nome dell'immagine. Aggiungiamo quindi un altro nome:
docker tag myhello:latest localhost:5000/myhello:latest
Ora possiamo compiere il push dell'immagine al registry locale:
docker push localhost:5000/myhello:latest
Creazione di Pod
Un manifest ~/scripts/localdemo.yml
che compie il pull di un’immagine dal registry locale:
vim ~/scripts/localdemo.yml
kind: Pod
apiVersion: v1
metadata:
name: myhello-pod
labels:
zone: prod
version: v1
spec:
containers:
- name: myhello-ctr
image: localhost:5000/myhello
ports:
- containerPort: 8888
Creare il pod:
kubectl apply -f ~/scripts/localdemo.yml
Check, dopo un opportuno tempo di creazione:
kubectl get pods
kubectl get events
Il control-plane decide quale nodo avrà il pod. L’immagine del pod container è presa dal registry locale.
Installazione di un Load Balancer
Deployment
File di manifest ~/scripts/deploy.yml
:
vim ~/scripts/deploy.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-deploy
spec:
replicas: 3
selector:
matchLabels:
app: hello-world
template:
metadata:
labels:
app: hello-world
spec:
containers:
- name: hello-pod
image: localhost:5000/myhello
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8888
Yl parametro imagePullPolicy
specifica se compiere il pull dell'immagine specificata. Ha tre possibili valori:
- IfNotPresent - se non è già stato compiuto un pull in precedenza (default)
- Always - sempre, anche se l'immagine è già presente
- Never - mai. Naturalmente può causare dei problemi
Creare il deployment:
kubectl apply -f ~/scripts/deploy.yml
Inspezionare il deployment:
kubectl get deploy hello-deploy
kubectl describe deploy hello-deploy
Mostrare il ReplicaSet associato:
kubectl get rs
Service
Scrivere il manifest ~/scripts/svc.yml
:
vim ~/scripts/svc.yml
apiVersion: v1
kind: Service
metadata:
name: hello-svc
labels:
app: hello-world
spec:
type: NodePort
ports:
- port: 8888
nodePort: 30001
protocol: TCP
selector:
app: hello-world
Applicare il servizio:
kubectl apply -f ~/scripts/svc.yml
Tipi di Servizio
ClusterIP - default
- Indirizzo IP interno al cluster, effimero
- Tipico per i pods e i service interni
NodePort
- Indirizzo IP interno al cluster, a livello nodi, statico
LoadBalancer
- Indirizzo IP esterno al cluster, statico
- Tipico per i service esterni
- Fornito dall’implementazione del cluster, non da kubernetes (add-on)
Uso del Service
Listare i servizi:
kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-svc NodePort 10.96.125.102 <none> 8888:30001/TCP 50m
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 55m
Un servizio NodePort è visibile su tutti i nodi del cluster alla porta configurata dal settaggio nodePort
.
Trovare l’indirizzo IP di un nodo:
kubectl describe node kind-worker | grep InternalIP
InternalIP: 172.19.0.3
Naturalmente il risultato può variare da questo esempio.
Connettersi al servizio su quel nodo:
curl 172.19.0.3:30001
Hello, 世界
Metallb
Per vedere un servizio da fuori del cluster serve un Load Balancer.
KinD fornisce il Metallb load balancer.
Scarichiamo i due manifest di Metallb:
cd ~/scripts
wget https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/namespace.yaml
mv namespace.yaml metallb-ns.yml
wget https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/metallb.yaml
mv metallb.yaml metallb-svc.yml
cd
Il primo manifest crea un namespace:
kubectl apply -f ~/scripts/metallb-ns.yml
Il secondo manifest è complesso, e istanzia tutti gli oggetti di Metallb:
kubectl apply -f ~/scripts/metallb-svc.yml
Verificare il progresso:
kubectl get pods -n metallb-system
- Due immagini docker vengono scaricate
- Attendere che i pod metallb abbiano lo status di Running
ConfigMap per Metallb
Ci serve un range si indirizzi IP per metallb, e lo scopriamo col comando:
docker network inspect -f '{{.IPAM.Config}}' kind
Ritorna:
[{172.18.0.0/16 172.18.0.1 map[]} {fc00:f853:ccd:e793::/64 map[]}]
Poichè la rete 172.18.0.0/16
è visibile, decidiamo di usare il range di indirizzi 172.18.255.200-172.19.255.250
In altre occasioni può ritornare indirizzi diversi. Occorre usare quelli specifici ritornati.
Scriviamo un manifest per il ConfigMap, chiamandolo ~/scripts/metallb-configmap.yml
:
vim ~/scripts/metallb-configmap.yml
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- 172.18.255.200-172.18.255.250
Creare il configmap:
kubectl apply -f ~/scripts/metallb-configmap.yml
Nota
Può darsi in futuro che la rete a cui appartiene kind cambi.
In tal caso occorre editare e riapplicare il file metallb-configmap.yml
Lo si può fare in modo batch tramite la seguente procedura shell:
ipnet=$(docker network inspect -f '{{.IPAM.Config}}' kind | \
cut -d{ -f2 | cut -d. -f1,2)
newaddr=" - ${ipnet}.255.200-${ipnet}.255.250"
sed -i "/^.*172.*/s//$newaddr/" ~/scripts/metallb-configmap.yml
Load Balancer service
Scriviamo un manifest ~/scripts/lb.yml
che configura il servizio di tipo Load Balancer:
vim ~/scripts/lb.yml
apiVersion: v1
kind: Service
metadata:
name: lb-svc
labels:
app: hello-world
spec:
type: LoadBalancer
ports:
- port: 8888
protocol: TCP
selector:
app: hello-world
Creiamo il servizio:
kubectl apply -f ~/scripts/lb.yml
Verificare la creazione del servizio:
kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-svc NodePort 10.96.125.102 <none> 8888:30001/TCP 74m
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 79m
lb-svc LoadBalancer 10.96.109.230 172.19.255.200 8888:31740/TCP 55m
Provare a connettersi da qualsiasi macchina sul Docker cluster:
curl 172.18.255.200:8888
Hello, 世界
Proviamo a vedere il servizio da una macchina esterna al cluster.
Lanciare un alpine, esterno ma sulla stessa rete del cluster:
docker run -ti --rm --privileged --net kind alpine sh
Aggiungere curl e provare:
apk add curl
curl 172.18.255.200:8888
``
Servizi e Repliche
Esercizio
Sulla macchina host scaricare le immagini nginx:latest
e nginx:1.16
e porle nel registry locale:
docker pull nginx
docker pull nginx:1.16
docker tag nginx:latest localhost:5000/nginx:latest
docker push localhost:5000/nginx:latest
docker tag nginx:1.16 localhost:5000/nginx:1.16
docker push localhost:5000/nginx:1.16
Entrare nel contenitore con Kind.
Scrivere il file di configurazione ~/scripts/nginx-depl.yml
:
vim ~/scripts/nginx-depl.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: localhost:5000/nginx
ports:
- containerPort: 8080
Direttive globali:
apiVersion: apps/v1
- Versione API. Diversa per tipi diversi.kind: Deployment
- Tipo di oggetto da produrre
Seguono due sezioni:
metadata:
- Dati globali sull’oggettospec:
- Specifiche di costruzione dell’oggetto. E’ il desired state
Creare l’oggetto tramite il file:
kubectl apply -f ~/scripts/nginx-depl.yml
File interno di specifiche del deployment. Vederlo con:
kubectl get deployment nginx-deployment -o yaml
Piu complesso e completo di quello sottoposto
Include la terza sezione status
:
...
status:
availableReplicas: 1
conditions:
- lastTransitionTime: "2022-10-30T11:40:04Z"
lastUpdateTime: "2022-10-30T11:40:04Z"
message: Deployment has minimum availability.
...
observedGeneration: 1
readyReplicas: 1
replicas: 1
updatedReplicas: 1
Questo è il current state
Cambiare il numero di repliche
Stato corrente:
kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 1/1 1 1 4h48m
kubectl get replicasets
NAME DESIRED CURRENT READY AGE
nginx-deployment-5cbffcb86d 1 1 1 4h49m
kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-5cbffcb86d-qwthn 1/1 Running 0 4h49m
Cambiamento: editare il file di specifiche ~/scripts/nginx-depl.yml
Cambiare
replicas: 1
in
replicas: 3
'''
Sottomettere i cambiamenti:
```bash
kubectl apply -f ~/scripts/nginx-depl.yml
Verificare il cambiamento con i comandi:
kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 2/3 3 2 4h57m
kubectl get replicasets
NAME DESIRED CURRENT READY AGE
nginx-deployment-5cbffcb86d 3 3 2 4h57m
kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-5cbffcb86d-nkpgw 1/1 Running 0 17s
nginx-deployment-5cbffcb86d-qwthn 1/1 Running 0 4h57m
nginx-deployment-5cbffcb86d-vkvh5 1/1 Running 0 17s
kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 4h57m
kubectl get replicasets
NAME DESIRED CURRENT READY AGE
nginx-deployment-5cbffcb86d 3 3 3 4h57m
Il current state converge velocemente al nuovo desired state
Cambiare l'immagine
Editare il file di specifiche scripts/nginx-depl.yml
Cambiare
image: localhost:5000/nginx
in
image: localhost:5000/nginx:1.16
Sottomettere i cambiamenti:
kubectl apply -f ~/scripts/nginx-depl.yml
Verificare lo stato di aggiornamento:
kubectl get replicasets
NAME DESIRED CURRENT READY AGE
nginx-deployment-5cbffcb86d 3 3 3 5h6m
nginx-deployment-65bd9f5d9d 1 1 0 9s
kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-5cbffcb86d-nkpgw 1/1 Terminating 0 9m11s
nginx-deployment-5cbffcb86d-qwthn 1/1 Running 0 5h6m
nginx-deployment-5cbffcb86d-vkvh5 1/1 Running 0 9m11s
nginx-deployment-65bd9f5d9d-9znxk 1/1 Running 0 17s
nginx-deployment-65bd9f5d9d-bms9t 0/1 ContainerCreating 0 6s
e presto converge completamente al nuovo stato
Creare un Servizio
File di specifiche ~/scripts/nginx-serv.yml
:
vim ~/scripts/nginx-serv.yml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 8080
Creare con:
kubectl apply -f ~/scripts/nginx-serv.yml
Corrispondenza col Deployment
Vedere pods e servizio:
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-65bd9f5d9d-9znxk 1/1 Running 0 31m 10.244.2.3 std-worker2 <none> <none>
nginx-deployment-65bd9f5d9d-bms9t 1/1 Running 0 30m 10.244.1.4 std-worker <none> <none>
nginx-deployment-65bd9f5d9d-tf4kp 1/1 Running 0 30m 10.244.2.4 std-worker2 <none> <none>
kubectl describe service nginx-service
Name: nginx-service
...
Type: ClusterIP
...
IPs: 10.96.34.171
Port: <unset> 80/TCP
TargetPort: 8080/TCP
Endpoints: 10.244.1.4:8080,10.244.2.3:8080,10.244.2.4:8080
kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6h34m
nginx-service ClusterIP 10.96.34.171 <none> 80/TCP 8m54s
Cancellare Deployment e Servizio
Cancellare deployment direttamente:
kubectl delete deployment nginx-deployment
deployment.apps "nginx-deployment" deleted
Il servizio non viene cancellato ma se ispezionato con
kubectl describe service nginx-service
non vi sono più Endpoints
Cancellare il servizio tramite file di specifiche:
kubectl delete -f ~/scripts/nginx-serv.yml
service "nginx-service" deleted
Un Esercizio Completo
Progetto
Preparazione
Sulla macchina host:
Scaricare le immagini mongo e mongo-express e porle nel registry locale:
docker pull mongo
docker pull mongo-express
docker tag mongo localhost:5000/mongo
docker push localhost:5000/mongo
docker tag mongo-express localhost:5000/mongo-express
docker push localhost:5000/mongo-express
Avere le immagini nel registry locale velocizza le operazioni di creazione dei pod
1. Deployment del Database mongo
Editare il manifest ~/scripts/mongodb-deployment.yml
vim ~/scripts/mongodb-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongodb-deployment
labels:
app: mongodb
spec:
replicas: 1
selector:
matchLabels:
app: mongodb
template:
metadata:
labels:
app: mongodb
spec:
containers:
- name: mongodb
image: localhost:5000/mongo
ports:
- containerPort: 27017
env:
- name: MONGO_INITDB_ROOT_USERNAME
value: username
- name: MONGO_INITDB_ROOT_PASSWORD
value: password
Creare il deployment e verificare:
kubectl create -f ~/scripts/mongodb-deployment.yml
kubectl get pods
NAME READY STATUS RESTARTS AGE
mongodb-deployment-f974c5bdd-b7dc8 1/1 Running 0 41s
kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
mongodb-deployment 1/1 1 1 46s
Non è bello avere username e password in chiaro. Creiamo un file di Secrets
2. Secrets
Stringhe Base64 per username
e password
:
echo -n 'username' | base64
dXNlcm5hbWU=
echo -n 'password' | base64
cGFzc3dvcmQ=
File di specifiche ~/scripts/mongodb-secret.yml
:
vim ~/scripts/mongodb-secret.yml
apiVersion: v1
kind: Secret
metadata:
name: mongodb-secret
type: Opaque
data:
mongo-root-username: dXNlcm5hbWU=
mongo-root-password: cGFzc3dvcmQ=
Creazione del secret:
kubectl apply -f ~/scripts/mongodb-secret.yml
Modifica a ~/scripts/mongodb-deployment.yml
:
......
env:
- name: MONGO_INITDB_ROOT_USERNAME
valueFrom:
secretKeyRef:
name: mongodb-secret
key: mongo-root-username
- name: MONGO_INITDB_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mongodb-secret
key: mongo-root-password
Applicare i cambiamenti:
kubectl delete -f ~/scripts/mongodb-deployment.yml
kubectl create -f ~/scripts/mongodb-deployment.yml
Non si può fare un apply perchè value
e valueFrom
non possono esistere simultaneamente
3. Servizio per mongodb
File di manifest ~/scripts/mongodb-service.yml
:
vim ~/scripts/mongodb-service.yml
apiVersion: v1
kind: Service
metadata:
name: mongodb-service
spec:
selector:
app: mongodb
ports:
- protocol: TCP
port: 27017
targetPort: 27017
Creare il servizio:
kubectl create -f ~/scripts/mongodb-service.yml
Verificare:
kubectl get services
kubectl describe service mongodb-service
4. Configmap per mongo-express
File di specifiche ~/scripts/mongodb-configmap.yml
:
vim ~/scripts/mongodb-configmap.yml
apiVersion: v1
kind: ConfigMap
metadata:
name: mongodb-configmap
data:
database_url: mongodb-service
Creazione del ConfigMap:
kubectl create -f ~/scripts/mongodb-configmap.yml
5. Deployment per mongo-express
File di manifest ~/scripts/mongo-express-deployment.yml
:
vim ~/scripts/mongo-express-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongo-express-deployment
labels:
app: mongo-express
spec:
replicas: 1
selector:
matchLabels:
app: mongo-express
template:
metadata:
labels:
app: mongo-express
spec:
containers:
- name: mongo-express
image: localhost:5000/mongo-express
ports:
- containerPort: 8081
env:
- name: ME_CONFIG_BASICAUTH_USERNAME
value: admin
- name: ME_CONFIG_BASICAUTH_PASSWORD
value: admin
- name: ME_CONFIG_MONGODB_ADMINUSERNAME
valueFrom:
secretKeyRef:
name: mongodb-secret
key: mongo-root-username
- name: ME_CONFIG_MONGODB_ADMINPASSWORD
valueFrom:
secretKeyRef:
name: mongodb-secret
key: mongo-root-password
- name: ME_CONFIG_MONGODB_SERVER
valueFrom:
configMapKeyRef:
name: mongodb-configmap
key: database_url
Mongodb-express ultima versione è dotato anche di un'autenticazione di accesso Basic, per cui occorrono configurare le due variabili d'ambiente:
- name: ME_CONFIG_BASICAUTH_USERNAME
value: admin
- name: ME_CONFIG_BASICAUTH_PASSWORD
value: admin
Creare il deployment:
kubectl create -f ~/scripts/mongo-express-deployment.yml
6. Servizio per mongo-express
File di definizione ~/scripts/mongo-express-service.yml
:
vim ~/scripts/mongo-express-service.yml
apiVersion: v1
kind: Service
metadata:
name: mongo-express-service
spec:
selector:
app: mongo-express
ports:
- protocol: TCP
port: 8081
targetPort: 8081
Creare il servizio:
kubectl create -f ~/scripts/mongo-express-service.yml
Verificare:
kubectl get services
kubectl describe service mongo-express-service
7. Servizio esterno
Editare la configurazione del servizio ~/scripts/mongo-express-service.yml
:
vim ~/scripts/mongo-express-service.yml
apiVersion: v1
kind: Service
metadata:
name: mongo-express-service
spec:
selector:
app: mongo-express
type: LoadBalancer
ports:
- protocol: TCP
port: 8081
targetPort: 8081
nodePort: 30000
Applicare i cambiamenti:
kubectl apply -f ~/scripts/mongo-express-service.yml
Accesso al servizio``
Vedere l’indirizzo del servizio:
kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 45m
mongo-express-service LoadBalancer 10.96.193.136 172.18.255.200 8081:30000/TCP 4s
mongodb-service ClusterIP 10.96.237.220 <none> 27017/TCP 16m
Dal browser sullo host connettersi a: 172.18.255.200:8081
Sommario
Il LoadBalancer Metallb è attivo.
Sequenza di comandi per il nostro applicativo:
kubectl create -f ~/scripts/mongodb-secret.yml
kubectl create -f ~/scripts/mongodb-deployment.yml
kubectl create -f ~/scripts/mongodb-service.yml
kubectl create -f ~/scripts/mongodb-configmap.yml
kubectl create -f ~/scripts/mongo-express-deployment.yml
kubectl create -f ~/scripts/mongo-express-service.yml
Conviene creare due procedure shell per il setup e il teardown del nostro applicativo mongo.
vim ~/scripts/mongo-setup.sh
kubectl apply -f ~/scripts/mongodb-secret.yml
kubectl apply -f ~/scripts/mongodb-deployment.yml
kubectl apply -f ~/scripts/mongodb-service.yml
kubectl apply -f ~/scripts/mongodb-configmap.yml
kubectl apply -f ~/scripts/mongo-express-deployment.yml
kubectl apply -f ~/scripts/mongo-express-service.yml
echo "App mongo created"
vim ~/scripts/mongo-teardown.sh
kubectl delete -f ~/scripts/mongo-express-service.yml
kubectl delete -f ~/scripts/mongo-express-deployment.yml
kubectl delete -f ~/scripts/mongodb-configmap.yml
kubectl delete -f ~/scripts/mongodb-service.yml
kubectl delete -f ~/scripts/mongodb-deployment.yml
kubectl delete -f ~/scripts/mongodb-secret.yml
echo "App mongo deleted"
chmod +x ~/scripts/mongo-setup.sh ~/scripts/mongo-teardown.sh
Namespaces e Storaggio
Refactoring e automatizzazione di Kind
Cluster KinD versione 2
Tutti gli esercizi utilizzeranno una nuova versione del cluster costruito con Kind.
Editare la versione nuova:
cd
vim std.sh
#!/bin/sh
set -o errexit
# crea il contenitore del registry se non esiste
reg_name='kind-registry'
reg_port='5000'
if [ "$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" != 'true' ]; then
docker run \
-d --restart=always -p "127.0.0.1:${reg_port}:5000" --name "${reg_name}" \
-v $PWD/registry:/var/lib/registry registry:2
fi
# crea un cluster con il registry locale abilitato in containerd
cat <<EOF | kind create cluster --image kindest/node:v1.24.0 --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:${reg_port}"]
endpoint = ["http://${reg_name}:5000"]
nodes:
- role: control-plane
extraMounts:
- hostPath: /data
containerPath: /data
- role: worker
extraMounts:
- hostPath: /data
containerPath: /data
- role: worker
extraMounts:
- hostPath: /data
containerPath: /data
EOF
# connette il registry alla rete del cluster se non connesso
if [ "$(docker inspect -f='{{json .NetworkSettings.Networks.kind}}' "${reg_name}")" = 'null' ]; then
docker network connect "kind" "${reg_name}"
fi
# Documenta il local registry
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: local-registry-hosting
namespace: kube-public
data:
localRegistryHosting.v1: |
host: "localhost:${reg_port}"
help: "https://kind.sigs.k8s.io/docs/user/local-registry/"
EOF
Renderla eseguibile:
chmod +x ~/std.sh
La sezione cambiata è:
.....
nodes:
- role: control-plane
extraMounts:
- hostPath: /data
containerPath: /data
- role: worker
extraMounts:
- hostPath: /data
containerPath: /data
- role: worker
extraMounts:
- hostPath: /data
containerPath: /data
.....
Ogni nodo crea un mappaggio tra la sua directory /data
e la directory /data
delle macchina host.
Questo ci servirà per la gestione di volumi dati.
Procedure Shell per il Cluster
La creazione ed eliminazione del cluster verranno gestite da due procedure shell.
Procedura di creazione ~/setup.sh
:
vim ~/setup.sh
#! /bin/sh
echo "Creating cluster ..."
echo
echo "~/std.sh"
~/std.sh
echo "Cluster info follows ..."
kubectl cluster-info --context kind-kind
echo
echo "---> Loading and configuring the Metallb load balancer ..."
echo
echo "-- Namespace"
echo "kubectl apply -f scripts/metallb-ns.yml"
kubectl apply -f scripts/metallb-ns.yml
echo
echo "-- Deployment and service"
echo "kubectl apply -f scripts/metallb-svc.yml"
kubectl apply -f scripts/metallb-svc.yml
echo
echo "-- Configmap"
echo "kubectl apply -f scripts/metallb-configmap.yml"
kubectl apply -f scripts/metallb-configmap.yml
echo
echo "Wait up to 120s for Metallb controller deployment to be ready ..."
kubectl wait deployment -n metallb-system controller --for condition=Available=True --timeout=120s
echo
echo " CLUSTER NOW READY"
echo " ===> All resources in namespace 'default'"
kubectl get all
echo
echo " ===> All resources in namespace 'metallb-system'"
kubectl get all -n metallb-system
Procedura di eliminazione ~/teardown.sh
:
vim ~/teardown.sh
#! /bin/sh
trap 'echo "Interrupted. Cluster NOT deleted"; exit 100' SIGINT
echo "About to delete cluster 'std'"
echo "Press Control-C within 10 seconds to interrupt"
sleep 10
kind delete cluster
echo "Cluster deleted"
Rendere le due procedure eseguibili:
chmod +x ~/setup.sh
chmod +x ~/teardown.sh
Spazi nomi ed organizzazione
Namespace
- Organizzazione delle risorse
- Cluster virtuale in un cluster
Lista dei namespace col comando:
kubectl get namespaces
NAME STATUS AGE
default Active 104s
kube-node-lease Active 110s
kube-public Active 110s
kube-system Active 111s
local-path-storage Active 72s
Alcuni namespace sono di sistema e non devono essere toccati.
Il namespace di default è default
.
Ogni nuova risorsa viene creata qui dentro.
Uso di Namespace
Creazione di namespace:
kubectl create namespace my-namespace
Rimozione di namespace:
kubectl delete namespace my-namespace
Uso di namespace in file di specifiche, p.es in scripts/mysql-configmap.yml
:
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-configmap
namespace: my-namespace
data:
db_url: mysql-service.database
La configmap corrente è nel namespace my-namespace
.
Il servizio riferito mysql-service è nel namespace database
.
Organizzazione dei Namespace
Suddivisione organizzativa in:
- Namespace privati
- Namespace comuni - a cui i ns privati si riferiscono
Limitazioni.
Alcune risorse possono riferire ad altri ns ma non essere riferite da altri ns:
- ConfigMap
- Secret
Alcune risorse non appartengono a namespaces ma sono globali:
- Volume
- Node
Utilities per Namespaces
Krew
Gestione plugins per Kubernetes.
Krew ha bisogno del pacchetto ncurses
. In Alpine:
apk add ncurses
Installazione, per Bash su qualsiasi Linux:
(
set -x; cd "$(mktemp -d)" &&
OS="$(uname | tr '[:upper:]' '[:lower:]')" &&
ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/')" &&
KREW="krew-${OS}_${ARCH}" &&
curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/${KREW}.tar.gz" &&
tar zxvf "${KREW}.tar.gz" &&
./"${KREW}" install krew
)
E modificare il PATH:
echo 'export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"' >> .profile
Logout e login di nuovo.
kubectx e kubens
kubectx
- cambiare contesto (cluster)kubens
- cambiare namespace di default
Installati tramite krew
:
kubectl krew install ctx
kubectl krew install ns
echo "alias kubectx='kubectl ctx'" >> ~/.profile
echo "alias kubens='kubectl ns'" >> ~/.profile
Logout e login di nuovo.
Uso.
Listare i namespaces:
kubens
Cambiare namespace di default:
kubens namespace
Secondo Cluster
KinD supporta la creazione di più cluster.
Configurare un nuovo cluster con la configurazione ~/scripts/second.yml
:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
Creare il cluster:
kind create cluster --name second --config ~/scripts/second.yml --image kindest/node:v1.24.0
Di default siamo posizionati nel contesto del nuovo cluster.
Listare i contesti (cluster):
kubectx
Il contesto corrente è evidenziato.
Cambiare contesto:
kubectx second
Cancellare il cluster:
kind delete cluster --name second
Storaggio esterno al cluster
Kubernetes non si occupa della persistenza dei dati.
Deve essere fornita dall’esterno.
Vincoli dello storage:
- Non deve dipendere dal ciclo di vita dei pod
- Deve essere disponibile su tutti i nodi
- Deve sopravvivere al crash del cluster
Casi d’uso tipici:
- Database
- Files di configurazione
Persistent Volume
- Risorsa del cluster
- Fuori dai namespaces
- Creato con file YAML
- Mappa a storaggio fisico esterno al cluster
- Locale allo host
- Network File System
- Cloud
- ....
- L’amministratore del cluster crea i Persistent Volumes
- Design dei requisiti degli applicativi
Un PV è mappato a storaggio fisico locale o remoto.
Mappaggio Nodi tramite Directories
Non è noto a priori in quale nodo sarà un pod.
Deve comunque vedere il Persistent Volume.
Occorre che ogni nodo mappi una directory locale (containerPath) ad una directory globale di cluster (
hostPath`).
Il Persistent Volume si aggancia allo hostPath
.
Per questo il file di configurazione standard.yml
di Kind ha le sezioni:
Persistent Volume Claim
L’utente che compie il deployment di un applicativo nel cluster genera un Persistent Volume Claim (PVC)
- Nel namespace dell’applicativo
- Il Persistent Volume Claim mappa un Persistent Volume
- Creato con un file di manifest YAML
- Viene riferito del file di specifiche del pod o del deployment
Esercizio: Persistenza Postgres
Persistenza di applicativo postgres
La datadir di postgres è mappata ad un Persistent Volume.
Passi di Esecuzione
Per preparare un servizio postgres occorre:
- acquisire l’immagine nel registry locale
- PV e PVC per postgres
- secrets per postgres
- deployment e servizio per postgres
I secrets necessari si scoprono consultando sul web il sito per ‘postgres docker’
- nel nostro caso
POSTGRES_PASSWORD
Immagine in Registry Locale
Dalla macchina host:
docker pull postgres:12.2
docker tag postgres:12.2 localhost:5000/postgres:12.2
docker push localhost:5000/postgres:12.2
PV e PVC per postgres
Nel container kub, prepariamo la directory per gli scripts:
mkdir -p ~/scripts/pg
Il manifest è ~/scripts/pg/pg-volume.yml
vim ~/scripts/pg/pg-volume.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: postgres-pv-volume
spec:
storageClassName: standard
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /data/pg/
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: postgres-pv-claim
spec:
volumeName: postgres-pv-volume
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
Applicare la configurazione:
kubectl apply -f ~/scripts/pg/pg-volume.yml
Secrets per postgres
La password di postgres sarà ‘secret’. Quindi:
echo -n secret | base64
c2VjcmV0
Il manifest è ~/scripts/pg/pg-secret.yml
:
vim ~/scripts/pg/pg-secret.yml
apiVersion: v1
kind: Secret
metadata:
name: pg-secret
type: Opaque
data:
root-password: c2VjcmV0
Applicare il file di configurazione:
kubectl apply -f ~/scripts/pg/pg-secret.yml
Deployment e Servizio
Anch'essi si possono combinare in un’unica configurazione. E’ raro che vi sia un deployment senza servizio.
Il manifest è ~/scripts/pg/pg-deployment.yml
:
vim ~/scripts/pg/pg-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: pg-deployment
labels:
app: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
volumes:
- name: postgresdb
persistentVolumeClaim:
claimName: postgres-pv-claim
containers:
- name: postgres
image: localhost:5000/postgres:12.2
volumeMounts:
- name: postgresdb
mountPath: /var/lib/postgresql/data
ports:
- containerPort: 5432
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: pg-secret
key: root-password
---
apiVersion: v1
kind: Service
metadata:
name: pg-service
spec:
selector:
app: postgres
type: LoadBalancer
ports:
- protocol: TCP
port: 5432
targetPort: 5432
Applicare la configurazione:
kubectl apply -f ~/scripts/pg/pg-deployment.yml
Attendere la generazione dei pods con:
kubectl get pods
Test dell'Esercizio
Creare un oggetto in postgres
Connettersi al pod che ha postgres:
kubectl get pods
NAME READY STATUS RESTARTS AGE
pg-deployment-794d8fcb76-c2hnn 1/1 Running 0 72s
Collegarsi con una shell al pod:
kubectl exec -ti pg-deployment-794d8fcb76-c2hnn -- bash
Naturalmente il nome del pod sarà diverso.
Verificare di essere sul pod:
hostname
pg-deployment-794d8fcb76-c2hnn
Connettersi al database:
psql -U postgres
psql (12.2 (Debian 12.2-2.pgdg100+1))
Type "help" for help.
postgres=#
La connessione può fallire. Postgres la prima volta che viene lanciato crea la struttura della sua datadir, e questo può impiegare qualche minuto.
Semplicemente attendere e riprovare.
Postgres ha nella sua datadir il file di configurazione di accessi pg_hba.conf
. Contiene di default un settaggio che permette l'accesso come utente amministrativo postgres
dalla macchina locale, senza che venga chiaesta la password.
Listare i database correnti: \l
Creare un database: create database test;
Listare di nuovo i database correnti: \l
Uscire dalla connessione a database: \q
Rimuovere tutto e ricreare tutto da capo, poi verificare se il database test esiste ancora.
Prossimi passi:
- uscire dal pod:
exit
- rimuovere il cluster:
~/teardown.sh
Sulla macchina host verificare la persistenza dei dati:
sudo ls /data/pg
Ripartenza e Test di Connettività
cd
./setup.sh
Procedure Shell per l'Applicativo
Per automatizzare il setup e teardown dell’applicativo postgres creiamo due procedure shell, nella directory di postgres.
~/scripts/pg/pg-setup.sh
vim ~/scripts/pg/pg-setup.sh
#! /bin/sh
echo "Starting application: postgres"
kubectl apply -f ~/scripts/pg/pg-volume.yml
kubectl apply -f ~/scripts/pg/pg-secret.yml
kubectl apply -f ~/scripts/pg/pg-deployment.yml
echo
echo "Wait up to 120s for Postgres deployment to be ready ..."
kubectl wait deployment -n default pg-deployment --for condition=Available=True --timeout=120s
echo
echo " POSTGRES NOW READY"
echo " ===> Pods running:"
kubectl get pods
La rendiamo eseguibile:
chmod +x ~/scripts/pg/pg-setup.sh
~/scripts/pg/pg-teardown.sh
vim ~/scripts/pg/pg-teardown.sh
#! /bin/sh
echo "About to delete application 'postgres'"
echo "Press Control-C within 10 seconds to interrupt"
sleep 10
kubectl delete -f ~/scripts/pg/pg-deployment.yml
kubectl delete -f ~/scripts/pg/pg-secret.yml
kubectl delete -f ~/scripts/pg/pg-volume.yml
echo
echo "Application deleted: postgres"
La rendiamo eseguibile:
chmod +x ~/scripts/pg/pg-teardown.sh
Continua il Test dell'Applicativo
Setup dell’applicativo:
~/scripts/pg/pg-setup.sh
....
===> Pods running:
NAME READY STATUS RESTARTS AGE
pg-deployment-794d8fcb76-sqwtf 1/1 Running 0 17s
Verifica del servizio:
kubectl get svc
Collegamento al pod:
kubectl exec -ti pg-deployment-794d8fcb76-sqwtf -- bash
Collegamento a postgres:
psql -U postgres
Verifica dei database esistenti:
\l
Uscita da postgres:
\q
Uscita dal pod:
exit
Test di Connettività
Creare un container alpine sulla rete kind:
docker run -ti --rm --name alp --privileged --net kind alpine sh
Siamo nel container alpine. Installare il client postgres:
apk add postgresql-client
Connettersi:
psql -U postgres -h 172.18.255.200
Da il seguente errore:
psql: error: connection to server at "172.18.255.200", port 5432 failed: FATAL: no pg_hba.conf entry for host "10.244.2.1", user "postgres", database "postgres", SSL off
Uscire con exit
.
Il file di configurazione di accessi di Postgres, pg_hba.conf
, di default può non contenere linee di configurazione per l'accesso da host remoti.
Queste linee hanno uno dei seguenti formati:
host all all 0.0.0.0/0 md5
host all all all md5
Tenendo conto che la datadir di Postgres è mappata sulla macchina ospite alla directory /data/pg
, possiamo modificare il file di configurazione col comando:
sudo vim /data/pg/pg_hba.conf
aggiungendo la linea di configurazione:
host all all 0.0.0.0/0 md5
Occorre ora far ripartire il server postgres con:
kubectl delete -f ~/scripts/pg/pg-deployment.yml
kubectl apply -f ~/scripts/pg/pg-deployment.yml
Non potevamo modificare prima il file /data/pg/pg_hba.conf
perchè la directory /data/pg
ancora non esisteva, o non era ancora stata popolata come datadir.
Rilanciamo quindi il contenitore docker, installiamo il client postgres e riproviamo la connessione:
docker run -ti --rm --name alp --privileged --net kind alpine sh
apk add postgresql-client
psql -U postgres -h 172.18.255.200
Password for user postgres: secret
psql (14.5, server 12.2 (Debian 12.2-2.pgdg100+1))
Type "help" for help.
postgres=#
Test OK.
Uscire da postgres e dal container alpine.
Teardown dell’applicativo:
~/scripts/pg/pg-teardown.sh
Riscrittura di pg-setup.sh
Il problema è che non vogliamo editare un file a mano, ma vogliamo che tutte le attività vengano compiute in modo batch.
Riscriviamo quindi la procedura shell ~/scripts/pg/pg-setup.sh
.
vim ~/scripts/pg/pg-setup.sh
#! /bin/sh
echo "Starting application: postgres"
kubectl apply -f ~/scripts/pg/pg-volume.yml
kubectl apply -f ~/scripts/pg/pg-secret.yml
kubectl apply -f ~/scripts/pg/pg-deployment.yml
echo
echo "Wait up to 120s for Postgres deployment to be ready ..."
kubectl wait deployment -n default pg-deployment --for condition=Available=True --timeout=120s
echo
echo " POSTGRES NOW READY"
echo " ===> Pods running:"
kubectl get pods
# pod running the deployment
pod=$(kubectl get pod | grep pg-deployment | cut -d\ -f1)
echo "Waiting for datadir to be ready"
cycles=30
while true
do
cycles=`expr $cycles - 1`
if kubectl logs $pod | grep "ready to accept connections"
then
echo "Datadir ready"
break
else
if [ $cycles -le 0 ]
then
echo "Losing patience. Patch pg_hba.conf yourself"
exit 1
fi
echo "Not ready yet, waiting 10 seconds"
sleep 10
fi
done
if sudo grep "host" /data/pg/pg_hba.conf | grep 0.0.0.0 \
|| sudo grep "host all all all md5" /data/pg/pg_hba.conf
then
echo pg_hba.conf is already patched
else
sudo sed -i -e '$ahost all all 0.0.0.0/0 md5' /data/pg/pg_hba.conf
echo pg_hba.conf has been patched
echo Stopping the postgres service:
kubectl delete -f ~/scripts/pg/pg-deployment.yml
echo Restarting the postgres service:
kubectl apply -f ~/scripts/pg/pg-deployment.yml
echo "Wait up to 120s for Postgres deployment to be ready ..."
kubectl wait deployment -n default pg-deployment --for condition=Available=True --timeout=120s
echo
echo " POSTGRES NOW REALLY READY"
fi
Connessione Esterna
Da un semplice container sulla stessa rete del cluster:
docker run -ti --rm --net kind --name client \
postgres:12.2 psql -h 172.18.255.200 -U postgres
Da un container che implementa un'utility grafica:
docker run -d --rm --name omnidb --net kind -v "$HOME/pg/omnidb":/data -p 127.0.0.1:8000:8000 -p 127.0.0.1:25482:25482 t4skforce/omnidb
- collegarsi con un browser a
localhost:8000
- credenziali di login:
admin/admin
- creare una nuova scheda di connessione
- usare la connessione
- uscire da omnidb
Altra utility grafica:
docker run -d --net kind --name pgadmin -p 8080:80 -e 'PGADMIN_DEFAULT_EMAIL=mich@stormforce.ac' -e 'PGADMIN_DEFAULT_PASSWORD=supersecret' dpage/pgadmin4
Esercizio: Mysql e Wordpress
Progetto
Acquisizione immagini
Dalla macchina host:
docker pull mysql:5.6
docker tag mysql:5.6 localhost:5000/mysql:5.6
docker push localhost:5000/mysql:5.6
docker pull wordpress:4.8-apache
docker tag wordpress:4.8-apache localhost:5000/wordpress:4.8-apache
docker push localhost:5000/wordpress:4.8-apache
Prima Parte: MySQL
1. MY Namespace
Preparazione della directory con gli scripts specifici di Mysql:
mkdir -p ~/scripts/my
Manifest del namespace, ~/scripts/my/my-ns.yml
:
vim ~/scripts/my/my-ns.yml
kind: Namespace
apiVersion: v1
metadata:
name: mysql-db
labels:
name: mysql-db
Creazione:
kubectl apply -f ~/scripts/my/my-ns.yml
2. MY Persistent Volume
Manifest ~/scripts/my/my-volume.yml
:
vim ~/scripts/my/my-volume.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pv-volume
spec:
storageClassName: standard
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /data/my/
Creazione:
kubectl apply -f ~/scripts/my/my-volume.yml
3. MY Persistent Volume Claim
Manifest ~/scripts/my/my-volclaim.yml
:
vim ~/scripts/my/my-volclaim.yml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: my-pv-claim
namespace: mysql-db
spec:
volumeName: my-pv-volume
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
Creazione:
kubectl apply -f ~/scripts/my/my-volclaim.yml
4. MY Secret
Manifest ~/scripts/my/my-secret.yml
:
vim ~/scripts/my/my-secret.yml
apiVersion: v1
kind: Secret
metadata:
namespace: mysql-db
name: mysql-pass
type: Opaque
data:
# secret
root-password: c2VjcmV0
Creazione:
kubectl apply -f ~/scripts/my/my-secret.yml
5. MY Deployment
Manifest ~/scripts/my/my-deploy.yml
:
vim ~/scripts/my/my-deploy.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
namespace: mysql-db
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
tier: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: mysql
spec:
containers:
- image: localhost:5000/mysql:5.6
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass
key: root-password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: my-pv-claim
Creazione:
kubectl apply -f ~/scripts/my/my-deploy.yml
6. MY Service
Manifest ~/scripts/my/my-svc.yml
:
vim ~/scripts/my/my-svc.yml
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: mysql-db
labels:
app: wordpress
spec:
type: LoadBalancer
ports:
- protocol: TCP
port: 3306
targetPort: 3306
nodePort: 30001
selector:
app: wordpress
tier: mysql
Creazione:
kubectl apply -f scripts/my/my-svc.yml
Test di connessione al database:
kubectl get pods -n mysql-db
NAME READY STATUS RESTARTS AGE
mysql-75c59b6bf6-lb9xs 1/1 Running 0 11m
kubectl exec -ti mysql-75c59b6bf6-lb9xs -n mysql-db -- bash
-c "mysql -u root -p"
Naturalmente com il nome del pod giusto per il nostro caso.
Enter password: secret
....
mysql>
ove la password è secret.
Uscire da mysql e dal pod: \q
Procedure Shell per MySQL
Conviene preparare due procedure shell per il setup e il teardown della componente MySQL del progetto.
~/scripts/my/my-setup.sh
vim ~/scripts/my/my-setup.sh
#! /bin/sh
echo "Starting application: mysql"
kubectl apply -f ~/scripts/my/my-ns.yml
kubectl apply -f ~/scripts/my/my-secret.yml
kubectl apply -f ~/scripts/my/my-volume.yml
kubectl apply -f ~/scripts/my/my-volclaim.yml
kubectl apply -f ~/scripts/my/my-deploy.yml
kubectl apply -f ~/scripts/my/my-svc.yml
echo
echo "Wait up to 120s for Mysql deployment to be ready ..."
kubectl wait deployment -n mysql-db mysql --for condition=Available=True --timeout=120s
echo
echo " MYSQL NOW READY"
echo " ===> Pods running:"
kubectl get pods -n mysql-db
Renderla eseguibile:
chmod +x ~/scripts/my/my-setup.sh
~/scripts/my/my-teardown.sh
vim ~/scripts/my/my-teardown.sh
#! /bin/sh
trap 'echo "Interrupted. Application NOT deleted"; exit 100' SIGINT
echo "About to delete application 'mysql'"
echo "Press Control-C within 10 seconds to interrupt"
sleep 10
kubectl delete -f ~/scripts/my/my-svc.yml
kubectl delete -f ~/scripts/my/my-deploy.yml
kubectl delete -f ~/scripts/my/my-volclaim.yml
kubectl delete -f ~/scripts/my/my-volume.yml
kubectl delete -f ~/scripts/my/my-secret.yml
kubectl delete -f ~/scripts/my/my-ns.yml
echo
echo "Application deleted: mysql"
Renderla eseguibile:
chmod +x ~/scripts/my/my-teardown.sh
Seconda Parte: Wordpress
7. WP Persistent Volume
Preparazione della directory er gli script WordPress:
mkdir ~/scripts/wp
Manifest ~/scripts/wp/wp-volume.yml
:
vim ~/scripts/wp/wp-volume.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: wp-pv-volume
spec:
storageClassName: standard
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /data/wp/
Creazione:
kubectl apply -f ~/scripts/wp/wp-volume.yml
8. WP Persistent Volume Claim
Manifest ~/scripts/wp/wp-volclaim.yml
:
vim ~/scripts/wp/wp-volclaim.yml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: wp-pv-claim
spec:
volumeName: wp-pv-volume
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
Creazione:
kubectl apply -f ~/scripts/wp/wp-volclaim.yml
9. WP Secret
Manifest ~/scripts/wp/wp-secret.yml
:
vim ~/scripts/wp/wp-secret.yml
apiVersion: v1
kind: Secret
metadata:
name: mysql-pass
type: Opaque
data:
root-password: c2VjcmV0
Creazione:
kubectl apply -f ~/scripts/wp/wp-secret.yml
10. WP Deployment
Manifest ~/scripts/wp/wp-deploy.yml
:
vim ~/scripts/wp/wp-deploy.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
tier: frontend
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: frontend
spec:
containers:
- image: localhost:5000/wordpress:4.8-apache
name: wordpress
env:
- name: WORDPRESS_DB_HOST
value: mysql.mysql-db
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass
key: root-password
ports:
- containerPort: 80
name: wordpress
volumeMounts:
- name: wordpress-persistent-storage
mountPath: /var/www/html
volumes:
- name: wordpress-persistent-storage
persistentVolumeClaim:
claimName: wp-pv-claim
Creazione:
kubectl apply -f ~/scripts/wp/wp-deploy.yml
11. WP Service
Manifest ~/scripts/wp/wp-svc.yml
:
vim ~/scripts/wp/wp-svc.yml
apiVersion: v1
kind: Service
metadata:
name: wordpress
labels:
app: wordpress
spec:
type: LoadBalancer
ports:
- port: 8080
targetPort: 80
selector:
app: wordpress
tier: frontend
Creazione:
kubectl apply -f ~/scripts/wp/wp-svc.yml
Test di Funzionamento
Vedere l’indirizzo esterno del servizio:
kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 132m
wordpress LoadBalancer 10.96.27.200 172.19.255.200 8080:30002/TCP 21m
Aprire un browser e puntarlo a: 172.19.255.200:8080
Si dovrebbe vedere la configurazione iniziale di Wordpress
Procedure Shell per WordPress
Prepariamo due procedure shell per il setup e il teardown della componente WordPress del progetto.
~/scripts/wp/wp-setup.sh
vim ~/scripts/wp/wp-setup.sh
#! /bin/sh
echo "Starting application: wordpress"
kubectl apply -f ~/scripts/wp/wp-secret.yml
kubectl apply -f ~/scripts/wp/wp-volume.yml
kubectl apply -f ~/scripts/wp/wp-volclaim.yml
kubectl apply -f ~/scripts/wp/wp-deploy.yml
kubectl apply -f ~/scripts/wp/wp-svc.yml
echo
echo "Wait up to 120s for Wordpress deployment to be ready ..."
kubectl wait deployment wordpress --for condition=Available=True --timeout=120s
echo
echo " WORDPRESS NOW READY"
echo " ===> Pods running:"
kubectl get pods
echo " ===> Services running:"
kubectl get services
Renderla eseguibile:
chmod +x ~/scripts/wp/wp-setup.sh
~/scripts/wp/wp-teardown.sh
vim ~/scripts/wp/wp-teardown.sh
#! /bin/sh
trap 'echo "Interrupted. Application NOT deleted"; exit 100' SIGINT
echo "About to delete application 'wordpress'"
echo "Press Control-C within 10 seconds to interrupt"
sleep 10
kubectl delete -f ~/scripts/wp/wp-svc.yml
kubectl delete -f ~/scripts/wp/wp-deploy.yml
kubectl delete -f ~/scripts/wp/wp-volclaim.yml
kubectl delete -f ~/scripts/wp/wp-volume.yml
kubectl delete -f ~/scripts/wp/wp-secret.yml
echo
echo "Application deleted: wordpress"
Renderla eseguibile:
chmod +x ~/scripts/wp/wp-teardown.sh