Allow performing boot & start operations in groups
Adds top-level configuration options for `group_limit` and `group_wait`. When a `group_limit` is present, we'll perform app boot & start operations on no more than `group_limit` hosts at a time, optionally sleeping for `group_wait` seconds after each batch. We currently only do this batching on boot & start operations (including when they are part of a deployment). Other commands, like `app stop` or `app details` still work on all hosts in parallel.
This commit is contained in:
17
README.md
17
README.md
@@ -831,6 +831,23 @@ mrsk lock acquire -m "Doing maintanence"
|
|||||||
mrsk lock release
|
mrsk lock release
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Gradual restarts
|
||||||
|
|
||||||
|
When deploying to large numbers of hosts, you might prefer not to restart your services on every host at the same time.
|
||||||
|
|
||||||
|
MRSK's default is to start new containers on all hosts in parallel. But you can control this by configuring `group_limit` and `group_wait`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
service: myservice
|
||||||
|
|
||||||
|
group_limit: 10
|
||||||
|
group_wait: 30
|
||||||
|
```
|
||||||
|
|
||||||
|
When `group_limit` is specified, containers will be started on, at most, `group_limit` hosts at once. MRSK will pause for `group_wait` seconds between batches.
|
||||||
|
|
||||||
|
These settings only apply when starting containers (using `mrsk deploy`, `mrsk app boot` or `mrsk app start`). For other commands, MRSK continues to run commands in parallel across all hosts.
|
||||||
|
|
||||||
## Stage of development
|
## Stage of development
|
||||||
|
|
||||||
This is beta software. Commands may still move around. But we're live in production at [37signals](https://37signals.com).
|
This is beta software. Commands may still move around. But we're live in production at [37signals](https://37signals.com).
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|||||||
execute *MRSK.app.tag_current_as_latest
|
execute *MRSK.app.tag_current_as_latest
|
||||||
end
|
end
|
||||||
|
|
||||||
on(MRSK.hosts) do |host|
|
on(MRSK.hosts, **MRSK.config.group_strategy) do |host|
|
||||||
roles = MRSK.roles_on(host)
|
roles = MRSK.roles_on(host)
|
||||||
|
|
||||||
roles.each do |role|
|
roles.each do |role|
|
||||||
@@ -39,7 +39,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|||||||
desc "start", "Start existing app container on servers"
|
desc "start", "Start existing app container on servers"
|
||||||
def start
|
def start
|
||||||
with_lock do
|
with_lock do
|
||||||
on(MRSK.hosts) do |host|
|
on(MRSK.hosts, **MRSK.config.group_strategy) do |host|
|
||||||
roles = MRSK.roles_on(host)
|
roles = MRSK.roles_on(host)
|
||||||
|
|
||||||
roles.each do |role|
|
roles.each do |role|
|
||||||
|
|||||||
@@ -153,6 +153,15 @@ class Mrsk::Configuration
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def group_strategy
|
||||||
|
if group_limit.present?
|
||||||
|
{ in: :groups, limit: group_limit, wait: group_wait }
|
||||||
|
else
|
||||||
|
{}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def audit_broadcast_cmd
|
def audit_broadcast_cmd
|
||||||
raw_config.audit_broadcast_cmd
|
raw_config.audit_broadcast_cmd
|
||||||
end
|
end
|
||||||
@@ -237,4 +246,12 @@ class Mrsk::Configuration
|
|||||||
raise "Can't use commit hash as version, no git repository found in #{Dir.pwd}"
|
raise "Can't use commit hash as version, no git repository found in #{Dir.pwd}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def group_limit
|
||||||
|
raw_config.group_limit&.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
def group_wait
|
||||||
|
raw_config.group_wait&.to_i
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -33,6 +33,16 @@ class CliAppTest < CliTestCase
|
|||||||
Thread.report_on_exception = true
|
Thread.report_on_exception = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "boot uses group strategy when specified" do
|
||||||
|
Mrsk::Cli::App.any_instance.stubs(:on).with("1.1.1.1").twice # acquire & release lock
|
||||||
|
Mrsk::Cli::App.any_instance.stubs(:on).with([ "1.1.1.1" ]) # tag container
|
||||||
|
|
||||||
|
# Strategy is used when booting the containers
|
||||||
|
Mrsk::Cli::App.any_instance.expects(:on).with([ "1.1.1.1" ], in: :groups, limit: 3, wait: 30).with_block_given
|
||||||
|
|
||||||
|
run_command("boot", config: :with_group_strategy)
|
||||||
|
end
|
||||||
|
|
||||||
test "start" do
|
test "start" do
|
||||||
run_command("start").tap do |output|
|
run_command("start").tap do |output|
|
||||||
assert_match "docker start app-web-999", output
|
assert_match "docker start app-web-999", output
|
||||||
@@ -151,7 +161,7 @@ class CliAppTest < CliTestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def run_command(*command)
|
def run_command(*command, config: :with_accessories)
|
||||||
stdouted { Mrsk::Cli::App.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml", "--hosts", "1.1.1.1"]) }
|
stdouted { Mrsk::Cli::App.start([*command, "-c", "test/fixtures/deploy_#{config}.yml", "--hosts", "1.1.1.1"]) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
16
test/fixtures/deploy_with_group_strategy.yml
vendored
Normal file
16
test/fixtures/deploy_with_group_strategy.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
service: app
|
||||||
|
image: dhh/app
|
||||||
|
servers:
|
||||||
|
web:
|
||||||
|
- "1.1.1.1"
|
||||||
|
- "1.1.1.2"
|
||||||
|
workers:
|
||||||
|
- "1.1.1.3"
|
||||||
|
- "1.1.1.4"
|
||||||
|
|
||||||
|
registry:
|
||||||
|
username: user
|
||||||
|
password: pw
|
||||||
|
|
||||||
|
group_limit: 3
|
||||||
|
group_wait: 30
|
||||||
Reference in New Issue
Block a user