krane 2.2.0 → 2.3.0
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.
- 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
|