kubernetes-deploy 0.18.0 → 0.18.1

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.
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
@@ -5,53 +5,57 @@ module KubernetesDeploy
5
5
  TIMEOUT = 5.minutes
6
6
  attr_reader :pods
7
7
 
8
- def sync
9
- raw_json, _err, st = kubectl.run("get", type, @name, "--output=json")
10
- @found = st.success?
11
-
12
- if @found
13
- daemonset_data = JSON.parse(raw_json)
14
- @current_generation = daemonset_data["metadata"]["generation"]
15
- @observed_generation = daemonset_data["status"]["observedGeneration"]
16
- @rollout_data = daemonset_data["status"]
17
- .slice("currentNumberScheduled", "desiredNumberScheduled", "numberReady")
18
- @status = @rollout_data.map { |state_replicas, num| "#{num} #{state_replicas}" }.join(", ")
19
- @pods = find_pods(daemonset_data)
20
- else # reset
21
- @rollout_data = { "currentNumberScheduled" => 0 }
22
- @current_generation = 1 # to make sure the current and observed generations are different
23
- @observed_generation = 0
24
- @status = nil
25
- @pods = []
26
- end
8
+ SYNC_DEPENDENCIES = %w(Pod)
9
+ def sync(mediator)
10
+ super
11
+ @pods = exists? ? find_pods(mediator) : []
12
+ end
13
+
14
+ def status
15
+ return super unless exists?
16
+ rollout_data.map { |state_replicas, num| "#{num} #{state_replicas}" }.join(", ")
27
17
  end
28
18
 
29
19
  def deploy_succeeded?
30
- @rollout_data["desiredNumberScheduled"].to_i == @rollout_data["currentNumberScheduled"].to_i &&
31
- @rollout_data["desiredNumberScheduled"].to_i == @rollout_data["numberReady"].to_i &&
32
- @current_generation == @observed_generation
20
+ return false unless exists?
21
+ rollout_data["desiredNumberScheduled"].to_i == rollout_data["currentNumberScheduled"].to_i &&
22
+ rollout_data["desiredNumberScheduled"].to_i == rollout_data["numberReady"].to_i &&
23
+ current_generation == observed_generation
33
24
  end
34
25
 
35
26
  def deploy_failed?
36
27
  pods.present? && pods.any?(&:deploy_failed?)
37
28
  end
38
29
 
39
- def fetch_logs
40
- return {} unless @pods.present?
41
- most_useful_pod = @pods.find(&:deploy_failed?) || @pods.find(&:deploy_timed_out?) || @pods.first
42
- most_useful_pod.fetch_logs
30
+ def fetch_logs(kubectl)
31
+ return {} unless pods.present?
32
+ most_useful_pod = pods.find(&:deploy_failed?) || pods.find(&:deploy_timed_out?) || pods.first
33
+ most_useful_pod.fetch_logs(kubectl)
34
+ end
35
+
36
+ private
37
+
38
+ def current_generation
39
+ return -1 unless exists? # must be different default than observed_generation
40
+ @instance_data["metadata"]["generation"]
43
41
  end
44
42
 
45
- def exists?
46
- @found
43
+ def observed_generation
44
+ return -2 unless exists?
45
+ @instance_data["status"]["observedGeneration"]
47
46
  end
48
47
 
49
- private
48
+ def rollout_data
49
+ return { "currentNumberScheduled" => 0 } unless exists?
50
+ @instance_data["status"]
51
+ .slice("currentNumberScheduled", "desiredNumberScheduled", "numberReady")
52
+ end
50
53
 
51
- def parent_of_pod?(set_data, pod_data)
54
+ def parent_of_pod?(pod_data)
52
55
  return false unless pod_data.dig("metadata", "ownerReferences")
53
- pod_data["metadata"]["ownerReferences"].any? { |ref| ref["uid"] == set_data["metadata"]["uid"] } &&
54
- pod_data["metadata"]["labels"]["pod-template-generation"].to_i == set_data["spec"]["templateGeneration"].to_i
56
+ pod_data["metadata"]["ownerReferences"].any? { |ref| ref["uid"] == @instance_data["metadata"]["uid"] } &&
57
+ pod_data["metadata"]["labels"]["pod-template-generation"].to_i ==
58
+ @instance_data["spec"]["templateGeneration"].to_i
55
59
  end
56
60
  end
57
61
  end
@@ -6,53 +6,37 @@ module KubernetesDeploy
6
6
  REQUIRED_ROLLOUT_TYPES = %w(maxUnavailable full none).freeze
7
7
  DEFAULT_REQUIRED_ROLLOUT = 'full'
