mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
Add option to bind to activated sockets (#2362)
Bind to (systemd) activated sockets, regardless of configured binds. Systemd can present sockets as file descriptors that are already opened. By default Puma will use these but only if it was explicitly told to bind to the socket. If not, it will close the activated sockets. This means all configuration is duplicated. Binds can contain additional configuration, but only SSL config is really relevant since the unix and TCP socket options are ignored. This means there is a lot of duplicated configuration for no additional value in most setups. This option tells the launcher to bind to all activated sockets, regardless of existing binds. The special value 'only' can be passed. If systemd activated sockets are detected, all other binds are cleared. When they aren't detected, the regular binds will be used.
This commit is contained in:
parent
64c0153cd0
commit
5af91ff6aa
7 changed files with 135 additions and 0 deletions
|
@ -6,6 +6,7 @@
|
|||
* Integrate with systemd's watchdog and notification features ([#2438])
|
||||
* Adds max_fast_inline as a configuration option for the Server object ([#2406])
|
||||
* You can now fork workers from worker 0 using SIGURG w/o fork_worker enabled [#2449]
|
||||
* Add option to bind to systemd activated sockets ([#2362])
|
||||
|
||||
* Bugfixes
|
||||
* Your bugfix goes here <Most recent on the top, like GitHub> (#Github Number)
|
||||
|
|
|
@ -129,6 +129,21 @@ Puma will detect the release path socket as different than the one provided by
|
|||
systemd and attempt to bind it again, resulting in the exception
|
||||
`There is already a server bound to:`.
|
||||
|
||||
### Binding
|
||||
|
||||
By default you need to configure puma to have binds matching with all
|
||||
ListenStream statements. Any mismatched systemd ListenStreams will be closed by
|
||||
puma.
|
||||
|
||||
To automatically bind to all activated sockets, the option
|
||||
`--bind-to-activated-sockets` can be used. This matches the config DSL
|
||||
`bind_to_activated_sockets` statement. This will cause puma to create a bind
|
||||
automatically for any activated socket. When systemd socket activation is not
|
||||
enabled, this option does nothing.
|
||||
|
||||
This also accepts an optional argument `only` (DSL: `'only'`) to discard any
|
||||
binds that's not socket activated.
|
||||
|
||||
## Usage
|
||||
|
||||
Without socket activation, use `systemctl` as root (e.g. via `sudo`) as
|
||||
|
|
|
@ -111,6 +111,43 @@ module Puma
|
|||
["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
|
||||
end
|
||||
|
||||
# Synthesize binds from systemd socket activation
|
||||
#
|
||||
# When systemd socket activation is enabled, it can be tedious to keep the
|
||||
# binds in sync. This method can synthesize any binds based on the received
|
||||
# activated sockets. Any existing matching binds will be respected.
|
||||
#
|
||||
# When only_matching is true in, all binds that do not match an activated
|
||||
# socket is removed in place.
|
||||
#
|
||||
# It's a noop if no activated sockets were received.
|
||||
def synthesize_binds_from_activated_fs(binds, only_matching)
|
||||
return binds unless activated_sockets.any?
|
||||
|
||||
activated_binds = []
|
||||
|
||||
activated_sockets.keys.each do |proto, addr, port|
|
||||
if port
|
||||
tcp_url = "#{proto}://#{addr}:#{port}"
|
||||
ssl_url = "ssl://#{addr}:#{port}"
|
||||
ssl_url_prefix = "#{ssl_url}?"
|
||||
|
||||
existing = binds.find { |bind| bind == tcp_url || bind == ssl_url || bind.start_with?(ssl_url_prefix) }
|
||||
|
||||
activated_binds << (existing || tcp_url)
|
||||
else
|
||||
# TODO: can there be a SSL bind without a port?
|
||||
activated_binds << "#{proto}://#{addr}"
|
||||
end
|
||||
end
|
||||
|
||||
if only_matching
|
||||
activated_binds
|
||||
else
|
||||
binds | activated_binds
|
||||
end
|
||||
end
|
||||
|
||||
def parse(binds, logger, log_msg = 'Listening')
|
||||
binds.each do |str|
|
||||
uri = URI.parse str
|
||||
|
|
|
@ -104,6 +104,10 @@ module Puma
|
|||
user_config.bind arg
|
||||
end
|
||||
|
||||
o.on "--bind-to-activated-sockets [only]", "Bind to all activated sockets" do |arg|
|
||||
user_config.bind_to_activated_sockets(arg || true)
|
||||
end
|
||||
|
||||
o.on "-C", "--config PATH", "Load PATH as a config file" do |arg|
|
||||
file_config.load arg
|
||||
end
|
||||
|
|
|
@ -191,6 +191,32 @@ module Puma
|
|||
@options[:binds] = []
|
||||
end
|
||||
|
||||
# Bind to (systemd) activated sockets, regardless of configured binds.
|
||||
#
|
||||
# Systemd can present sockets as file descriptors that are already opened.
|
||||
# By default Puma will use these but only if it was explicitly told to bind
|
||||
# to the socket. If not, it will close the activated sockets. This means
|
||||
# all configuration is duplicated.
|
||||
#
|
||||
# Binds can contain additional configuration, but only SSL config is really
|
||||
# relevant since the unix and TCP socket options are ignored.
|
||||
#
|
||||
# This means there is a lot of duplicated configuration for no additional
|
||||
# value in most setups. This method tells the launcher to bind to all
|
||||
# activated sockets, regardless of existing bind.
|
||||
#
|
||||
# To clear configured binds, the value only can be passed. This will clear
|
||||
# out any binds that may have been configured.
|
||||
#
|
||||
# @example Use any systemd activated sockets as well as configured binds
|
||||
# bind_to_activated_sockets
|
||||
#
|
||||
# @example Only bind to systemd activated sockets, ignoring other binds
|
||||
# bind_to_activated_sockets 'only'
|
||||
def bind_to_activated_sockets(bind=true)
|
||||
@options[:bind_to_activated_sockets] = bind
|
||||
end
|
||||
|
||||
# Define the TCP port to bind to. Use +bind+ for more advanced options.
|
||||
#
|
||||
# @example
|
||||
|
|
|
@ -58,6 +58,13 @@ module Puma
|
|||
|
||||
@config.load
|
||||
|
||||
if @config.options[:bind_to_activated_sockets]
|
||||
@config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
|
||||
@config.options[:binds],
|
||||
@config.options[:bind_to_activated_sockets] == 'only'
|
||||
)
|
||||
end
|
||||
|
||||
@options = @config.options
|
||||
@config.clamp
|
||||
|
||||
|
|
|
@ -34,6 +34,51 @@ end
|
|||
class TestBinder < TestBinderBase
|
||||
parallelize_me!
|
||||
|
||||
def test_synthesize_binds_from_activated_fds_no_sockets
|
||||
binds = ['tcp://0.0.0.0:3000']
|
||||
result = @binder.synthesize_binds_from_activated_fs(binds, true)
|
||||
|
||||
assert_equal ['tcp://0.0.0.0:3000'], result
|
||||
end
|
||||
|
||||
def test_synthesize_binds_from_activated_fds_non_matching_together
|
||||
binds = ['tcp://0.0.0.0:3000']
|
||||
sockets = {['tcp', '0.0.0.0', '5000'] => nil}
|
||||
@binder.instance_variable_set(:@activated_sockets, sockets)
|
||||
result = @binder.synthesize_binds_from_activated_fs(binds, false)
|
||||
|
||||
assert_equal ['tcp://0.0.0.0:3000', 'tcp://0.0.0.0:5000'], result
|
||||
end
|
||||
|
||||
def test_synthesize_binds_from_activated_fds_non_matching_only
|
||||
binds = ['tcp://0.0.0.0:3000']
|
||||
sockets = {['tcp', '0.0.0.0', '5000'] => nil}
|
||||
@binder.instance_variable_set(:@activated_sockets, sockets)
|
||||
result = @binder.synthesize_binds_from_activated_fs(binds, true)
|
||||
|
||||
assert_equal ['tcp://0.0.0.0:5000'], result
|
||||
end
|
||||
|
||||
def test_synthesize_binds_from_activated_fds_complex_binds
|
||||
binds = [
|
||||
'tcp://0.0.0.0:3000',
|
||||
'ssl://192.0.2.100:5000',
|
||||
'ssl://192.0.2.101:5000?no_tlsv1=true',
|
||||
'unix:///run/puma.sock'
|
||||
]
|
||||
sockets = {
|
||||
['tcp', '0.0.0.0', '5000'] => nil,
|
||||
['tcp', '192.0.2.100', '5000'] => nil,
|
||||
['tcp', '192.0.2.101', '5000'] => nil,
|
||||
['unix', '/run/puma.sock'] => nil
|
||||
}
|
||||
@binder.instance_variable_set(:@activated_sockets, sockets)
|
||||
result = @binder.synthesize_binds_from_activated_fs(binds, false)
|
||||
|
||||
expected = ['tcp://0.0.0.0:3000', 'ssl://192.0.2.100:5000', 'ssl://192.0.2.101:5000?no_tlsv1=true', 'unix:///run/puma.sock', 'tcp://0.0.0.0:5000']
|
||||
assert_equal expected, result
|
||||
end
|
||||
|
||||
def test_localhost_addresses_dont_alter_listeners_for_tcp_addresses
|
||||
@binder.parse ["tcp://localhost:0"], @events
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue