Development

Development

To get info about new technologies, perspective products and useful services

BigData

BigData

To know more about big data, data analysis techniques, tools and projects

Refactoring

Refactoring

To improve your code quality, speed up development process

Erlang service easy deploy with Enot

Erlang service easy deploy with Enot

In this guide you’ll learn how to add deploy feature to the Erlang service.

As a result you will get a real Erlang service, with logs in /var/log/example_service, systemd support and possibility to easy install it on every system via enot install comtihon/example_service even without Erlang installed!

1.1 Add logs

To determine whether our service is still alive and running – lets add some logs.

Add a lager dependency to enot_config.json:

{
 "name": "lager",
 "url": "https://github.com/erlang-lager/lager.git",
 "tag": "3.5.0"
}

And add a module es_log with lager’s parsetransform:

-module(es_log).

-compile([{parse_transform, lager_transform}]).

-export([
 debug/1, debug/2,
 info/1, info/2,
 warn/1, warn/2,
 err/1, err/2]).

debug(M) -> debug(M, []).
debug(M, A) -> lager:debug(M, A).

info(M) -> info(M, []).
info(M, A) -> lager:info(M, A).

warn(M) -> warn(M, []).
warn(M, A) -> lager:warning(M, A).

err(M) -> err(M, []).
err(M, A) -> lager:error(M, A).

Let’s use it. First log we saw was “example application started”. It was done with io:format. We should change it to lager es_log:info("example_service started").

Also it is a good idea to log all incoming requests. Add this code to es_handler:

Path = cowboy_req:path(Req0),
Qs = cowboy_req:qs(Req0),
es_log:info("New request ~p : ~p", [Path, Qs]),

1.2 Check the logs

Build a release again and launch the service:

enot release
_rel/example_service/bin/example_service console

Call it with curl "127.0.0.1:8080/hello/world?foo=bar&baz=bar"

You will see logs directly in terminal you’ve started the service:

18:05:15.210 [info] New request <<"/hello/world">> : <<"foo=bar&baz=bar">>

Besides the console log lager can write logs to file (and do it by default) in the release’s directory.
Go to _rel/example_service/log and make sure.

1.3 Move lager’s output

To make our service look real lets create a user first: sudo useradd example_user.

To make a service comfortable usage for devOps we should move log files to a standard /var/log path.

To create output directory for logs and make our example_user its owner run:

sudo mkdir /var/log/example_service
sudo chown -R example_user /var/log/example_service

Next step is to configure lager to use created logs directory. To do it we should modify release configuration file rel/sys.conf. It was created automatically when we run enot release for the first time.  Inside sys.conf we can specify dependent applications configuration. To do it for the lager application insert this block in []:

{lager, [
 {log_root, "/var/log/example_service"},
 {handlers, [
 {lager_console_backend, [{level, info}]},
 {lager_file_backend, [{file, "error.log"}, {level, error}]},
 {lager_file_backend, [{file, "console.log"}, {level, info}]}
 ]}
]}

You should call enot release once more to build service with this change.

2.1 Add systemd support

Running service manually is not that comfortable. Lets make it run through Systemd. Add example_service.service to /etc/systemd/system:

[Unit]

[Service]
Type=simple
User=example_user
Group=example_user
ExecStart=/opt/example_service/_rel/example_service/bin/example_service foreground
ExecStop=/opt/example_service/_rel/example_service/bin/example_service stop
ExecReload=/opt/example_service/_rel/example_service/bin/example_service restart
Restart=always

[Install]
WantedBy=multi-user.target

As systemd requires absoulute path for Exec let’s move our service to /opt/example_service.

Run:

sudo cp -r ../example_service /opt/
sudo chown -R example_user /opt/example_service

2.2 Run and check

Ensure you don’t have any other service listening 8080. This also applies to our example service. If you’ve run it manually – kill it now.

To run your example service via systemd use systemctl start example_service. To get it’s state use systemctl status example_service. You should get:

● example_service.service 
  Loaded: loaded (/etc/systemd/system/example_service.service; disabled; vendor preset: disabled) 
  Active: active (running) since Sun 2017-07-23 18:35:52 CEST; 3s ago 
