kubernetes-deploy 0.6.6 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/exe/kubernetes-deploy +21 -13
  3. data/exe/kubernetes-restart +7 -4
  4. data/exe/kubernetes-run +14 -10
  5. data/kubernetes-deploy.gemspec +1 -0
  6. data/lib/kubernetes-deploy.rb +3 -2
  7. data/lib/kubernetes-deploy/deferred_summary_logging.rb +87 -0
  8. data/lib/kubernetes-deploy/ejson_secret_provisioner.rb +18 -20
  9. data/lib/kubernetes-deploy/formatted_logger.rb +42 -0
  10. data/lib/kubernetes-deploy/kubectl.rb +21 -8
  11. data/lib/kubernetes-deploy/kubernetes_resource.rb +111 -52
  12. data/lib/kubernetes-deploy/kubernetes_resource/bugsnag.rb +3 -11
  13. data/lib/kubernetes-deploy/kubernetes_resource/cloudsql.rb +7 -14
  14. data/lib/kubernetes-deploy/kubernetes_resource/config_map.rb +5 -9
  15. data/lib/kubernetes-deploy/kubernetes_resource/deployment.rb +31 -14
  16. data/lib/kubernetes-deploy/kubernetes_resource/ingress.rb +1 -13
  17. data/lib/kubernetes-deploy/kubernetes_resource/persistent_volume_claim.rb +2 -9
  18. data/lib/kubernetes-deploy/kubernetes_resource/pod.rb +48 -22
  19. data/lib/kubernetes-deploy/kubernetes_resource/pod_disruption_budget.rb +5 -9
  20. data/lib/kubernetes-deploy/kubernetes_resource/pod_template.rb +5 -9
  21. data/lib/kubernetes-deploy/kubernetes_resource/redis.rb +9 -15
  22. data/lib/kubernetes-deploy/kubernetes_resource/service.rb +9 -10
  23. data/lib/kubernetes-deploy/resource_watcher.rb +22 -10
  24. data/lib/kubernetes-deploy/restart_task.rb +12 -7
  25. data/lib/kubernetes-deploy/runner.rb +163 -110
  26. data/lib/kubernetes-deploy/runner_task.rb +22 -19
  27. data/lib/kubernetes-deploy/version.rb +1 -1
  28. metadata +18 -4
  29. data/lib/kubernetes-deploy/logger.rb +0 -45
  30. data/lib/kubernetes-deploy/ui_helpers.rb +0 -19
@@ -3,23 +3,15 @@ module KubernetesDeploy
3
3
  class Bugsnag < KubernetesResource
4
4
  TIMEOUT = 1.minute
5
5
 
6
- def initialize(name, namespace, context, file)
7
- @name = name
8
- @namespace = namespace
9
- @context = context
10
- @file = file
11
- @secret_found = false
12
- end
13
-
14
6
  def sync
15
- _, _err, st = run_kubectl("get", type, @name)
7
+ @secret_found = false
8
+ _, _err, st = kubectl.run("get", type, @name)
16
9
  @found = st.success?
17
10
  if @found
18
- secrets, _err, _st = run_kubectl("get", "secrets", "--output=name")
11
+ secrets, _err, _st = kubectl.run("get", "secrets", "--output=name")
19
12
  @secret_found = secrets.split.any? { |s| s.end_with?("-bugsnag") }
20
13
  end
21
14
  @status = @secret_found ? "Available" : "Unknown"
22
- log_status
23
15
  end
24
16
 
25
17
  def deploy_succeeded?
@@ -3,27 +3,20 @@ module KubernetesDeploy
3
3
  class Cloudsql < KubernetesResource
4
4
  TIMEOUT = 10.minutes
5
5
 
6
- def initialize(name, namespace, context, file)
7
- @name = name
8
- @namespace = namespace
9
- @context = context
10
- @file = file
11
- end
12
-
13
6
  def sync
14
- _, _err, st = run_kubectl("get", type, @name)
7
+ _, _err, st = kubectl.run("get", type, @name)
15
8
  @found = st.success?
