Deploying a secure, highly available blog site with Jekyll, Cloudflare, Linode and ansible.
After being down for a few weeks, my terrible blog is back up and going to be better than ever! I’ve got things nicely automated and I thought I would share some of the love around about how I did it. I have created a few script and ansible playbooks which I will host on my github for people to use.
For some context, my site used to be a wordpress instance on a single Ubutnu server hosted on linode for $30 a month. It was very lame and I hated how big Wordpress was. I needed something simple that I could dump my notes into and move on. I stumbled into a video about Jekyll by Techno Tim, which is a static site generator from markdown. Maintaining a site with this is much easier then Wordpress. I take all of my notes in markdown anyway so it was perfect. No changes to my workflow and I feel a lot more productive. I also felt like $30 for a single server that gets no traffic is kind of boring and lame. So I decided to deploy 2 alpine VM’s and a Load Balancer for the same price. Am I ever gonna need load balancing or multiple servers, probably not, but its been fun. I’ll stop waffling and show you the set up: </img>
At the Moment Cloudflare is proxying traffic to a Linode NodeBalancer (Linode’s node balancing offering… How clever) which is then distrubiting traffic between my 2 alpine machines.
Alright first things first, configurations. I don’t wanna be clicking a bunch of buttons on a web UI to be able to spin up and configure the new servers and all the other configurations I was gonna need. I wanted something on the command line that can do it twice as fast. For this, I’ll be using the linode and cloudflare API’s and linode stackscripts.
Linode stack scripts
I have a pretty simple stack script, it just installs my SSH keys on the server
Alpine setup stacks script
The Linode and Cloudflare API.
I may open source the scripts I used to configure everything at some stage, but I want to improve them privately first. For now I will just tell you about the configurations I have done and then you can do them yourself.
In cloudflare I have SSL enabled and a few CNAME records proxied for the root of my domain. This lets me be lazy and not have to worry about https. I created this script to help me update IP addresses for the DNS as I built and deleted Load Balancer from linode:
Cloudflare update DNS scriptAll of the magic is hidden in lib but I want improve these before I show them off.
I went a bit nuts on the linode API, I have scripts to automate Firewalls, Load balancer’s, Linode node’s configuration deployment and deletion.
The main configuration I want to look at for this is a firewall configuration script:
Linode firewall config script
Yet again, another magic lib but I promise that it works well and I’ll release them later. At the top, what we are doing is getting a list of the IPv4 & IPv6 ranges that Cloudflare is using at the moment. I have this running in a cron job to make sure that only Cloudflare is able to talk to the servers.
Other then that the configurations are just standard. If you can set up a linode and node balancer, you can keep up with my environment.
So now I have an environment to run my site, I need a way to deploy it. For this I thought I would use docker as it would be easiest to spin up and deploy across machines. I created this an nginx-alpine dockerfile with my ./_site configured to be the nginx site root:
This works pretty well when I provided a working nginx configuration. In the end, my directory structure looks like this: </img>
To make the build and deploy process easy and secure, I wanted to deploy my container to a private registry in the cloud. I decided to deploy it ghcr to integrated some sort of automated building on there servers. This is where github actions comes in. I created the following file in the .github/workflows/build_container.yml This workflow builds the container using github actions, then will send a notification to my discord server via a webhook. Make sure you fix the formatting on the variables.
Finally, it was time for deployment automation, I have a rather large inventory yaml file with lots of roles, and these servers are under the “alpinecloud” group. I mapped the following group vars:
Firstly, I created a role with ansible-galaxy
Create a role template
Then in the tasks folder I create the task files The main.yml task will import the other tasks that I’ll need. It just keeps all of my jobs clean and simple to re-use. It skips the install pip step if pip is already installed.
The first task we import is install_python_modules.yml. This ensures that pip and the docker package are installed on the machine.
That script grabs a local file found at files/requirements.txt
Now, onto actually deploying the container. I created the tasks/deploy_blog.yml task to handle that job. It uses the docker module to log into the container registry and pull the latest version of my containers.
After all of this we finally can create our play book. In the root of your ansible directory, create “ansible/playbooks/deploy_blog.yml”