8
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
- deployment_data = JSON.parse(raw_json)
15
- @desired_replicas = deployment_data["spec"]["replicas"].to_i
16
- @latest_rs = find_latest_rs(deployment_data)
17
-
18
- @rollout_data = { "replicas" => 0 }.merge(deployment_data["status"]
19
- .slice("replicas", "updatedReplicas", "availableReplicas", "unavailableReplicas"))
20
- @status = @rollout_data.map { |state_replicas, num| "#{num} #{state_replicas.chop.pluralize(num)}" }.join(", ")
21
-
22
- conditions = deployment_data.fetch("status", {}).fetch("conditions", [])
23
- @progress_condition = conditions.find { |condition| condition['type'] == 'Progressing' }
24
- @progress_deadline = deployment_data['spec']['progressDeadlineSeconds']
25
- @max_unavailable = deployment_data.dig('spec', 'strategy', 'rollingUpdate', 'maxUnavailable')
26
- else # reset
27
- @latest_rs = nil
28
- @rollout_data = { "replicas" => 0 }
29
- @status = nil
30
- @progress_condition = nil
31
- @progress_deadline = @definition['spec']['progressDeadlineSeconds']
32
- @desired_replicas = -1
33
- @max_unavailable = @definition.dig('spec', 'strategy', 'rollingUpdate', 'maxUnavailable')
34
- end
9
+ SYNC_DEPENDENCIES = %w(Pod ReplicaSet)
10
+ def sync(mediator)
11
+ super
12
+ @latest_rs = exists? ? find_latest_rs(mediator) : nil
13
+ @server_version ||= mediator.kubectl.server_version
14
+ end
15
+
16
+ def status
17
+ return super unless exists?
18
+ rollout_data.map { |state_replicas, num| "#{num} #{state_replicas.chop.pluralize(num)}" }.join(", ")
35
19
  end
36
20
 
37
- def fetch_events
21
+ def fetch_events(kubectl)
38
22
  own_events = super
39
23
  return own_events unless @latest_rs.present?
40
- own_events.merge(@latest_rs.fetch_events)
24
+ own_events.merge(@latest_rs.fetch_events(kubectl))
41
25
  end
42
26
 
43
- def fetch_logs
27
+ def fetch_logs(kubectl)
44
28
  return {} unless @latest_rs.present?
45
- @latest_rs.fetch_logs
29
+ @latest_rs.fetch_logs(kubectl)
46
30
  end
47
31
 
48
32
  def deploy_succeeded?
49
- return false unless @latest_rs.present?
33
+ return false unless exists? && @latest_rs.present?
50
34
 
51
35
  if required_rollout == 'full'
52
36
  @latest_rs.deploy_succeeded? &&
53
- @latest_rs.desired_replicas == @desired_replicas && # latest RS fully scaled up
54
- @rollout_data["updatedReplicas"].to_i == @desired_replicas &&
55
- @rollout_data["updatedReplicas"].to_i == @rollout_data["availableReplicas"].to_i
37
+ @latest_rs.desired_replicas == desired_replicas && # latest RS fully scaled up
38
+ rollout_data["updatedReplicas"].to_i == desired_replicas &&
39
+ rollout_data["updatedReplicas"].to_i == rollout_data["availableReplicas"].to_i
56
40
  elsif required_rollout == 'none'
57
41
  true
58
42
  elsif required_rollout == 'maxUnavailable' || percent?(required_rollout)
@@ -76,8 +60,8 @@ module KubernetesDeploy
76
60
  end
77
61
 
78
62
  def timeout_message
79
- reason_msg = if @progress_condition.present?
80
- "Timeout reason: #{@progress_condition['reason']}"
63
+ reason_msg = if progress_condition.present?
64
+ "Timeout reason: #{progress_condition['reason']}"
81
65
  else
82
66
  "Timeout reason: hard deadline for #{type}"
83
67
  end
@@ -86,19 +70,15 @@ module KubernetesDeploy
86
70
  end
87
71
 
88
72
  def pretty_timeout_type
89
- @progress_deadline.present? ? "progress deadline: #{@progress_deadline}s" : super
73
+ progress_deadline.present? ? "progress deadline: #{progress_deadline}s" : super
90
74
  end
91
75
 
92
76
  def deploy_timed_out?
93
77
  # Do not use the hard timeout if progress deadline is set
94
- @progress_condition.present? ? deploy_failing_to_progress? : super
95
- end
96
-
97
- def exists?
98
- @found
78
+ progress_condition.present? ? deploy_failing_to_progress? : super
99
79
  end
100
80
 
101
- def validate_definition
81
+ def validate_definition(_)
102
82
  super
103
83
 
104
84
  unless REQUIRED_ROLLOUT_TYPES.include?(required_rollout) || percent?(required_rollout)
@@ -116,39 +96,57 @@ module KubernetesDeploy
116
96
 
117
97
  private
118
98
 