16
- @status = if cloudsql_proxy_deployment_exists? && mysql_service_exists?
9
+ @deployment_exists = cloudsql_proxy_deployment_exists?
10
+ @service_exists = mysql_service_exists?
11
+ @status = if @deployment_exists && @service_exists
17
12
  "Provisioned"
18
13
  else
19
14
  "Unknown"
20
15
  end
21
-
22
- log_status
23
16
  end
24
17
 
25
18
  def deploy_succeeded?
26
- cloudsql_proxy_deployment_exists? && mysql_service_exists?
19
+ @service_exists && @deployment_exists
27
20
  end
28
21
 
29
22
  def deploy_failed?
@@ -41,7 +34,7 @@ module KubernetesDeploy
41
34
  private
42
35
 
43
36
  def cloudsql_proxy_deployment_exists?
44
- deployment, _err, st = run_kubectl("get", "deployments", "cloudsql-proxy", "-o=json")
37
+ deployment, _err, st = kubectl.run("get", "deployments", "cloudsql-proxy", "-o=json")
45
38
 
46
39
  if st.success?
47
40
  parsed = JSON.parse(deployment)
@@ -56,7 +49,7 @@ module KubernetesDeploy
56
49
  end
57
50
 
58
51
  def mysql_service_exists?
59
- service, _err, st = run_kubectl("get", "services", "mysql", "-o=json")
52
+ service, _err, st = kubectl.run("get", "services", "mysql", "-o=json")
60
53
 
61
54
  if st.success?
62
55
  parsed = JSON.parse(service)
@@ -3,18 +3,10 @@ module KubernetesDeploy
3
3
  class ConfigMap < KubernetesResource
4
4
  TIMEOUT = 30.seconds
5
5
 
6
- def initialize(name, namespace, context, file)
7
- @name = name
8
- @namespace = namespace
9
- @context = context
10
- @file = file
11
- end
12
-
13
6
  def sync
14
- _, _err, st = run_kubectl("get", type, @name)
7
+ _, _err, st = kubectl.run("get", type, @name)
15
8
  @status = st.success? ? "Available" : "Unknown"
16
9
  @found = st.success?
17
- log_status
18
10
  end
19
11
 
20
12
  def deploy_succeeded?
@@ -25,6 +17,10 @@ module KubernetesDeploy
25
17
  false
26
18
  end
27
19
 
20
+ def timeout_message
21
+ UNUSUAL_FAILURE_MESSAGE
22
+ end
23
+
28
24
  def exists?
29
25
  @found
30
26
  end
@@ -3,39 +3,52 @@ module KubernetesDeploy
3
3
  class Deployment < KubernetesResource
4
4
  TIMEOUT = 5.minutes
5
5
 
6
- def initialize(name, namespace, context, file)
7
- @name = name
8
- @namespace = namespace
9
- @context = context
10
- @file = file
11
- end
12
-
13
6
  def sync
14
- json_data, _err, st = run_kubectl("get", type, @name, "--output=json")
7
+ json_data, _err, st = kubectl.run("get", type, @name, "--output=json")
15
8
  @found = st.success?
16
9
  @rollout_data = {}
17
10
  @status = nil
11
+ @representative_pod = nil
18
12
  @pods = []
19
13
 
20
14
  if @found
21
15
  @rollout_data = JSON.parse(json_data)["status"]
22
16
  .slice("updatedReplicas", "replicas", "availableReplicas", "unavailableReplicas")
23
- @status, _err, _ = run_kubectl("rollout", "status", type, @name, "--watch=false") if @deploy_started
17
+ @status = @rollout_data.map { |replica_state, num| "#{num} #{replica_state}" }.join(", ")
24
18
 
25
- pod_list, _err, st = run_kubectl("get", "pods", "-a", "-l", "name=#{name}", "--output=json")
19
+ pod_list, _err, st = kubectl.run("get", "pods", "-a", "-l", "name=#{name}", "--output=json")
26
20
  if st.success?
27
21
  pods_json = JSON.parse(pod_list)["items"]
28
22
  pods_json.each do |pod_json|
29
23
  pod_name = pod_json["metadata"]["name"]
