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.
- checksums.yaml +4 -4
- data/exe/kubernetes-deploy +21 -13
- data/exe/kubernetes-restart +7 -4
- data/exe/kubernetes-run +14 -10
- data/kubernetes-deploy.gemspec +1 -0
- data/lib/kubernetes-deploy.rb +3 -2
- data/lib/kubernetes-deploy/deferred_summary_logging.rb +87 -0
- data/lib/kubernetes-deploy/ejson_secret_provisioner.rb +18 -20
- data/lib/kubernetes-deploy/formatted_logger.rb +42 -0
- data/lib/kubernetes-deploy/kubectl.rb +21 -8
- data/lib/kubernetes-deploy/kubernetes_resource.rb +111 -52
- data/lib/kubernetes-deploy/kubernetes_resource/bugsnag.rb +3 -11
- data/lib/kubernetes-deploy/kubernetes_resource/cloudsql.rb +7 -14
- data/lib/kubernetes-deploy/kubernetes_resource/config_map.rb +5 -9
- data/lib/kubernetes-deploy/kubernetes_resource/deployment.rb +31 -14
- data/lib/kubernetes-deploy/kubernetes_resource/ingress.rb +1 -13
- data/lib/kubernetes-deploy/kubernetes_resource/persistent_volume_claim.rb +2 -9
- data/lib/kubernetes-deploy/kubernetes_resource/pod.rb +48 -22
- data/lib/kubernetes-deploy/kubernetes_resource/pod_disruption_budget.rb +5 -9
- data/lib/kubernetes-deploy/kubernetes_resource/pod_template.rb +5 -9
- data/lib/kubernetes-deploy/kubernetes_resource/redis.rb +9 -15
- data/lib/kubernetes-deploy/kubernetes_resource/service.rb +9 -10
- data/lib/kubernetes-deploy/resource_watcher.rb +22 -10
- data/lib/kubernetes-deploy/restart_task.rb +12 -7
- data/lib/kubernetes-deploy/runner.rb +163 -110
- data/lib/kubernetes-deploy/runner_task.rb +22 -19
- data/lib/kubernetes-deploy/version.rb +1 -1
- metadata +18 -4
- data/lib/kubernetes-deploy/logger.rb +0 -45
- 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
|
-
|
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 =
|
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 =
|
7
|
+
_, _err, st = kubectl.run("get", type, @name)
|
15
8
|
@found = st.success?
|
16
|
-
@
|
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
|
-
|
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 =
|
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 =
|
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 =
|
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 =
|
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
|
17
|
+
@status = @rollout_data.map { |replica_state, num| "#{num} #{replica_state}" }.join(", ")
|
24
18
|
|
25
|
-
pod_list, _err, st =
|
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(
|
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
|
-
|
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
|
-
|
63
|
-
|
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 =
|
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
|
-
|
7
|
+
out, _err, st = kubectl.run("get", type, @name, "--output=jsonpath={.status.phase}")
|
15
8
|
@found = st.success?
|
16
|
-
|
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
|
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
|
-
@
|
13
|
+
@logger = logger
|
14
14
|
end
|
15
15
|
|
16
16
|
def sync
|
17
|
-
out, _err, st =
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
66
|
+
unmanaged? ? @found : true
|
68
67
|
end
|
69
68
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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.
|
80
|
-
out, _err,
|
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
|
-
|
84
|
+
container_logs["#{id}/#{container_name}"] = out
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
87
89
|
|
88
|
-
|
89
|
-
|
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 =
|
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 =
|
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 =
|
8
|
+
_, _err, st = kubectl.run("get", type, @name)
|
16
9
|
@found = st.success?
|
17
|
-
@
|
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
|
-
|
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 =
|
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 =
|
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 =
|
70
|
+
redis, _err, st = kubectl.run("get", "redises", @name, "-o=json")
|
77
71
|
if st.success?
|
78
72
|
parsed = JSON.parse(redis)
|
79
73
|
|