kuby-core 0.6.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/kuby-core.gemspec +7 -3
  4. data/lib/kuby.rb +9 -7
  5. data/lib/kuby/definition.rb +0 -8
  6. data/lib/kuby/docker/cli.rb +32 -0
  7. data/lib/kuby/docker/errors.rb +1 -0
  8. data/lib/kuby/docker/layer.rb +4 -4
  9. data/lib/kuby/docker/metadata.rb +11 -7
  10. data/lib/kuby/docker/package_phase.rb +2 -2
  11. data/lib/kuby/docker/spec.rb +11 -11
  12. data/lib/kuby/docker/timestamp_tag.rb +6 -3
  13. data/lib/kuby/environment.rb +6 -2
  14. data/lib/kuby/kubernetes.rb +0 -2
  15. data/lib/kuby/kubernetes/deploy_task.rb +0 -1
  16. data/lib/kuby/kubernetes/deployer.rb +7 -7
  17. data/lib/kuby/kubernetes/minikube_provider.rb +4 -0
  18. data/lib/kuby/kubernetes/provider.rb +5 -5
  19. data/lib/kuby/kubernetes/spec.rb +8 -8
  20. data/lib/kuby/plugin.rb +59 -0
  21. data/lib/kuby/plugins.rb +6 -0
  22. data/lib/kuby/plugins/nginx_ingress.rb +71 -0
  23. data/lib/kuby/plugins/rails_app.rb +18 -0
  24. data/lib/kuby/plugins/rails_app/asset_copy_task.rb +117 -0
  25. data/lib/kuby/plugins/rails_app/assets.rb +347 -0
  26. data/lib/kuby/plugins/rails_app/database.rb +75 -0
  27. data/lib/kuby/{kubernetes/plugins → plugins}/rails_app/generators/kuby.rb +0 -0
  28. data/lib/kuby/plugins/rails_app/mysql.rb +155 -0
  29. data/lib/kuby/plugins/rails_app/plugin.rb +398 -0
  30. data/lib/kuby/plugins/rails_app/postgres.rb +143 -0
  31. data/lib/kuby/plugins/rails_app/rewrite_db_config.rb +11 -0
  32. data/lib/kuby/plugins/rails_app/sqlite.rb +32 -0
  33. data/lib/kuby/{kubernetes/plugins → plugins}/rails_app/tasks.rake +10 -2
  34. data/lib/kuby/tasks.rb +28 -9
  35. data/lib/kuby/tasks/kuby.rake +1 -1
  36. data/lib/kuby/version.rb +1 -1
  37. data/spec/docker/timestamp_tag_spec.rb +11 -0
  38. data/spec/spec_helper.rb +102 -0
  39. metadata +50 -27
  40. data/lib/ext/krane/kubernetes_resource.rb +0 -16
  41. data/lib/kuby/kubernetes/plugin.rb +0 -55
  42. data/lib/kuby/kubernetes/plugins.rb +0 -8
  43. data/lib/kuby/kubernetes/plugins/nginx_ingress.rb +0 -73
  44. data/lib/kuby/kubernetes/plugins/rails_app.rb +0 -16
  45. data/lib/kuby/kubernetes/plugins/rails_app/database.rb +0 -79
  46. data/lib/kuby/kubernetes/plugins/rails_app/mysql.rb +0 -154
  47. data/lib/kuby/kubernetes/plugins/rails_app/plugin.rb +0 -379
  48. data/lib/kuby/kubernetes/plugins/rails_app/postgres.rb +0 -142
  49. data/lib/kuby/kubernetes/plugins/rails_app/rewrite_db_config.rb +0 -13
  50. data/lib/kuby/kubernetes/plugins/rails_app/sqlite.rb +0 -30
@@ -5,10 +5,10 @@ module Kuby
5
5
  class Spec
6
6
  extend ::KubeDSL::ValueFields
7
7
 
8
- attr_reader :definition, :plugins, :tag
8
+ attr_reader :environment, :plugins, :tag
9
9
 
10
- def initialize(definition)
11
- @definition = definition
10
+ def initialize(environment)
11
+ @environment = environment
12
12
  @plugins = TrailingHash.new
