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
@@ -17,33 +17,29 @@ module KubernetesDeploy
17
17
  super(namespace: namespace, context: context, definition: definition, logger: logger)
18
18
  end
19
19
 
20
- def sync(pod_data = nil)
21
- if pod_data.blank?
22
- raw_json, _err, st = kubectl.run("get", type, @name, "-a", "--output=json")
23
- pod_data = JSON.parse(raw_json) if st.success?
24
- raise_predates_deploy_error if pod_data.present? && unmanaged? && !deploy_started?
25
- end
26
-
27
- if pod_data.present?
28
- @found = true
29
- @phase = @status = pod_data["status"]["phase"]
30
- @status += " (Reason: #{pod_data['status']['reason']})" if pod_data['status']['reason'].present?
31
- @ready = ready?(pod_data["status"])
32
- update_container_statuses(pod_data["status"])
20
+ def sync(mediator)
21
+ super
22
+ raise_predates_deploy_error if exists? && unmanaged? && !deploy_started?
23
+
24
+ if exists?
25
+ update_container_statuses(@instance_data["status"])
33
26
  else # reset
34
- @found = @ready = false
35
- @status = @phase = 'Unknown'
36
27
  @containers.each(&:reset_status)
37
28
  end
38
29
 
39
- display_logs if unmanaged? && deploy_succeeded?
30
+ display_logs(mediator) if unmanaged? && deploy_succeeded?
31
+ end
32
+
33
+ def status
34
+ return phase if @instance_data.dig('status', 'reason').blank?
35
+ "#{phase} (Reason: #{@instance_data['status']['reason']})"
40
36
  end
41
37
 
42
38
  def deploy_succeeded?
43
39
  if unmanaged?
44
- @phase == "Succeeded"
40
+ phase == "Succeeded"
45
41
  else
46
- @phase == "Running" && @ready
42
+ phase == "Running" && ready?
47
43
  end
48
44
  end
49
45
 
@@ -51,10 +47,6 @@ module KubernetesDeploy
51
47
  failure_message.present?
52
48
  end
53
49
 
54
- def exists?
55
- @found
56
- end
57
-
58
50
  def timeout_message
59
51
  return STANDARD_TIMEOUT_MESSAGE unless readiness_probe_failure?
60
52
  probe_failure_msgs = @containers.map(&:readiness_fail_reason).compact
@@ -63,8 +55,8 @@ module KubernetesDeploy
63
55
  end
64
56
 
65
57
  def failure_message
66
- if @phase == FAILED_PHASE_NAME
67
- phase_problem = "Pod status: #{@status}. "
58
+ if phase == FAILED_PHASE_NAME
59
+ phase_problem = "Pod status: #{status}. "
68
60
  end
69
61
 
70
62
  doomed_containers = @containers.select(&:doomed?)
@@ -87,7 +79,7 @@ module KubernetesDeploy
87
79
  # "app" => ["array of log lines", "received from app container"],
88
80
  # "nginx" => ["array of log lines", "received from nginx container"]
89
81
  # }
90
- def fetch_logs
82
+ def fetch_logs(kubectl)
91
83
  return {} unless exists? && @containers.present?
92
84
  @containers.each_with_object({}) do |container, container_logs|
93
85
  cmd = [
@@ -97,20 +89,25 @@ module KubernetesDeploy
97
89
  "--since-time=#{@deploy_started_at.to_datetime.rfc3339}",
98
90
  ]
99
91
  cmd << "--tail=#{LOG_LINE_COUNT}" unless unmanaged?
100
- out, _err, _st = kubectl.run(*cmd)
92
+ out, _err, _st = kubectl.run(*cmd, log_failure: false)
101
93
  container_logs[container.name] = out.split("\n")
102
94
  end
103
95
  end
104
96
 
105
97
  private
106
98
 
99
+ def phase
100
+ @instance_data.dig("status", "phase") || "Unknown"
101
+ end
102
+
107
103
  def readiness_probe_failure?
108
- return false if @ready || unmanaged?
109
- return false if @phase != "Running"
104
+ return false if ready? || unmanaged?
105
+ return false if phase != "Running"
110
106
  @containers.any?(&:readiness_fail_reason)
111
107
  end
112
108
 
113
- def ready?(status_data)
109
+ def ready?
110
+ return false unless status_data = @instance_data["status"]
114
111
  ready_condition = status_data.fetch("conditions", []).find { |condition| condition["type"] == "Ready" }
115
112
  ready_condition.present? && (ready_condition["status"] == "True")
