synapse 0.11.1 → 0.12.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -21,3 +21,4 @@ tmp
21
21
  vendor/
22
22
 
23
23
  synapse.jar
24
+ .ruby-version
data/.travis.yml CHANGED
@@ -2,4 +2,6 @@ language: ruby
2
2
  cache: bundler
3
3
  rvm:
4
4
  - 1.9.3
5
-
5
+ - 2.0.0
6
+ - 2.1.6
7
+ - 2.2.2
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- synapse (0.11.1)
4
+ synapse (0.12.1)
5
+ aws-sdk (~> 1.39)
5
6
  docker-api (~> 1.7.2)
6
7
  zk (~> 1.9.4)
7
8
 
@@ -9,21 +10,29 @@ GEM
9
10
  remote: https://rubygems.org/
10
11
  specs:
11
12
  archive-tar-minitar (0.5.2)
13
+ aws-sdk (1.64.0)
14
+ aws-sdk-v1 (= 1.64.0)
15
+ aws-sdk-v1 (1.64.0)
16
+ json (~> 1.4)
17
+ nokogiri (>= 1.4.4)
12
18
  coderay (1.0.9)
13
- diff-lcs (1.2.4)
19
+ diff-lcs (1.2.5)
14
20
  docker-api (1.7.6)
15
21
  archive-tar-minitar
16
22
  excon (>= 0.28)
17
23
  json
18
- excon (0.32.1)
24
+ excon (0.45.4)
19
25
  ffi (1.9.3-java)
20
- json (1.8.1)
26
+ json (1.8.3)
21
27
  little-plugger (1.1.3)
22
28
  logging (1.8.2)
23
29
  little-plugger (>= 1.1.3)
24
30
  multi_json (>= 1.8.4)
25
31
  method_source (0.8.2)
26
- multi_json (1.9.2)
32
+ mini_portile (0.6.2)
33
+ multi_json (1.11.2)
34
+ nokogiri (1.6.6.2)
35
+ mini_portile (~> 0.6.0)
27
36
  pry (0.9.12.2)
28
37
  coderay (~> 1.0.5)
29
38
  method_source (~> 0.8)
@@ -36,21 +45,25 @@ GEM
36
45
  pry-nav (0.2.3)
37
46
  pry (~> 0.9.10)
38
47
  rake (10.1.1)
39
- rspec (2.14.1)
40
- rspec-core (~> 2.14.0)
41
- rspec-expectations (~> 2.14.0)
42
- rspec-mocks (~> 2.14.0)
43
- rspec-core (2.14.5)
44
- rspec-expectations (2.14.2)
45
- diff-lcs (>= 1.1.3, < 2.0)
46
- rspec-mocks (2.14.3)
48
+ rspec (3.1.0)
49
+ rspec-core (~> 3.1.0)
50
+ rspec-expectations (~> 3.1.0)
51
+ rspec-mocks (~> 3.1.0)
52
+ rspec-core (3.1.7)
53
+ rspec-support (~> 3.1.0)
54
+ rspec-expectations (3.1.2)
55
+ diff-lcs (>= 1.2.0, < 2.0)
56
+ rspec-support (~> 3.1.0)
57
+ rspec-mocks (3.1.3)
58
+ rspec-support (~> 3.1.0)
59
+ rspec-support (3.1.2)
47
60
  slop (3.4.6)
48
61
  spoon (0.0.4)
49
62
  ffi
50
- zk (1.9.4)
63
+ zk (1.9.5)
51
64
  logging (~> 1.8.2)
52
65
  zookeeper (~> 1.4.0)
53
- zookeeper (1.4.8)
66
+ zookeeper (1.4.10)
54
67
 
55
68
  PLATFORMS
56
69
  java
@@ -60,5 +73,8 @@ DEPENDENCIES
60
73
  pry
61
74
  pry-nav
62
75
  rake
63
- rspec
76
+ rspec (~> 3.1.0)
64
77
  synapse!
78
+
79
+ BUNDLED WITH
80
+ 1.10.5
data/README.md CHANGED
@@ -52,36 +52,27 @@ production:
52
52
 
53
53
  You would like to be able to fail over to a different database in case the original dies.
