Merge branch 'main' into cleanup-excessive-containers-running

* main: (24 commits)
  Bump version for 0.11.0
  Labels can be added to Traefik
  Make rollbacks role-aware
  fix typo role to roles
  Explained the latest modifications of Traefik container labels
  Remove .idea folder
  Updated README.md with new healthcheck.max_attempts option
  Fix test case: console output message was not updated to display the current/total attempts
  Require net-ssh ~> 7.0 for SHA-2 support
  Improved deploy lock acquisition
  Excess CR
  Style
  Simpler
  Make it explicit, focus on Ubuntu
  More explicit
  Not that --bundle is a Rails 7+ option
  Update README.md
  Update README.md
  Improved: configurable max_attempts for healthcheck
  Traefik service name to be derived from role and destination
  ...
This commit is contained in:
Jacopo
2023-04-12 11:52:47 +02:00
21 changed files with 239 additions and 79 deletions

View File

@@ -1,4 +1,5 @@
module Mrsk::Cli
class LockError < StandardError; end
end
# SSHKit uses instance eval, so we need a global const for ergonomics

View File

@@ -6,8 +6,6 @@ module Mrsk::Cli
class Base < Thor
include SSHKit::DSL
class LockError < StandardError; end
def self.exit_on_failure?() true end
class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
@@ -82,8 +80,11 @@ module Mrsk::Cli
acquire_lock
yield
ensure
release_lock
rescue
error " \e[31mDeploy lock was not released\e[0m" if MRSK.lock_count > 0
raise
end
def acquire_lock
@@ -95,9 +96,10 @@ module Mrsk::Cli
rescue SSHKit::Runner::ExecuteError => e
if e.message =~ /cannot create directory/
invoke "mrsk:cli:lock:status", []
raise LockError, "Deploy lock found"
else
raise e
end
raise LockError, "Deploy lock found"
end
def release_lock

View File

@@ -1,5 +1,4 @@
class Mrsk::Cli::Healthcheck < Mrsk::Cli::Base
MAX_ATTEMPTS = 7
class HealthcheckError < StandardError; end
@@ -13,6 +12,7 @@ class Mrsk::Cli::Healthcheck < Mrsk::Cli::Base
target = "Health check against #{MRSK.config.healthcheck["path"]}"
attempt = 1
max_attempts = MRSK.config.healthcheck["max_attempts"]
begin
status = capture_with_info(*MRSK.healthcheck.curl)
@@ -23,8 +23,8 @@ class Mrsk::Cli::Healthcheck < Mrsk::Cli::Base
raise HealthcheckError, "#{target} failed with status #{status}"
end
rescue SSHKit::Command::Failed
if attempt <= MAX_ATTEMPTS
info "#{target} failed to respond, retrying in #{attempt}s..."
if attempt <= max_attempts
info "#{target} failed to respond, retrying in #{attempt}s (attempt #{attempt}/#{max_attempts})..."
sleep attempt
attempt += 1

View File

@@ -12,7 +12,7 @@ class Mrsk::Cli::Lock < Mrsk::Cli::Base
message = options[:message]
handle_missing_lock do
on(MRSK.primary_host) { execute *MRSK.lock.acquire(message, MRSK.config.version) }
say "Set the deploy lock"
say "Acquired the deploy lock"
end
end
@@ -20,7 +20,7 @@ class Mrsk::Cli::Lock < Mrsk::Cli::Base
def release
handle_missing_lock do
on(MRSK.primary_host) { execute *MRSK.lock.release }
say "Removed the deploy lock"
say "Released the deploy lock"
end
end

View File

@@ -83,21 +83,26 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
with_lock do
MRSK.config.version = version
if container_name_available?(MRSK.config.service_with_version)
if container_available?(version)
say "Start version #{version}, then wait #{MRSK.config.readiness_delay}s for app to boot before stopping the old version...", :magenta
cli = self
old_version = nil
on(MRSK.hosts) do |host|
old_version = capture_with_info(*MRSK.app.current_running_version).strip.presence
roles = MRSK.roles_on(host)
execute *MRSK.app.start
roles.each do |role|
app = MRSK.app(role: role)
old_version = capture_with_info(*app.current_running_version).strip.presence
if old_version
sleep MRSK.config.readiness_delay
execute *app.start
execute *MRSK.app.stop(version: old_version), raise_on_non_zero_exit: false
if old_version
sleep MRSK.config.readiness_delay
execute *app.stop(version: old_version), raise_on_non_zero_exit: false
end
end
end
@@ -220,10 +225,15 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
subcommand "lock", Mrsk::Cli::Lock
private
def container_name_available?(container_name, host: MRSK.primary_host)
container_names = nil
on(host) { container_names = capture_with_info(*MRSK.app.list_container_names).split("\n") }
Array(container_names).include?(container_name)
def container_available?(version, host: MRSK.primary_host)
available = nil
on(host) do
first_role = MRSK.roles_on(host).first
available = capture_with_info(*MRSK.app(role: first_role).container_id_for_version(version)).present?
end
available
end
def deploy_options

