indocker 0.4.1 → 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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/indocker/container_deployer.rb +47 -16
- data/lib/indocker/launchers/configuration_deployer.rb +81 -46
- data/lib/indocker/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f5c86b947ae3162d9b81812db303c76b6825a1adc397cee5e3495bbd68437e96
|
|
4
|
+
data.tar.gz: 6dd6c07468dad622731efeb333a55279dabf18e74a940e62fa2308d572636713
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5546b2240618feacfe4efaa8c6b8ae82e58dde32eb383ff8cf7fd7295cf50caae8833ae0c8205031dea06bf7d7acb536935b0235c425bb5bf8506cb6cb22e7ef
|
|
7
|
+
data.tar.gz: e9d0d5ae9c6ee1b114d7551e954a282775a62c20840dc4c3a51675659f7ee99e5faa2691fb450e3e4c1ab1757b61d3459bc6aa1e0e67be0b75dd9d2de3036c74
|
data/Gemfile.lock
CHANGED
|
@@ -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
|
|
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
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
41
|
+
results = threads.map(&:value)
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
|
@@ -88,27 +88,16 @@ class Indocker::Launchers::ConfigurationDeployer
|
|
|
88
88
|
|
|
89
89
|
update_crontab_redeploy_rules(configuration, build_servers.first)
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
333
|
+
ordered_containers.each do |container|
|
|
334
|
+
@progress.start_building_container(container)
|
|
327
335
|
|
|
328
|
-
|
|
336
|
+
if !skip_build
|
|
337
|
+
build_server = build_server_pool.get
|
|
329
338
|
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
344
|
+
@progress.finish_building_container(container)
|
|
345
|
+
|
|
346
|
+
deploy_queue.push(container) if !skip_deploy
|
|
336
347
|
end
|
|
337
348
|
|
|
338
|
-
|
|
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
|
-
|
|
341
|
-
|
|
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)
|
data/lib/indocker/version.rb
CHANGED