I stepped through this guide for a Hello World app running on Google Cloud Platform. How quickly can you get something working, and how convenient would it be to use longer-term?
In doing so, I confirmed GCP doesn’t make use of Erlang’s hot code reloading when re-deploying apps, but how important is that?
Starting point: OS X machine with Python installed; Google account with developer access and an existing payment profile.
Minutes elapsed | Step |
---|---|
0 | Start! |
1 | Create new Google Cloud project |
3 | Add billing information (using existing payment profile) – they labour the point that you won’t actually be charged |
8 | Google Cloud SDK installed and initialised |
13 | Elixir and Node packages installed locally |
15 | Local development server up and running |
19 | Distillery-based release running locally |
20 | Start initial deploy to Google Cloud |
33 | Initial deploy finished |
35 | Start to deploy update to the app |
45 | App update finished |
As mentioned above, Erlang and Elixir apps can be updated on the fly without stopping the app. In GCP’s case, this feature isn’t being used. I proved this to myself with the following test:
By including an Agent
in the Phoenix app, I could store state between requests:
# words.ex
defmodule AppengineExample.Words do
use Agent
def start_link do
Agent.start_link(fn -> [] end, name: __MODULE__)
end
def put(value) do
Agent.update(__MODULE__, fn(state) -> state ++ [value] end)
end
def get do
Agent.get(__MODULE__, fn(state) -> state end)
end
end
I hooked my controllers up to that Agent
, to save and retrieve words submitted from a <form>
:
# page_controller.ex
defmodule AppengineExampleWeb.PageController do
use AppengineExampleWeb, :controller
def index(conn, _params) do
render conn, "index.html", words: AppengineExample.Words.get
end
def save(conn, %{"word" => word}) do
AppengineExample.Words.put(word)
redirect conn, to: "/"
end
end
When running the server locally, I could update the code of a running server with something like:
# create the initial release
env MIX_ENV=prod mix release --env=prod
# run the server in the background
env PORT=8080 _build/prod/rel/appengine_example/bin/appengine_example foreground &
# edit mix.exs to bump the version
vim mix.exs
# create the upgrade release
env MIX_ENV=prod mix release --upgrade --env=prod
# deploy the upgrade
_build/prod/rel/appengine_example/bin/appengine_example upgrade 0.0.2
Crucially, state stored in the Agent
was preserved throughout this upgrade.
When performing an analogous update on the Google Cloud app, this isn’t true. GCP appears to be following the usual process of spinning up a new version of the app, then cutting over at the load-balancer level.
For webapps in frameworks like Rails or Django, this isn’t a problem, as you don’t store state in the app server. To achieve something similar to what I did using Agents
in Elixir, you’d use Redis or similar.
However, for Elixir apps it’s a shame, as process- or Agent
-based state is extremely convenient. Not only that, but because it’s so idiomatic there’s a plethora of documentation encouraging its usage. New users in particular might get a nasty shock if they lean on process-based state then find that app updates wipe it out.