indocker 0.6.1 → 0.6.2

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: 0eda49208a82e456326a838e76cab5152673a078e30668832c97c7c1b525a4fa
4
- data.tar.gz: 5904dabfde8e6110821b104be6fd0809799a5deb3b35f991bed33627323bf1c9
3
+ metadata.gz: 10915e6da5c1e8d2899b510a223d8761a0542880776b61e8078d38d23e261b28
4
+ data.tar.gz: dd047c45c09dd0f26b95ae668f00266c2f45c46ff1c4aecff14ef0155d9e5a59
5
5
  SHA512:
6
- metadata.gz: '020961f0cae0f634adf95f355d2e537bca061ec0380294f0a12ca75d0e72b54f19eb57d357dbe1f1dbef50cfd5d43896ed7f80107fe7ac822e6d3addd2645586'
7
- data.tar.gz: f9ff2feb5c3288474635a3e7d31354c8065075068872865f51b6542e1c6933c0342e253260c58492cc7eb99e3ae09e1904692a9b1acd5a13751d467ca0edaf5d
6
+ metadata.gz: b879a49d9defa1cbe4dc56685e0467ff070c9d0310a6fd5531766c941e7b51028aa1ace5817dec1638dec944ccba8afbbceffa062095a658fdb248c615095253
7
+ data.tar.gz: 70814bb24a5a66da13c36a6d1350403686b6a5672f184aa691179cfb40850b74e66b059fc82f249fc052ebe942c2ed26326570ce041270d561f25ca7bb67021c
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- indocker (0.6.1)
4
+ indocker (0.6.2)
5
5
  net-ssh
6
6
 
7
7
  GEM
@@ -46,6 +46,9 @@ class Indocker::Launchers::ConfigurationDeployer
46
46
  deployer = Indocker::ContainerDeployer.new(configuration: configuration, logger: @logger)
47
47
 
48
48
  @global_logger.info("Establishing ssh sessions to all servers...")
49
+
50
+ warm_jump_hosts(deploy_servers + build_servers)
51
+
49
52
  build_server_pool.create_sessions!
50
53
 
51
54
  build_servers = configuration
@@ -103,6 +106,15 @@ class Indocker::Launchers::ConfigurationDeployer
103
106
 
104
107
  private
105
108
 
109
+ # Opens the shared connection to each jump host before anything runs in parallel, so the
110
+ # fan-out reuses it instead of racing to create one connection per operation.
111
+ def warm_jump_hosts(servers)
112
+ servers.map(&:jump_host).compact.uniq.each do |jump_host|
113
+ @logger.info("Establishing shared connection to jump host #{jump_host}")
114
+ Indocker::SshSession.warm_jump_host(jump_host, logger: @logger)
115
+ end
116
+ end
117
+
106
118
  def wait_remote_operations(remote_operations)
107
119
  remote_operations.each do |remote_operation|
108
120
  server = remote_operation.server
@@ -44,8 +44,13 @@ class Indocker::Rsync
44
44
  ]
45
45
 
46
46
  # Tunnel through the jump host the server is defined with, so that a server
47
- # reachable only over its internal address syncs like any other one.
48
- ssh_command << "-J #{session.jump_host}" if session.jump_host
47
+ # reachable only over its internal address syncs like any other one. ProxyCommand
48
+ # rather than -J: it is the same tunnel every other remote operation uses, including
49
+ # the shared connection that keeps a parallel sync from exhausting the jump host's
50
+ # MaxStartups.
51
+ if session.jump_host
52
+ ssh_command << %Q{-o ProxyCommand="#{Indocker::SshSession.jump_proxy_command(session.jump_host)}"}
53
+ end
49
54
 
50
55
  ssh_command = ssh_command.join(' ')
51
56
  command = "rsync --delete-after -a -e '#{ssh_command}' #{from} #{session.user}@#{session.host}:#{to}"
@@ -2,6 +2,7 @@ require_relative 'shell'
2
2
 
3
3
  class Indocker::SshSession
4
4
  LOCALHOST = 'localhost'.freeze
5
+ JUMP_CONTROL_PERSIST = '600'.freeze
5
6
 
6
7
  class ExecResult
7
8
  attr_reader :stdout_data, :stderr_data, :exit_code, :exit_signal
@@ -21,6 +22,53 @@ class Indocker::SshSession
21
22
 
