mina-kubernetes 2.0.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f69cc691a91d27b5cefa7d4a79056184cfad126ffd833ce2c11c8c85438516ef
4
- data.tar.gz: 15e3ee21f521d47f1cc4d22ab70edeb7ace125244b301a571fcb88a3ea01537d
3
+ metadata.gz: 7be64670afaedf5f60cb172d47331af1df66e3e843d21cdeee54851f2aafb5fa
4
+ data.tar.gz: 9eea0c759fd4823586b551413b7a456b780baeb8af24a0464ea962683212023e
5
5
  SHA512:
6
- metadata.gz: 4e3d514d8ee7e171f28d646f2142dc1fad2acca1416ecb30baa77742c1f046e19cd92990d88e34016d196f62fbaf7492f99778158a69504365af7c3844096a70
7
- data.tar.gz: '01066097331bdac2aeba3b2228fc4d78f68511accef2ea59d111478d379bf37551b55673842dbf77b9cc1d0012222d6378328bb7538dde1c9ea46335c3121be9'
6
+ metadata.gz: 59927afc8c3504e3b381b4c749b5fb390fc2c7f20c143674811bbede57f6c2e89d6852dc9b4ecd534fd84a7bf1ef6b3aa35144c1704ec36569f157868feca7e3
7
+ data.tar.gz: 5b96dc9e7d993063be539d294656f2a3337290adbfaef1e104a5877c9f09736b7e3bb8ee35ee6407812ce7072d047f95c84fe04b3194b2ef0d446150b11eaf82
@@ -1,3 +1,51 @@
1
+ ## 2.5.0
2
+
3
+ *Enhancements*
4
+
5
+ - `kubernetes:command` starts pod with identifiable name and allows session reconnection
6
+ - `kubernetes:command` accepts a `kubectl_pod_overrides` option
7
+
8
+ ## 2.4.1
9
+
10
+ *Fixes*
11
+
12
+ - Security: update rake dependency
13
+
14
+ ## 2.4.0
15
+
16
+ *Enhancements*
17
+
18
+ - Use `secrets.ejson` if present
19
+
20
+ ## 2.3.0
21
+
22
+ *Enhancements*
23
+
24
+ - Allow using a proxy to connect to a Kubernetes cluster
25
+
26
+ ## 2.2.4
27
+
28
+ *Fixes*
29
+
30
+ - run custom command within given namespace instead of `default`
31
+
32
+ ## 2.2.1 to 2.2.3
33
+
34
+ *Fixes*
35
+
36
+ - handle nil/undefined options passed to `krane`
37
+
38
+ ## 2.2.0
39
+
40
+ *Enhancements*
41
+
42
+ - Using `krane` 1.0.0 (previously `kubernetes-deploy`)
43
+ - Allow passing of options to `krane`
44
+
45
+ ## 2.1.0
46
+
47
+ Yanked release.
48
+
1
49
  ## 2.0.0
2
50
 
3
51
  *Breaking*
@@ -7,4 +55,4 @@
7
55
 
8
56
  *Fixes*
9
57
 
