Goodbye Bluehost!

Or, how I moved my WordPress deployment to my own kubernetes cluster hosted on Google Compute Engine (GCE).

Until today, this site has been hosted by Bluehost.  I originally chose Bluehost because it is easy to use, inexpensive, and came at the recommendation of WordPress.org.  However, I was never very satisfied with Bluehost.  The page load times were dismal.  The homepage took up to 15 seconds to load.

There are a lot of ways to host a WordPress app, but I knew I wanted to use kubernetes to make it happen.  Kubernetes is a container centric way to host your infrastructure and applications.  I also wanted to try helm to make deploying my application easier.  Helm is a method for managing kubernetes deployments that abstracts some of the complexity away.

If you are looking for a simple way to host your WordPress app, the method I chose has nothing but downsides.  A solution like Bluehost, or WP Engine is the way to go.  On the other hand, if you want to deploy other applications within your hosting framework, kubernetes will give you the most flexibility.

I have used kubernetes to deploy applications for work, but this was my first time deploying my own cluster and hosting a real application on it.  I decided to go with GCE over Google Container Engine (GKE) because I wanted to avoid vendor lock.  If I want to, I can take my configs and go to another vendor to deploy the same infrastructure and applications.

This project took me about 10 hours to complete, but it could be done more quickly learning from my mistakes.  Don’t consider this post a complete tutorial on the process.  This is an overview of the work that I did to migrate my site.  If you are thinking of doing something similar, this article will give you a good idea of how to make it happen while avoiding some of the pitfalls I found myself stumbling over.

Getting Started – GCE and Gcloud

The first thing that you’ll need to do is sign up for Google Cloud Platform (gcloud).  Currently, you can try gcloud for free for 12 months.  Actually, google gives you $300.00 USD to spend over the course of 12 months on google products.  Sadly, you will not be able to deploy your kubernetes cluster in GCE under the restrictions of the free trial because you will need to have more than one static ip address.  Fortunately, you can start by signing up for the free trial, and keep your $300.00 credit when you upgrade.  You can find out more information about signing up for the free tier here.

You need to provide a credit card to sign up.  After signing up, go ahead and enable billing.  This will save you some headache later when you are trying to figure out why your cluster won’t deploy your WordPress app and it is because you have hit the static ip quota.

From here, follow the directions in this article about running Kubernetes in GCE with k8s.  I won’t rewrite the instructions here, but I will will offer a couple of helpful hints.

Hint 0 – Take a minute to consider what you need.

The default configuration of the cluster in the article mentioned above are probably more than you need.  I started out with the default configuration, but found that the standard machines are pretty expensive.  They are also far more than I need.  I am deploying my WordPress site with 300 millicores and 512mb RAM.  The default cluster has 24gb RAM.

The default cluster is also 3 minions plus 1 master node.  You might not need that much, and can run a 1 node plus 1 master as well.

To change your configuration, edit:

kubernetes/cluster/gce/config-default.sh

Afterwards, you can run kube-up.sh to deploy your cluster.

kubernetes/cluster/kube-up.sh

Hint 1 – You don’t need a glcoud VM instance to deploy your kubernetes cluster.

You can do the entire deployment from your local machine, assuming you are working with a supported operating system.  However, you will need to take care to ensure that you are not deploying a cluster in your current kubernetes context.  If you use kubernetes for work, consider running the following command to check your context.

kubectl config current-context

Hint 2- If you do decide to deploy from a GCE VM, Ubuntu is by far the easiest.

It can be useful to have a VM that you can access remotely for managing your deployment.  Let’s say you need to access your cluster from a computer without kubernetes installed, it could be pretty painful to reinstall everything on a separate machine.  On the other hand, you can set up a VM in GCE that you can ssh into as long as you can remember your google username and password.

I tried setting up VMs using google’s Container Optimised OS, CoreOS, and Ubuntu.  Ultimately, I found Ubuntu to be the easiest to work with.  If you go this route, make sure that you give your machine proper scoping to allow it to install new software like kubernetes and kubectl.  Here is what the gcloud command should look like:

gcloud compute instances create <node-name> --image-family ubuntu-1604-lts --image-project ubuntu-os-cloud --scopes cloud-platform

Take note of the flag –scopes cloud-platform.  This is the permissions scope that will allow your machine to provision additional resources as needed.  If you don’t take this step, you will encounter a number of permission denied errors when attempting to run cluster/kube-up.sh.  This is because your machine does not have permission to use the gcloud api to provision additional resources.  Allegedly, you can change permissions on the fly in the GCE console, but I found that it was easiest if you created your machine with those permissions to start with.

Installing Helm