116
113
  end
@@ -131,9 +128,9 @@ module KubernetesDeploy
131
128
  @parent.blank?
132
129
  end
133
130
 
134
- def display_logs
131
+ def display_logs(mediator)
135
132
  return if @already_displayed
136
- container_logs = fetch_logs
133
+ container_logs = fetch_logs(mediator.kubectl)
137
134
 
138
135
  if container_logs.empty?
139
136
  @logger.warn("No logs found for pod #{id}")
@@ -3,10 +3,8 @@ module KubernetesDeploy
3
3
  class PodDisruptionBudget < KubernetesResource
4
4
  TIMEOUT = 10.seconds
5
5
 
6
- def sync
7
- _, _err, st = kubectl.run("get", type, @name)
8
- @found = st.success?
9
- @status = @found ? "Available" : "Unknown"
6
+ def status
7
+ exists? ? "Available" : "Unknown"
10
8
  end
11
9
 
12
10
  def deploy_succeeded?
@@ -21,9 +19,5 @@ module KubernetesDeploy
21
19
  def timeout_message
22
20
  UNUSUAL_FAILURE_MESSAGE
23
21
  end
24
-
25
- def exists?
26
- @found
27
- end
28
22
  end
29
23
  end
@@ -9,14 +9,14 @@ module KubernetesDeploy
9
9
  pods.map(&:timeout_message).compact.uniq.join("\n")
10
10
  end
11
11
 
12
- def fetch_events
12
+ def fetch_events(kubectl)
13
13
  own_events = super
14
14
  return own_events unless pods.present?
15
15
  most_useful_pod = pods.find(&:deploy_failed?) || pods.find(&:deploy_timed_out?) || pods.first
16
- own_events.merge(most_useful_pod.fetch_events)
16
+ own_events.merge(most_useful_pod.fetch_events(kubectl))
17
17
  end
18
18
 
19
- def fetch_logs
19
+ def fetch_logs(kubectl)
20
20
  return {} unless pods.present? # the kubectl command times out if no pods exist
21
21
  container_names.each_with_object({}) do |container_name, container_logs|
22
22
  out, _err, _st = kubectl.run(
@@ -24,7 +24,8 @@ module KubernetesDeploy
24
24
  id,
25
25
  "--container=#{container_name}",
26
26
  "--since-time=#{@deploy_started_at.to_datetime.rfc3339}",
27
- "--tail=#{LOG_LINE_COUNT}"
27
+ "--tail=#{LOG_LINE_COUNT}",
28
+ log_failure: false
28
29
  )
29
30
  container_logs[container_name] = out.split("\n")
30
31
  end
@@ -36,7 +37,7 @@ module KubernetesDeploy
36
37
  raise NotImplementedError, "Subclasses must define a `pods` accessor"
37
38
  end
38
39
 
39
- def parent_of_pod?(_, _)
40
+ def parent_of_pod?(_)
40
41
  raise NotImplementedError, "Subclasses must define a `parent_of_pod?` method"
41
42
  end
42
43
 
@@ -46,14 +47,11 @@ module KubernetesDeploy
46
47
  regular_containers + init_containers
47
48
  end
48
49
 
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?
50
+ def find_pods(mediator)
51
+ all_pods = mediator.get_all(Pod.kind, @instance_data["spec"]["selector"]["matchLabels"])
53
52
 
54
- all_pods = JSON.parse(raw_json)["items"]
55
53
  all_pods.each_with_object([]) do |pod_data, relevant_pods|
56
- next unless parent_of_pod?(pod_controller_data, pod_data)
54
+ next unless parent_of_pod?(pod_data)
57
55
  pod = Pod.new(
58
56
  namespace: namespace,
59
57
  context: context,
@@ -62,7 +60,7 @@ module KubernetesDeploy
62
60
  parent: "#{name.capitalize} #{type}",
63
61
  deploy_started_at: @deploy_started_at
64
62
  )
65
- pod.sync(pod_data)
63
+ pod.sync(mediator)
66
64
  relevant_pods << pod
67
65
  end
68
66
  end
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
3
  class PodTemplate < KubernetesResource
4
- def sync
5
- _, _err, st = kubectl.run("get", type, @name)
6
- @status = st.success? ? "Available" : "Unknown"
7
- @found = st.success?
4
+ def status
5
+ exists? ? "Available" : "Unknown"
8
6
  end
9
7
 
10
8
  def deploy_succeeded?
@@ -18,9 +16,5 @@ module KubernetesDeploy
18
16
  def timeout_message
