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):
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
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
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:
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
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.
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 as
issuer-staging.yamlon 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: email@example.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
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
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.
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.
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 18.104.22.168
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: firstname.lastname@example.org # 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: email@example.com 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!