I am using helm to deploy my WordPress app.  I did this because it was very easy.  As much as I love to code, I don’t really care to do much coding for my WordPress site.  Using the helm WordPress chart required the least amount of effort on my part.  After I got kubernetes installed in my cluster, I copied my ~/.kube/config into my local ~/.kube/config file and switched to that context.

I installed helm with homebrew, and then ran the command helm init to install.  However, I wish that I would known that I should have added a helm serviceAccount and clusterRoleBinding before running helm init.  So, before you run helm init,  be sure to create both of those using the following config in your k8s repo under opt/helm.yml:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: helm
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: helm
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: helm
    namespace: kube-system

Then, apply this config with the following command:

kubectl apply -f opt/helm.yml

Afterward, you can run:

helm init

Credit to Jay Vyas.  I would not have figured this out if it weren’t for this article by Jay.

Installing WordPress

Installing WordPress is probably the easiest part.  After installing helm and running helm init, simply run:

helm install stable/wordpress

If you are going to run multiple WordPress deployments in your cluster, it might not be a bad idea to use named releases:

helm install --name my-release stable/wordpress

Afterwards, you will see some output like this:

NAME:   tan-rat
LAST DEPLOYED: Sun Oct 15 13:09:19 2017
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/Secret
NAME               TYPE    DATA  AGE
tan-rat-mariadb    Opaque  2     1s
tan-rat-wordpress  Opaque  2     1s

==> v1/ConfigMap
NAME             DATA  AGE
tan-rat-mariadb  1     1s

==> v1/PersistentVolumeClaim
NAME               STATUS   VOLUME    CAPACITY  ACCESSMODES  STORAGECLASS  AGE
tan-rat-mariadb    Pending  standard  1s
tan-rat-wordpress  Pending  standard  1s

==> v1/Service
NAME               CLUSTER-IP  EXTERNAL-IP  PORT(S)                       AGE
tan-rat-mariadb    <redacted>  <none>       3306/TCP                      1s
tan-rat-wordpress  <redacted>  <pending>    80:<port>/TCP,443:<port>/TCP  1s

==> v1beta1/Deployment
NAME               DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
tan-rat-mariadb    1        1        1           0          1s
tan-rat-wordpress  1        1        1           0          1s

NOTES:
1. Get the WordPress URL:

  NOTE: It may take a few minutes for the LoadBalancer IP to be available.
        Watch the status with: 'kubectl get svc --namespace default -w tan-rat-wordpress'

  export SERVICE_IP=$(kubectl get svc --namespace default tan-rat-wordpress -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
  echo http://$SERVICE_IP/admin

2. Login with the following credentials to see your blog

  echo Username: user
  echo Password: $(kubectl get secret --namespace default tan-rat-wordpress -o jsonpath="{.data.wordpress-password}" | base64 --decode)

Pay close attention to the notes in the output as they tell you how to access the deployment.  To get the static ip of your deployment, run:

kubectl get svc --namespace default -w tan-rat-wordpress

You will see some output like what is shown below:

NAME                CLUSTER-IP   EXTERNAL-IP   PORT(S)                      AGE
tan-rat-wordpress   10.0.80.20   <your-ip>     80:30312/TCP,443:30089/TCP   1m

Now you can visit your new WordPress deployment by navigating to http://<your-ip>/admin.  Try running echo http://$SERVICE_IP/admin.

Migrating Your Data

Keeping with the theme of keeping things simple, I like to use All-in-One WP Migration.  This is a paid plugin if you want to use all of the fancy features it has to offer, or if your site is larger than 512mb.  However, my site is 52mb, and basic file exports work fine for me.  So, I exported the data from my current site, activated the plugin in my new deployment, and imported the data.

Migrating Your Domain

Depending on how you have your domain setup, you may only need to update your A record to point to the static IP of your new deployment.  Since I was leaving Bluehost for google, I decided I also wanted to move my domain to google domains.  To this I followed Bluehost’s instructions for transferring away a domain.  Once my domain was registered in google domains, I added a new A record that points *.joecreager.com and joecreager.com to the static IP of my deployment.  To confirm this worked:

curl -v <yourdomain>

You should see something like this in the first couple lines of output:

Connected to <your-domain> (<your-deployment-static-ip>) port 80 (#0)
That’s It!

That’s all it took for me to get my WordPress deployment migrated to kubernetes.  If this is something you have been thinking of doing, I think you will be surprised to learn that it is much easier than you might think.  I only wrote one kuberetes configuration file to make it happen.  I’m excited to deploy a rich ecosystem of applications around my deployment.  My next goals are to get an SSL cert and start direct traffic through https, set up monitoring with prometheus and grafana, and to deploy some of my custom apps to my cluster.