kubernetes-deploy 0.18.0 → 0.18.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -1
  3. data/ISSUE_TEMPLATE.md +25 -0
  4. data/README.md +2 -3
  5. data/lib/kubernetes-deploy.rb +1 -0
  6. data/lib/kubernetes-deploy/deploy_task.rb +8 -5
  7. data/lib/kubernetes-deploy/kubernetes_resource.rb +34 -32
  8. data/lib/kubernetes-deploy/kubernetes_resource/bucket.rb +2 -7
  9. data/lib/kubernetes-deploy/kubernetes_resource/cloudsql.rb +20 -49
  10. data/lib/kubernetes-deploy/kubernetes_resource/config_map.rb +4 -10
  11. data/lib/kubernetes-deploy/kubernetes_resource/cron_job.rb +0 -10
  12. data/lib/kubernetes-deploy/kubernetes_resource/daemon_set.rb +36 -32
  13. data/lib/kubernetes-deploy/kubernetes_resource/deployment.rb +69 -66
  14. data/lib/kubernetes-deploy/kubernetes_resource/elasticsearch.rb +1 -16
  15. data/lib/kubernetes-deploy/kubernetes_resource/ingress.rb +2 -8
  16. data/lib/kubernetes-deploy/kubernetes_resource/memcached.rb +18 -31
  17. data/lib/kubernetes-deploy/kubernetes_resource/persistent_volume_claim.rb +4 -10
  18. data/lib/kubernetes-deploy/kubernetes_resource/pod.rb +28 -31
  19. data/lib/kubernetes-deploy/kubernetes_resource/pod_disruption_budget.rb +2 -8
  20. data/lib/kubernetes-deploy/kubernetes_resource/pod_set_base.rb +10 -12
  21. data/lib/kubernetes-deploy/kubernetes_resource/pod_template.rb +2 -8
  22. data/lib/kubernetes-deploy/kubernetes_resource/redis.rb +19 -48
  23. data/lib/kubernetes-deploy/kubernetes_resource/replica_set.rb +33 -35
  24. data/lib/kubernetes-deploy/kubernetes_resource/resource_quota.rb +3 -14
  25. data/lib/kubernetes-deploy/kubernetes_resource/service.rb +20 -34
  26. data/lib/kubernetes-deploy/kubernetes_resource/service_account.rb +2 -8
  27. data/lib/kubernetes-deploy/kubernetes_resource/stateful_set.rb +37 -31
  28. data/lib/kubernetes-deploy/kubernetes_resource/statefulservice.rb +1 -16
  29. data/lib/kubernetes-deploy/kubernetes_resource/topic.rb +1 -16
  30. data/lib/kubernetes-deploy/resource_watcher.rb +6 -3
  31. data/lib/kubernetes-deploy/restart_task.rb +3 -1
  32. data/lib/kubernetes-deploy/sync_mediator.rb +66 -0
  33. data/lib/kubernetes-deploy/version.rb +1 -1
  34. metadata +4 -3
  35. data/lib/kubernetes-deploy/kubernetes_resource/bugsnag.rb +0 -33
@@ -3,18 +3,18 @@ module KubernetesDeploy
3
3
  class Service < KubernetesResource
4
4
  TIMEOUT = 7.minutes
5
5
 
6
- def sync
7
- _, _err, st = kubectl.run("get", type, @name)
8
- @found = st.success?
9
- @related_deployment_replicas = fetch_related_replica_count
10
- @num_pods_selected = fetch_related_pod_count
6
+ SYNC_DEPENDENCIES = %w(Pod Deployment)
7
+ def sync(mediator)
8
+ super
9
+ @related_deployments = selector.present? ? mediator.get_all(Deployment.kind, selector) : []
10
+ @related_pods = selector.present? ? mediator.get_all(Pod.kind, selector) : []
11
11
  end
12
12
 
13
13
  def status
14
- if !requires_endpoints?
15
- "Doesn't require any endpoint"
16
- elsif @num_pods_selected.blank?
17
- "Failed to count related pods"
14
+ if !exists?
15
+ "Not found"
16
+ elsif !requires_endpoints?
17
+ "Doesn't require any endpoints"
18
18
  elsif selects_some_pods?