54
54
  Let's suppose your instance is running in AWS and you're using the tag 'proddb' set to 'true' to indicate the prod DB.
55
- You set up synapse to proxy the DB connection on `localhost:3219` in the `synapse.conf.json` file.
55
+ You set up synapse to proxy the DB connection on `localhost:3219` in the `synapse.conf.yaml` file.
56
56
  Add a hash under `services` that looks like this:
57
57
 
58
- ```json
59
- {"services":
60
- "proddb": {
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
- "haproxy": {
74
- "port": 3219,
75
- "server_options": "check inter 2000 rise 3 fall 2",
76
- "frontend": [
77
- "mode tcp",
78
- ],
79
- "backend": [
80
- "mode tcp",
81
- ],
82
- },
83
- },
84
- ...
58
+ ```yaml
59
+ ---
60
+ services:
61
+ proddb:
62
+ default_servers:
63
+ -
64
+ name: "default-db"
65
+ host: "mydb.example.com"
66
+ port: 5432
67
+ discovery:
68
+ method: "awstag"
69
+ tag_name: "proddb"
70
+ tag_value: "true"
71
+ haproxy:
72
+ port: 3219
73
+ server_options: "check inter 2000 rise 3 fall 2"
74
+ frontend: mode tcp
75
+ backend: mode tcp
85
76
  ```
86
77
 
87
78
  And then change your database.yaml file to look like this:
@@ -112,6 +103,9 @@ And then execute:
112
103
  Or install it yourself as:
113
104
 
114
105
  $ gem install synapse
106
+
107
+
108
+ Don't forget to install HAProxy prior to installing Synapse.
115
109
 
116
110
  ## Configuration ##
117
111
 
@@ -129,7 +123,6 @@ Each value in the services hash is also a hash, and should contain the following
129
123
  * `discovery`: how synapse will discover hosts providing this service (see below)
130
124
  * `default_servers`: the list of default servers providing this service; synapse uses these if no others can be discovered
131
125
  * `haproxy`: how will the haproxy section for this service be configured
132
- * `shared_frontend`: optional: haproxy configuration directives for a shared http frontend (see below)
133
126
 
134
127
  #### Service Discovery ####
135
128
 
@@ -167,6 +160,29 @@ It takes the following options:
167
160
  * `container_port`: find containers forwarding this port
168
161
  * `check_interval`: how often to poll the docker API on each server. Default is 15s.
169
162
 
163
+ ##### AWS EC2 tags #####
164
+
165
+ This watcher retrieves a list of Amazon EC2 instances that have a tag
166
+ with particular value using the AWS API.
167
+ It takes the following options:
168
+
169
+ * `method`: ec2tag
170
+ * `tag_name`: the name of the tag to inspect. As per the AWS docs,
171
+ this is case-sensitive.
172
+ * `tag_value`: the value to match on. Case-sensitive.
173
+
174
+ Additionally, you MUST supply `server_port_override` in the `haproxy`
175
+ section of the configuration as this watcher does not know which port
176
+ the backend service is listening on.
177
+
178
+ The following options are optional, provided the well-known `AWS_`
179
+ environment variables shown are set. If supplied, these options will
180
+ be used in preference to the `AWS_` environment variables.
181
+
182
+ * `aws_access_key_id`: AWS key or set `AWS_ACCESS_KEY_ID` in the environment.
183
+ * `aws_secret_access_key`: AWS secret key or set `AWS_SECRET_ACCESS_KEY` in the environment.
184
+ * `aws_region`: AWS region (i.e. `us-east-1`) or set `AWS_REGION` in the environment.
185
+
170
186
  #### Listing Default Servers ####
171
187
 
172
188
  You may list a number of default servers providing a service.
@@ -182,16 +198,30 @@ If you do not list any default servers, no proxy will be created. The
182
198
  `default_servers` will also be used in addition to discovered servers if the
183
199
  `keep_default_servers` option is set.
184
200
 
201
+ If you do not list any `default_servers`, and all backends for a service
202
+ disappear then the previous known backends will be used. Disable this behavior
203
+ by unsetting `use_previous_backends`.
204
+
205
+ #### The `file_output` Section ####
206
+
207
+ This section controls whether or not synapse will write out service state
208
+ to the filesystem in json format. This can be used for services that want to
209
+ use discovery information but not go through HAProxy.
210
+
211
+ * `output_directory`: the path to a directory on disk that service registrations
212
+ should be written to.
213
+
185
214
  #### The `haproxy` Section ####
186
215
 
187
216
  This section is its own hash, which should contain the following keys:
188
217
 
189
- * `port`: the port (on localhost) where HAProxy will listen for connections to the service.
218
+ * `port`: the port (on localhost) where HAProxy will listen for connections to the service. If this is omitted, only a backend stanza (and no frontend stanza) will be generated for this service; you'll need to get traffic to your service yourself via the `shared_frontend` or manual frontends in `extra_sections`
190
219
  * `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.
