indocker 0.5.0 → 0.5.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f5c86b947ae3162d9b81812db303c76b6825a1adc397cee5e3495bbd68437e96
4
- data.tar.gz: 6dd6c07468dad622731efeb333a55279dabf18e74a940e62fa2308d572636713
3
+ metadata.gz: 738373105e7204366594f9789bc989e5c83453bc209fe0053485ef239156cc7a
4
+ data.tar.gz: 63dc5708eee2e75caab7a4bfaa8ac9c0e80c73fb46fbca987215d28309f07d86
5
5
  SHA512:
6
- metadata.gz: 5546b2240618feacfe4efaa8c6b8ae82e58dde32eb383ff8cf7fd7295cf50caae8833ae0c8205031dea06bf7d7acb536935b0235c425bb5bf8506cb6cb22e7ef
7
- data.tar.gz: e9d0d5ae9c6ee1b114d7551e954a282775a62c20840dc4c3a51675659f7ee99e5faa2691fb450e3e4c1ab1757b61d3459bc6aa1e0e67be0b75dd9d2de3036c74
6
+ metadata.gz: 477f6ae6ffd9fa9087743f06e354d751af669e803c4f9f5a7ee74c820bbfb5872674c379a0a006d46200f5fe174bf7f1779b088df093b6dfeabe51c2fcd3e7b9
7
+ data.tar.gz: 355bee8644ae9e6e478cc878f41ee26c3f73bcf31c951561dc80fdc2c4bdde162e1d3d0bd387633d74501767c2b089a3f7cb20610795c5a6d2808a2b61dd079f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- indocker (0.5.0)
4
+ indocker (0.5.3)
5
5
  net-ssh
6
6
 
7
7
  GEM
@@ -20,29 +20,29 @@ class Indocker::ContainerDeployer
20
20
  def deploy(container, force_restart, skip_force_restart, progress)
21
21
  return if @deployed_containers[container]
22
22
 
23
- # Deploy the container to all of its servers in parallel, each server in its
24
- # own thread with its own ssh connection.
25
- #
26
- # Per-server exclusion: only one container may deploy on a given server at a
27
- # time, so several container types don't spike CPU/memory by starting at once
28
- # on the same host. We grab the lock of every target server before deploying.
29
- # Locks are always taken in a consistent (sorted) order, so concurrent
30
- # deployments of containers with overlapping server sets can't deadlock.
31
- ordered_servers = container.servers.sort_by { |server| server_lock_key(server) }
32
- locks = ordered_servers.map { |server| server_lock(server) }.uniq
33
-
34
- with_locks(locks) do
35
- threads = container.servers.map do |server|
36
- Thread.new do
23
+ exit_codes =
24
+ if deploy_servers_in_parallel?(container)
25
+ # Stateless containers: deploy to all servers at once.
26
+ threads = container.servers.map do |server|
27
+ Thread.new do
28
+ deploy_to_server(container, server, force_restart, skip_force_restart, progress)
29
+ end
30
+ end
31
+
32
+ threads.map(&:value)
33
+ else
34
+ # Rolling deploy: one server at a time. Containers with rolling-restart
35
+ # hooks (e.g. draining/enabling an nginx upstream) sit behind a load
36
+ # balancer, so all of their servers must not restart simultaneously —
37
+ # otherwise there is a moment with no live backend (502s). Deploying
38
+ # server by server keeps the rest of the fleet serving.
39
+ container.servers.map do |server|
37
40
  deploy_to_server(container, server, force_restart, skip_force_restart, progress)
38
41
  end
39
42
  end
40
43
 
41
- results = threads.map(&:value)
42
-
43
- if results.any? { |exit_code| exit_code != 0 }
44
- exit 1
45
- end
44
+ if exit_codes.any? { |exit_code| exit_code != 0 }
45
+ exit 1
46
46
  end
47
47
 
48
48
  @deployed_containers[container] = true
@@ -56,52 +56,57 @@ class Indocker::ContainerDeployer
56
56
 
57
57
  private
58
58
 
59
+ # Whether the container may be deployed to all of its servers concurrently.
60
+ # An explicit :parallel_deploy start option wins; otherwise a container is
61
+ # deployed in parallel only when it has no rolling-restart hooks (containers
62
+ # with before/after start hooks are drained at a load balancer and must roll
63
+ # across servers one at a time).
64
+ def deploy_servers_in_parallel?(container)
65
+ option = container.get_start_option(:parallel_deploy)
66
+ return option unless option.nil?
67
+
68
+ container.before_start_proc.nil? && container.after_start_proc.nil?
69
+ end
70
+
59
71
  # Runs a single container deployment on a single server. Returns the remote
60
- # exit code (0 on success). Runs inside its own thread with a dedicated ssh
61
- # connection, so it must not touch shared mutable state except the
62
- # thread-safe `progress`.
72
+ # exit code (0 on success).
73
+ #
74
+ # Per-server exclusion: only one container deploys on a given server at a time,
75
+ # so several container types don't spike CPU/memory by starting at once on the
76
+ # same host. Each invocation holds only its own server's lock, so concurrent
77
+ # deployments can't deadlock.
63
78
  def deploy_to_server(container, server, force_restart, skip_force_restart, progress)
