非rootユーザーでのDocker実行を自動化
Kamalのbootstrap処理において、非rootユーザーが自動的にdockerグループに追加されるようになりました。これにより、手動でのグループ設定なしにDocker操作が可能になります。
背景
Kamal 1から存在していた問題として、非rootユーザーでサーバーをセットアップする際に Dockerソケットへの接続権限エラー が発生していました。#980 で報告されているように、デプロイ時に以下のエラーが発生します:
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
このエラーを解消するには、セットアップ後に手動で sudo usermod -aG docker $USER を実行してユーザーをdockerグループに追加する必要がありました。この手動作業がセットアップの自動化を妨げていました。
技術的な変更
bootstrap処理に dockerグループへの自動追加ロジック が組み込まれました。変更は lib/kamal/cli/server.rb と lib/kamal/commands/docker.rb の2つのファイルに及びます。
superuserチェックの厳格化
変更前:
def superuser?
[ '[ "${EUID:-$(id -u)}" -eq 0 ] || command -v sudo >/dev/null || command -v su >/dev/null' ]
end
変更後:
def superuser?
[ '[ "${EUID:-$(id -u)}" -eq 0 ] || sudo -nl usermod >/dev/null' ]
end
superuser? メソッドが、単にsudoコマンドの存在確認から usermod コマンドの実行権限チェック に変更されました。sudo -nl usermod により、パスワードなしで usermod を実行できるかを事前に確認します。
dockerグループ追加処理の実装
新たに add_group メソッド が追加されました:
def add_group
[ '[ "${EUID:-$(id -u)}" -eq 0 ] || id -nG "${USER:-$(id -un)}" | grep -qw docker || { sudo -n usermod -aG docker "${USER:-$(id -un)}" && kill -HUP ${PPID:-ps -o ppid= -p $$}; }' ]
end
このシェルコマンドは以下の3つの条件を順に評価します:
- rootユーザーの場合: 何もしない(グループ追加不要)
- 既にdockerグループに所属している場合: 何もしない
-
それ以外の場合:
usermod -aG dockerでユーザーをdockerグループに追加し、kill -HUPで親プロセス(SSH接続)を終了
bootstrap処理への統合
lib/kamal/cli/server.rb のbootstrap処理に、Docker インストール後の グループ追加と例外ハンドリング が追加されました:
if execute(*KAMAL.docker.superuser?, raise_on_non_zero_exit: false)
info "Missing Docker on #{host}. Installing…"
execute *KAMAL.docker.install
begin
execute *KAMAL.docker.add_group
# If the groups change, the session is terminated to force a re-login.
# Catch the resulting IOError and inform the user
rescue IOError
info "Session refreshed due to group change."
end
else
missing << host
end
グループ変更によってSSHセッションが終了する際に発生する IOError を捕捉 し、ユーザーに「Session refreshed due to group change.」というメッセージを表示します。
設計判断
SSH接続の強制終了 という一見過激な手段が採用されています。
Linuxのグループ変更は、ログイン時に読み込まれるため、既存のSSHセッションでは新しいグループ権限が反映されません。kill -HUP で親プロセスを終了することで、次回のコマンド実行時に新しいセッションが確立され、dockerグループの権限が有効になります。
テストコード(test/cli/server_test.rb)では、この動作を再現するために IOError を発生させるケースが追加されており、セッション終了が想定された挙動であることが確認できます:
SSHKit::Backend::Abstract.any_instance.expects(:execute)
.with('[ "${EUID:-$(id -u)}" -eq 0 ] || id -nG "${USER:-$(id -un)}" | grep -qw docker || { sudo -n usermod -aG docker "${USER:-$(id -un)}" && kill -HUP ${PPID:-ps -o ppid= -p $$}; }')
.at_least_once
.raises(IOError, "closed stream")
まとめ
本PRは、非rootユーザーでのDocker実行に必要な権限設定を自動化し、Kamalのセットアップ体験を改善しています。グループ変更とSSHセッション終了の関係を理解した上で、IOError を正常系として扱う設計により、手動介入なしの完全自動セットアップを実現しました。