Friday, November 28, 2014

Part 2. Run Erlang Release

Once you have an Erlang release, you might want to run it. And very often it should be run with specific parameters, for instance, some security cookie or node name. At toSeemo.org we also have enviroment-specific configuration like credentials for database connection. So let's change release env settings. As I mentioned in my previous post, we should change vm.args file. For toseemo I changed release default vm.args in rel/files folder.

0. Set enviroment dependent configuration.

As software development lifecycle includes such steps as QA, production deployment and development itself, we should have a number of enviroments for each of these steps. And your application configuration parameters may vary in response to a particular environment you run your software on. I.e. database connection: definitely database creds are different for production and qa platforms. This is the case for toSeemo.org. There we have a database and, we also want to have port to run cowboy webserver on in our configuration files. Here is part of our configuration file:
  {env, [
    {cowboy, [{port, 8888}]},
    {mongo, [{host, "127.0.0.1"}, {port, 27017}, {dbname, toseemo}]}
  ]}
Initially it's a part of toseemo.app.src file and all of these valuse can be reached with application:get_env/1,2,3 functions. But it's not a good idea to have this parameters in the code, as every config change will lead to the application being recompiled. It's not really flexible. Fortunately, Erlang allows storing these params separately in config files and passing this config as flag to erl command line application as follows:
$ erl -config PATH_TO_YOUR_CONFIG_FILE
This config has specific format and in general case it look like this:
[{Application1, [{Par11, Val11}, ..]},
 ..
 {ApplicationN, [{ParN1, ValN1}, ..]}].
And here is toseemo.config:
[
  {toseemo, [
    {cowboy, [{port, 8888}]},
    {mongo, [{host, "127.0.0.1"}, {port, 27017}, {dbname, toseemo}]}
  ]}
].
Now let's change rel/files/vm.args to make our releases use the config. (Added lines are marked in green)
## Enviroment vars config
-config /etc/toseemo/toseemo.config

## Name of the node
-name toseemo@127.0.0.1

## Cookie for distributed erlang
-setcookie toseemo

## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive
## (Disabled by default..use with caution!)
##-heart

## Enable kernel poll and a few async threads
##+K true
##+A 5

## Increase number of concurrent ports/sockets
##-env ERL_MAX_PORTS 4096

## Tweak GC to run more often
##-env ERL_FULLSWEEP_AFTER 10
That's it. We just need to create /etc/toseemo/toseemo.config file to make our releases use enviroment specific-configuration, which is still accessible with application:get_env/1,2,3 functions. Please follow this link for more info about configuration files.

1. Configuration for distributed node.

As we are going to run toseemo app on distributed nodes our configuration should reflect that. This means that node name and security cookie should be defined. Basically, they are defined by default, but you might want to have more secured cookie. And in case of toseemo we made some "magic" in nodes naming just to automate deployment of the release a bit. We use DigitalOcean to host toseemo. Each of toseemo nodes (DO droplets) has 2 network interfaces: eth0 and eth1. We would like to use an IP address attached to eth1 for node naming, so our node name looks like toseemo@10.X.Y.Z.

Setting of, lets say "IP based" name of an erlang node, is pretty simple task if you start erlang VM via console using erl command:
$ erl -name toseemo@`ip addr list eth1 |grep "inet " |cut -d' ' -f6|cut -d/ -f1`

But this will not work if you set it in vm.args. Our solution for that is using eval argument. Thus, here is our rel/files/vm.args, changes are marked in green:
## Enviroment vars config
-config /etc/toseemo/toseemo.config

## Name of the node
-name toseemo@`ip addr list eth1 |grep "inet " |cut -d' ' -f6|cut -d/ -f1`

## Cookie for distributed erlang
-setcookie super_duper_secured_cookie

## Name of the node
-eval "{ok,[{addr, Ip}]} = inet:ifget(\"eth1\", [addr]), IpStr = inet_parse:ntoa(Ip), net_kernel:start([list_to_atom(lists:concat(['toseemo@', IpStr])), longnames])."

## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive
## (Disabled by default..use with caution!)
##-heart

## Enable kernel poll and a few async threads
##+K true
##+A 5

## Increase number of concurrent ports/sockets
##-env ERL_MAX_PORTS 4096