19
19
  "Selects at least 1 pod"
20
20
  else
@@ -23,6 +23,7 @@ module KubernetesDeploy
23
23
  end
24
24
 
25
25
  def deploy_succeeded?
26
+ return false unless exists?
26
27
  return exists? unless requires_endpoints?
27
28
  # We can't use endpoints if we want the service to be able to fail fast when the pods are down
28
29
  exposes_zero_replica_deployment? || selects_some_pods?
@@ -36,15 +37,11 @@ module KubernetesDeploy
36
37
  "This service does not seem to select any pods. This means its spec.selector is probably incorrect."
37
38
  end
38
39
 
39
- def exists?
40
- @found
41
- end
42
-
43
40
  private
44
41
 
45
42
  def exposes_zero_replica_deployment?
46
- return false unless @related_deployment_replicas
47
- @related_deployment_replicas == 0
43
+ return false unless related_replica_count
44
+ related_replica_count == 0
48
45
  end
49
46
 
50
47
  def requires_endpoints?
@@ -52,35 +49,24 @@ module KubernetesDeploy
52
49
  return false if external_name_svc?
53
50
 
54
51
  # problem counting replicas - by default, assume endpoints are required
55
- return true if @related_deployment_replicas.blank?
52
+ return true if related_replica_count.blank?
56
53
 
57
- @related_deployment_replicas > 0
54
+ related_replica_count > 0
58
55
  end
59
56
 
60
57
  def selects_some_pods?
61
- return false unless @num_pods_selected
62
- @num_pods_selected > 0
58
+ return false unless selector.present?
59
+ @related_pods.present?
63
60
  end
64
61
 
65
62
  def selector
66
- @selector ||= @definition["spec"].fetch("selector", []).map { |k, v| "#{k}=#{v}" }.join(",")
67
- end
68
-
69
- def fetch_related_pod_count
70
- return 0 unless selector.present?
71
- raw_json, _err, st = kubectl.run("get", "pods", "--selector=#{selector}", "--output=json")
72
- return unless st.success?
73
- JSON.parse(raw_json)["items"].length
63
+ @definition["spec"].fetch("selector", {})
74
64
  end
75
65
 
76
- def fetch_related_replica_count
66
+ def related_replica_count
77
67
  return 0 unless selector.present?
78
- raw_json, _err, st = kubectl.run("get", "deployments", "--selector=#{selector}", "--output=json")
79
- return unless st.success?
80
-
81
- deployments = JSON.parse(raw_json)["items"]
82
- return unless deployments.length == 1
83
- deployments.first["spec"]["replicas"].to_i
68
+ return unless @related_deployments.length == 1
69
+ @related_deployments.first["spec"]["replicas"].to_i
84
70
  end
85
71
 
86
72
  def external_name_svc?
@@ -3,10 +3,8 @@ module KubernetesDeploy
3
3
  class ServiceAccount < KubernetesResource
4
4
  TIMEOUT = 30.seconds
5
5
 
6
- def sync
7
- _, _err, st = kubectl.run("get", type, @name, "--output=json")
8
- @status = st.success? ? "Created" : "Unknown"
9
- @found = st.success?
6
+ def status
7
+ exists? ? "Created" : "Unknown"
10
8
  end
11
9
 
12
10
  def deploy_succeeded?
@@ -17,10 +15,6 @@ module KubernetesDeploy
17
15
  false
18
16
  end
19
17
 
20
- def exists?
21
- @found
22
- end
23
-
24
18
  def timeout_message
25
19
  UNUSUAL_FAILURE_MESSAGE
26
20
  end
@@ -6,31 +6,21 @@ module KubernetesDeploy
6
6
  ONDELETE = 'OnDelete'
7
7
  attr_reader :pods
8
8
 
