synapse 0.0.1 → 0.2.1
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.
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/Gemfile.lock +45 -0
- data/README.md +185 -4
- data/Vagrantfile +112 -0
- data/bin/synapse +54 -0
- data/chef/converge +4 -0
- data/chef/cookbooks/lxc/recipes/default.rb +2 -0
- data/chef/cookbooks/synapse/attributes/default.rb +1 -0
- data/chef/cookbooks/synapse/recipes/default.rb +6 -0
- data/chef/run.json +8 -0
- data/chef/run.rb +2 -0
- data/client/.RData +0 -0
- data/client/.Rhistory +294 -0
- data/client/bench_rewrite_config.dat +2013 -0
- data/client/benchmark-client.iml +20 -0
- data/client/pom.xml +45 -0
- data/client/src/main/java/ClientArsch.java +68 -0
- data/client/src/main/java/META-INF/MANIFEST.MF +3 -0
- data/config/synapse.conf.json +96 -0
- data/haproxy.pid +1 -0
- data/lib/gen-rb/endpoint_types.rb +65 -0
- data/lib/gen-rb/thrift.rb +65 -0
- data/lib/synapse/base.rb +5 -0
- data/lib/synapse/haproxy.rb +200 -0
- data/lib/synapse/service_watcher/base.rb +44 -0
- data/lib/synapse/service_watcher/dns.rb +98 -0
- data/lib/synapse/service_watcher/ec2tag.rb +26 -0
- data/lib/synapse/service_watcher/zookeeper.rb +138 -0
- data/lib/synapse/service_watcher.rb +28 -0
- data/lib/synapse/version.rb +1 -1
- data/lib/synapse.rb +61 -2
- data/spec/lib/synapse/haproxy_spec.rb +14 -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 +7 -0
- data/test.sh +3 -0
- metadata +123 -5
data/.gitignore
CHANGED
data/.rspec
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
synapse (0.1.4)
|
5
|
+
thrift (~> 0.9.0)
|
6
|
+
zk (~> 1.7.4)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
coderay (1.0.8)
|
12
|
+
diff-lcs (1.1.3)
|
13
|
+
little-plugger (1.1.3)
|
14
|
+
logging (1.7.2)
|
15
|
+
little-plugger (>= 1.1.3)
|
16
|
+
method_source (0.8.1)
|
17
|
+
pry (0.9.10)
|
18
|
+
coderay (~> 1.0.5)
|
19
|
+
method_source (~> 0.8)
|
20
|
+
slop (~> 3.3.1)
|
21
|
+
pry-nav (0.2.2)
|
22
|
+
pry (~> 0.9.10)
|
23
|
+
rspec (2.12.0)
|
24
|
+
rspec-core (~> 2.12.0)
|
25
|
+
rspec-expectations (~> 2.12.0)
|
26
|
+
rspec-mocks (~> 2.12.0)
|
27
|
+
rspec-core (2.12.0)
|
28
|
+
rspec-expectations (2.12.0)
|
29
|
+
diff-lcs (~> 1.1.3)
|
30
|
+
rspec-mocks (2.12.0)
|
31
|
+
slop (3.3.3)
|
32
|
+
thrift (0.9.0)
|
33
|
+
zk (1.7.5)
|
34
|
+
logging (~> 1.7.2)
|
35
|
+
zookeeper (~> 1.4.0)
|
36
|
+
zookeeper (1.4.4)
|
37
|
+
|
38
|
+
PLATFORMS
|
39
|
+
ruby
|
40
|
+
|
41
|
+
DEPENDENCIES
|
42
|
+
pry
|
43
|
+
pry-nav
|
44
|
+
rspec
|
45
|
+
synapse!
|
data/README.md
CHANGED
@@ -1,6 +1,95 @@
|
|
1
|
-
# Synapse
|
1
|
+
# Synapse #
|
2
2
|
|
3
|
-
|
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
|
+
{
|
58
|
+
"name": "proddb",
|
59
|
+
"local_port": 3219,
|
60
|
+
"server_options": "check inter 2000 rise 3 fall 2",
|
61
|
+
"default_servers": [
|
62
|
+
{
|
63
|
+
"name": "default-db",
|
64
|
+
"host": "mydb.example.com",
|
65
|
+
"port": 5432
|
66
|
+
}
|
67
|
+
],
|
68
|
+
"discovery": {
|
69
|
+
"method": "awstag",
|
70
|
+
"tag": "proddb",
|
71
|
+
"value": "true"
|
72
|
+
},
|
73
|
+
"listen": [
|
74
|
+
]
|
75
|
+
},
|
76
|
+
...
|
77
|
+
```
|
78
|
+
|
79
|
+
And then change your database.yaml file to look like this:
|
80
|
+
|
81
|
+
```yaml
|
82
|
+
production:
|
83
|
+
database: mydb
|
84
|
+
host: localhost
|
85
|
+
port: 3219
|
86
|
+
```
|
87
|
+
|
88
|
+
Start up synapse.
|
89
|
+
It will configure HAProxy with a proxy from `localhost:3219` to your DB.
|
90
|
+
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`.
|
91
|
+
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.
|
92
|
+
HAProxy will be transparently reloaded, and your application will keep running without a hiccup.
|
4
93
|
|
5
94
|
## Installation
|
6
95
|
|
@@ -16,9 +105,73 @@ Or install it yourself as:
|
|
16
105
|
|
17
106
|
$ gem install synapse
|
18
107
|
|
19
|
-
##
|
108
|
+
## Configuration ##
|
109
|
+
|
110
|
+
Synapse depends on a single config file in JSON format; it's usually called `synapse.conf.json`.
|
111
|
+
The file has two main sections.
|
112
|
+
The first is the `services` section, which lists the services you'd like to connect.
|
113
|
+
The second is the `haproxy` section, which specifies how to configure and interact with HAProxy.
|
114
|
+
|
115
|
+
### Configuring a Service ###
|
116
|
+
|
117
|
+
Each service hash has the following options:
|
118
|
+
|
119
|
+
* `name`: a human-readable name for the service, this is used in logs and notifications
|
120
|
+
* `local_port`: the port (on localhost) where HAProxy will listen for connections to the serivce
|
121
|
+
* `discovery`: how synapse will discover hosts providing this service (see below)
|
122
|
+
* `default_servers`: the list of default servers providing this service; synapse uses these if none others can be discovered
|
123
|
+
* `server_port_override`: the port that discovered servers listen on; 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
|
124
|
+
* `server_options`: the haproxy options for each `server` line of the service in HAProxy config; may be left out
|
125
|
+
* `listen`: additional lines passed to the HAProxy config in the `listen` stanza of this service
|
20
126
|
|
21
|
-
|
127
|
+
#### Service Discovery ####
|
128
|
+
|
129
|
+
We've included a number of `watchers` which provide service discovery.
|
130
|
+
Put these into the `discovery` section of the service hash, with these options:
|
131
|
+
|
132
|
+
##### Stub #####
|
133
|
+
|
134
|
+
The stub watcher, this is useful in situations where you only want to use the servers in the `default_servers` list.
|
135
|
+
It has only one option:
|
136
|
+
|
137
|
+
* `method`: stub
|
138
|
+
|
139
|
+
##### Zookeeper #####
|
140
|
+
|
141
|
+
This watcher retrieves a list of servers from zookeeper.
|
142
|
+
It takes the following options:
|
143
|
+
|
144
|
+
* `method`: zookeeper
|
145
|
+
* `path`: the zookeeper path where ephemeral nodes will be created for each available service server
|
146
|
+
* `hosts`: the list of zookeeper servers to query
|
147
|
+
|
148
|
+
The watcher assumes that each node under `path` represents a service server.
|
149
|
+
Synapse attempts to decode the data in each of these nodes using JSON and also using Thrift under the standard Twitter service encoding.
|
150
|
+
We assume that the data contains a hostname and a port for service servers.
|
151
|
+
|
152
|
+
#### Listing Default Servers ####
|
153
|
+
|
154
|
+
You may list a number of default servers providing a service.
|
155
|
+
Each hash in that section has the following options:
|
156
|
+
|
157
|
+
* `name`: a human-readable name for the default server; must be unique
|
158
|
+
* `host`: the host or IP address of the server
|
159
|
+
* `port`: the port where the service runs on the `host`
|
160
|
+
|
161
|
+
The `default_servers` list is used only when service discovery returns no servers.
|
162
|
+
In that case, the service proxy will be created with the servers listed here.
|
163
|
+
If you do not list any default servers, no proxy will be created.
|
164
|
+
|
165
|
+
### Configuring HAProxy ###
|
166
|
+
|
167
|
+
The `haproxy` section of the config file has the following options:
|
168
|
+
|
169
|
+
* `reload_command`: the command Synapse will run to reload HAProxy
|
170
|
+
* `config_file_path`: where Synapse will write the HAProxy config file
|
171
|
+
* `do_writes`: whether or not the config file will be written (default to `true`)
|
172
|
+
* `do_reloads`: whether or not Synapse will reload HAProxy (default to `true`)
|
173
|
+
* `global`: options listed here will be written into the `global` section of the HAProxy config
|
174
|
+
* `defaults`: options listed here will be written into the `defaults` section of the HAProxy config
|
22
175
|
|
23
176
|
## Contributing
|
24
177
|
|
@@ -27,3 +180,31 @@ Write usage instructions here
|
|
27
180
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
181
|
4. Push to the branch (`git push origin my-new-feature`)
|
29
182
|
5. Create new Pull Request
|
183
|
+
|
184
|
+
### Creating a Service Watcher ###
|
185
|
+
|
186
|
+
If you'd like to create a new service watcher:
|
187
|
+
|
188
|
+
1. Create a file for your watcher in `service_watcher` dir
|
189
|
+
2. Use the following template:
|
190
|
+
```ruby
|
191
|
+
require_relative "./base"
|
192
|
+
module Synapse
|
193
|
+
class NewWatcher < BaseWatcher
|
194
|
+
def start
|
195
|
+
# write code which begins running service discovery
|
196
|
+
end
|
197
|
+
|
198
|
+
private
|
199
|
+
def validate_discovery_opts
|
200
|
+
# here, validate any required options in @discovery
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
```
|
205
|
+
|
206
|
+
3. Implement the `start` and `validate_discovery_opts` methods
|
207
|
+
4. Implement whatever additional methods your discovery requires
|
208
|
+
|
209
|
+
When your watcher detects a list of new backends, they should be written to `@backends`.
|
210
|
+
You should then call `@synapse.configure` to force synapse to update the HAProxy config.
|
data/Vagrantfile
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# -*- mode: ruby -*-
|
2
|
+
# vi: set ft=ruby :
|
3
|
+
|
4
|
+
this_dir = File.dirname(File.expand_path __FILE__)
|
5
|
+
|
6
|
+
Vagrant::Config.run do |config|
|
7
|
+
# All Vagrant configuration is done here. The most common configuration
|
8
|
+
# options are documented and commented below. For a complete reference,
|
9
|
+
# please see the online documentation at vagrantup.com.
|
10
|
+
|
11
|
+
# Every Vagrant virtual environment requires a box to build off of.
|
12
|
+
config.vm.box = "precise64"
|
13
|
+
|
14
|
+
# The url from where the 'config.vm.box' box will be fetched if it
|
15
|
+
# doesn't already exist on the user's system.
|
16
|
+
# config.vm.box_url = "http://domain.com/path/to/above.box"
|
17
|
+
|
18
|
+
config.vm.box_url = 'http://files.vagrantup.com/precise64.box'
|
19
|
+
|
20
|
+
# Boot with a GUI so you can see the screen. (Default is headless)
|
21
|
+
# config.vm.boot_mode = :gui
|
22
|
+
|
23
|
+
# Assign this VM to a host-only network IP, allowing you to access it
|
24
|
+
# via the IP. Host-only networks can talk to the host machine as well as
|
25
|
+
# any other machines on the same network, but cannot be accessed (through this
|
26
|
+
# network interface) by any external networks.
|
27
|
+
# config.vm.network :hostonly, "192.168.33.10"
|
28
|
+
|
29
|
+
# Assign this VM to a bridged network, allowing you to connect directly to a
|
30
|
+
# network using the host's network device. This makes the VM appear as another
|
31
|
+
# physical device on your network.
|
32
|
+
# config.vm.network :bridged
|
33
|
+
|
34
|
+
# Forward a port from the guest to the host, which allows for outside
|
35
|
+
# computers to access the VM, whereas host only networking does not.
|
36
|
+
# config.vm.forward_port 80, 8080
|
37
|
+
|
38
|
+
# Share an additional folder to the guest VM. The first argument is
|
39
|
+
# an identifier, the second is the path on the guest to mount the
|
40
|
+
# folder, and the third is the path on the host to the actual folder.
|
41
|
+
# config.vm.share_folder "v-data", "/vagrant_data", "../data"
|
42
|
+
|
43
|
+
config.vm.share_folder 'this_dir', '/this_dir', this_dir
|
44
|
+
|
45
|
+
# Enable provisioning with Puppet stand alone. Puppet manifests
|
46
|
+
# are contained in a directory path relative to this Vagrantfile.
|
47
|
+
# You will need to create the manifests directory and a manifest in
|
48
|
+
# the file base.pp in the manifests_path directory.
|
49
|
+
#
|
50
|
+
# An example Puppet manifest to provision the message of the day:
|
51
|
+
#
|
52
|
+
# # group { "puppet":
|
53
|
+
# # ensure => "present",
|
54
|
+
# # }
|
55
|
+
# #
|
56
|
+
# # File { owner => 0, group => 0, mode => 0644 }
|
57
|
+
# #
|
58
|
+
# # file { '/etc/motd':
|
59
|
+
# # content => "Welcome to your Vagrant-built virtual machine!
|
60
|
+
# # Managed by Puppet.\n"
|
61
|
+
# # }
|
62
|
+
#
|
63
|
+
# config.vm.provision :puppet do |puppet|
|
64
|
+
# puppet.manifests_path = "manifests"
|
65
|
+
# puppet.manifest_file = "base.pp"
|
66
|
+
# end
|
67
|
+
|
68
|
+
# Enable provisioning with chef solo, specifying a cookbooks path, roles
|
69
|
+
# path, and data_bags path (all relative to this Vagrantfile), and adding
|
70
|
+
# some recipes and/or roles.
|
71
|
+
#
|
72
|
+
config.vm.provision :chef_solo do |chef|
|
73
|
+
# chef.cookbooks_path = "../my-recipes/cookbooks"
|
74
|
+
# chef.roles_path = "../my-recipes/roles"
|
75
|
+
# chef.data_bags_path = "../my-recipes/data_bags"
|
76
|
+
# chef.add_recipe "mysql"
|
77
|
+
# chef.add_role "web"
|
78
|
+
|
79
|
+
# You may also specify custom JSON attributes:
|
80
|
+
# chef.json = { :mysql_password => "foo" }
|
81
|
+
|
82
|
+
chef.cookbooks_path = File.join(this_dir, "chef/cookbooks")
|
83
|
+
chef.add_recipe "synapse"
|
84
|
+
end
|
85
|
+
|
86
|
+
# Enable provisioning with chef server, specifying the chef server URL,
|
87
|
+
# and the path to the validation key (relative to this Vagrantfile).
|
88
|
+
#
|
89
|
+
# The Opscode Platform uses HTTPS. Substitute your organization for
|
90
|
+
# ORGNAME in the URL and validation key.
|
91
|
+
#
|
92
|
+
# If you have your own Chef Server, use the appropriate URL, which may be
|
93
|
+
# HTTP instead of HTTPS depending on your configuration. Also change the
|
94
|
+
# validation key to validation.pem.
|
95
|
+
#
|
96
|
+
# config.vm.provision :chef_client do |chef|
|
97
|
+
# chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME"
|
98
|
+
# chef.validation_key_path = "ORGNAME-validator.pem"
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# If you're using the Opscode platform, your validator client is
|
102
|
+
# ORGNAME-validator, replacing ORGNAME with your organization name.
|
103
|
+
#
|
104
|
+
# IF you have your own Chef Server, the default validation client name is
|
105
|
+
# chef-validator, unless you changed the configuration.
|
106
|
+
#
|
107
|
+
# chef.validation_client_name = "ORGNAME-validator"
|
108
|
+
|
109
|
+
Vagrant::Config.run do |config|
|
110
|
+
config.vm.provision :shell, :path => "test.sh"
|
111
|
+
end
|
112
|
+
end
|
data/bin/synapse
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
require_relative "../lib/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
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# parse command line arguments
|
32
|
+
optparse.parse!
|
33
|
+
|
34
|
+
|
35
|
+
# parse synapse config file
|
36
|
+
begin
|
37
|
+
config = JSON::parse(File.read(options[:config]))
|
38
|
+
rescue Errno::ENOENT => e
|
39
|
+
raise ArgumentError, "config file does not exist:\n#{e.inspect}"
|
40
|
+
rescue Errno::EACCES => e
|
41
|
+
raise ArgumentError, "could not open config file:\n#{e.inspect}"
|
42
|
+
rescue JSON::ParserError => e
|
43
|
+
raise "config file #{options[:config]} is not json:\n#{e.inspect}"
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
# create synapse object
|
48
|
+
s = Synapse::Synapse.new config
|
49
|
+
|
50
|
+
# start synapse
|
51
|
+
s.run
|
52
|
+
|
53
|
+
|
54
|
+
puts "exiting synapse"
|
data/chef/converge
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
default.synapse.dir = '/opt/synapse'
|
data/chef/run.json
ADDED
data/chef/run.rb
ADDED
data/client/.RData
ADDED
Binary file
|