centurion 1.4.2 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -25,7 +25,7 @@ Commercial Docker Registry Providers:
25
25
  Open-source:
26
26
  - The [Docker registry](https://github.com/dotcloud/docker-registry) project,
27
27
  built and maintained by Docker. You host this yourself.
28
- - (*NEW!*) [Dogestry](https://github.com/newrelic-forks/dogestry) is an
28
+ - (*NEW!*) [Dogestry](https://github.com/dogestry/dogestry) is an
29
29
  s3-backed Docker registry alternative that removes the requirement to set up
30
30
  a centralized registry service or host anything yourself.
31
31
 
@@ -184,6 +184,23 @@ This is currently pretty inflexible in that the hostname will now be the same
184
184
  on all of your hosts. A possible future addition is the ability to pass
185
185
  a block to be evaluated on each host.
186
186
 
187
+ ### CGroup Resource Constraints
188
+
189
+ Limits on memory and CPU can be specified with the `memory` and `cpu_shares`
190
+ settings. Both of these expect a 64-bit integer describing the number of
191
+ bytes, and the number of CPU shares, respectively.
192
+
193
+ For example, to limit the memory to 1G, and the cpu time slice to half the
194
+ normal length, include the following:
195
+
196
+ ```ruby
197
+ memory 1024000000
198
+ cpu_shares 512
199
+ ```
200
+
201
+ For more information on Docker's CGroup limits see [the Docker
202
+ docs](https://docs.docker.com/reference/run/#runtime-constraints-on-cpu-and-memory).
203
+
187
204
  ### Interpolation
188
205
 
189
206
  Currently there is one special string for interpolation that can be added to
@@ -317,7 +334,7 @@ $ bundle exec centurion -p radio-radio -e staging -a list
317
334
 
318
335
  Centurion needs to have access to some registry in order to pull images to
319
336
  remote Docker servers. This needs to be either a hosted registry (public or
320
- private), or [Dogestry](https://github.com/newrelic-forks/dogestry).
337
+ private), or [Dogestry](https://github.com/dogestry/dogestry).
321
338
 
322
339
  #### Access to the registry
323
340
 
@@ -340,7 +357,7 @@ These correspond to the following settings:
340
357
  Centurion normally uses the built-in registry support in the Docker daemon to
341
358
  handle pushing and pulling images. But Centurion also has the ability to use
342
359
  external tooling to support hosting your registry on Amazon S3. That tooling is
343
- from a project called [Dogestry](https://github.com/newrelic-forks/dogestry).
360
+ from a project called [Dogestry](https://github.com/dogestry/dogestry).
344
361
  We have recently improved that tooling substantially in coordination with the
345
362
  Centurion support.
346
363
 
@@ -349,7 +366,7 @@ with Amazon S3 to provide reliable hosting of images. Setting Centurion up to
349
366
  use Dogestry is pretty trivial:
350
367
 
351
368
  1. Install Dogestry binaries on the client from which Dogestry is run.
352
- Binaries are provided in the [GitHub release](https://github.com/newrelic-forks/dogestry).
369
+ Binaries are provided in the [GitHub release](https://github.com/dogestry/dogestry).
353
370
  1. Add the settings necessary to get Centurion to pull from Dogestry. A config
354
371
  example is provided below:
355
372
 
@@ -365,6 +382,24 @@ namespace :environment do
365
382
  end
366
383
  ```
367
384
 
385
+ Development
386
+ -----------
387
+
388
+ Sometimes when you're doing development you want to try out some configuration
389
+ settings in environment variables that aren't in the config yet. Or perhaps you
390
+ want to override existing settings to test with. You can provide the
391
+ `--override-env` command line flag with some overrides or new variables to set.
392
+ Here's how to use it:
393
+
394
+ ```bash
395
+ $ centurion -e development -a deploy -p radio-radio --override-env=SERVICE_PORT=8080,NAME=radio
396
+ ```
397
+
398
+ Centurion is aimed at repeatable deployments so we don't recommend that you use
399
+ this functionality for production deployments. It will work, but it means that
400
+ the config is not the whole source of truth for your container configuration.
401
+ Caveat emptor.
402
+
368
403
  Future Additions
369
404
  ----------------
370
405
 
@@ -388,6 +423,7 @@ Pull requests should:
388
423
  * Have a description that explains the need for the changes
389
424
  * Include tests!
390
425
  * Not break the public API
426
+ * Add yourself to the CONTRIBUTORS file. I might forget.
391
427
 
392
428
  If you are simply looking to contribute to the project, taking on one of the
393
429
  items in the "Future Additions" section above would be a great place to start.
@@ -400,4 +436,4 @@ patents, and ideas in that code in our products if we so choose. You also agree
400
436
  the code is provided as-is and you provide no warranties as to its fitness or
401
437
  correctness for any purpose
402
438
 
403
- Copyright (c) 2014 New Relic, Inc. All rights reserved.
439
+ Copyright (c) 2014-2015 New Relic, Inc. All rights reserved.
@@ -22,16 +22,17 @@ Dir.glob(File.join(task_dir, '*.rake')).each { |file| load file }
22
22
  require 'trollop'
23
23
 
24
24
  opts = Trollop::options do
25
- opt :project, 'project (blog, forums...)', type: String, required: true, short: '-p'
26
- opt :environment, "environment (production, staging...)", type: String, required: true, short: '-e'
27
- opt :action, 'action (deploy, list...)', type: String, default: 'list', short: '-a'
28
- opt :image, 'image (yourco/project...)', type: String, required: false, short: '-i'
29
- opt :tag, 'tag (latest...)', type: String, required: false, short: '-t'
30
- opt :hosts, 'hosts, comma separated', type: String, required: false, short: '-h'
31
- opt :docker_path, 'path to docker executable (default: docker)', type: String, default: 'docker', short: '-d'
32
- opt :no_pull, 'Skip the pull_image step', type: :flag, default: false, short: '-n'
33
- opt :registry_user, 'user for registry auth (default: nil)', type: String, default: nil, short: :none
34
- opt :registry_password,'password for registry auth (default: nil)', type: String, default: nil, short: :none
25
+ opt :project, 'project (blog, forums...)', type: String, required: true, short: '-p'
26
+ opt :environment, "environment (production, staging...)", type: String, required: true, short: '-e'
27
+ opt :action, 'action (deploy, list...)', type: String, default: 'list', short: '-a'
28
+ opt :image, 'image (yourco/project...)', type: String, short: '-i'
29
+ opt :tag, 'tag (latest...)', type: String, short: '-t'
30
+ opt :hosts, 'hosts, comma separated', type: String, short: '-h'
31
+ opt :docker_path, 'path to docker executable', type: String, default: 'docker', short: '-d'
32
+ opt :no_pull, 'Skip the pull_image step', type: :flag, default: false, short: '-n'
33
+ opt :registry_user, 'user for registry auth', type: String, short: :none
34
+ opt :registry_password,'password for registry auth', type: String, short: :none
35
+ opt :override_env, 'override environment variables, comma separated', type: String
35
36
  end
36
37
 
37
38
  set_current_environment(opts[:environment].to_sym)
@@ -55,6 +56,14 @@ set :image, opts[:image] if opts[:image]
55
56
  set :tag, opts[:tag] if opts[:tag]
56
57
  set :hosts, opts[:hosts].split(",") if opts[:hosts]
57
58
 
59
+ # Override environment variables when specified
60
+ if opts[:override_env]
61
+ opts[:override_env].split(',').each do |envvar|
62
+ key, value = envvar.split('=')
63
+ env_vars(key => value)
64
+ end
65
+ end
66
+
58
67
  # Default tag should be "latest"
59
68
  set :tag, 'latest' unless any?(:tag)
60
69
  set :docker_registry, Centurion::DockerRegistry::OFFICIAL_URL unless any?(:docker_registry)
@@ -4,6 +4,8 @@ module Centurion; end
4
4
 
5
5
  module Centurion::Deploy
6
6
  FAILED_CONTAINER_VALIDATION = 100
7
+ INVALID_CGROUP_CPUSHARES_VALUE = 101
8
+ INVALID_CGROUP_MEMORY_VALUE = 102
7
9
 
8
10
  def stop_containers(target_server, port_bindings, timeout = 30)
9
11
  public_port = public_port_for(port_bindings)
@@ -71,6 +73,17 @@ module Centurion::Deploy
71
73
  false
72
74
  end
73
75
 
76
+ def is_a_uint64?(value)
77
+ result = false
78
+ if !value.is_a? Integer
79
+ return result
80
+ end
81
+ if value < 0 || value > 0xFFFFFFFFFFFFFFFF
82
+ return result
83
+ end
84
+ return true
85
+ end
86
+
74
87
  def wait_for_load_balancer_check_interval
75
88
  sleep(fetch(:rolling_deploy_check_interval, 5))
76
89
  end
@@ -87,13 +100,26 @@ module Centurion::Deploy
87
100
  end
88
101
  end
89
102
 
90
- def container_config_for(target_server, image_id, port_bindings=nil, env_vars=nil, volumes=nil, command=nil, hostname=nil)
103
+ def container_config_for(target_server, image_id, port_bindings=nil, env_vars=nil, volumes=nil, command=nil, memory=nil, cpu_shares=nil)
104
+
105
+ if memory && ! is_a_uint64?(memory)
106
+ error "Invalid value for CGroup memory constraint: #{memory}, value must be a between 0 and 18446744073709551615"
107
+ exit(INVALID_CGROUP_MEMORY_VALUE)
108
+ end
109
+
110
+ if cpu_shares && ! is_a_uint64?(cpu_shares)
111
+ error "Invalid value for CGroup CPU constraint: #{cpu_shares}, value must be between 0 and 18446744073709551615"
112
+ exit(INVALID_CGROUP_CPUSHARES_VALUE)
113
+ end
114
+
91
115
  container_config = {
92
116
  'Image' => image_id,
93
117
  'Hostname' => fetch(:container_hostname, target_server.hostname),
94
118
  }
95
119
 
96
120
  container_config.merge!('Cmd' => command) if command
121
+ container_config.merge!('Memory' => memory) if memory
122
+ container_config.merge!('CpuShares' => cpu_shares) if cpu_shares
97
123
 
98
124
  if port_bindings
99
125
  container_config['ExposedPorts'] ||= {}
@@ -119,8 +145,8 @@ module Centurion::Deploy
119
145
  container_config
120
146
  end
121
147
 
122
- def start_new_container(target_server, image_id, port_bindings, volumes, env_vars=nil, command=nil)
123
- container_config = container_config_for(target_server, image_id, port_bindings, env_vars, volumes, command)
148
+ def start_new_container(target_server, image_id, port_bindings, volumes, env_vars=nil, command=nil, memory=nil, cpu_shares=nil)
149
+ container_config = container_config_for(target_server, image_id, port_bindings, env_vars, volumes, command, memory, cpu_shares)
124
150
  start_container_with_config(target_server, volumes, port_bindings, container_config)
125
151
  end
126
152
 
@@ -20,6 +20,14 @@ module Centurion::DeployDSL
20
20
  set(:hosts, current)
21
21
  end
22
22
 
23
+ def memory(memory)
24
+ set(:memory, memory)
25
+ end
26
+
27
+ def cpu_shares(cpu_shares)
28
+ set(:cpu_shares, cpu_shares)
29
+ end
30
+
23
31
  def command(command)
24
32
  set(:command, command)
25
33
  end
@@ -34,10 +34,18 @@ class Centurion::DockerServer
34
34
 
35
35
  def find_containers_by_public_port(public_port, type='tcp')
36
36
  ps.select do |container|
37
- if container['Ports']
38
- container['Ports'].find do |port|
39
- port['PublicPort'] == public_port.to_i && port['Type'] == type
40
- end
37
+ next unless container && container['Ports']
38
+ container['Ports'].find do |port|
39
+ port['PublicPort'] == public_port.to_i && port['Type'] == type
40
+ end
41
+ end
42
+ end
43
+
44
+ def find_containers_by_name(wanted_name)
45
+ ps.select do |container|
46
+ next unless container && container['Names']
47
+ container['Names'].find do |name|
48
+ name =~ /\A\/#{wanted_name}(-[a-f0-9]{7})?\Z/
41
49
  end
42
50
  end
43
51
  end
@@ -1,3 +1,3 @@
1
1
  module Centurion
2
- VERSION = '1.4.2'
2
+ VERSION = '1.5.0'
3
3
  end
@@ -91,7 +91,9 @@ namespace :deploy do
91
91
  fetch(:port_bindings),
92
92
  fetch(:binds),
93
93
  fetch(:env_vars),
94
- fetch(:command)
94
+ fetch(:command),
95
+ fetch(:memory),
96
+ fetch(:cpu_shares)
95
97
  )
96
98
  end
97
99
  end
@@ -118,7 +120,9 @@ namespace :deploy do
118
120
  fetch(:port_bindings),
119
121
  fetch(:binds),
120
122
  fetch(:env_vars),
121
- fetch(:command)
123
+ fetch(:command),
124
+ fetch(:memory),
125
+ fetch(:cpu_shares)
122
126
  )
123
127
 
124
128
  skip_ports = Array(fetch(:rolling_deploy_skip_ports, [])).map(&:to_s)
@@ -1,3 +1,4 @@
1
+ require 'spec_helper'
1
2
  require 'centurion/deploy'
2
3
  require 'centurion/deploy_dsl'
3
4
  require 'centurion/logging'
@@ -217,6 +218,36 @@ describe Centurion::Deploy do
217
218
  expect(config['Cmd']).to eq(command)
218
219
  end
219
220
  end
221
+
222
+ context 'when cgroup limits are specified' do
223
+ let(:memory) { 10000000 }
224
+ let(:cpu_shares) { 1234 }
225
+
226
+ before do
227
+ allow(test_deploy).to receive(:error) # Make sure we don't have red output in tests
228
+ end
229
+
230
+ it 'sets cgroup limits in the config' do
231
+ config = test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, memory, cpu_shares)
232
+
233
+ expect(config).to be_a(Hash)
234
+ expect(config.keys).to match_array(%w{ Hostname Image Memory CpuShares })
235
+ expect(config['Memory']).to eq(10000000)
236
+ expect(config['CpuShares']).to eq(1234)
237
+ end
238
+
239
+ it 'throws a fatal error value for Cgroup Memory limit is invalid' do
240
+ expect { config = test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, "I like pie", cpu_shares) }.to terminate.with_code(102)
241
+ expect { test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, -100, cpu_shares) }.to terminate.with_code(102)
242
+ expect { test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, 0xFFFFFFFFFFFFFFFFFF, cpu_shares) }.to terminate.with_code(102)
243
+ end
244
+
245
+ it 'throws a fatal error value for Cgroup CPU limit is invalid' do
246
+ expect { test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, memory, "I like pie") }.to terminate.with_code(101)
247
+ expect { test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, memory, -100) }.to terminate.with_code(101)
248
+ expect { test_deploy.container_config_for(server, image_id, port_bindings, env, volumes, command, memory, 0xFFFFFFFFFFFFFFFFFF) }.to terminate.with_code(101)
249
+ end
250
+ end
220
251
  end
221
252
 
222
253
  describe '#start_container_with_config' do
@@ -359,7 +390,7 @@ describe Centurion::Deploy do
359
390
  let(:command) { ['/bin/echo', 'hi'] }
360
391
 
361
392
  it 'configures the container' do
362
- expect(test_deploy).to receive(:container_config_for).with(server, 'image_id', bindings, nil, {}, nil).once
393
+ expect(test_deploy).to receive(:container_config_for).with(server, 'image_id', bindings, nil, {}, nil, nil, nil).once
363
394
 
364
395
  allow(test_deploy).to receive(:start_container_with_config)
365
396
 
@@ -417,16 +448,18 @@ describe Centurion::Deploy do
417
448
  end
418
449
 
419
450
  describe '#launch_console' do
420
- let(:bindings) { {'80/tcp'=>[{'HostIp'=>'0.0.0.0', 'HostPort'=>'80'}]} }
421
- let(:volumes) { nil }
422
- let(:env) { nil }
423
- let(:command) { nil }
451
+ let(:bindings) { {'80/tcp'=>[{'HostIp'=>'0.0.0.0', 'HostPort'=>'80'}]} }
452
+ let(:volumes) { nil }
453
+ let(:env) { nil }
454
+ let(:command) { nil }
455
+ let(:memory) { nil }
456
+ let(:cpu_shares) { nil }
424
457
 
425
458
  it 'configures the container' do
426
- expect(test_deploy).to receive(:container_config_for).with(server, 'image_id', bindings, env, volumes, command).once
459
+ expect(test_deploy).to receive(:container_config_for).with(server, 'image_id', bindings, env, volumes, command, memory, cpu_shares).once
427
460
  allow(test_deploy).to receive(:start_container_with_config)
428
461
 
429
- test_deploy.start_new_container(server, 'image_id', bindings, volumes, env, command)
462
+ test_deploy.start_new_container(server, 'image_id', bindings, volumes, env, command, memory, cpu_shares)
430
463
  end
431
464
 
432
465
  it 'augments the container_config' do
@@ -2,9 +2,21 @@ require 'spec_helper'
2
2
  require 'centurion/docker_server'
3
3
 
4
4
  describe Centurion::DockerServer do
5
- let(:host) { 'host1' }
5
+ let(:host) { 'host1' }
6
6
  let(:docker_path) { 'docker' }
7
7
  let(:server) { Centurion::DockerServer.new(host, docker_path) }
8
+ let(:container) {
9
+ {
10
+ 'Command' => '/bin/bash',
11
+ 'Created' => 1414797234,
12
+ 'Id' => '28970c706db0f69716af43527ed926acbd82581e1cef5e4e6ff152fce1b79972',
13
+ 'Image' => 'centurion-test:latest',
14
+ 'Names' => ['/centurion-783aac4'],
15
+ 'Ports' => [{'PrivatePort'=>80, 'Type'=>'tcp', 'IP'=>'0.0.0.0', 'PublicPort'=>23235}],
16
+ 'Status' => 'Up 3 days'
17
+ }
18
+ }
19
+ let(:ps) { [ container, {}, nil ] }
8
20
 
9
21
  it 'knows its hostname' do
10
22
  expect(server.hostname).to eq('host1')
@@ -40,4 +52,26 @@ describe Centurion::DockerServer do
40
52
  allow(server).to receive(:ps).and_return(image_names.map {|name| { 'Image' => name } })
41
53
  expect(server.current_tags_for('target')).to eq(%w[latest production])
42
54
  end
55
+
56
+ context 'finding containers' do
57
+ before do
58
+ allow(server).to receive(:ps).and_return(ps)
59
+ end
60
+
61
+ it 'finds containers by port' do
62
+ expect(server.find_containers_by_public_port(23235, 'tcp')).to eq([container])
63
+ end
64
+
65
+ it 'only returns correct matches by port' do
66
+ expect(server.find_containers_by_public_port(1234, 'tcp')).to be_empty
67
+ end
68
+
69
+ it 'finds containers by name' do
70
+ expect(server.find_containers_by_name('centurion')).to eq([container])
71
+ end
72
+
73
+ it 'only returns correct matches by name' do
74
+ expect(server.find_containers_by_name('fbomb')).to be_empty
75
+ end
76
+ end
43
77
  end
@@ -0,0 +1,38 @@
1
+ # https://gist.github.com/mmasashi/58bd7e2668836a387856
2
+ RSpec::Matchers.define :terminate do |code|
3
+ actual = nil
4
+
5
+ def supports_block_expectations?
6
+ true
7
+ end
8
+
9
+ match do |block|
10
+ begin
11
+ block.call
12
+ rescue SystemExit => e
13
+ actual = e.status
14
+ end
15
+ actual and actual == status_code
16
+ end
17
+
18
+ chain :with_code do |status_code|
19
+ @status_code = status_code
20
+ end
21
+
22
+ failure_message do |block|
23
+ "expected block to call exit(#{status_code}) but exit" +
24
+ (actual.nil? ? " not called" : "(#{actual}) was called")
25
+ end
26
+
27
+ failure_message_when_negated do |block|
28
+ "expected block not to call exit(#{status_code})"
29
+ end
30
+
31
+ description do
32
+ "expect block to call exit(#{status_code})"
33
+ end
34
+
35
+ def status_code
36
+ @status_code ||= 0
37
+ end
38
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: centurion
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -21,7 +21,7 @@ authors:
21
21
  autorequire:
22
22
  bindir: bin
23
23
  cert_chain: []
24
- date: 2015-01-26 00:00:00.000000000 Z
24
+ date: 2015-02-24 00:00:00.000000000 Z
25
25
  dependencies:
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: trollop
@@ -208,6 +208,7 @@ files:
208
208
  - spec/logging_spec.rb
209
209
  - spec/spec_helper.rb
210
210
  - spec/support/matchers/capistrano_dsl_matchers.rb
211
+ - spec/support/matchers/exit_code_matches.rb
211
212
  homepage: https://github.com/newrelic/centurion
212
213
  licenses:
213
214
  - MIT
@@ -249,3 +250,4 @@ test_files:
249
250
  - spec/logging_spec.rb
250
251
  - spec/spec_helper.rb
251
252
  - spec/support/matchers/capistrano_dsl_matchers.rb
253
+ - spec/support/matchers/exit_code_matches.rb