19
17
  UNUSUAL_FAILURE_MESSAGE
20
18
  end
21
-
22
- def exists?
23
- @found
24
- end
25
19
  end
26
20
  end
@@ -4,75 +4,46 @@ module KubernetesDeploy
4
4
  TIMEOUT = 5.minutes
5
5
  UUID_ANNOTATION = "redis.stable.shopify.io/owner_uid"
6
6
 
7
- def sync
8
- _, _err, st = kubectl.run("get", type, @name)
9
- @found = st.success?
10
- @deployment_exists = redis_deployment_exists?
11
- @service_exists = redis_service_exists?
7
+ SYNC_DEPENDENCIES = %w(Deployment Service)
8
+ def sync(mediator)
9
+ super
10
+ @deployment = mediator.get_instance(Deployment.kind, "redis-#{redis_resource_uuid}")
11
+ @service = mediator.get_instance(Service.kind, "redis-#{redis_resource_uuid}")
12
+ end
12
13
 
13
- @status = if @deployment_exists && @service_exists
14
- "Provisioned"
15
- else
16
- "Unknown"
17
- end
14
+ def status
15
+ deploy_succeeded? ? "Provisioned" : "Unknown"
18
16
  end
19
17
 
20
18
  def deploy_succeeded?
21
- @deployment_exists && @service_exists
19
+ deployment_ready? && service_ready?
22
20
  end
23
21
 
24
22
  def deploy_failed?
25
23
  false
26
24
  end
27
25
 
28
- def exists?
29
- @found
30
- end
31
-
32
26
  def deploy_method
33
27
  :replace
34
28
  end
35
29
 
36
30
  private
37
31
 
38
- def redis_deployment_exists?
39
- deployment, _err, st = kubectl.run("get", "deployments", "redis-#{redis_resource_uuid}", "-o=json")
40
-
41
- if st.success?
42
- parsed = JSON.parse(deployment)
43
-
44
- if parsed.fetch("status", {}).fetch("availableReplicas", -1) == parsed.fetch("status", {}).fetch("replicas", 0)
45
- # all redis pods are running
46
- return true
47
- end
48
- end
49
-
50
- false
32
+ def deployment_ready?
33
+ return false unless status = @deployment["status"]
34
+ # all redis pods are running
35
+ status.fetch("availableReplicas", -1) == status.fetch("replicas", 0)
51
36
  end
52
37
 
53
- def redis_service_exists?
54
- service, _err, st = kubectl.run("get", "services", "redis-#{redis_resource_uuid}", "-o=json")
55
-
56
- if st.success?
57
- parsed = JSON.parse(service)
58
-
59
- if parsed.dig("spec", "clusterIP").present?
60
- return true
61
- end
62
- end
63
-
64
- false
38
+ def service_ready?
39
+ return false unless @service.present?
40
+ # the service has an assigned cluster IP and is therefore functioning
41
+ @service.dig("spec", "clusterIP").present?
65
42
  end
66
43
 
67
44
  def redis_resource_uuid
68
- return @redis_resource_uuid if defined?(@redis_resource_uuid) && @redis_resource_uuid
69
-
70
- redis, _err, st = kubectl.run("get", "redises", @name, "-o=json")
71
- if st.success?
72
- parsed = JSON.parse(redis)
73
-
74
- @redis_resource_uuid = parsed.dig("metadata", "uid")
75
- end
45
+ return unless @instance_data.present?
46
+ @instance_data.dig("metadata", "uid")
76
47
  end
77
48
  end
78
49
  end
@@ -3,64 +3,62 @@ require 'kubernetes-deploy/kubernetes_resource/pod_set_base'
3
3
  module KubernetesDeploy
4
4
  class ReplicaSet < PodSetBase
5
5
  TIMEOUT = 5.minutes
6
- attr_reader :desired_replicas, :ready_replicas, :available_replicas, :pods
6
+ attr_reader :pods
7
7
 
8
8
  def initialize(namespace:, context:, definition:, logger:, parent: nil, deploy_started_at: nil)
9
9
  @parent = parent
10
10
  @deploy_started_at = deploy_started_at
11
- @rollout_data = { "replicas" => 0 }
12
- @desired_replicas = -1
13
- @ready_replicas = -1
14
- @available_replicas = -1
15
11
  @pods = []
16
12
  super(namespace: namespace, context: context, definition: definition, logger: logger)
17
13
  end
18
14
 
