krane 1.1.1 → 2.1.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/.github/CODEOWNERS +1 -0
- data/{ISSUE_TEMPLATE.md → .github/ISSUE_TEMPLATE.md} +0 -0
- data/{pull_request_template.md → .github/pull_request_template.md} +0 -0
- data/.rubocop-http---shopify-github-io-ruby-style-guide-rubocop-yml +27 -33
- data/.rubocop.yml +0 -12
- data/.shopify-build/krane.yml +20 -6
- data/1.0-Upgrade.md +1 -1
- data/CHANGELOG.md +57 -0
- data/CONTRIBUTING.md +2 -2
- data/README.md +17 -14
- data/bin/ci +1 -1
- data/bin/test +2 -2
- data/dev.yml +3 -2
- data/krane.gemspec +6 -4
- data/lib/krane/annotation.rb +11 -0
- data/lib/krane/cli/deploy_command.rb +2 -3
- data/lib/krane/cli/global_deploy_command.rb +3 -3
- data/lib/krane/cli/render_command.rb +3 -3
- data/lib/krane/cluster_resource_discovery.rb +10 -6
- data/lib/krane/concerns/template_reporting.rb +0 -6
- data/lib/krane/container_logs.rb +1 -1
- data/lib/krane/container_overrides.rb +33 -0
- data/lib/krane/deploy_task.rb +21 -18
- data/lib/krane/ejson_secret_provisioner.rb +1 -2
- data/lib/krane/global_deploy_task.rb +8 -14
- data/lib/krane/kubeclient_builder.rb +4 -2
- data/lib/krane/kubectl.rb +18 -5
- data/lib/krane/kubernetes_resource.rb +32 -42
- data/lib/krane/kubernetes_resource/custom_resource.rb +2 -2
- data/lib/krane/kubernetes_resource/custom_resource_definition.rb +13 -10
- data/lib/krane/kubernetes_resource/deployment.rb +5 -7
- data/lib/krane/kubernetes_resource/persistent_volume_claim.rb +1 -0
- data/lib/krane/kubernetes_resource/pod.rb +12 -8
- data/lib/krane/psych_k8s_compatibility.rb +36 -0
- data/lib/krane/render_task.rb +2 -2
- data/lib/krane/renderer.rb +2 -0
- data/lib/krane/resource_deployer.rb +9 -5
- data/lib/krane/resource_watcher.rb +1 -1
- data/lib/krane/restart_task.rb +8 -8
- data/lib/krane/runner_task.rb +21 -24
- data/lib/krane/statsd.rb +2 -2
- data/lib/krane/task_config.rb +7 -2
- data/lib/krane/task_config_validator.rb +3 -3
- data/lib/krane/template_sets.rb +1 -1
- data/lib/krane/version.rb +1 -1
- metadata +17 -13
- data/lib/krane/kubernetes_resource/cloudsql.rb +0 -44
@@ -10,8 +10,10 @@ module Krane
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
attr_reader :kubeconfig_files
|
14
|
+
|
15
|
+
def initialize(kubeconfig: nil)
|
16
|
+
files = kubeconfig || ENV["KUBECONFIG"] || "#{Dir.home}/.kube/config"
|
15
17
|
# Split the list by colon for Linux and Mac, and semicolon for Windows.
|
16
18
|
@kubeconfig_files = files.split(/[:;]/).map!(&:strip).reject(&:empty?)
|
17
19
|
end
|
data/lib/krane/kubectl.rb
CHANGED
@@ -6,6 +6,8 @@ module Krane
|
|
6
6
|
ERROR_MATCHERS = {
|
7
7
|
not_found: /NotFound/,
|
8
8
|
client_timeout: /Client\.Timeout exceeded while awaiting headers/,
|
9
|
+
empty: /\A\z/,
|
10
|
+
context_deadline: /context deadline exceeded/,
|
9
11
|
}
|
10
12
|
DEFAULT_TIMEOUT = 15
|
11
13
|
MAX_RETRY_DELAY = 16
|
@@ -13,7 +15,7 @@ module Krane
|
|
13
15
|
|
14
16
|
class ResourceNotFoundError < StandardError; end
|
15
17
|
|
16
|
-
delegate :namespace, :context, :logger, to: :@task_config
|
18
|
+
delegate :namespace, :context, :logger, :kubeconfig, to: :@task_config
|
17
19
|
|
18
20
|
def initialize(task_config:, log_failure_by_default:, default_timeout: DEFAULT_TIMEOUT,
|
19
21
|
output_is_sensitive_default: false)
|
@@ -33,8 +35,18 @@ module Krane
|
|
33
35
|
|
34
36
|
(1..attempts).to_a.each do |current_attempt|
|
35
37
|
logger.debug("Running command (attempt #{current_attempt}): #{cmd.join(' ')}")
|
36
|
-
|
37
|
-
|
38
|
+
env = { 'KUBECONFIG' => kubeconfig }
|
39
|
+
out, err, st = Open3.capture3(env, *cmd)
|
40
|
+
|
41
|
+
# https://github.com/Shopify/krane/issues/395
|
42
|
+
unless out.valid_encoding?
|
43
|
+
out = out.dup.force_encoding(Encoding::UTF_8)
|
44
|
+
end
|
45
|
+
|
46
|
+
if logger.debug? && !output_is_sensitive
|
47
|
+
# don't do the gsub unless we're going to print this
|
48
|
+
logger.debug("Kubectl out: " + out.gsub(/\s+/, ' '))
|
49
|
+
end
|
38
50
|
|
39
51
|
break if st.success?
|
40
52
|
raise(ResourceNotFoundError, err) if err.match(ERROR_MATCHERS[:not_found]) && raise_if_not_found
|
@@ -52,7 +64,8 @@ module Krane
|
|
52
64
|
else
|
53
65
|
logger.debug("Kubectl err: #{output_is_sensitive ? '<suppressed sensitive output>' : err}")
|
54
66
|
end
|
55
|
-
StatsD.client.increment('kubectl.error', 1, tags: { context: context, namespace: namespace, cmd: cmd[1]
|
67
|
+
StatsD.client.increment('kubectl.error', 1, tags: { context: context, namespace: namespace, cmd: cmd[1],
|
68
|
+
max_attempt: attempts, current_attempt: current_attempt })
|
56
69
|
|
57
70
|
break unless retriable_err?(err, retry_whitelist) && current_attempt < attempts
|
58
71
|
sleep(retry_delay(current_attempt))
|
@@ -69,7 +82,7 @@ module Krane
|
|
69
82
|
def version_info
|
70
83
|
@version_info ||=
|
71
84
|
begin
|
72
|
-
response, _, status = run("version", use_namespace: false, log_failure: true)
|
85
|
+
response, _, status = run("version", use_namespace: false, log_failure: true, attempts: 2)
|
73
86
|
raise KubectlError, "Could not retrieve kubectl version info" unless status.success?
|
74
87
|
extract_version_info_from_kubectl_response(response)
|
75
88
|
end
|
@@ -6,9 +6,12 @@ require 'krane/remote_logs'
|
|
6
6
|
require 'krane/duration_parser'
|
7
7
|
require 'krane/label_selector'
|
8
8
|
require 'krane/rollout_conditions'
|
9
|
+
require 'krane/psych_k8s_compatibility'
|
9
10
|
|
10
11
|
module Krane
|
11
12
|
class KubernetesResource
|
13
|
+
using PsychK8sCompatibility
|
14
|
+
|
12
15
|
attr_reader :name, :namespace, :context
|
13
16
|
attr_writer :type, :deploy_started_at, :global
|
14
17
|
|
@@ -32,9 +35,9 @@ module Krane
|
|
32
35
|
If you have reason to believe it will succeed, retry the deploy to continue to monitor the rollout.
|
33
36
|
MSG
|
34
37
|
|
35
|
-
|
36
|
-
|
37
|
-
TIMEOUT_OVERRIDE_ANNOTATION = "
|
38
|
+
ALLOWED_DEPLOY_METHOD_OVERRIDES = %w(create replace replace-force)
|
39
|
+
DEPLOY_METHOD_OVERRIDE_ANNOTATION = "deploy-method-override"
|
40
|
+
TIMEOUT_OVERRIDE_ANNOTATION = "timeout-override"
|
38
41
|
LAST_APPLIED_ANNOTATION = "kubectl.kubernetes.io/last-applied-configuration"
|
39
42
|
SENSITIVE_TEMPLATE_CONTENT = false
|
40
43
|
SERVER_DRY_RUNNABLE = false
|
@@ -60,8 +63,8 @@ module Krane
|
|
60
63
|
end
|
61
64
|
|
62
65
|
def class_for_kind(kind)
|
63
|
-
if Krane.const_defined?(kind)
|
64
|
-
Krane.const_get(kind)
|
66
|
+
if Krane.const_defined?(kind, false)
|
67
|
+
Krane.const_get(kind, false)
|
65
68
|
end
|
66
69
|
rescue NameError
|
67
70
|
nil
|
@@ -103,7 +106,7 @@ module Krane
|
|
103
106
|
def timeout_override
|
104
107
|
return @timeout_override if defined?(@timeout_override)
|
105
108
|
|
106
|
-
@timeout_override = DurationParser.new(krane_annotation_value(
|
109
|
+
@timeout_override = DurationParser.new(krane_annotation_value(TIMEOUT_OVERRIDE_ANNOTATION)).parse!.to_i
|
107
110
|
rescue DurationParser::ParsingError
|
108
111
|
@timeout_override = nil
|
109
112
|
end
|
@@ -123,7 +126,6 @@ module Krane
|
|
123
126
|
@statsd_report_done = false
|
124
127
|
@disappeared = false
|
125
128
|
@validation_errors = []
|
126
|
-
@validation_warnings = []
|
127
129
|
@instance_data = {}
|
128
130
|
@server_dry_run_validated = false
|
129
131
|
end
|
@@ -134,22 +136,13 @@ module Krane
|
|
134
136
|
|
135
137
|
def validate_definition(kubectl, selector: nil)
|
136
138
|
@validation_errors = []
|
137
|
-
@validation_warnings = []
|
138
139
|
validate_selector(selector) if selector
|
139
140
|
validate_timeout_annotation
|
140
|
-
|
141
|
+
validate_deploy_method_override_annotation
|
141
142
|
validate_spec_with_kubectl(kubectl)
|
142
143
|
@validation_errors.present?
|
143
144
|
end
|
144
145
|
|
145
|
-
def validation_warning_msg
|
146
|
-
@validation_warnings.join("\n")
|
147
|
-
end
|
148
|
-
|
149
|
-
def has_warnings?
|
150
|
-
@validation_warnings.present?
|
151
|
-
end
|
152
|
-
|
153
146
|
def validation_error_msg
|
154
147
|
@validation_errors.join("\n")
|
155
148
|
end
|
@@ -228,6 +221,11 @@ module Krane
|
|
228
221
|
@type || self.class.kind
|
229
222
|
end
|
230
223
|
|
224
|
+
def group
|
225
|
+
grouping, version = @definition.dig("apiVersion").split("/")
|
226
|
+
version ? grouping : "core"
|
227
|
+
end
|
228
|
+
|
231
229
|
def kubectl_resource_type
|
232
230
|
type
|
233
231
|
end
|
@@ -242,10 +240,14 @@ module Krane
|
|
242
240
|
if @definition.dig("metadata", "name").blank? && uses_generate_name?
|
243
241
|
:create
|
244
242
|
else
|
245
|
-
:apply
|
243
|
+
deploy_method_override || :apply
|
246
244
|
end
|
247
245
|
end
|
248
246
|
|
247
|
+
def deploy_method_override
|
248
|
+
krane_annotation_value(DEPLOY_METHOD_OVERRIDE_ANNOTATION)&.to_sym
|
249
|
+
end
|
250
|
+
|
249
251
|
def sync_debug_info(kubectl)
|
250
252
|
@debug_events = fetch_events(kubectl) unless ENV[DISABLE_FETCHING_EVENT_INFO]
|
251
253
|
@debug_logs = fetch_debug_logs if print_debug_logs? && !ENV[DISABLE_FETCHING_LOG_INFO]
|
@@ -495,8 +497,8 @@ module Krane
|
|
495
497
|
private
|
496
498
|
|
497
499
|
def validate_timeout_annotation
|
498
|
-
timeout_override_value = krane_annotation_value(
|
499
|
-
timeout_annotation_key =
|
500
|
+
timeout_override_value = krane_annotation_value(TIMEOUT_OVERRIDE_ANNOTATION)
|
501
|
+
timeout_annotation_key = Annotation.for(TIMEOUT_OVERRIDE_ANNOTATION)
|
500
502
|
return if timeout_override_value.nil?
|
501
503
|
|
502
504
|
override = DurationParser.new(timeout_override_value).parse!
|
@@ -509,30 +511,18 @@ module Krane
|
|
509
511
|
@validation_errors << "#{timeout_annotation_key} annotation is invalid: #{e}"
|
510
512
|
end
|
511
513
|
|
512
|
-
def
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
"Use the 'krane.shopify.io' annotation prefix instead"
|
520
|
-
return
|
521
|
-
end
|
514
|
+
def validate_deploy_method_override_annotation
|
515
|
+
deploy_method_override_value = krane_annotation_value(DEPLOY_METHOD_OVERRIDE_ANNOTATION)
|
516
|
+
deploy_method_override_annotation_key = Annotation.for(DEPLOY_METHOD_OVERRIDE_ANNOTATION)
|
517
|
+
return unless deploy_method_override_value
|
518
|
+
unless ALLOWED_DEPLOY_METHOD_OVERRIDES.include?(deploy_method_override_value)
|
519
|
+
@validation_errors << "#{deploy_method_override_annotation_key} is invalid: Accepted values are: " \
|
520
|
+
"#{ALLOWED_DEPLOY_METHOD_OVERRIDES.join(', ')} but got #{deploy_method_override_value}"
|
522
521
|
end
|
523
522
|
end
|
524
523
|
|
525
524
|
def krane_annotation_value(suffix)
|
526
|
-
@definition.dig("metadata", "annotations",
|
527
|
-
@definition.dig("metadata", "annotations", "krane.shopify.io/#{suffix}")
|
528
|
-
end
|
529
|
-
|
530
|
-
def krane_annotation_key(suffix)
|
531
|
-
if @definition.dig("metadata", "annotations", "kubernetes-deploy.shopify.io/#{suffix}")
|
532
|
-
"kubernetes-deploy.shopify.io/#{suffix}"
|
533
|
-
elsif @definition.dig("metadata", "annotations", "krane.shopify.io/#{suffix}")
|
534
|
-
"krane.shopify.io/#{suffix}"
|
535
|
-
end
|
525
|
+
@definition.dig("metadata", "annotations", Annotation.for(suffix))
|
536
526
|
end
|
537
527
|
|
538
528
|
def validate_selector(selector)
|
@@ -572,7 +562,7 @@ module Krane
|
|
572
562
|
def validate_with_server_side_dry_run(kubectl)
|
573
563
|
command = ["apply", "-f", file_path, "--server-dry-run", "--output=name"]
|
574
564
|
kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
|
575
|
-
retry_whitelist: [:client_timeout], attempts: 3)
|
565
|
+
retry_whitelist: [:client_timeout, :empty, :context_deadline], attempts: 3)
|
576
566
|
end
|
577
567
|
|
578
568
|
# Local dry run is supported on only create and apply
|
@@ -582,7 +572,7 @@ module Krane
|
|
582
572
|
verb = deploy_method == :apply ? "apply" : "create"
|
583
573
|
command = [verb, "-f", file_path, "--dry-run", "--output=name"]
|
584
574
|
kubectl.run(*command, log_failure: false, output_is_sensitive: sensitive_template_content?,
|
585
|
-
retry_whitelist: [:client_timeout], attempts: 3, use_namespace: !global?)
|
575
|
+
retry_whitelist: [:client_timeout, :empty, :context_deadline], attempts: 3, use_namespace: !global?)
|
586
576
|
end
|
587
577
|
|
588
578
|
def labels
|
@@ -62,7 +62,7 @@ module Krane
|
|
62
62
|
kind
|
63
63
|
end
|
64
64
|
|
65
|
-
def validate_definition(
|
65
|
+
def validate_definition(*, **)
|
66
66
|
super
|
67
67
|
|
68
68
|
@crd.validate_rollout_conditions
|
@@ -70,7 +70,7 @@ module Krane
|
|
70
70
|
@validation_errors << "The CRD that specifies this resource is using invalid rollout conditions. " \
|
71
71
|
"Krane will not be able to continue until those rollout conditions are fixed.\n" \
|
72
72
|
"Rollout conditions can be found on the CRD that defines this resource (#{@crd.name}), " \
|
73
|
-
"under the annotation #{CustomResourceDefinition::ROLLOUT_CONDITIONS_ANNOTATION}.\n" \
|
73
|
+
"under the annotation #{Annotation.for(CustomResourceDefinition::ROLLOUT_CONDITIONS_ANNOTATION)}.\n" \
|
74
74
|
"Validation failed with: #{e}"
|
75
75
|
end
|
76
76
|
|
@@ -2,9 +2,8 @@
|
|
2
2
|
module Krane
|
3
3
|
class CustomResourceDefinition < KubernetesResource
|
4
4
|
TIMEOUT = 2.minutes
|
5
|
-
|
6
|
-
|
7
|
-
TIMEOUT_FOR_INSTANCE_ANNOTATION = "krane.shopify.io/instance-timeout"
|
5
|
+
ROLLOUT_CONDITIONS_ANNOTATION = "instance-rollout-conditions"
|
6
|
+
TIMEOUT_FOR_INSTANCE_ANNOTATION = "instance-timeout"
|
8
7
|
GLOBAL = true
|
9
8
|
|
10
9
|
def deploy_succeeded?
|
@@ -20,7 +19,7 @@ module Krane
|
|
20
19
|
end
|
21
20
|
|
22
21
|
def timeout_for_instance
|
23
|
-
timeout = krane_annotation_value(
|
22
|
+
timeout = krane_annotation_value(TIMEOUT_FOR_INSTANCE_ANNOTATION)
|
24
23
|
DurationParser.new(timeout).parse!.to_i
|
25
24
|
rescue DurationParser::ParsingError
|
26
25
|
nil
|
@@ -46,6 +45,10 @@ module Krane
|
|
46
45
|
@definition.dig("spec", "names", "kind")
|
47
46
|
end
|
48
47
|
|
48
|
+
def group
|
49
|
+
@definition.dig("spec", "group")
|
50
|
+
end
|
51
|
+
|
49
52
|
def prunable?
|
50
53
|
prunable = krane_annotation_value("prunable")
|
51
54
|
prunable == "true"
|
@@ -59,25 +62,25 @@ module Krane
|
|
59
62
|
def rollout_conditions
|
60
63
|
return @rollout_conditions if defined?(@rollout_conditions)
|
61
64
|
|
62
|
-
@rollout_conditions = if krane_annotation_value(
|
63
|
-
RolloutConditions.from_annotation(krane_annotation_value(
|
65
|
+
@rollout_conditions = if krane_annotation_value(ROLLOUT_CONDITIONS_ANNOTATION)
|
66
|
+
RolloutConditions.from_annotation(krane_annotation_value(ROLLOUT_CONDITIONS_ANNOTATION))
|
64
67
|
end
|
65
68
|
rescue RolloutConditionsError
|
66
69
|
@rollout_conditions = nil
|
67
70
|
end
|
68
71
|
|
69
|
-
def validate_definition(
|
72
|
+
def validate_definition(*, **)
|
70
73
|
super
|
71
74
|
|
72
75
|
validate_rollout_conditions
|
73
76
|
rescue RolloutConditionsError => e
|
74
|
-
@validation_errors << "Annotation #{
|
77
|
+
@validation_errors << "Annotation #{Annotation.for(ROLLOUT_CONDITIONS_ANNOTATION)} " \
|
75
78
|
"on #{name} is invalid: #{e}"
|
76
79
|
end
|
77
80
|
|
78
81
|
def validate_rollout_conditions
|
79
|
-
if krane_annotation_value(
|
80
|
-
conditions = RolloutConditions.from_annotation(krane_annotation_value(
|
82
|
+
if krane_annotation_value(ROLLOUT_CONDITIONS_ANNOTATION) && @rollout_conditions_validated.nil?
|
83
|
+
conditions = RolloutConditions.from_annotation(krane_annotation_value(ROLLOUT_CONDITIONS_ANNOTATION))
|
81
84
|
conditions.validate!
|
82
85
|
end
|
83
86
|
|
@@ -5,9 +5,7 @@ module Krane
|
|
5
5
|
class Deployment < KubernetesResource
|
6
6
|
TIMEOUT = 7.minutes
|
7
7
|
SYNC_DEPENDENCIES = %w(Pod ReplicaSet)
|
8
|
-
|
9
|
-
REQUIRED_ROLLOUT_ANNOTATION_DEPRECATED = "kubernetes-deploy.shopify.io/#{REQUIRED_ROLLOUT_ANNOTATION_SUFFIX}"
|
10
|
-
REQUIRED_ROLLOUT_ANNOTATION = "krane.shopify.io/#{REQUIRED_ROLLOUT_ANNOTATION_SUFFIX}"
|
8
|
+
REQUIRED_ROLLOUT_ANNOTATION = "required-rollout"
|
11
9
|
REQUIRED_ROLLOUT_TYPES = %w(maxUnavailable full none).freeze
|
12
10
|
DEFAULT_REQUIRED_ROLLOUT = 'full'
|
13
11
|
|
@@ -97,7 +95,7 @@ module Krane
|
|
97
95
|
progress_condition.present? ? deploy_failing_to_progress? : super
|
98
96
|
end
|
99
97
|
|
100
|
-
def validate_definition(
|
98
|
+
def validate_definition(*, **)
|
101
99
|
super
|
102
100
|
|
103
101
|
unless REQUIRED_ROLLOUT_TYPES.include?(required_rollout) || percent?(required_rollout)
|
@@ -106,7 +104,7 @@ module Krane
|
|
106
104
|
|
107
105
|
strategy = @definition.dig('spec', 'strategy', 'type').to_s
|
108
106
|
if required_rollout.downcase == 'maxunavailable' && strategy.present? && strategy.downcase != 'rollingupdate'
|
109
|
-
@validation_errors << "'#{
|
107
|
+
@validation_errors << "'#{Annotation.for(REQUIRED_ROLLOUT_ANNOTATION)}: #{required_rollout}' " \
|
110
108
|
"is incompatible with strategy '#{strategy}'"
|
111
109
|
end
|
112
110
|
|
@@ -151,7 +149,7 @@ module Krane
|
|
151
149
|
end
|
152
150
|
|
153
151
|
def rollout_annotation_err_msg
|
154
|
-
"'#{
|
152
|
+
"'#{Annotation.for(REQUIRED_ROLLOUT_ANNOTATION)}: #{required_rollout}' is invalid. " \
|
155
153
|
"Acceptable values: #{REQUIRED_ROLLOUT_TYPES.join(', ')}"
|
156
154
|
end
|
157
155
|
|
@@ -191,7 +189,7 @@ module Krane
|
|
191
189
|
def min_available_replicas
|
192
190
|
if percent?(required_rollout)
|
193
191
|
(desired_replicas * required_rollout.to_i / 100.0).ceil
|
194
|
-
elsif max_unavailable =~ /%/
|
192
|
+
elsif max_unavailable.is_a?(String) && max_unavailable =~ /%/
|
195
193
|
(desired_replicas * (100 - max_unavailable.to_i) / 100.0).ceil
|
196
194
|
else
|
197
195
|
desired_replicas - max_unavailable.to_i
|
@@ -219,14 +219,7 @@ module Krane
|
|
219
219
|
limbo_reason = @status.dig("state", "waiting", "reason")
|
220
220
|
limbo_message = @status.dig("state", "waiting", "message")
|
221
221
|
|
222
|
-
if
|
223
|
-
# ref: https://github.com/kubernetes/kubernetes/blob/562e721ece8a16e05c7e7d6bdd6334c910733ab2/pkg/kubelet/dockershim/docker_container.go#L353
|
224
|
-
exit_code = @status.dig('lastState', 'terminated', 'exitCode')
|
225
|
-
"Failed to start (exit #{exit_code}): #{@status.dig('lastState', 'terminated', 'message')}"
|
226
|
-
elsif @status.dig("state", "terminated", "reason") == "ContainerCannotRun"
|
227
|
-
exit_code = @status.dig('state', 'terminated', 'exitCode')
|
228
|
-
"Failed to start (exit #{exit_code}): #{@status.dig('state', 'terminated', 'message')}"
|
229
|
-
elsif limbo_reason == "CrashLoopBackOff"
|
222
|
+
if limbo_reason == "CrashLoopBackOff"
|
230
223
|
exit_code = @status.dig('lastState', 'terminated', 'exitCode')
|
231
224
|
"Crashing repeatedly (exit #{exit_code}). See logs for more information."
|
232
225
|
elsif limbo_reason == "ErrImagePull" && limbo_message.match(/not found/i)
|
@@ -234,6 +227,17 @@ module Krane
|
|
234
227
|
"Did you wait for it to be built and pushed to the registry before deploying?"
|
235
228
|
elsif limbo_reason == "CreateContainerConfigError"
|
236
229
|
"Failed to generate container configuration: #{limbo_message}"
|
230
|
+
elsif @status.dig("lastState", "terminated", "reason") == "ContainerCannotRun"
|
231
|
+
# ref: https://github.com/kubernetes/kubernetes/blob/562e721ece8a16e05c7e7d6bdd6334c910733ab2/pkg/kubelet/dockershim/docker_container.go#L353
|
232
|
+
exit_code = @status.dig('lastState', 'terminated', 'exitCode')
|
233
|
+
# We've observed failures here that are actually issues with the node or kube infra, and not with the
|
234
|
+
# container. These issues have been transient and result in a 128 exit code, so do not treat these as fatal.
|
235
|
+
return if exit_code == 128
|
236
|
+
"Failed to start (exit #{exit_code}): #{@status.dig('lastState', 'terminated', 'message')}"
|
237
|
+
elsif @status.dig("state", "terminated", "reason") == "ContainerCannotRun"
|
238
|
+
exit_code = @status.dig('state', 'terminated', 'exitCode')
|
239
|
+
return if exit_code == 128
|
240
|
+
"Failed to start (exit #{exit_code}): #{@status.dig('state', 'terminated', 'message')}"
|
237
241
|
end
|
238
242
|
end
|
239
243
|
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'psych'
|
4
|
+
|
5
|
+
module PsychK8sCompatibility
|
6
|
+
def self.massage_node(n)
|
7
|
+
if n.is_a?(Psych::Nodes::Scalar) &&
|
8
|
+
(n.style == Psych::Nodes::Scalar::PLAIN) &&
|
9
|
+
n.value.is_a?(String) &&
|
10
|
+
n.value =~ /\A[+-]?\d+(?:\.\d+)?[eE][+-]?\d+\z/
|
11
|
+
n.style = Psych::Nodes::Scalar::DOUBLE_QUOTED
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
refine Psych.singleton_class do
|
16
|
+
def dump(o, io = nil, options = {})
|
17
|
+
if io.is_a?(Hash)
|
18
|
+
options = io
|
19
|
+
io = nil
|
20
|
+
end
|
21
|
+
visitor = Psych::Visitors::YAMLTree.create(options)
|
22
|
+
visitor << o
|
23
|
+
visitor.tree.each { |n| PsychK8sCompatibility.massage_node(n) }
|
24
|
+
visitor.tree.yaml(io, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def dump_stream(*objects)
|
28
|
+
visitor = Psych::Visitors::YAMLTree.create({})
|
29
|
+
objects.each do |o|
|
30
|
+
visitor << o
|
31
|
+
end
|
32
|
+
visitor.tree.each { |n| PsychK8sCompatibility.massage_node(n) }
|
33
|
+
visitor.tree.yaml
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/krane/render_task.rb
CHANGED