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.
System architecture
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.
Cloudflare API
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.
Linode API
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.
Docker
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:
Dockerfile
This works pretty well when I provided a working nginx configuration. In the end, my directory structure looks like this: </img>
Github actions
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.
build_container.yml
Ansible
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:
ansible/inventories/group_vars/cloudalpine.ymld
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.
tasks/main.yml
The first task we import is install_python_modules.yml. This ensures that pip and the docker package are installed on the machine.
tasks/install_python_modules.yml
That script grabs a local file found at files/requirements.txt
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.
tasks/deploy_blog.yml
After all of this we finally can create our play book. In the root of your ansible directory, create “ansible/playbooks/deploy_blog.yml”