30
- pod = Pod.new(pod_name, namespace, context, nil, parent: "#{@name.capitalize} deployment")
24
+ pod = Pod.new(
25
+ name: pod_name,
26
+ namespace: namespace,
27
+ context: context,
28
+ file: nil,
29
+ parent: "#{@name.capitalize} deployment",
30
+ logger: @logger
31
+ )
31
32
  pod.deploy_started = @deploy_started
32
33
  pod.interpret_json_data(pod_json)
34
+
35
+ if !@representative_pod && pod_probably_new?(pod_json)
36
+ @representative_pod = pod
37
+ end
33
38
  @pods << pod
34
39
  end
35
40
  end
36
41
  end
42
+ end
37
43
 
38
- log_status
44
+ def fetch_logs
45
+ @representative_pod ? @representative_pod.fetch_logs : {}
46
+ end
47
+
48
+ def fetch_events
49
+ own_events = super
50
+ return own_events unless @representative_pod
51
+ own_events.merge(@representative_pod.fetch_events)
39
52
  end
40
53
 
41
54
  def deploy_succeeded?
@@ -59,8 +72,12 @@ module KubernetesDeploy
59
72
  @found
60
73
  end
61
74
 
62
- def status_data
63
- super.merge(replicas: @rollout_data, num_pods: @pods.length)
75
+ private
76
+
77
+ def pod_probably_new?(pod_json)
78
+ return false unless @deploy_started
79
+ # Shitty, brittle workaround to identify a pod from the new ReplicaSet before implementing ReplicaSet awareness
80
+ Time.parse(pod_json["metadata"]["creationTimestamp"]) >= (@deploy_started - 30.seconds)
64
81
  end
65
82
  end
66
83
  end
@@ -3,18 +3,10 @@ module KubernetesDeploy
3
3
  class Ingress < KubernetesResource
4
4
  TIMEOUT = 30.seconds
5
5
 
6
- def initialize(name, namespace, context, file)
7
- @name = name
8
- @namespace = namespace
9
- @context = context
10
- @file = file
11
- end
12
-
13
6
  def sync
14
- _, _err, st = run_kubectl("get", type, @name)
7
+ _, _err, st = kubectl.run("get", type, @name)
15
8
  @status = st.success? ? "Created" : "Unknown"
16
9
  @found = st.success?
17
- log_status
18
10
  end
19
11
 
20
12
  def deploy_succeeded?
@@ -28,9 +20,5 @@ module KubernetesDeploy
28
20
  def exists?
29
21
  @found
30
22
  end
31
-
32
- def group_name
33
- "Ingresses"
34
- end
35
23
  end
36
24
  end
@@ -3,17 +3,10 @@ module KubernetesDeploy
3
3
  class PersistentVolumeClaim < KubernetesResource
4
4
  TIMEOUT = 5.minutes
5
5
 
6
- def initialize(name, namespace, context, file)
7
- @name = name
8
- @namespace = namespace
9
- @context = context
10
- @file = file
11
- end
12
-
13
6
  def sync
14
- @status, _err, st = run_kubectl("get", type, @name, "--output=jsonpath={.status.phase}")
7
+ out, _err, st = kubectl.run("get", type, @name, "--output=jsonpath={.status.phase}")
15
8
  @found = st.success?
16
- log_status
9
+ @status = out if @found
17
10
  end
18
11
 
19
12
  def deploy_succeeded?
@@ -2,19 +2,19 @@
2
2
  module KubernetesDeploy
3
3
  class Pod < KubernetesResource
4
4
  TIMEOUT = 10.minutes
5
- SUSPICIOUS_CONTAINER_STATES = %w(ImagePullBackOff RunContainerError).freeze
5
+ SUSPICIOUS_CONTAINER_STATES = %w(ImagePullBackOff RunContainerError ErrImagePull).freeze
6
6
 
7
- def initialize(name, namespace, context, file, parent: nil)
7
+ def initialize(name:, namespace:, context:, file:, parent: nil, logger:)
8
8
  @name = name
9
9
  @namespace = namespace
10
10
  @context = context
11
11
  @file = file
12
12
  @parent = parent
13
- @bare = !@parent
13
+ @logger = logger
14
14
  end
15
15
 
16
16
  def sync