9
- def sync
10
- raw_json, _err, st = kubectl.run("get", type, @name, "--output=json")
11
- @found = st.success?
9
+ SYNC_DEPENDENCIES = %w(Pod)
10
+ def sync(mediator)
11
+ super
12
+ @pods = exists? ? find_pods(mediator) : []
13
+ @server_version ||= mediator.kubectl.server_version
14
+ end
12
15
 
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
16
+ def status
17
+ return super unless @instance_data["status"].present?
18
+ rollout_data = @instance_data["status"].slice("replicas", "readyReplicas", "currentReplicas")
19
+ rollout_data.map { |state_replicas, num| "#{num} #{state_replicas.chop.pluralize(num)}" }.join(", ")
30
20
  end
31
21
 
32
22
  def deploy_succeeded?
33
- if @update_strategy == ONDELETE
23
+ if update_strategy == ONDELETE
34
24
  # Gem cannot monitor update since it doesn't occur until delete
35
25
  unless @success_assumption_warning_shown
36
26
  @logger.warn("WARNING: Your StatefulSet's updateStrategy is set to OnDelete, "\
@@ -40,27 +30,43 @@ module KubernetesDeploy
40
30
  end
41
31
  true
42
32
  else
43
- @status_data['currentRevision'] == @status_data['updateRevision'] &&
44
- @desired_replicas == @status_data['readyReplicas'].to_i &&
45
- @desired_replicas == @status_data['currentReplicas'].to_i
33
+ status_data['currentRevision'] == status_data['updateRevision'] &&
34
+ desired_replicas == status_data['readyReplicas'].to_i &&
35
+ desired_replicas == status_data['currentReplicas'].to_i
46
36
  end
47
37
  end
48
38
 
49
39
  def deploy_failed?
50
- return false if @update_strategy == ONDELETE
40
+ return false if update_strategy == ONDELETE
51
41
  pods.present? && pods.any?(&:deploy_failed?)
52
42
  end
53
43
 
54
- def exists?
55
- @found
44
+ private
45
+
46
+ def update_strategy
47
+ if @server_version < Gem::Version.new("1.7.0")
48
+ ONDELETE
49
+ elsif exists?
50
+ @instance_data['spec']['updateStrategy']['type']
51
+ else
52
+ 'Unknown'
53
+ end
56
54
  end
57
55
 
58
- private
56
+ def status_data
57
+ return { 'readyReplicas' => '-1', 'currentReplicas' => '-2' } unless exists?
58
+ @instance_data["status"]
59
+ end
60
+
61
+ def desired_replicas
62
+ return -1 unless exists?
63
+ @instance_data["spec"]["replicas"].to_i
64
+ end
59
65
 
60
- def parent_of_pod?(set_data, pod_data)
66
+ def parent_of_pod?(pod_data)
61
67
  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"]
68
+ pod_data["metadata"]["ownerReferences"].any? { |ref| ref["uid"] == @instance_data["metadata"]["uid"] } &&
69
+ @instance_data["status"]["currentRevision"] == pod_data["metadata"]["labels"]["controller-revision-hash"]
64
70
  end
65
71
  end
66
72
  end
@@ -1,23 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
3
  class Statefulservice < KubernetesResource
4
- def sync
5
- _, _err, st = kubectl.run("get", type, @name)
6
- @found = st.success?
7
- end
8
-
9
4
  def deploy_succeeded?
10
- return false unless deploy_started?
11
-
12
- unless @success_assumption_warning_shown
13
- @logger.warn("Don't know how to monitor resources of type #{type}. Assuming #{id} deployed successfully.")
14
- @success_assumption_warning_shown = true
15
- end
16
- true
17
- end
18
-
19
- def exists?
20
- @found
5
+ super # success assumption, with warning
21
6
  end
22
7
 
23
8
  def deploy_failed?
@@ -1,23 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
3
  class Topic < KubernetesResource
4
- def sync
5
- _, _err, st = kubectl.run("get", type, @name)
6
- @found = st.success?
7
- end
8
-
9
4
  def deploy_succeeded?
10
- return false unless deploy_started?
11
-
12
- unless @success_assumption_warning_shown
13
- @logger.warn("Don't know how to monitor resources of type #{type}. Assuming #{id} deployed successfully.")
14
- @success_assumption_warning_shown = true
15
- end
16
- true
17
- end
18
-
19
- def exists?
20
- @found
5
+ super # success assumption, with warning
21
6
  end
22
7
 
23
8
  def deploy_failed?
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
3
  class ResourceWatcher
4
- def initialize(resources, logger:, deploy_started_at: Time.now.utc, operation_name: "deploy")
4
+ def initialize(resources:, sync_mediator:, logger:, deploy_started_at: Time.now.utc, operation_name: "deploy")
5
5
  unless resources.is_a?(Enumerable)
6
6
  raise ArgumentError, <<~MSG
7
7
  ResourceWatcher expects Enumerable collection, got `#{resources.class}` instead
@@ -9,6 +9,7 @@ module KubernetesDeploy
9
9
  end
10
10
  @resources = resources
11
11
  @logger = logger
12
+ @sync_mediator = sync_mediator
12
13
  @deploy_started_at = deploy_started_at
13
14
  @operation_name = operation_name
14
15
  end
@@ -23,7 +24,7 @@ module KubernetesDeploy
23
24
  end
24
25
  delay_sync_until = Time.now.utc + delay_sync # don't pummel the API if the sync is fast
25
26
 
26
- KubernetesDeploy::Concurrency.split_across_threads(remainder, &:sync)
27
+ @sync_mediator.sync(remainder)
27
28
  new_successes, remainder = remainder.partition(&:deploy_succeeded?)
28
29
  new_failures, remainder = remainder.partition(&:deploy_failed?)
29
30
  new_timeouts, remainder = remainder.partition(&:deploy_timed_out?)
@@ -93,7 +94,9 @@ module KubernetesDeploy
93
94
  "failed to #{@operation_name} #{failures.length} #{'resource'.pluralize(failures.length)}"
94
95
  )