191
220
  * `server_options`: the haproxy options for each `server` line of the service in HAProxy config; it may be left out.
192
221
  * `frontend`: additional lines passed to the HAProxy config in the `frontend` stanza of this service
193
222
  * `backend`: additional lines passed to the HAProxy config in the `backend` stanza of this service
194
223
  * `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.
224
+ * `shared_frontend`: optional: haproxy configuration directives for a shared http frontend (see below)
195
225
 
196
226
  ### Configuring HAProxy ###
197
227
 
@@ -201,80 +231,75 @@ The `haproxy` section of the config file has the following options:
201
231
  * `config_file_path`: where Synapse will write the HAProxy config file
202
232
  * `do_writes`: whether or not the config file will be written (default to `true`)
203
233
  * `do_reloads`: whether or not Synapse will reload HAProxy (default to `true`)
234
+ * `do_socket`: whether or not Synapse will use the HAProxy socket commands to prevent reloads (default to `true`)
204
235
  * `global`: options listed here will be written into the `global` section of the HAProxy config
205
236
  * `defaults`: options listed here will be written into the `defaults` section of the HAProxy config
237
+ * `extra_sections`: additional, manually-configured `frontend`, `backend`, or `listen` stanzas
206
238
  * `bind_address`: force HAProxy to listen on this address (default is localhost)
207
- * `shared_fronted`: (OPTIONAL) additional lines passed to the HAProxy config used to configure a shared HTTP frontend (see below)
208
-
209
- 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.
239
+ * `shared_frontend`: (OPTIONAL) additional lines passed to the HAProxy config used to configure a shared HTTP frontend (see below)
240
+ * `restart_interval`: number of seconds to wait between restarts of haproxy (default: 2)
241
+ * `restart_jitter`: percentage, expressed as a float, of jitter to multiply the `restart_interval` by when determining the next
242
+ restart time. Use this to help prevent healthcheck storms when HAProxy restarts. (default: 0.0)
243
+ * `state_file_path`: full path on disk (e.g. /tmp/synapse/state.json) for caching haproxy state between reloads.
244
+ If provided, synapse will store recently seen backends at this location and can "remember" backends across both synapse and
245
+ HAProxy restarts. Any backends that are "down" in the reporter but listed in the cache will be put into HAProxy disabled (default: nil)
246
+ * `state_file_ttl`: the number of seconds that backends should be kept in the state file cache.
247
+ This only applies if `state_file_path` is provided (default: 86400)
248
+
249
+ Note that a non-default `bind_address` can be dangerous.
250
+ If you configure an `address:port` combination that is already in use on the system, haproxy will fail to start.
210
251
 
211
252
  ### HAProxy shared HTTP Frontend ###
212
253
 
213
254
  For HTTP-only services, it is not always necessary or desirable to dedicate a TCP port per service, since HAProxy can route traffic based on host headers.
214
- To support this, the optional `shared_fronted` section can be added to both the `haproxy` section and each indvidual service definition: synapse will concatenate them all into a single frontend section in the generated haproxy.cfg file.
215
- Note that synapse does not assemble the routing ACLs for you: you have to do that yourself based on your needs.
255
+ To support this, the optional `shared_frontend` section can be added to both the `haproxy` section and each indvidual service definition.
256
+ Synapse will concatenate them all into a single frontend section in the generated haproxy.cfg file.
257
+ Note that synapse does not assemble the routing ACLs for you; you have to do that yourself based on your needs.
216
258
  This is probably most useful in combination with the `service_conf_dir` directive in a case where the individual service config files are being distributed by a configuration manager such as puppet or chef, or bundled into service packages.
217
259
  For example:
218
260
 
219
261
  ```yaml