Main PID: 8710 (beam.smp) 
   Tasks: 41 (limit: 4915) 
  Memory: 45.3M 
     CPU: 604ms 
  CGroup: /system.slice/example_service.service 
          ├─8710 /opt/example_service/_rel/example_service/erts-9.0/bin/beam.smp -Bd -- -root /opt/example_service/_rel/example_serv 
          └─8754 erl_child_setup 1024

Part of the logs can also be seen in systemctl status, but lets check logs in /var/log/example_service with tail -f /var/log/example_service/console.log. In another terminal run curl "127.0.0.1:8080/hello/world?foo=bar&baz=bar". In the first terminal you will see the request’s log:

2017-07-23 18:38:57.691 [info] <0.586.0>@es_log:info:15 New request <<"/hello/world">> : <<"foo=bar&baz=bar">>

3.1 Automatic install steps for easy deployment

To make our service’s usage more comfortable for other people lets simplify its installation.
First thing to do is to add systemd service script to your example_service git repository into priv directory.

Then lets repeat all configuration steps we did below in enot_config.json install section:

"install" : [
 {"release": {"rel_dir" : "/opt"}},
 {"shell": "id -u example_user &>/dev/null || useradd example_user"},
 {"shell": "mkdir -p /var/log/example_service"},
 {"shell": "chown -R example_user /var/log/example_service"},
 {"shell": "cp /opt/_rel/example_service/lib/example_service-*/priv/example_service.service /etc/systemd/system/"},
 {"shell": "chown -R example_user /opt/example_service"},
 {"shell": "systemctl enable example_service"},
 {"shell": "systemctl start example_service"}
]

What does it mean?

  • build a release of the example service and put it into opt dir. This step will download right version of erts from EnotHub, so it can be performed on systems with no Erlang installed.
  • create a user example_user if it was not created
  • create log directory
  • change owner of created log directory from root to example_user
  • copy systemd service script, saved to priv previosly
  • change owner of /opt/example_service to example_user
  • enable example_service autostart in systemd
  • start example_service via systemd

3.2 Deploy your service to EnotHub and install on another system

To add your service to EnotHub follow these steps.

After it is added go to another machine with Enot installed and run this command:

enot install <your_github_username>/example_service

You will see something like this:

INFO:enot:Extract /tmp/enot/example_service.cp
INFO:enot:add comtihon/example_service
INFO:enot:build deps for example_service
INFO:enot:build deps for cowboy
INFO:enot:build deps for cowlib
INFO:enot:build deps for ranch
INFO:enot:build deps for jsone
INFO:enot:build deps for lager
INFO:enot:build deps for goldrush
INFO:enot:fetch erts for 20
===> Starting relx build process ...
===> Resolving OTP Applications from directories:
 /root/.cache/enot/comtihon/example_service/0.0.6/20/ebin
 /root/.cache/enot/comtihon/example_service/0.0.6/20/deps
 /usr/lib/erlang/lib
 /root/.cache/enot/comtihon/example_service/0.0.6/20/_rel
===> Resolved example_service-0.0.1
===> Including Erts from /root/.cache/enot/comtihon/example_service/0.0.6/20
===> release successfully created!
Created symlink /etc/systemd/system/multi-user.target.wants/example_service.service → /etc/systemd/system/example_service.service.
INFO:enot:comtihon/example_service: 0.0.6 installed

This means:

  • example_service prebuilt package was downloaded from EnotHub
  • all prebuilt deps for example_service were also downloaded
  • erts for 20 erlang was downloaded as well
  • example_service was added to systemd, enabled and started

If you go to 127.0.0.1:8080 you will see your installed service’s output.

3.3 Uninstall gracefully

You should provide a convenient way to uninstall your useful service. To do so – just add uninstall block to your enot_config.json with cleanup steps.

For example_service it is:

"uninstall": [
 {"shell" : "systemctl stop example_service"},
 {"shell" : "systemctl disable example_service"},
 {"shell" : "rm -rf /opt/_rel"},
 {"shell" : "userdel example_user"}
]

After this you can run enot uninstall comtihon/example_service and service will be uninstalled:

Removed /etc/systemd/system/multi-user.target.wants/example_service.service.
INFO:enot:comtihon/example_service: 0.0.6 uninstalled

Thus deploying an Erlang service is a piece of cake now. You can forget about Erlang-specific configuration files, Erlang installation and building all deps.

Happy deploying! 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *