kubernetes-deploy 0.12.12 → 0.13.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1824eeb996c6d82344af3296b3000706ea6f3516
4
- data.tar.gz: bd6786d3454f52ac5ac8edf332c62a3a5f7f7164
3
+ metadata.gz: 6f9ccce3d57089a13afd20e62c5bba0b49c08b92
4
+ data.tar.gz: 58f63b9dbda402ec6cee2833ec0beb209aa3d51c
5
5
  SHA512:
6
- metadata.gz: 07442fae42d0982d81f9d48a684afb59330124a62680ccb00e9b51b93422f60340e8a48b799f43b7b048dcb084f5d3a8daba93229818e00e9d71f7bafb765731
7
- data.tar.gz: f3acaaa7d676ee0c286d800232b96f6232e04fbb2ee2187a31156f217008705a7b87d6152a030a6d524e548905d3dd7bb93d012232dc375137c1e2b0d27936a2
6
+ metadata.gz: 7969d60b7979ef48b82cb717a22a153203ce495bd8c94c6c8fb1a0c711ed229b7bb32de359b7f71d0b812c64c53caf7afaafab362295bcf8ace6bc090649ef7b
7
+ data.tar.gz: 214dfbc251bcb0a121ec49ca8e37a803793e8f6aa7bcc1fdac8b5069d149abbdd0154b24f58324e5c9eb3c4cb6e43b705946862db5963b523887285aeb5d31ad
@@ -1,3 +1,13 @@
1
+ ### 0.13.0
2
+ *Features*
3
+ - Added support for StatefulSets for kubernetes 1.7+ using RollingUpdate
4
+
5
+ *Bug Fixes*
6
+ - Explicitly require the minimum rest-client version required by kubeclient ([#202](https://github.com/Shopify/kubernetes-deploy/pull/202))
7
+
8
+ *Enhancements*
9
+ - Begin official support for Kubernetes v1.8 ([#198](https://github.com/Shopify/kubernetes-deploy/pull/198), [#200](https://github.com/Shopify/kubernetes-deploy/pull/200))
10
+
1
11
  ### 0.12.12
2
12
  *Bug Fixes*
3
13
  - Fix an issue deploying Shopify's internal custom resources.
data/README.md CHANGED
@@ -298,9 +298,13 @@ If you push your commit and the tag separately, Shipit usually fails with `You n
298
298
 
299
299
  ## CI (External contributors)
300
300
 
301
- Please make sure you run the tests locally before submitting your PR (see [Running the test suite locally](#running-the-test-suite-locally)). After reviewing your PR, a Shopify employee will trigger CI for you from the [Buildkite UI](https://buildkite.com/shopify/kubernetes-deploy-gem) (just specify the branch; SHA is not required).
301
+ Please make sure you run the tests locally before submitting your PR (see [Running the test suite locally](#running-the-test-suite-locally)). After reviewing your PR, a Shopify employee will trigger CI for you.
302
302
 
303
- <img width="464" alt="screen shot 2017-02-21 at 10 55 33" src="https://cloud.githubusercontent.com/assets/522155/23172610/52771a3a-f824-11e6-8c8e-3d59c45e7ff8.png">
303
+ #### Employees: Triggering CI for a contributed PR
304
+
305
+ Go to the [kubernetes-deploy-gem pipeline](https://buildkite.com/shopify/kubernetes-deploy-gem) and click "New Build". Use branch `external_contrib_ci` and the specific sha of the commit you want to build. Add `BUILDKITE_REFSPEC="refs/pull/${PR_NUM}/head"` in the Environment Variables section.
306
+
307
+ <img width="350" alt="build external contrib PR" src="https://screenshot.click/2017-11-07--163728_7ovek-wrpwq.png">
304
308
 
305
309
  # Contributing
306
310
 
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.required_ruby_version = '>= 2.3.0'
26
26
  spec.add_dependency "activesupport", ">= 4.2"
27
27
  spec.add_dependency "kubeclient", "~> 2.4"
28
+ spec.add_dependency "rest-client", ">= 1.7" # Minimum required by kubeclient. Remove when kubeclient releases v3.0.
28
29
  spec.add_dependency "googleauth", ">= 0.5"
29
30
  spec.add_dependency "ejson", "1.0.1"
30
31
  spec.add_dependency "colorize", "~> 0.8"
@@ -35,6 +35,14 @@ module KubernetesDeploy
35
35
  )
36
36
  end
37
37
 
38
+ def build_apps_v1beta1_kubeclient(context)
39
+ _build_kubeclient(
40
+ api_version: "v1beta1",
41
+ context: context,
42
+ endpoint_path: "/apis/apps"
43
+ )
44
+ end
45
+
38
46
  def _build_kubeclient(api_version:, context:, endpoint_path: nil)
39
47
  config = GoogleFriendlyConfig.read(ENV.fetch("KUBECONFIG"))
40
48
  unless config.contexts.include?(context)
@@ -31,5 +31,35 @@ module KubernetesDeploy
31
31
  end
32
32
  [out.chomp, err.chomp, st]
33
33
  end
34
+
35
+ def version_info
36
+ @version_info ||=
37
+ begin
38
+ response, _, status = run("version", use_namespace: false, log_failure: true)
39
+ raise KubectlError, "Could not retrieve kubectl version info" unless status.success?
40
+ extract_version_info_from_kubectl_response(response)
41
+ end
42
+ end
43
+
44
+ def client_version
45
+ version_info[:client]
46
+ end
47
+
48
+ def server_version
49
+ version_info[:server]
50
+ end
51
+
52
+ private
53
+
54
+ def extract_version_info_from_kubectl_response(response)
55
+ info = {}
56
+ response.each_line do |l|
57
+ match = l.match(/^(?<kind>Client|Server).* GitVersion:"v(?<version>\d+\.\d+\.\d+)/)
58
+ if match
59
+ info[match[:kind].downcase.to_sym] = Gem::Version.new(match[:version])
60
+ end
61
+ end
62
+ info
63
+ end
34
64
  end
35
65
  end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
+ require 'kubernetes-deploy/kubernetes_resource/pod_set_base'
2
3
  module KubernetesDeploy
3
- class DaemonSet < KubernetesResource
4
+ class DaemonSet < PodSetBase
4
5
  TIMEOUT = 5.minutes
6
+ attr_reader :pods
5
7
 
6
8
  def sync
7
9
  raw_json, _err, st = kubectl.run("get", type, @name, "--output=json")
@@ -31,66 +33,24 @@ module KubernetesDeploy
31
33
  end
32
34
 
33
35
  def deploy_failed?
34
- @pods.present? && @pods.any?(&:deploy_failed?)
36
+ pods.present? && pods.any?(&:deploy_failed?)
35
37
  end
36
38
 
37
- def failure_message
38
- @pods.map(&:failure_message).compact.uniq.join("\n")
39
- end
40
-
41
- def timeout_message
42
- @pods.map(&:timeout_message).compact.uniq.join("\n")
43
- end
44
-
45
- def deploy_timed_out?
46
- super || @pods.present? && @pods.any?(&:deploy_timed_out?)
39
+ def fetch_logs
40
+ most_useful_pod = @pods.find(&:deploy_failed?) || @pods.find(&:deploy_timed_out?) || @pods.first
41
+ most_useful_pod.fetch_logs
47
42
  end
48
43
 
49
44
  def exists?
50
45
  @found
51
46
  end
52
47
 
53
- def fetch_events
54
- own_events = super
55
- return own_events unless @pods.present?
56
- most_useful_pod = @pods.find(&:deploy_failed?) || @pods.find(&:deploy_timed_out?) || @pods.first
57
- own_events.merge(most_useful_pod.fetch_events)
58
- end
59
-
60
- def fetch_logs
61
- most_useful_pod = @pods.find(&:deploy_failed?) || @pods.find(&:deploy_timed_out?) || @pods.first
62
- most_useful_pod.fetch_logs
63
- end
64
-
65
48
  private
66
49
 
67
- def find_pods(ds_data)
68
- label_string = ds_data["spec"]["selector"]["matchLabels"].map { |k, v| "#{k}=#{v}" }.join(",")
69
- raw_json, _err, st = kubectl.run("get", "pods", "-a", "--output=json", "--selector=#{label_string}")
70
- return [] unless st.success?
71
-
72
- all_pods = JSON.parse(raw_json)["items"]
73
- template_generation = ds_data["spec"]["templateGeneration"]
74
-
75
- latest_pods = all_pods.find_all do |pod|
76
- next unless owners = pod.dig("metadata", "ownerReferences")
77
- owners.any? { |ref| ref["uid"] == ds_data["metadata"]["uid"] } &&
78
- pod["metadata"]["labels"]["pod-template-generation"].to_i == template_generation.to_i
79
- end
80
- return [] unless latest_pods.present?
81
-
82
- latest_pods.each_with_object([]) do |pod_data, relevant_pods|
83
- pod = Pod.new(
84
- namespace: namespace,
85
- context: context,
86
- definition: pod_data,
87
- logger: @logger,
88
- parent: "#{@name.capitalize} daemon set",
89
- deploy_started: @deploy_started
90
- )
91
- pod.sync(pod_data)
92
- relevant_pods << pod
93
- end
50
+ def parent_of_pod?(set_data, pod_data)
51
+ return false unless pod_data.dig("metadata", "ownerReferences")
52
+ pod_data["metadata"]["ownerReferences"].any? { |ref| ref["uid"] == set_data["metadata"]["uid"] } &&
53
+ pod_data["metadata"]["labels"]["pod-template-generation"].to_i == set_data["spec"]["templateGeneration"].to_i
94
54
  end
95
55
  end
96
56
  end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+ module KubernetesDeploy
3
+ class PodSetBase < KubernetesResource
4
+ def failure_message
5
+ pods.map(&:failure_message).compact.uniq.join("\n")
6
+ end
7
+
8
+ def timeout_message
9
+ pods.map(&:timeout_message).compact.uniq.join("\n")
10
+ end
11
+
12
+ def fetch_events
13
+ own_events = super
14
+ return own_events unless pods.present?
15
+ most_useful_pod = pods.find(&:deploy_failed?) || pods.find(&:deploy_timed_out?) || pods.first
16
+ own_events.merge(most_useful_pod.fetch_events)
17
+ end
18
+
19
+ def fetch_logs
20
+ return {} unless pods.present? # the kubectl command times out if no pods exist
21
+ container_names.each_with_object({}) do |container_name, container_logs|
22
+ out, _err, _st = kubectl.run(
23
+ "logs",
24
+ id,
25
+ "--container=#{container_name}",
26
+ "--since-time=#{@deploy_started.to_datetime.rfc3339}",
27
+ "--tail=#{LOG_LINE_COUNT}"
28
+ )
29
+ container_logs[container_name] = out.split("\n")
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def pods
36
+ raise NotImplementedError, "Subclasses must define a `pods` accessor"
37
+ end
38
+
39
+ def parent_of_pod?(_, _)
40
+ raise NotImplementedError, "Subclasses must define a `parent_of_pod?` method"
41
+ end
42
+
43
+ def container_names
44
+ regular_containers = @definition["spec"]["template"]["spec"]["containers"].map { |c| c["name"] }
45
+ init_containers = @definition["spec"]["template"]["spec"].fetch("initContainers", {}).map { |c| c["name"] }
46
+ regular_containers + init_containers
47
+ end
48
+
49
+ def find_pods(pod_controller_data)
50
+ label_string = pod_controller_data["spec"]["selector"]["matchLabels"].map { |k, v| "#{k}=#{v}" }.join(",")
51
+ raw_json, _err, st = kubectl.run("get", "pods", "-a", "--output=json", "--selector=#{label_string}")
52
+ return [] unless st.success?
53
+
54
+ all_pods = JSON.parse(raw_json)["items"]
55
+ all_pods.each_with_object([]) do |pod_data, relevant_pods|
56
+ next unless parent_of_pod?(pod_controller_data, pod_data)
57
+ pod = Pod.new(
58
+ namespace: namespace,
59
+ context: context,
60
+ definition: pod_data,
61
+ logger: @logger,
62
+ parent: "#{name.capitalize} #{self.class.name}",
63
+ deploy_started: @deploy_started
64
+ )
65
+ pod.sync(pod_data)
66
+ relevant_pods << pod
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
+ require 'kubernetes-deploy/kubernetes_resource/pod_set_base'
2
3
  module KubernetesDeploy
3
- class ReplicaSet < KubernetesResource
4
+ class ReplicaSet < PodSetBase
4
5
  TIMEOUT = 5.minutes
5
- attr_reader :desired_replicas
6
+ attr_reader :desired_replicas, :pods
6
7
 
7
8
  def initialize(namespace:, context:, definition:, logger:, parent: nil, deploy_started: nil)
8
9
  @parent = parent
@@ -39,74 +40,22 @@ module KubernetesDeploy
39
40
  end
40
41
 
41
42
  def deploy_failed?
42
- @pods.present? && @pods.all?(&:deploy_failed?)
43
- end
44
-
45
- def failure_message
46
- @pods.map(&:failure_message).compact.uniq.join("\n")
47
- end
48
-
49
- def timeout_message
50
- @pods.map(&:timeout_message).compact.uniq.join("\n")
43
+ pods.present? && pods.all?(&:deploy_failed?)
51
44
  end
52
45
 
53
46
  def exists?
54
47
  @found
55
48
  end
56
49
 
57
- def fetch_events
58
- own_events = super
59
- return own_events unless @pods.present?
60
- most_useful_pod = @pods.find(&:deploy_failed?) || @pods.find(&:deploy_timed_out?) || @pods.first
61
- own_events.merge(most_useful_pod.fetch_events)
62
- end
50
+ private
63
51
 
64
- def fetch_logs
65
- return {} unless @pods.present? # the kubectl command times out if no pods exist
66
- container_names.each_with_object({}) do |container_name, container_logs|
67
- out, _err, _st = kubectl.run(
68
- "logs",
69
- id,
70
- "--container=#{container_name}",
71
- "--since-time=#{@deploy_started.to_datetime.rfc3339}",
72
- "--tail=#{LOG_LINE_COUNT}"
73
- )
74
- container_logs[container_name] = out.split("\n")
75
- end
52
+ def parent_of_pod?(set_data, pod_data)
53
+ return false unless pod_data.dig("metadata", "ownerReferences")
54
+ pod_data["metadata"]["ownerReferences"].any? { |ref| ref["uid"] == set_data["metadata"]["uid"] }
76
55
  end
77
56
 
78
- private
79
-
80
57
  def unmanaged?
81
58
  @parent.blank?
82
59
  end
83
-
84
- def container_names
85
- regular_containers = @definition["spec"]["template"]["spec"]["containers"].map { |c| c["name"] }
86
- init_containers = @definition["spec"]["template"]["spec"].fetch("initContainers", []).map { |c| c["name"] }
87
- regular_containers + init_containers
88
- end
89
-
90
- def find_pods(rs_data)
91
- label_string = rs_data["spec"]["selector"]["matchLabels"].map { |k, v| "#{k}=#{v}" }.join(",")
92
- raw_json, _err, st = kubectl.run("get", "pods", "-a", "--output=json", "--selector=#{label_string}")
93
- return [] unless st.success?
94
-
95
- all_pods = JSON.parse(raw_json)["items"]
96
- all_pods.each_with_object([]) do |pod_data, relevant_pods|
97
- next unless owners = pod_data.dig("metadata", "ownerReferences")
98
- next unless owners.any? { |ref| ref["uid"] == rs_data["metadata"]["uid"] }
99
- pod = Pod.new(
100
- namespace: namespace,
101
- context: context,
102
- definition: pod_data,
103
- logger: @logger,
104
- parent: "#{@name.capitalize} replica set",
105
- deploy_started: @deploy_started
106
- )
107
- pod.sync(pod_data)
108
- relevant_pods << pod
109
- end
110
- end
111
60
  end
112
61
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+ require 'kubernetes-deploy/kubernetes_resource/pod_set_base'
3
+ module KubernetesDeploy
4
+ class StatefulSet < PodSetBase
5
+ TIMEOUT = 10.minutes
6
+ ONDELETE = 'OnDelete'
7
+ attr_reader :pods
8
+
9
+ def sync
10
+ raw_json, _err, st = kubectl.run("get", type, @name, "--output=json")
11
+ @found = st.success?
12
+
13
+ if @found
14
+ stateful_data = JSON.parse(raw_json)
15
+ @desired_replicas = stateful_data["spec"]["replicas"].to_i
16
+ @status_data = stateful_data["status"]
17
+ rollout_data = stateful_data["status"].slice("replicas", "readyReplicas", "currentReplicas")
18
+ @update_strategy = if kubectl.server_version < Gem::Version.new("1.7.0")
19
+ ONDELETE
20
+ else
21
+ stateful_data['spec']['updateStrategy']['type']
22
+ end
23
+ @status = rollout_data.map { |state_replicas, num| "#{num} #{state_replicas.chop.pluralize(num)}" }.join(", ")
24
+ @pods = find_pods(stateful_data)
25
+ else # reset
26
+ @status_data = { 'readyReplicas' => '-1', 'currentReplicas' => '-2' }
27
+ @status = nil
28
+ @pods = []
29
+ end
30
+ end
31
+
32
+ def deploy_succeeded?
33
+ if @update_strategy == ONDELETE
34
+ # Gem cannot monitor update since it doesn't occur until delete
35
+ unless @success_assumption_warning_shown
36
+ @logger.warn("WARNING: Your StatefulSet's updateStrategy is set to OnDelete, "\
37
+ "which means updates will not be applied until its pods are deleted. "\
38
+ "If you are using k8s 1.7+, consider switching to rollingUpdate.")
39
+ @success_assumption_warning_shown = true
40
+ end
41
+ true
42
+ else
43
+ @status_data['currentRevision'] == @status_data['updateRevision'] &&
44
+ @desired_replicas == @status_data['readyReplicas'].to_i &&
45
+ @desired_replicas == @status_data['currentReplicas'].to_i
46
+ end
47
+ end
48
+
49
+ def deploy_failed?
50
+ return false if @update_strategy == ONDELETE
51
+ pods.present? && pods.any?(&:deploy_failed?)
52
+ end
53
+
54
+ def exists?
55
+ @found
56
+ end
57
+
58
+ private
59
+
60
+ def parent_of_pod?(set_data, pod_data)
61
+ return false unless pod_data.dig("metadata", "ownerReferences")
62
+ pod_data["metadata"]["ownerReferences"].any? { |ref| ref["uid"] == set_data["metadata"]["uid"] } &&
63
+ set_data["status"]["currentRevision"] == pod_data["metadata"]["labels"]["controller-revision-hash"]
64
+ end
65
+ end
66
+ end
@@ -27,6 +27,7 @@ require 'kubernetes-deploy/kubernetes_resource'
27
27
  statefulservice
28
28
  topic
29
29
  bucket
30
+ stateful_set
30
31
  ).each do |subresource|
31
32
  require "kubernetes-deploy/kubernetes_resource/#{subresource}"
32
33
  end
@@ -415,10 +416,12 @@ module KubernetesDeploy
415
416
  def confirm_cluster_reachable
416
417
  success = false
417
418
  with_retries(2) do
418
- _, _, st = kubectl.run("version", use_namespace: false, log_failure: true)
419
- success = st.success?
419
+ begin
420
+ success = kubectl.version_info
421
+ rescue KubectlError
422
+ success = false
423
+ end
420
424
  end
421
-
422
425
  raise FatalDeploymentError, "Failed to reach server for #{@context}" unless success
423
426
  end
424
427
 
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
- VERSION = "0.12.12"
3
+ VERSION = "0.13.0"
4
4
  end
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.12.12
4
+ version: 0.13.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: 2017-11-02 00:00:00.000000000 Z
12
+ date: 2017-11-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -39,6 +39,20 @@ dependencies:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
41
  version: '2.4'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rest-client
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '1.7'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '1.7'
42
56
  - !ruby/object:Gem::Dependency
43
57
  name: googleauth
44
58
  requirement: !ruby/object:Gem::Requirement
@@ -229,12 +243,14 @@ files:
229
243
  - lib/kubernetes-deploy/kubernetes_resource/persistent_volume_claim.rb
230
244
  - lib/kubernetes-deploy/kubernetes_resource/pod.rb
231
245
  - lib/kubernetes-deploy/kubernetes_resource/pod_disruption_budget.rb
246
+ - lib/kubernetes-deploy/kubernetes_resource/pod_set_base.rb
232
247
  - lib/kubernetes-deploy/kubernetes_resource/pod_template.rb
233
248
  - lib/kubernetes-deploy/kubernetes_resource/redis.rb
234
249
  - lib/kubernetes-deploy/kubernetes_resource/replica_set.rb
235
250
  - lib/kubernetes-deploy/kubernetes_resource/resource_quota.rb
236
251
  - lib/kubernetes-deploy/kubernetes_resource/service.rb
237
252
  - lib/kubernetes-deploy/kubernetes_resource/service_account.rb
253
+ - lib/kubernetes-deploy/kubernetes_resource/stateful_set.rb
238
254
  - lib/kubernetes-deploy/kubernetes_resource/statefulservice.rb
239
255
  - lib/kubernetes-deploy/kubernetes_resource/topic.rb
240
256
  - lib/kubernetes-deploy/resource_watcher.rb