Foreman Systemd Export

Posted on 12 Feb 2017 by Eric Oestrich

I switched my side project to a single Linode instance this weekend in an effort to simplify its setup. In the process I decided to try foreman as a way of keeping the rails server up and running. I was very pleased with what came out of it.

Export scripts

To start you need to export systemd scripts into the /etc/systemd/system folder. Make sure to replace --user deploy with the user that your app should run as. This capistrano task will handle that.

namespace :app do
  desc "Reload systemd"
  task :systemd do
    on roles(:web) do
      within release_path do
        execute :sudo, :foreman, :export, :systemd, "/etc/systemd/system", "--user deploy"
        execute :sudo, :systemctl, "daemon-reload"
      end
    end
  end
end

The first deploy you won’t need to daemon-reload, but whenever a systemd file changes the daemon needs to reload to know about the changes.

The export will create files that are named app-web@.service, where web is the name of the Procfile line. In systemd an @ service allows you to pass in parameters to the script. For this case, it will be the PORT environment variable.

Start/Stop/Restart

Once the scripts are in place. You need capistrano tasks that will. These tasks are all essentially the same and look like this:

namespace :app do
  desc "Start web server"
  task :start do
    on roles(:web) do |host|
      within release_path do
        execute :sudo, :systemctl, :start, "app-web@5000.service"
        execute :sudo, :systemctl, :start, "app-worker@5100.service"
      end
    end
  end
end

This will start up a web and worker instance with the default ports that foreman uses. As you add in new Procfile lines, you will need to add a new line here.

Adding to a deploy

Finally you need to insert systemd into the deploy cycle. This is easy by adding the tasks in after the appropriate capistrano task deploy:publishing.

after 'deploy:publishing', 'app:systemd'
after 'app:systemd', 'app:restart'

Enhancements

Capistrano connects into the deploy user and that user has sudo rights. It would be more secure to only allow these specific commands to be sudoable. Add this to your sudoers file to enable specific commands only.

deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart app-web@5000.service
deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart app-worker@5100.service

The full config/deploy.rb file

namespace :app do
  desc "Start web server"
  task :start do
    on roles(:web) do |host|
      within release_path do
        execute :sudo, :systemctl, :start, "app-web@5000.service"
        execute :sudo, :systemctl, :start, "app-worker@5001.service"
        execute :sudo, :systemctl, :start, "app-clock@5002.service"
      end
    end
  end

  desc "Stop web server"
  task :stop do
    on roles(:web) do |host|
      within release_path do
        execute :sudo, :systemctl, :stop, "app-web@5000.service"
        execute :sudo, :systemctl, :stop, "app-worker@5001.service"
        execute :sudo, :systemctl, :stop, "app-clock@5002.service"
      end
    end
  end

  desc "Restart web server"
  task :restart do
    on roles(:web) do |host|
      within release_path do
        execute :sudo, :systemctl, :restart, "app-web@5000.service"
        execute :sudo, :systemctl, :restart, "app-worker@5001.service"
        execute :sudo, :systemctl, :restart, "app-clock@5002.service"
      end
    end
  end

  desc "Reload systemd"
  task :systemd do
    on roles(:web) do
      within release_path do
        execute :sudo, :foreman, :export, :systemd, "/etc/systemd/system", "--user deploy"
        execute :sudo, :systemctl, "daemon-reload"
      end
    end
  end
end

after 'deploy:publishing', 'app:systemd'
after 'app:systemd', 'app:restart'
comments powered by Disqus
Creative Commons License
This site's content is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License unless otherwise specified. Code on this site is licensed under the MIT License unless otherwise specified.