17
- out, _err, st = run_kubectl("get", type, @name, "-a", "--output=json")
17
+ out, _err, st = kubectl.run("get", type, @name, "-a", "--output=json")
18
18
  if @found = st.success?
19
19
  pod_data = JSON.parse(out)
20
20
  interpret_json_data(pod_data)
@@ -23,8 +23,7 @@ module KubernetesDeploy
23
23
  @ready = false
24
24
  @containers = []
25
25
  end
26
- log_status
27
- display_logs if @bare && deploy_finished?
26
+ display_logs if unmanaged? && deploy_succeeded?
28
27
  end
29
28
 
30
29
  def interpret_json_data(pod_data)
@@ -36,7 +35,7 @@ module KubernetesDeploy
36
35
  waiting_state = status["state"]["waiting"] if status["state"]
37
36
  reason = waiting_state["reason"] if waiting_state
38
37
  next unless SUSPICIOUS_CONTAINER_STATES.include?(reason)
39
- KubernetesDeploy.logger.warn("#{id} has container in state #{reason} (#{waiting_state['message']})")
38
+ @logger.warn("#{id} has container in state #{reason} (#{waiting_state['message']})")
40
39
  end
41
40
  end
42
41
 
@@ -52,7 +51,7 @@ module KubernetesDeploy
52
51
  end
53
52
 
54
53
  def deploy_succeeded?
55
- if @bare
54
+ if unmanaged?
56
55
  @phase == "Succeeded"
57
56
  else
58
57
  @phase == "Running" && @ready
@@ -64,30 +63,57 @@ module KubernetesDeploy
64
63
  end
65
64
 
66
65
  def exists?
67
- @bare ? @found : true
66
+ unmanaged? ? @found : true
68
67
  end
69
68
 
70
- def group_name
71
- @bare ? "Bare pods" : @parent
72
- end
73
-
74
- private
75
-
76
- def display_logs
77
- return {} unless exists? && @containers.present? && !@already_displayed
69
+ # Returns a hash in the following format:
70
+ # {
71
+ # "pod/web-1/app-container" => "giant blob of logs\nas a single string"
72
+ # "pod/web-1/nginx-container" => "another giant blob of logs\nas a single string"
73
+ # }
74
+ def fetch_logs
75
+ return {} unless exists? && @containers.present?
78
76
 
