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! 🙂

By Val

Leave a Reply