krane 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +4 -0
- data/README.md +1 -1
- data/lib/krane/cli/restart_command.rb +7 -1
- data/lib/krane/restart_task.rb +152 -48
- data/lib/krane/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 676fdc875449178de6e0f4ff1f649271bd691637e4e56d540c5e5ea71dc8ded4
|
4
|
+
data.tar.gz: 6a16a1e9e32947b0bd6aff7f679ca7f4958a4ef4d3c27e8855fc2a7a6e39abfa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20aad308cbb7c96ad518d0bb57026d6b95e6e8ced1f9eb968c2d144906920a45da272fdf20c94fd8d455a6265b266b6d10912573839e48b08c8162c382905774
|
7
|
+
data.tar.gz: 31711f6a966d5364d6e084eb67b8bdcff4e511e40967cc9fb0528a9e8dd1739ed3f59363ab1dd2ba122d40331019eaf2a9ef850830d6c92ddcdfc5a291bd5e89
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -451,7 +451,7 @@ resource to deploy.
|
|
451
451
|
|
452
452
|
# krane restart
|
453
453
|
|
454
|
-
`krane restart` is a tool for restarting all of the pods in one or more deployments. It triggers the restart by
|
454
|
+
`krane restart` is a tool for restarting all of the pods in one or more deployments, statefuls sets, and/or daemon sets. It triggers the restart by patching template metadata with the `kubectl.kubernetes.io/restartedAt` annotation (with the value being an RFC 3339 representation of the current time). Note this is the manner in which `kubectl rollout restart` itself triggers restarts.
|
455
455
|
|
456
456
|
## Usage
|
457
457
|
|
@@ -6,7 +6,11 @@ module Krane
|
|
6
6
|
DEFAULT_RESTART_TIMEOUT = '300s'
|
7
7
|
OPTIONS = {
|
8
8
|
"deployments" => { type: :array, banner: "list of deployments",
|
9
|
-
desc: "List of
|
9
|
+
desc: "List of deployment names to restart", default: [] },
|
10
|
+
"statefulsets" => { type: :array, banner: "list of statefulsets",
|
11
|
+
desc: "List of statefulset names to restart", default: [] },
|
12
|
+
"daemonsets" => { type: :array, banner: "list of daemonsets",
|
13
|
+
desc: "List of daemonset names to restart", default: [] },
|
10
14
|
"global-timeout" => { type: :string, banner: "duration", default: DEFAULT_RESTART_TIMEOUT,
|
11
15
|
desc: "Max duration to monitor workloads correctly restarted" },
|
12
16
|
"selector" => { type: :string, banner: "'label=value'",
|
@@ -25,6 +29,8 @@ module Krane
|
|
25
29
|
)
|
26
30
|
restart.run!(
|
27
31
|
deployments: options[:deployments],
|
32
|
+
statefulsets: options[:statefulsets],
|
33
|
+
daemonsets: options[:daemonsets],
|
28
34
|
selector: selector,
|
29
35
|
verify_result: options["verify-result"]
|
30
36
|
)
|
data/lib/krane/restart_task.rb
CHANGED
@@ -22,6 +22,8 @@ module Krane
|
|
22
22
|
HTTP_OK_RANGE = 200..299
|
23
23
|
ANNOTATION = "shipit.shopify.io/restart"
|
24
24
|
|
25
|
+
RESTART_TRIGGER_ANNOTATION = "kubectl.kubernetes.io/restartedAt"
|
26
|
+
|
25
27
|
attr_reader :task_config
|
26
28
|
|
27
29
|
delegate :kubeclient_builder, to: :task_config
|
@@ -58,33 +60,41 @@ module Krane
|
|
58
60
|
# @param verify_result [Boolean] Wait for completion and verify success
|
59
61
|
#
|
60
62
|
# @return [nil]
|
61
|
-
def run!(deployments:
|
63
|
+
def run!(deployments: [], statefulsets: [], daemonsets: [], selector: nil, verify_result: true)
|
62
64
|
start = Time.now.utc
|
63
65
|
@logger.reset
|
64
66
|
|
65
67
|
@logger.phase_heading("Initializing restart")
|
66
68
|
verify_config!
|
67
|
-
deployments =
|
69
|
+
deployments, statefulsets, daemonsets = identify_target_workloads(deployments, statefulsets,
|
70
|
+
daemonsets, selector: selector)
|
68
71
|
|
69
|
-
@logger.phase_heading("Triggering restart by
|
72
|
+
@logger.phase_heading("Triggering restart by annotating pod template #{RESTART_TRIGGER_ANNOTATION} annotation")
|
70
73
|
patch_kubeclient_deployments(deployments)
|
74
|
+
patch_kubeclient_statefulsets(statefulsets)
|
75
|
+
patch_kubeclient_daemonsets(daemonsets)
|
71
76
|
|
72
77
|
if verify_result
|
73
78
|
@logger.phase_heading("Waiting for rollout")
|
74
|
-
resources = build_watchables(deployments, start)
|
79
|
+
resources = build_watchables(deployments, start, Deployment)
|
80
|
+
resources += build_watchables(statefulsets, start, StatefulSet)
|
81
|
+
resources += build_watchables(daemonsets, start, DaemonSet)
|
75
82
|
verify_restart(resources)
|
76
83
|
else
|
77
84
|
warning = "Result verification is disabled for this task"
|
78
85
|
@logger.summary.add_paragraph(ColorizedString.new(warning).yellow)
|
79
86
|
end
|
80
|
-
StatsD.client.distribution('restart.duration', StatsD.duration(start),
|
87
|
+
StatsD.client.distribution('restart.duration', StatsD.duration(start),
|
88
|
+
tags: tags('success', deployments, statefulsets, daemonsets))
|
81
89
|
@logger.print_summary(:success)
|
82
90
|
rescue DeploymentTimeoutError
|
83
|
-
StatsD.client.distribution('restart.duration', StatsD.duration(start),
|
91
|
+
StatsD.client.distribution('restart.duration', StatsD.duration(start),
|
92
|
+
tags: tags('timeout', deployments, statefulsets, daemonsets))
|
84
93
|
@logger.print_summary(:timed_out)
|
85
94
|
raise
|
86
95
|
rescue FatalDeploymentError => error
|
87
|
-
StatsD.client.distribution('restart.duration', StatsD.duration(start),
|
96
|
+
StatsD.client.distribution('restart.duration', StatsD.duration(start),
|
97
|
+
tags: tags('failure', deployments, statefulsets, daemonsets))
|
88
98
|
@logger.summary.add_action(error.message) if error.message != error.class.to_s
|
89
99
|
@logger.print_summary(:failure)
|
90
100
|
raise
|
@@ -93,66 +103,140 @@ module Krane
|
|
93
103
|
|
94
104
|
private
|
95
105
|
|
96
|
-
def tags(status, deployments)
|
97
|
-
%W(namespace:#{@namespace} context:#{@context} status:#{status} deployments:#{deployments.to_a.length}
|
106
|
+
def tags(status, deployments, statefulsets, daemonsets)
|
107
|
+
%W(namespace:#{@namespace} context:#{@context} status:#{status} deployments:#{deployments.to_a.length}
|
108
|
+
statefulsets:#{statefulsets.to_a.length} daemonsets:#{daemonsets.to_a.length}})
|
98
109
|
end
|
99
110
|
|
100
|
-
def
|
101
|
-
if deployment_names.
|
102
|
-
|
103
|
-
@logger.info("Configured to restart all
|
104
|
-
apps_v1_kubeclient.get_deployments(namespace: @namespace)
|
111
|
+
def identify_target_workloads(deployment_names, statefulset_names, daemonset_names, selector: nil)
|
112
|
+
if deployment_names.blank? && statefulset_names.blank? && daemonset_names.blank?
|
113
|
+
if selector.nil?
|
114
|
+
@logger.info("Configured to restart all workloads with the `#{ANNOTATION}` annotation")
|
105
115
|
else
|
106
|
-
selector_string = selector.to_s
|
107
116
|
@logger.info(
|
108
|
-
"Configured to restart all
|
117
|
+
"Configured to restart all workloads with the `#{ANNOTATION}` annotation and #{selector} selector"
|
109
118
|
)
|
110
|
-
apps_v1_kubeclient.get_deployments(namespace: @namespace, label_selector: selector_string)
|
111
119
|
end
|
112
|
-
deployments
|
120
|
+
deployments = identify_target_deployments(selector: selector)
|
121
|
+
statefulsets = identify_target_statefulsets(selector: selector)
|
122
|
+
daemonsets = identify_target_daemonsets(selector: selector)
|
113
123
|
|
114
|
-
if deployments.none?
|
115
|
-
raise FatalRestartError, "no deployments with the `#{ANNOTATION}`
|
124
|
+
if deployments.none? && statefulsets.none? && daemonsets.none?
|
125
|
+
raise FatalRestartError, "no deployments, statefulsets, or daemonsets, with the `#{ANNOTATION}` " \
|
126
|
+
"annotation found in namespace #{@namespace}"
|
116
127
|
end
|
117
|
-
elsif deployment_names.empty?
|
118
|
-
raise FatalRestartError, "Configured to restart deployments by name, but list of names was blank"
|
119
128
|
elsif !selector.nil?
|
120
|
-
raise FatalRestartError, "Can't specify
|
129
|
+
raise FatalRestartError, "Can't specify workload names and selector at the same time"
|
121
130
|
else
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
131
|
+
deployments, statefulsets, daemonsets = identify_target_workloads_by_name(deployment_names,
|
132
|
+
statefulset_names, daemonset_names)
|
133
|
+
if deployments.none? && statefulsets.none? && daemonsets.none?
|
134
|
+
error_msgs = []
|
135
|
+
error_msgs << "no deployments with names #{list} found in namespace #{@namespace}" if deployment_names
|
136
|
+
error_msgs << "no statefulsets with names #{list} found in namespace #{@namespace}" if statefulset_names
|
137
|
+
error_msgs << "no daemonsets with names #{list} found in namespace #{@namespace}" if daemonset_names
|
138
|
+
raise FatalRestartError, error_msgs.join(', ')
|
129
139
|
end
|
130
140
|
end
|
131
|
-
deployments
|
141
|
+
[deployments, statefulsets, daemonsets]
|
132
142
|
end
|
133
143
|
|
134
|
-
def
|
144
|
+
def identify_target_workloads_by_name(deployment_names, statefulset_names, daemonset_names)
|
145
|
+
deployment_names = deployment_names.uniq
|
146
|
+
statefulset_names = statefulset_names.uniq
|
147
|
+
daemonset_names = daemonset_names.uniq
|
148
|
+
|
149
|
+
if deployment_names.present?
|
150
|
+
@logger.info("Configured to restart deployments by name: #{deployment_names.join(', ')}")
|
151
|
+
end
|
152
|
+
if statefulset_names.present?
|
153
|
+
@logger.info("Configured to restart statefulsets by name: #{statefulset_names.join(', ')}")
|
154
|
+
end
|
155
|
+
if daemonset_names.present?
|
156
|
+
@logger.info("Configured to restart daemonsets by name: #{daemonset_names.join(', ')}")
|
157
|
+
end
|
158
|
+
|
159
|
+
[fetch_deployments(deployment_names), fetch_statefulsets(statefulset_names), fetch_daemonsets(daemonset_names)]
|
160
|
+
end
|
161
|
+
|
162
|
+
def identify_target_deployments(selector: nil)
|
163
|
+
deployments = if selector.nil?
|
164
|
+
apps_v1_kubeclient.get_deployments(namespace: @namespace)
|
165
|
+
else
|
166
|
+
selector_string = selector.to_s
|
167
|
+
apps_v1_kubeclient.get_deployments(namespace: @namespace, label_selector: selector_string)
|
168
|
+
end
|
169
|
+
deployments.select { |d| d.metadata.annotations[ANNOTATION] }
|
170
|
+
end
|
171
|
+
|
172
|
+
def identify_target_statefulsets(selector: nil)
|
173
|
+
statefulsets = if selector.nil?
|
174
|
+
apps_v1_kubeclient.get_stateful_sets(namespace: @namespace)
|
175
|
+
else
|
176
|
+
selector_string = selector.to_s
|
177
|
+
apps_v1_kubeclient.get_stateful_sets(namespace: @namespace, label_selector: selector_string)
|
178
|
+
end
|
179
|
+
statefulsets.select { |d| d.metadata.annotations[ANNOTATION] }
|
180
|
+
end
|
181
|
+
|
182
|
+
def identify_target_daemonsets(selector: nil)
|
183
|
+
daemonsets = if selector.nil?
|
184
|
+
apps_v1_kubeclient.get_daemon_sets(namespace: @namespace)
|
185
|
+
else
|
186
|
+
selector_string = selector.to_s
|
187
|
+
apps_v1_kubeclient.get_daemon_sets(namespace: @namespace, label_selector: selector_string)
|
188
|
+
end
|
189
|
+
daemonsets.select { |d| d.metadata.annotations[ANNOTATION] }
|
190
|
+
end
|
191
|
+
|
192
|
+
def build_watchables(kubeclient_resources, started, klass)
|
135
193
|
kubeclient_resources.map do |d|
|
136
194
|
definition = d.to_h.deep_stringify_keys
|
137
|
-
r =
|
195
|
+
r = klass.new(namespace: @namespace, context: @context, definition: definition, logger: @logger)
|
138
196
|
r.deploy_started_at = started # we don't care what happened to the resource before the restart cmd ran
|
139
197
|
r
|
140
198
|
end
|
141
199
|
end
|
142
200
|
|
143
201
|
def patch_deployment_with_restart(record)
|
144
|
-
apps_v1_kubeclient.patch_deployment(
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
)
|
202
|
+
apps_v1_kubeclient.patch_deployment(record.metadata.name, build_patch_payload(record), @namespace)
|
203
|
+
end
|
204
|
+
|
205
|
+
def patch_statefulset_with_restart(record)
|
206
|
+
apps_v1_kubeclient.patch_stateful_set(record.metadata.name, build_patch_payload(record), @namespace)
|
207
|
+
end
|
208
|
+
|
209
|
+
def patch_daemonset_with_restart(record)
|
210
|
+
apps_v1_kubeclient.patch_daemon_set(record.metadata.name, build_patch_payload(record), @namespace)
|
149
211
|
end
|
150
212
|
|
151
213
|
def patch_kubeclient_deployments(deployments)
|
152
214
|
deployments.each do |record|
|
153
215
|
begin
|
154
216
|
patch_deployment_with_restart(record)
|
155
|
-
@logger.info("Triggered
|
217
|
+
@logger.info("Triggered `Deployment/#{record.metadata.name}` restart")
|
218
|
+
rescue Kubeclient::HttpError => e
|
219
|
+
raise RestartAPIError.new(record.metadata.name, e.message)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def patch_kubeclient_statefulsets(statefulsets)
|
225
|
+
statefulsets.each do |record|
|
226
|
+
begin
|
227
|
+
patch_statefulset_with_restart(record)
|
228
|
+
@logger.info("Triggered `StatefulSet/#{record.metadata.name}` restart")
|
229
|
+
rescue Kubeclient::HttpError => e
|
230
|
+
raise RestartAPIError.new(record.metadata.name, e.message)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def patch_kubeclient_daemonsets(daemonsets)
|
236
|
+
daemonsets.each do |record|
|
237
|
+
begin
|
238
|
+
patch_daemonset_with_restart(record)
|
239
|
+
@logger.info("Triggered `DaemonSet/#{record.metadata.name}` restart")
|
156
240
|
rescue Kubeclient::HttpError => e
|
157
241
|
raise RestartAPIError.new(record.metadata.name, e.message)
|
158
242
|
end
|
@@ -171,18 +255,38 @@ module Krane
|
|
171
255
|
end
|
172
256
|
end
|
173
257
|
|
174
|
-
def
|
175
|
-
|
258
|
+
def fetch_statefulsets(list)
|
259
|
+
list.map do |name|
|
260
|
+
record = nil
|
261
|
+
begin
|
262
|
+
record = apps_v1_kubeclient.get_stateful_set(name, @namespace)
|
263
|
+
rescue Kubeclient::ResourceNotFoundError
|
264
|
+
raise FatalRestartError, "StatefulSet `#{name}` not found in namespace `#{@namespace}`"
|
265
|
+
end
|
266
|
+
record
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def fetch_daemonsets(list)
|
271
|
+
list.map do |name|
|
272
|
+
record = nil
|
273
|
+
begin
|
274
|
+
record = apps_v1_kubeclient.get_daemon_set(name, @namespace)
|
275
|
+
rescue Kubeclient::ResourceNotFoundError
|
276
|
+
raise FatalRestartError, "DaemonSet `#{name}` not found in namespace `#{@namespace}`"
|
277
|
+
end
|
278
|
+
record
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def build_patch_payload(_deployment)
|
176
283
|
{
|
177
284
|
spec: {
|
178
285
|
template: {
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
env: [{ name: "RESTARTED_AT", value: Time.now.to_i.to_s }],
|
184
|
-
}
|
185
|
-
end,
|
286
|
+
metadata: {
|
287
|
+
annotations: {
|
288
|
+
RESTART_TRIGGER_ANNOTATION => Time.now.utc.to_datetime.rfc3339,
|
289
|
+
},
|
186
290
|
},
|
187
291
|
},
|
188
292
|
},
|
data/lib/krane/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: krane
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Katrina Verey
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date: 2021-
|
13
|
+
date: 2021-10-01 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
@@ -528,7 +528,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
528
528
|
- !ruby/object:Gem::Version
|
529
529
|
version: '0'
|
530
530
|
requirements: []
|
531
|
-
rubygems_version: 3.2.
|
531
|
+
rubygems_version: 3.2.20
|
532
532
|
signing_key:
|
533
533
|
specification_version: 4
|
534
534
|
summary: A command line tool that helps you ship changes to a Kubernetes namespace
|