Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow registrations to be manifested on the file system and add 'use_previous_backends' option #115

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d5d061a
Add 'use_previous_backends' option.
jnb Sep 27, 2014
b855bff
Merge pull request #1 from jnb/use_previous_backends
jolynch Feb 27, 2015
35f1f8f
Allow registrations to be manifested on the file system
jolynch Feb 27, 2015
c3d862c
Merge pull request #2 from jolynch/add_flat_file_output
jolynch Mar 5, 2015
01826a0
Merge remote-tracking branch 'upstream/master'
jolynch Mar 5, 2015
1fdd57e
Explicitly deduplicate registrations
jolynch Mar 16, 2015
6eb5850
Allow the option allredisp option to haproxy.
jolynch Mar 16, 2015
e5411b0
Merge pull request #5 from jolynch/allow_allredisp_option
jnb Mar 16, 2015
c6c37fa
Merge pull request #4 from jolynch/dedup_ignore_ids
jolynch Mar 17, 2015
136d1fe
Add rate limiter.
jnb May 20, 2015
e057dbf
Merge pull request #8 from Yelp/rate_limiter
jnb May 26, 2015
e6d03c2
Revert "Add rate limiter."
jnb May 27, 2015
a3e84d2
Increase HAProxy restart interval.
jnb May 27, 2015
d0c1161
Merge pull request #9 from Yelp/restart_interval
jolynch May 27, 2015
5928c2b
ZooKeeper connection pooling.
jnb May 28, 2015
e96b2a9
Merge pull request #11 from Yelp/zk_pool
jolynch May 28, 2015
4eab7e0
Rate limit restarts but not stats socket updates
jolynch May 28, 2015
7de6a59
Merge pull request #10 from jolynch/jlynch_separate_stats_from_restart
jnb May 28, 2015
030ccfe
Add state file.
jnb May 29, 2015
235fa85
Merge pull request #12 from Yelp/state_file
jnb May 29, 2015
36f2a94
Fix bug in caching logic.
jnb Jun 17, 2015
e90cf85
Merge pull request #13 from Yelp/fix_cache
jolynch Jun 17, 2015
86d7f9d
Add support for the weight key added in nerve
bobtfish Jun 29, 2015
e49afdf
Merge pull request #14 from bobtfish/add_weight
jolynch Jul 6, 2015
a6a48e3
Try out :per_callback threads and get more debug information
jolynch Jul 13, 2015
29f3197
Merge pull request #15 from jolynch/work_on_watcher_slowness
jnb Jul 14, 2015
420440f
Turns out it's important to handle session disconnects correctly
Jul 15, 2015
429a20a
Merge pull request #16 from jolynch/fix_zk_session_bug
jnb Jul 15, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ If you do not list any default servers, no proxy will be created. The
`default_servers` will also be used in addition to discovered servers if the
`keep_default_servers` option is set.

If you do not list any `default_servers`, and all backends for a service
disappear then the previous known backends will be used. Disable this behavior
by unsetting `use_previous_backends`.

#### The `haproxy` Section ####

This section is its own hash, which should contain the following keys:
Expand Down Expand Up @@ -335,5 +339,5 @@ end
3. Implement the `start` and `validate_discovery_opts` methods
4. Implement whatever additional methods your discovery requires

When your watcher detects a list of new backends, they should be written to `@backends`.
You should then call `@synapse.configure` to force synapse to update the HAProxy config.
When your watcher detects a list of new backends, you should call `set_backends` to
store the new backends and update the HAProxy config.
19 changes: 15 additions & 4 deletions lib/synapse.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "synapse/version"
require "synapse/service_watcher/base"
require "synapse/haproxy"
require "synapse/file_output"
require "synapse/service_watcher"
require "synapse/log"

Expand All @@ -17,9 +18,17 @@ def initialize(opts={})
raise "specify a list of services to connect in the config" unless opts.has_key?('services')
@service_watchers = create_service_watchers(opts['services'])

# create the haproxy object
# create objects that need to be notified of service changes
@config_generators = []
# create the haproxy config generator, this is mandatory
raise "haproxy config section is missing" unless opts.has_key?('haproxy')
@haproxy = Haproxy.new(opts['haproxy'])
@config_generators << Haproxy.new(opts['haproxy'])

# possibly create a file manifestation for services that do not
# want to communicate via haproxy, e.g. cassandra
if opts.has_key?('file_output')
@config_generators << FileOutput.new(opts['file_output'])
end

# configuration is initially enabled to configure on first loop
@config_updated = true
Expand Down Expand Up @@ -47,8 +56,10 @@ def run

if @config_updated
@config_updated = false
log.info "synapse: regenerating haproxy config"
@haproxy.update_config(@service_watchers)
@config_generators.each do |config_generator|
log.info "synapse: regenerating #{config_generator.name} config"
config_generator.update_config(@service_watchers)
end
else
sleep 1
end
Expand Down
53 changes: 53 additions & 0 deletions lib/synapse/file_output.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
require 'synapse/log'
require 'fileutils'
require 'tempfile'

module Synapse
class FileOutput
include Logging
attr_reader :opts, :name