64
- progress.start_deploying_container(container, server)
65
-
66
- deploy_server = @server_pool.create_connection!(server)
67
- @logger.info("Deploying container: #{container.name.to_s.green} to #{server.user}@#{server.host}")
79
+ server_lock(server).synchronize do
80
+ progress.start_deploying_container(container, server)
81
+
82
+ deploy_server = @server_pool.create_connection!(server)
83
+ @logger.info("Deploying container: #{container.name.to_s.green} to #{server.user}@#{server.host}")
84
+
85
+ result = deploy_server
86
+ .run_container_remotely(
87
+ configuration_name: Indocker.configuration_name,
88
+ container_name: container.name,
89
+ force_restart: force_restart && !skip_force_restart.include?(container.name)
90
+ )
91
+
92
+ if result.exit_code == 0
93
+ @logger.info("Container deployment to #{server.user}@#{server.host} finished: #{container.name.to_s.green}")
94
+ progress.finish_deploying_container(container, server)
95
+ end
68
96
 
69
- result = deploy_server
70
- .run_container_remotely(
71
- configuration_name: Indocker.configuration_name,
72
- container_name: container.name,
73
- force_restart: force_restart && !skip_force_restart.include?(container.name)
74
- )
97
+ deploy_server.close_session
75
98
 
76
- if result.exit_code == 0
77
- @logger.info("Container deployment to #{server.user}@#{server.host} finished: #{container.name.to_s.green}")
78
- progress.finish_deploying_container(container, server)
99
+ result.exit_code
79
100
  end
80
-
81
- deploy_server.close_session
82
-
83
- result.exit_code
84
- end
85
-
86
- def server_lock_key(server)
87
- [server.host, server.port, server.user]
88
101
  end
89
102
 
90
103
  # Returns a process-wide singleton Mutex for the given server, so the same lock
91
104
  # is shared across every container targeting that server.
92
105
  def server_lock(server)
93
- key = server_lock_key(server)
106
+ key = [server.host, server.port, server.user]
94
107
 
95
108
  @server_locks_mutex.synchronize do
96
109
  @server_locks[key] ||= Mutex.new
97
110
  end
98
111
  end
99
-
100
- def with_locks(locks, &block)
101
- if locks.empty?
102
- block.call
103
- else
104
- locks.first.synchronize { with_locks(locks.drop(1), &block) }
105
- end
106
- end
107
112
  end
@@ -302,31 +302,28 @@ class Indocker::Launchers::ConfigurationDeployer
302
302
  # container to the deploy stage — so the next image is already building while
303
303
  # the previous container deploys.
304
304
  #
305
- # The consumer starts a deploy per built container. Each deploy first waits for
306
- # all of the container's dependencies to finish deploying (depends_on ordering),
307
- # then deploys. Independent containers deploy concurrently; per-server exclusion
308
- # (one container at a time per host) is enforced inside ContainerDeployer.
305
+ # The consumer deploys containers strictly one at a time, in the order they
306
+ # were built (which is dependency order, so depends_on is respected). Only a
307
+ # single container is ever restarting across the whole fleet at any moment —
308
+ # the invariant the original sequential deploy guaranteed. This matters for
309
+ # zero-downtime web apps: deploying several containers concurrently can flap an
310
+ # app's health (its health check depends on backends that would be restarting
311
+ # in parallel), leaving nginx with a live-but-unhealthy upstream and serving
312
+ # 502s during the deploy.
313
+ #
314
+ # Only image builds run ahead of deploys (below) — that is where the pipeline
315
+ # speedup comes from. A single container still fans out to its own servers in
316
+ # parallel (see ContainerDeployer), so multi-server containers stay fast.
309
317
  def pipeline_build_and_deploy(configuration, deployer, build_server_pool, containers,
310
318
  skip_build, skip_deploy, force_restart, skip_force_restart)
311
319
 
312
320
  ordered_containers = build_deploy_order(containers)
313
321
 
314
- deploy_queue = Queue.new
315
- deploy_threads = {}
322
+ deploy_queue = Queue.new
316
323
 
317
324
  consumer = Thread.new do
318
325
  while (container = deploy_queue.pop) != :done
319
- # Dependencies are always built (and therefore queued) before their
320
- # dependents, so their deploy threads already exist here.
321
- dependency_threads = container
322
- .dependent_containers
323
- .map { |dependency| deploy_threads[dependency] }
324
- .compact
325
-
326
- deploy_threads[container] = Thread.new do
327
- dependency_threads.each(&:join)
328
- deploy_container(deployer, container, force_restart, skip_force_restart)
329
- end
326
+ deploy_container(deployer, container, force_restart, skip_force_restart)
330
327
  end
331
328
  end
332
329
 
@@ -348,7 +345,6 @@ class Indocker::Launchers::ConfigurationDeployer
348
345
 
349
346
  deploy_queue.push(:done)
350
347
  consumer.join
351
- deploy_threads.values.each(&:join)
352
348
  end
353
349
 
354
350
  # Returns the containers to deploy in dependency order (each container's
@@ -1,3 +1,3 @@
1
1
  module Indocker
2
- VERSION = "0.5.0"
2
+ VERSION = "0.5.3"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: indocker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ruslan Gatiyatov
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2026-07-06 00:00:00.000000000 Z
12
+ date: 2026-07-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: net-ssh