K8S WordPress applicatie

Nu de Kubernetes Clusterup-and-running‘ is kan er een applicatie op geplaatst worden. Als toepassing ga ik een WordPress site maken die uit twee deployments bestaat: WordPress en mySQL. De data voor zowel de WordPress site zelf als de mySQL database plaats ik een een NFS volume. Het plaatje ziet er alsvolgt uit:

Gebruikers die de URL aankiezen komen via een loadbalancer binnen (ik gebruik hier MetalLB voor) en worden naar de wordpress-service gestuurd die benaderbaar is via een networkpolicy die uitsluitend http-verkeer naar de wordpress-deployment toestaat.

De wordpress-deployment maakt gebruik van de mySQL database via een networkpolicy die alleen de wordpress-pods toestaat om via TCP-port 3306 naar de mySQL-deployments te verbinden.

mySQL is toegankelijk met behulp van een mysql-root-password die in een secret opgeslagen is en zodoende kan door de applicatie een wordpress-database aangemaakt worden.

Alle data voor WordPress en mySQL wordt opgslagen in een NFS share die bereikbaar is via twee persistent-volume-claims die gebruik maken van de persistent-volumes die van te voren zijn aangemaakt.

Namespace en secret

Voor de gehele applicatie wordt uiteraard gebruik gemaakt van een eigen namespace genaamd wordpress:

$ kubectl create ns wordpress

Vervolgens maken we hierin de secret voor het mysql-wachtwoord:

$ kubectl -n wordpress create secret generic mysql-pass --from-literal=password=type-een-wachtwoord

Pesistent Volume

De manifest genaamd pv-wordpress.yml voor de WordPress PersistentVolume ziet er alsvolgt uit:

apiVersion: v1
kind: PersistentVolume
metadata:
   name: nfs-wordpress
   namespace: wordpress
spec:
   capacity:
     storage: 100Gi
   accessModes:
     - ReadWriteOnce
   persistentVolumeReclaimPolicy: Recycle
   nfs:
     server: 192.168.1.123
     path: /nfs/wordpress

WordPress Deployment

Als eerste wordt gebuik gemaakt van een PV-claim genaamd pvc-wordpress.yml om het volume te ‘binden‘:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
   name: wp-pv-wordpress
   namespace: wordpress
   labels:
     app: wordpress
spec:
   accessModes:
     - ReadWriteOnce
   resources:
     requests:
       storage: 100Gi

Vervolgens dient er een Service genaamd svc-wordpress.yml van het type LoadBalancer gemaakt te worden die http-verkeer toestaat naar de deployment voor de pod(s) die de labels app=wordpress en tier=frontend hebben: (later zou ik daar https van maken)

apiVersion: v1
kind: Service
metadata:
   name: wordpress
   namespace: wordpress
   labels:
     app: wordpress
spec:
   ports:
     - port: 80
   selector:
     app: wordpress
     tier: frontend
   type: LoadBalancer

En tenslotte dan de manifest voor de deployment zelf, genaamd deploy-wordpress.yml:

apiVersion: apps/v1
kind: Deployment
metadata:
   name: wordpress
   namespace: wordpress
   labels:
     app: wordpress
spec:
   selector:
     matchLabels:
       app: wordpress
       tier: frontend
   strategy:
     type: Recreate
   template:
     metadata:
       labels:
         app: wordpress
         tier: frontend
     spec:
       replicas: 1
       containers:
       - image: nokkie/wordpress-rpi:latest
         name: wordpress
         env:
         - name: WORDPRESS_DB_HOST
           value: wordpress-mysql
         - name: WORDPRESS_DB_PASSWORD
           valueFrom:
             secretKeyRef:
               name: mysql-pass
               key: password
         securityContext:
           runAsUser: 0
           runAsGroup: 0
         ports:
         - containerPort: 80
           name: wordpress
         volumeMounts:
         - name: wordpress-persistent-storage
           mountPath: /var/www/html
       volumes:
       - name: wordpress-persistent-storage
         persistentVolumeClaim:
           claimName: wp-pv-wordpress

Wat hier opvalt is het gebruik van de securityContext voor uid:0 en guid:0. Dit is omdat de WordPress app deze rechten nodig heeft om de configuratie bestanden voor de php-site aan te passen.

Mocht het druk worden op de site, dan kan ik replicas toevoegen, vooralsnog houd ik de standaard van 1 replica aan.

Ik ga nu eerst deze deployment starten zodat ik zeker weet dat de PVC de enige PV gebruikt die er is, namelijk de wordpress-pv. Dit is dan de volgorde:

$ kubectl apply -f pv-wordpress.yml
$ kubectl apply -f pvc-wordpress.yml
$ kubectl apply -f svc-wordpress.yml
$ kubectl apply -f deploy-wordpress.yml

Controle

Ter controle ga ik de PV en PVC status bekijken, de service opvragen en zien of de WordPress deployment ‘READY’ is:

$ kubectl -n wordpress get pv nfs-wordpress
NAME            CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                          STORAGECLASS   REASON   AGE
nfs-wordpress   100Gi      RWO            Recycle          Bound    wordpress/wp-pv-wordpress                              37s

$ kubectl -n wordpress get svc wordpress
NAME              TYPE           CLUSTER-IP     EXTERNAL-IP       PORT(S)        AGE
wordpress         LoadBalancer   10.104.38.24   192.168.123.101   80:31212/TCP   37s

$ kubectl -n wordpress get deploy wordpress
NAME              READY   UP-TO-DATE   AVAILABLE   AGE
wordpress         1/1     1            1           35s

Dat ziet er goed uit, door naar de database!

mySQL Deployment

Als eerste de PersistentVolume genaamd pv-mysql.yml voor het mysql-volume:

