Come anticipato, ecco i passaggi basilari per deployare WordPress su un’installazione di Kubernetes come quella dell’articolo precedente. L’unica differenza del setup precedente, è che ho abbandonato NFS Subdir External Provisioner in favore di Longhorn, ora sufficientemente maturo per garantire scalabilità (ReadWriteMany) e iperconvergenza della soluzione.
Per creare oggetti all’interno di Kubernetes si utilizza il formato YAML. È possibile creare singoli file per i singoli oggetti, in modo da poterli modificare “pezzo per pezzo”, oppure mettere tutto in unico file e applicarlo tutto insieme. Oppure, come preferisco io, si può utilizzare un Kubernetes IDE come Lens ed editare “al volo” gli oggetti, applicando le modifiche da GUI. Di seguito metterò solo il contenuto dello YAML.
Nel resto dell’articolo, il codice YAML inizierà con ‘—‘, quando invece opero da terminale, inizierà con ‘#‘.
Al fine di organizzare e compartimentare i deploy su Kubernetes, creiamo un namespace, dentro al quale metteremo tutti gli oggetti che compongono lo stack WordPress che stiamo creando:
---
apiVersion: v1
kind: Namespace
metadata:
name: marcobertorelloit-wp
labels:
name: marcobertorelloit-wp
Successivamente, creiamo una password che andremo ad utilizzare come password dell’utente root di Mysql.
# echo -n MyP4sSw0Rd! | base64
TXlQNHNTdzBSZCE=
Ora possiamo archiviare questa password in un secret di kubernetes e metterla a disposizione per essere utilizzata dal nostro deploy:
---
apiVersion: v1
kind: Secret
metadata:
name: wp-db-secrets
namespace: marcobertorelloit-wp
type: Opaque
data:
MYSQL_ROOT_PASSWORD: TXlQNHNTdzBSZCE=
La prima cosa di cui avrà bisogno il nostro Mysql, sarà un disco (o meglio, un Volume), dove archiviare in maniera persistente il nostro database. Su Kubernetes, sarà necessario creare una PersistenVolumeClaim, ovvero una richiesta, per il nostro storage provider, di uno spazio di archiviazione:
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-volume
namespace: marcobertorelloit-wp
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: longhorn
Adesso abbiamo tutto il necessario per poter descrivere il nostro Mysql sul cluster Kubernets:
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
namespace: marcobertorelloit-wp
labels:
app: mysql
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: database
image: mysql:8
envFrom:
- secretRef:
name: wp-db-secrets
ports:
- containerPort: 3306
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: mysql-volume
Il nostro database partirà, ma non ci sarà possibile accedervi, a meno di non creare un servizio per esso:
---
apiVersion: v1
kind: Service
metadata:
name: mysql-service
namespace: marcobertorelloit-wp
spec:
ports:
- port: 3306
protocol: TCP
selector:
app: mysql
A questo punto occorre fermarsi un attimo, perchè il “motore” del nostro db è partito (o sta partendo), ma occore creare in database che userà wordpress. Per fare questo identifichiamo il pod e colleghiamoci all’interno. Colleghiamoci a mysql e creiamo il db:
# k0s kubectl get pods -n marcobertorelloit-wp
NAME READY STATUS RESTARTS AGE
mysql-7774b9f74c-cnzmr 1/1 Running 0 24h
# k0s kubectl exec -it mysql-7774b9f74c-cnzmr -n marcobertorelloit-wp -- bash
bash-4.4# mysql -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 1161
Server version: 8.0.31 MySQL Community Server - GPL
Copyright (c) 2000, 2022, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> create database wordpress;
Query OK, 1 row affected (0.29 sec)
mysql>
Siamo quindi pronti alla costruzione del nostro WordPress. Anche questo componente è composto a sua volta, così come il precedente, da un PersistenVolumeClaim, un Deployment e un Servizio. In aggiunta andremo ad attivare una rete di Ingress, necessaria nel caso non si voglia utilizzare un Servizio LoadBalancer ma comoda a prescindere, sopratutto per la possibilità di applicarvi un WAF per la protezione del servizio. Al momento della creazione del servizio, dimostrerò le tre alternative: ClusterIP, NodePort e LoadBalancer.
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wordpress-volume
namespace: marcobertorelloit-wp
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: longhorn
Da notare l’accessModes, impostato a ReadWriteMany, necessario per poter far girare due repliche bilanciate del servizio web.
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
namespace: marcobertorelloit-wp
spec:
replicas: 2
selector:
matchLabels:
app: wordpress
template:
metadata:
labels:
app: wordpress
spec:
containers:
- name: wordpress
image: wordpress:latest
ports:
- containerPort: 80
name: wordpress-http
volumeMounts:
- name: wordpress-data
mountPath: /var/www/html
env:
- name: WORDPRESS_DB_HOST
value: mysql-service.marcobertorelloit-wp.svc.cluster.local
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: wp-db-secrets
key: MYSQL_ROOT_PASSWORD
- name: WORDPRESS_DB_USER
value: root
- name: WORDPRESS_DB_NAME
value: wordpress
volumes:
- name: wordpress-data
persistentVolumeClaim:
claimName: wordpress-volume
arrivati a questo punto si può scegliere con che tipo di servizio si vuole operare: ClusterIP, NodePort e LoadBalancer. Se avete seguito la guida precedente e disponete di Metallb, il nostro setup li supporta tutti e tre. Vediamo le differenze sostanziali:
ClusterIP: Exposes the service on a cluster-internal IP. Choosing this value makes the service only reachable from within the cluster. This is the default ServiceType
NodePort: Exposes the service on each Node’s IP at a static port (the NodePort). A ClusterIP service, to which the NodePort service will route, is automatically created. You’ll be able to contact the NodePort service, from outside the cluster, by requesting
<NodeIP>:<NodePort>
.LoadBalancer: Exposes the service externally using a cloud provider’s load balancer. NodePort and ClusterIP services, to which the external load balancer will route, are automatically created.
Personalmente uso un servizio ClusterIP, insieme alla Ingress per poter raggiungere il servizio sull’ indirizzo IP assegnato al LoadBalancer della Ingress. Tuttavia vedremo tutte e tre le configurazioni.
ClusterIP+Ingress:
---
kind: Service
apiVersion: v1
metadata:
name: wordpress-service
namespace: marcobertorelloit-wp
spec:
type: ClusterIP
selector:
app: wordpress
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-wordpress
namespace: marcobertorelloit-wp
spec:
rules:
- host: www.marcobertorello.it
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: wordpress-service
port:
number: 80
ingressClassName: nginx
NodePort:
---
kind: Service
apiVersion: v1
metadata:
name: wordpress-service
namespace: marcobertorelloit-wp
spec:
type: NodePort
selector:
app: wordpress
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
LoadBalancer:
---
kind: Service
apiVersion: v1
metadata:
name: wordpress-service
namespace: marcobertorelloit-wp
spec:
type: LoadBalancer
selector:
app: wordpress
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
A questo punto, il mio sito WordPress è stato deployato con successo sul mio cluster Kubernetes (in precedenza girava con un setup simile, ma su un cluster Docker Swarm).