kubernetes-deploy 0.18.1 → 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +20 -4
- data/exe/kubernetes-deploy +8 -2
- data/exe/kubernetes-restart +4 -1
- data/kubernetes-deploy.gemspec +1 -1
- data/lib/kubernetes-deploy/bindings_parser.rb +23 -1
- data/lib/kubernetes-deploy/deploy_task.rb +5 -2
- data/lib/kubernetes-deploy/kubernetes_resource.rb +6 -2
- data/lib/kubernetes-deploy/resource_watcher.rb +37 -11
- data/lib/kubernetes-deploy/restart_task.rb +3 -2
- data/lib/kubernetes-deploy/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 99f56eb5da1d7f17885cff3d19787b45a1484504
|
4
|
+
data.tar.gz: e2919b899b313e2c4be0af2b7731816f9c8336de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 103546e46a2e476e1cc1ae339fcce8362fd199d144d890d1b895ed46848181ba822dcb713eb672581f625b25e3992c4ea9439e59888c4b3f0b74b44d156bba50
|
7
|
+
data.tar.gz: 7aeb474292e52f2b3e9ead4f038732804949c253da2119993c89d64e1357d7920c2031bce184821cb711c091ed54f8b6edf181c47d72b9242c3d2e0c972d7355
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
### 0.19.0
|
2
|
+
*Features*
|
3
|
+
- Added `--max-watch-seconds=seconds` to kubernetes-restart and kubernetes-deploy. When set
|
4
|
+
a timeout error is raised if it takes longer than _seconds_ for any resource to deploy.
|
5
|
+
- Adds YAML and JSON file reference support to the kubernetes-deploy `--bindings` argument ([#269](https://github.com/Shopify/kubernetes-deploy/pull/269))
|
6
|
+
|
7
|
+
*Enhancements*
|
8
|
+
- Prune resource quotas ([#264](https://github.com/Shopify/kubernetes-deploy/pull/264/files))
|
9
|
+
|
10
|
+
*Bug Fixes*
|
11
|
+
- Update gemspec to reflect need for ActiveSupport >= 5.0([#270](https://github.com/Shopify/kubernetes-deploy/pull/270))
|
12
|
+
|
1
13
|
### 0.18.1
|
2
14
|
*Enhancements*
|
3
15
|
- Change the way the resource watcher fetches resources to make it more efficient for large deploys. Deploys with hundreds of resources are expected to see a measurable performance improvement from this change. ([#251](https://github.com/Shopify/kubernetes-deploy/pull/251))
|
data/README.md
CHANGED
@@ -107,9 +107,8 @@ Refer to `kubernetes-deploy --help` for the authoritative set of options.
|
|
107
107
|
- `--template-dir=DIR`: Used to set the deploy directory. Set `$ENVIRONMENT` instead to use `config/deploy/$ENVIRONMENT`.
|
108
108
|
- `--bindings=BINDINGS`: Makes additional variables available to your ERB templates. For example, `kubernetes-deploy my-app cluster1 --bindings=color=blue,size=large` will expose `color` and `size`.
|
109
109
|
- `--no-prune`: Skips pruning of resources that are no longer in your Kubernetes template set. Not recommended, as it allows your namespace to accumulate cruft that is not reflected in your deploy directory.
|
110
|
-
|
111
|
-
|
112
|
-
|
110
|
+
- `--max-watch-seconds=seconds`: Raise a timeout error if it takes longer than _seconds_ for any
|
111
|
+
resource to deploy.
|
113
112
|
|
114
113
|
|
115
114
|
### Using templates and variables
|
@@ -121,7 +120,24 @@ All templates must be YAML formatted. You can also use ERB. The following local
|
|
121
120
|
* `current_sha`: The value of `$REVISION`
|
122
121
|
* `deployment_id`: A randomly generated identifier for the deploy. Useful for creating unique names for task-runner pods (e.g. a pod that runs rails migrations at the beginning of deploys).
|
123
122
|
|
124
|
-
You can add additional variables using the `--bindings=BINDINGS` option which can be formated as comma separated
|
123
|
+
You can add additional variables using the `--bindings=BINDINGS` option which can be formated as comma separated string, JSON string or path to a JSON or YAML file. Complex JSON or YAML data will be converted to a Hash for use in templates. To load a file the argument should include the relative file path prefixed with an `@` sign. An argument error will be raised if the string argument cannot be parsed, the referenced file does not include a valid extension (`.json`, `.yaml` or `.yml`) or the referenced file does not exist.
|
124
|
+
|
125
|
+
#### Bindings examples
|
126
|
+
|
127
|
+
```
|
128
|
+
# Comma separated string. Exposes, 'color' and 'size'
|
129
|
+
$ kubernetes-deploy my-app cluster1 --bindings=color=blue,size=large
|
130
|
+
|
131
|
+
# JSON string. Exposes, 'color' and 'size'
|
132
|
+
$ kubernetes-deploy my-app cluster1 --bindings='{"color":"blue","size":"large"}'
|
133
|
+
|
134
|
+
# Load JSON file from ./config
|
135
|
+
$ kubernetes-deploy my-app cluster1 --bindings='@config/production.json'
|
136
|
+
|
137
|
+
# Load YAML file from ./config (.yaml or .yml supported)
|
138
|
+
$ kubernetes-deploy my-app cluster1 --bindings='@config/production.yaml'
|
139
|
+
```
|
140
|
+
|
125
141
|
|
126
142
|
#### Using partials
|
127
143
|
|
data/exe/kubernetes-deploy
CHANGED
@@ -10,10 +10,11 @@ allow_protected_ns = false
|
|
10
10
|
prune = true
|
11
11
|
bindings = {}
|
12
12
|
verbose_log_prefix = false
|
13
|
+
max_watch_seconds = nil
|
13
14
|
|
14
15
|
ARGV.options do |opts|
|
15
16
|
opts.on("--bindings=BINDINGS", "Expose additional variables to ERB templates " \
|
16
|
-
"(format: k1=v1,k2=v2 or JSON)") do |binds|
|
17
|
+
"(format: k1=v1,k2=v2, JSON string or file (JSON or YAML) path prefixed by '@')") do |binds|
|
17
18
|
bindings = KubernetesDeploy::BindingsParser.parse(binds)
|
18
19
|
end
|
19
20
|
|
@@ -23,6 +24,10 @@ ARGV.options do |opts|
|
|
23
24
|
opts.on("--no-prune", "Disable deletion of resources that do not appear in the template dir") { prune = false }
|
24
25
|
opts.on("--template-dir=DIR", "Set the template dir (default: config/deploy/$ENVIRONMENT)") { |v| template_dir = v }
|
25
26
|
opts.on("--verbose-log-prefix", "Add [context][namespace] to the log prefix") { verbose_log_prefix = true }
|
27
|
+
opts.on("--max-watch-seconds=seconds",
|
28
|
+
"Timeout error is raised if it takes longer than the specified number of seconds") do |t|
|
29
|
+
max_watch_seconds = t.to_i
|
30
|
+
end
|
26
31
|
|
27
32
|
opts.on_tail("-h", "--help", "Print this help") do
|
28
33
|
puts opts
|
@@ -60,7 +65,8 @@ runner = KubernetesDeploy::DeployTask.new(
|
|
60
65
|
current_sha: revision,
|
61
66
|
template_dir: template_dir,
|
62
67
|
bindings: bindings,
|
63
|
-
logger: logger
|
68
|
+
logger: logger,
|
69
|
+
max_watch_seconds: max_watch_seconds
|
64
70
|
)
|
65
71
|
|
66
72
|
begin
|
data/exe/kubernetes-restart
CHANGED
@@ -7,8 +7,10 @@ require 'kubernetes-deploy'
|
|
7
7
|
require 'kubernetes-deploy/restart_task'
|
8
8
|
|
9
9
|
raw_deployments = nil
|
10
|
+
max_watch_seconds = nil
|
10
11
|
ARGV.options do |opts|
|
11
12
|
opts.on("--deployments=LIST") { |v| raw_deployments = v.split(",") }
|
13
|
+
opts.on("--max-watch-seconds=seconds") { |t| max_watch_seconds = t.to_i }
|
12
14
|
opts.parse!
|
13
15
|
end
|
14
16
|
|
@@ -16,7 +18,8 @@ namespace = ARGV[0]
|
|
16
18
|
context = ARGV[1]
|
17
19
|
logger = KubernetesDeploy::FormattedLogger.build(namespace, context)
|
18
20
|
|
19
|
-
restart = KubernetesDeploy::RestartTask.new(namespace: namespace, context: context, logger: logger
|
21
|
+
restart = KubernetesDeploy::RestartTask.new(namespace: namespace, context: context, logger: logger,
|
22
|
+
max_watch_seconds: max_watch_seconds)
|
20
23
|
begin
|
21
24
|
restart.perform!(raw_deployments)
|
22
25
|
rescue KubernetesDeploy::DeploymentTimeoutError
|
data/kubernetes-deploy.gemspec
CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.require_paths = ["lib"]
|
24
24
|
|
25
25
|
spec.required_ruby_version = '>= 2.3.0'
|
26
|
-
spec.add_dependency "activesupport", ">=
|
26
|
+
spec.add_dependency "activesupport", ">= 5.0"
|
27
27
|
spec.add_dependency "kubeclient", "~> 2.4"
|
28
28
|
spec.add_dependency "rest-client", ">= 1.7" # Minimum required by kubeclient. Remove when kubeclient releases v3.0.
|
29
29
|
spec.add_dependency "googleauth", ">= 0.5"
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'json'
|
3
|
+
require 'yaml'
|
3
4
|
require 'csv'
|
4
5
|
|
5
6
|
module KubernetesDeploy
|
@@ -7,7 +8,7 @@ module KubernetesDeploy
|
|
7
8
|
extend self
|
8
9
|
|
9
10
|
def parse(string)
|
10
|
-
bindings = parse_json(string) || parse_csv(string)
|
11
|
+
bindings = parse_file(string) || parse_json(string) || parse_csv(string)
|
11
12
|
|
12
13
|
unless bindings
|
13
14
|
raise ArgumentError, "Failed to parse bindings."
|
@@ -18,6 +19,27 @@ module KubernetesDeploy
|
|
18
19
|
|
19
20
|
private
|
20
21
|
|
22
|
+
def parse_file(string)
|
23
|
+
return unless string =~ /\A@/
|
24
|
+
|
25
|
+
begin
|
26
|
+
file_path = string.gsub(/\A@/, '')
|
27
|
+
|
28
|
+
case File.extname(file_path)
|
29
|
+
when '.json'
|
30
|
+
bindings = parse_json(File.read(file_path))
|
31
|
+
when '.yaml', '.yml'
|
32
|
+
bindings = YAML.load(File.read(file_path))
|
33
|
+
else
|
34
|
+
raise ArgumentError, "Supplied file does not appear to be JSON or YAML"
|
35
|
+
end
|
36
|
+
|
37
|
+
bindings
|
38
|
+
rescue Errno::ENOENT
|
39
|
+
raise ArgumentError, "Supplied file does not exist: #{string}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
21
43
|
def parse_json(string)
|
22
44
|
bindings = JSON.parse(string)
|
23
45
|
|
@@ -68,6 +68,7 @@ module KubernetesDeploy
|
|
68
68
|
core/v1/ConfigMap
|
69
69
|
core/v1/Pod
|
70
70
|
core/v1/Service
|
71
|
+
core/v1/ResourceQuota
|
71
72
|
batch/v1/Job
|
72
73
|
extensions/v1beta1/DaemonSet
|
73
74
|
extensions/v1beta1/Deployment
|
@@ -88,13 +89,15 @@ module KubernetesDeploy
|
|
88
89
|
|
89
90
|
NOT_FOUND_ERROR = 'NotFound'
|
90
91
|
|
91
|
-
def initialize(namespace:, context:, current_sha:, template_dir:, logger:, kubectl_instance: nil, bindings: {}
|
92
|
+
def initialize(namespace:, context:, current_sha:, template_dir:, logger:, kubectl_instance: nil, bindings: {},
|
93
|
+
max_watch_seconds: nil)
|
92
94
|
@namespace = namespace
|
93
95
|
@context = context
|
94
96
|
@current_sha = current_sha
|
95
97
|
@template_dir = File.expand_path(template_dir)
|
96
98
|
@logger = logger
|
97
99
|
@kubectl = kubectl_instance
|
100
|
+
@max_watch_seconds = max_watch_seconds
|
98
101
|
@renderer = KubernetesDeploy::Renderer.new(
|
99
102
|
current_sha: @current_sha,
|
100
103
|
template_dir: @template_dir,
|
@@ -376,7 +379,7 @@ module KubernetesDeploy
|
|
376
379
|
|
377
380
|
if verify
|
378
381
|
watcher = ResourceWatcher.new(resources: resources, sync_mediator: @sync_mediator,
|
379
|
-
logger: @logger, deploy_started_at: deploy_started_at)
|
382
|
+
logger: @logger, deploy_started_at: deploy_started_at, timeout: @max_watch_seconds)
|
380
383
|
watcher.run(record_summary: record_summary)
|
381
384
|
end
|
382
385
|
end
|
@@ -158,9 +158,13 @@ module KubernetesDeploy
|
|
158
158
|
@debug_info_synced = true
|
159
159
|
end
|
160
160
|
|
161
|
-
def debug_message
|
161
|
+
def debug_message(cause = nil, info_hash = {})
|
162
162
|
helpful_info = []
|
163
|
-
if
|
163
|
+
if cause == :gave_up
|
164
|
+
helpful_info << ColorizedString.new("#{id}: GLOBAL WATCH TIMEOUT (#{info_hash[:timeout]} seconds)").yellow
|
165
|
+
helpful_info << "If you expected it to take longer than #{info_hash[:timeout]} seconds for your deploy"\
|
166
|
+
" to roll out, increase --max-watch-seconds."
|
167
|
+
elsif deploy_failed?
|
164
168
|
helpful_info << ColorizedString.new("#{id}: FAILED").red
|
165
169
|
helpful_info << failure_message if failure_message.present?
|
166
170
|
elsif deploy_timed_out?
|
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module KubernetesDeploy
|
3
3
|
class ResourceWatcher
|
4
|
-
def initialize(resources:, sync_mediator:, logger:, deploy_started_at: Time.now.utc,
|
4
|
+
def initialize(resources:, sync_mediator:, logger:, deploy_started_at: Time.now.utc,
|
5
|
+
operation_name: "deploy", timeout: nil)
|
5
6
|
unless resources.is_a?(Enumerable)
|
6
7
|
raise ArgumentError, <<~MSG
|
7
8
|
ResourceWatcher expects Enumerable collection, got `#{resources.class}` instead
|
@@ -12,13 +13,17 @@ module KubernetesDeploy
|
|
12
13
|
@sync_mediator = sync_mediator
|
13
14
|
@deploy_started_at = deploy_started_at
|
14
15
|
@operation_name = operation_name
|
16
|
+
@timeout = timeout
|
15
17
|
end
|
16
18
|
|
17
19
|
def run(delay_sync: 3.seconds, reminder_interval: 30.seconds, record_summary: true)
|
18
|
-
delay_sync_until = last_message_logged_at = Time.now.utc
|
20
|
+
delay_sync_until = last_message_logged_at = monitoring_started = Time.now.utc
|
19
21
|
remainder = @resources.dup
|
20
22
|
|
21
23
|
while remainder.present?
|
24
|
+
if @timeout && (Time.now.utc - monitoring_started > @timeout)
|
25
|
+
report_and_give_up(remainder)
|
26
|
+
end
|
22
27
|
if Time.now.utc < delay_sync_until
|
23
28
|
sleep(delay_sync_until - Time.now.utc)
|
24
29
|
end
|
@@ -69,20 +74,30 @@ module KubernetesDeploy
|
|
69
74
|
@logger.info(msg)
|
70
75
|
end
|
71
76
|
|
77
|
+
def report_and_give_up(remaining_resources)
|
78
|
+
successful_resources, failed_resources = (@resources - remaining_resources).partition(&:deploy_succeeded?)
|
79
|
+
record_success_statuses(successful_resources)
|
80
|
+
record_failed_statuses(failed_resources, remaining_resources)
|
81
|
+
|
82
|
+
if failed_resources.present? && !failed_resources.all?(&:deploy_timed_out?)
|
83
|
+
raise FatalDeploymentError
|
84
|
+
else
|
85
|
+
raise DeploymentTimeoutError
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
72
89
|
def record_statuses_for_summary(resources)
|
73
90
|
successful_resources, failed_resources = resources.partition(&:deploy_succeeded?)
|
74
|
-
|
75
|
-
|
91
|
+
record_success_statuses(successful_resources)
|
92
|
+
record_failed_statuses(failed_resources)
|
93
|
+
end
|
76
94
|
|
77
|
-
|
78
|
-
|
79
|
-
"#{'resource'.pluralize(success_count)}")
|
80
|
-
final_statuses = successful_resources.map(&:pretty_status).join("\n")
|
81
|
-
@logger.summary.add_paragraph("#{ColorizedString.new('Successful resources').green}\n#{final_statuses}")
|
82
|
-
end
|
95
|
+
def record_failed_statuses(failed_resources, global_timeouts = [])
|
96
|
+
fail_count = failed_resources.length + global_timeouts.length
|
83
97
|
|
84
98
|
if fail_count > 0
|
85
99
|
timeouts, failures = failed_resources.partition(&:deploy_timed_out?)
|
100
|
+
timeouts += global_timeouts
|
86
101
|
if timeouts.present?
|
87
102
|
@logger.summary.add_action(
|
88
103
|
"timed out waiting for #{timeouts.length} #{'resource'.pluralize(timeouts.length)} to #{@operation_name}"
|
@@ -94,10 +109,21 @@ module KubernetesDeploy
|
|
94
109
|
"failed to #{@operation_name} #{failures.length} #{'resource'.pluralize(failures.length)}"
|
95
110
|
)
|
96
111
|
end
|
97
|
-
KubernetesDeploy::Concurrency.split_across_threads(failed_resources) do |r|
|
112
|
+
KubernetesDeploy::Concurrency.split_across_threads(failed_resources + global_timeouts) do |r|
|
98
113
|
r.sync_debug_info(@sync_mediator.kubectl)
|
99
114
|
end
|
100
115
|
failed_resources.each { |r| @logger.summary.add_paragraph(r.debug_message) }
|
116
|
+
global_timeouts.each { |r| @logger.summary.add_paragraph(r.debug_message(:gave_up, timeout: @timeout)) }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def record_success_statuses(successful_resources)
|
121
|
+
success_count = successful_resources.length
|
122
|
+
if success_count > 0
|
123
|
+
@logger.summary.add_action("successfully #{@operation_name}ed #{success_count} "\
|
124
|
+
"#{'resource'.pluralize(success_count)}")
|
125
|
+
final_statuses = successful_resources.map(&:pretty_status).join("\n")
|
126
|
+
@logger.summary.add_paragraph("#{ColorizedString.new('Successful resources').green}\n#{final_statuses}")
|
101
127
|
end
|
102
128
|
end
|
103
129
|
|
@@ -20,11 +20,12 @@ module KubernetesDeploy
|
|
20
20
|
HTTP_OK_RANGE = 200..299
|
21
21
|
ANNOTATION = "shipit.shopify.io/restart"
|
22
22
|
|
23
|
-
def initialize(context:, namespace:, logger:)
|
23
|
+
def initialize(context:, namespace:, logger:, max_watch_seconds: nil)
|
24
24
|
@context = context
|
25
25
|
@namespace = namespace
|
26
26
|
@logger = logger
|
27
27
|
@sync_mediator = SyncMediator.new(namespace: @namespace, context: @context, logger: @logger)
|
28
|
+
@max_watch_seconds = max_watch_seconds
|
28
29
|
end
|
29
30
|
|
30
31
|
def perform(*args)
|
@@ -50,7 +51,7 @@ module KubernetesDeploy
|
|
50
51
|
@logger.phase_heading("Waiting for rollout")
|
51
52
|
resources = build_watchables(deployments, start)
|
52
53
|
ResourceWatcher.new(resources: resources, sync_mediator: @sync_mediator,
|
53
|
-
logger: @logger, operation_name: "restart").run
|
54
|
+
logger: @logger, operation_name: "restart", timeout: @max_watch_seconds).run
|
54
55
|
failed_resources = resources.reject(&:deploy_succeeded?)
|
55
56
|
success = failed_resources.empty?
|
56
57
|
if !success && failed_resources.all?(&:deploy_timed_out?)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kubernetes-deploy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.19.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Katrina Verey
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-04-
|
12
|
+
date: 2018-04-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -17,14 +17,14 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '
|
20
|
+
version: '5.0'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '
|
27
|
+
version: '5.0'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: kubeclient
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|