kubernetes-deploy 0.20.6 → 0.21.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.buildkite/pipeline.nightly.yml +16 -7
- data/.buildkite/pipeline.yml +16 -7
- data/CHANGELOG.md +7 -2
- data/README.md +21 -7
- data/bin/setup +2 -2
- data/dev.yml +1 -1
- data/exe/kubernetes-run +10 -1
- data/lib/kubernetes-deploy.rb +1 -1
- data/lib/kubernetes-deploy/container_logs.rb +87 -0
- data/lib/kubernetes-deploy/deferred_summary_logging.rb +8 -7
- data/lib/kubernetes-deploy/deploy_task.rb +0 -1
- data/lib/kubernetes-deploy/errors.rb +1 -0
- data/lib/kubernetes-deploy/kubeclient_builder.rb +5 -1
- data/lib/kubernetes-deploy/kubectl.rb +3 -1
- data/lib/kubernetes-deploy/kubernetes_resource.rb +26 -15
- data/lib/kubernetes-deploy/kubernetes_resource/daemon_set.rb +6 -3
- data/lib/kubernetes-deploy/kubernetes_resource/deployment.rb +6 -9
- data/lib/kubernetes-deploy/kubernetes_resource/pod.rb +26 -43
- data/lib/kubernetes-deploy/kubernetes_resource/pod_set_base.rb +8 -13
- data/lib/kubernetes-deploy/kubernetes_resource/stateful_set.rb +2 -4
- data/lib/kubernetes-deploy/remote_logs.rb +49 -0
- data/lib/kubernetes-deploy/resource_watcher.rb +23 -10
- data/lib/kubernetes-deploy/runner_task.rb +114 -69
- data/lib/kubernetes-deploy/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1155a58879313f8d0466b9d551a5ba9fb4a0ccab
|
4
|
+
data.tar.gz: a5856d1be46d1aef9a6393e4b5f95715e5832284
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9eecadcbaa7709aae55ba3baac6e0afaf120277df59c1dae1a407bd49f6f07755757448de776b7f33ed30298cacad081276ba2afcee4927d2b56ae899ea9a196
|
7
|
+
data.tar.gz: 75cc0f78449088f47d621da287477c77a01ef33a0d1460fc5739676fee82d5a6652542412c483cd2ad80c708ed72a8350573a57515e848100b5c27cb6b9a2129
|
@@ -1,5 +1,19 @@
|
|
1
|
+
shared: &shared
|
2
|
+
retry:
|
3
|
+
automatic:
|
4
|
+
- exit_status: "*"
|
5
|
+
limit: 1
|
1
6
|
steps:
|
7
|
+
- name: 'Run Test Suite (:kubernetes: 1.11-latest)'
|
8
|
+
<<: *shared
|
9
|
+
command: bin/ci
|
10
|
+
agents:
|
11
|
+
queue: k8s-ci
|
12
|
+
env:
|
13
|
+
LOGGING_LEVEL: 4
|
14
|
+
KUBERNETES_VERSION: v1.11-latest
|
2
15
|
- name: 'Run Test Suite (:kubernetes: 1.10-latest)'
|
16
|
+
<<: *shared
|
3
17
|
command: bin/ci
|
4
18
|
agents:
|
5
19
|
queue: minikube-ci
|
@@ -7,6 +21,7 @@ steps:
|
|
7
21
|
LOGGING_LEVEL: 4
|
8
22
|
KUBERNETES_VERSION: v1.10-latest
|
9
23
|
- name: 'Run Test Suite (:kubernetes: 1.9-latest)'
|
24
|
+
<<: *shared
|
10
25
|
command: bin/ci
|
11
26
|
agents:
|
12
27
|
queue: minikube-ci
|
@@ -14,16 +29,10 @@ steps:
|
|
14
29
|
LOGGING_LEVEL: 4
|
15
30
|
KUBERNETES_VERSION: v1.9-latest
|
16
31
|
- name: 'Run Test Suite (:kubernetes: 1.8-latest)'
|
32
|
+
<<: *shared
|
17
33
|
command: bin/ci
|
18
34
|
agents:
|
19
35
|
queue: minikube-ci
|
20
36
|
env:
|
21
37
|
LOGGING_LEVEL: 4
|
22
38
|
KUBERNETES_VERSION: v1.8-latest
|
23
|
-
- name: 'Run Test Suite (:kubernetes: 1.7-latest)'
|
24
|
-
command: bin/ci
|
25
|
-
agents:
|
26
|
-
queue: minikube-ci
|
27
|
-
env:
|
28
|
-
LOGGING_LEVEL: 4
|
29
|
-
KUBERNETES_VERSION: v1.7-latest
|
data/.buildkite/pipeline.yml
CHANGED
@@ -1,5 +1,19 @@
|
|
1
|
+
shared: &shared
|
2
|
+
retry:
|
3
|
+
automatic:
|
4
|
+
- exit_status: "*"
|
5
|
+
limit: 1
|
1
6
|
steps:
|
7
|
+
- name: 'Run Test Suite (:kubernetes: 1.11-latest)'
|
8
|
+
<<: *shared
|
9
|
+
command: bin/ci
|
10
|
+
agents:
|
11
|
+
queue: k8s-ci
|
12
|
+
env:
|
13
|
+
LOGGING_LEVEL: 4
|
14
|
+
KUBERNETES_VERSION: v1.11-latest
|
2
15
|
- name: 'Run Test Suite (:kubernetes: 1.10-latest)'
|
16
|
+
<<: *shared
|
3
17
|
command: bin/ci
|
4
18
|
agents:
|
5
19
|
queue: minikube-ci
|
@@ -7,6 +21,7 @@ steps:
|
|
7
21
|
LOGGING_LEVEL: 4
|
8
22
|
KUBERNETES_VERSION: v1.10-latest
|
9
23
|
- name: 'Run Test Suite (:kubernetes: 1.9-latest)'
|
24
|
+
<<: *shared
|
10
25
|
command: bin/ci
|
11
26
|
agents:
|
12
27
|
queue: minikube-ci
|
@@ -14,16 +29,10 @@ steps:
|
|
14
29
|
LOGGING_LEVEL: 4
|
15
30
|
KUBERNETES_VERSION: v1.9-latest
|
16
31
|
- name: 'Run Test Suite (:kubernetes: 1.8-latest)'
|
32
|
+
<<: *shared
|
17
33
|
command: bin/ci
|
18
34
|
agents:
|
19
35
|
queue: minikube-ci
|
20
36
|
env:
|
21
37
|
LOGGING_LEVEL: 4
|
22
38
|
KUBERNETES_VERSION: v1.8-latest
|
23
|
-
- name: 'Run Test Suite (:kubernetes: 1.7-latest)'
|
24
|
-
command: bin/ci
|
25
|
-
agents:
|
26
|
-
queue: minikube-ci
|
27
|
-
env:
|
28
|
-
LOGGING_LEVEL: 4
|
29
|
-
KUBERNETES_VERSION: v1.7-latest
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,11 @@
|
|
1
|
-
|
1
|
+
## 0.21.0
|
2
2
|
|
3
|
-
*
|
3
|
+
*Features*
|
4
|
+
- **[Breaking change]** `kubernetes-run` now streams container logs and waits for the pod to succeed or fail **by default**. You can disable this using `--skip-wait`, or you can use `--max-watch-seconds=seconds` to set a time limit on the watch. ([#337](https://github.com/Shopify/kubernetes-deploy/pull/337))
|
5
|
+
|
6
|
+
|
7
|
+
*Other*
|
8
|
+
- Kubernetes 1.7 is no longer officially supported as of this version
|
4
9
|
|
5
10
|
## 0.20.6
|
6
11
|
|
data/README.md
CHANGED
@@ -69,19 +69,21 @@ This repo also includes related tools for [running tasks](#kubernetes-run) and [
|
|
69
69
|
## Prerequisites
|
70
70
|
|
71
71
|
* Ruby 2.3+
|
72
|
-
* Your cluster must be running Kubernetes v1.
|
72
|
+
* Your cluster must be running Kubernetes v1.8.0 or higher<sup>1</sup>
|
73
73
|
* Each app must have a deploy directory containing its Kubernetes templates (see [Templates](#using-templates-and-variables))
|
74
74
|
* You must remove the` kubectl.kubernetes.io/last-applied-configuration` annotation from any resources in the namespace that are not included in your deploy directory. This annotation is added automatically when you create resources with `kubectl apply`. `kubernetes-deploy` will prune any resources that have this annotation and are not in the deploy directory.<sup>2</sup>
|
75
75
|
* Each app managed by `kubernetes-deploy` must have its own exclusive Kubernetes namespace.
|
76
76
|
|
77
|
-
<sup>1</sup> We run integration tests against these Kubernetes versions. Kubernetes v1.
|
77
|
+
<sup>1</sup> We run integration tests against these Kubernetes versions. Kubernetes v1.7 was officially
|
78
|
+
supported in gem versions < 0.21. Kubernetes v1.6 was officially supported in gem versions < 0.16. Kubernetes
|
79
|
+
v1.5 was officially supported in gem versions < 0.12.
|
78
80
|
|
79
81
|
<sup>2</sup> This requirement can be bypassed with the `--no-prune` option, but it is not recommended.
|
80
82
|
|
81
83
|
|
82
84
|
## Installation
|
83
85
|
|
84
|
-
1. [Install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-binary-via-curl) (requires v1.
|
86
|
+
1. [Install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-binary-via-curl) (requires v1.8.0 or higher) and make sure it is available in your $PATH
|
85
87
|
2. Set up your [kubeconfig file](https://kubernetes.io/docs/tasks/access-application-cluster/authenticate-across-clusters-kubeconfig/) for access to your cluster(s).
|
86
88
|
3. `gem install kubernetes-deploy`
|
87
89
|
|
@@ -352,12 +354,15 @@ Based on this specification `kubernetes-run` will create a new pod with the entr
|
|
352
354
|
|
353
355
|
## Usage
|
354
356
|
|
355
|
-
`kubernetes-run <kube namespace> <kube context> <arguments> --entrypoint
|
357
|
+
`kubernetes-run <kube namespace> <kube context> <arguments> --entrypoint=<entrypoint> --template=<template name>`
|
356
358
|
|
357
359
|
*Options:*
|
358
360
|
|
359
361
|
* `--template=TEMPLATE`: Specifies the name of the PodTemplate to use (default is `task-runner-template` if this option is not set).
|
360
362
|
* `--env-vars=ENV_VARS`: Accepts a comma separated list of environment variables to be added to the pod template. For example, `--env-vars="ENV=VAL,ENV2=VAL2"` will make `ENV` and `ENV2` available to the container.
|
363
|
+
* `--entrypoint=ENTRYPOINT`: Specify the entrypoint to use to start the task runner container.
|
364
|
+
* `--skip-wait`: Skip verification of pod success
|
365
|
+
* `--max-watch-seconds=seconds`: Raise a timeout error if the pod runs for longer than the specified number of seconds
|
361
366
|
|
362
367
|
|
363
368
|
|
@@ -369,7 +374,7 @@ Based on this specification `kubernetes-run` will create a new pod with the entr
|
|
369
374
|
|
370
375
|
If you work for Shopify, just run `dev up`, but otherwise:
|
371
376
|
|
372
|
-
1. [Install kubectl version 1.
|
377
|
+
1. [Install kubectl version 1.8.0 or higher](https://kubernetes.io/docs/user-guide/prereqs/) and make sure it is in your path
|
373
378
|
2. [Install minikube](https://kubernetes.io/docs/getting-started-guides/minikube/#installation) (required to run the test suite)
|
374
379
|
3. Check out the repo
|
375
380
|
4. Run `bin/setup` to install dependencies
|
@@ -423,9 +428,18 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/Shopif
|
|
423
428
|
|
424
429
|
Contributions to help us support additional resource types or increase the sophistication of our success heuristics for an existing type are especially encouraged! (See tips below)
|
425
430
|
|
431
|
+
## Feature acceptance guidelines
|
426
432
|
|
433
|
+
- This project's mission is to make it easy to ship changes to a Kubernetes namespace and understand the result. Features that introduce new classes of responsibility to the tool are not usually accepted.
|
434
|
+
- Deploys can be a very tempting place to cram features. Imagine a proposed feature actually fits better elsewhere—where might that be? (Examples: validator in CI, custom controller, initializer, pre-processing step in the CD pipeline, or even Kubernetes core)
|
435
|
+
- The basic ERB renderer included with the tool is intended as a convenience feature for a better out-of-the box experience. Providing complex rendering capabilities is out of scope of this project's mission, and enhancements in this area may be rejected.
|
436
|
+
- The deploy command does not officially support non-namespaced resource types.
|
437
|
+
- This project strives to be composable with other tools in the ecosystem, such as renderers and validators. The deploy command must work with any Kubernetes templates provided to it, no matter how they were generated.
|
438
|
+
- This project is open-source. Features tied to any specific organization (including Shopify) will be rejected.
|
439
|
+
- The deploy command must remain performant when given several hundred resources at a time, generating 1000+ pods. (Technical note: This means only `sync` methods can make calls to the Kuberentes API server during result verification. This both limits the number of API calls made and ensures a consistent view of the world within each polling cycle.)
|
440
|
+
- This tool must be able to run concurrent deploys to different targets safely, including when used as a library.
|
427
441
|
|
428
|
-
|
442
|
+
## Contributing a new resource type
|
429
443
|
|
430
444
|
The list of fully supported types is effectively the list of classes found in `lib/kubernetes-deploy/kubernetes_resource/`.
|
431
445
|
|
@@ -440,7 +454,7 @@ This gem uses subclasses of `KubernetesResource` to implement custom success/fai
|
|
440
454
|
4. Add the a basic example of the type to the hello-cloud [fixture set](https://github.com/Shopify/kubernetes-deploy/tree/master/test/fixtures/hello-cloud) and appropriate assertions to `#assert_all_up` in [`hello_cloud.rb`](https://github.com/Shopify/kubernetes-deploy/blob/master/test/helpers/fixture_sets/hello_cloud.rb). This will get you coverage in several existing tests, such as `test_full_hello_cloud_set_deploy_succeeds`.
|
441
455
|
5. Add tests for any edge cases you foresee.
|
442
456
|
|
443
|
-
|
457
|
+
## Code of Conduct
|
444
458
|
Everyone is expected to follow our [Code of Conduct](https://github.com/Shopify/kubernetes-deploy/blob/master/CODE_OF_CONDUCT.md).
|
445
459
|
|
446
460
|
|
data/bin/setup
CHANGED
@@ -9,8 +9,8 @@ if [ ! -x "$(which minikube)" ]; then
|
|
9
9
|
fi
|
10
10
|
|
11
11
|
if [ ! -x "$(which kubectl)" ]; then
|
12
|
-
echo -e "\n\033[0;33mPlease install kubectl version 1.
|
12
|
+
echo -e "\n\033[0;33mPlease install kubectl version 1.8.0 or higher:\nhttps://kubernetes.io/docs/user-guide/prereqs/\033[0m"
|
13
13
|
else
|
14
14
|
KUBECTL_VERSION=$(kubectl version --short --client | grep -oe "v[[:digit:]\.]\+")
|
15
|
-
echo -e "\n\033[0;32mKubectl version $KUBECTL_VERSION is already installed. This gem requires version v1.
|
15
|
+
echo -e "\n\033[0;32mKubectl version $KUBECTL_VERSION is already installed. This gem requires version v1.8.0 or greater.\033[0m"
|
16
16
|
fi
|
data/dev.yml
CHANGED
@@ -8,7 +8,7 @@ up:
|
|
8
8
|
- custom:
|
9
9
|
name: Minikube Cluster
|
10
10
|
met?: test $(minikube status | grep Running | wc -l) -eq 2 && $(minikube status | grep -q 'Correctly Configured')
|
11
|
-
meet: minikube start --kubernetes-version=v1.10.
|
11
|
+
meet: minikube start --kubernetes-version=v1.10.6 --vm-driver=hyperkit
|
12
12
|
down: minikube stop
|
13
13
|
commands:
|
14
14
|
reset-minikube: minikube delete && rm -rf ~/.minikube
|
data/exe/kubernetes-run
CHANGED
@@ -8,8 +8,15 @@ require 'optparse'
|
|
8
8
|
template = "task-runner-template"
|
9
9
|
entrypoint = nil
|
10
10
|
env_vars = []
|
11
|
+
verify_result = true
|
12
|
+
max_watch_seconds = nil
|
11
13
|
|
12
14
|
ARGV.options do |opts|
|
15
|
+
opts.on("--skip-wait", "Skip verification of pod success") { verify_result = false }
|
16
|
+
opts.on("--max-watch-seconds=seconds",
|
17
|
+
"Timeout error is raised if the pod runs for longer than the specified number of seconds") do |t|
|
18
|
+
max_watch_seconds = t.to_i
|
19
|
+
end
|
13
20
|
opts.on("--template=TEMPLATE") { |n| template = n }
|
14
21
|
opts.on("--env-vars=ENV_VARS") { |vars| env_vars = vars.split(",") }
|
15
22
|
opts.on("--entrypoint=ENTRYPOINT") { |c| entrypoint = [c] }
|
@@ -23,10 +30,12 @@ logger = KubernetesDeploy::FormattedLogger.build(namespace, context)
|
|
23
30
|
runner = KubernetesDeploy::RunnerTask.new(
|
24
31
|
namespace: namespace,
|
25
32
|
context: context,
|
26
|
-
logger: logger
|
33
|
+
logger: logger,
|
34
|
+
max_watch_seconds: max_watch_seconds
|
27
35
|
)
|
28
36
|
|
29
37
|
success = runner.run(
|
38
|
+
verify_result: verify_result,
|
30
39
|
task_template: template,
|
31
40
|
entrypoint: entrypoint,
|
32
41
|
args: ARGV[2..-1],
|
data/lib/kubernetes-deploy.rb
CHANGED
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module KubernetesDeploy
|
3
|
+
class ContainerLogs
|
4
|
+
attr_reader :lines, :container_name
|
5
|
+
|
6
|
+
DEFAULT_LINE_LIMIT = 250
|
7
|
+
|
8
|
+
def initialize(parent_id:, container_name:, logger:)
|
9
|
+
@parent_id = parent_id
|
10
|
+
@container_name = container_name
|
11
|
+
@logger = logger
|
12
|
+
@lines = []
|
13
|
+
@next_print_index = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def sync(kubectl)
|
17
|
+
new_logs = fetch_latest(kubectl)
|
18
|
+
return unless new_logs.present?
|
19
|
+
@lines += deduplicate(new_logs)
|
20
|
+
end
|
21
|
+
|
22
|
+
def empty?
|
23
|
+
lines.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
def print_latest(prefix: false)
|
27
|
+
prefix_str = "[#{container_name}] " if prefix
|
28
|
+
|
29
|
+
lines[@next_print_index..-1].each do |msg|
|
30
|
+
@logger.info "#{prefix_str}#{msg}"
|
31
|
+
end
|
32
|
+
|
33
|
+
@next_print_index = lines.length
|
34
|
+
end
|
35
|
+
|
36
|
+
def print_all
|
37
|
+
lines.each { |line| @logger.info("\t#{line}") }
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def fetch_latest(kubectl)
|
43
|
+
cmd = ["logs", @parent_id, "--container=#{container_name}", "--timestamps"]
|
44
|
+
cmd << if @last_timestamp.present?
|
45
|
+
"--since-time=#{rfc3339_timestamp(@last_timestamp)}"
|
46
|
+
else
|
47
|
+
"--tail=#{DEFAULT_LINE_LIMIT}"
|
48
|
+
end
|
49
|
+
out, _err, _st = kubectl.run(*cmd, log_failure: false)
|
50
|
+
out.split("\n")
|
51
|
+
end
|
52
|
+
|
53
|
+
def rfc3339_timestamp(time)
|
54
|
+
time.strftime("%FT%T.%N%:z")
|
55
|
+
end
|
56
|
+
|
57
|
+
def deduplicate(logs)
|
58
|
+
deduped = []
|
59
|
+
check_for_duplicate = true
|
60
|
+
|
61
|
+
logs.each do |line|
|
62
|
+
timestamp, msg = split_timestamped_line(line)
|
63
|
+
next if check_for_duplicate && likely_duplicate?(timestamp)
|
64
|
+
check_for_duplicate = false # logs are ordered, so once we've seen a new one, assume all subsequent logs are new
|
65
|
+
@last_timestamp = timestamp if timestamp
|
66
|
+
deduped << msg
|
67
|
+
end
|
68
|
+
|
69
|
+
deduped
|
70
|
+
end
|
71
|
+
|
72
|
+
def split_timestamped_line(log_line)
|
73
|
+
timestamp, message = log_line.split(" ", 2)
|
74
|
+
[Time.parse(timestamp), message]
|
75
|
+
rescue ArgumentError
|
76
|
+
# If the log file can't be opened, k8s 1.8 writes an error message without a timestamp to stdout
|
77
|
+
[nil, log_line]
|
78
|
+
end
|
79
|
+
|
80
|
+
def likely_duplicate?(timestamp)
|
81
|
+
return false unless @last_timestamp && timestamp
|
82
|
+
# The --since-time granularity the API server supports is not adequate to prevent duplicates
|
83
|
+
# This comparison takes the fractional seconds into account
|
84
|
+
timestamp <= @last_timestamp
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -47,11 +47,15 @@ module KubernetesDeploy
|
|
47
47
|
level = :fatal
|
48
48
|
end
|
49
49
|
|
50
|
-
|
51
|
-
|
50
|
+
if actions_sentence = summary.actions_sentence.presence
|
51
|
+
public_send(level, actions_sentence)
|
52
52
|
blank_line(level)
|
53
|
+
end
|
54
|
+
|
55
|
+
summary.paragraphs.each do |para|
|
53
56
|
msg_lines = para.split("\n")
|
54
57
|
msg_lines.each { |line| public_send(level, line) }
|
58
|
+
blank_line(level) unless para == summary.paragraphs.last
|
55
59
|
end
|
56
60
|
end
|
57
61
|
|
@@ -64,11 +68,8 @@ module KubernetesDeploy
|
|
64
68
|
end
|
65
69
|
|
66
70
|
def actions_sentence
|
67
|
-
|
68
|
-
|
69
|
-
else
|
70
|
-
@actions_taken.to_sentence.capitalize
|
71
|
-
end
|
71
|
+
return unless @actions_taken.present?
|
72
|
+
@actions_taken.to_sentence.capitalize
|
72
73
|
end
|
73
74
|
|
74
75
|
# Saves a sentence fragment to be displayed in the first sentence of the summary section
|
@@ -89,7 +89,11 @@ module KubernetesDeploy
|
|
89
89
|
"#{kube_context.api_endpoint}#{endpoint_path}",
|
90
90
|
api_version,
|
91
91
|
ssl_options: kube_context.ssl_options,
|
92
|
-
auth_options: kube_context.auth_options
|
92
|
+
auth_options: kube_context.auth_options,
|
93
|
+
timeouts: {
|
94
|
+
open: KubernetesDeploy::Kubectl::DEFAULT_TIMEOUT,
|
95
|
+
read: KubernetesDeploy::Kubectl::DEFAULT_TIMEOUT
|
96
|
+
}
|
93
97
|
)
|
94
98
|
client.discover
|
95
99
|
client
|
@@ -2,7 +2,9 @@
|
|
2
2
|
|
3
3
|
module KubernetesDeploy
|
4
4
|
class Kubectl
|
5
|
-
|
5
|
+
DEFAULT_TIMEOUT = 30
|
6
|
+
|
7
|
+
def initialize(namespace:, context:, logger:, log_failure_by_default:, default_timeout: DEFAULT_TIMEOUT,
|
6
8
|
output_is_sensitive: false)
|
7
9
|
@namespace = namespace
|
8
10
|
@context = context
|
@@ -3,6 +3,8 @@ require 'json'
|
|
3
3
|
require 'open3'
|
4
4
|
require 'shellwords'
|
5
5
|
|
6
|
+
require 'kubernetes-deploy/remote_logs'
|
7
|
+
|
6
8
|
module KubernetesDeploy
|
7
9
|
class KubernetesResource
|
8
10
|
attr_reader :name, :namespace, :context
|
@@ -87,6 +89,10 @@ module KubernetesDeploy
|
|
87
89
|
@instance_data = {}
|
88
90
|
end
|
89
91
|
|
92
|
+
def to_kubeclient_resource
|
93
|
+
Kubeclient::Resource.new(@definition)
|
94
|
+
end
|
95
|
+
|
90
96
|
def validate_definition(kubectl)
|
91
97
|
@validation_errors = []
|
92
98
|
validate_timeout_annotation
|
@@ -118,6 +124,9 @@ module KubernetesDeploy
|
|
118
124
|
@instance_data = mediator.get_instance(kubectl_resource_type, name)
|
119
125
|
end
|
120
126
|
|
127
|
+
def after_sync
|
128
|
+
end
|
129
|
+
|
121
130
|
def deploy_failed?
|
122
131
|
false
|
123
132
|
end
|
@@ -173,9 +182,8 @@ module KubernetesDeploy
|
|
173
182
|
end
|
174
183
|
|
175
184
|
def sync_debug_info(kubectl)
|
176
|
-
@
|
177
|
-
@
|
178
|
-
@debug_info_synced = true
|
185
|
+
@debug_events = fetch_events(kubectl) unless ENV[DISABLE_FETCHING_EVENT_INFO]
|
186
|
+
@debug_logs = fetch_debug_logs(kubectl) if print_debug_logs? && !ENV[DISABLE_FETCHING_LOG_INFO]
|
179
187
|
end
|
180
188
|
|
181
189
|
def debug_message(cause = nil, info_hash = {})
|
@@ -198,9 +206,9 @@ module KubernetesDeploy
|
|
198
206
|
end
|
199
207
|
helpful_info << " - Final status: #{status}"
|
200
208
|
|
201
|
-
if @
|
209
|
+
if @debug_events.present?
|
202
210
|
helpful_info << " - Events (common success events excluded):"
|
203
|
-
@
|
211
|
+
@debug_events.each do |identifier, event_hashes|
|
204
212
|
event_hashes.each { |event| helpful_info << " [#{identifier}]\t#{event}" }
|
205
213
|
end
|
206
214
|
elsif ENV[DISABLE_FETCHING_EVENT_INFO]
|
@@ -209,21 +217,24 @@ module KubernetesDeploy
|
|
209
217
|
helpful_info << " - Events: #{DEBUG_RESOURCE_NOT_FOUND_MESSAGE}"
|
210
218
|
end
|
211
219
|
|
212
|
-
if
|
220
|
+
if print_debug_logs?
|
213
221
|
if ENV[DISABLE_FETCHING_LOG_INFO]
|
214
222
|
helpful_info << " - Logs: #{DISABLED_LOG_INFO_MESSAGE}"
|
215
|
-
elsif @
|
223
|
+
elsif @debug_logs.blank?
|
216
224
|
helpful_info << " - Logs: #{DEBUG_RESOURCE_NOT_FOUND_MESSAGE}"
|
217
225
|
else
|
218
|
-
|
219
|
-
|
220
|
-
if
|
221
|
-
helpful_info << " - Logs from container '#{
|
226
|
+
container_logs = @debug_logs.container_logs.sort_by { |c| c.lines.length }
|
227
|
+
container_logs.each do |logs|
|
228
|
+
if logs.empty?
|
229
|
+
helpful_info << " - Logs from container '#{logs.container_name}': #{DEBUG_RESOURCE_NOT_FOUND_MESSAGE}"
|
222
230
|
next
|
223
231
|
end
|
224
232
|
|
225
|
-
|
226
|
-
|
233
|
+
if logs.lines.length == ContainerLogs::DEFAULT_LINE_LIMIT
|
234
|
+
truncated = " (last #{ContainerLogs::DEFAULT_LINE_LIMIT} lines shown)"
|
235
|
+
end
|
236
|
+
helpful_info << " - Logs from container '#{logs.container_name}'#{truncated}:"
|
237
|
+
logs.lines.each do |line|
|
227
238
|
helpful_info << " #{line}"
|
228
239
|
end
|
229
240
|
end
|
@@ -366,8 +377,8 @@ module KubernetesDeploy
|
|
366
377
|
file&.close
|
367
378
|
end
|
368
379
|
|
369
|
-
def
|
370
|
-
|
380
|
+
def print_debug_logs?
|
381
|
+
false
|
371
382
|
end
|
372
383
|
|
373
384
|
def statsd_tags
|
@@ -28,10 +28,13 @@ module KubernetesDeploy
|
|
28
28
|
observed_generation == current_generation
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
32
|
-
return {} unless pods.present?
|
31
|
+
def fetch_debug_logs(kubectl)
|
33
32
|
most_useful_pod = pods.find(&:deploy_failed?) || pods.find(&:deploy_timed_out?) || pods.first
|
34
|
-
most_useful_pod.
|
33
|
+
most_useful_pod.fetch_debug_logs(kubectl)
|
34
|
+
end
|
35
|
+
|
36
|
+
def print_debug_logs?
|
37
|
+
pods.present? # the kubectl command times out if no pods exist
|
35
38
|
end
|
36
39
|
|
37
40
|
private
|
@@ -24,9 +24,12 @@ module KubernetesDeploy
|
|
24
24
|
own_events.merge(@latest_rs.fetch_events(kubectl))
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
27
|
+
def print_debug_logs?
|
28
|
+
@latest_rs.present?
|
29
|
+
end
|
30
|
+
|
31
|
+
def fetch_debug_logs(kubectl)
|
32
|
+
@latest_rs.fetch_debug_logs(kubectl)
|
30
33
|
end
|
31
34
|
|
32
35
|
def deploy_succeeded?
|
@@ -142,12 +145,6 @@ module KubernetesDeploy
|
|
142
145
|
def deploy_failing_to_progress?
|
143
146
|
return false unless progress_condition.present?
|
144
147
|
|
145
|
-
if @server_version < Gem::Version.new("1.7.7")
|
146
|
-
# Deployments were being updated prematurely with incorrect progress information
|
147
|
-
# https://github.com/kubernetes/kubernetes/issues/49637
|
148
|
-
return false unless Time.now.utc - @deploy_started_at >= progress_deadline.to_i
|
149
|
-
end
|
150
|
-
|
151
148
|
# This assumes that when the controller bumps the observed generation, it also updates/clears all the status
|
152
149
|
# conditions. Specifically, it assumes the progress condition is immediately set to True if a rollout is starting.
|
153
150
|
deploy_started? &&
|
@@ -10,15 +10,17 @@ module KubernetesDeploy
|
|
10
10
|
)
|
11
11
|
|
12
12
|
def initialize(namespace:, context:, definition:, logger:,
|
13
|
-
statsd_tags: nil, parent: nil, deploy_started_at: nil)
|
13
|
+
statsd_tags: nil, parent: nil, deploy_started_at: nil, stream_logs: false)
|
14
14
|
@parent = parent
|
15
15
|
@deploy_started_at = deploy_started_at
|
16
|
+
|
16
17
|
@containers = definition.fetch("spec", {}).fetch("containers", []).map { |c| Container.new(c) }
|
17
18
|
unless @containers.present?
|
18
19
|
logger.summary.add_paragraph("Rendered template content:\n#{definition.to_yaml}")
|
19
20
|
raise FatalDeploymentError, "Template is missing required field spec.containers"
|
20
21
|
end
|
21
22
|
@containers += definition["spec"].fetch("initContainers", []).map { |c| Container.new(c, init_container: true) }
|
23
|
+
@stream_logs = stream_logs
|
22
24
|
super(namespace: namespace, context: context, definition: definition,
|
23
25
|
logger: logger, statsd_tags: statsd_tags)
|
24
26
|
end
|
@@ -28,12 +30,19 @@ module KubernetesDeploy
|
|
28
30
|
raise_predates_deploy_error if exists? && unmanaged? && !deploy_started?
|
29
31
|
|
30
32
|
if exists?
|
33
|
+
logs.sync(mediator.kubectl) if unmanaged?
|
31
34
|
update_container_statuses(@instance_data["status"])
|
32
35
|
else # reset
|
33
36
|
@containers.each(&:reset_status)
|
34
37
|
end
|
38
|
+
end
|
35
39
|
|
36
|
-
|
40
|
+
def after_sync
|
41
|
+
if @stream_logs
|
42
|
+
logs.print_latest
|
43
|
+
elsif unmanaged? && deploy_succeeded?
|
44
|
+
logs.print_all
|
45
|
+
end
|
37
46
|
end
|
38
47
|
|
39
48
|
def status
|
@@ -80,28 +89,25 @@ module KubernetesDeploy
|
|
80
89
|
"#{phase_problem}#{container_problems}".presence
|
81
90
|
end
|
82
91
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
def
|
89
|
-
|
90
|
-
@containers.each_with_object({}) do |container, container_logs|
|
91
|
-
cmd = [
|
92
|
-
"logs",
|
93
|
-
@name,
|
94
|
-
"--container=#{container.name}",
|
95
|
-
"--since-time=#{@deploy_started_at.to_datetime.rfc3339}",
|
96
|
-
]
|
97
|
-
cmd << "--tail=#{LOG_LINE_COUNT}" unless unmanaged?
|
98
|
-
out, _err, _st = kubectl.run(*cmd, log_failure: false)
|
99
|
-
container_logs[container.name] = out.split("\n")
|
100
|
-
end
|
92
|
+
def fetch_debug_logs(kubectl)
|
93
|
+
logs.sync(kubectl)
|
94
|
+
logs
|
95
|
+
end
|
96
|
+
|
97
|
+
def print_debug_logs?
|
98
|
+
exists? && !@stream_logs # don't print them a second time
|
101
99
|
end
|
102
100
|
|
103
101
|
private
|
104
102
|
|
103
|
+
def logs
|
104
|
+
@logs ||= KubernetesDeploy::RemoteLogs.new(
|
105
|
+
logger: @logger,
|
106
|
+
parent_id: id,
|
107
|
+
container_names: @containers.map(&:name)
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
105
111
|
def phase
|
106
112
|
@instance_data.dig("status", "phase") || "Unknown"
|
107
113
|
end
|
@@ -138,29 +144,6 @@ module KubernetesDeploy
|
|
138
144
|
@parent.blank?
|
139
145
|
end
|
140
146
|
|
141
|
-
def display_logs(mediator)
|
142
|
-
return if @already_displayed
|
143
|
-
container_logs = fetch_logs(mediator.kubectl)
|
144
|
-
|
145
|
-
if container_logs.empty?
|
146
|
-
@logger.warn("No logs found for pod #{id}")
|
147
|
-
return
|
148
|
-
end
|
149
|
-
|
150
|
-
container_logs.each do |container_identifier, logs|
|
151
|
-
if logs.blank?
|
152
|
-
@logger.warn("No logs found for container '#{container_identifier}'")
|
153
|
-
else
|
154
|
-
@logger.blank_line
|
155
|
-
@logger.info("Logs from #{id} container '#{container_identifier}':")
|
156
|
-
logs.each { |line| @logger.info("\t#{line}") }
|
157
|
-
@logger.blank_line
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
@already_displayed = true
|
162
|
-
end
|
163
|
-
|
164
147
|
def raise_predates_deploy_error
|
165
148
|
example_color = :green
|
166
149
|
msg = <<-STRING.strip_heredoc
|
@@ -16,19 +16,14 @@ module KubernetesDeploy
|
|
16
16
|
own_events.merge(most_useful_pod.fetch_events(kubectl))
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
"--tail=#{LOG_LINE_COUNT}",
|
28
|
-
log_failure: false
|
29
|
-
)
|
30
|
-
container_logs[container_name] = out.split("\n")
|
31
|
-
end
|
19
|
+
def fetch_debug_logs(kubectl)
|
20
|
+
logs = KubernetesDeploy::RemoteLogs.new(logger: @logger, parent_id: id, container_names: container_names)
|
21
|
+
logs.sync(kubectl)
|
22
|
+
logs
|
23
|
+
end
|
24
|
+
|
25
|
+
def print_debug_logs?
|
26
|
+
pods.present? # the kubectl command times out if no pods exist
|
32
27
|
end
|
33
28
|
|
34
29
|
private
|
@@ -25,7 +25,7 @@ module KubernetesDeploy
|
|
25
25
|
unless @success_assumption_warning_shown
|
26
26
|
@logger.warn("WARNING: Your StatefulSet's updateStrategy is set to OnDelete, "\
|
27
27
|
"which means updates will not be applied until its pods are deleted. "\
|
28
|
-
"
|
28
|
+
"Consider switching to rollingUpdate.")
|
29
29
|
@success_assumption_warning_shown = true
|
30
30
|
end
|
31
31
|
true
|
@@ -46,9 +46,7 @@ module KubernetesDeploy
|
|
46
46
|
private
|
47
47
|
|
48
48
|
def update_strategy
|
49
|
-
if
|
50
|
-
ONDELETE
|
51
|
-
elsif exists?
|
49
|
+
if exists?
|
52
50
|
@instance_data['spec']['updateStrategy']['type']
|
53
51
|
else
|
54
52
|
'Unknown'
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'kubernetes-deploy/container_logs'
|
3
|
+
|
4
|
+
module KubernetesDeploy
|
5
|
+
class RemoteLogs
|
6
|
+
attr_reader :container_logs
|
7
|
+
|
8
|
+
def initialize(logger:, parent_id:, container_names:)
|
9
|
+
@logger = logger
|
10
|
+
@parent_id = parent_id
|
11
|
+
@container_logs = container_names.map do |n|
|
12
|
+
ContainerLogs.new(logger: logger, container_name: n, parent_id: parent_id)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def empty?
|
17
|
+
@container_logs.all?(&:empty?)
|
18
|
+
end
|
19
|
+
|
20
|
+
def sync(kubectl)
|
21
|
+
@container_logs.each { |cl| cl.sync(kubectl) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def print_latest
|
25
|
+
@container_logs.each { |cl| cl.print_latest(prefix: @container_logs.length > 1) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def print_all(prevent_duplicate: true)
|
29
|
+
return if @already_displayed && prevent_duplicate
|
30
|
+
|
31
|
+
if @container_logs.all?(&:empty?)
|
32
|
+
@logger.warn("No logs found for #{@parent_id}")
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
@container_logs.each do |cl|
|
37
|
+
if cl.empty?
|
38
|
+
@logger.warn("No logs found for #{@parent_id} container '#{cl.container_name}'")
|
39
|
+
else
|
40
|
+
@logger.info("Logs from #{@parent_id} container '#{cl.container_name}':")
|
41
|
+
cl.print_all
|
42
|
+
@logger.blank_line
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
@already_displayed = true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -17,19 +17,16 @@ module KubernetesDeploy
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def run(delay_sync: 3.seconds, reminder_interval: 30.seconds, record_summary: true)
|
20
|
-
|
20
|
+
last_message_logged_at = monitoring_started = Time.now.utc
|
21
21
|
remainder = @resources.dup
|
22
22
|
|
23
23
|
while remainder.present?
|
24
|
-
if
|
25
|
-
|
26
|
-
end
|
27
|
-
if (sleep_duration = delay_sync_until - Time.now.utc) > 0
|
28
|
-
sleep(sleep_duration)
|
29
|
-
end
|
30
|
-
delay_sync_until = Time.now.utc + delay_sync # don't pummel the API if the sync is fast
|
24
|
+
report_and_give_up(remainder) if global_timeout?(monitoring_started)
|
25
|
+
sleep_until_next_sync(delay_sync)
|
31
26
|
|
32
27
|
@sync_mediator.sync(remainder)
|
28
|
+
remainder.each(&:after_sync)
|
29
|
+
|
33
30
|
new_successes, remainder = remainder.partition(&:deploy_succeeded?)
|
34
31
|
new_failures, remainder = remainder.partition(&:deploy_failed?)
|
35
32
|
new_timeouts, remainder = remainder.partition(&:deploy_timed_out?)
|
@@ -48,6 +45,18 @@ module KubernetesDeploy
|
|
48
45
|
|
49
46
|
private
|
50
47
|
|
48
|
+
def global_timeout?(started_at)
|
49
|
+
@timeout && (Time.now.utc - started_at > @timeout)
|
50
|
+
end
|
51
|
+
|
52
|
+
def sleep_until_next_sync(min_interval)
|
53
|
+
@next_sync_time ||= Time.now.utc
|
54
|
+
if (sleep_duration = @next_sync_time - Time.now.utc) > 0
|
55
|
+
sleep(sleep_duration)
|
56
|
+
end
|
57
|
+
@next_sync_time = Time.now.utc + min_interval
|
58
|
+
end
|
59
|
+
|
51
60
|
def report_what_just_happened(new_successes, new_failures, new_timeouts)
|
52
61
|
watch_time = (Time.now.utc - @deploy_started_at).round(1)
|
53
62
|
new_failures.each do |resource|
|
@@ -62,7 +71,7 @@ module KubernetesDeploy
|
|
62
71
|
|
63
72
|
if new_successes.present?
|
64
73
|
new_successes.each { |r| r.report_status_to_statsd(watch_time) }
|
65
|
-
success_string = ColorizedString.new("Successfully #{
|
74
|
+
success_string = ColorizedString.new("Successfully #{past_tense_operation} in #{watch_time}s:").green
|
66
75
|
@logger.info("#{success_string} #{new_successes.map(&:id).join(', ')}")
|
67
76
|
end
|
68
77
|
end
|
@@ -120,7 +129,7 @@ module KubernetesDeploy
|
|
120
129
|
def record_success_statuses(successful_resources)
|
121
130
|
success_count = successful_resources.length
|
122
131
|
if success_count > 0
|
123
|
-
@logger.summary.add_action("successfully #{
|
132
|
+
@logger.summary.add_action("successfully #{past_tense_operation} #{success_count} "\
|
124
133
|
"#{'resource'.pluralize(success_count)}")
|
125
134
|
final_statuses = successful_resources.map(&:pretty_status).join("\n")
|
126
135
|
@logger.summary.add_paragraph("#{ColorizedString.new('Successful resources').green}\n#{final_statuses}")
|
@@ -130,5 +139,9 @@ module KubernetesDeploy
|
|
130
139
|
def due_for_reminder?(last_message_logged_at, reminder_interval)
|
131
140
|
(last_message_logged_at.to_f + reminder_interval.to_f) <= Time.now.utc.to_f
|
132
141
|
end
|
142
|
+
|
143
|
+
def past_tense_operation
|
144
|
+
@operation_name == "run" ? "ran" : "#{@operation_name}ed"
|
145
|
+
end
|
133
146
|
end
|
134
147
|
end
|
@@ -8,51 +8,99 @@ module KubernetesDeploy
|
|
8
8
|
class RunnerTask
|
9
9
|
include KubeclientBuilder
|
10
10
|
|
11
|
-
class
|
12
|
-
class TaskTemplateMissingError <
|
13
|
-
|
14
|
-
|
15
|
-
end
|
16
|
-
end
|
11
|
+
class TaskConfigurationError < FatalDeploymentError; end
|
12
|
+
class TaskTemplateMissingError < TaskConfigurationError; end
|
13
|
+
|
14
|
+
attr_reader :pod_name
|
17
15
|
|
18
|
-
def initialize(namespace:, context:, logger:)
|
16
|
+
def initialize(namespace:, context:, logger:, max_watch_seconds: nil)
|
19
17
|
@logger = logger
|
20
18
|
@namespace = namespace
|
21
|
-
@kubeclient = build_v1_kubeclient(context)
|
22
19
|
@context = context
|
20
|
+
@max_watch_seconds = max_watch_seconds
|
23
21
|
end
|
24
22
|
|
25
23
|
def run(*args)
|
26
24
|
run!(*args)
|
27
25
|
true
|
28
|
-
rescue FatalDeploymentError
|
29
|
-
@logger.fatal "#{error.class}: #{error.message}"
|
26
|
+
rescue DeploymentTimeoutError, FatalDeploymentError
|
30
27
|
false
|
31
28
|
end
|
32
29
|
|
33
|
-
def run!(task_template:, entrypoint:, args:, env_vars: [])
|
30
|
+
def run!(task_template:, entrypoint:, args:, env_vars: [], verify_result: true)
|
34
31
|
@logger.reset
|
35
|
-
|
32
|
+
|
33
|
+
@logger.phase_heading("Initializing task")
|
36
34
|
validate_configuration(task_template, args)
|
37
|
-
|
38
|
-
|
35
|
+
pod = build_pod(task_template, entrypoint, args, env_vars, verify_result)
|
36
|
+
validate_pod(pod)
|
37
|
+
|
38
|
+
@logger.phase_heading("Running pod")
|
39
|
+
create_pod(pod)
|
40
|
+
|
41
|
+
if verify_result
|
42
|
+
@logger.phase_heading("Streaming logs")
|
43
|
+
watch_pod(pod)
|
44
|
+
else
|
45
|
+
record_status_once(pod)
|
39
46
|
end
|
40
|
-
@logger.
|
41
|
-
|
47
|
+
@logger.print_summary(:success)
|
48
|
+
rescue DeploymentTimeoutError
|
49
|
+
@logger.print_summary(:timed_out)
|
50
|
+
raise
|
51
|
+
rescue FatalDeploymentError
|
52
|
+
@logger.print_summary(:failure)
|
53
|
+
raise
|
54
|
+
end
|
42
55
|
|
43
|
-
|
44
|
-
rendered_template = build_pod_template(raw_template, entrypoint, args, env_vars)
|
56
|
+
private
|
45
57
|
|
46
|
-
|
58
|
+
def create_pod(pod)
|
59
|
+
@logger.info "Creating pod '#{pod.name}'"
|
60
|
+
pod.deploy_started_at = Time.now.utc
|
61
|
+
kubeclient.create_pod(pod.to_kubeclient_resource)
|
62
|
+
@pod_name = pod.name
|
63
|
+
@logger.info("Pod creation succeeded")
|
64
|
+
rescue KubeException => e
|
65
|
+
msg = "Failed to create pod: #{e.class.name}: #{e.message}"
|
66
|
+
@logger.summary.add_paragraph(msg)
|
67
|
+
raise FatalDeploymentError, msg
|
68
|
+
end
|
47
69
|
|
48
|
-
|
49
|
-
|
50
|
-
@
|
70
|
+
def build_pod(template_name, entrypoint, args, env_vars, verify_result)
|
71
|
+
task_template = get_template(template_name)
|
72
|
+
@logger.info("Using template '#{template_name}'")
|
73
|
+
pod_template = build_pod_definition(task_template)
|
74
|
+
set_container_overrides!(pod_template, entrypoint, args, env_vars)
|
75
|
+
ensure_valid_restart_policy!(pod_template, verify_result)
|
76
|
+
Pod.new(namespace: @namespace, context: @context, logger: @logger, stream_logs: true,
|
77
|
+
definition: pod_template.to_hash.deep_stringify_keys, statsd_tags: [])
|
51
78
|
end
|
52
79
|
|
53
|
-
|
80
|
+
def validate_pod(pod)
|
81
|
+
pod.validate_definition(kubectl)
|
82
|
+
end
|
83
|
+
|
84
|
+
def watch_pod(pod)
|
85
|
+
rw = ResourceWatcher.new(resources: [pod], logger: @logger, timeout: @max_watch_seconds,
|
86
|
+
sync_mediator: sync_mediator, operation_name: "run")
|
87
|
+
rw.run(delay_sync: 1, reminder_interval: 30.seconds)
|
88
|
+
raise DeploymentTimeoutError if pod.deploy_timed_out?
|
89
|
+
raise FatalDeploymentError if pod.deploy_failed?
|
90
|
+
end
|
91
|
+
|
92
|
+
def record_status_once(pod)
|
93
|
+
pod.sync(sync_mediator)
|
94
|
+
warning = <<~STRING
|
95
|
+
#{ColorizedString.new('Result verification is disabled for this task.').yellow}
|
96
|
+
The following status was observed immediately after pod creation:
|
97
|
+
#{pod.pretty_status}
|
98
|
+
STRING
|
99
|
+
@logger.summary.add_paragraph(warning)
|
100
|
+
end
|
54
101
|
|
55
102
|
def validate_configuration(task_template, args)
|
103
|
+
@logger.info("Validating configuration")
|
56
104
|
errors = []
|
57
105
|
|
58
106
|
if task_template.blank?
|
@@ -68,71 +116,74 @@ module KubernetesDeploy
|
|
68
116
|
end
|
69
117
|
|
70
118
|
begin
|
71
|
-
|
119
|
+
kubeclient.get_namespace(@namespace) if @namespace.present?
|
120
|
+
@logger.info "Using namespace '#{@namespace}' in context '#{@context}'"
|
72
121
|
rescue KubeException => e
|
73
122
|
msg = e.error_code == 404 ? "Namespace was not found" : "Could not connect to kubernetes cluster"
|
74
123
|
errors << msg
|
75
124
|
end
|
76
125
|
|
77
|
-
|
126
|
+
unless errors.empty?
|
127
|
+
@logger.summary.add_action("Configuration invalid")
|
128
|
+
@logger.summary.add_paragraph(errors.map { |err| "- #{err}" }.join("\n"))
|
129
|
+
raise TaskConfigurationError, "Configuration invalid: #{errors.join(', ')}"
|
130
|
+
end
|
131
|
+
|
132
|
+
if kubectl.server_version < Gem::Version.new(MIN_KUBE_VERSION)
|
133
|
+
@logger.warn(KubernetesDeploy::Errors.server_version_warning(kubectl.server_version))
|
134
|
+
end
|
78
135
|
end
|
79
136
|
|
80
137
|
def get_template(template_name)
|
81
|
-
|
82
|
-
"Fetching task runner pod template: '#{template_name}' in namespace: '#{@namespace}'"
|
83
|
-
)
|
84
|
-
|
85
|
-
pod_template = @kubeclient.get_pod_template(template_name, @namespace)
|
138
|
+
pod_template = kubeclient.get_pod_template(template_name, @namespace)
|
86
139
|
|
87
140
|
pod_template.template
|
88
141
|
rescue KubeException => error
|
89
142
|
if error.error_code == 404
|
90
|
-
|
143
|
+
msg = "Pod template `#{template_name}` not found in namespace `#{@namespace}`, context `#{@context}`"
|
144
|
+
@logger.summary.add_paragraph(msg)
|
145
|
+
raise TaskTemplateMissingError, msg
|
91
146
|
else
|
92
|
-
raise
|
147
|
+
raise FatalKubeAPIError, "Error retrieving pod template: #{error.class.name}: #{error.message}"
|
93
148
|
end
|
94
149
|
end
|
95
150
|
|
96
|
-
def
|
97
|
-
|
151
|
+
def build_pod_definition(base_template)
|
152
|
+
pod_definition = base_template.dup
|
153
|
+
pod_definition.kind = 'Pod'
|
154
|
+
pod_definition.apiVersion = 'v1'
|
155
|
+
pod_definition.metadata.namespace = @namespace
|
98
156
|
|
99
|
-
|
100
|
-
|
101
|
-
|
157
|
+
unique_name = pod_definition.metadata.name + "-" + SecureRandom.hex(8)
|
158
|
+
@logger.warn("Name is too long, using '#{unique_name[0..62]}'") if unique_name.length > 63
|
159
|
+
pod_definition.metadata.name = unique_name[0..62]
|
102
160
|
|
103
|
-
|
161
|
+
pod_definition
|
162
|
+
end
|
104
163
|
|
105
|
-
|
164
|
+
def set_container_overrides!(pod_definition, entrypoint, args, env_vars)
|
165
|
+
container = pod_definition.spec.containers.find { |cont| cont.name == 'task-runner' }
|
166
|
+
if container.nil?
|
167
|
+
raise TaskConfigurationError, "Pod spec does not contain a template container called 'task-runner'"
|
168
|
+
end
|
106
169
|
|
107
170
|
container.command = entrypoint
|
108
171
|
container.args = args
|
109
|
-
container.env ||= []
|
110
172
|
|
111
173
|
env_args = env_vars.map do |env|
|
112
174
|
key, value = env.split('=', 2)
|
113
175
|
{ name: key, value: value }
|
114
176
|
end
|
115
|
-
|
177
|
+
container.env ||= []
|
116
178
|
container.env = container.env.map(&:to_h) + env_args
|
117
|
-
|
118
|
-
unique_name = rendered_template.metadata.name + "-" + SecureRandom.hex(8)
|
119
|
-
|
120
|
-
@logger.warn("Name is too long, using '#{unique_name[0..62]}'") if unique_name.length > 63
|
121
|
-
rendered_template.metadata.name = unique_name[0..62]
|
122
|
-
rendered_template.metadata.namespace = @namespace
|
123
|
-
|
124
|
-
rendered_template
|
125
179
|
end
|
126
180
|
|
127
|
-
def
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
unless status.success?
|
135
|
-
raise FatalTaskRunError, "Invalid pod spec: #{err}"
|
181
|
+
def ensure_valid_restart_policy!(template, verify)
|
182
|
+
restart_policy = template.spec.restartPolicy
|
183
|
+
if verify && restart_policy != "Never"
|
184
|
+
@logger.warn("Changed Pod RestartPolicy from '#{restart_policy}' to 'Never'. Disable "\
|
185
|
+
"result verification to use '#{restart_policy}'.")
|
186
|
+
template.spec.restartPolicy = "Never"
|
136
187
|
end
|
137
188
|
end
|
138
189
|
|
@@ -140,18 +191,12 @@ module KubernetesDeploy
|
|
140
191
|
@kubectl ||= Kubectl.new(namespace: @namespace, context: @context, logger: @logger, log_failure_by_default: true)
|
141
192
|
end
|
142
193
|
|
143
|
-
def
|
144
|
-
|
145
|
-
|
146
|
-
end
|
147
|
-
|
148
|
-
hash = {}
|
194
|
+
def sync_mediator
|
195
|
+
@sync_mediator ||= SyncMediator.new(namespace: @namespace, context: @context, logger: @logger)
|
196
|
+
end
|
149
197
|
|
150
|
-
|
151
|
-
|
152
|
-
hash[k] = recursive_val ? recursive_to_h(v) : v
|
153
|
-
end
|
154
|
-
hash
|
198
|
+
def kubeclient
|
199
|
+
@kubeclient ||= build_v1_kubeclient(@context)
|
155
200
|
end
|
156
201
|
end
|
157
202
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kubernetes-deploy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.21.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Katrina Verey
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-
|
12
|
+
date: 2018-10-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -213,6 +213,7 @@ files:
|
|
213
213
|
- lib/kubernetes-deploy.rb
|
214
214
|
- lib/kubernetes-deploy/bindings_parser.rb
|
215
215
|
- lib/kubernetes-deploy/concurrency.rb
|
216
|
+
- lib/kubernetes-deploy/container_logs.rb
|
216
217
|
- lib/kubernetes-deploy/deferred_summary_logging.rb
|
217
218
|
- lib/kubernetes-deploy/deploy_task.rb
|
218
219
|
- lib/kubernetes-deploy/duration_parser.rb
|
@@ -248,6 +249,7 @@ files:
|
|
248
249
|
- lib/kubernetes-deploy/kubernetes_resource/stateful_set.rb
|
249
250
|
- lib/kubernetes-deploy/kubernetes_resource/statefulservice.rb
|
250
251
|
- lib/kubernetes-deploy/kubernetes_resource/topic.rb
|
252
|
+
- lib/kubernetes-deploy/remote_logs.rb
|
251
253
|
- lib/kubernetes-deploy/renderer.rb
|
252
254
|
- lib/kubernetes-deploy/resource_discovery.rb
|
253
255
|
- lib/kubernetes-deploy/resource_watcher.rb
|