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
@@ -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