19
- def sync(rs_data = nil)
20
- if rs_data.blank?
21
- raw_json, _err, st = kubectl.run("get", type, @name, "--output=json")
22
- rs_data = JSON.parse(raw_json) if st.success?
23
- end
15
+ SYNC_DEPENDENCIES = %w(Pod)
16
+ def sync(mediator)
17
+ super
18
+ @pods = exists? ? find_pods(mediator) : []
19
+ end
24
20
 
25
- if rs_data.present?
26
- @found = true
27
- @desired_replicas = rs_data["spec"]["replicas"].to_i
28
- @rollout_data = { "replicas" => 0 }.merge(
29
- rs_data["status"].slice("replicas", "availableReplicas", "readyReplicas")
30
- )
31
- @ready_replicas = @rollout_data['readyReplicas'].to_i
32
- @available_replicas = @rollout_data["availableReplicas"].to_i
33
- @status = @rollout_data.map { |state_replicas, num| "#{num} #{state_replicas.chop.pluralize(num)}" }.join(", ")
34
- @pods = find_pods(rs_data)
35
- else # reset
36
- @found = false
37
- @rollout_data = { "replicas" => 0 }
38
- @status = nil
39
- @pods = []
40
- @desired_replicas = -1
41
- @ready_replicas = -1
42
- @available_replicas = -1
43
- end
21
+ def status
22
+ return super unless rollout_data.present?
23
+ rollout_data.map { |state_replicas, num| "#{num} #{state_replicas.chop.pluralize(num)}" }.join(", ")
44
24
  end
45
25
 
46
26
  def deploy_succeeded?
47
- @desired_replicas == @rollout_data["availableReplicas"].to_i &&
48
- @desired_replicas == @rollout_data["readyReplicas"].to_i
27
+ desired_replicas == rollout_data["availableReplicas"].to_i &&
28
+ desired_replicas == rollout_data["readyReplicas"].to_i
49
29
  end
50
30
 
51
31
  def deploy_failed?
52
32
  pods.present? && pods.all?(&:deploy_failed?)
53
33
  end
54
34
 
55
- def exists?
56
- @found
35
+ def desired_replicas
36
+ return -1 unless exists?
37
+ @instance_data["spec"]["replicas"].to_i
38
+ end
39
+
40
+ def ready_replicas
41
+ return -1 unless exists?
42
+ rollout_data['readyReplicas'].to_i
43
+ end
44
+
45
+ def available_replicas
46
+ return -1 unless exists?
47
+ rollout_data["availableReplicas"].to_i
57
48
  end
58
49
 
59
50
  private
60
51
 
61
- def parent_of_pod?(set_data, pod_data)
52
+ def rollout_data
53
+ return { "replicas" => 0 } unless exists?
54
+ { "replicas" => 0 }.merge(
55
+ @instance_data["status"].slice("replicas", "availableReplicas", "readyReplicas")
56
+ )
57
+ end
58
+
59
+ def parent_of_pod?(pod_data)
62
60
  return false unless pod_data.dig("metadata", "ownerReferences")
63
- pod_data["metadata"]["ownerReferences"].any? { |ref| ref["uid"] == set_data["metadata"]["uid"] }
61
+ pod_data["metadata"]["ownerReferences"].any? { |ref| ref["uid"] == @instance_data["metadata"]["uid"] }
64
62
  end
65
63
 
66
64
  def unmanaged?
@@ -3,19 +3,12 @@ module KubernetesDeploy
3
3
  class ResourceQuota < KubernetesResource
4
4
  TIMEOUT = 30.seconds
5
5
 
6
- def sync
7
- raw_json, _err, st = kubectl.run("get", type, @name, "--output=json")
8
- @status = st.success? ? "Available" : "Unknown"
9
- @found = st.success?
10
- @rollout_data = if @found
11
- JSON.parse(raw_json)
12
- else
13
- {}
14
- end
6
+ def status
7
+ exists? ? "In effect" : "Unknown"
15
8
  end
16
9
 
17
10
  def deploy_succeeded?
18
- @rollout_data.dig("spec", "hard") == @rollout_data.dig("status", "hard")
11
+ @instance_data.dig("spec", "hard") == @instance_data.dig("status", "hard")
19
12
  end
20
13
 
21
14
  def deploy_failed?
@@ -25,9 +18,5 @@ module KubernetesDeploy
25
18
  def timeout_message
26
19
  UNUSUAL_FAILURE_MESSAGE
27
20
  end
28
-
29
- def exists?
30
- @found
31
- end
32
21
  end
33
22
  end