indocker 0.4.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e66344870970bb12d0f6811219da96fe68d4549416280ac39cf3bd07960ff96
4
- data.tar.gz: ff05089cd25b1aea14d84a052febae5436b002cedbb03d7490cc2659815a6846
3
+ metadata.gz: f5c86b947ae3162d9b81812db303c76b6825a1adc397cee5e3495bbd68437e96
4
+ data.tar.gz: 6dd6c07468dad622731efeb333a55279dabf18e74a940e62fa2308d572636713
5
5
  SHA512:
6
- metadata.gz: 44a2d51ed48f473d5b5f65f81d681d188be17830487b1ff98ac63b252f99f5ca33686f262c41fc763f3f70e9244b31a1469615fae6b47f6f16b91b3632129eb3
7
- data.tar.gz: d196b2da95b698b3b897c54a408526229978f69e29f9c6d297c4bda8234662ea633f6e493b7612cd218132b4aa96bdc97bcb4c057a591e270e4d35b1714f48d4
6
+ metadata.gz: 5546b2240618feacfe4efaa8c6b8ae82e58dde32eb383ff8cf7fd7295cf50caae8833ae0c8205031dea06bf7d7acb536935b0235c425bb5bf8506cb6cb22e7ef
7
+ data.tar.gz: e9d0d5ae9c6ee1b114d7551e954a282775a62c20840dc4c3a51675659f7ee99e5faa2691fb450e3e4c1ab1757b61d3459bc6aa1e0e67be0b75dd9d2de3036c74
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- indocker (0.4.0)
4
+ indocker (0.5.0)
5
5
  net-ssh
6
6
 
7
7
  GEM
@@ -12,30 +12,37 @@ class Indocker::ContainerDeployer
12
12
 
13
13
  @deployed_containers = Hash.new(false)
14
14
  @deployed_servers = {}
15
+
16
+ @server_locks = {}
17
+ @server_locks_mutex = Mutex.new
15
18
  end
16
19
 
17
20
  def deploy(container, force_restart, skip_force_restart, progress)
18
21
  return if @deployed_containers[container]
19
22
 
20
- # Deploy the same container to all of its servers in parallel.
23
+ # Deploy the container to all of its servers in parallel, each server in its
24
+ # own thread with its own ssh connection.
21
25
  #
22
- # Each thread opens its OWN ssh connection (create_connection!), so no
23
- # net-ssh session is shared between threads. We join all threads before
24
- # returning, so container dependency ordering (depends_on) is preserved:
25
- # a dependent container never starts deploying until every server of its
26
- # dependency has finished.
27
- threads = container.servers.map do |server|
28
- progress.start_deploying_container(container, server)
29
-
30
- Thread.new do
31
- deploy_to_server(container, server, force_restart, skip_force_restart, progress)
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
37
+ deploy_to_server(container, server, force_restart, skip_force_restart, progress)
38
+ end
32
39
  end
33
- end
34
40
 
35
- results = threads.map(&:value)
41
+ results = threads.map(&:value)
36
42
 
37
- if results.any? { |exit_code| exit_code != 0 }
38
- exit 1
43
+ if results.any? { |exit_code| exit_code != 0 }
44
+ exit 1
45
+ end
39
46
  end
40
47
 
41
48
  @deployed_containers[container] = true
@@ -54,6 +61,8 @@ class Indocker::ContainerDeployer
54
61
  # connection, so it must not touch shared mutable state except the
55
62
  # thread-safe `progress`.
56
63
  def deploy_to_server(container, server, force_restart, skip_force_restart, progress)
64
+ progress.start_deploying_container(container, server)
65
+
57
66
  deploy_server = @server_pool.create_connection!(server)
58
67
  @logger.info("Deploying container: #{container.name.to_s.green} to #{server.user}@#{server.host}")
59
68
 
@@ -73,4 +82,26 @@ class Indocker::ContainerDeployer
73
82
 
74
83
  result.exit_code
75
84
  end
76
- end
85
+
86
+ def server_lock_key(server)
87
+ [server.host, server.port, server.user]
88
+ end
89
+
90
+ # Returns a process-wide singleton Mutex for the given server, so the same lock
91
+ # is shared across every container targeting that server.
92
+ def server_lock(server)
93
+ key = server_lock_key(server)
94
+
95
+ @server_locks_mutex.synchronize do
96
+ @server_locks[key] ||= Mutex.new
97
+ end
98
+ 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
+ end
@@ -20,7 +20,7 @@ class Indocker::Docker
20
20
 
21
21
  def stop(container_name, time: nil, skip_errors: false)
22
22
  time ||= 10 # default timeout for Linux
23
- Indocker::Shell.command("docker stop --time=#{time} #{container_name}", Indocker.logger, skip_errors: skip_errors)
23
+ Indocker::Shell.command("docker stop -t #{time} #{container_name}", Indocker.logger, skip_errors: skip_errors)
24
24
  rm(container_name, skip_errors: skip_errors)
