Introduzione

Kuberlogo1

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

Gfdl

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

Dockerlogo1

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

Appmono

Applicativo a Microservizi

Appmicro

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à

Microcont

Architettura di Docker

Docker

Hykes

  • 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

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

ufs

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

Compon

  • 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

Cliser

  • 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

Socket

Il collegamento tra client e server avviene tramite un socket. In locale (default) questo è un socket Unix.

Tipi di Contenitori

Tipicont

  • 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

DDesk

Nuova offerta Docker

Basato su Macchina Virtuale Linux

Dashboard:

Ddashboard

  • 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 systemctlazionedocker

ove azione può essere:

  • status - stato del servizio e processi attivi
  • start - far partire il servizio
  • stop - fermare il servizio
  • enable - configurare lo start automatico al boot
  • disable - disabilitare lo start automatico al boot

Due componenti del server:

  • docker.service - il servizio
  • docker.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:

dockerambiente comando

Alcuni comandi storici non hanno bisogno della specifica dell’ambiente e sono del formato:

dockercomando

Per avere un veloce aiuto sui comandi disponibili:

docker

Per avere un veloce aiuto sui comandi disponibili in un certo ambiente:

dockerambiente

dockerambiente--help - da anche le opzioni

Ambienti

Ogni ambiente permette la gestione di un diverso aspetto di Docker:

  • config - configurazioni del cluster Swarm
  • container – contenitori di processo
  • image - immagini
  • network – contenitori di reti
  • node - nodi del cluster Swarm
  • plugin - plugins
  • secret - segreti del cluster Swarm
  • service - servizi offerti dal cluster Swarm
  • swarm - il cluster Swarm
  • system - aspetti generali del sistema
  • trust - fiducia delle immagini
  • volume - 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:

dockerambiente comando

ha manualistica con

man docker-ambiente-comando

Esempi:

man docker
man docker-run
man docker-network-create

Nota: Alpine Linux

Alpine

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

Kublogo

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

Kubstoria

Macchine Virtuali e Contenitori

Macchine Virtuali

Mvirt

Virtualizzazione dell'Hardware

  • Più sistemi operativi diversi
  • Più flessibile
  • Più maturo

Contenitori

Conts

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

Tipiconts

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

Clusterk

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

Masterc

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

Nodec

  • 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
    • Possibili plugins e utilities aggiuntive

Topologia di Cluster Kubernetes

Rete Locale

Clocnet

In una rete locale

  • Su hardware dedicato, PC o Blades
  • Richiede installazione complessa e manutenzione

Cloud

Ccloud

In un cloud

  • Soluzione tipica in produzione
  • Richiede sottoscrizione ad un Cloud Provider
  • Costi a metratura di utilizzo

Macchine Virtuali

Cvirt

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

Cldocker

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

Structmini

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 cluster
  • minikube stop - ferma il cluster, ma non toglie il contenitore docker che lo implementa
  • minikube pause - mette in pausa, non ha impatto sugli applicativi già deployed, ma non accetta comandi
  • minikube unpause - toglie dalla pausa
  • minikube 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

Compkub

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

Mpod1

Comunicano tra loro tramite localhost

Mpod2

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

Scalpod

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

Effimeri

Servizi

Service

  • Endpoints di rete persistenti e affidabili per i pod
  • Include il servizio DNS
  • Un oggetto di K8s

Associare i Pod ai Servizi

Assoc

  • Ottenuto tramite un insieme di Labels
  • Associati quando i pod hanno lo stesso sottoinsieme dei serrvizi

ReplicaSet

Replica

  • 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

Deploy

  • Oggetto di prima classe
  • Specificato da Manifest
    • YAML
    • JSON
  • Inviato via REST allo API Server
  • Molte features
    • Rollouts
    • Rollbacks

Ingress

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

Imperpod

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 podnomepod

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.

Podkill

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:

  1. Il client Kubernetes (kubectl) invia una richiesta allo API Server di creazione di un pod come definito nel file di specifica pod/db.yml
  2. Lo Scheduler monitorizza lo API Server e scopre che vi è un pod non assegnato
  3. Lo Scheduler decide quale nodo assegnare al pod ed invia tale informazione allo API Server
  4. Anche Kubelet del nodo assegnato monitorizza lo API Server e scopre che gli è stato assegnato il pod
  5. 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
  6. Kubelet notifica lo API Server che il pod è stato creato con successo

Umlseq


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

Podphases

E' il campo Status bel rapporto di
kubectl describe podnomepod.

  • 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 fallimento
  • Never - 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 running
  • Prestop - 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)

Kindlogo

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

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.

Kindpod

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

  1. Sviluppiamo un applicativo
  2. Lo inseriamo in un'immagine
  3. Manteniamo l'immagine in un registry locale
  4. 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;

  1. viene compilato l'eseguibile
  2. 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

Docknew

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

Localpod

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

Nginxdepl

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’oggetto
  • spec: - 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

Nginxsvc

Creare con:

kubectl apply -f ~/scripts/nginx-serv.yml

Corrispondenza col Deployment

Ngincorr

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

Exprog

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=

Secrfile

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

Secrmod

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

Ns1

  • 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.

Ns-default

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

Ns-org

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.

Pvmap

Mappaggio Nodi tramite Directories

Non è noto a priori in quale nodo sarà un pod.

Deve comunque vedere il Persistent Volume.

Pvnodemap

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:

Confsec

Persistent Volume Claim

Pvc1

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

Perspost

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

Pgprog

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

Conclusione