Nella lezione precedente abbiamo iniziato a parlare di un'applicazione su Kubernetes in grado di mettere al lavoro i concetti visti sinora ed attivare i principali ingranaggi di questa splendida piattaforma.
Si tratta di una semplice applicazione Web in PHP che accede ad un database MySQL: quest'ultimo verrà precaricato con un database di prova creato in fase di inizializzazione mentre la prima consisterà in una semplice pagina che offrirà i dati letti in una tabella.
Nonostante il caso non appaia particolarmente complicato ci aiuterà ad imparare ad organizzare un'applicazione (avrà due livelli: un data layer con MySQL ed un Web layer con Apache/PHP), ad impiegare immagini Docker personalizzate nonché a sperimentare il service discovery, meccanismo innato che permette a servizi della stessa applicazione di trovarsi tra loro senza conoscere esplicitamente i reciproci indirizzi IP.
Riepiloghiamo i principi fondanti su cui ci stiamo muovendo:
- ogni layer sarà costituito da un Deployment che gestirà i Pod necessari mentre la sua esposizione verso il resto dell'applicazione sarà offerta da un Service. Come si vedrà, per prima cosa, andremo a definire le immagini Docker che definiranno i container da attivare;
- visto che il database MySQL fornirà accesso mediante password quest'ultima sarà conservata in un Secret cui dovrà fare accesso sia il data layer sia il Web layer. Nel primo caso, abbiamo già visto, la password non sarà altro che la password dell'utente
root
- l'applicazione Web potrà connettersi al database conoscendo il nome del Service che lo rappresenta e lo userà come suo indirizzo. Mediante il service discovery, Kubernetes stabilirà i contatti.
Gestione della password
Per prima cosa, ricordiamo, come già visto nella lezione precedente, che per i nostri scopi creeremo in modalità imperativa un Secret che conterrà la password. Questo il comando da noi utilizzato che viene riportato qui per comodità:
kubectl create secret generic \
passwd-secret --from-literal=password=topolino
Creazione delle immagini
Parliamo ora delle immagini che definiremo. Visto che vogliamo precaricare il database da leggere, abbiamo scelto (non era l'unica soluzione) di immettere nell'immagine un file SQL di inizializzazione di nome init.sql
. Eccone il testo:
CREATE DATABASE IF NOT EXISTS scuola;
USE scuola;
CREATE TABLE allievi(
nome varchar(50),
cognome varchar(50),
classe int);
INSERT INTO allievi (nome,cognome,classe) VALUES ('Silvia', 'Rossi', 1);
INSERT INTO allievi (nome,cognome,classe) VALUES ('Elena', 'Neri', 1);
INSERT INTO allievi (nome,cognome,classe) VALUES ('Michele', 'Bianchi', 2);
INSERT INTO allievi (nome,cognome,classe) VALUES ('Simone', 'Azzurri', 2);
INSERT INTO allievi (nome,cognome,classe) VALUES ('Sergio', 'Rossi', 3);
INSERT INTO allievi (nome,cognome,classe) VALUES ('Ilenia', 'Gialli', 3);
Il Dockerfile che lo trasformerà in un'immagine è questo:
FROM mysql:5.6
COPY init.sql /docker-entrypoint-initdb.d/
Per quanto riguarda il Web layer abbiamo un Dockerfile che si basa su un'immagine Docker per PHP su server Apache. Aggiungeremo solo l'accortezza di chiedere l'installazione della libreria mysqli
per accedere a MySQL:
FROM php:8.0-apache
RUN docker-php-ext-install mysqli
WORKDIR /var/www/html
COPY index.php index.php
EXPOSE 80
Il file index.php
conterrà il codice per eseguire una query sull'unica tabella del database e mostrarla in una griglia: ne riportiamo uno stralcio limitandoci al codice PHP evitando il testo del CSS non indispensabile al nostro scopo:
<?php
$hostname = "data-layer-service";
$username = "root";
$password = getenv('MYSQL_ROOT_PASSWORD');
$dbname = "scuola";
$con = mysqli_connect($hostname, $username, $password, $dbname);
if(!$con)
{
die("Connessione fallita! Verificare parametri del server di database: " . mysqli_connect_error());
}
$sql = "SELECT nome,cognome,classe FROM allievi";
$risultato = mysqli_query($con, $sql);
if(mysqli_num_rows($risultato) > 0)
{
echo '<table> <tr> <th> Nome </th> <th> Cognome </th> <th> Classe </th> </tr>';
while($riga = mysqli_fetch_assoc($risultato)){
echo '<tr > <td>' . $riga["nome"] . '</td>
<td>' . $riga["cognome"] . '</td>
<td> ' . $riga["classe"] . '</td></tr>';
}
echo '</table>';
}
else
{
echo "Nessun risultato!";
}
mysqli_close($con);
?>
Il risultato che otterremo è quello che si vede nella figura seguente.
In questo codice notiamo un paio di aspetti molto importanti:
- l'indirizzo del database corrisponde a
data-layer-service
ovvero il nome del Service Kubernetes che espone il database (lo si verifichi nel prossimo paragrafo); - la password del database verrà immessa nel Deployment come variabile d'ambiente di nome
MYSQL_ROOT_PASSWORD
(verificare anche questo nel prossimo paragrafo) quindi dovrà essere letta dal codice PHP accedendovi congetenv('MYSQL_ROOT_PASSWORD')
.
Solitamente, le immagini di questo tipo vengono create ed immagazzinate in un registry (tipicamente disponibile in Cloud) ma in questo caso stiamo sperimentando su minikube
quindi, affinché questo tool abbia a disposizione le immagini, dobbiamo farle compilare a lui con i comandi che seguono. Lanceremo questo dalla cartella in cui si trova il Dockerfile dell'immagine del database:
$ minikube image build -t data-layer-custom .
Questo dalla cartella in cui si trova il Dockerfile dell'immagine del Web layer:
$ minikube image build -t web-layer-custom .
Infine potremo verificare la loro presenza negli asset di minikube con il seguente comando:
$ minikube image ls
...
docker.io/library/web-layer-custom:latest
docker.io/library/php:8.0-apache
docker.io/library/mysql:5.6
docker.io/library/data-layer-custom:latest
...
Componenti Kubernetes
Siamo ora pronti per lanciare le componenti Kubernetes necessarie. Riportiamo anche il data layer, già provato nella lezione precedente, sia per comodità sia per mostrare come impiegare la nuova immagine di cui abbiamo parlato qui:
apiVersion: apps/v1
kind: Deployment
metadata:
name: data-layer-deployment
spec:
selector:
matchLabels:
app: mysqlsrv
template:
metadata:
labels:
app: mysqlsrv
spec:
containers:
- name: mysql-server
image: data-layer-custom
imagePullPolicy: Never
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: passwd-secret
key: password
volumeMounts:
- name: data-storage
mountPath: /var/lib/mysql
volumes:
- name: data-storage
hostPath:
path: /dbstorage
---
apiVersion: v1
kind: Service
metadata:
name: data-layer-service
spec:
ports:
- port: 3306
selector:
app: mysqlsrv
Il Web layer avrà una struttura simile e sarà predisposto per essere contattato da utenza esterna mentre il data layer è a uso esclusivo interno.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-layer-deployment
labels:
server: web-layer
spec:
selector:
matchLabels:
server: web-layer
template:
metadata:
labels:
server: web-layer
spec:
containers:
- name: web-layer
imagePullPolicy: Never
image: web-layer-custom
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: passwd-secret
key: password
---
apiVersion: v1
kind: Service
metadata:
name: web-layer-service
spec:
type: NodePort
ports:
- port: 80
selector:
app: web-layer
A questo punto potremo avviare il data layer:
kubectl apply -f data-layer.yaml
ed il Web layer:
kubectl apply -f web-service.yaml
per poi verificarne l'effettiva operatività grazie a kubectl
:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
data-layer-service ClusterIP 10.100.19.62 <none> 3306/TCP 68s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7m1s
web-layer-service NodePort 10.96.53.253 <none> 80:31427/TCP 31s
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
data-layer-deployment 1/1 1 1 90s
web-layer-deployment 1/1 1 1 53s
A questo punto, si potrà visualizzare la tabella mostrata all'inizio di questa lezione invocando il Service del Web layer dal browser con le modalità mostrate durante la guida ed in dipendenza dell'installazione di minikube o Kubernetes che si è scelta.
Da qui potete scaricare il codice della lezione.