Roll your own Platform as a Service

About three weeks ago, I decided it was time for my static GitHub Page website to become an actual Ghost blog running on good ol’ Heroku!

For those of you who are new to Platform as a Service and haven’t heard of Heroku, it’s is a great way to deploy your application to a public endpoint very quickly without all the infrastructure headaches—which we honestly won’t be avoiding in this post, sorry.

A very good point to note about Heroku is that your application needs to make use of environment variables and should be built for service-oriented architecture. Ghost, normally, utilises local storage for both its database (SQLite) and site assets (images, attachments, etc). I follow the 12 factor application methodology.

 12-factor App Methodology

Basically, the 12-factor app methodology a guide for building software-as-a-service applications that use declarative methods for setup automation, are suitable for deployment just about anywhere, maintain minimal to zero divergence between development and production, can scale infinitely with no change to development practices and are portable.

 What are the twelve factors?

  1. Codebase — one codebase for the service tracked with version control (ie. git)
  2. Dependencies — explicitly declared and isolated dependencies (ie. node modules)
  3. Config — storing configuration in the environment (ie. .env files & Heroku environment variables)
  4. Services — backing services are treated as attached resources
  5. Continuous development — compilation, release and run stages are separated in code as they are in deployment
  6. Process — your application should run completely stateless (ie. no reliance on local storage for session, etc)
  7. Bindings — export your services using port bindings
  8. Concurrency — your application should scale via the process model (6)
  9. Disposability — fast startup and graceful shutdown are critical for scalability
  10. Parity — keep development, staging and production as similar as possible (ie. use those environment variables)
  11. Log — treat logs as event streams
  12. Admin — run admin / management tasks as one-off processes, not part of the application

 Overkill? Not really.

A lot of this seems overkill, but it really isn’t. This is a simple blog, but it does handle some traffic, and it does need to scale seamlessly. Previously, the content was static, so I relied on GitHub’s infrastructure to serve you HTML pages. However, they’ve proven to have some difficulties with building these pages and handling subdirectories, so I’ve just decided to host Ghost properly.

 Database

Ghost uses SQLite for its internal database by default, which isn’t at all compatible with the way Heroku works. Heroku apps should be completely stateless, following rule 7 of the 12 factor application. This is solved easily, merely by making use of npm install --save postgres and some environment variables to point Ghost in the right direction. For PostgreSQL hosting, I’ve used Heroku’s out-of-the-box solution which you can add to your app with a few simple button presses. Then, merely point your production database configuration to the environment variables it needs to look for:

database: {
    client: 'postgres',
    connection: process.env.DATABASE_URL
}

 Asset Storage

Ghost would normally store files locally, but because the application needs to be stateless, when Heroku shuts down, any locally stores files would be lost. So, for this we make use of rule 4 by utilizing services to handle resources.

I’ve made use of Amazon S3 instead of the local storage that Ghost normally utilises. The first thing you’ll need to do is npm install --save ghost-s3-storage and add index.js with the content below to /contents/storage/ghost-s3/.

'use strict'
module.exports = require('ghost-s3-storage');

Again, just like above, I recommend that you store your Amazon IAM credentials as environment variables, so this configuration is completely portable.

storage: {
    active: 'ghost-s3',
    'ghost-s3': {
        accessKeyId: process.env.S3_ACCESS_KEY_ID, // ABCD1234ABCD1234ABCD
        secretAccessKey: process.env.S3_SECRET_ACCESS_KEY, // abcdA1A1A1+abcdabcdabcdabcd1234A3A3a
        bucket: process.env.S3_BUCKET, // your_bucket_name
        region: process.env.S3_REGION, // ap-southeast-2 (or whatever)
        assetHost: process.env.S3_ASSET_HOST // '//your_bucket_name.s3.amazonaws.com/' (use a // for SPDY urls)
    }
}

 Build, Deploy, etc

Ghost has pretty much handled the whole build and run part of the application, which is really nice. We just need to declare in a Procfile (the file Heroku uses to do a deployment), what commands we’re running for each of the processes on the instance.

