If you are unaware of basics on Hot Code Swapping, read the part 1 below to get some idea before you take a long step on code swapping or hot plugging.
How to perform Hot Code Swapping in Elixir — #1.
This article will guide you to perform hot code swapping in Elixir using the Elixir package distillery-2.0 (at the moment of writing this article). A live demo will stand out.
Brief Intro about Distillery
The distillery
helps us to simplify deployments in Elixir with OTP releases.
Who wants to fall into hassle of dependencies these days. We look for things to run independently with out depending on any required environment. It feels me to write a line which I read in many books “write once, and run anywhere”. In a simple way, we need a complete package which is ready to deploy anywhere & independent of running environment.
This is exactly what distillery is used for.
Let’s code it and taste the fruit.
Create a new mix
project
$ mix new hotcode
$ cd hotcode
Add distillery
as a project dependency inside the file mix.exs
under dependencies
section.
#file: mix.exs
defp deps do
[{:distillery, "~> 2.0"}]
end
Fetch the dependency
$ mix deps.get
Let’s add a file demo_server.ex
in lib
folder and copy the following lines of code to the file.
defmodule DemoServer do
use GenServer
@vsn "1"
## Client API
def start_link employee do
GenServer.start_link __MODULE__, employee, []
end
def add_money(pid, value) do
GenServer.call(pid, {:add, value})
end
## Server API
def init(employee) do # initiating state with passed employee details
{:ok, employee}
end
# add the value to the state and returns :ok
def handle_call({:add, value},_from, %{name: name, money: money} = state) do
{:reply, "#{value} added to #{name} ", Map.put(state, :money, money+value)}
end
end
If you need explanation of the code, check the part-1.
It’s a GenServer
module which puts the initial state to the passed employee details %{name: "some_good_name", money: some_big_number}
e.g. %{name: "blackode", money: 999999}
.
Don’t forget to save the file demo_server.ex
after copying the code. Don’t feel shame to copy, it saves your time.
Releasing first version
Just run the command mix release
from the project root directory.
$ mix release
To your surprise, you’ll hit with an error saying release config file
is missing and asks you to run the mix task release.init
$ mix release.init
It creates an example config file rel/config.exs
review the file, make changes as you desired, and then run the command mix release
to build your first release.
The file itself is enough to use. You can edit the cookie
in environment
section.
environment :prod do
set cookie: :crypto.hash(:sha256, System.get_env("COOKIE")) |> Base.encod16(:lower) |> String.to_atom
........
end
You can also see the release section in the file.
release :hotcode do
set version: current_version(:hotcode)
set applications: [
:runtime_tools
]
end
You can define one or more releases in the rel/config.exs
file.
If the default_release
option is not specified and running mix release
, the first release in the file will be used by default or else the specified release is used.
Let’s add another release in the file rel/config.exs
and check the releases.
release :hotcode2 do
set version: "2"
set applications: [
:runtime_tools
]
end
After adding the release
, you need change the value of default_release
. By default its value is :default
. Now, change this to :hotcode2
and run mix release
It uses default environment for building the release. You can find a line in console like Building release hotcode2:2 using environment dev
. However, you can still mention the environment to use by setting environment on the fly MIX_ENV=prod mix release
.
The build is placed inside a directory _build
. At the moment this folder contains only dev
directory.
Now, we change the default_release
option to :default
and will make another release using MIX_ENV=prod
. This time it uses the release :hotcode
as it is the first release available in the file rel/config.exs
.
Let’s check that.
default_release: :default
After changing the release now run the release.
$ MIX_ENV=prod mix release
You can see a line called Building release hotcode:0.1.0 using environment
prod in your terminal. At this moment there will be two folders inside your _build
directory based on the environment we used for building.
The package is released in the directory _build/prod/rel/hotcode/bin/hotcode
. The bin folder is the place for the scripts related to Erlang entry. The releases folder contains all the version releases. Each version folder will have its own rel
file, boot scripts, and tarball file which we use for deploying in remote systems.
Starting Releases
Now change your directory to _build/prod/rel/hotcode/bin
and run the command ./hotcode console
.
$ cd _build/prod/rel/hotcode/bin
$ ./hotcode console
It opens shell environment similar to iex -S mix
Let’s do some computations over the DemoServer
.
iex(hotcode@127.0.0.1)1> {:ok, pid} = DemoServer.start_link %{name: "blackode", money: 3_00_000}
{:ok, #PID<0.773.0>}
iex(hotcode@127.0.0.1)3> DemoServer.add_money pid, 400000
"400000 added to blackode "
iex(hotcode@127.0.0.1)4> :sys.get_state pid
%{money: 700000, name: "blackode"}
This is one way of starting elixir.
You can also start
somewhere as daemon and attach
to it. Before attaching, you need to start
first.
$ ./hotcode start
$ ./hotcode attach
Attaching to /home/john/mycode/elixir/hotcode/_build/prod/rel/hotcode/var/erl_pipes/hotcode@127.0.0.1/erlang.pipe.1 (^D to exit)
iex(hotcode@127.0.0.1)1> {:ok, pid} = DemoServer.start_link %{name: "blackode", money: 34000}
{:ok, #PID<0.773.0>}
Code Deployment
As I mentioned earlier, when you run mix release
, it produces a tarball, which contains our application and required stuff to run the application inside the releases
directory. _build/prod/rel/hotcode/releases or _build/dev/rel/hotcode/releases
based on the environment used for releases.
This directory contains the folders with version
names. For example, if we are releasing 0.1.0, then you will find a folder with same name 0.1.0
.
Inside every, version folder, you will find a tarball with the name of the release. In our case it is hotcode
. So, our tarball name would be hotcode.tar.gz
.
Let’s deploy the release in remote system.
Copying tarball to remote system
Copy the tarball to remote system using scp
. The scp is secure copy (remote file copy program).
$ cd _build/prod/rel/hotcode/releases
$ cd 0.1.0
$ scp hotcode.tar.gz user@host:/home/user/.
Here, replace user
with your username
and host
with your hostname
. This copies the tarball file to your remote system inside your home directory if you hold the privileges to your remote system.
Extracting the tarball in your remote system
Login to your remote system using ssh.
ssh user@host
Enter password if it asks you so.
After login, extract the hotcode.tar.gz
file using tar
command. As we copied the tarball to our home directory, you can simply run the following commands.
use@host$ mkdir hotcode
use@host$ tar -xzcf ./hotcode.tar.gz --directory hotcode
Start at the remote system
use@host$ cd hotcode/bin
use@host$ ./hotcode start
This will get you the Erlang Virtual Machine and the application on the remote system.
Now, make some changes to the DemoServer.ex
file and update the version value in mix.exs
file to "0.2.0"
.
Changes I made to demo_server.ex
file are following
I updated the state and added code_change
function to match and update the current state to new state of GenServer
. Otherwise, we end up with a miss match on state pattern matching.
You can read part1 , where I explained about the changes. Just by looking at the code we can understand them easily.
Next, after making some application changes and bumping the project version, we can generate an upgrade release using the following command:
$ MIX_ENV=prod mix release --upgrade
This again generates a regular release. As we updated the project version from 0.1.0 to 0.2.0, a new folder with name 0.2.0 is created in the releases directory.
Change the directory to new folder cd 0.2.0/
and you will see a new file called relup
for upgrade. Just read yourself about this file.
Copying the upgraded tarball
You need to make a directory releases/0.2.0
in the previous extracted hotcode folder and secure copy the tarball file to this directory. You don’t need to extract this time.
Upgrading the release 0.2.0
After it’s copied, upgrading the release can be done by stop
and start
again will upgrade to latest release
use@host$ cd hotcode/binuse@host$ ./hotcode stop
use@host$ ./hotcode start
We can also upgrade by running the upgrade
command like below.
user@host$ ./hotcode upgrade "0.2.0"
The desired version need to be specified to upgrade. Here, 0.2.0 is our upgraded version.
Let’s check our new state. We changed the key money
to salary
iex(user@host)1> {:ok, pid} = DemoServer.start_link %{name: "blackode", salary: 50000}
{:ok, #PID<0.773.0>}
iex(hotcode@127.0.0.1)6> DemoServer.add_money pid, 50000
%{name: "blackode", salary: 100000}
# In the previous it returns a string, now it gives you the map as the state got updated.
iex(hotcode@127.0.0.1)7> :sys.get_state pid
%{name: "blackode", salary: 100000}
Boom!! We have Upgraded the code successfully.
Conclusion
Managing releases is really a tough job and without restarting is really a night mare. It is just like opening the door for the run-time bugs unless you are cautious about what you are doing.
Hope you liked it. Feel free to exchange your ideas here…
Check out the GitHub repository on Killer Elixir Tips
Glad if you can contribute with a ★