Upgrade commands for Kamal 1.x -> 2.0

Adds:
- `kamal upgrade` to upgrade all app hosts and accessory hosts
- `kamal proxy upgrade` to upgrade the proxy on all hosts
- `kamal accessory upgrade [name]` to upgrade accessories on all hosts

Upgrade takes rolling and confirmed options and calls `proxy upgrade`
and `accessory upgrade` in turn.

To just upgrade a single host add -h [host] to the command. But the
upgrade should run on all hosts, not just those running the proxy.

Calling upgrade on a host that has already been upgraded should work ok.

Upgrading hosts causes downtime but you can avoid if you run multiple
hosts by:
1. Implementing the pre-proxy-reboot and post-proxy-reboot hooks to
   remove the host from external load balancers
2. Running the upgrade with the --rolling option

**kamal proxy upgrade**
1. Creates a `kamal` network if required
2. Stops and removes the old proxy (whether Traefik or kamal-proxy)
3. Starts a kamal-proxy container in the `kamal` network
4. Reboots the app containers in the `kamal` network

**kamal accessory upgrade [name]**
1. Creates a `kamal` network if required
2. Reboots the accessory containers in the `kamal` network

A matching `downgrade` command will be added to Kamal 1.9.
This commit is contained in:
Donal McBreen
2024-09-17 12:55:11 +01:00
parent 434490bd0c
commit 7f15fd143f
9 changed files with 180 additions and 9 deletions

View File

@@ -218,6 +218,29 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
end
end
desc "upgrade", "Upgrade accessories from Kamal 1.x to 2.0 (restart them in 'kamal' network)"
option :rolling, type: :boolean, default: false, desc: "Upgrade one host at a time"
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
def upgrade(name)
confirming "This will restart all accessories" do
with_lock do
if options[:rolling]
KAMAL.accessory_hosts.each do |host|
say "Upgrading accessories on #{host}...", :magenta
KAMAL.with_specific_hosts(host) do
reboot name
end
say "Upgraded accessories on #{host}...", :magenta
end
else
say "Upgrading accessories on all hosts...", :magenta
reboot name
say "Upgraded accessories on all hosts", :magenta
end
end
end
end
private
def with_accessory(name)
if KAMAL.config.accessory(name)

View File

@@ -174,6 +174,10 @@ module Kamal::Cli
instance_variable_get("@_invocations").first
end
def reset_invocation(cli_class)
instance_variable_get("@_invocations")[cli_class].pop
end
def ensure_run_directory
on(KAMAL.hosts) do
execute(*KAMAL.server.ensure_run_directory)

View File

@@ -189,6 +189,37 @@ class Kamal::Cli::Main < Kamal::Cli::Base
end
end
desc "upgrade", "Upgrade from Kamal 1.x to 2.0"
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
option :rolling, type: :boolean, default: false, desc: "Upgrade one host at a time"
def upgrade
confirming "This will replace Traefik with kamal-proxy and restart all accessories" do
with_lock do
if options[:rolling]
(KAMAL.hosts | KAMAL.accessory_hosts).each do |host|
KAMAL.with_specific_hosts(host) do
say "Upgrading #{host}...", :magenta
if KAMAL.hosts.include?(host)
invoke "kamal:cli:proxy:upgrade", [], options.merge(confirmed: true, rolling: false)
reset_invocation(Kamal::Cli::Proxy)
end
if KAMAL.accessory_hosts.include?(host)
invoke "kamal:cli:accessory:upgrade", [ "all" ], options.merge(confirmed: true, rolling: false)
reset_invocation(Kamal::Cli::Accessory)
end
say "Upgraded #{host}", :magenta
end
end
else
say "Upgrading all hosts...", :magenta
invoke "kamal:cli:proxy:upgrade", [], options.merge(confirmed: true)
invoke "kamal:cli:accessory:upgrade", [ "all" ], options.merge(confirmed: true)
say "Upgraded all hosts", :magenta
end
end
end
end
desc "version", "Show Kamal version"
def version
puts Kamal::VERSION

View File

@@ -62,7 +62,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
end
end
desc "upgrade", "Upgrade to correct proxy on servers (stop container, remove container, start new container)"
desc "upgrade", "Upgrade to kamal-proxy on servers (stop container, remove container, start new container, reboot app)"
option :rolling, type: :boolean, default: false, desc: "Reboot proxy on hosts in sequence, rather than in parallel"
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
def upgrade
@@ -72,6 +72,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
host_groups = options[:rolling] ? KAMAL.hosts : [ KAMAL.hosts ]
host_groups.each do |hosts|
host_list = Array(hosts).join(",")
say "Upgrading proxy on #{host_list}..."
run_hook "pre-proxy-reboot", hosts: host_list
on(hosts) do |host|
execute *KAMAL.auditor.record("Rebooted proxy"), verbosity: :debug
@@ -86,19 +87,17 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
execute *KAMAL.proxy.remove_image
end
begin
old_hosts, KAMAL.specific_hosts = KAMAL.specific_hosts, hosts
KAMAL.with_specific_hosts(hosts) do
invoke "kamal:cli:proxy:boot", [], invoke_options
reset_invocation(Kamal::Cli::Proxy)
invoke "kamal:cli:app:boot", [], invoke_options
reset_invocation(Kamal::Cli::App)
invoke "kamal:cli:prune:all", [], invoke_options
reset_invocation(Kamal::Cli::Prune)
ensure
KAMAL.specific_hosts = old_hosts
end
run_hook "post-proxy-reboot", hosts: host_list
say "Upgraded proxy on #{host_list}"
end
end
end
@@ -204,10 +203,6 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
end
private
def reset_invocation(cli_class)
instance_variable_get("@_invocations")[cli_class].pop
end
def removal_allowed?(force)
on(KAMAL.proxy_hosts) do |host|
app_count = capture_with_info(*KAMAL.server.app_directory_count).chomp.to_i

View File

@@ -65,6 +65,13 @@ class Kamal::Commander
end
end
def with_specific_hosts(hosts)
original_hosts, self.specific_hosts = specific_hosts, hosts
yield
ensure
self.specific_hosts = original_hosts
end
def accessory_names
config.accessories&.collect(&:name) || []
end