def initialize(opts)
super()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what this is calling out to, this class does not inherit from anything

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed it does not, I'll fix that up


unless opts.has_key?("output_directory")
raise ArgumentError, "flat file generation requires an output_directory key"
end

begin
FileUtils.mkdir_p(opts['output_directory'])
rescue SystemCallError => err
raise ArgumentError, "provided output directory #{opts['output_directory']} is not present or creatable"
end

@opts = opts
@name = 'file_output'
end

def update_config(watchers)
watchers.each do |watcher|
write_backends_to_file(watcher.name, watcher.backends)
end
end

def write_backends_to_file(service_name, new_backends)
data_path = File.join(@opts['output_directory'], "#{service_name}.json")
begin
old_backends = JSON.load(File.read(data_path))
rescue Errno::ENOENT
old_backends = nil
end

if old_backends == new_backends
return false
else
# Atomically write new sevice configuration file
temp_path = File.join(@opts['output_directory'],
".#{service_name}.json.tmp")
File.open(temp_path, 'w', 0644) {|f| f.write(new_backends.to_json)}
FileUtils.mv(temp_path, data_path)
return true
end
end
end
end
6 changes: 5 additions & 1 deletion lib/synapse/haproxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
module Synapse
class Haproxy
include Logging
attr_reader :opts
attr_reader :opts, :name

# these come from the documentation for haproxy 1.5
# http://haproxy.1wt.eu/download/1.5/doc/configuration.txt
Expand Down Expand Up @@ -43,6 +43,7 @@ class Haproxy
"option abortonclose",
"option accept-invalid-http-response",
"option allbackups",
"option allredisp",
"option checkcache",
"option forceclose",
"option forwardfor",
Expand Down Expand Up @@ -173,6 +174,7 @@ class Haproxy
"option accept-invalid-http-request",
"option accept-invalid-http-response",
"option allbackups",
"option allredisp",
"option checkcache",
"option clitcpka",
"option contstats",
Expand Down Expand Up @@ -386,6 +388,7 @@ class Haproxy
"option accept-invalid-http-request",
"option accept-invalid-http-response",
"option allbackups",
"option allredisp",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we try to re-sync these lists of options with latest haproxy? i wonder what other options have been added since i put these lists in here...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh, this isn't even the option that I ended up getting accepted. http://marc.info/?l=haproxy&m=143161966505891&w=2 basically added a parameter to "option redispatch" which allows this behavior. Until that is released in 1.6 do you mind if we keep this in here (we use is pretty heavily at Yelp)

"option checkcache",
"option clitcpka",
"option contstats",
Expand Down Expand Up @@ -523,6 +526,7 @@ def initialize(opts)
end

@opts = opts
@name = 'haproxy'

# how to restart haproxy
@restart_interval = 2
Expand Down
40 changes: 38 additions & 2 deletions lib/synapse/service_watcher/base.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require 'set'
require 'synapse/log'

module Synapse
Expand Down Expand Up @@ -42,6 +43,10 @@ def initialize(opts={}, synapse)

@keep_default_servers = opts['keep_default_servers'] || false

# If there are no default servers and a watcher reports no backends, then
# use the previous backends that we already know about.
@use_previous_backends = opts.fetch('use_previous_backends', true)

# set a flag used to tell the watchers to exit
# this is not used in every watcher
@should_exit = false
Expand Down Expand Up @@ -95,13 +100,44 @@ def validate_discovery_opts
end

def set_backends(new_backends)
if @keep_default_servers
@backends = @default_servers + new_backends
# Aggregate and deduplicate all potential backend service instances.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you really concerned about duplicates in the default servers list? if that's the case, maybe we should de-dup @default_servers when we set it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned with duplicates in the set of backends we write out to HAProxy in general. Like, we had a few cases where duplicate backends got written out and then synapse would only down a single one when we failed healthchecks.

If we dedup the default_servers separately doesn't that mean that we might end up with duplicates between the defaults and new_backends and could end up with the bad situation I mention above?

new_backends = (new_backends + (@keep_default_servers ? @default_servers : [])).uniq {|b|
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh i see. i think this would read a little cleaner if it was not all on one line. i would say:

new_backends += @default_servers if @keep_default_servers
new_backends.uniq!{|b|  [b['host'], b['port'], b.fetch('name', '')]}

[b['host'], b['port'], b.fetch('name', '')]
}

if new_backends.to_set == @backends.to_set
return false
end

if new_backends.empty?
if @default_servers.empty?
if @use_previous_backends
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for addressing this original "mis-feature"

# Discard this update
log.warn "synapse: no default servers for service #{@name};" \
" using previous backends: #{@backends.inspect}"
return false
else
log.warn "synapse: no default servers for service #{@name} and" \
" 'use_previous_backends' is disabled; dropping all backends"
@backends.clear
end
else
log.warn "synapse: no backends for service #{@name};" \
" using default servers: #{@default_servers.inspect}"
@backends = @default_servers
end
else
log.info "synapse: discovered #{new_backends.length} backends for service #{@name}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on an unrelated note, one thing i always thought might be necessary eventually is to specify the minimum number of backends to serve traffic.