25
25
  end
26
26
 
@@ -88,27 +88,16 @@ class Indocker::Launchers::ConfigurationDeployer
88
88
 
89
89
  update_crontab_redeploy_rules(configuration, build_servers.first)
90
90
 
91
- containers.uniq.each do |container|
92
- recursively_deploy_container(
93
- configuration,
94
- deployer,
95
- build_server_pool,
96
- container,
97
- containers,
98
- deployment_policy.skip_build,
99
- deployment_policy.skip_deploy,
100
- deployment_policy.force_restart,
101
- deployment_policy.skip_force_restart
102
- )
103
- end
104
-
105
- Thread
106
- .list
107
- .each { |t|
108
- if t != Thread.current
109
- t.join if !t.stop?
110
- end
111
- }
91
+ pipeline_build_and_deploy(
92
+ configuration,
93
+ deployer,
94
+ build_server_pool,
95
+ containers.uniq,
96
+ deployment_policy.skip_build,
97
+ deployment_policy.skip_deploy,
98
+ deployment_policy.force_restart,
99
+ deployment_policy.skip_force_restart
100
+ )
112
101
  ensure
113
102
  build_server_pool.close_sessions if build_server_pool
114
103
  deployer.close_sessions if deployer
@@ -306,40 +295,86 @@ class Indocker::Launchers::ConfigurationDeployer
306
295
  @compiled_images[image] = true
307
296
  end
308
297
 
309
- def recursively_deploy_container(configuration, deployer, build_server_pool, container,
310
- containers, skip_build, skip_deploy, force_restart, skip_force_restart)
311
-
312
- container.dependent_containers.each do |container|
313
- recursively_deploy_container(
314
- configuration,
315
- deployer,
316
- build_server_pool,
317
- container,
318
- containers,
319
- skip_build,
320
- skip_deploy,
321
- force_restart,
322
- skip_force_restart
323
- )
298
+ # Producer/consumer pipeline that decouples image builds from deployments.
299
+ #
300
+ # The producer builds images one after another on the build server (serially,
301
+ # in dependency order) and, as soon as an image is built & pushed, hands its
302
+ # container to the deploy stage — so the next image is already building while
303
+ # the previous container deploys.
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.
309
+ def pipeline_build_and_deploy(configuration, deployer, build_server_pool, containers,
310
+ skip_build, skip_deploy, force_restart, skip_force_restart)
311
+
312
+ ordered_containers = build_deploy_order(containers)
313
+
314
+ deploy_queue = Queue.new
315
+ deploy_threads = {}
316
+
317
+ consumer = Thread.new do
318
+ 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
330
+ end
324
331
  end
325
332
 
326
- return if !containers.include?(container)
333
+ ordered_containers.each do |container|
334
+ @progress.start_building_container(container)
327
335
 
328
- @progress.start_building_container(container)
336
+ if !skip_build
337
+ build_server = build_server_pool.get
329
338
 
330
- if !skip_build
331
- build_server = build_server_pool.get
339
+ build_server.set_busy(true)
340
+ compile_image(configuration, container.image, build_server)
341
+ build_server.set_busy(false)
342
+ end
332
343
 
333
- build_server.set_busy(true)
334
- compile_image(configuration, container.image, build_server)
335
- build_server.set_busy(false)
344
+ @progress.finish_building_container(container)
345
+
346
+ deploy_queue.push(container) if !skip_deploy
336
347
  end
337
348
 
338
- @progress.finish_building_container(container)
349
+ deploy_queue.push(:done)
350
+ consumer.join
351
+ deploy_threads.values.each(&:join)
352
+ end
353
+
354
+ # Returns the containers to deploy in dependency order (each container's
355
+ # dependencies come before it), deduplicated. Containers that are only pulled
356
+ # in as dependencies but are not part of the deploy set are used for ordering
357
+ # but not included.
358
+ def build_deploy_order(containers)
359
+ ordered = []
360
+ visited = {}
339
361
 
340
- if !skip_deploy
341
- deploy_container(deployer, container, force_restart, skip_force_restart)
362
+ containers.each do |container|
363
+ collect_deploy_order(container, containers, ordered, visited)
342
364
  end
365
+
366
+ ordered
367
+ end
368
+
369
+ def collect_deploy_order(container, containers, ordered, visited)
370
+ return if visited[container]
371
+ visited[container] = true
372
+
373
+ container.dependent_containers.each do |dependency|
374
+ collect_deploy_order(dependency, containers, ordered, visited)
375
+ end
376
+
377
+ ordered << container if containers.include?(container)
343
378
  end
344
379
 
345
380
  def deploy_container(deployer, container, force_restart, skip_force_restart)
@@ -1,3 +1,3 @@
1
1
  module Indocker
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
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.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ruslan Gatiyatov