mina-kubernetes 2.2.4 → 2.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -0
- data/README.md +35 -12
- data/lib/mina/kubernetes.rb +63 -12
- data/lib/mina/kubernetes/version.rb +1 -1
- data/mina-kubernetes.gemspec +2 -2
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a40a0047704517ce7a6e10daf7373ca3fe7398d0f655280992f3bb76ba8fff0
|
4
|
+
data.tar.gz: d7efeb94f504ece35b47829c927429b502112f511b91b552ca9c03df20d0e28d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3aaf4c5a80be9b84e75be8d2b15be9e79622ff58ed75e43ae90cde743ba944071707fee26d47e95ae6f8a504ceaa85225c18ed3c8eba5833bc3a6ff84ccbde04
|
7
|
+
data.tar.gz: 5045d3eeaae653fede93ae8bdba3631d8cdcd3297210ec8d8d3add742fbd9e5a0a062af5eaf85edd579604275e80cb89215ed97f3ad96cc84e50ba4cf2ffa67c
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,41 @@
|
|
1
|
+
## 2.7.0
|
2
|
+
|
3
|
+
*Enhancements*
|
4
|
+
|
5
|
+
- `kubernetes:command` allows to choose pod name
|
6
|
+
- `kubernetes:command` offers to kill & start fresh pod or start other pod with different name if pod already exists
|
7
|
+
|
8
|
+
## 2.6.0
|
9
|
+
|
10
|
+
*Fixes*
|
11
|
+
|
12
|
+
- Update krane gem to ~> 2.1 for compatibility with k8s >= 1.17
|
13
|
+
|
14
|
+
## 2.5.0
|
15
|
+
|
16
|
+
*Enhancements*
|
17
|
+
|
18
|
+
- `kubernetes:command` starts pod with identifiable name and allows session reconnection
|
19
|
+
- `kubernetes:command` accepts a `kubectl_pod_overrides` option
|
20
|
+
|
21
|
+
## 2.4.1
|
22
|
+
|
23
|
+
*Fixes*
|
24
|
+
|
25
|
+
- Security: update rake dependency
|
26
|
+
|
27
|
+
## 2.4.0
|
28
|
+
|
29
|
+
*Enhancements*
|
30
|
+
|
31
|
+
- Use `secrets.ejson` if present
|
32
|
+
|
33
|
+
## 2.3.0
|
34
|
+
|
35
|
+
*Enhancements*
|
36
|
+
|
37
|
+
- Allow using a proxy to connect to a Kubernetes cluster
|
38
|
+
|
1
39
|
## 2.2.4
|
2
40
|
|
3
41
|
*Fixes*
|
data/README.md
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
# mina-kubernetes
|
2
|
-
|
2
|
+
mina-kubernetes is a plugin for the [mina](https://github.com/mina-deploy/mina) deployment tool to streamline deployment of resources to Kubernetes clusters, using the [krane](https://github.com/Shopify/krane) gem with the [mina-multistage](https://github.com/endoze/mina-multistage) plugin.
|
3
3
|
|
4
|
-
|
4
|
+
It requires local Docker and [kubectl](https://cloud.google.com/kubernetes-engine/docs/quickstart) with local authentication set up to connect to the destination Kubernetes cluster as context in your local KUBE_CONFIG. See https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl#generate_kubeconfig_entry for example with Google Kubernetes Engine.
|
5
5
|
|
6
|
-
NB: `docker manifest inspect` is used to check whether the Docker image with requested tag is available.
|
7
|
-
If the image
|
6
|
+
NB: `docker manifest inspect` is used to check whether the Docker image with requested tag is available. At the time of writing this is still an experimental feature that needs to be enabled in your local Docker config by adding `"experimental": "enabled"` to `~/.docker/config.json`.
|
7
|
+
If the image to deploy is in a private repository authentication will have to be set up for your local Docker, for instance see https://cloud.google.com/container-registry/docs/advanced-authentication#gcloud_as_a_docker_credential_helper for images hosted on the Google Cloud Registry.
|
8
8
|
|
9
9
|
## Usage
|
10
10
|
|
11
11
|
Add `mina-kubernetes` to your local Gemfile.
|
12
12
|
|
13
|
-
Create a configuration file for mina in `config/deploy.rb`
|
13
|
+
Create a configuration file for mina in `config/deploy.rb` similar to the one below (which replaces the default deploy task):
|
14
14
|
```ruby
|
15
15
|
require "mina/default"
|
16
16
|
require "mina/multistage"
|
@@ -28,9 +28,9 @@ set :image_repo, "gcr.io/project-id/myapp"
|
|
28
28
|
set :kubernetes_context, "kubernetes_context_name"
|
29
29
|
```
|
30
30
|
|
31
|
-
If `set :image_tag, "my_image_tag"` is also defined, it'll be used to deploy the image tagged with this tag on the repository. Otherwise you'll be prompted to pick a branch from current
|
31
|
+
If `set :image_tag, "my_image_tag"` is also defined, it'll be used to deploy the image tagged with this tag on the repository. Otherwise you'll be prompted to pick a branch from the current Git repository and the image to deploy will be assumed to be tagged with the commit hash of that branch, i.e. `gcr.io/project-id/myapp:abcd1234`.
|
32
32
|
|
33
|
-
Then add `*.yml.erb` Kubernetes resource definition files in the stage folder,
|
33
|
+
Then add `*.yml.erb` Kubernetes resource definition files in the stage folder, for instance `config/deploy/production/webserver.yml.erb` and `config/deploy/production/backgroundjobs.yml.erb`. Occurences of `<%= image_repo %>` and `<%= current_sha %>` in these files will be dynamically replaced on deploy by the image repository URL and the latest commit hash of the selected branch on its git origin.
|
34
34
|
|
35
35
|
You can also get the RAILS_MASTER_KEY for encrypted credentials deployed as a Kubernetes secrets by adding a secrets.yml.erb like below:
|
36
36
|
```yml
|
@@ -42,9 +42,14 @@ data:
|
|
42
42
|
RAILS_MASTER_KEY: <%= Base64.strict_encode64(File.read("#{Dir.pwd}/config/credentials/production.key").strip) %>
|
43
43
|
```
|
44
44
|
|
45
|
-
When running `mina production deploy`, it'll
|
45
|
+
When running `mina production deploy`, it'll check the image is available on the repository and then call the `krane` executable to fill in the variables in the resource templates and apply them all to the cluster under the given namespace (see https://github.com/Shopify/krane#deploy-walkthrough for more details)
|
46
46
|
|
47
|
-
###
|
47
|
+
### EJSON Encrypted secrets
|
48
|
+
|
49
|
+
Krane can dynamically generate Kubernetes secrets from an encrypted EJSON file, see: https://github.com/Shopify/krane#deploying-kubernetes-secrets-from-ejson. As per current Krane documentation "The ejson file must be included in the resources passed to --filenames, it can not be read through stdin.", so
|
50
|
+
following convention-over-configuration principles `mina-kubernetes` checks for the presence of a file named `secrets.ejson` in the stage folder and uses it if available.
|
51
|
+
|
52
|
+
### Passing custom options to krane
|
48
53
|
|
49
54
|
```ruby
|
50
55
|
invoke :"kubernetes:deploy", "--no-prune"
|
@@ -52,11 +57,11 @@ When running `mina production deploy`, it'll prompt for a branch and check the i
|
|
52
57
|
|
53
58
|
Refer to https://github.com/Shopify/krane#usage for a complete set of options
|
54
59
|
|
55
|
-
|
60
|
+
## Tasks available
|
56
61
|
|
57
62
|
#### `kubernetes:deploy`
|
58
63
|
|
59
|
-
Creates namespace on cluster
|
64
|
+
Creates the namespace on cluster if it doesn't exist, prompts for a git branch if no image tag is already specified in stage file, then applies all resources to cluster after checking tagged image is available.
|
60
65
|
|
61
66
|
#### `kubernetes:bash`
|
62
67
|
|
@@ -64,8 +69,26 @@ Prompts for branch unless image tag is set, then spins up a temporary pod with t
|
|
64
69
|
|
65
70
|
#### `kubernetes:command`
|
66
71
|
|
67
|
-
Prompts for branch unless image tag is set, then spins up a temporary pod with the image and
|
72
|
+
Prompts for branch unless image tag is set, then spins up a temporary pod with the image and runs the command given in the task variable `command`, for instance with `set :command, "rails console"`. Environment variables can also be passed by defining`env_hash`, i.e. `set :env_hash, {"RAILS_ENV" => "production", "MY_VAR" => "abcd123"}`
|
73
|
+
|
74
|
+
The pod will be named `command-username-branch`, and can be reattached/killed in case of disconnection.
|
75
|
+
|
76
|
+
A `kubectl_pod_overrides` task option is available to pass a value to the `overrides` option of the `kubectl run` command.
|
68
77
|
|
69
78
|
#### `kubernetes:delete`
|
70
79
|
|
71
80
|
Confirms and delete all resources on cluster under namespace.
|
81
|
+
|
82
|
+
## Example use: run rails console on non-preemptible GKE node
|
83
|
+
|
84
|
+
Add the following to your `deploy.rb`
|
85
|
+
``` ruby
|
86
|
+
task :console do
|
87
|
+
set :command, "rails console"
|
88
|
+
set :env_hash, "RAILS_ENV" => fetch(:stage), "RAILS_MASTER_KEY" => File.read("#{Dir.pwd}/config/credentials/#{fetch(:stage)}.key").strip
|
89
|
+
set :kubectl_pod_overrides, '{"spec": {"affinity": {"nodeAffinity": {"requiredDuringSchedulingIgnoredDuringExecution": {"nodeSelectorTerms": [{"matchExpressions": [{"key": "cloud.google.com/gke-preemptible", "operator": "DoesNotExist"} ] } ] } } } } }'
|
90
|
+
|
91
|
+
invoke :'kubernetes:command'
|
92
|
+
end
|
93
|
+
```
|
94
|
+
You can now run `mina production console` to open a rails console in production environment with the image of your choice!
|
data/lib/mina/kubernetes.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
require "tty-prompt"
|
2
2
|
require "tty-spinner"
|
3
|
-
require "securerandom"
|
4
3
|
require "json"
|
5
|
-
require "
|
4
|
+
require "time"
|
6
5
|
|
7
6
|
# required by mina
|
8
7
|
set :execution_mode, :pretty
|
9
8
|
|
10
9
|
namespace :kubernetes do
|
10
|
+
set :proxy, nil
|
11
11
|
|
12
12
|
task :deploy, [:options] do |task, args|
|
13
13
|
desc "Set image tag to be latest commit of prompted branch (unless provided) then applies resources to cluster"
|
@@ -25,9 +25,9 @@ namespace :kubernetes do
|
|
25
25
|
end
|
26
26
|
|
27
27
|
task :command do
|
28
|
+
set :skip_report_time, true
|
28
29
|
desc "Spins up temporary pod with image and runs given command in interactive shell, passing given environment variable"
|
29
30
|
set_tag_from_branch_commit unless fetch(:image_tag)
|
30
|
-
wait_until_image_ready(fetch(:image_tag))
|
31
31
|
run_command(fetch(:command), env_hash_arg)
|
32
32
|
end
|
33
33
|
|
@@ -51,7 +51,7 @@ end
|
|
51
51
|
|
52
52
|
def set_tag_from_branch_commit
|
53
53
|
run :local do
|
54
|
-
comment "
|
54
|
+
comment "Refreshing Git branches..."
|
55
55
|
end
|
56
56
|
remote_branches = `git fetch --prune && git branch -r --no-merged master --sort=-committerdate | grep origin`.split("\n").collect { |b| b.strip.gsub("origin/", "") }.reject { |b| b == "master" }
|
57
57
|
set :branch, TTY::Prompt.new.select("Which branch?", ["master"].concat(remote_branches))
|
@@ -61,13 +61,14 @@ end
|
|
61
61
|
def create_namespace_on_cluster
|
62
62
|
run :local do
|
63
63
|
comment "Create/update namespace on Kubernetes cluster..."
|
64
|
-
|
64
|
+
proxy_env = "HTTPS_PROXY=#{fetch(:proxy)}" if fetch(:proxy)
|
65
|
+
command "kubectl create namespace #{fetch(:namespace)} --dry-run -o yaml | #{proxy_env} kubectl apply -f - --context=#{fetch(:kubernetes_context)}"
|
65
66
|
end
|
66
67
|
end
|
67
68
|
|
68
69
|
def wait_until_image_ready(commit)
|
69
70
|
run :local do
|
70
|
-
comment "
|
71
|
+
comment "Checking image #{fetch(:image_repo)}:#{commit} is available..."
|
71
72
|
end
|
72
73
|
spinner = TTY::Spinner.new
|
73
74
|
spinner.auto_spin
|
@@ -83,18 +84,68 @@ end
|
|
83
84
|
|
84
85
|
def run_command(command, env_hash = {})
|
85
86
|
env = env_hash.collect{|k,v| "--env #{k}=#{v}" }.join(" ")
|
86
|
-
|
87
|
-
|
88
|
-
|
87
|
+
proxy_env = "HTTPS_PROXY=#{fetch(:proxy)}" if fetch(:proxy)
|
88
|
+
|
89
|
+
default_pod_name = "#{`whoami`.strip}-#{command}-#{fetch(:branch)}".downcase.gsub(" ", "-").gsub(":", "-")
|
90
|
+
pod_name = TTY::Prompt.new.ask("What name for the pod?", :value => default_pod_name)
|
91
|
+
|
92
|
+
run :local do
|
93
|
+
comment "Lauching pod #{color(pod_name, 36)} to run #{color(command, 36)}"
|
94
|
+
end
|
95
|
+
|
96
|
+
pod_run_command = "#{proxy_env} kubectl run #{pod_name} --rm -i --tty --restart=Never --overrides='#{fetch(:kubectl_pod_overrides)}' --context=#{fetch(:kubernetes_context)} --namespace=#{fetch(:namespace)} --image #{fetch(:image_repo)}:#{fetch(:image_tag)} #{env}"
|
97
|
+
running_pod_info = `#{proxy_env} kubectl get pod #{pod_name} -o json --ignore-not-found --context=#{fetch(:kubernetes_context)} --namespace=#{fetch(:namespace)}`
|
98
|
+
|
99
|
+
if running_pod_info.empty?
|
100
|
+
wait_for_image_and_run_command("#{pod_run_command} -- #{command}")
|
101
|
+
else
|
102
|
+
started_at = Time.parse(JSON.parse(running_pod_info)["status"]["startTime"]).strftime('%b %e, %H:%M')
|
103
|
+
choice = TTY::Prompt.new.select(
|
104
|
+
"Pod already exists, running since #{started_at} UTC, what would you like to do?",
|
105
|
+
{
|
106
|
+
"Reattach to its container" => :attach,
|
107
|
+
"Kill it and launch a fresh one" => :replace,
|
108
|
+
"Keep it and start one with a different name" => :other,
|
109
|
+
}
|
110
|
+
)
|
111
|
+
|
112
|
+
delete_command = "#{proxy_env} kubectl delete pod #{pod_name} --context=#{fetch(:kubernetes_context)} --namespace=#{fetch(:namespace)}"
|
113
|
+
|
114
|
+
case choice
|
115
|
+
when :attach
|
116
|
+
attach_command = "#{proxy_env} kubectl attach #{pod_name} -i --tty -c #{pod_name} --context=#{fetch(:kubernetes_context)} --namespace=#{fetch(:namespace)}"
|
117
|
+
system "#{attach_command} && #{delete_command}"
|
118
|
+
when :replace
|
119
|
+
system delete_command
|
120
|
+
run :local do
|
121
|
+
comment "Lauching Pod #{color(pod_name, 36)} to run #{color(command, 36)}"
|
122
|
+
end
|
123
|
+
wait_for_image_and_run_command("#{pod_run_command} -- #{command}")
|
124
|
+
when :other
|
125
|
+
run_command(command, env_hash)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def wait_for_image_and_run_command(command)
|
131
|
+
wait_until_image_ready(fetch(:image_tag))
|
132
|
+
system command
|
89
133
|
end
|
90
134
|
|
91
135
|
def apply_kubernetes_resources(options)
|
92
136
|
run :local do
|
93
|
-
comment "
|
137
|
+
comment "Applying all Kubernetes resources..."
|
138
|
+
|
139
|
+
proxy_env = "HTTPS_PROXY=#{fetch(:proxy)}" if fetch(:proxy)
|
94
140
|
filepaths = options&.[](:filepaths) || "config/deploy/#{fetch(:stage)}"
|
95
|
-
|
96
|
-
|
141
|
+
|
142
|
+
render_cmd = "#{proxy_env} krane render --bindings=image_repo=#{fetch(:image_repo)},image_tag=#{fetch(:image_tag)},namespace=#{fetch(:namespace)} --current_sha #{fetch(:image_tag)} -f #{filepaths}"
|
143
|
+
deploy_cmd = "#{proxy_env} krane deploy #{fetch(:namespace)} #{fetch(:kubernetes_context)} --stdin "
|
97
144
|
deploy_cmd += options[:deployment_options] if options&.[](:deployment_options)
|
145
|
+
|
146
|
+
ejson_secrets_path = "#{filepaths}/secrets.ejson"
|
147
|
+
deploy_cmd += " --filenames #{ejson_secrets_path}" if File.exists?(ejson_secrets_path)
|
148
|
+
|
98
149
|
command "#{render_cmd} | #{deploy_cmd}"
|
99
150
|
end
|
100
151
|
end
|
data/mina-kubernetes.gemspec
CHANGED
@@ -20,11 +20,11 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
22
|
spec.add_development_dependency 'bundler', '~> 1.11'
|
23
|
-
spec.add_development_dependency 'rake', '
|
23
|
+
spec.add_development_dependency 'rake', '>= 12.3.3'
|
24
24
|
|
25
25
|
spec.add_runtime_dependency 'mina', '~> 1.0'
|
26
26
|
spec.add_runtime_dependency 'mina-multistage', '~> 1.0'
|
27
|
-
spec.add_runtime_dependency 'krane', '~> 1
|
27
|
+
spec.add_runtime_dependency 'krane', '~> 2.1'
|
28
28
|
spec.add_runtime_dependency 'tty-prompt'
|
29
29
|
spec.add_runtime_dependency 'tty-spinner'
|
30
30
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mina-kubernetes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Antoine Sabourin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-11-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -28,16 +28,16 @@ dependencies:
|
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 12.3.3
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 12.3.3
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: mina
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '1
|
75
|
+
version: '2.1'
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '1
|
82
|
+
version: '2.1'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: tty-prompt
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -141,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
141
|
- !ruby/object:Gem::Version
|
142
142
|
version: '0'
|
143
143
|
requirements: []
|
144
|
-
rubygems_version: 3.
|
144
|
+
rubygems_version: 3.1.2
|
145
145
|
signing_key:
|
146
146
|
specification_version: 4
|
147
147
|
summary: Mina plugin to streamline deployment of resources to Kubernetes cluster
|