democracyworks-synapse 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 035f5185668b79655c47279251a52255efa293a0
4
+ data.tar.gz: 578f659d158fde0be97f2c852ecbe44df03692a8
5
+ SHA512:
6
+ metadata.gz: 32eda48b033735638ca19e21c2022ca2db80886829ee303a0c03a368398a4207e01e5d39211cdd23ccf8fc65540f95b0ae32ca9d3b7ab846ec13772bbd71e6a2
7
+ data.tar.gz: 6ebf7d1cfd7f504793bf1d09191c7e49f67f564fc11e9196065d3d458d499e53457d190a8000e4f7d33abcee303a703a581346c670dc0efb0698482a4e94c7a7
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *~
19
+ .vagrant
20
+ .*sw?
21
+ vendor/
22
+
23
+ synapse.jar
@@ -0,0 +1,3 @@
1
+ <igor.serebryany@airbedandbreakfast.com> <igor47@moomers.org>
2
+ <martin.rhoads@airbnb.com> <ermal14@gmail.com>
3
+ Pierre Carrier <pierre@gcarrier.fr>
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in synapse.gemspec
4
+ gemspec
@@ -0,0 +1,23 @@
1
+ Original work Copyright (c) 2013 Airbnb, Inc.
2
+ Modified work Copyright (c) 2014 Democracy Works, Inc.
3
+
4
+ MIT License
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,6 @@
1
+ build: synapse.jar
2
+
3
+ synapse.jar:
4
+ jruby -S warble jar
5
+
6
+ .PHONY: build push
@@ -0,0 +1,241 @@
1
+ # Synapse #
2
+
3
+ Synapse is Airbnb's new system for service discovery.
4
+ Synapse solves the problem of automated fail-over in the cloud, where failover via network re-configuration is impossible.
5
+ The end result is the ability to connect internal services together in a scalable, fault-tolerant way.
6
+
7
+ ## Motivation ##
8
+
9
+ Synapse emerged from the need to maintain high-availability applications in the cloud.
10
+ Traditional high-availability techniques, which involve using a CRM like [pacemaker](http://linux-ha.org/wiki/Pacemaker), do not work in environments where the end-user has no control over the networking.
11
+ In an environment like Amazon's EC2, all of the available workarounds are suboptimal:
12
+
13
+ * Round-robin DNS: Slow to converge, and doesn't work when applications cache DNS lookups (which is frequent)
14
+ * Elastic IPs: slow to converge, limited in number, public-facing-only, which makes them less useful for internal services
15
+ * ELB: Again, public-facing only, and only useful for HTTP
16
+
17
+ One solution to this problem is a discovery service, like [Apache Zookeeper](http://zookeeper.apache.org/).
18
+ However, Zookeeper and similar services have their own problems:
19
+
20
+ * Service discovery is embedded in all of your apps; often, integration is not simple
21
+ * The discovery layer itself it subject to failure
22
+ * Requires additional servers/instances
23
+
24
+ Synapse solves these difficulties in a simple and fault-tolerant way.
25
+
26
+ ## How Synapse Works ##
27
+
28
+ Synapse runs on your application servers; here at Airbnb, we just run it on every box we deploy.
29
+ The heart of synapse is actually [HAProxy](http://haproxy.1wt.eu/), a stable and proven routing component.
30
+ For every external service that your application talks to, we assign a synapse local port on localhost.
31
+ Synapse creates a proxy from the local port to the service, and you reconfigure your application to talk to the proxy.
32
+
33
+ Synapse comes with a number of `watchers`, which are responsible for service discovery.
34
+ The synapse watchers take care of re-configuring the proxy so that it always points at available servers.
35
+ We've included a number of default watchers, including ones that query zookeeper and ones using the AWS API.
36
+ It is easy to write your own watchers for your use case, and we encourage submitting them back to the project.
37
+
38
+ ## Example Migration ##
39
+
40
+ Lets suppose your rails application depends on a Postgre database instance.
41
+ The database.yaml file has the DB host and port hardcoded:
42
+
43
+ ```yaml
44
+ production:
45
+ database: mydb
46
+ host: mydb.example.com
47
+ port: 5432
48
+ ```
49
+
50
+ You would like to be able to fail over to a different database in case the original dies.
51
+ Let's suppose your instance is running in AWS and you're using the tag 'proddb' set to 'true' to indicate the prod DB.
52
+ You set up synapse to proxy the DB connection on `localhost:3219` in the `synapse.conf.json` file.
53
+ Add a hash under `services` that looks like this:
54
+
55
+ ```json
56
+ {"services":
57
+ "proddb": {
58
+ "default_servers": [
59
+ {
60
+ "name": "default-db",
61
+ "host": "mydb.example.com",
62
+ "port": 5432
63
+ }
64
+ ],
65
+ "discovery": {
66
+ "method": "awstag",
67
+ "tag": "proddb",
68
+ "value": "true"
69
+ },
70
+ "haproxy": {
71
+ "port": 3219,
72
+ "server_options": "check inter 2000 rise 3 fall 2",
73
+ "frontend": [
74
+ "mode tcp",
75
+ ],
76
+ "backend": [
77
+ "mode tcp",
78
+ ],
79
+ },
80
+ },
81
+ ...
82
+ ```
83
+
84
+ And then change your database.yaml file to look like this:
85
+
86
+ ```yaml
87
+ production:
88
+ database: mydb
89
+ host: localhost
90
+ port: 3219
91
+ ```
92
+
93
+ Start up synapse.
94
+ It will configure HAProxy with a proxy from `localhost:3219` to your DB.
95
+ It will attempt to find the DB using the AWS API; if that does not work, it will default to the DB given in `default_servers`.
96
+ In the worst case, if AWS API is down and you need to change which DB your application talks to, simply edit the `synapse.conf.json` file, update the `default_servers` and restart synapse.
97
+ HAProxy will be transparently reloaded, and your application will keep running without a hiccup.
98
+
99
+ ## Installation
100
+
101
+ Add this line to your application's Gemfile:
102
+
103
+ gem 'synapse'
104
+
105
+ And then execute:
106
+
107
+ $ bundle
108
+
109
+ Or install it yourself as:
110
+
111
+ $ gem install synapse
112
+
113
+ ## Configuration ##
114
+
115
+ Synapse depends on a single config file in JSON format; it's usually called `synapse.conf.json`.
116
+ The file has two main sections.
117
+ The first is the `services` section, which lists the services you'd like to connect.
118
+ The second is the `haproxy` section, which specifies how to configure and interact with HAProxy.
119
+
120
+ ### Configuring a Service ###
121
+
122
+ The services are a hash, where the keys are the `name` of the service to be configured.
123
+ The name is just a human-readable string; it will be used in logs and notifications.
124
+ Each value in the services hash is also a hash, and should contain the following keys:
125
+
126
+ * `discovery`: how synapse will discover hosts providing this service (see below)
127
+ * `default_servers`: the list of default servers providing this service; synapse uses these if none others can be discovered
128
+ * `haproxy`: how will the haproxy section for this service be configured
129
+
130
+ #### Service Discovery ####
131
+
132
+ We've included a number of `watchers` which provide service discovery.
133
+ Put these into the `discovery` section of the service hash, with these options:
134
+
135
+ ##### Stub #####
136
+
137
+ The stub watcher, this is useful in situations where you only want to use the servers in the `default_servers` list.
138
+ It has only one option:
139
+
140
+ * `method`: stub
141
+
142
+ ##### Zookeeper #####
143
+
144
+ This watcher retrieves a list of servers from zookeeper.
145
+ It takes the following options:
146
+
147
+ * `method`: zookeeper
148
+ * `path`: the zookeeper path where ephemeral nodes will be created for each available service server
149
+ * `hosts`: the list of zookeeper servers to query
150
+
151
+ The watcher assumes that each node under `path` represents a service server.
152
+ Synapse attempts to decode the data in each of these nodes using JSON and also using Thrift under the standard Twitter service encoding.
153
+ We assume that the data contains a hostname and a port for service servers.
154
+
155
+ ##### Docker #####
156
+
157
+ This watcher retrieves a list of [docker](http://www.docker.io/) containers via docker's [HTTP API](http://docs.docker.io/en/latest/api/docker_remote_api/).
158
+ It takes the following options:
159
+
160
+ * `method`: docker
161
+ * `servers`: a list of servers running docker as a daemon. Format is `{"name":"...", "host": "..."[, port: 4243]}`
162
+ * `image_name`: find containers running this image
163
+ * `container_port`: find containers forwarding this port
164
+ * `check_interval`: how often to poll the docker API on each server. Default is 15s.
165
+
166
+ #### Listing Default Servers ####
167
+
168
+ You may list a number of default servers providing a service.
169
+ Each hash in that section has the following options:
170
+
171
+ * `name`: a human-readable name for the default server; must be unique
172
+ * `host`: the host or IP address of the server
173
+ * `port`: the port where the service runs on the `host`
174
+
175
+ The `default_servers` list is used only when service discovery returns no servers.
176
+ In that case, the service proxy will be created with the servers listed here.
177
+ If you do not list any default servers, no proxy will be created. The
178
+ `default_servers` will also be used in addition to discovered servers if the
179
+ `keep_default_servers` option is set.
180
+
181
+ #### The `haproxy` Section ####
182
+
183
+ This section is it's own hash, which should contain the following keys:
184
+
185
+ * `port`: the port (on localhost) where HAProxy will listen for connections to the service.
186
+ * `server_port_override`: the port that discovered servers listen on; you should specify this if your discovery mechanism only discovers names or addresses (like the DNS watcher). If the discovery method discovers a port along with hostnames (like the zookeeper watcher) this option may be left out, but will be used in preference if given.
187
+ * `server_options`: the haproxy options for each `server` line of the service in HAProxy config; it may be left out.
188
+ * `frontend`: additional lines passed to the HAProxy config in the `frontend` stanza of this service
189
+ * `backend`: additional lines passed to the HAProxy config in the `backend` stanza of this service
190
+ * `listen`: these lines will be parsed and placed in the correct `frontend`/`backend` section as applicable; you can put lines which are the same for the frontend and backend here.
191
+
192
+ ### Configuring HAProxy ###
193
+
194
+ The `haproxy` section of the config file has the following options:
195
+
196
+ * `reload_command`: the command Synapse will run to reload HAProxy
197
+ * `config_file_path`: where Synapse will write the HAProxy config file
198
+ * `do_writes`: whether or not the config file will be written (default to `true`)
199
+ * `do_reloads`: whether or not Synapse will reload HAProxy (default to `true`)
200
+ * `global`: options listed here will be written into the `global` section of the HAProxy config
201
+ * `defaults`: options listed here will be written into the `defaults` section of the HAProxy config
202
+ * `bind_address`: force HAProxy to listen on this address (default is localhost)
203
+
204
+ Note that a non-default `bind_address` can be dangerous: it is up to you to ensure that HAProxy will not attempt to bind an address:port combination that is not already in use by one of your services.
205
+
206
+ ## Contributing
207
+
208
+ 1. Fork it
209
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
210
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
211
+ 4. Push to the branch (`git push origin my-new-feature`)
212
+ 5. Create new Pull Request
213
+
214
+ ### Creating a Service Watcher ###
215
+
216
+ If you'd like to create a new service watcher:
217
+
218
+ 1. Create a file for your watcher in `service_watcher` dir
219
+ 2. Use the following template:
220
+ ```ruby
221
+ require 'synapse/service_watcher/base'
222
+
223
+ module Synapse
224
+ class NewWatcher < BaseWatcher
225
+ def start
226
+ # write code which begins running service discovery
227
+ end
228
+
229
+ private
230
+ def validate_discovery_opts
231
+ # here, validate any required options in @discovery
232
+ end
233
+ end
234
+ end
235
+ ```
236
+
237
+ 3. Implement the `start` and `validate_discovery_opts` methods
238
+ 4. Implement whatever additional methods your discovery requires
239
+
240
+ When your watcher detects a list of new backends, they should be written to `@backends`.
241
+ You should then call `@synapse.configure` to force synapse to update the HAProxy config.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'yaml'
4
+ require 'optparse'
5
+
6
+ require 'synapse'
7
+
8
+ options={}
9
+
10
+ # set command line options
11
+ optparse = OptionParser.new do |opts|
12
+ opts.banner =<<-EOB
13
+ Welcome to synapse
14
+
15
+ Usage: synapse --config /path/to/synapse/config
16
+ EOB
17
+
18
+ options[:config] = ENV['SYNAPSE_CONFIG']
19
+ opts.on('-c config','--config config', String, 'path to synapse config') do |key,value|
20
+ options[:config] = key
21
+ end
22
+
23
+ opts.on( '-h', '--help', 'Display this screen' ) do
24
+ puts opts
25
+ exit
26
+ end
27
+ end
28
+
29
+ # parse command line arguments
30
+ optparse.parse!
31
+
32
+ def parseconfig(filename)
33
+ # parse synapse config file
34
+ begin
35
+ c = YAML::parse(File.read(filename))
36
+ rescue Errno::ENOENT => e
37
+ raise ArgumentError, "config file does not exist:\n#{e.inspect}"
38
+ rescue Errno::EACCES => e
39
+ raise ArgumentError, "could not open config file:\n#{e.inspect}"
40
+ rescue YAML::ParseError => e
41
+ raise "config file #{filename} is not yaml:\n#{e.inspect}"
42
+ end
43
+ return c.to_ruby
44
+ end
45
+
46
+ config = parseconfig(options[:config])
47
+ config['services'] ||= {}
48
+
49
+ if config.has_key?('service_conf_dir')
50
+ cdir = File.expand_path(config['service_conf_dir'])
51
+ if ! Dir.exists?(cdir)
52
+ raise "service conf dir does not exist:#{cdir}"
53
+ end
54
+ cfiles = Dir.glob(File.join(cdir, '*.{json,yaml}'))
55
+ cfiles.each { |x| config['services'][File.basename(x[/(.*)\.(json|yaml)$/, 1])] = parseconfig(x) }
56
+ end
57
+
58
+ # run synapse
59
+ s = Synapse::Synapse.new(config)
60
+ s.run
61
+
62
+ puts "synapse has exited"
@@ -0,0 +1,71 @@
1
+ {
2
+ "services": {
3
+ "service1": {
4
+ "default_servers": [
5
+ { "name": "default1", "host": "localhost", "port": 8080 }
6
+ ],
7
+ "discovery": {
8
+ "method": "dns",
9
+ "nameserver": "127.0.0.1",
10
+ "servers": [
11
+ "0.www.example.com",
12
+ "1.www.example.com"
13
+ ]
14
+ },
15
+ "haproxy": {
16
+ "server_options": "check inter 2s rise 3 fall 2",
17
+ "listen": [
18
+ "mode http",
19
+ "option httplog"
20
+ ],
21
+ "backend": [
22
+ "mode http",
23
+ "option httpchk GET /health HTTP/1.0"
24
+ ]
25
+ }
26
+ }
27
+ },
28
+ "haproxy": {
29
+ "reload_command": "sudo service haproxy reload",
30
+ "config_file_path": "/etc/haproxy/haproxy.cfg",
31
+ "socket_file_path": "/var/haproxy/stats.sock",
32
+ "do_writes": true,
33
+ "do_reloads": true,
34
+ "do_socket": true,
35
+ "global": [
36
+ "daemon",
37
+ "user haproxy",
38
+ "group haproxy",
39
+ "maxconn 4096",
40
+ "log 127.0.0.1 local0",
41
+ "log 127.0.0.1 local1 notice",
42
+ "stats socket /var/haproxy/stats.sock mode 666 level admin"
43
+ ],
44
+ "defaults": [
45
+ "log global",
46
+ "option dontlognull",
47
+ "maxconn 2000",
48
+ "retries 3",
49
+ "timeout connect 5s",
50
+ "timeout client 1m",
51
+ "timeout server 1m",
52
+ "option redispatch",
53
+ "balance roundrobin"
54
+ ],
55
+ "extra_sections": {
56
+ "listen stats :3212": [
57
+ "mode http",
58
+ "stats enable",
59
+ "stats uri /",
60
+ "stats refresh 5s"
61
+ ],
62
+ "frontend http-generic-in": [
63
+ "bind 127.0.0.1:80",
64
+ "acl is_service1 hdr_dom(host) -i service1.lb",
65
+ "acl is_cache hdr_dom(host) -i cache.lb",
66
+ "use_backend service1 if is_service1",
67
+ "use_backend cache if is_cache"
68
+ ]
69
+ }
70
+ }
71
+ }