13
13
 
14
14
  # default plugins
@@ -18,7 +18,7 @@ module Kuby
18
18
  def provider(provider_name = nil, &block)
19
19
  if provider_name
20
20
  if @provider || provider_klass = Kuby.providers[provider_name]
21
- @provider ||= provider_klass.new(definition)
21
+ @provider ||= provider_klass.new(environment)
22
22
  @provider.configure(&block)
23
23
  else
24
24
  msg = if provider_name
@@ -37,7 +37,7 @@ module Kuby
37
37
 
38
38
  def configure_plugin(plugin_name, &block)
39
39
  if @plugins[plugin_name] || plugin_klass = Kuby.plugins[plugin_name]
40
- @plugins[plugin_name] ||= plugin_klass.new(definition)
40
+ @plugins[plugin_name] ||= plugin_klass.new(environment)
41
41
  @plugins[plugin_name].configure(&block) if block
42
42
  else
43
43
  raise MissingPluginError, "no plugin registered with name #{plugin_name}, "\
@@ -118,7 +118,7 @@ module Kuby
118
118
 
119
119
  @namespace ||= KubeDSL.namespace do
120
120
  metadata do
121
- name "#{spec.selector_app}-#{spec.definition.environment.name}"
121
+ name "#{spec.selector_app}-#{spec.environment.name}"
122
122
  end
123
123
  end
124
124
 
@@ -156,11 +156,11 @@ module Kuby
156
156
  end
157
157
 
158
158
  def selector_app
159
- @selector_app ||= definition.app_name.downcase
159
+ @selector_app ||= environment.app_name.downcase
160
160
  end
161
161
 
162
162
  def docker
163
- definition.docker
163
+ environment.docker
164
164
  end
165
165
  end
166
166
  end
