democracyworks-synapse 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.mailmap +3 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +23 -0
- data/Makefile +6 -0
- data/README.md +241 -0
- data/Rakefile +1 -0
- data/bin/synapse +62 -0
- data/config/hostheader_test.json +71 -0
- data/config/svcdir_test.json +46 -0
- data/config/synapse.conf.json +90 -0
- data/config/synapse_services/service1.json +24 -0
- data/config/synapse_services/service2.json +24 -0
- data/lib/synapse.rb +79 -0
- data/lib/synapse/base.rb +5 -0
- data/lib/synapse/haproxy.rb +759 -0
- data/lib/synapse/log.rb +24 -0
- data/lib/synapse/service_watcher.rb +32 -0
- data/lib/synapse/service_watcher/base.rb +105 -0
- data/lib/synapse/service_watcher/dns.rb +103 -0
- data/lib/synapse/service_watcher/docker.rb +115 -0
- data/lib/synapse/service_watcher/ec2tag.rb +26 -0
- data/lib/synapse/service_watcher/zookeeper.rb +127 -0
- data/lib/synapse/version.rb +3 -0
- data/spec/lib/synapse/haproxy_spec.rb +13 -0
- data/spec/lib/synapse/service_watcher_base_spec.rb +55 -0
- data/spec/lib/synapse/service_watcher_docker_spec.rb +138 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/config.rb +9 -0
- data/spec/support/minimum.conf.yaml +27 -0
- data/synapse.gemspec +25 -0
- metadata +155 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
@@ -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
|
data/.mailmap
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/Makefile
ADDED
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/synapse
ADDED
@@ -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
|
+
}
|