Target systems and release handling

March 10, 2010

Categories: erlang, release_handler

The SASL OTP application provides tools for upgrading and downgrading application versions in a running system. This is done by packaging an application and its dependencies along with the Erlang runtime system into a tarball that can be deployed and installed on a target system. Once the first version of the target system is running, application upgrades can be performed on live code.

Creating the first target system

Before any hot code swapping can occur there must be an initial system running. Packaging the first target system is slightly different than packaging subsequent application releases.

Here is the documentation that I followed to get this working: http://www.erlang.org/doc/system_principles/create_target.html

Start with foo

I'm going to create a foo application and deploy the initial version of the target system locally. To facilitate comparing app versions I'll initialize a git repository in the foo directory.

$ mkdir /src/foo
$ cd /src/foo
$ git init
Initialized empty Git repository in /src/foo/.git/

The foo application will have an app file, an application module and a gen_server.

$ mkdir ebin src
$ touch ebin/foo.app src/foo.erl src/foo_app.erl

- ebin/
     - foo.app
- src/
     - foo_app.erl
     - foo.erl

foo.app:

foo_app.erl

foo.erl:

The foo gen_server does nothing special. It stores an integer as its state and provides a function, get_state/0, that returns the current state.

Rel file

Create a rel file for version 0.1 of the foo app:

$ touch foo-0.1.rel

foo-0.1.rel:

Add an empty sys.config file:

$ echo "[]." > sys.config

At this point you should have the following app structure

- ebin/
     - foo.app
- src/
     - foo_app.erl
     - foo.erl
- foo-0.1.rel
- sys.config

Add the files to git and commit

$ git add .
$ git commit -m "commit of 0.1"

Packaging the target system

Compile the modules and create the target system package:

$ erlc -o ebin src/*.erl    
$ mkdir bin releases
$ erl -pa /src/foo/ebin
1> systools:make_script("foo-0.1", [no_module_tests]).
ok
2> file:copy(filename:join([code:root_dir(), "bin", "start_clean.boot"]), "bin/start.boot").
{ok,5354}
3> file:write_file("releases/start_erl.data", iolist_to_binary([erlang:system_info(version), " ", "0.1", "\n"])).
ok
4> systools:make_tar("foo-0.1", [{erts, code:root_dir()}, {dirs, ['bin', 'releases']}]).
ok

Installing the target system

Now we have a target system that can be deployed. I'm going to deploy and run the target system locally in the /usr/local/erl-target directory:

$ sudo mkdir /usr/local/erl-target
$ sudo chown -R jacobvorreuter:jacobvorreuter /usr/local/erl-target/

$ cp foo-0.1.tar.gz /usr/local/erl-target/
$ cd /usr/local/erl-target
$ tar xvf foo-0.1.tar.gz
$ mv lib/foo-0.1/bin ./
$ mv lib/foo-0.1/releases/start_erl.data releases/

$ sed "s|%FINAL_ROOTDIR%|`pwd`|" <erts-5.7.2/bin/erl.src >bin/erl
$ sed "s|%FINAL_ROOTDIR%|`pwd`|" <erts-5.7.2/bin/start.src >bin/start
$ sed "s|%FINAL_ROOTDIR%|`pwd`|" <erts-5.7.2/bin/start_erl.src >bin/start_erl
$ cp erts-5.7.2/bin/epmd erts-5.7.2/bin/run_erl erts-5.7.2/bin/to_erl bin/
$ chmod +x bin/*

$ cd releases
$ ../bin/erl
1> release_handler:create_RELEASES("/usr/local/erl-target", "foo-0.1.rel").

We're going to edit the startup script that was generated for us at /usr/local/erl-target/bin/start_erl. Add a node name so that the last line looks like this:

exec $BINDIR/erlexec -boot $RELDIR/$VSN/start -config $RELDIR/$VSN/sys -name foo@`hostname` ${1+"$@"} 

Fire up the foo application:

$ cd /usr/local/erl-target
$ bin/start
$ bin/erl -name jake -remsh foo@`hostname`
1> application:which_applications().
[{foo,"foo app","0.1"},
 {sasl,"SASL  CXC 138 11","2.1.6"},
 {stdlib,"ERTS  CXC 138 10","1.16.2"},
 {kernel,"ERTS  CXC 138 10","2.13.2"}]
2> foo:get_state().
0
3> foo:get_state().
1

The foo application is now running...

Version 0.2 of foo

The documentation I followed for the steps below can be found here: http://www.erlang.org/doc/design_principles/release_handling.html

Create another directory called foo1 and clone the repo from foo:

$ cd /src
$ git clone -l /src/foo foo1
$ cd foo1

Change the foo app version in foo.app:

Remove foo-0.1.rel and replace with foo-0.2.rel:

Update the foo.erl gen_server to store a float in its state:

Create an appup file:

$ touch ebin/foo.appup

foo.appup:

Packaging release 0.2

Compile the code

$ erlc -o ebin src/*.erl

Generate a relup file, boot script and tarball:

$ erl -pa /src/foo /src/foo/ebin /src/foo1/ebin
1> systools:make_relup("foo-0.2", ["foo-0.1"], ["foo-0.1"]).
ok
2> systools:make_script("foo-0.2"). 
ok
3> systools:make_tar("foo-0.2").   
ok

Deploying release 0.2

Copy the new release package into the releases directory:

$ cp foo-0.2.tar.gz /usr/local/erl-target/releases/

Connect to our running node, upack the release and install it:

$ /usr/local/erl-target/bin/erl -name rel -remsh foo@`hostname`
1> release_handler:unpack_release("foo-0.2").
{ok,"0.2"}
2> release_handler:install_release("0.2").

Test that it worked:

3> foo:get_state().
2.0
4> foo:get_state().
3.0

It did!!!

blog comments powered by Disqus