dash 3.2.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/dash/cli/app/boot.rb +2 -2
- data/lib/dash/cli/app.rb +1 -1
- data/lib/dash/cli/base.rb +1 -1
- data/lib/dash/cli/doctor/endpoint_checks.rb +1 -1
- data/lib/dash/cli/doctor/host_checks.rb +4 -4
- data/lib/dash/cli/main.rb +5 -5
- data/lib/dash/cli/proxy/legacy_rename.rb +58 -0
- data/lib/dash/cli/proxy/loadbalancer_reboot.rb +1 -1
- data/lib/dash/cli/proxy/reboot.rb +8 -8
- data/lib/dash/cli/proxy.rb +14 -10
- data/lib/dash/commander.rb +1 -1
- data/lib/dash/commands/accessory/proxy.rb +1 -1
- data/lib/dash/commands/app/execution.rb +1 -1
- data/lib/dash/commands/app/proxy.rb +1 -1
- data/lib/dash/commands/app.rb +1 -1
- data/lib/dash/commands/docker.rb +35 -1
- data/lib/dash/commands/loadbalancer.rb +47 -17
- data/lib/dash/commands/lock.rb +1 -1
- data/lib/dash/commands/proxy/cert_transfer.rb +4 -4
- data/lib/dash/commands/proxy.rb +112 -13
- data/lib/dash/commands/prune.rb +1 -1
- data/lib/dash/configuration/accessory.rb +2 -2
- data/lib/dash/configuration/docs/accessory.yml +3 -3
- data/lib/dash/configuration/docs/configuration.yml +1 -1
- data/lib/dash/configuration/docs/proxy.yml +38 -38
- data/lib/dash/configuration/loadbalancer.rb +2 -2
- data/lib/dash/configuration/proxy/acme.rb +5 -5
- data/lib/dash/configuration/proxy/boot.rb +2 -4
- data/lib/dash/configuration/proxy/run.rb +10 -10
- data/lib/dash/configuration/proxy.rb +36 -14
- data/lib/dash/configuration/role.rb +2 -2
- data/lib/dash/configuration/validator/proxy.rb +23 -23
- data/lib/dash/configuration.rb +8 -8
- data/lib/dash/utils.rb +1 -1
- data/lib/dash/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 749c8e286224f96b085fc8c325affcfde1cafaa18824e1405f880cc1aea966b1
|
|
4
|
+
data.tar.gz: 0dab0ae01b69f742e58551caaeee3352f98548c5a6265e98fcbe2de3025b50d1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b8a1e07a72a34388817185a8e476b4c400a90061292fe9d143a9a67ab1e5e5d2a87cba154077d151922a306f577dce9d04d60ecef371296ca9b2e3c49a277cb
|
|
7
|
+
data.tar.gz: 5b396df37122ed76ff2e5b7b2b694940a9eb1bcaa5bf31ed0de3f01039bf59836224f225da0a59acc24445f1d6a3dfe19165f2c83fd2e4a4023e68a1ec41c2c6
|
data/lib/dash/cli/app/boot.rb
CHANGED
|
@@ -58,7 +58,7 @@ class Dash::Cli::App::Boot
|
|
|
58
58
|
raise Dash::Cli::BootError, "Failed to get endpoint for #{role} on #{host}, did the container boot?" if endpoint.empty?
|
|
59
59
|
|
|
60
60
|
run_hook "pre-proxy-deploy", hosts: host.to_s, role: role.name
|
|
61
|
-
info "Deploying #{role} on #{host} via
|
|
61
|
+
info "Deploying #{role} on #{host} via dash-proxy (waiting up to #{DASH.config.deploy_timeout}s for it to become healthy)..."
|
|
62
62
|
execute *app.deploy(target: endpoint)
|
|
63
63
|
run_hook "post-proxy-deploy", hosts: host.to_s, role: role.name
|
|
64
64
|
else
|
|
@@ -84,7 +84,7 @@ class Dash::Cli::App::Boot
|
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
# Every failed boot gets the container log, and the health probe history when the
|
|
87
|
-
# container declares a healthcheck — non-primary roles have no
|
|
87
|
+
# container declares a healthcheck — non-primary roles have no dash-proxy report to fall back on.
|
|
88
88
|
def dump_diagnostics
|
|
89
89
|
error capture_with_info(*app.logs(container_id: app.container_id_for_version(version)))
|
|
90
90
|
|
data/lib/dash/cli/app.rb
CHANGED
|
@@ -283,7 +283,7 @@ class Dash::Cli::App < Dash::Cli::Base
|
|
|
283
283
|
end
|
|
284
284
|
end
|
|
285
285
|
|
|
286
|
-
desc "rollout <deploy|set|stop>", "Manage a canary rollout of a new version through
|
|
286
|
+
desc "rollout <deploy|set|stop>", "Manage a canary rollout of a new version through dash-proxy"
|
|
287
287
|
option :percent, type: :numeric, desc: "Percentage of traffic to send to the rollout target"
|
|
288
288
|
option :list, type: :array, desc: "Send requests whose kamal-rollout cookie matches these values to the rollout target"
|
|
289
289
|
def rollout(action)
|
data/lib/dash/cli/base.rb
CHANGED
|
@@ -170,7 +170,7 @@ module Dash::Cli
|
|
|
170
170
|
raw ? DASH.with_verbosity(:error, &block) : block.call
|
|
171
171
|
end
|
|
172
172
|
|
|
173
|
-
#
|
|
173
|
+
# dash-proxy is one container per host, shared by every destination
|
|
174
174
|
# deployed there, but the deploy lock is per-destination — so two
|
|
175
175
|
# destinations deploying at once take different locks and both mutate the
|
|
176
176
|
# same proxy. Anything touching the proxy takes this lock as well.
|
|
@@ -2,7 +2,7 @@ require "resolv"
|
|
|
2
2
|
require "socket"
|
|
3
3
|
require "openssl"
|
|
4
4
|
|
|
5
|
-
# Local (no SSH) readiness checks for the domains
|
|
5
|
+
# Local (no SSH) readiness checks for the domains dash-proxy will serve:
|
|
6
6
|
# DNS resolution against the configured hosts and TLS certificate expiry.
|
|
7
7
|
class Dash::Cli::Doctor::EndpointChecks
|
|
8
8
|
CERTIFICATE_EXPIRY_WARN_DAYS = 14
|
|
@@ -127,13 +127,13 @@ class Dash::Cli::Doctor::HostChecks
|
|
|
127
127
|
if mounted.include?(expected)
|
|
128
128
|
result :proxy_socket, :ok, "docker socket #{expected} is mounted"
|
|
129
129
|
elsif sleep_configured?
|
|
130
|
-
result :proxy_socket, :fail, "the running
|
|
130
|
+
result :proxy_socket, :fail, "the running dash-proxy has no #{expected} mount, so sleeping services never wake - run `dash proxy reboot`"
|
|
131
131
|
else
|
|
132
132
|
# Nothing sleeps yet, so nothing hangs - drift rather than breakage.
|
|
133
|
-
result :proxy_socket, :warn, "the running
|
|
133
|
+
result :proxy_socket, :warn, "the running dash-proxy has no #{expected} mount - run `dash proxy reboot` to apply the current configuration"
|
|
134
134
|
end
|
|
135
135
|
elsif (stray = mounted.grep(DOCKER_SOCKET_PATTERN).first)
|
|
136
|
-
result :proxy_socket, :warn, "the running
|
|
136
|
+
result :proxy_socket, :warn, "the running dash-proxy mounts #{stray} but the config no longer asks for it - " \
|
|
137
137
|
"the socket is root-equivalent host access; `dash proxy reboot` removes it"
|
|
138
138
|
else
|
|
139
139
|
result :proxy_socket, :ok, "no docker socket configured or mounted"
|
|
@@ -154,7 +154,7 @@ class Dash::Cli::Doctor::HostChecks
|
|
|
154
154
|
https_port = run_config&.https_port || Dash::Configuration::Proxy::Run::DEFAULT_HTTPS_PORT
|
|
155
155
|
|
|
156
156
|
if proxy_running
|
|
157
|
-
result :ports, :ok, "ports #{http_port}/#{https_port} held by the running
|
|
157
|
+
result :ports, :ok, "ports #{http_port}/#{https_port} held by the running dash-proxy"
|
|
158
158
|
elsif (busy = busy_ports(http_port, https_port)).any?
|
|
159
159
|
result :ports, :fail, "port(s) #{busy.join(", ")} already in use by another process"
|
|
160
160
|
else
|
data/lib/dash/cli/main.rb
CHANGED
|
@@ -39,7 +39,7 @@ class Dash::Cli::Main < Dash::Cli::Base
|
|
|
39
39
|
modify(lock: true) do
|
|
40
40
|
run_hook "pre-deploy", secrets: true
|
|
41
41
|
|
|
42
|
-
say "Ensure
|
|
42
|
+
say "Ensure dash-proxy is running...", :magenta
|
|
43
43
|
invoke "dash:cli:proxy:boot", [], invoke_options
|
|
44
44
|
|
|
45
45
|
invoke "dash:cli:accessory:boot", [ "all" ], invoke_options if boot_accessories
|
|
@@ -63,7 +63,7 @@ class Dash::Cli::Main < Dash::Cli::Base
|
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
desc "redeploy", "Deploy app to servers without bootstrapping servers, starting
|
|
66
|
+
desc "redeploy", "Deploy app to servers without bootstrapping servers, starting dash-proxy and pruning"
|
|
67
67
|
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
|
68
68
|
option :no_cache, type: :boolean, default: false, desc: "Build without using Docker's build cache"
|
|
69
69
|
def redeploy
|
|
@@ -226,7 +226,7 @@ class Dash::Cli::Main < Dash::Cli::Base
|
|
|
226
226
|
end
|
|
227
227
|
end
|
|
228
228
|
|
|
229
|
-
desc "remove", "Remove
|
|
229
|
+
desc "remove", "Remove dash-proxy, app, accessories, and registry session from servers"
|
|
230
230
|
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
|
231
231
|
def remove
|
|
232
232
|
confirming "This will remove all containers and images. Are you sure?" do
|
|
@@ -249,7 +249,7 @@ class Dash::Cli::Main < Dash::Cli::Base
|
|
|
249
249
|
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
|
250
250
|
option :rolling, type: :boolean, default: false, desc: "Upgrade one host at a time"
|
|
251
251
|
def upgrade
|
|
252
|
-
confirming "This will replace Traefik with
|
|
252
|
+
confirming "This will replace Traefik with dash-proxy and restart all accessories" do
|
|
253
253
|
modify(lock: true) do
|
|
254
254
|
if options[:rolling]
|
|
255
255
|
DASH.hosts.each do |host|
|
|
@@ -293,7 +293,7 @@ class Dash::Cli::Main < Dash::Cli::Base
|
|
|
293
293
|
desc "lock", "Manage the deploy lock"
|
|
294
294
|
subcommand "lock", Dash::Cli::Lock
|
|
295
295
|
|
|
296
|
-
desc "proxy", "Manage
|
|
296
|
+
desc "proxy", "Manage dash-proxy"
|
|
297
297
|
subcommand "proxy", Dash::Cli::Proxy
|
|
298
298
|
|
|
299
299
|
desc "prune", "Prune old application images and containers"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Stage 3c migration: brings a host that still carries pre-rename container
|
|
2
|
+
# identity onto the renamed one.
|
|
3
|
+
#
|
|
4
|
+
# Three steps, run in this order on each proxy host before the normal boot:
|
|
5
|
+
#
|
|
6
|
+
# 1. Bridge the network. Docker cannot rename one, so `dash` is created
|
|
7
|
+
# alongside `kamal` and everything still attached to the old network joins
|
|
8
|
+
# the new one. App containers would be replaced by the next deploy anyway;
|
|
9
|
+
# accessories are not, which is the whole reason this exists — without it a
|
|
10
|
+
# renamed proxy cannot reach `db` or `redis`.
|
|
11
|
+
#
|
|
12
|
+
# 2. Copy the config volume. It holds the routing table and the ACME account
|
|
13
|
+
# and certificate cache, so losing it means re-issuing every certificate
|
|
14
|
+
# and spending Let's Encrypt rate limits to get back to where we were. This
|
|
15
|
+
# must happen before the new container starts.
|
|
16
|
+
#
|
|
17
|
+
# 3. Replace the legacy container. A rename means the old container has to
|
|
18
|
+
# release ports 80/443 before the new one can claim them, and no
|
|
19
|
+
# port-holder handoff spans two container names — so this stage accepts a
|
|
20
|
+
# brief outage per host. Deliberate; see zoolutions/dash#124.
|
|
21
|
+
#
|
|
22
|
+
# Every step is idempotent and guarded on its destination not already existing,
|
|
23
|
+
# so a second deploy is a no-op. Nothing here removes the legacy network or
|
|
24
|
+
# volume: an operator who wants them gone removes them by hand, and stage 3d
|
|
25
|
+
# deletes this class outright.
|
|
26
|
+
class Dash::Cli::Proxy::LegacyRename
|
|
27
|
+
attr_reader :host, :sshkit
|
|
28
|
+
delegate :execute, to: :sshkit
|
|
29
|
+
|
|
30
|
+
def initialize(host, sshkit)
|
|
31
|
+
@host = host
|
|
32
|
+
@sshkit = sshkit
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def run
|
|
36
|
+
bridge_network
|
|
37
|
+
adopt_config_volume
|
|
38
|
+
replace_legacy_container
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
def bridge_network
|
|
43
|
+
execute *DASH.docker.connect_legacy_network_containers
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def adopt_config_volume
|
|
47
|
+
execute *DASH.proxy(host).copy_legacy_config_volume
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The drain timeout the proxy is configured with, so a busy host is not cut
|
|
51
|
+
# off mid-request any more abruptly than a normal reboot would.
|
|
52
|
+
def replace_legacy_container
|
|
53
|
+
proxy = DASH.proxy(host)
|
|
54
|
+
|
|
55
|
+
execute *proxy.remove_legacy_container(timeout: DASH.config.drain_timeout)
|
|
56
|
+
execute *proxy.remove_legacy_holder_container
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -38,7 +38,7 @@ class Dash::Cli::Proxy::LoadbalancerReboot
|
|
|
38
38
|
wait_until_ready
|
|
39
39
|
verify_service if re_register_service
|
|
40
40
|
|
|
41
|
-
#
|
|
41
|
+
# dash-proxy keeps its service state in the config volume, which the
|
|
42
42
|
# replacement container re-mounts - every app's routes survive.
|
|
43
43
|
services = capture_with_info(*DASH.loadbalancer.list).strip
|
|
44
44
|
info "Services registered on the load balancer at #{host} after reboot:\n#{services}"
|
|
@@ -31,7 +31,7 @@ class Dash::Cli::Proxy::Reboot
|
|
|
31
31
|
# Pull before stopping the old container: a registry outage or rate limit
|
|
32
32
|
# can then no longer strand the host with no proxy at all.
|
|
33
33
|
def pull_image
|
|
34
|
-
info "Pulling
|
|
34
|
+
info "Pulling dash-proxy image on #{host}..."
|
|
35
35
|
execute *proxy.pull
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -64,7 +64,7 @@ class Dash::Cli::Proxy::Reboot
|
|
|
64
64
|
|
|
65
65
|
# Phase 1 replacement: stop the old container, start the new one. Brief gap.
|
|
66
66
|
def stop_and_replace
|
|
67
|
-
info "Stopping and removing
|
|
67
|
+
info "Stopping and removing dash-proxy on #{host}, if running..."
|
|
68
68
|
execute *proxy.stop(timeout: DASH.config.drain_timeout + 10), raise_on_non_zero_exit: false
|
|
69
69
|
execute *proxy.remove_container
|
|
70
70
|
|
|
@@ -87,7 +87,7 @@ class Dash::Cli::Proxy::Reboot
|
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
def handoff_generation
|
|
90
|
-
info "Handing off
|
|
90
|
+
info "Handing off dash-proxy on #{host} to a new generation (zero downtime)..."
|
|
91
91
|
execute *proxy.remove_stopped_container(name: proxy.next_container_name), raise_on_non_zero_exit: false
|
|
92
92
|
execute *proxy.run(digest: drift.expected_digest, name: proxy.next_container_name)
|
|
93
93
|
wait_until_ready(name: proxy.next_container_name)
|
|
@@ -102,7 +102,7 @@ class Dash::Cli::Proxy::Reboot
|
|
|
102
102
|
# The old proxy still owns the published ports, so the holder cannot bind
|
|
103
103
|
# them until it is gone. One final replacement with a brief gap.
|
|
104
104
|
def migrate_to_holder
|
|
105
|
-
info "Migrating
|
|
105
|
+
info "Migrating dash-proxy on #{host} to the port-holder architecture (brief gap)..."
|
|
106
106
|
execute *proxy.stop(timeout: DASH.config.drain_timeout + 10), raise_on_non_zero_exit: false
|
|
107
107
|
execute *proxy.remove_container
|
|
108
108
|
|
|
@@ -124,7 +124,7 @@ class Dash::Cli::Proxy::Reboot
|
|
|
124
124
|
begin
|
|
125
125
|
capture_with_info(*proxy.list(**{ name: name }.compact), verbosity: :debug)
|
|
126
126
|
rescue SSHKit::Command::Failed
|
|
127
|
-
raise Dash::Cli::BootError, "
|
|
127
|
+
raise Dash::Cli::BootError, "dash-proxy on #{host} did not become ready within #{READY_TIMEOUT} seconds" if Time.now >= deadline
|
|
128
128
|
sleep 0.5
|
|
129
129
|
retry
|
|
130
130
|
end
|
|
@@ -146,7 +146,7 @@ class Dash::Cli::Proxy::Reboot
|
|
|
146
146
|
endpoint = capture_with_info(*app.container_id_for_version(version, only_running: true), raise_on_non_zero_exit: false).strip.presence
|
|
147
147
|
next unless endpoint
|
|
148
148
|
|
|
149
|
-
info "Re-registering #{role.container_prefix} with
|
|
149
|
+
info "Re-registering #{role.container_prefix} with dash-proxy on #{host}..."
|
|
150
150
|
execute *app.deploy(target: endpoint)
|
|
151
151
|
registered_services << role.container_prefix
|
|
152
152
|
end
|
|
@@ -158,7 +158,7 @@ class Dash::Cli::Proxy::Reboot
|
|
|
158
158
|
target = capture_with_info(*accessory.container_id_for(container_name: accessory_config.service_name, only_running: true), raise_on_non_zero_exit: false).strip.presence
|
|
159
159
|
next unless target
|
|
160
160
|
|
|
161
|
-
info "Re-registering #{accessory_config.service_name} with
|
|
161
|
+
info "Re-registering #{accessory_config.service_name} with dash-proxy on #{host}..."
|
|
162
162
|
execute *accessory.deploy(target: target)
|
|
163
163
|
registered_services << accessory_config.service_name
|
|
164
164
|
end
|
|
@@ -176,7 +176,7 @@ class Dash::Cli::Proxy::Reboot
|
|
|
176
176
|
missing = registered_services - listed
|
|
177
177
|
|
|
178
178
|
if missing.any?
|
|
179
|
-
raise Dash::Cli::BootError, "
|
|
179
|
+
raise Dash::Cli::BootError, "dash-proxy on #{host} is missing services after reboot: #{missing.join(", ")}"
|
|
180
180
|
end
|
|
181
181
|
end
|
|
182
182
|
end
|
data/lib/dash/cli/proxy.rb
CHANGED
|
@@ -21,6 +21,10 @@ class Dash::Cli::Proxy < Dash::Cli::Base
|
|
|
21
21
|
on(proxy_hosts) do |host|
|
|
22
22
|
execute *DASH.registry.login
|
|
23
23
|
|
|
24
|
+
# Before anything reads the new container, volume or network: bring a
|
|
25
|
+
# host still on pre-rename identity across. A no-op once it has been.
|
|
26
|
+
Dash::Cli::Proxy::LegacyRename.new(host, self).run
|
|
27
|
+
|
|
24
28
|
proxy = DASH.proxy(host)
|
|
25
29
|
drift = Dash::Cli::Proxy::Drift.new(host, self)
|
|
26
30
|
|
|
@@ -33,7 +37,7 @@ class Dash::Cli::Proxy < Dash::Cli::Base
|
|
|
33
37
|
version = capture_with_info(*proxy.version).strip.presence
|
|
34
38
|
|
|
35
39
|
if version && Dash::Utils.older_version?(version, Dash::Configuration::Proxy::Run::MINIMUM_VERSION)
|
|
36
|
-
raise "
|
|
40
|
+
raise "dash-proxy version #{version} is too old, run `dash proxy reboot` in order to update to at least #{Dash::Configuration::Proxy::Run::MINIMUM_VERSION}"
|
|
37
41
|
end
|
|
38
42
|
|
|
39
43
|
if (run_config = proxy.proxy_run_config)&.secrets?
|
|
@@ -51,12 +55,12 @@ class Dash::Cli::Proxy < Dash::Cli::Base
|
|
|
51
55
|
end
|
|
52
56
|
|
|
53
57
|
if stale_hosts.any?
|
|
54
|
-
say "
|
|
58
|
+
say "dash-proxy on #{stale_hosts.sort.join(", ")} is running with a configuration that no longer matches the deploy config. " \
|
|
55
59
|
"Automatic reboot is disabled (proxy: reboot_on_deploy: false) - run `dash proxy reboot` to apply the new configuration.", :yellow
|
|
56
60
|
end
|
|
57
61
|
|
|
58
62
|
drifted_hosts.sort.each do |host|
|
|
59
|
-
say "
|
|
63
|
+
say "dash-proxy configuration changed, rebooting on #{host}...", :magenta
|
|
60
64
|
run_hook "pre-proxy-reboot", hosts: host
|
|
61
65
|
on(host) do |h|
|
|
62
66
|
Dash::Cli::Proxy::Reboot.new(h, self).run
|
|
@@ -129,7 +133,7 @@ class Dash::Cli::Proxy < Dash::Cli::Base
|
|
|
129
133
|
end
|
|
130
134
|
end
|
|
131
135
|
|
|
132
|
-
desc "boot_config <set|get|reset>", "Manage
|
|
136
|
+
desc "boot_config <set|get|reset>", "Manage dash-proxy boot configuration"
|
|
133
137
|
option :publish, type: :boolean, default: true, desc: "Publish the proxy ports on the host"
|
|
134
138
|
option :publish_host_ip, type: :string, repeatable: true, default: nil, desc: "Host IP address to bind HTTP/HTTPS traffic to. Defaults to all interfaces"
|
|
135
139
|
option :http_port, type: :numeric, default: Dash::Configuration::Proxy::Run::DEFAULT_HTTP_PORT, desc: "HTTP port to publish on the host"
|
|
@@ -163,7 +167,7 @@ class Dash::Cli::Proxy < Dash::Cli::Base
|
|
|
163
167
|
image_version = options[:image_version]
|
|
164
168
|
|
|
165
169
|
run_command_options = { debug: options[:debug] || nil, "metrics-port": options[:metrics_port] }.compact
|
|
166
|
-
run_command = "
|
|
170
|
+
run_command = "dash-proxy run #{Dash::Utils.optionize(run_command_options).join(" ")}" if run_command_options.any?
|
|
167
171
|
|
|
168
172
|
on(DASH.proxy_hosts) do |host|
|
|
169
173
|
proxy = DASH.proxy(host)
|
|
@@ -229,7 +233,7 @@ class Dash::Cli::Proxy < Dash::Cli::Base
|
|
|
229
233
|
host_list = Array(hosts).join(",")
|
|
230
234
|
run_hook "pre-proxy-reboot", hosts: host_list
|
|
231
235
|
on(hosts) do |host|
|
|
232
|
-
info "Rebooting
|
|
236
|
+
info "Rebooting dash-proxy on #{host}..."
|
|
233
237
|
Dash::Cli::Proxy::Reboot.new(host, self).run
|
|
234
238
|
end
|
|
235
239
|
run_hook "post-proxy-reboot", hosts: host_list
|
|
@@ -249,7 +253,7 @@ class Dash::Cli::Proxy < Dash::Cli::Base
|
|
|
249
253
|
end
|
|
250
254
|
end
|
|
251
255
|
|
|
252
|
-
desc "upgrade", "Upgrade to
|
|
256
|
+
desc "upgrade", "Upgrade to dash-proxy on servers (stop container, remove container, start new container, reboot app)", hide: true
|
|
253
257
|
option :rolling, type: :boolean, default: false, desc: "Reboot proxy on hosts in sequence, rather than in parallel"
|
|
254
258
|
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
|
255
259
|
def upgrade
|
|
@@ -269,7 +273,7 @@ class Dash::Cli::Proxy < Dash::Cli::Base
|
|
|
269
273
|
info "Stopping and removing Traefik on #{host}, if running..."
|
|
270
274
|
execute *proxy.cleanup_traefik
|
|
271
275
|
|
|
272
|
-
info "Stopping and removing
|
|
276
|
+
info "Stopping and removing dash-proxy on #{host}, if running..."
|
|
273
277
|
execute *proxy.stop, raise_on_non_zero_exit: false
|
|
274
278
|
execute *proxy.remove_container
|
|
275
279
|
execute *proxy.remove_image
|
|
@@ -566,7 +570,7 @@ class Dash::Cli::Proxy < Dash::Cli::Base
|
|
|
566
570
|
on(cert_store_host) do |host|
|
|
567
571
|
commands = load_balancing ? DASH.loadbalancer : DASH.proxy(host)
|
|
568
572
|
|
|
569
|
-
#
|
|
573
|
+
# dash-proxy import runs offline against the data directory - importing
|
|
570
574
|
# under a live proxy risks a torn store. --verify only reads the archive.
|
|
571
575
|
unless verify
|
|
572
576
|
if capture_with_info(*commands.container_id(only_running: true), raise_on_non_zero_exit: false).strip.present?
|
|
@@ -652,7 +656,7 @@ class Dash::Cli::Proxy < Dash::Cli::Base
|
|
|
652
656
|
DASH.config.proxy.load_balancing? ? DASH.config.proxy.effective_loadbalancer : DASH.primary_host
|
|
653
657
|
end
|
|
654
658
|
|
|
655
|
-
# Mirrors
|
|
659
|
+
# Mirrors dash-proxy's own flag groups (import.go), so a contradictory
|
|
656
660
|
# invocation fails before anything is uploaded.
|
|
657
661
|
def validate_import_certs_options!
|
|
658
662
|
if options[:traefik_acme].present? == options[:archive].present?
|
data/lib/dash/commander.rb
CHANGED
|
@@ -110,7 +110,7 @@ class Dash::Commander
|
|
|
110
110
|
@commands[:lock] ||= Dash::Commands::Lock.new(config)
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
-
# Guards the
|
|
113
|
+
# Guards the dash-proxy container, which a host runs once for every
|
|
114
114
|
# destination deployed onto it. Every destination contends for this one.
|
|
115
115
|
def server_lock
|
|
116
116
|
@commands[:server_lock] ||= Dash::Commands::Lock.new(config, scope: :server)
|
data/lib/dash/commands/app.rb
CHANGED
|
@@ -18,7 +18,7 @@ class Dash::Commands::App < Dash::Commands::Base
|
|
|
18
18
|
"--detach",
|
|
19
19
|
"--restart", role.restart_policy,
|
|
20
20
|
"--name", container_name,
|
|
21
|
-
"--network", "
|
|
21
|
+
"--network", "dash",
|
|
22
22
|
*([ "--hostname", hostname ] if hostname),
|
|
23
23
|
"--env", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
|
|
24
24
|
"--env", "KAMAL_VERSION=\"#{config.version}\"",
|
data/lib/dash/commands/docker.rb
CHANGED
|
@@ -41,10 +41,44 @@ class Dash::Commands::Docker < Dash::Commands::Base
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def create_network
|
|
44
|
-
docker :network, :create,
|
|
44
|
+
docker :network, :create, Dash::Configuration::Proxy::NETWORK
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Stage 3c: docker cannot rename a network, so `dash` is created alongside
|
|
48
|
+
# `kamal` and everything still on the old one is joined to the new one.
|
|
49
|
+
#
|
|
50
|
+
# App containers would be replaced by the next deploy anyway; accessories are
|
|
51
|
+
# not, and that is the whole point — without this a renamed proxy cannot
|
|
52
|
+
# reach `db` or `redis`. Connecting a container that is already attached
|
|
53
|
+
# fails, so each connect tolerates its own failure rather than aborting the
|
|
54
|
+
# sweep. The old network is never removed; that is documented manual cleanup
|
|
55
|
+
# and stage 3d's code change.
|
|
56
|
+
def connect_legacy_network_containers
|
|
57
|
+
any \
|
|
58
|
+
combine(
|
|
59
|
+
legacy_network_exists,
|
|
60
|
+
pipe(legacy_network_container_names, connect_each_to_network)
|
|
61
|
+
),
|
|
62
|
+
[ :true ]
|
|
45
63
|
end
|
|
46
64
|
|
|
47
65
|
private
|
|
66
|
+
def legacy_network_exists
|
|
67
|
+
docker :network, :inspect, Dash::Configuration::Proxy::LEGACY_NETWORK, ">", "/dev/null", "2>&1"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def legacy_network_container_names
|
|
71
|
+
docker :network, :inspect, Dash::Configuration::Proxy::LEGACY_NETWORK,
|
|
72
|
+
"--format", "'{{range .Containers}}{{.Name}}{{\"\\n\"}}{{end}}'"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Already-attached containers make `network connect` fail; tolerate each
|
|
76
|
+
# one's failure rather than letting it abort the sweep.
|
|
77
|
+
def connect_each_to_network
|
|
78
|
+
[ :xargs, "-r", "-I{}", "sh", "-c",
|
|
79
|
+
"'docker network connect #{Dash::Configuration::Proxy::NETWORK} {} 2>/dev/null || true'" ]
|
|
80
|
+
end
|
|
81
|
+
|
|
48
82
|
def get_docker
|
|
49
83
|
shell \
|
|
50
84
|
any \
|
|
@@ -14,7 +14,7 @@ class Dash::Commands::Loadbalancer < Dash::Commands::Base
|
|
|
14
14
|
docker \
|
|
15
15
|
:run,
|
|
16
16
|
"--name", container_name,
|
|
17
|
-
"--network", "
|
|
17
|
+
"--network", "dash",
|
|
18
18
|
"--detach",
|
|
19
19
|
"--restart", "unless-stopped",
|
|
20
20
|
"--label", label,
|
|
@@ -38,28 +38,28 @@ class Dash::Commands::Loadbalancer < Dash::Commands::Base
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def deploy(targets: [])
|
|
41
|
-
docker :exec, container_name, "
|
|
41
|
+
docker :exec, container_name, "dash-proxy", "deploy", loadbalancer_config.config.service,
|
|
42
42
|
*loadbalancer_config.deploy_command_args(targets: targets)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
# `retry` takes a host, or --all; the rest take no arguments.
|
|
46
46
|
def domains(subcommand, *args)
|
|
47
|
-
docker :exec, container_name, "
|
|
47
|
+
docker :exec, container_name, "dash-proxy", "domains", subcommand, *args
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def list(json: false)
|
|
51
|
-
docker :exec, container_name, "
|
|
51
|
+
docker :exec, container_name, "dash-proxy", :list, *("--json" if json)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
# Cache policy is edge-only under load balancing (see the layering contract),
|
|
55
55
|
# so the cache admin surface lives here - registered under the bare service
|
|
56
56
|
# name, unlike the per-role services on the proxy hosts.
|
|
57
57
|
def cache_stats(count: false, json: false)
|
|
58
|
-
docker :exec, container_name, "
|
|
58
|
+
docker :exec, container_name, "dash-proxy", :cache, :stats, *optionize({ count: count || nil, json: json || nil }.compact)
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def cache_purge(service, path_prefix: nil)
|
|
62
|
-
docker :exec, container_name, "
|
|
62
|
+
docker :exec, container_name, "dash-proxy", :cache, :purge, service, *optionize({ "path-prefix": path_prefix }.compact)
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def config_digest
|
|
@@ -94,16 +94,24 @@ class Dash::Commands::Loadbalancer < Dash::Commands::Base
|
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
# Prune by the label the container was actually created with - on a shared
|
|
97
|
-
# proxy host that is the
|
|
98
|
-
# title would leave the container behind for `run` to collide with.
|
|
97
|
+
# proxy host that is the dash-proxy title, and pruning by the loadbalancer
|
|
98
|
+
# title would leave the container behind for `run` to collide with. Both the
|
|
99
|
+
# current and the pre-rename title are pruned, so a container created before
|
|
100
|
+
# the rename is not left behind to collide either.
|
|
99
101
|
def remove_container
|
|
100
|
-
|
|
102
|
+
combine \
|
|
103
|
+
prune_containers_titled(image_title),
|
|
104
|
+
prune_containers_titled(legacy_image_title)
|
|
101
105
|
end
|
|
102
106
|
|
|
103
107
|
# Image label filters match labels baked into the image, and the load balancer
|
|
104
|
-
# runs the
|
|
108
|
+
# runs the dash-proxy image whichever host it sits on. Both titles are matched
|
|
109
|
+
# so a host still carrying a pre-rename image is cleaned up too; Docker ANDs
|
|
110
|
+
# multiple `--filter label=` values, so that is two commands.
|
|
105
111
|
def remove_image
|
|
106
|
-
|
|
112
|
+
combine \
|
|
113
|
+
prune_images_titled(Dash::Configuration::Proxy::IMAGE_TITLE),
|
|
114
|
+
prune_images_titled(Dash::Configuration::Proxy::LEGACY_IMAGE_TITLE)
|
|
107
115
|
end
|
|
108
116
|
|
|
109
117
|
def ensure_directory
|
|
@@ -153,28 +161,50 @@ class Dash::Commands::Loadbalancer < Dash::Commands::Base
|
|
|
153
161
|
loadbalancer_config.on_proxy_host?
|
|
154
162
|
end
|
|
155
163
|
|
|
164
|
+
# The full `key=value` the container is created with, so `--label #{label}`
|
|
165
|
+
# stays correct. The prune helpers take a bare title instead.
|
|
156
166
|
def label
|
|
167
|
+
"org.opencontainers.image.title=#{image_title}"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def image_title
|
|
157
171
|
if on_proxy_host?
|
|
158
|
-
|
|
172
|
+
Dash::Configuration::Proxy::IMAGE_TITLE
|
|
159
173
|
else
|
|
160
|
-
|
|
174
|
+
Dash::Configuration::Proxy::LOADBALANCER_IMAGE_TITLE
|
|
161
175
|
end
|
|
162
176
|
end
|
|
163
177
|
|
|
164
|
-
|
|
178
|
+
def legacy_image_title
|
|
179
|
+
if on_proxy_host?
|
|
180
|
+
Dash::Configuration::Proxy::LEGACY_IMAGE_TITLE
|
|
181
|
+
else
|
|
182
|
+
Dash::Configuration::Proxy::LEGACY_LOADBALANCER_IMAGE_TITLE
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def prune_containers_titled(title)
|
|
187
|
+
docker :container, :prune, "--force", "--filter", "label=org.opencontainers.image.title=#{title}"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def prune_images_titled(title)
|
|
191
|
+
docker :image, :prune, "--all", "--force", "--filter", "label=org.opencontainers.image.title=#{title}"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# dash-proxy keeps its state under /home/dash-proxy/.config/dash-proxy
|
|
165
195
|
# whichever host it runs on - only the volume name differs, so a dedicated
|
|
166
196
|
# load balancer and a shared proxy host never fight over the same volume.
|
|
167
197
|
# (The apps-config mount comes with run_args, via the proxy's run surface.)
|
|
168
198
|
def config_volume
|
|
169
199
|
if on_proxy_host?
|
|
170
|
-
[ "--volume", "
|
|
200
|
+
[ "--volume", "dash-proxy-config:/home/dash-proxy/.config/dash-proxy" ]
|
|
171
201
|
else
|
|
172
|
-
[ "--volume", "
|
|
202
|
+
[ "--volume", "dash-loadbalancer-config:/home/dash-proxy/.config/dash-proxy" ]
|
|
173
203
|
end
|
|
174
204
|
end
|
|
175
205
|
|
|
176
206
|
# The certificate store lives in whichever config volume this loadbalancer
|
|
177
|
-
# actually mounts — the shared
|
|
207
|
+
# actually mounts — the shared dash-proxy one on a proxy host.
|
|
178
208
|
def cert_store_volume_args
|
|
179
209
|
config_volume
|
|
180
210
|
end
|
data/lib/dash/commands/lock.rb
CHANGED
|
@@ -4,7 +4,7 @@ require "base64"
|
|
|
4
4
|
|
|
5
5
|
class Dash::Commands::Lock < Dash::Commands::Base
|
|
6
6
|
# The deploy lock is scoped to service+destination, so two destinations can
|
|
7
|
-
# deploy at once. Server-scoped resources — above all the single
|
|
7
|
+
# deploy at once. Server-scoped resources — above all the single dash-proxy
|
|
8
8
|
# container a host runs for every destination on it — need a lock that every
|
|
9
9
|
# destination contends for, hence :server.
|
|
10
10
|
SCOPES = %i[ destination server ].freeze
|
|
@@ -18,7 +18,7 @@ module Dash::Commands::Proxy::CertTransfer
|
|
|
18
18
|
# Through the RPC socket of the running container, under the proxy's own
|
|
19
19
|
# certificate write lock, so a backup taken mid-renewal is never torn.
|
|
20
20
|
def export_certs
|
|
21
|
-
docker :exec, container_name, "
|
|
21
|
+
docker :exec, container_name, "dash-proxy", :export, :certs, certs_archive_container_path
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# Reads the data directory offline over the config volume — only safe when
|
|
@@ -28,10 +28,10 @@ module Dash::Commands::Proxy::CertTransfer
|
|
|
28
28
|
*cert_store_volume_args,
|
|
29
29
|
*config.proxy_boot.apps_volume.docker_args,
|
|
30
30
|
*one_off_image,
|
|
31
|
-
"
|
|
31
|
+
"dash-proxy", :export, :certs, certs_archive_container_path
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
# Offline by design (
|
|
34
|
+
# Offline by design (dash-proxy import has no RPC path): the one-off
|
|
35
35
|
# container mounts the config volume — creating it when no proxy has booted
|
|
36
36
|
# yet, which is the Traefik-migration case — and the staged source streams
|
|
37
37
|
# through stdin.
|
|
@@ -42,7 +42,7 @@ module Dash::Commands::Proxy::CertTransfer
|
|
|
42
42
|
# run whatever follows on the target host.
|
|
43
43
|
import_command = shell [
|
|
44
44
|
"cat > #{CONTAINER_IMPORT_PATH} &&",
|
|
45
|
-
"
|
|
45
|
+
"dash-proxy import certs",
|
|
46
46
|
*optionize({ source_flag => CONTAINER_IMPORT_PATH, resolver: resolver, force: force || nil, verify: verify || nil }.compact, with: "=")
|
|
47
47
|
]
|
|
48
48
|
|