Restart ポリシーのカスタマイズを可能にしたリバートと実装改善
この PR は、デフォルトで付与されていた --restart unless-stopped をリバートし、アクセサリとロールごとに任意の再起動ポリシーを設定できるようにした変更です。これにより、Init コンテナなど再起動ポリシーが重要になるシナリオでのデプロイが阻害されなくなります。
背景
従来の実装では、docker run コマンドに常に --restart unless-stopped が付与されていました。その結果、Init コンテナで on-failure など特定のポリシーを要求するケースでは、Kamal がエラーを返すか期待通りに動作しませんでした。Issue #1863 では、PR #1503 の変更を元に戻しつつ、再起動ポリシーを柔軟に指定できる仕組みが求められていました。実際に作者は restart: on-failure を設定した Init コンテナで動作確認を取り、問題が解消されたことを報告しています。
技術的な変更
コマンド層の修正
lib/kamal/commands/accessory.rb と lib/kamal/commands/app.rb で、ハードコードされた unless‑stopped を restart_policy / role.restart_policy から取得するよう置き換えました。
@@ -4,7 +4,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
attr_reader :accessory_config
delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd,
:network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args,
- :secrets_io, :secrets_path, :env_directory, :proxy, :running_proxy?, :registry,
+ :restart_policy, :secrets_io, :secrets_path, :env_directory, :proxy, :running_proxy?, :registry,
to: :accessory_config
@@ -16,7 +16,7 @@ def run(host: nil)
docker :run,
"--name", service_name,
"--detach",
- "--restart", "unless-stopped",
+ "--restart", restart_policy,
*network_args,
*config.logging_args,
@@ -16,7 +16,7 @@ def run(hostname: nil)
docker :run,
"--detach",
- "--restart unless-stopped",
+ "--restart", role.restart_policy,
"--name", container_name,
"--network", "kamal",
設定層の拡張
アクセサリ設定
lib/kamal/configuration/accessory.rb に以下の変更を加えました。
-
option_argsがrestartキーを除外して Docker オプションを生成するようになりました。 -
restart_policyメソッドがdocker_optionsからrestartキーを探し、存在しなければデフォルトunless-stoppedを返します。
@@
def option_args
- if args = accessory_config["options"]
- optionize args
- else
- []
- end
+ optionize docker_options.reject { |key, _| key.to_s == "restart" }
end
+
+ def restart_policy
+ restart_policy_option || "unless-stopped"
+ end
@@
def docker_options
- accessory_config["options"] || {}
+ accessory_config["options"] || {}
end
def restart_policy_option
- # previous implementation removed
+ docker_options.find { |key, _| key.to_s == "restart" }&.last
end
ロール設定
lib/kamal/configuration/role.rb でも同様に option_args から restart を除外し、restart_policy を取得するロジックを追加しました。
@@
def option_args
- if args = specializations["options"]
- optionize args
- else
- []
- end
+ optionize docker_options.reject { |key, _| key.to_s == "restart" }
end
+
+ def restart_policy
+ restart_policy_option || "unless-stopped"
+ end
@@
def docker_options
- specializations["options"] || {}
+ specializations["options"] || {}
end
def restart_policy_option
- # previous implementation removed
+ docker_options.find { |key, _| key.to_s == "restart" }&.last
end
バリデーションの強化
lib/kamal/configuration/validator.rb に validate_restart_policy! を追加し、restart オプションが文字列であり、許容される形式(no, always, unless‑stopped, on‑failure, on‑failure:N)に合致するかを検証します。
@@
- if options
- error "Cannot set restart policy in docker options, unless-stopped is required" if options["restart"]
+ if restart_policy = options&.find { |key, _| key.to_s == "restart" }
+ validate_restart_policy!(restart_policy.last)
end
end
+
+ def validate_restart_policy!(restart_policy)
+ with_context("options/restart") do
+ unless restart_policy.is_a?(String)
+ error %(should be a string. Use "no" to disable restarts)
+ end
+ unless restart_policy.match?(/\A(?:no|always|unless-stopped|on-failure(?::\d+)?)\z/)
+ error "should be no, always, unless-stopped, on-failure, or on-failure:N"
+ end
+ end
+ end
テストの追加
- アクセサリ・ロールごとの
restart_policy取得テスト -
docker runコマンドに期待通り--restartが出力されること、execute_in_new_container系では除外されること - 不正な型(boolean)や形式に対するエラーメッセージの検証
設計判断
最小限の破壊的変更で後方互換性を保つ 方針が全体に徹底されています。既存の options ハッシュをそのまま利用し、restart キーだけを特別扱いすることで、ユーザーは従来通り他の Docker オプションを書き続けられ、かつ再起動ポリシーを柔軟に指定できるようになりました。新しい設定キーを導入しなかった点は、設定ファイルの破壊的変更を避ける意図が明確です。
バリデーションを集中管理 したことで、設定ミスが早期に検出され、エラーメッセージは階層的コンテキスト(options/restart)を示すためデバッグが容易になります。これらは Kamal が掲げる「設定は宣言的に、失敗は早期に」の設計哲学と一致します。
まとめ
この PR は、デフォルトの --restart unless-stopped を撤廃し、アクセサリとロールごとに任意の再起動ポリシーを設定できるようにしたリバートと実装改善です。設定構造の拡張は最小限に抑えつつ、バリデーションとテストで堅牢性を高め、Init コンテナなど再起動ポリシーが重要なシナリオで安全にデプロイできるようになりました。