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