kuby-core 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/kuby-core.gemspec +5 -3
  4. data/lib/kuby.rb +5 -3
  5. data/lib/kuby/definition.rb +0 -8
  6. data/lib/kuby/docker/layer.rb +4 -4
  7. data/lib/kuby/docker/metadata.rb +6 -6
  8. data/lib/kuby/docker/package_phase.rb +2 -2
  9. data/lib/kuby/docker/spec.rb +11 -11
  10. data/lib/kuby/environment.rb +6 -2
  11. data/lib/kuby/kubernetes.rb +0 -2
  12. data/lib/kuby/kubernetes/deploy_task.rb +0 -1
  13. data/lib/kuby/kubernetes/deployer.rb +7 -7
  14. data/lib/kuby/kubernetes/minikube_provider.rb +4 -0
  15. data/lib/kuby/kubernetes/provider.rb +5 -5
  16. data/lib/kuby/kubernetes/spec.rb +8 -8
  17. data/lib/kuby/plugin.rb +59 -0
  18. data/lib/kuby/plugins.rb +6 -0
  19. data/lib/kuby/plugins/nginx_ingress.rb +71 -0
  20. data/lib/kuby/plugins/rails_app.rb +18 -0
  21. data/lib/kuby/plugins/rails_app/asset_copy_task.rb +117 -0
  22. data/lib/kuby/plugins/rails_app/assets.rb +347 -0
  23. data/lib/kuby/plugins/rails_app/database.rb +75 -0
  24. data/lib/kuby/{kubernetes/plugins → plugins}/rails_app/generators/kuby.rb +0 -0
  25. data/lib/kuby/plugins/rails_app/mysql.rb +155 -0
  26. data/lib/kuby/plugins/rails_app/plugin.rb +398 -0
  27. data/lib/kuby/plugins/rails_app/postgres.rb +143 -0
  28. data/lib/kuby/plugins/rails_app/rewrite_db_config.rb +11 -0
  29. data/lib/kuby/plugins/rails_app/sqlite.rb +32 -0
  30. data/lib/kuby/{kubernetes/plugins → plugins}/rails_app/tasks.rake +10 -2
  31. data/lib/kuby/tasks.rb +9 -9
  32. data/lib/kuby/tasks/kuby.rake +1 -1
  33. data/lib/kuby/version.rb +1 -1
  34. metadata +34 -27
  35. data/lib/ext/krane/kubernetes_resource.rb +0 -16
  36. data/lib/kuby/kubernetes/plugin.rb +0 -55
  37. data/lib/kuby/kubernetes/plugins.rb +0 -8
  38. data/lib/kuby/kubernetes/plugins/nginx_ingress.rb +0 -73
  39. data/lib/kuby/kubernetes/plugins/rails_app.rb +0 -16
  40. data/lib/kuby/kubernetes/plugins/rails_app/database.rb +0 -79
  41. data/lib/kuby/kubernetes/plugins/rails_app/mysql.rb +0 -154
  42. data/lib/kuby/kubernetes/plugins/rails_app/plugin.rb +0 -379
  43. data/lib/kuby/kubernetes/plugins/rails_app/postgres.rb +0 -142
  44. data/lib/kuby/kubernetes/plugins/rails_app/rewrite_db_config.rb +0 -13
  45. data/lib/kuby/kubernetes/plugins/rails_app/sqlite.rb +0 -30
@@ -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