blakehawkins.com

Architecture of my Blog

Posted 2020-06-28

This edition is not a philosophical rant, but rather a brief summary documenting how this blog/site is run, with some rationale and commentary.

Front-end

Because I like to be in Vogue, the front-end for this blog is statically generated markup, rendered with templated markdown, and with just a sprinkling of webshit.

Fun fact: the design/css was something I made ~2008, when this site was originally backed by php 😱.

I don't have anything more to add about the front-end, which is how it ought to be.

Back-end

The back-end is more interesting (read: over-engineered). There are a few layers worth mentioning:

  • The website templates are hand-rolled, using askama
  • The blog content is rendered from markdown using askama-filters, which itself uses pulldown-cmark as the parser/renderer.
  • All of the blogs and content are rendered and stored as static UTF-8 buffers in memory, and served by actix-web. Ignoring dependencies, this is just 150 lines of rust.

Commentary: I wanted something robust, enterprise-grade, web-scale. Just kidding - I like rust, and I wanted to be able to serve REST endpoints, and so I avoided solutions for purely static sites. I have nothing against static site generators, and I highly recommend them!

Deployment model

Asking a software engineer to maintain a webshit is a bit rough, isn't it. If my blog crashes due to request volume, then I'm an unprepared doofus - but if my site is genuinely web scale, then I've probably just been burning time and money for what is ultimately not critical software.

There are really only a few deployment options:

  • Static site operators, ideally with CDN, like GitHub Pages
  • Provisioning a relatively high-spec VPS and 🤷 if it gets more than ~30k requests per second
  • Managing a k8s cluster, whether GKE or otherwise
  • FaaS

Upon inspection, none of these were really satisfying for my needs (read: wants). I had already thrown away the thought of static site generators as using one here takes the fun engineering work away, and I did still have in mind that I might eventually expose some REST endpoints. Managing a VPS is too high-effort - and I am operationally irresponsible. Kubernetes with auto-scaling is quite elegant, but I didn't much want to work through the faff of Google Cloud or AWS if it could be avoided. FaaS is the dream, but all of the big providers have somehow settled on node.js being the right common denominator.

So I began a journey by asking: how can I FaaS my rust application, with minimal faff, reasonable prices, and reasonable scaling expectations? I was prepared to throw away actix (service layer), but rust was a hard requirement.

I did already know about things like crowbar, for writing rust code that transforms into an AWS Lambda python shim, but that didn't defeat the problem of avoiding touching AWS. What I really wanted was a better FaaS platform.

I found vercel, which was then called zeit.co/now.js, and found it quite compelling. Some code was re-written to use their rust shim, and within a day I managed to get my site running on their platform - truly FaaS - and with some nice automated continuous delivery to boot!

Unfortunately, after zeit re-branded as vercel, they also threw away their old FaaS model. The new company image wanted to be more focused on a webshit CDN platform, and along with that, hard FaaS request limits, and pricing 150,000x higher than Lambda.

In my ensuing rage, I found myself reading about FaaS platforms more carefully, at which point I discovered fly.io. Fly appears on comparison sites as a FaaS platform - and this is mostly true, just with longer lived instances and a reuse model (as I recall, firebase functions also have this feature).

The deployment model is fantastic - you just push a docker image, and Fly serves it on its edge network in 16 regions with an efficient hypervisor. Instance size and concurrency limits are also configurable. Here's my deploy.sh:

#!/usr/bin/env bash

set -euxo pipefail

docker run --rm -it -v "$(pwd)":/home/rust/src ekidd/rust-musl-builder cargo build --release

flyctl deploy

The docker command above is using rust-musl-builder to produce low-effort static MUSL binaries. With this, my whole webserver docker image is just 17MB.

Bottom line up front

Markdown rendered by askama, served by an actix microservice, hosted on fly.io.