Puma を systemd のユーザーサービスとして起動する
目次
Table of Contents
概要
Ruby on Rails でのデフォルト web サーバーである Puma を起動する方法は何通りかあるのですが、今回は systemd のユーザーサービスとして起動する方法を紹介します。
systemd は、主にサーバープロセスの管理などに使われ、サービスの登録・起動などには通常は root 権限が必要です。一方、systemd では、一般ユーザーがサービスを管理する方法もあり、それがユーザーサービスです。今回はそれを使って Puma を起動します。
また、設定方法だけで無く、その背景や個人的な意見なども後の方に記載しようと思います。
前提・やりたいこと
- Linux サーバー(Amazon Linux 2)上で Puma を直接動かす
- Docker 等は使わない
- Ruby on Rails 6
- Puma 5
- Capistrano を使ってデプロイ
- capistrano-puma を使用
Linux サーバー上の app
というユーザーの権限で Puma を動かし、app
ユーザー権限で Puma の再起動などをできるようにします。
Linux、Rails、Capistrano の基本的な知識は持っていることを前提とします。
設定方法
まずは設定方法をざっと説明します。
1. systemd のユーザーサービスを有効化する
最初の方で「systemd では、一般ユーザーがサービスを管理する方法もあり、それがユーザーサービスです」と記載しましたが、実はこの機能は Red Hat Enterprise Linux や派生 OS では、デフォルトでは無効とされているようです。以下、参考 URL です。
そのため、まずはこの機能を有効化します。
今回使用する app
というユーザーの uid が 1001 だとすると、/etc/systemd/system/user@1001.service
というファイルを以下の内容で作成します。
[Unit]
Description=User Manager for UID %i
After=systemd-user-sessions.service
# These are present in the RHEL8 version of this file except that the unit is Requires, not Wants.
# It's listed as Wants here so that if this file is used in a RHEL7 settings, it will not fail.
# If a user upgrades from RHEL7 to RHEL8, this unit file will continue to work until it's
# deleted the next time they upgrade Tableau Server itself.
After=user-runtime-dir@%i.service
Wants=user-runtime-dir@%i.service
[Service]
LimitNOFILE=infinity
LimitNPROC=infinity
User=%i
PAMName=systemd-user
Type=notify
# PermissionsStartOnly is deprecated and will be removed in future versions of systemd
# This is required for all systemd versions prior to version 231
PermissionsStartOnly=true
ExecStartPre=/bin/loginctl enable-linger %i
ExecStart=-/lib/systemd/systemd --user
Slice=user-%i.slice
KillMode=mixed
Delegate=yes
TasksMax=infinity
Restart=always
RestartSec=15
[Install]
WantedBy=default.target
次に、root
ユーザーの権限で以下を実行します。
systemctl daemon-reload
systemctl enable user@1001.service
systemctl start user@1001.service
loginctl enable-linger app
root 権限が必要な作業はここで終わりです。
後は、app
ユーザーの .bashrc
に以下の行を追記します。
[ -z "${XDG_RUNTIME_DIR}" ] && export XDG_RUNTIME_DIR=/run/user/$(id -ru)
2. puma サービスの登録
app
ユーザーのホームディレクトリ配下に .config/systemd/user/puma.service
というファイルを以下の内容で作成します。
[Unit]
Description=Puma HTTP Server
After=network.target
[Service]
Type=simple
# 以下の3行は環境によって適宜変えて下さい
WorkingDirectory=/home/app/current
Environment=RAILS_ENV=production
Environment=PORT=3000
Environment=USING_WEB_SERVER=true
ExecStart=/bin/bash -lc 'bundle exec puma -C config/puma.rb'
Restart=always
[Install]
WantedBy=default.target
次に、app
ユーザーの権限で以下のコマンドを実行します。
systemctl --user daemon-reload
systemctl --user start puma.service
systemctl --user enable puma.service
ここまでで、app
ユーザーが Puma の管理を出来るようになりました。
3. Capistrano の設定
Capfile
に以下を追記して下さい。
install_plugin Capistrano::Puma::Systemd
set :puma_systemctl_user, :app
set :puma_service_unit_name, :puma
config/puma.rb
には以下を追記します。
port ENV.fetch("PORT") { 3000 }
4. 確認
以下のようなコマンドが正しく動作するか確認して下さい。
cap production deploy
cap production puma:status
cap production puma:restart
背景
設定方法の説明が終わったところで、なぜ、こんな面倒な設定をする必要があるのか、背景について説明します。
Puma 5 から、デーモン化のコードが削除された
以下の issue 及び PR にある通り、Puma 5 でプロセスをデーモン化するコードが削除されました。
- Remove daemonization · Issue #1983 · puma/puma
- Remove daemonization by nateberkopec · Pull Request #2170 · puma/puma
以前は、Linux サーバー側では特に設定などはせず、Capfile
に以下のように記述しておけば、
# Capfile
install_plugin Capistrano::Puma::Daemon
以下のようなコマンドで Puma の管理が出来ました。
cap production puma:restart
が、Puma 5 からは出来なくなりました。
puma-daemon というのはある
issue のやり取りなどを見る限り、割と唐突に当該機能が削除されたため、その機能を復活させる gem が作られました。
ただ、自分の場合(色々焦っていたせいもあるのか) capistrano からうまく使う事が出来なかったので、違う方法でやろうと思いました。”capistrano puma-daemon” などで検索しても情報が少なかったのも1つの理由です。
解説・補足
ここでは、細かい解説・補足をしようと思います。
puma.service で User, Group を指定する方法との違い
/etc/systemd/system/puma.service
を作成し、そこで以下のように User
, Group
を指定することも出来ます。
[Unit]
Description=Puma HTTP Server
After=network.target
[Service]
Type=simple
User=app
Group=app
WorkingDirectory=/home/app/current
Environment=RAILS_ENV=production
Environment=PORT=3000
ExecStart=bundle exec puma -C /home/app/current/config/puma.rb
Restart=always
[Install]
WantedBy=multi-user.target
この方法と今回説明した方法の違いですが、この方法だと
- Puma サービスの起動・停止は
root
が行う - Puma のプロセスは
app
ユーザー、app
グループの権限で実行される
のに対し、今回説明した方法だと、起動・停止も app
ユーザーが行える点が異なります。
journalctl で、ユーザーサービスのログを見る方法
systemd 関連で問題が発生した場合は、journalctl
コマンドでログを見ます。同コマンドは、デフォルトでは(ユーザーサービスではない)システムサービスのログが表示されますが、ユーザーサービスのログを見るためには以下の通り実行します。
journalctl --user --user-unit=puma.service
以下のページが参考になりました。
- permissions – How to allow a user to use journalctl to see user-specific systemd service logs? – Server Fault
- systemd/User – ArchWiki
別の選択肢: sudo
今回の方法は若干面倒なので、もう少し簡単な別の選択肢があります。
app
ユーザーをパスワード無しで sudo
出来るようにすれば、Puma を通常の systemd のサービスとして登録して、普通に Capistrano で管理できます。
visudo
で以下のような行を追加すれば、app
ユーザーはパスワード無しで sudo systemctl
が実行できるようになると思います。(試していません。)
app ALL=(ALL) NOPASSWD:ALL /bin/systemctl
この方法の方が簡単ですが、 puma
以外のサービスも触れてしまうのが問題かもしれません。
capistrano-puma の systemd 関連のオプション
今回の方法では、Capfile
に以下の2つのオプションを設定しました。
:puma_systemctl_user
:puma_service_unit_name
どんなオプションがあるのかドキュメントではよく分からなかったので、ソースを見た方が早いと思いました。以下のソースです。
capistrano-puma/systemd.rb at master · seuros/capistrano-puma
トラブルシューティング
Failed at step GROUP spawning
今回紹介したユーザーサービスとして起動する方法の場合、一般ユーザー権限で動作するのですが、 puma.service
ファイルに User
, Group
を指定する必要はありません。というか指定してはいけません。
指定すると、 “Failed at step GROUP spawning” といったエラーが出ます。User
, Group
を指定すると setuid
, setgid
という、root 権限が必要なシステムコールが呼ばれるからだと思います。
自分も一度このエラーが出て、以下のページを見て間違いに気づきました。
raspberry pi3 – Systemctl – Failed at step Group spawning – Stack Overflow
Failed to get D-Bus connection: Connection refused
上の手順通りにやればこのエラーは起こらないはずです。このエラーが出た場合は、「1. systemd のユーザーサービスを有効化」の手順を正しく行ったか、再度見直してみて下さい。
参考になったのは以下の投稿です。
感想
削除する前にもう少し議論して欲しかった
削除する理由としては以下が挙げられていました。
It was almost 700 lines that no one maintained and is no longer necessary for us to include in Puma because there are better options available. Daemonization has not been a good application-level concern for a while now..
Remove daemonization · Issue #1983 · puma/puma
要約すると以下の通りです。
- デーモン化のコードはメンテされていない
- systemd 等のもっと良い選択肢がある
とは言え、あまり議論もされずに PR がマージされてしまったのは、OSS としてどうかなと思いました。少なくとも、同機能を「deprecated」にしてから、次のバージョンくらいで削除するとかにして欲しかったです。
better options についての説明がいまいち
systemd を使えという理屈は分からなくはないのですが、既存の機能(デーモン化)を削除して新しい機能(systemd)に移行してもらいたいのであれば、そのやり方などが分かりやすく説明してあるのが望ましいです。
Puma には systemd 関連の設定などをまとめた以下のドキュメントがあります。割としっかりした内容ではあります。
puma/systemd.md at master · puma/puma
ただ、Puma のデーモン化機能を使っていた人っていうのは、EC2 などの Linux サーバーで普通に Puma を起動して、デプロイには Capistrano を使うという場合が多いと思います。そうした人は、root
以外のユーザーを使うことが一般的なので、非 root ユーザーの場合どうするのか、という情報が無いと困ってしまいます。
レガシーな環境も考慮して欲しい
そもそも、2021年現在、web アプリはコンテナ化されている環境がかなり多いはずで、デーモン化のコードを使っているシステムというのはそれなりにレガシーな環境だと思います。レガシーな環境というのはそれなりに理由があって、例えば以下のようなものがあります。
- 近いうちにシステムが使われなくなる
- 新システムが開発中で、近いうちに現システムが置き換えられる
- 閉じた環境なのでセキュリティはあまり気にする必要が無く、あまり保守・修正はしていない
話は少し戻りますが、上の方で紹介した「削除する理由」からリンクされているページで、デーモン化がいかに良くないかといった説明がされています。
Don’t Daemonize your Daemons! | Mike Perham
我々はそんなことは百も承知なのです。bad practice は承知の上で、デーモン化のコードを使いたいのです。
それをいきなり
「デーモン化は bad practice だしあまりメンテされてないから機能を削除しといたよ。代わりに systemd とか使ってね。」
とか言われると困ってしまいます。
まとめ
Ruby on Rails + Puma のアプリを Capistrano でデプロイする環境の場合、以前は Puma 自体の機能で Puma をデーモン化して使う事が出来ましたが、Puma 5 で同機能は削除されました。
今回は、それの代わりに Puma を systemd のユーザーサービスとして起動する方法を紹介しました。この方法では(初期設定を除き)root 権限は必要無いので、Capistrano 経由で Puma の起動・停止が出来ます。
2021年現在、新規で Rails アプリを作る場合、実行環境では何らかのコンテナ化技術を使う事がかなり多いと思いますが、レガシーな Rails アプリのを Puma をアップグレードする場合などには、本記事の方法は参考になるかもしれません。
その他参考になったページ
systemd でのサービス管理について説明したページは沢山ありますが、今回使ったユーザーサービスについての説明はかなり少なかったです。その中で、以下のページは日本語で唯一参考になったページです。
コメントを残す