22
23
  attr_reader :host, :user, :port, :logger, :jump_host
23
24
 
25
+ # Builds the tunnel to a jump host, shared by Net::SSH sessions and by rsync.
26
+ #
27
+ # Net::SSH::Proxy::Jump would build a bare `ssh -W`: a jump host that asks for a password
28
+ # hangs the deploy, and probing a server whose sshd is still starting prints
29
+ # "channel 0: open failed" from the ssh client on every retry.
30
+ #
31
+ # Multiplexing matters even more. A deploy syncs and deploys to every server in parallel,
32
+ # and each of those opens its own tunnel; sshd drops everything above MaxStartups (10 by
33
+ # default), which surfaces as "connection closed by remote host" on random servers. Sharing
34
+ # one connection to the jump host keeps the whole fan-out down to a single tunnel.
35
+ def self.jump_proxy_command(jump_host)
36
+ require 'digest'
37
+
38
+ host = jump_host
39
+ port = nil
40
+
41
+ if host =~ /\A(.+):(\d+)\z/
42
+ host = $1
43
+ port = $2
44
+ end
45
+
46
+ control_path = "/tmp/indocker-jump-#{Digest::MD5.hexdigest(jump_host)[0, 10]}"
47
+
48
+ command = [
49
+ "ssh -q",
50
+ "-o BatchMode=yes",
51
+ "-o ConnectTimeout=#{Indocker.ssh_connect_timeout}",
52
+ "-o ControlMaster=auto",
53
+ "-o ControlPath=#{control_path}",
54
+ "-o ControlPersist=#{JUMP_CONTROL_PERSIST}",
55
+ ]
56
+
57
+ command << "-p #{port}" if port
58
+ command << "-W %h:%p"
59
+ command << host
60
+
61
+ command.join(' ')
62
+ end
63
+
64
+ # Opens the shared connection to a jump host up front. Without it the first wave of
65
+ # parallel operations races to create the master connection and opens a burst of them.
66
+ def self.warm_jump_host(jump_host, logger:)
67
+ command = jump_proxy_command(jump_host).sub("-W %h:%p ", "")
68
+
69
+ Indocker::Shell.command("#{command} true", logger)
70
+ end
71
+
24
72
  # Opens a session to a server as it is defined in the configuration, including its
25
73
  # jump host, if any. Every remote operation goes through here, so a server reached
26
74
  # through a jump host is reached that way by binaries sync, container deploys and
@@ -56,7 +104,7 @@ class Indocker::SshSession
56
104
 
57
105
  if @jump_host
58
106
  require 'net/ssh/proxy/command'
59
- options[:proxy] = Net::SSH::Proxy::Command.new(jump_proxy_command)
107
+ options[:proxy] = Net::SSH::Proxy::Command.new(self.class.jump_proxy_command(@jump_host))
60
108
  end
61
109
 
62
110
  @ssh = Net::SSH.start(@host, @user, options)
@@ -82,32 +130,6 @@ class Indocker::SshSession
82
130
  end
83
131
 
84
132
  private
85
- # Net::SSH::Proxy::Jump builds this same `ssh -W` tunnel, but without BatchMode,
86
- # connect timeout or -q: a jump host that asks for a password hangs the deploy, and
87
- # every probe of a server whose sshd is still starting prints "channel 0: open failed"
88
- # from the ssh client.
89
- def jump_proxy_command
90
- host = @jump_host
91
- port = nil
92
-
93
- if host =~ /\A(.+):(\d+)\z/
94
- host = $1
95
- port = $2
96
- end
97
-
98
- command = [
99
- "ssh -q",
100
- "-o BatchMode=yes",
101
- "-o ConnectTimeout=#{Indocker.ssh_connect_timeout}",
102
- ]
103
-
104
- command << "-p #{port}" if port
105
- command << "-W %h:%p"
106
- command << host
107
-
108
- command.join(' ')
109
- end
110
-
111
133
  def exec_locally!(command)
112
134
  res = Indocker::Shell.command_with_result(command, @logger, skip_logging: false)
113
135
  ExecResult.new(res.stdout, '', res.exit_status, nil)
@@ -1,3 +1,3 @@
1
1
  module Indocker
2
- VERSION = "0.6.1"
2
+ VERSION = "0.6.2"
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.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ruslan Gatiyatov