Moving Wordpress to Civo K3s
Introduction
Moving your web site to a new provider is a bit like moving house: you don't know where to start, stuff breaks along the way and you vow never to do it again! I've been running a web site on a few cloud providers for a while now, but sometimes you just get the itch to play with something new...
So when a new provider comes onto the scene my brain says... "oh look at that shiny new hosting platform! Look how cheap it is! Look how much fun (late nights and alcohol) you could have moving your Wordpress site to it!"
Along came Civo.....
Following up from my previous post (Building a blog on Civo k3s) I was so impressed by not just what these guys were trying to do from a technology point of view but the passion they have for their service and helping their customers. Also, moving from a traditional web server to Kubernetes was too tempting to resist! Talk about that shiny new thing...
Building the new site
To follow along with this guide, you will need to have access to Civo's managed Kubernetes service. You can sign up here - the service is currently in beta so is accepting applicants through a form. If you get accepted to the beta, you will get free credit for the duration in return for your feedback on the service.
If you have an existing Wordpress site, this guide will walk you through moving it to the new platform, but it can also be used to deploy a fresh Wordpress installation on Kubernetes, with SSL certification.
Pre-requisites (Both in the CIVO marketplace):
-
Longhorn (storage)
-
Cert-manager (SSL certificate)
I have also assumed some prior knowledge of using k3s on Civo, I recommend you work through my previous blog if you have not done so already.
First, start up a cluster of your choice of size - the default is a 3-node cluster composed of Medium-sized nodes which will be plenty.
Then, let's create a namespace to put the new site into:
kubectl create namespace wordpress
Creating the volumes
Let's create the 2 volumes used for mysql server storage and the wordpress files, you can apply them directly using the following commands, but their contents are produced below as well for you to save locally and deploy:
kubectl apply -f https://raw.githubusercontent.com/keithhubner/civo-k3s-wordpress/master/vol-mysql.yml
kubectl apply -f https://raw.githubusercontent.com/keithhubner/civo-k3s-wordpress/master/vol-wordpress.yml
vol-mysql.yml
:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wordpress-mysql-pv-claim
namespace: wordpress
labels:
type: longhorn
app: wordpress
spec:
storageClassName: longhorn
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
vol-wordpress.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wordpress-pv-claim
namespace: wordpress
labels:
type: longhorn
app: wordpress
spec:
storageClassName: longhorn
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
You can check these have been created by doing the following:
kubectl get pvc --all-namespaces
NAMESPACE NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
wordpress wordpress-mysql-pv-claim Bound pvc-09aff171-dfe9-4cd0-a5be-024244a78348 5Gi RWO longhorn 55s
wordpress wordpress-pv-claim Bound pvc-b2f7ecda-2d09-467f-8b2b-d5e625bb7365 5Gi RWO longhorn 44s
kubectl get pv
pvc-285dcc0c-6a10-4428-943e-8c0726feae32 5Gi RWO Delete Bound default/wordpress-mysql-pv-claim longhorn 74s
pvc-d6f27478-56b2-402a-93c9-cfbdcbbe67b7 5Gi RWO Delete Bound default/wordpress-pv-claim longhorn 53s
We will also need a secret to be used by mysql (make sure you change the some_text_to_encode)
Make a note of this password, you will need it later
echo -n some_text_to_encode | base64
You will get something like the following:
c29tZV90ZXh0X3RvX2VuY29kZQ==
Copy and paste the below into a new file called mysql-secret.yml (remembering to replace the password value with your base64 value generated in the previous step):
apiVersion: v1
kind: Secret
metadata:
name: mysql-pass
namespace: wordpress
type: Opaque
data:
password: c29tZV90ZXh0X3RvX2VuY29kZQ==
And apply the file:
kubectl apply -f mysql-secret.yml
Now let's deploy mySQL
kubectl apply -f https://raw.githubusercontent.com/keithhubner/civo-k3s-wordpress/master/mysql-wordpress.yml
For reference, the contents of the above file mysql-wordpress.yml
are:
apiVersion: v1
kind: Service
metadata:
name: wordpress-mysql
namespace: wordpress
labels:
app: wordpress
spec:
ports:
- port: 3306
selector:
app: wordpress
tier: mysql
clusterIP: None
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
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: mysql:5.6
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass
key: password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: wordpress-pv-claim
All being well you should have a lovely new mysql pod:
kubectl get pods -n wordpress
wordpress-mysql-56b57b8d45-tgd7p 1/1 Running 0 26s
And checking mysql is happy (replacing the pod name with yours):
kubectl logs -n wordpress wordpress-mysql-56b57b8d45-tgd7p
........
2020-02-19 21:03:05 1 [Note] mysqld: ready for connections.
Deploying Wordpress:
You will need to alter the scripts below replacing the site name with one of your chosing. In order for cert-manager to issue a real certificate, this will need to be a domain you own and can point the DNS record at the public IP.
First we will need to create the cert manager cluster issuer:
Staging certificate allows you to test the configuration before pointing live DNS records
Create the following file asissuer-staging.yaml
on your local machine.
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
name: letsencrypt-staging-wp
spec:
acme:
# The ACME server URL
server: https://acme-staging-v02.api.letsencrypt.org/directory
# Email address used for ACME registration
email: someone@somewhere.com
# Name of a secret used to store the ACME account private key
privateKeySecretRef:
name: letsencrypt-staging-wp
# Enable the HTTP-01 challenge provider
solvers:
- http01:
ingress:
class: traefik
Then, run kubectl apply -f issuer-staging.yaml
to apply this to your cluster.
DO NOT INSTALL WORDPRESS IF YOU ARE MIGRATING FROM ANOTHER SERVER
If you are migrating your blog from another host, you will not need to re-create the files as you will be bringing these over.
Below is the manifest template to create the wordpress pod. Copy and paste this into your favourite editor and amend the values as required.
You can also get the file here
deploy-wordpress.yml.
You will need to match the wordpress version to the version you are running in your live site
apiVersion: v1
kind: Service
metadata:
namespace: wordpress
name: wordpress
labels:
app: wordpress
spec:
ports:
- port: 80
selector:
app: wordpress
tier: frontend
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: wordpress-ingress
namespace: wordpress
annotations:
kubernetes.io/ingress.class: "traefik"
cert-manager.io/cluster-issuer: letsencrypt-prod-wp
ingress.kubernetes.io/ssl-redirect: "true"
labels:
app: wordpress
spec:
tls:
- hosts:
- www.somesite.com
- somesite.com
secretName: letsencrypt-prod-wp
rules:
- host: www.somesite.com
http:
paths:
- path: /
backend:
serviceName: wordpress
servicePort: 80
- host: somesite.com
http:
paths:
- path: /
backend:
serviceName: wordpress
servicePort: 80
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
namespace: wordpress
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: wordpress:5.3.2-php7.3-apache
name: wordpress
env:
- name: WORDPRESS_DB_HOST
value: wordpress-mysql
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: wordpress-mysql-pass
key: password
ports:
- containerPort: 80
name: wordpress
volumeMounts:
- name: wordpress-persistent-storage
mountPath: /var/www/html
volumes:
- name: wordpress-persistent-storage
persistentVolumeClaim:
claimName: wordpress-pv-claim
If all is well in the world you should be able to see the pod is now running:
kubectl get pods -n wordpress
wordpress-776d9476c8-rs679 1/1 Running 0 47s
You should also be able to see cert-manager doing its thing:
kubectl get cert -n wordpress
NAME READY SECRET AGE
letsencrypt-staging-wp True 35s
By either visiting the cluster's public IP, pointing your hosts
file or updating your DNS records to point to the public IP of your cluster, you should see the Wordpress setup page that will look a little bit like this:
Next we need to create the secret which will be used for the database. Again you can look at this post for instructions on how to do this.
Once the storage has been created we can deploy mysql:
kubectl apply -f mysql-wordpress.yml
You can copy and paste the code below to a file called wordpress.yml
to get yourself a templated wordpress service:
apiVersion: v1
kind: Service
metadata:
name: wordpress
labels:
app: wordpress
spec:
ports:
- port: 80
selector:
app: wordpress
tier: frontend
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: wordpress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
kubernetes.io/ingress.class: "traefik"
ingress.kubernetes.io/ssl-redirect: "true"
namespace: default
spec:
tls:
- hosts:
- www.yoursitehere.com
- yoursitehere.com
secretName: letsencrypt-prod
rules:
- host: www.yoursitehere.com
http:
paths:
- path: /
backend:
serviceName: wordpress
servicePort: http
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
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: wordpress:5.2.3-php7.1-apache
name: wordpress
env:
- name: WORDPRESS_DB_HOST
value: wordpress-mysql
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass
key: password
ports:
- containerPort: 80
name: wordpress
volumeMounts:
- name: wordpress-persistent-storage
mountPath: /var/www/html
volumes:
- name: wordpress-persistent-storage
persistentVolumeClaim:
claimName: wordpress-pv-claim
If everything has gone to plan, then you should be able to reach the Wordpress installation page by editing your local host file with the domain name used earlier and public IP on the CIVO master node.
DO NOT INSTALL WORDPRESS WITH THE ABOVE INSTRUCTIONS IF YOU ARE MIGRATING FROM ANOTHER SERVER
You will notice the SSL certificate isn't quite right yet as we are using the staging issuer!
Before we re-point your live DNS and setup the proper certificate, we need to import your current site.
Where your current site is hosted will determine your export/import process. For this example I'll take an existing site I have root access to to backup and copy both the Wordpress files and the mysql database.
Backing up Wordpress Files
The easiest way to backup the Wordpress web files is to ZIP up the whole html folder.
Again this may vary depending on your server.
zip -r /tmp/wordpress.zip /var/www/html/
This will zip up the whole site into a wordpress.zip file in the /tmp folder. You will need to copy this file to your local machine. How to to do this is deyond the scope of this post, but I used a tool called CyberDuck
for Mac.
Backing up MySQL
Again the process to backup the database will vary depending on your situation. I will be using the CLI to take a backup of the wordpress database and assuming the database name is wordpress.
mysqldump -u root -p wordpress > wordpress.sql
cp wordpress.sql /tmp/wordpress.sql
Again, copy this file to your local machine.
At this point I recommend either shutting down your old site or blocking any updates, otherwise you risk not having an up to date version in CIVO. There is a way to put your blog into "maintenance mode" in the dashboard, meaning people will not be able to post comments, for example.
Copying files into your containers
We will need to copy these backup files into the respective containers.
MySQL Restore
You will need to make a note of your mysql pod name:
kubectl get pods -n wordpress
NAME READY STATUS RESTARTS AGE
wordpress-mysql-56b57b8d45-kht26 1/1 Running 0 5d12h
wordpress-776d9476c8-jjs4d 1/1 Running 0 5d12h
Next copy the SQL backup file into the pod from the directory you saved it:
kubectl cp /Users/keithhubner/Documents/wp_backup.sql wordpress/wordpress-mysql-56b57b8d45-kht26:/tmp/wp_backup.sql
Next restore the data into SQL:
Note your root password is the one which you set in the secret earlier
mysql -u root -p < wp_backup.sql
You can log into mysql and check the data has restored:
mysql -u root -p
USE wordpress;
SHOW tables;
select * from wp_users;
This should hopefully return some data you recongnise from your old wordpress setup.
Wordpress Web Restore
Again we need to copy in the backup file, remember to change the pod name:
kubectl cp /Users/keithhubner/wp_backup.zip wordpress/wordpress-776d9476c8-jjs4d:/tmp/wp_backup.zip
And unzip the files
The unzip package is not installed as part of the image, so you will need to install it. This will only be installed for the lifetime of the pod, but we only need it once.
apt-get install unzip
unzip wp_backup.zip -d /
You should now see all your files restoring into the wordpress html folder.
All being well, when you navigate to your site, you should see your site restored. Again you will get certificate warnings/issues until we issue the real certificate.
Re-pointing LIVE
Due to the time it takes to re-point DNS and setup the live cert make sure you plan for some downtime.
Once you are happy the site has been restored, you can re-point your public DNS records to the new IP. This is done through your domain registrar once you log in to manage your domains.
Check your DNS has updated:
nslookup www.yoursite.com 8.8.8.8
This should return the new CIVO public IP
We can now issue the new live certificate, amending the values as needed. Let's create a file called issuer-prod.yaml
with the following contents:
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
name: letsencrypt-prod-wp
namespace: wordpress
spec:
acme:
# The ACME server URL
server: https://acme-v02.api.letsencrypt.org/directory
# Email address used for ACME registration
email: someone@something.com
# Name of a secret used to store the ACME account private key
privateKeySecretRef:
name: letsencrypt-prod-wp
# Enable the HTTP-01 challenge provider
solvers:
- http01:
ingress:
class: traefik
Then, you guessed it, apply it to your cluster:
kubectl apply -f issuer-prod.yaml
You can check the status of the certificate using the following command:
kubectl get cert -n wordpress
NAME READY SECRET AGE
letsencrypt-prod-wp True letsencrypt-prod-wp 5d12h
And you should see it issued successfully:
kubectl describe cert letsencrypt-prod-wp -n wordpress
...
Last Transition Time: 2020-03-04T21:13:07Z
Message: Certificate is up to date and has not expired
Reason: Ready
Status: True
Type: Ready
Not After: 2020-06-02T20:13:06Z
Events: <none>
kubectl describe clusterissuer letsencrypt-prod-wp
Status:
Acme:
Last Registered Email: keith@hubner.co.uk
Uri: https://acme-v02.api.letsencrypt.org/acme/acct/79741635
Conditions:
Last Transition Time: 2020-03-04T21:08:53Z
Message: The ACME account was registered with the ACME server
Reason: ACMEAccountRegistered
Status: True
Type: Ready
Events: <none>
Now you should be able to browse to your web address (removing any local host file records) and see a nice trusted certificate and your site!
Let me know on Twitter if you found this guide useful, and follow Civo for updates on their service as well.