## Tweak GC to run more often
##-env ERL_FULLSWEEP_AFTER 10
All the "magic" is done here by this code snippet. You can try to run it in erlang console, but please remember that node should not have a name.
{ok,[{addr, Ip}]} = inet:ifget("eth1", [addr]), 
IpStr = inet_parse:ntoa(Ip), 
net_kernel:start([list_to_atom(lists:concat(['toseemo@', IpStr])), longnames]).
Meanwhile -name attribute also present in the config. And to make it work we will need to change a bit application management script (rel/files/YOUR_NODE.sh (in our case it's rel/files/toseemo.sh))). Basically, defining -eval attribute is enought to start release with required node name but it's not enought to do another actions like attach, ping, getpid, stop etc. That's why we left -name in out config. The only thing left to do is changing the app management file by making it execute out "node-naming" command from -name attribute. Just open rel/files/YOUR_NODE.sh and make following changes:

Location in file Old Version New Version
function ping_node() $NODETOOL ping < /dev/null eval $NODETOOL ping < /dev/null
function get_pid() PID=`$NODETOOL getpid < /dev/null` PID=`eval $NODETOOL getpid < /dev/null`
stop action handler $NODETOOL stop eval $NODETOOL stop

Now we can start our nodes with dynamic names.

2. Attach node to cluster.

By default rebar generated release management script has no option to attach the node to a cluster, but it would be usefull to have it. So let's teach our node to do it. Basically, we have almost everything for that. There is one more rebar generated file - rel/files/nodetool which can be found in erts folder after release generation. If you open it (just open we don't need to change it) you will see handling for rpc command so let's use it.

First thing we need to do is adding a small module to an application. In case of toseemo it's toseemo_ctrl.erl :
-module(toseemo_ctrl).
-author("konstantin.shamko@gmail.com").

%% API
-export([connect_node/1]).

connect_node([Node]) ->
  net_kernel:connect_node(list_to_atom(Node)),
  ok.
We have just one function to connect current node to Node. This function should return ok atom. In general case when you connect a node to another node attached to cluster, all other cluster nodes will know about their new friend. Connections are by default transitive, so Node in the function above is just long name of any node in the cluster. On the next step let's edit rel/files/YOUR_NODE.sh (in our case it's rel/files/toseemo.sh) again. We need just to add an option to attach node to cluster. Just add this code to main sh script "case":
cluster_add)
    # Make sure a node is running
    ping_node
    ES=$?
    if [ "$ES" -ne 0 ]; then
        echo "Node is not running!"
        exit $ES
    fi

    eval $NODETOOL rpc toseemo_ctrl connect_node "$2"
    ;; 
That's it just run the following command on your newly started node:
$ bin/toseemo cluster_add toseemo@10.X.Y.Z
That's all for this post. Next time we will talk about hot code swapping.

Thursday, November 13, 2014

Part 1. Erlang releases using Rebar

At toSeemo.org we are widely using Erlang/OTP. Basically, all our major functionality is developed using Erlang.  And when our application was ready, there was a question on how to deliver and deploy it. Surely,  we wanted to do it in OTP way.Since by that time we had already relied on rebar, it was decided to expand its usage to include making releases.

Using rebar and Erlang releases was the idea we liked the most. Probably, it's the only correct approach to do application releases in OTP way. I say "probably", as there is really awesome tool called sync.  We used it as a development tool and we plan trying it in production.

This is the first article from the series of three or four. Later I will explain how we are doing hot code swapping and describe how it is incorporeted into our Jenkins software. And now please meet

Erlang releases using Rebar.

In this post I will explain how to create Erlang releases. I don't think that it's really necessary to explain how to setup rebar and start new project using it. I will simply assume that you just have rebar installed and an application started. As a result, your folders structure should look like this:
deps - folder for dependencies
ebin - folder for compiled sources 
include - folder for include files 
src - your application sources 
rebar.config - rebar configuration file 
So lets proceed to our releases:

Step 0. Change a bit folders structure.