99
+ def desired_replicas
100
+ return -1 unless exists?
101
+ @instance_data["spec"]["replicas"].to_i
102
+ end
103
+
104
+ def rollout_data
105
+ return { "replicas" => 0 } unless exists?
106
+ { "replicas" => 0 }.merge(@instance_data["status"]
107
+ .slice("replicas", "updatedReplicas", "availableReplicas", "unavailableReplicas"))
108
+ end
109
+
110
+ def progress_condition
111
+ return unless exists?
112
+ conditions = @instance_data.fetch("status", {}).fetch("conditions", [])
113
+ conditions.find { |condition| condition['type'] == 'Progressing' }
114
+ end
115
+
116
+ def progress_deadline
117
+ if exists?
118
+ @instance_data['spec']['progressDeadlineSeconds']
119
+ else
120
+ @definition['spec']['progressDeadlineSeconds']
121
+ end
122
+ end
123
+
119
124
  def rollout_annotation_err_msg
120
125
  "'#{REQUIRED_ROLLOUT_ANNOTATION}: #{required_rollout}' is invalid. "\
121
126
  "Acceptable values: #{REQUIRED_ROLLOUT_TYPES.join(', ')}"
122
127
  end
123
128
 
124
129
  def deploy_failing_to_progress?
125
- return false unless @progress_condition.present?
130
+ return false unless progress_condition.present?
126
131
 
127
- if kubectl.server_version < Gem::Version.new("1.7.7")
132
+ if @server_version < Gem::Version.new("1.7.7")
128
133
  # Deployments were being updated prematurely with incorrect progress information
129
134
  # https://github.com/kubernetes/kubernetes/issues/49637
130
- return false unless Time.now.utc - @deploy_started_at >= @progress_deadline.to_i
135
+ return false unless Time.now.utc - @deploy_started_at >= progress_deadline.to_i
131
136
  else
132
137
  return false unless deploy_started?
133
138
  end
134
139
 
135
- @progress_condition["status"] == 'False' &&
136
- Time.parse(@progress_condition["lastUpdateTime"]).to_i >= (@deploy_started_at - 5.seconds).to_i
140
+ progress_condition["status"] == 'False' &&
141
+ Time.parse(progress_condition["lastUpdateTime"]).to_i >= (@deploy_started_at - 5.seconds).to_i
137
142
  end
138
143
 
139
- def all_rs_data(match_labels)
140
- label_string = match_labels.map { |k, v| "#{k}=#{v}" }.join(",")
141
- raw_json, _err, st = kubectl.run("get", "replicasets", "--output=json", "--selector=#{label_string}")
142
- return {} unless st.success?
144
+ def find_latest_rs(mediator)
145
+ all_rs_data = mediator.get_all(ReplicaSet.kind, @instance_data["spec"]["selector"]["matchLabels"])
146
+ current_revision = @instance_data["metadata"]["annotations"]["deployment.kubernetes.io/revision"]
143
147
 
144
- JSON.parse(raw_json)["items"]
145
- end
146
-
147
- def find_latest_rs(deployment_data)
148
- current_revision = deployment_data["metadata"]["annotations"]["deployment.kubernetes.io/revision"]
149
-
150
- latest_rs_data = all_rs_data(deployment_data["spec"]["selector"]["matchLabels"]).find do |rs|
151
- rs["metadata"]["ownerReferences"].any? { |ref| ref["uid"] == deployment_data["metadata"]["uid"] } &&
148
+ latest_rs_data = all_rs_data.find do |rs|
149
+ rs["metadata"]["ownerReferences"].any? { |ref| ref["uid"] == @instance_data["metadata"]["uid"] } &&
152
150
  rs["metadata"]["annotations"]["deployment.kubernetes.io/revision"] == current_revision
153
151
  end
154
152
 
@@ -162,20 +160,25 @@ module KubernetesDeploy
162
160
  parent: "#{@name.capitalize} deployment",
163
161
  deploy_started_at: @deploy_started_at
164
162
  )
165
- rs.sync(latest_rs_data)
163
+ rs.sync(mediator)
166
164
  rs
167
165
  end
168
166
 
169
167
  def min_available_replicas
170
- max_unavailable = percent?(required_rollout) ? "#{100 - required_rollout.to_i}%" : @max_unavailable
171
-
172
- if max_unavailable =~ /%/
173
- (@desired_replicas * (100 - max_unavailable.to_i) / 100.0).ceil
168
+ if percent?(required_rollout)
169
+ (desired_replicas * required_rollout.to_i / 100.0).ceil
170
+ elsif max_unavailable =~ /%/
171
+ (desired_replicas * (100 - max_unavailable.to_i) / 100.0).ceil
174
172
  else
175
- @desired_replicas - max_unavailable.to_i
173
+ desired_replicas - max_unavailable.to_i
176
174
  end
177
175
  end
178
176
 
