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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46134385171fa351edf5611c3ecd248c299339d2134cf4ff64f6227d68470b96
4
- data.tar.gz: c30ad94176b02c7c402fa50ec5b73aa0c2c289a64401eefcc95494feb64354b3
3
+ metadata.gz: 676fdc875449178de6e0f4ff1f649271bd691637e4e56d540c5e5ea71dc8ded4
4
+ data.tar.gz: 6a16a1e9e32947b0bd6aff7f679ca7f4958a4ef4d3c27e8855fc2a7a6e39abfa
5
5
  SHA512:
6
- metadata.gz: '0792b68c262c3c310b7c51801c206f5422a2b65832eda020a2c0d58c804bb14adc755f9db5ff335b084d982303fbd376b846c5ff6af46d489530ffe6cfe2ae4b'
7
- data.tar.gz: 185a56c603ccbc5dfcdbd7d247d6b7bec512da59a69f2a3e873f41243e79a70f3281596d5346829a4cd26ac1453c8d458cf45f41f498d8e61f1c5cf4e77a13ad
6
+ metadata.gz: 20aad308cbb7c96ad518d0bb57026d6b95e6e8ced1f9eb968c2d144906920a45da272fdf20c94fd8d455a6265b266b6d10912573839e48b08c8162c382905774
7
+ data.tar.gz: 31711f6a966d5364d6e084eb67b8bdcff4e511e40967cc9fb0528a9e8dd1739ed3f59363ab1dd2ba122d40331019eaf2a9ef850830d6c92ddcdfc5a291bd5e89
data/.rubocop.yml CHANGED
@@ -1,5 +1,8 @@
1
1
  inherit_gem:
2
2
  rubocop-shopify: rubocop.yml
3
3
 
4
+ Style/DateTime:
5
+ Enabled: false
6
+
4
7
  AllCops:
5
8
  TargetRubyVersion: 2.4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## next
2
2
 
3
+ ## 2.3.0
4
+
5
+ - Restart tasks now support restarting StatefulSets and DaemonSets, in addition to Deployments [#836](https://github.com/Shopify/krane/pull/836)
6
+
3
7
  ## 2.2.0
4
8
 
5
9
  *Enhancements*
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 touching the `RESTARTED_AT` environment variable in the deployment's podSpec. The rollout strategy defined for each deployment will be respected by the restart.
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 workload names to restart" },
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
  )
@@ -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: nil, selector: nil, verify_result: true)
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 = identify_target_deployments(deployments, selector: selector)
69
+ deployments, statefulsets, daemonsets = identify_target_workloads(deployments, statefulsets,
70
+ daemonsets, selector: selector)
68
71
 
69
- @logger.phase_heading("Triggering restart by touching ENV[RESTARTED_AT]")
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), tags: tags('success', deployments))
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), tags: tags('timeout', deployments))
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), tags: tags('failure', deployments))
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 identify_target_deployments(deployment_names, selector: nil)
101
- if deployment_names.nil?
102
- deployments = if selector.nil?
103
- @logger.info("Configured to restart all deployments with the `#{ANNOTATION}` annotation")
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 deployments with the `#{ANNOTATION}` annotation and #{selector_string} selector"
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.select! { |d| d.metadata.annotations[ANNOTATION] }
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}` annotation found in namespace #{@namespace}"
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 deployment names and selector at the same time"
129
+ raise FatalRestartError, "Can't specify workload names and selector at the same time"
121
130
  else
122
- deployment_names = deployment_names.uniq
123
- list = deployment_names.join(', ')
124
- @logger.info("Configured to restart deployments by name: #{list}")
125
-
126
- deployments = fetch_deployments(deployment_names)
127
- if deployments.none?
128
- raise FatalRestartError, "no deployments with names #{list} found in namespace #{@namespace}"
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 build_watchables(kubeclient_resources, started)
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 = Deployment.new(namespace: @namespace, context: @context, definition: definition, logger: @logger)
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
- record.metadata.name,
146
- build_patch_payload(record),
147
- @namespace
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 `#{record.metadata.name}` restart")
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 build_patch_payload(deployment)
175
- containers = deployment.spec.template.spec.containers
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
- spec: {
180
- containers: containers.map do |container|
181
- {
182
- name: container.name,
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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Krane
3
- VERSION = "2.2.0"
3
+ VERSION = "2.3.0"
4
4
  end
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.2.0
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-06-09 00:00:00.000000000 Z
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.17
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