In production environment, you have to deal with service crashes and auto restarts, and there are plenty of tools to that end— supervisord, monit etc. Having lots of projects, we try to use standard utilities that come with distros, Ubuntu in our case.
Standard Ubuntu init system is Systemd, definitely one of the most famous and widely used tools. It’s a security standard to run apps under non-privileged users, and systemd offers a solution in this case.
Our initial setup is Ruby via RVM and Unicorn/Puma running simple Rack application.
Important notice, I don't provide Docker files with examples as systemd won't succeed in a non-privileged mode in Docker, so I advise you to test this in a separate virtual machine. You can find more on this case on askubuntu and Docker docs. For macOS, you can use LXD or multipass to spin up Ubuntu VM. LXD setup can be tricky, so for this project, I've prepared a cloud-init file that can be used as a multipass entrypoint.
multipass launch -n sysd -c 1 -m 2g --cloud-init init.yml xenial
Based on Xenial, because we made an infrastructure upgrade from 12.04 to 16.04 LTS in 2017, so it’s our standard for this & next year.
Assume we have a non-privileged user
awesomeapp with home directory
/apps/awesomeapp and shell
/bin/bash. App root
Let’s prepare the directory structure for systemd files:
mkdir -p /apps/awesomeapp/.config/systemd/user
unicorn.service . All files for the application can be found at sample repo on my Github.
awesomeapp@sysd:~$ systemctl --user daemon-reload Failed to connect to bus: No such file or directory
There is a discussion about this error on launchpad. To fix it, just add to
export XDG_RUNTIME_DIR=/run/user/`id -u`
After that, from root or your sudo user enable user lingering:
sudo loginctl enable-linger awesomeapp
check that it works:
loginctl user-status awesomeapp
You should see smth like this:
Login back to the user and reload the daemon:
sudo -iu awesomeapp systemctl --user daemon-reload
systemctl command under app user without sudo, it’s not a mistake. App user can use it freely, so, dev team can maintain service files easily without operations team involved.
Check the status:
It’s inactive, not running and disabled. It means that it won’t start up after boot. For example, you have 1..N app machines, and some of them can go down for system upgrades or just because of a crash, so we need the app running after reboot.
systemctl --user enable unicorn
Let’s give it a try:
Unicorn master process and 2 worker process, as we configured.
Let’s do the same for Puma, standard Rails web server. Puma.service file:
Now you can reboot VM and check that everything up and running.
All commands with comments.
# check unicorn.service status systemctl --user status unicorn # start unicorn.service systemctl --user start unicorn # start unicorn.service on VM boot systemctl --user enable unicorn # disable starting unicorn.service on VM boot systemctl --user disable unicorn # restart unicorn.service systemctl --user restart unicorn # graceful restart unicorn.service systemctl --user reload unicorn # stop unicorn.service systemctl --user stop unicorn
# check puma.service status systemctl --user status puma # start puma.service systemctl --user start puma # start puma.service on VM boot systemctl --user enable puma # disable starting puma.service on VM boot systemctl --user disable puma # restart puma.service systemctl --user restart puma # graceful restart puma.service systemctl --user reload puma # stop puma.service systemctl --user stop puma
That’s it for today. Puma and Unicorn configs are for the sample app, for production purposes, you definitely must adopt to needs and load.