177
+ def max_unavailable
178
+ source = exists? ? @instance_data : @definition
179
+ source.dig('spec', 'strategy', 'rollingUpdate', 'maxUnavailable')
180
+ end
181
+
179
182
  def required_rollout
180
183
  @definition.dig('metadata', 'annotations', REQUIRED_ROLLOUT_ANNOTATION).presence || DEFAULT_REQUIRED_ROLLOUT
181
184
  end
@@ -1,23 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
3
  class Elasticsearch < 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?
@@ -3,10 +3,8 @@ module KubernetesDeploy
3
3
  class Ingress < KubernetesResource
4
4
  TIMEOUT = 30.seconds
5
5
 
6
- def sync
7
- _, _err, st = kubectl.run("get", type, @name)
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?
@@ -16,9 +14,5 @@ module KubernetesDeploy
16
14
  def deploy_failed?
17
15
  false
18
16
  end
19
-
20
- def exists?
21
- @found
22
- end
23
17
  end
24
18
  end
@@ -4,58 +4,45 @@ module KubernetesDeploy
4
4
  TIMEOUT = 5.minutes
5
5
  CONFIGMAP_NAME = "memcached-url"
6
6
 
7
- def sync
8
- _, _err, st = kubectl.run("get", type, @name)
9
- @found = st.success?
10
- @deployment_exists = memcached_deployment_exists?
11
- @service_exists = memcached_service_exists?
12
- @configmap_exists = memcached_configmap_exists?
7
+ SYNC_DEPENDENCIES = %w(Deployment Service ConfigMap)
8
+ def sync(mediator)
9
+ super
10
+ @deployment = mediator.get_instance(Deployment.kind, "memcached-#{@name}")
11
+ @service = mediator.get_instance(Service.kind, "memcached-#{@name}")
12
+ @configmap = mediator.get_instance(ConfigMap.kind, CONFIGMAP_NAME)
13
+ end
13
14
 
14
- @status = if @deployment_exists && @service_exists && @configmap_exists
15
- "Provisioned"
16
- else
17
- "Unknown"
18
- end
15
+ def status
16
+ deploy_succeeded? ? "Provisioned" : "Unknown"
19
17
  end
20
18
 
21
19
  def deploy_succeeded?
22
- @deployment_exists && @service_exists && @configmap_exists
20
+ deployment_ready? && service_ready? && configmap_ready?
23
21
  end
24
22
 
25
23
  def deploy_failed?
26
24
  false
27
25
  end
28
26
 
29
- def exists?
30
- @found
31
- end
32
-
33
27
  def deploy_method
34
28
  :replace
35
29
  end
36
30
 
37
31
  private
38
32
 
39
- def memcached_deployment_exists?
40
- deployment, _err, st = kubectl.run("get", "deployments", "memcached-#{@name}", "-o=json")
41
- return false unless st.success?
42
- parsed = JSON.parse(deployment)
43
- status = parsed.fetch("status", {})
33
+ def deployment_ready?
34
+ return false unless status = @deployment["status"]
44
35
  status.fetch("availableReplicas", -1) == status.fetch("replicas", 0)
45
36
  end
46
37
 
47
- def memcached_service_exists?
48
- service, _err, st = kubectl.run("get", "services", "memcached-#{@name}", "-o=json")
49
- return false unless st.success?
50
- parsed = JSON.parse(service)
51
- parsed.dig("spec", "clusterIP").present?
38
+ def service_ready?
39
+ return false unless @service.present?
40
+ @service.dig("spec", "clusterIP").present?
52
41
  end
53
42
 
54
- def memcached_configmap_exists?
55
- secret, _err, st = kubectl.run("get", "configmaps", CONFIGMAP_NAME, "-o=json")
56
- return false unless st.success?
57
- parsed = JSON.parse(secret)
58
- parsed.dig("data", @name).present?
43
+ def configmap_ready?
44
+ return false unless @configmap.present?
45
+ @configmap.dig("data", @name).present?
59
46
  end
60
47
  end
61
48
  end
@@ -3,22 +3,16 @@ module KubernetesDeploy
3
3
  class PersistentVolumeClaim < KubernetesResource
4
4
  TIMEOUT = 5.minutes
5
5
 
6
- def sync
7
- out, _err, st = kubectl.run("get", type, @name, "--output=jsonpath={.status.phase}")
8
- @found = st.success?
9
- @status = out if @found
6
+ def status
7
+ exists? ? @instance_data["status"]["phase"] : "Unknown"
10
8
  end
11
9
 
12
10
  def deploy_succeeded?
13
- @status == "Bound"
11
+ status == "Bound"
14
12
  end
15
13
 
16
14
  def deploy_failed?
17
- @status == "Lost"
18
- end
19
-
20
- def exists?
21
- @found
15
+ status == "Lost"
22
16
  end
23
17
  end
24
18
  end