suppose a service goes down. when functionality is restored, a single backend is naturally going to be the first to come up. that backend might then be pounded with all traffic which is normally meant for many multiple backends, and as a result might go down because of excess traffic. this would then happen to the second backend, etc.. etc..

it might be nice here to avoid setting any backends unless the number exceeds some limit which we know can handle the traffic. just a brain-dump, not even necessarily a todo.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this is a needed feature. I've been hacking on a "latency sensitive farm failover" method as suggested in http://blog.haproxy.com/2013/12/23/failover-and-worst-case-management-with-haproxy/ and determining how many backends are needed is core to that.

The idea is that you have multiple HAProxy backends powering a single frontend, with each backend having more nodes (e.g. because it encompasses multiple datacenters).

@backends = new_backends
end

reconfigure!

return true
end

# Subclasses should not invoke this directly; it's only exposed so that it
# can be overridden in subclasses.
def reconfigure!
@synapse.reconfigure!
end
Expand Down
16 changes: 1 addition & 15 deletions lib/synapse/service_watcher/dns.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,21 +89,7 @@ def configure_backends(servers)
end
end

if new_backends.empty?
if @default_servers.empty?
log.warn "synapse: no backends and no default servers for service #{@name};" \
" using previous backends: #{@backends.inspect}"
else
log.warn "synapse: no backends for service #{@name};" \
" using default servers: #{@default_servers.inspect}"
@backends = @default_servers
end
else
log.info "synapse: discovered #{new_backends.length} backends for service #{@name}"
set_backends(new_backends)
end

reconfigure!
set_backends(new_backends)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great!

end
end
end
26 changes: 1 addition & 25 deletions lib/synapse/service_watcher/docker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,10 @@ def validate_discovery_opts
end

def watch
last_containers = []
until @should_exit
begin
start = Time.now
current_containers = containers
unless last_containers == current_containers
last_containers = current_containers
configure_backends(last_containers)
end

set_backends(containers)
sleep_until_next_check(start)
rescue Exception => e
log.warn "synapse: error in watcher thread: #{e.inspect}"
Expand Down Expand Up @@ -98,23 +92,5 @@ def containers
log.warn "synapse: error while polling for containers: #{e.inspect}"
[]
end

def configure_backends(new_backends)
if new_backends.empty?
if @default_servers.empty?
log.warn "synapse: no backends and no default servers for service #{@name};" \
" using previous backends: #{@backends.inspect}"
else
log.warn "synapse: no backends for service #{@name};" \
" using default servers: #{@default_servers.inspect}"
@backends = @default_servers
end
else
log.info "synapse: discovered #{new_backends.length} backends for service #{@name}"
set_backends(new_backends)
end
reconfigure!
end

end
end
27 changes: 1 addition & 26 deletions lib/synapse/service_watcher/ec2tag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,12 @@ def validate_discovery_opts
end

def watch
last_backends = []
until @should_exit
begin
start = Time.now
current_backends = discover_instances

if last_backends != current_backends
if set_backends(discover_instances)
log.info "synapse: ec2tag watcher backends have changed."
last_backends = current_backends
configure_backends(current_backends)
else
log.info "synapse: ec2tag watcher backends are unchanged."
end

sleep_until_next_check(start)
rescue Exception => e
log.warn "synapse: error in ec2tag watcher thread: #{e.inspect}"
Expand Down Expand Up @@ -111,23 +103,6 @@ def instances_with_tags(tag_name, tag_value)
.tagged_values(tag_value)
.select { |i| i.status == :running }
end

def configure_backends(new_backends)
if new_backends.empty?
if @default_servers.empty?
log.warn "synapse: no backends and no default servers for service #{@name};" \
" using previous backends: #{@backends.inspect}"
else
log.warn "synapse: no backends for service #{@name};" \
" using default servers: #{@default_servers.inspect}"
@backends = @default_servers
end
else
log.info "synapse: discovered #{new_backends.length} backends for service #{@name}"
@backends = new_backends
end
@synapse.reconfigure!
end
end
end

16 changes: 2 additions & 14 deletions lib/synapse/service_watcher/zookeeper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def create(path)
@zk.create(path, ignore: :node_exists)
end

# find the current backends at the discovery path; sets @backends
# find the current backends at the discovery path
def discover
log.info "synapse: discovering backends for service #{@name}"

Expand All @@ -69,17 +69,7 @@ def discover
end
end

if new_backends.empty?
if @default_servers.empty?
log.warn "synapse: no backends and no default servers for service #{@name}; using previous backends: #{@backends.inspect}"
else
log.warn "synapse: no backends for service #{@name}; using default servers: #{@default_servers.inspect}"
@backends = @default_servers
end
else
log.info "synapse: discovered #{new_backends.length} backends for service #{@name}"
set_backends(new_backends)
end
set_backends(new_backends)
end

# sets up zookeeper callbacks if the data at the discovery path changes
Expand All @@ -103,8 +93,6 @@ def watcher_callback
watch
# Rediscover
discover
# send a message to calling class to reconfigure
reconfigure!
end
end

Expand Down
Loading