kubernetes-deploy 0.6.6 → 0.7.0
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.
- 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
|
|