apiVersion: v1
 kind: PersistentVolume
 metadata:
   name: nfs-mysql
   namespace: wordpress
 spec:
   capacity:
     storage: 100Gi
   accessModes:
     - ReadWriteOnce
   persistentVolumeReclaimPolicy: Recycle
   nfs:
     server: 192.168.1.123
     path: /nfs/mysql

De PV-Claim pvc-mysql.yml voor mySQL ziet er alsvolgt uit:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
   name: mysql-pv-wordpress
   namespace: wordpress
   labels:
     app: wordpress
spec:
   accessModes:
     - ReadWriteOnce
   resources:
     requests:
       storage: 100Gi

De service svc-mysql.yml is alsvolgt:

apiVersion: v1
kind: Service
metadata:
   name: wordpress-mysql
   namespace: wordpress
   labels:
     app: wordpress
spec:
   ports:
     - port: 3306
   selector:
     app: wordpress
     tier: mysql
   clusterIP: None

Hier valt op de we geen clusterIP aanmaken, hierdoor kan de service uitsluitend benaderd worden via svc-naam, oftewel wordpress-mysql zodat dit door de coreDNS wordt afgehandeld. Je ziet dit terug in de wordpress-deployment manifest bij de environment-variable WORDPRESS_DB_HOST.

Verder met de deployment voor mysql, genaamd deploy-mysql.yml:

apiVersion: apps/v1
kind: Deployment
metadata:
   name: wordpress-mysql
   namespace: wordpress
   labels:
     app: wordpress
spec:
   selector:
     matchLabels:
       app: wordpress
       tier: mysql
   strategy:
     type: Recreate
   template:
     metadata:
       labels:
         app: wordpress
         tier: mysql
     spec:
       containers:
       - image: nokkie/mariadb-arm:latest
         name: mysql
         env:
         - name: MYSQL_ROOT_PASSWORD
           valueFrom:
             secretKeyRef:
               name: mysql-pass
               key: password
         securityContext:
           runAsUser: 1000
           runAsGroup: 1000
         ports:
         - containerPort: 3306
           name: mysql
         volumeMounts:
         - name: mysql-persistent-storage
           mountPath: /var/lib/mysql
       volumes:
       - name: mysql-persistent-storage
         persistentVolumeClaim:
           claimName: mysql-pv-wordpress

Hier gebruik ik de securityContext uid:1000 en guid:1000 om de mysql-useraccount de rechten te geven om de database aan te maken in het NFS-Volume. Ik da de mysql-deployment ‘uitrollen’:

$ kubectl apply -f pv-mysql.yml
$ kubectl apply -f pvc-mysql.yml
$ kubectl apply -f svc-mysql.yml
$ kubectl apply -f deploy-mysql.yml

Controle

Nu de mysql ook deployed is ga ik controleren of het goed opgestart is, de PV en PVC status, de Service en de Deployment:

$ kubectl -n wordpress get pv nfs-mysql
NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                          STORAGECLASS   REASON   AGE
nfs-mysql   100Gi      RWO            Recycle          Bound    wordpress/mysql-pv-wordpress                           37s

$ kubectl -n wordpress get svc wordpress-mysql
NAME              TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
wordpress-mysql   ClusterIP   None                 3306/TCP   37s

$ kubectl -n wordpress get deploy wordpress-mysql
NAME              READY   UP-TO-DATE   AVAILABLE   AGE
wordpress-mysql   1/1     1            1           37s

Ziet er ook goed uit!

Security

Ik ga een en ander nu beveiligen met behulp van networkPolicies. Hiervoor dient er wel een CNI te zijn die dat ondersteund en aangezien ik Weave gebruik is dat geen probleem.

De eerste stap is om de gehele wordpress namespace te isoleren met een deny-all policy. Ik maak een manifest genaamd netpol-deny-all.yml:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
   name: default-deny-ingress
spec:
   podSelector: {}
   policyTypes:
   - Ingress 

Vervolgens is het noodzakelijk om http-verkeer toe te staan naar alle pods die het labeltier=frontend‘ hebben. Even ter controle:

$ kubectl -n wordpress get po --show-labels
NAME                               READY   STATUS    RESTARTS   AGE   LABELS
wordpress-7fcf7c6789-jbw6c         1/1     Running   0          36h   app=wordpress,pod-template-hash=7fcf7c6789,tier=frontend
wordpress-mysql-7996d586c8-qtdnm   1/1     Running   0          37h   app=wordpress,pod-template-hash=7996d586c8,tier=mysql

Het manifest netpol-allow-http.yml ziet er dan zo uit:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
   name: allow-all-ingress
spec:
   podSelector:
     matchLabels:
       tier: frontend
   ingress:
   - from: 
     ports: 
       protocol: TCP
       port: 80
   policyTypes:
   -  Ingress

Tenslotte isoleer ik de mysql-deployment zodat uitsluitend de wordpress-deployment hier bij kan via port 3306 vanuit de wordpress-namespace. De manifest netpol-wp-to-mysql.yml:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
   name: allow-wp-to-mysql
spec:
   podSelector:
     matchLabels:
       tier: mysql
   ingress:
   - from: 
     - namespaceSelector:
         matchLabels:
           name: wordpress
     - podSelector:
         matchLabels:
           tier: frontend
     ports:
     - protocol: TCP
       port: 3306
   policyTypes:
   - Ingress 

Uitrollen gaat alsvolgt:

$ kubectl apply -f netpol-deny-all.yml
$ kubectl apply -f netpol-allow-http.yml
$ kubectl apply -f netpol-wp-to-mysql.yml

The proof is in the pudding!

Gebruik een Internet browser om naar het IP-adres van de loadbalancer te gaan en de WordPress site in te stellen:

http://192.168.123.101