@@ -0,0 +1,59 @@
1
+ module Kuby
2
+ class Plugin
3
+ attr_reader :environment
4
+
5
+ def initialize(environment)
6
+ @environment = environment
7
+ after_initialize
8
+ end
9
+
10
+ def configure(&block)
11
+ # do nothing by default
12
+ end
13
+
14
+ def setup
15
+ # do nothing by default
16
+ end
17
+
18
+ # additional kubernetes resources that should be deployed
19
+ def resources
20
+ []
21
+ end
22
+
23
+ # additional dockerfiles that should be built and pushed
24
+ def dockerfiles
25
+ []
26
+ end
27
+
28
+ # called after all plugins have been configured
29
+ def after_configuration
30
+ # do nothing by default
31
+ end
32
+
33
+ # called before any plugins have been setup
34
+ def before_setup
35
+ # do nothing by default
36
+ end
37
+
38
+ # called after all plugins have been setup
39
+ def after_setup
40
+ # do nothing by default
41
+ end
42
+
43
+ # called before deploying any resources
44
+ def before_deploy(manifest)
45
+ # do nothing by default
46
+ end
47
+
48
+ # called after deploying all resources
49
+ def after_deploy(manifest)
50
+ # do nothing by default
51
+ end
52
+
53
+ private
54
+
55
+ def after_initialize
56
+ # override this in derived classes
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,6 @@
1
+ module Kuby
2
+ module Plugins
3
+ autoload :NginxIngress, 'kuby/plugins/nginx_ingress'
4
+ autoload :RailsApp, 'kuby/plugins/rails_app'
5
+ end
6
+ end
@@ -0,0 +1,71 @@
1
+ require 'kube-dsl'
2
+
3
+ module Kuby
4
+ module Plugins
5
+ class NginxIngress < ::Kuby::Plugin
6
+ class Config
7
+ extend ::KubeDSL::ValueFields
8
+
9
+ value_fields :provider
10
+ end
11
+
12
+ VERSION = '0.27.1'.freeze
13
+ DEFAULT_PROVIDER = 'cloud-generic'.freeze
14
+ NAMESPACE = 'ingress-nginx'.freeze
15
+ SERVICE_NAME = 'ingress-nginx'.freeze
16
+
17
+ SETUP_RESOURCES = [
18
+ "https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-#{VERSION}/deploy/static/mandatory.yaml",
19
+ "https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-#{VERSION}/deploy/static/provider/%{provider}.yaml"
20
+ ].freeze
21
+
22
+ def configure(&block)
23
+ @config.instance_eval(&block) if block
24
+ end
25
+
26
+ def setup
27
+ Kuby.logger.info('Deploying nginx ingress resources')
28
+
29
+ if already_deployed?
30
+ Kuby.logger.info('Nginx ingress already deployed, skipping')
31
+ return
32
+ end
33
+
34
+ SETUP_RESOURCES.each do |uri|
35
+ uri = uri % { provider: @config.provider || DEFAULT_PROVIDER }
36
+ kubernetes_cli.apply_uri(uri)
37
+ end
38
+
39
+ Kuby.logger.info('Nginx ingress resources deployed!')
40
+ rescue => e
41
+ Kuby.logger.fatal(e.message)
42
+ raise
43
+ end
44
+
45
+ def namespace
46
+ NAMESPACE
47
+ end
48
+
49
+ def service_name
50
+ SERVICE_NAME
51
+ end
52
+
53
+ private
54
+
55
+ def already_deployed?
56
+ kubernetes_cli.get_object('Service', 'ingress-nginx', 'ingress-nginx')
57
+ true
58
+ rescue KubernetesCLI::GetResourceError
59
+ return false
60
+ end
61
+
62
+ def after_initialize
63
+ @config = Config.new
64
+ end
65
+
66
+ def kubernetes_cli
67
+ environment.kubernetes.provider.kubernetes_cli
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,18 @@
1
+ module Kuby
2
+ module Plugins
3
+ module RailsApp
4
+ autoload :AssetCopyTask, 'kuby/plugins/rails_app/asset_copy_task'
5
+ autoload :Assets, 'kuby/plugins/rails_app/assets'
6
+ autoload :Database, 'kuby/plugins/rails_app/database'
7
+ autoload :MySQL, 'kuby/plugins/rails_app/mysql'
8
+ autoload :Plugin, 'kuby/plugins/rails_app/plugin'
9
+ autoload :Postgres, 'kuby/plugins/rails_app/postgres'
10
+ autoload :RewriteDbConfig, 'kuby/plugins/rails_app/rewrite_db_config'
11
+ autoload :Sqlite, 'kuby/plugins/rails_app/sqlite'
12
+ end
13
+ end
14
+ end
15
+
16
+ Kuby.register_plugin(:rails_assets, Kuby::Plugins::RailsApp::Assets)
17
+
18
+ load File.expand_path(File.join('rails_app', 'tasks.rake'), __dir__)
@@ -0,0 +1,117 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+
4
+ module Kuby
5
+ module Plugins
6
+ module RailsApp
7
+ # Works by maintaining a directory structure where each deploy creates
8
+ # a new directory. Each directory is given a timestamped name. Assets
9
+ # are symlinked into a special 'current' directory from each of the
10
+ # timestamped directories. Each invocation overrides existing symlinks
11
+ # if they already exist. This technique ensures assets from the previous
12
+ # deploy remain available while the web servers are restarting.
13
+ class AssetCopyTask
14
+ TIMESTAMP_FORMAT = '%Y%m%d%H%M%S'.freeze
15
+ KEEP = 5
16
+
17
+ attr_reader :dest_path, :source_path
18
+
19
+ def initialize(to:, from:)
20
+ @dest_path = to
21
+ @source_path = from
22
+ end
23
+
24
+ def run
25
+ FileUtils.mkdir_p(ts_dir)
26
+ FileUtils.mkdir_p(current_dir)
27
+
28
+ copy_new_assets
29
+ delete_old_assets
30
+
31
+ nil
32
+ end
33
+
34
+ private
35
+
36
+ def copy_new_assets
37
+ # Copy all assets to new timestamp directory
38
+ #
39
+ # "source_path/." is special syntax. From the Ruby docs:
40
+ # cp_r('src', 'dest') makes dest/src, but cp_r('src/.', 'dest') doesn't
41
+ FileUtils.cp_r(File.join(source_path, '.'), ts_dir)
42
+
43
+ relative_source_files = Dir.chdir(ts_dir) do
44
+ Dir.glob(File.join('**', '*'))
45
+ end
46
+
47
+ relative_source_files.each do |relative_source_file|
48
+ source_file = Pathname(File.join(current_dir, relative_source_file))
49
+ source_ts_file = Pathname(File.join(ts_dir, relative_source_file))
50
+ next unless File.file?(source_ts_file)
51
+
52
+ # create individual symlinks for each file in source dir
53
+ target_file = File.join(current_dir, relative_source_file)
54
+ FileUtils.mkdir_p(File.dirname(target_file))
55
+ source_ln_file = source_ts_file.relative_path_from(source_file.dirname)
56
+ FileUtils.ln_s(source_ln_file, target_file, force: true)
57
+ Kuby.logger.info("Linked #{source_ln_file} -> #{target_file}")
58
+ end
59
+ end
60
+
61
+ def delete_old_assets
62
+ # find all asset directories; directories have timestamp names
63
+ asset_dirs = (Dir.glob(File.join(dest_path, '*')) - [current_dir])
64
+ .select { |dir| File.directory?(dir) && try_parse_ts(File.basename(dir)) }
65
+ .sort_by { |dir| parse_ts(File.basename(dir)) }
66
+ .reverse
67
+
68
+ # only keep the n most recent directories
69
+ dirs_to_delete = asset_dirs[KEEP..-1] || []
70
+
71
+ dirs_to_delete.each do |dir_to_delete|
72
+ relative_files_to_delete = Dir.chdir(dir_to_delete) do
73
+ Dir.glob(File.join('**', '*'))
74
+ end
75
+
76
+ relative_files_to_delete.each do |relative_file_to_delete|
77
+ file_to_delete = File.join(dir_to_delete, relative_file_to_delete)
78
+ next unless File.file?(file_to_delete)
79
+
80
+ link = File.join(current_dir, relative_file_to_delete)
81
+ next unless File.symlink?(link)
82
+
83
+ # Only remove a symlink if it still points to a resource
84
+ # in the directory we're currently deleting. Othewise, leave
85
+ # it there - it was added by another deploy.
86
+ if File.readlink(link) == file_to_delete
87
+ File.unlink(link)
88
+ end
89
+ end
90
+
91
+ FileUtils.rm_r(dir_to_delete)
92
+ end
93
+ end
94
+
95
+ def try_parse_ts(ts)
96
+ parse_ts(ts)
97
+ rescue ArgumentError
98
+ return nil
99
+ end
100
+
101
+ def parse_ts(ts)
102
+ Time.strptime(ts, TIMESTAMP_FORMAT)
103
+ end
104
+
105
+ def ts_dir
106
+ @ts_dir ||= File.join(
107
+ dest_path, Time.now.strftime(TIMESTAMP_FORMAT)
108
+ )
109
+ end
110
+
111
+ def current_dir
112
+ @current_dir ||= File.join(dest_path, 'current')
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,347 @@
1
+ require 'kube-dsl'
2
+
3
+ module Kuby
4
+ module Plugins
5
+ module RailsApp
6
+ class Assets < ::Kuby::Plugin
7
+ extend ::KubeDSL::ValueFields
8
+
9
+ ROLE = 'assets'.freeze
10
+ NGINX_IMAGE = 'nginx:1.9-alpine'.freeze
11
+ NGINX_PORT = 8082
12
+ NGINX_MOUNT_PATH = '/usr/share/nginx/assets'.freeze
13
+ RAILS_MOUNT_PATH = '/usr/share/assets'.freeze
14
+
15
+ value_fields :asset_url, :packs_url, :asset_path
16
+
17
+ def configure(&block)
18
+ instance_eval(&block)
19
+ end
20
+
21
+ def configure_ingress(ingress, hostname)
22
+ spec = self
23
+
24
+ ingress.spec.rule do
25
+ host hostname
26
+
27
+ http do
28
+ path do
29
+ path spec.asset_url
30
+
31
+ backend do
32
+ service_name spec.service.metadata.name
33
+ service_port spec.service.spec.ports.first.port
34
+ end
35
+ end
36
+
37
+ path do
38
+ path spec.packs_url
39
+
40
+ backend do
41
+ service_name spec.service.metadata.name
42
+ service_port spec.service.spec.ports.first.port
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def configure_deployment(deployment, docker_image)
50
+ spec = self
51
+
52
+ deployment.spec.template.spec do
53
+ init_container(:copy_assets) do
54
+ name "#{spec.selector_app}-copy-assets"
55
+ command %w(bundle exec rake kuby:rails_app:assets:copy)
56
+ image docker_image
57
+
58
+ volume_mount do
59
+ name 'assets'
60
+ mount_path RAILS_MOUNT_PATH
61
+ end
62
+ end
63
+
64
+ container(:web) do
65
+ volume_mount do
66
+ name 'assets'
67
+ mount_path NGINX_MOUNT_PATH
68
+ end
69
+ end
70
+
71
+ volume do
72
+ name 'assets'
73
+
74
+ persistent_volume_claim do
75
+ claim_name spec.volume_claim.metadata.name
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ def copy_task
82
+ @copy_task ||= AssetCopyTask.new(
83
+ from: asset_path, to: RAILS_MOUNT_PATH
84
+ )
85
+ end
86
+
87
+ def service(&block)
88
+ spec = self
89
+
90
+ @service ||= KubeDSL.service do
91
+ metadata do
92
+ name "#{spec.selector_app}-#{spec.role}-svc"
93
+ namespace spec.namespace.metadata.name
94
+
95
+ labels do
96
+ add :app, spec.selector_app
97
+ add :role, spec.role
98
+ end
99
+ end
100
+
101
+ spec do
102
+ type 'NodePort'
103
+
104
+ selector do
105
+ add :app, spec.selector_app
106
+ add :role, spec.role
107
+ end
108
+
109
+ port do
110
+ name 'http'
111
+ port NGINX_PORT
112
+ protocol 'TCP'
113
+ target_port 'http'
114
+ end
115
+ end
116
+ end
117
+
118
+ @service.instance_eval(&block) if block
119
+ @service
120
+ end
121
+
122
+ def service_account(&block)
123
+ spec = self
124
+
125
+ @service_account ||= KubeDSL.service_account do
126
+ metadata do
127
+ name "#{spec.selector_app}-#{spec.role}-sa"
128
+ namespace spec.namespace.metadata.name
129
+
130
+ labels do
131
+ add :app, spec.selector_app
132
+ add :role, spec.role
133
+ end
134
+ end
135
+ end
136
+
137
+ @service_account.instance_eval(&block) if block
138
+ @service_account
139
+ end
140
+
141
+ def nginx_config(&block)
142
+ spec = self
143
+
144
+ @nginx_config ||= KubeDSL.config_map do
145
+ metadata do
146
+ name "#{spec.selector_app}-#{spec.role}-nginx-config"
147
+ namespace spec.namespace.metadata.name
148
+ end
149
+
150
+ data do
151
+ add 'nginx.conf', <<~END
152
+ user nginx;
153
+ worker_processes 1;
154
+
155
+ error_log /var/log/nginx/error.log warn;
156
+ pid /var/run/nginx.pid;
157
+
158
+ events {
159
+ worker_connections 1024;
160
+ }
161
+
162
+ http {
163
+ include /etc/nginx/mime.types;
164
+ default_type application/octet-stream;
165
+
166
+ log_format main '$remote_addr - $remote_user [$time_local] "$request" '
167
+ '$status $body_bytes_sent "$http_referer" '
168
+ '"$http_user_agent" "$http_x_forwarded_for"';
169
+
170
+ access_log /var/log/nginx/access.log main;
171
+
172
+ sendfile on;
173
+ keepalive_timeout 65;
174
+ gzip on;
175
+
176
+ server {
177
+ listen #{NGINX_PORT};
178
+ server_name localhost;
179
+
180
+ location / {
181
+ root #{File.join(NGINX_MOUNT_PATH, 'current')};
182
+ }
183
+
184
+ error_page 500 502 503 504 /500.html;
185
+ }
186
+ }
187
+ END
188
+ end
189
+ end
190
+
191
+ @nginx_config.instance_eval(&block) if block
192
+ @nginx_config
193
+ end
194
+
195
+ def deployment(&block)
196
+ kube_spec = self
197
+
198
+ @deployment ||= KubeDSL.deployment do
199
+ metadata do
200
+ name "#{kube_spec.selector_app}-#{kube_spec.role}"
201
+ namespace kube_spec.namespace.metadata.name
202
+
203
+ labels do
204
+ add :app, kube_spec.selector_app
205
+ add :role, kube_spec.role
206
+ end
207
+ end
208
+
209
+ spec do
210
+ replicas 1
211
+
212
+ selector do
213
+ match_labels do
214
+ add :app, kube_spec.selector_app
215
+ add :role, kube_spec.role
216
+ end
217
+ end
218
+
219
+ strategy do
220
+ type 'RollingUpdate'
221
+
222
+ rolling_update do
223
+ max_surge '25%'
224
+ max_unavailable 0
225
+ end
226
+ end
227
+
228
+ template do
229
+ metadata do
230
+ labels do
231
+ add :app, kube_spec.selector_app
232
+ add :role, kube_spec.role
233
+ end
234
+ end
235
+
236
+ spec do
237
+ container(:nginx) do
238
+ name "#{kube_spec.selector_app}-#{kube_spec.role}"
239
+ image_pull_policy 'IfNotPresent'
240
+ image NGINX_IMAGE
241
+
242
+ port do
243
+ container_port NGINX_PORT
244
+ name 'http'
245
+ protocol 'TCP'
246
+ end
247
+
248
+ volume_mount do
249
+ name 'nginx-config'
250
+ mount_path '/etc/nginx/nginx.conf'
251
+ sub_path 'nginx.conf'
252
+ end
253
+
254
+ volume_mount do
255
+ name 'assets'
256
+ mount_path NGINX_MOUNT_PATH
257
+ end
258
+
259
+ readiness_probe do
260
+ success_threshold 1
261
+ failure_threshold 2
262
+ initial_delay_seconds 5
263
+ period_seconds 3
264
+ timeout_seconds 1
265
+
266
+ http_get do
267
+ path '/500.html'
268
+ port NGINX_PORT
269
+ scheme 'HTTP'
270
+ end
271
+ end
272
+ end
273
+
274
+ volume do
275
+ name 'nginx-config'
276
+
277
+ config_map do
278
+ name kube_spec.nginx_config.metadata.name
279
+ end
280
+ end
281
+
282
+ volume do
283
+ name 'assets'
284
+
285
+ persistent_volume_claim do
286
+ claim_name kube_spec.volume_claim.metadata.name
287
+ end
288
+ end
289
+
290
+ restart_policy 'Always'
291
+ service_account_name kube_spec.service_account.metadata.name
292
+ end
293
+ end
294
+ end
295
+ end
296
+
297
+ @deployment.instance_eval(&block) if block
298
+ @deployment
299
+ end
300
+
301
+ def volume_claim
302
+ spec = self
303
+
304
+ @volume_claim ||= KubeDSL.persistent_volume_claim do
305
+ metadata do
306
+ name "#{spec.selector_app}-#{spec.role}"
307
+ namespace spec.namespace.metadata.name
308
+ end
309
+
310
+ spec do
311
+ access_modes ['ReadWriteOnce']
312
+ storage_class_name spec.environment.kubernetes.provider.storage_class_name
313
+
314
+ resources do
315
+ requests do
316
+ add :storage, '10Gi'
317
+ end
318
+ end
319
+ end
320
+ end
321
+ end
322
+
323
+ def resources
324
+ @resources ||= [
325
+ service,
326
+ service_account,
327
+ nginx_config,
328
+ deployment,
329
+ volume_claim
330
+ ]
331
+ end
332
+
333
+ def namespace
334
+ environment.kubernetes.namespace
335
+ end
336
+
337
+ def selector_app
338
+ environment.kubernetes.selector_app
339
+ end
340
+
341
+ def role
342
+ ROLE
343
+ end
344
+ end
345
+ end
346
+ end
347
+ end