220
- {
221
- "haproxy": {
222
- "shared_frontend": [
223
- "bind 127.0.0.1:8081"
224
- ],
225
- "reload_command": "service haproxy reload",
226
- "config_file_path": "/etc/haproxy/haproxy.cfg",
227
- "socket_file_path": "/var/run/haproxy.sock",
228
- "global": [
229
- "daemon",
230
- "user haproxy",
231
- "group haproxy",
232
- "maxconn 4096",
233
- "log 127.0.0.1 local2 notice",
234
- "stats socket /var/run/haproxy.sock"
235
- ],
236
- "defaults": [
237
- "log global",
238
- "balance roundrobin"
239
- ]
240
- },
241
- "services": {
242
- "service1": {
243
- "discovery": {
244
- "method": "zookeeper",
245
- "path": "/nerve/services/service1",
246
- "hosts": [ "0.zookeeper.example.com:2181" ]
247
- },
248
- "haproxy": {
249
- "server_options": "check inter 2s rise 3 fall 2",
250
- "shared_frontend": [
251
- "acl is_service1 hdr_dom(host) -i service1.lb.example.com",
252
- "use_backend service1 if is_service1"
253
- ],
254
- "backend": [
255
- "mode http"
256
- ]
257
- }
258
- },
259
- "service2": {
260
- "discovery": {
261
- "method": "zookeeper",
262
- "path": "/nerve/services/service2",
263
- "hosts": [ "0.zookeeper.example.com:2181" ]
264
- },
265
- "haproxy": {
266
- "server_options": "check inter 2s rise 3 fall 2",
267
- "shared_frontend": [
268
- "acl is_service1 hdr_dom(host) -i service2.lb.example.com",
269
- "use_backend service2 if is_service2"
270
- ],
271
- "backend": [
272
- "mode http"
273
- ]
274
- }
275
- }
276
- }
277
- }
262
+ haproxy:
263
+ shared_frontend: "bind 127.0.0.1:8081"
264
+ reload_command: "service haproxy reload"
265
+ config_file_path: "/etc/haproxy/haproxy.cfg"
266
+ socket_file_path: "/var/run/haproxy.sock"
267
+ global:
268
+ - "daemon"
269
+ - "user haproxy"
270
+ - "group haproxy"
271
+ - "maxconn 4096"
272
+ - "log 127.0.0.1 local2 notice"
273
+ - "stats socket /var/run/haproxy.sock"
274
+ defaults:
275
+ - "log global"
276
+ - "balance roundrobin"
277
+ services:
278
+ service1:
279
+ discovery:
280
+ method: "zookeeper"
281
+ path: "/nerve/services/service1"
282
+ hosts: "0.zookeeper.example.com:2181"
283
+ haproxy:
284
+ server_options: "check inter 2s rise 3 fall 2"
285
+ shared_frontend:
286
+ - "acl is_service1 hdr_dom(host) -i service1.lb.example.com"
287
+ - "use_backend service1 if is_service1"
288
+ backend: "mode http"
289
+
290
+ service2:
291
+ discovery:
292
+ method: "zookeeper"
293
+ path: "/nerve/services/service2"
294
+ hosts: "0.zookeeper.example.com:2181"
295
+
296
+ haproxy:
297
+ server_options: "check inter 2s rise 3 fall 2"
298
+ shared_frontend:
299
+ - "acl is_service1 hdr_dom(host) -i service2.lb.example.com"
300
+ - "use_backend service2 if is_service2
301
+ backend: "mode http"
302
+
278
303
  ```
279
304
 
280
305
  This would produce an haproxy.cfg much like the following:
@@ -332,5 +357,5 @@ end
332
357
  3. Implement the `start` and `validate_discovery_opts` methods
333
358
  4. Implement whatever additional methods your discovery requires
334
359
 
335
- When your watcher detects a list of new backends, they should be written to `@backends`.
336
- You should then call `@synapse.configure` to force synapse to update the HAProxy config.
360
+ When your watcher detects a list of new backends, you should call `set_backends` to
361
+ store the new backends and update the HAProxy config.
data/lib/synapse.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "synapse/version"
2
2
  require "synapse/service_watcher/base"
3
3
  require "synapse/haproxy"
4
+ require "synapse/file_output"
4
5
  require "synapse/service_watcher"
5
6
  require "synapse/log"
6
7
 
@@ -17,9 +18,17 @@ module Synapse
17
18
  raise "specify a list of services to connect in the config" unless opts.has_key?('services')
18
19
  @service_watchers = create_service_watchers(opts['services'])
19
20
 
20
- # create the haproxy object
21
+ # create objects that need to be notified of service changes
22
+ @config_generators = []
23
+ # create the haproxy config generator, this is mandatory
21
24
  raise "haproxy config section is missing" unless opts.has_key?('haproxy')
22
- @haproxy = Haproxy.new(opts['haproxy'])
25
+ @config_generators << Haproxy.new(opts['haproxy'])
26
+
27
+ # possibly create a file manifestation for services that do not
28
+ # want to communicate via haproxy, e.g. cassandra
29
+ if opts.has_key?('file_output')
30
+ @config_generators << FileOutput.new(opts['file_output'])
31
+ end
23
32
 
24
33
  # configuration is initially enabled to configure on first loop
25
34
  @config_updated = true
@@ -47,10 +56,15 @@ module Synapse
47
56
 
48
57
  if @config_updated
49
58
  @config_updated = false
50
- log.info "synapse: regenerating haproxy config"
51
- @haproxy.update_config(@service_watchers)
52
- else
53
- sleep 1
59
+ @config_generators.each do |config_generator|
60
+ log.info "synapse: configuring #{config_generator.name}"
61
+ config_generator.update_config(@service_watchers)
62
+ end
63
+ end
64
+
65
+ sleep 1
66
+ @config_generators.each do |config_generator|
67
+ config_generator.tick(@service_watchers)
54
68
  end
55
69
 
56
70
  loops += 1
@@ -0,0 +1,57 @@
1
+ require 'synapse/log'
2
+ require 'fileutils'
3
+ require 'tempfile'
4
+
5
+ module Synapse
6
+ class FileOutput
7
+ include Logging
8
+ attr_reader :opts, :name
9
+
10
+ def initialize(opts)
11
+ unless opts.has_key?("output_directory")
12
+ raise ArgumentError, "flat file generation requires an output_directory key"
13
+ end
14
+
15
+ begin
16
+ FileUtils.mkdir_p(opts['output_directory'])
17
+ rescue SystemCallError => err
18
+ raise ArgumentError, "provided output directory #{opts['output_directory']} is not present or creatable"
19
+ end
20
+
21
+ @opts = opts
22
+ @name = 'file_output'
23
+ end
24
+
25
+ def tick(watchers)
26
+ end
27
+
28
+ def update_config(watchers)
29
+ watchers.each do |watcher|
30
+ write_backends_to_file(watcher.name, watcher.backends)
31
+ end
32
+ end
33
+
34
+ def write_backends_to_file(service_name, new_backends)
35
+ data_path = File.join(@opts['output_directory'], "#{service_name}.json")
36
+ begin
37
+ old_backends = JSON.load(File.read(data_path))
38
+ rescue Errno::ENOENT
39
+ old_backends = nil
40
+ end
41
+
42
+ if old_backends == new_backends
43
+ # Prevent modifying the file unless something has actually changed
44
+ # This way clients can set watches on this file and update their
45
+ # internal state only when the smartstack state has actually changed
46
+ return false
47
+ else
48
+ # Atomically write new sevice configuration file
49
+ temp_path = File.join(@opts['output_directory'],
50
+ ".#{service_name}.json.tmp")
51
+ File.open(temp_path, 'w', 0644) {|f| f.write(new_backends.to_json)}
52
+ FileUtils.mv(temp_path, data_path)
53
+ return true
54
+ end
55
+ end
56
+ end
57
+ end