As OTP release is set of OTP applications, it makes sense to reflect it in our folders structure. There are two type of applications: apps and deps. The apps folder will contain our applications (in my case it's just a single toseemo application) and deps folder will contain 3rd party dependencies:
apps - folder for our custom applications
    toseemo
        ebin - folder for compiled sources 
        include - folder for include files 
        src - your application sources 
deps - folder for dependencies
rebar.config - rebar configuration file 

Step 1. Create folder for releases.  

Go to your project folder root and create one more folder. Name of this folder should be rel.
$ mkdir rel 

Step 2. Change a bit rebar configuration file.

Just open your rebar.config file and add sub_dirs tuple there like in example below (highlighted with bold italic). I'm using real toSeemo.org configuration file in this example.
%>>> rebar.config begin
{sub_dirs, ["apps/toseemo", "rel"]}. 
{deps, [
    {cowboy, ".*", {git, "https://github.com/extend/cowboy.git", {tag, "1.0.0"}}},
    {mongodb, ".*", {git, "https://github.com/kshamko/mongodb-erlang.git", {branch, "cryptofix"}}},
    {cutkey, ".*", {git, "https://github.com/kshamko/cutkey.git", {branch, "r16-compatible"}}},
    {mochijson2, ".*", {git, " https://github.com/tel/mochijson2.git", {tag, "2.3.2"}}}
]}. 
%>>> rebar.config end 

Step 3. Create default application configuration.

On this step we will generate a set of files which are required to build OTP release (some default configs, binaries etc). Please note that I'm using toseemo as node id and it will be different for you application.
$ cd rel
$ rebar create-node nodeid=toseemo 
==> rel (create-node)
Writing reltool.config
Writing files/erl
Writing files/nodetool
Writing files/toseemo
Writing files/sys.config
Writing files/vm.args
Writing files/toseemo.cmd
Writing files/start_erl.cmd
Writing files/install_upgrade.escript
Actually it's not a big problem if you use toseemo as node id. This value can be changed any time in your rel/files/vm.args file

NOTE!!! To learn more about rebar commands you can use $ rebar -c in console. Or just follow this link (perhaps, even better way) to find more about available rebar options

Step 4. Edit reltool.config file.

We need to define paths to out application and it's dependencies dir, our application version, set the list of applications which should be started as part of the release (in case of toseemo one of these apps is cowboy webserver). Please note that rebar already added toseemo to applications list. In case you have no any other applications to start just skip this section.

Here is our initial reltool.config file created on the previous step:
{sys, [
       {lib_dirs, []}, % <=== here we will set pathes.
       {erts, [{mod_cond, derived}, {app_file, strip}]},
       {app_file, strip},
       {rel, "toseemo", "1", <=== here we will set the version
        [
         kernel,
         stdlib,
         sasl,
         toseemo <=== here we will add applications to be started
        ]},
       {rel, "start_clean", "",
        [
         kernel,
         stdlib
        ]},
       {boot_rel, "toseemo"},
       {profile, embedded},
       {incl_cond, derived},
       {excl_archive_filters, [".*"]}, %% Do not archive built libs
       {excl_sys_filters, ["^bin/(?!start_clean.boot)",
                           "^erts.*/bin/(dialyzer|typer)",
                           "^erts.*/(doc|info|include|lib|man|src)"]},
       {excl_app_filters, ["\.gitignore"]},
       {app, toseemo, [{mod_cond, app}, {incl_cond, include}]}
      ]}.

{target_dir, "toseemo"}.

{overlay, [
           {mkdir, "log/sasl"},
           {copy, "files/erl", "\{\{erts_vsn\}\}/bin/erl"},
           {copy, "files/nodetool", "\{\{erts_vsn\}\}/bin/nodetool"},
           {copy, "toseemo/bin/start_clean.boot",
                  "\{\{erts_vsn\}\}/bin/start_clean.boot"},
           {copy, "files/toseemo", "bin/toseemo"},
           {copy, "files/toseemo.cmd", "bin/toseemo.cmd"},
           {copy, "files/start_erl.cmd", "bin/start_erl.cmd"},
           {copy, "files/install_upgrade.escript", "bin/install_upgrade.escript"},
           {copy, "files/sys.config", "releases/\{\{rel_vsn\}\}/sys.config"},
           {copy, "files/vm.args", "releases/\{\{rel_vsn\}\}/vm.args"}
          ]}.
And the updated version of our config file is:
{sys, [
       {lib_dirs, ["../apps", "../deps"]}, 
       {erts, [{mod_cond, derived}, {app_file, strip}]},
       {app_file, strip},
       {rel, "toseemo", "0.1.1", <=== you can use any convention you want for version numbers
        [
         kernel,
         stdlib,
         sasl,
         inets,
         cowboy,
         cutkey,
         mongodb,
         toseemo
        ]},
       {rel, "start_clean", "",
        [
         kernel,
         stdlib
        ]},
       {boot_rel, "toseemo"},
       {profile, embedded},
       {incl_cond, derived},
       {excl_archive_filters, [".*"]}, %% Do not archive built libs
       {excl_sys_filters, ["^bin/(?!start_clean.boot)",
                           "^erts.*/bin/(dialyzer|typer)",
                           "^erts.*/(doc|info|include|lib|man|src)"]},
       {excl_app_filters, ["\.gitignore"]},
       {app, toseemo, [{mod_cond, app}, {incl_cond, include}]}
      ]}.

% target_dir and overlay sections are omitted here but they should exist.
Now let's generate a release.

Step 5. Generate release.

$ rebar get-deps
$ rebar compile
$ rebar generate
==> rel (generate)
$ ls rel
files  reltool.config  toseemo 
In this example toseemo is a folder with the release. It contains bin folder with executable file for your app.

Step 6. Run application.

$ ./rel/toseemo/bin/toseemo
Usage: toseemo {start|start_boot |foreground|stop|restart|reboot
|ping|console|getpid|console_clean|console_boot |attach|remote_console|upgrade}
As you can see it's possible to start app in different modes: daemon, foreground or with console attached, attach console to running node, ping node etc. Rebar has generated this usefull executable file for us.

But sometimes just runnig your erlang application is not enough. Sometimes you need to run erlang VM with some specific parameters. These parameters can be set in vm.args file. It can be changed just for single release so the file can be found in rel/toseemo/releases/RELEASE_VERSION/vm.args. Or it can be changed for all future releases in rel/files/vm.args - it's kind of default vm.args file. A bit more details about  that in my next post