How to Deploy a { Monolithic | Stateful | Huge } Web App to Cloud Foundry?

Antoine Toubhans
Sicara's blog
Published in
6 min readMar 30, 2017

Read the original article on Sicara’s blog here.

As part of my last project, I had to deploy a web app to Cloud Foundry.
This article tells the story of the many challenges we had to solve.

The app

First, I briefly sketch the app at stake.

Architecture

It’s a modern web app offering data visualization and model building for prediction. Roughly, there are three components:

  • Python workers: these scripts are at the core of the app, they process data and compute models for data visualization and prediction;
  • a Node/Express server: it handles basic features e.g., user management/authentication and it serves JSON/CSV files produced by workers;
  • an AngularJS front-end: providing the UI for exploring your data, it includes state-of-the-art libraries e.g., Google Materials-UI, D3js.

Dataflow

The front-end fetches its data from the Node server through http and
websocket. The Node server and workers communicates to each other through different channels including:

  • a two-ways message queue service based on ZeroMQ;
  • a MongoDb database;
  • the local file system.

Deploying the old way to an IaaS

Traditionally, the app is deployed to an infrastructure as a service (IaaS) providing virtual machines and abstracting away physical resources (machine, network).

The app source code looks like the following:

App folder structure

Before deploying the app, you needed to provision a server with all binary dependencies i.e., Nginx (reverse proxy and static files), Node+Npm, Mongo, ZeroMQ C lib, Python 3.5 …

Then you can deploy the app by copying the source code to your server
and by starting all services (Nginx/Mongo/Node/…).

Why PaaS/Cloud Foundry?

Cloud Foundry is an open source cloud platform as a service
(Paas). While IaaS provide low-level features (virtual machines / networks), PaaS allow you to work directly with web-app containers and managed services e.g., message queues, databases or job launchers.
Many advantages stem from the higher level of abstraction of PaaS.

Open Source

You may install Cloud Foundry yourself in your own servers or on your favorite IaaS (Amazon, Google, …); it will take *at least 14 machines* for a basic usage, though. If you don’t want to bother yourself with this (that’s our case), choose among the many managed Cloud Foundry providers: Pivotal,
BlueMix, Predix, …

Easy to deploy

All you need to do is push your code to the cloud, and that’s it. Cloud Foundry creates a container for running your code. You don’t know exactly where and that’s ok: the only thing that matters is that your code is running somewhere.

Easy to provision

When you push a piece of code, Cloud Foundry detects the language and provision the container accordingly. For instance, the presence of a package.json file triggers the NodeJs buildpack that will install Node/Npm binaries and Node modules in accordance with it.

Cloud Foundry has inherited from Heroku the concept of buildpacks together with a bunch of buildpacks for most common languages to choose from.

Easy to manage

Cloud Foundry has a healthcheck mechanism to ensure your app stays up. If your app goes down, Cloud Foundry automatically restarts it. You can monitor / start / stop your apps by calling the Cloud Foundry API
or through the UI your Cloud Foundry provider has built for you.

Easy to scale

Need to handle more traffic? Add more instances of your app. The cloud controller (CC) includes a load balancer dispatching traffic among your instances.

Benefit from the community / the catalog

You don’t want to scale your app manually? Use an autoscaler service either from the Cloud Foundry community (e.g., this one) or from your Cloud Foundry provider catalog (e.g. the Pivotal autoscaler).

Cloud Foundry has an active community so you can easily find plenty of services for databases, blob stores, message queues, sso authentication, adding ssl …

What does Cloud Foundry require?

All benefits from Cloud Foundry come at a price: the app has to follow some
basic guidelines to fit the framework and work well in the cloud. In the following, I’ll give my recommendations.

Push micro-apps

Our app is monolithic.

Even though the app have separate components (front-end / back-end / workers), it is meant to be deployed as a single block of code. In order to fit the Cloud Foundry paradigm, we need to push small independent components and make them work together in the cloud.

The app has to be stateless

Our app is clearly not stateless.

For instance, the Node app reads and writes the local filesystem all the time.
This is a major issue as Cloud Foundry does not persist app container filesystem. If Cloud Foundry decides to restart the Node app — and it does for plenty of reasons — the filesystem is wiped out.

Push the minimal amount of code

Our app is huge.

Python dependencies weight ~1.5GB which is way above the 1GB Cloud Foundry upload limit.

Anyways, we’re positive we don’t want to push ~1.5GB each time we deploy.
Python dependencies should be downloaded and cached in the cloud.
Hopefully, there are plenty of buildpacks that do the job.

The easiest part: push the front-end

Traditionally, Nginx serves the front-end files. We keep it the exact same way in the Cloud by pushing the entire webapp folder
and using the staticfile-buildpack.

To do so, we create an application manifest file at the root of your app describing how and what to push to the Cloud.

Here is our manifest.front-end.yml file:

The manifest is self-explicit and quite intuitive:

  • buildpack: this is the name of the buildpack used to build the app. Note that you can omit this field and let Cloud Foundry detect which buildpack to use;
  • disk_quota and memory: disk and memory size of the app container;
  • name: the name the app is registered in the Cloud;
  • path: path to the folder containing the code to be pushed to the cloud;
  • routes: dns resolution for CC, http://my-awesome-app.io will redirect to MyAwesomeAppFrontEnd app.

That’s it! you are ready to deploy to the cloud: first be sure to have the cf command line client installed, and the run the following:

cf push -f manifest.front-end.yml

You should get something like:

Go to http://my-awesome-app/ and look at your front-end app running :)

Front-end vendors

For the sake of simplicity, we choose to push the front-end source code
together with vendor files (bower). This means we have to build locally the front-end app i.e., install vendor files, before pushing. This is fine as long as the source code and vendor files together remains lightweight (~50MB in our case).

To be continued

In my next articles — available soon :) — , I’ll present the following subjects:

  • How to deal with stateful apps?
    Both Node app and Python workers are stateful, mainly because they read/write to MongoDb and the filesystem. In that matter, there is no alternative but to make the app stateless! Identify what pieces of code are stateful, move them out the app and inject it to the app through stateful services.
  • How to push a NodeJs app to Cloud Foundry?
    I’ll show how we used the nodejs-buildpack and user-provided services.
  • How to push huge apps to cloud foundry?
    Python workers carry an heavy load of dependencies (~1.5GB) that we can’t push directly to the cloud (and we don’t want to!). We tried to use the python-buildpack, but it failed during the building phase due to lack of space. Instead, we used a custom buildpack.
  • How to enable communication between several apps in Cloud Foundry?
    In our architecture, the Node app and Python workers talk to each other through ZeroMQ. We tried (and failed) to keep it working the same way in the cloud. Instead, we used an on-the-shelves message queue service (rabbitMQ).
  • How to have workers in Cloud Foundry?
    Python workers are triggered by some event to do a job; usually the Node app launch them through the command line. Once their job is done, they went off. This is not how Cloud Foundry apps are supposed to work. This was maybe our most difficult challenge during the project, I’ll explain how we figured this out.

--

--