95
96
  end
96
- KubernetesDeploy::Concurrency.split_across_threads(failed_resources, &:sync_debug_info)
97
+ KubernetesDeploy::Concurrency.split_across_threads(failed_resources) do |r|
98
+ r.sync_debug_info(@sync_mediator.kubectl)
99
+ end
97
100
  failed_resources.each { |r| @logger.summary.add_paragraph(r.debug_message) }
98
101
  end
99
102
  end
@@ -24,6 +24,7 @@ module KubernetesDeploy
24
24
  @context = context
25
25
  @namespace = namespace
26
26
  @logger = logger
27
+ @sync_mediator = SyncMediator.new(namespace: @namespace, context: @context, logger: @logger)
27
28
  end
28
29
 
29
30
  def perform(*args)
@@ -48,7 +49,8 @@ module KubernetesDeploy
48
49
 
49
50
  @logger.phase_heading("Waiting for rollout")
50
51
  resources = build_watchables(deployments, start)
51
- ResourceWatcher.new(resources, logger: @logger, operation_name: "restart").run
52
+ ResourceWatcher.new(resources: resources, sync_mediator: @sync_mediator,
53
+ logger: @logger, operation_name: "restart").run
52
54
  failed_resources = resources.reject(&:deploy_succeeded?)
53
55
  success = failed_resources.empty?