10
- - Not overriding $KUBE_CONFIG environment variable anymore
58
+ - Not overriding $KUBE_CONFIG environment variable anymore
data/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # mina-kubernetes
2
- Plugin for the [mina](https://github.com/mina-deploy/mina) deployment tool to streamline deployment of resources to Kubernetes cluster, using the [kubernetes-deploy](https://github.com/Shopify/kubernetes-deploy) gem and [mina-multistage](https://github.com/endoze/mina-multistage) plugin.
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
- Requires local Docker and [kubectl](https://cloud.google.com/kubernetes-engine/docs/quickstart) with authentication set up to connect to the destination Kubernetes cluster.
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 the image is available. This requires experimental features to be enabled in your local Docker config by adding `"experimental": "enabled"` to `~/.docker/config.json`.
7
- If the image repository is not public authentication will need 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
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
- Add `mina-kubernetes` to your local Gemfile.
11
+ Add `mina-kubernetes` to your local Gemfile.
12
12
 
13
- Create a configuration file for mina in `config/deploy.rb` like the one below:
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,17 +28,40 @@ 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 working Git repository and the image to deploy will be assumed to be tagged with the Git commit hash, i.e. `gcr.io/project-123456/my_app:abcd1234`.
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, i.e. `config/deploy/production/app.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.
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
- When you run `mina production deploy`, a namespace labelled `my_app-production` will be created on the Kubernetes cluster and set as a local kubectl context. Then the resources are applied to the cluster after checking/waiting for the image to be available on the repository.
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
+ ```yml
37
+ apiVersion: v1
38
+ kind: Secret
39
+ metadata:
40
+ name: secrets
41
+ data:
42
+ RAILS_MASTER_KEY: <%= Base64.strict_encode64(File.read("#{Dir.pwd}/config/credentials/production.key").strip) %>
43
+ ```
44
+
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
+
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
53
+
54
+ ```ruby
55
+ invoke :"kubernetes:deploy", "--no-prune"
56
+ ```
36
57
 
37
- ### Tasks available
58
+ Refer to https://github.com/Shopify/krane#usage for a complete set of options
59
+
60
+ ## Tasks available
38
61
 
39
62
  #### `kubernetes:deploy`
40
63
 
41
- Creates namespace on cluster and assigns it to a local kubectl context, prompts for git branch if no image tag specified, applies all resources to cluster after checking tagged image is available.
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.
42
65
 
43
66
  #### `kubernetes:bash`
44
67
 
@@ -46,8 +69,26 @@ Prompts for branch unless image tag is set, then spins up a temporary pod with t
46
69
 
47
70
  #### `kubernetes:command`
48
71
 
49
- Prompts for branch unless image tag is set, then spins up a temporary pod with the image and run command given by task variable `command`, for instance with `set :command, "rails console"`. Environment variables can also be given by defining`env_hash`, i.e. `set :env_hash, {"RAILS_ENV" => "production", "MY_VAR" => "abcd123"}`
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.
50
77
 
51
78
  #### `kubernetes:delete`
52
79
 
53
- Confirms and delete all resources on cluster under namespace.
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!
@@ -1,34 +1,34 @@
1
1
  require "tty-prompt"
2
2
  require "tty-spinner"
3
- require "securerandom"
4
3
  require "json"
5
- require "base64"
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
- task :deploy do
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"
14
14
  set_tag_from_branch_commit unless fetch(:image_tag)
15
15
  wait_until_image_ready(fetch(:image_tag))
16
16
  create_namespace_on_cluster
17
- apply_kubernetes_resources
17
+ apply_kubernetes_resources(args[:options])
18
18
  end
19
19
 
20
20
  task :bash do
21
21
  desc "Spins up temporary pod with image and opens remote interactive bash"
22
22
  set_tag_from_branch_commit unless fetch(:image_tag)
23
23
  wait_until_image_ready(fetch(:image_tag))
24
- run_terminal_command("bash")
24
+ run_command("bash")
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
- run_terminal_command(fetch(:command), env_hash_arg)
31
+ run_command(fetch(:command), env_hash_arg)
32
32
  end
33
33
 
34
34
  task :delete do
@@ -51,7 +51,7 @@ end
51
51
 
52
52
  def set_tag_from_branch_commit
53
53
  run :local do
54
- comment "Updating Git branches..."
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
- command "kubectl create namespace #{fetch(:namespace)} --dry-run -o yaml | kubectl apply -f - --context=#{fetch(:kubernetes_context)}"
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 "Check image #{fetch(:image_repo)}:#{commit} is available..."
71
+ comment "Checking image #{fetch(:image_repo)}:#{commit} is available..."
71
72
  end
72
73
  spinner = TTY::Spinner.new
73
74
  spinner.auto_spin
@@ -81,16 +82,50 @@ def image_available?(commit)
81
82
  system("docker manifest inspect #{fetch(:image_repo)}:#{commit} > /dev/null") == true
82
83
  end
83
84
 
84
- def run_terminal_command(command, env_hash = {})
85
+ def run_command(command, env_hash = {})
85
86
  env = env_hash.collect{|k,v| "--env #{k}=#{v}" }.join(" ")
86
- label = command.downcase.gsub(" ", "-").gsub(":", "-")
87
- # using system instead of mina's command so tty opens successfully
88
- system "kubectl run #{label}-#{SecureRandom.hex(4)} --rm -i --tty --restart=Never --context=#{fetch(:kubernetes_context)} --image #{fetch(:image_repo)}:#{fetch(:image_tag)} #{env} -- #{command}"
87
+ label = command.downcase.gsub(" ", "-").gsub(":", "-")+ "-#{`whoami`}".strip + "-#{fetch(:branch)}"
88
+ proxy_env = "HTTPS_PROXY=#{fetch(:proxy)}" if fetch(:proxy)
89
+
90
+ run :local do
91
+ comment "Lauching Pod #{color(label, 36)} to run #{color(command, 36)}"
92
+ end
93
+
94
+ pod_description = `#{proxy_env} kubectl get pod #{label} -o json --ignore-not-found --context=#{fetch(:kubernetes_context)} --namespace=#{fetch(:namespace)}`
95
+
96
+ if pod_description.empty?
97
+ wait_until_image_ready(fetch(:image_tag))
98
+ run_command = "#{proxy_env} kubectl run #{label} --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}"
99
+ system "#{run_command} -- #{command}"
100
+ else
101
+ started_at = Time.parse(JSON.parse(pod_description)["status"]["startTime"]).strftime('%b %e, %H:%M')
102
+ choice = TTY::Prompt.new.select("Pod already exists, running since #{started_at} UTC, what would you like to do?", {"Reattach session" => 1, "Kill it" => 0})
103
+
104
+ attach_command = "#{proxy_env} kubectl attach #{label} -i --tty -c #{label} --context=#{fetch(:kubernetes_context)} --namespace=#{fetch(:namespace)}"
105
+ delete_command = "#{proxy_env} kubectl delete pod #{label} --context=#{fetch(:kubernetes_context)} --namespace=#{fetch(:namespace)}"
106
+
107
+ if choice == 1
108
+ system "#{attach_command} && #{delete_command}"
109
+ else
110
+ system delete_command
111
+ end
112
+ end
89
113
  end
90
114
 
91
- def apply_kubernetes_resources
115
+ def apply_kubernetes_resources(options)
92
116
  run :local do
93
- comment "Apply all Kubernetes resources..."
94
- command "REVISION=#{fetch(:image_tag)} kubernetes-deploy --template-dir=config/deploy/#{fetch(:stage)} --bindings=image_repo=#{fetch(:image_repo)},image_tag=#{fetch(:image_tag)},namespace=#{fetch(:namespace)} #{fetch(:namespace)} #{fetch(:kubernetes_context)}"
117
+ comment "Applying all Kubernetes resources..."
118
+
119
+ proxy_env = "HTTPS_PROXY=#{fetch(:proxy)}" if fetch(:proxy)
120
+ filepaths = options&.[](:filepaths) || "config/deploy/#{fetch(:stage)}"
121
+
122
+ 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}"
123
+ deploy_cmd = "#{proxy_env} krane deploy #{fetch(:namespace)} #{fetch(:kubernetes_context)} --stdin "
124
+ deploy_cmd += options[:deployment_options] if options&.[](:deployment_options)
125
+
126
+ ejson_secrets_path = "#{filepaths}/secrets.ejson"
127
+ deploy_cmd += " --filenames #{ejson_secrets_path}" if File.exists?(ejson_secrets_path)
128
+
129
+ command "#{render_cmd} | #{deploy_cmd}"
95
130
  end
96
131
  end
@@ -1,5 +1,5 @@
1
1
  module Mina
2
2
  module Kubernetes
3
- VERSION = "2.0.0"
3
+ VERSION = "2.5.0"
4
4
  end
5
- end
5
+ end
@@ -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', '~> 10.0'
24
-
23
+ spec.add_development_dependency 'rake', '>= 12.3.3'
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 'kubernetes-deploy'
27
+ spec.add_runtime_dependency 'krane', '~> 1.0'
28
28
  spec.add_runtime_dependency 'tty-prompt'
29
29
  spec.add_runtime_dependency 'tty-spinner'
30
- end
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.0.0
4
+ version: 2.5.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: 2019-10-28 00:00:00.000000000 Z
11
+ date: 2020-09-25 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: '10.0'
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: '10.0'
40
+ version: 12.3.3
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: mina
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -67,19 +67,19 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: kubernetes-deploy
70
+ name: krane
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: '1.0'
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: '0'
82
+ version: '1.0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: tty-prompt
85
85
  requirement: !ruby/object:Gem::Requirement