79
- @containers.each do |container_name|
80
- out, _err, st = run_kubectl(
77
+ @containers.each_with_object({}) do |container_name, container_logs|
78
+ out, _err, _st = kubectl.run(
81
79
  "logs",
82
80
  @name,
83
81
  "--timestamps=true",
84
82
  "--since-time=#{@deploy_started.to_datetime.rfc3339}"
85
83
  )
86
- next unless st.success? && out.present?
84
+ container_logs["#{id}/#{container_name}"] = out
85
+ end
86
+ end
87
+
88
+ private
87
89
 
88
- KubernetesDeploy.logger.info "Logs from #{id} container #{container_name}:\x1b[0m \n#{out}\n"
89
- @already_displayed = true
90
+ def unmanaged?
91
+ @parent.blank?
92
+ end
93
+
94
+ def display_logs
95
+ return if @already_displayed
96
+ container_logs = fetch_logs
97
+
98
+ if container_logs.empty?
99
+ @logger.warn("No logs found for pod #{id}")
100
+ return
90
101
  end
102
+
103
+ container_logs.each do |container_identifier, logs|
104
+ if logs.blank?
105
+ @logger.warn("No logs found for #{container_identifier}")
106
+ else
107
+ @logger.blank_line
108
+ @logger.info("Logs from #{container_identifier}:")
109
+ logs.split("\n").each do |line|
110
+ @logger.info("[#{container_identifier}]\t#{line}")
111
+ end
112
+ @logger.blank_line
113
+ end
114
+ end
115
+
116
+ @already_displayed = true
91
117
  end
92
118
  end
93
119
  end
@@ -3,18 +3,10 @@ module KubernetesDeploy
3
3
  class PodDisruptionBudget < KubernetesResource
4
4
  TIMEOUT = 10.seconds
5
5
 
6
- def initialize(name, namespace, context, file)
7
- @name = name
8
- @namespace = namespace
9
- @context = context
10
- @file = file
11
- end
12
-
13
6
  def sync
14
- _, _err, st = run_kubectl("get", type, @name)
7
+ _, _err, st = kubectl.run("get", type, @name)
15
8
  @found = st.success?
16
9
  @status = @found ? "Available" : "Unknown"
17
- log_status
18
10
  end
19
11
 
20
12
  def deploy_succeeded?
@@ -26,6 +18,10 @@ module KubernetesDeploy
26
18
  :replace_force
27
19
  end
28
20
 
21
+ def timeout_message
22
+ UNUSUAL_FAILURE_MESSAGE
23
+ end
24
+
29
25
  def exists?
30
26
  @found
31
27
  end
@@ -1,18 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
3
  class PodTemplate < KubernetesResource
4
- def initialize(name, namespace, context, file)
5
- @name = name
6
- @namespace = namespace
7
- @context = context
8
- @file = file
9
- end
10
-
11
4
  def sync
12
- _, _err, st = run_kubectl("get", type, @name)
5
+ _, _err, st = kubectl.run("get", type, @name)
13
6
  @status = st.success? ? "Available" : "Unknown"
14
7
  @found = st.success?
15
- log_status
16
8
  end
17
9
 
18
10
  def deploy_succeeded?
@@ -23,6 +15,10 @@ module KubernetesDeploy
23
15
  false
24
16
  end
25
17
 
18
+ def timeout_message
19
+ UNUSUAL_FAILURE_MESSAGE
20
+ end
21
+
26
22
  def exists?
27
23
  @found
28
24
  end
@@ -4,27 +4,21 @@ module KubernetesDeploy
4
4
  TIMEOUT = 5.minutes
5
5
  UUID_ANNOTATION = "redis.stable.shopify.io/owner_uid"
6
6
 
7
- def initialize(name, namespace, context, file)
8
- @name = name
9
- @namespace = namespace
10
- @context = context
11
- @file = file
12
- end
13
-
14
7
  def sync
15
- _, _err, st = run_kubectl("get", type, @name)
8
+ _, _err, st = kubectl.run("get", type, @name)
16
9
  @found = st.success?
17
- @status = if redis_deployment_exists? && redis_service_exists?
10
+ @deployment_exists = redis_deployment_exists?
11
+ @service_exists = redis_service_exists?
12
+
13
+ @status = if @deployment_exists && @service_exists
18
14
  "Provisioned"
19
15
  else
20
16
  "Unknown"
21
17
  end
22
-
23
- log_status
24
18
  end
25
19
 
26
20
  def deploy_succeeded?
27
- redis_deployment_exists? && redis_service_exists?
21
+ @deployment_exists && @service_exists
28
22
  end
29
23
 
30
24
  def deploy_failed?
@@ -42,7 +36,7 @@ module KubernetesDeploy
42
36
  private
43
37
 
44
38
  def redis_deployment_exists?
45
- deployment, _err, st = run_kubectl("get", "deployments", "redis-#{redis_resource_uuid}", "-o=json")
39
+ deployment, _err, st = kubectl.run("get", "deployments", "redis-#{redis_resource_uuid}", "-o=json")
46
40
 
47
41
  if st.success?
48
42
  parsed = JSON.parse(deployment)
@@ -57,7 +51,7 @@ module KubernetesDeploy
57
51
  end
58
52
 
59
53
  def redis_service_exists?
60
- service, _err, st = run_kubectl("get", "services", "redis-#{redis_resource_uuid}", "-o=json")
54
+ service, _err, st = kubectl.run("get", "services", "redis-#{redis_resource_uuid}", "-o=json")
61
55
 
62
56
  if st.success?
63
57
  parsed = JSON.parse(service)
@@ -73,7 +67,7 @@ module KubernetesDeploy
73
67
  def redis_resource_uuid
74
68
  return @redis_resource_uuid if defined?(@redis_resource_uuid) && @redis_resource_uuid
75
69
 
76
- redis, _err, st = run_kubectl("get", "redises", @name, "-o=json")
70
+ redis, _err, st = kubectl.run("get", "redises", @name, "-o=json")
77
71
  if st.success?
78
72
  parsed = JSON.parse(redis)
79
73