View File

@@ -2,7 +2,10 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
desc "boot", "Boot Traefik on servers"
def boot
with_lock do
on(MRSK.traefik_hosts) { execute *MRSK.traefik.run, raise_on_non_zero_exit: false }
on(MRSK.traefik_hosts) do
execute *MRSK.registry.login
execute *MRSK.traefik.run, raise_on_non_zero_exit: false
end
end
end

View File

@@ -1,7 +1,7 @@
class Mrsk::Commands::Traefik < Mrsk::Commands::Base
delegate :optionize, to: Mrsk::Utils
delegate :argumentize, :optionize, to: Mrsk::Utils
IMAGE = "traefik:v2.9.9"
DEFAULT_IMAGE = "traefik:v2.9"
CONTAINER_PORT = 80
def run
@@ -11,8 +11,9 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
"--publish", port,
"--volume", "/var/run/docker.sock:/var/run/docker.sock",
*config.logging_args,
*label_args,
*docker_options_args,
IMAGE,
image,
"--providers.docker",
"--log.level=DEBUG",
*cmd_option_args
@@ -56,6 +57,18 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
end
private
def label_args
argumentize "--label", labels
end
def labels
config.traefik["labels"] || []
end
def image
config.traefik.fetch("image") { DEFAULT_IMAGE }
end
def docker_options_args
optionize(config.traefik["options"] || {})
end

View File

@@ -143,6 +143,8 @@ class Mrsk::Configuration
if raw_config.ssh.present? && raw_config.ssh["proxy"]
Net::SSH::Proxy::Jump.new \
raw_config.ssh["proxy"].include?("@") ? raw_config.ssh["proxy"] : "root@#{raw_config.ssh["proxy"]}"
elsif raw_config.ssh.present? && raw_config.ssh["proxy_command"]
Net::SSH::Proxy::Command.new(raw_config.ssh["proxy_command"])
end
end
@@ -156,7 +158,7 @@ class Mrsk::Configuration
end
def healthcheck
{ "path" => "/up", "port" => 3000 }.merge(raw_config.healthcheck || {})
{ "path" => "/up", "port" => 3000, "max_attempts" => 7 }.merge(raw_config.healthcheck || {})
end
def readiness_delay

View File

@@ -74,18 +74,22 @@ class Mrsk::Configuration::Role
def traefik_labels
if running_traefik?
{
"traefik.http.routers.#{config.service}.rule" => "PathPrefix(`/`)",
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.path" => config.healthcheck["path"],
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.interval" => "1s",
"traefik.http.middlewares.#{config.service}-retry.retry.attempts" => "5",
"traefik.http.middlewares.#{config.service}-retry.retry.initialinterval" => "500ms",
"traefik.http.routers.#{config.service}.middlewares" => "#{config.service}-retry@docker"
"traefik.http.routers.#{traefik_service}.rule" => "PathPrefix(`/`)",
"traefik.http.services.#{traefik_service}.loadbalancer.healthcheck.path" => config.healthcheck["path"],
"traefik.http.services.#{traefik_service}.loadbalancer.healthcheck.interval" => "1s",
"traefik.http.middlewares.#{traefik_service}-retry.retry.attempts" => "5",
"traefik.http.middlewares.#{traefik_service}-retry.retry.initialinterval" => "500ms",
"traefik.http.routers.#{traefik_service}.middlewares" => "#{traefik_service}-retry@docker"
}
else
{}
end
end
def traefik_service
[ config.service, name, config.destination ].compact.join("-")
end
def custom_labels
Hash.new.tap do |labels|
labels.merge!(config.labels) if config.labels.present?

View File

@@ -1,3 +1,3 @@
module Mrsk
VERSION = "0.10.1"
VERSION = "0.11.0"
end