web: npm start --production

It’s as simple as that. You can now commit this to Heroku and watch as your Ghost installation comes to life.

 Rolling your own Platform

Heroku is really, really powerful in the sense that it can host your web applications while providing some sexy interfaces for you to configure parts of your application, and even to automatically provision the environment variables third-party services need. It has a whole catalog of services you can just click to turn on—it’s hard not to love.

However, we’ll go through how to build your own platform using Docker—for science.

[Docker](//assets.willhackett.com/2016/Mar/CotW_CoreOS_Docker-1458114682494.jpg)

 Docker?

I’ve been over this people, Docker’s a pretty sweet tool that you can use to run just about any environment on top of the Linux kernel. On my CoreOS cluster, I can have containers running CentOS + NodeJS + Nginx, Ubuntu + Apache + MySQL and maybe even Fedora and Minecraft—it’s pretty sweet, and takes similar template definitions to Heroku—Dockerfiles.

 Basics of what Heroku does

Heroku does a lot of stuff in the background on top of the basic application containerization. We’re going to want to focus on the basics to make sure that our platform works just as nicely.

  1. Resource allocation—we have some servers and we know which ones we can put containers on and how much they can use
  2. Container service—we can run applications in containers and bind them to ports
  3. Configuration—using some environment variables, we can tell the application about where it lives
  4. Routing—we can route HTTP/HTTPS requests to subdomains/verified domains to the port running our application.

A few technologies jump to mind that can achieve all of this, but we’ll get started with a basic solution. (Nginx, Docker, Docker Swarm, etcd, gitreceive, buildstep)

 DEIS

[DEIS](//assets.willhackett.com/2016/Mar/DeisCoreOs-1458114613446.png)

DEIS is a really big learning curve for people who are new to both CoreOS and Docker, but becuase of some nice buildstep integrations, you can use the Heroku buildpack’s and Procfile’s to declare what your application should look like and DEIS will fill in the blank’s.

 How to?

I’m not going to run through how to install DEIS here, simply because it’s an ever-changing beast and any guide I write or anybody else write’s will eventually diverge from the guide’s on their website—which are really really easy, promise!

http://deis.io/get-deis/

It’s a little bit unclear in their documentation, but you need to install the deis CLI onto your local machine. Don’t worry, this isn’t the only machine that can access your cluster, just keep in mind that you will need to remember your authentication credentials if you intend to regain access to it from another machine.

DEIS CLI provides Heroku CLI-like commands that allow you to interact with your cluster. These work similarly to Heroku in the sense that it will add a git remote to the repo that commits code. The repository must follow specific guidelines and can use either a Heroku Buildpack, a Dockerfile or a Docker Image from the Hub.

DEIS is built on top of CoreOS which utilises etcd to discover servers in the cluster. Make sure that you don’t lose your etcd URL or you’ll lose the ability to add servers to your cluster. CoreOS servers are to be considered stateless, they use a configuration file similar to the way Docker and Heroku do to register themselves on etcd and start spinning up containers. DEIS has a controller service that runs across all of the CoreOS machines which will manage all of the routing and configuration for you, you simply need to interact with the APIs within the DEIS CLI.

If you plan to remove servers from your cluster, or if you need help removing a damanged instance from your cluster that is causing trouble during setup, please make use of the etcd clustering guides and DEIS’ guide to adding and removing hosts. Also, if you plan to deploy to production, there’s some important information that you should check out here.

There is a tonne of online documentation that can help you, and I’ll also answer questions written in the discussion below because DEIS has been a bit fiddly in the past, especially when working with etcd and Digital Ocean.

 
0
Kudos
 
0
Kudos

Now read this

For the love of Promises, stop nesting your chains.then()

I’ve been spending a lot of time in some express JS applications lately. I love express, but generally only when I can work with ES7 syntax. However, lately I’ve been tackling deep, nested, unreadable promise chains. app.use((req, res)... Continue →