54
56
  if !success && failed_resources.all?(&:deploy_timed_out?)
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+ module KubernetesDeploy
3
+ class SyncMediator
4
+ def initialize(namespace:, context:, logger:)
5
+ @namespace = namespace
6
+ @context = context
7
+ @logger = logger
8
+ clear_cache
9
+ end
10
+
11
+ def get_instance(kind, resource_name)
12
+ if @cache.key?(kind)
13
+ @cache.dig(kind, resource_name) || {}
14
+ else
15
+ request_instance(kind, resource_name)
16
+ end
17
+ end
18
+
19
+ def get_all(kind, selector = nil)
20
+ fetch_by_kind(kind) unless @cache.key?(kind)
21
+ instances = @cache.fetch(kind, {}).values
22
+ return instances unless selector
23
+
24
+ instances.select do |r|
25
+ labels = r.dig("metadata", "labels") || {}
26
+ labels >= selector
27
+ end
28
+ end
29
+
30
+ def sync(resources)
31
+ clear_cache
32
+ dependencies = resources.map(&:class).uniq.flat_map do |c|
33
+ c::SYNC_DEPENDENCIES if c.const_defined?('SYNC_DEPENDENCIES')
34
+ end
35
+ kinds = (resources.map(&:type) + dependencies).compact.uniq
36
+ kinds.each { |kind| fetch_by_kind(kind) }
37
+
38
+ KubernetesDeploy::Concurrency.split_across_threads(resources) do |r|
39
+ r.sync(dup)
40
+ end
41
+ end
42
+
43
+ def kubectl
44
+ @kubectl ||= Kubectl.new(namespace: @namespace, context: @context, logger: @logger, log_failure_by_default: false)
45
+ end
46
+
47
+ private
48
+
49
+ def clear_cache
50
+ @cache = {}
51
+ end
52
+
53
+ def request_instance(kind, iname)
54
+ raw_json, _, st = kubectl.run("get", kind, iname, "-a", "--output=json")
55
+ st.success? ? JSON.parse(raw_json) : {}
56
+ end
57
+
58
+ def fetch_by_kind(kind)
59
+ raw_json, _, st = kubectl.run("get", kind, "-a", "--output=json")
60
+ return unless st.success?
61
+ @cache[kind] = JSON.parse(raw_json)["items"].each_with_object({}) do |r, instances|
62
+ instances[r.dig("metadata", "name")] = r
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
- VERSION = "0.18.0"
3
+ VERSION = "0.18.1"
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.18.0
4
+ version: 0.18.1
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-03 00:00:00.000000000 Z
12
+ date: 2018-04-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -210,6 +210,7 @@ files:
210
210
  - CHANGELOG.md
211
211
  - CODE_OF_CONDUCT.md
212
212
  - Gemfile
213
+ - ISSUE_TEMPLATE.md
213
214
  - LICENSE.txt
214
215
  - NO-BINAUTH
215
216
  - README.md
@@ -237,7 +238,6 @@ files:
237
238
  - lib/kubernetes-deploy/kubectl.rb
238
239
  - lib/kubernetes-deploy/kubernetes_resource.rb
239
240
  - lib/kubernetes-deploy/kubernetes_resource/bucket.rb
240
- - lib/kubernetes-deploy/kubernetes_resource/bugsnag.rb
241
241
  - lib/kubernetes-deploy/kubernetes_resource/cloudsql.rb
242
242
  - lib/kubernetes-deploy/kubernetes_resource/config_map.rb
243
243
  - lib/kubernetes-deploy/kubernetes_resource/cron_job.rb
@@ -264,6 +264,7 @@ files:
264
264
  - lib/kubernetes-deploy/restart_task.rb
265
265
  - lib/kubernetes-deploy/runner_task.rb
266
266
  - lib/kubernetes-deploy/statsd.rb
267
+ - lib/kubernetes-deploy/sync_mediator.rb
267
268
  - lib/kubernetes-deploy/version.rb
268
269
  - screenshots/deploy-demo.gif
269
270
  - screenshots/migrate-logs.png
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
- module KubernetesDeploy
3
- class Bugsnag < KubernetesResource
4
- TIMEOUT = 1.minute
5
-
6
- def sync
7
- @secret_found = false
8
- _, _err, st = kubectl.run("get", type, @name)
9
- @found = st.success?
10
- if @found
11
- secrets, _err, _st = kubectl.run("get", "secrets", "--output=name")
12
- @secret_found = secrets.split.any? { |s| s.end_with?("-bugsnag") }
13
- end
14
- @status = @secret_found ? "Available" : "Unknown"
15
- end
16
-
17
- def deploy_succeeded?
18
- @secret_found
19
- end
20
-
21
- def deploy_failed?
22
- false
23
- end
24
-
25
- def exists?
26
- @found
27
- end
28
-
29
- def deploy_method
30
- :replace
31
- end
32
- end
33
- end