kube_queue 0.2.0 → 0.3.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
- SHA1:
3
- metadata.gz: 1511d8cfdd1b8fb8375d4162e389750b4372bc19
4
- data.tar.gz: 963ace432f1420c863d94d491e81611fa7141279
2
+ SHA256:
3
+ metadata.gz: 1c864d0acb58ed1b538edeebcdec417d6284b9a4df1d151a9c008eb190e9b9f3
4
+ data.tar.gz: 49d9aff0aca53a00836f9358149741694173b19df4ea1e16aef40ff0a23441df
5
5
  SHA512:
6
- metadata.gz: 92c9790d5954b947e494e408556ba5cf5ddac75c2f5c4dc70b094833f2afdd24d4e565bcb3da86951c070283df1451d73d14dc672b74103cddfea40639b557b9
7
- data.tar.gz: 3932e801e465f3d5b7427dfd027af5b0680c8fffc338ab7f8abd151ca86010617386f00b35941a604436c94fc638e0b1aa4192f5f7198758cbe30caae7959170
6
+ metadata.gz: af4c1d02900ac0e864e59ee4fa81dcb43771ddafcdd50bf2dfc4956f2e37b04f121ca6bf13c7be185d2876f74ab582425022fe687f7ca9993e4261fd6b65bedb
7
+ data.tar.gz: 314585e098b2578c124ce28b3accf08e6f51c05058a0b2590ee5ca47c65ef0b58cabe6c5e105372693c986d6c2dc1ec51a8c3fb5a4052323f11ce4897419528d
data/.gcloudignore CHANGED
@@ -1 +1,4 @@
1
1
  !.git
2
+ secrets
3
+ examples/myapp/tmp/*
4
+ examples/myapp/log/*
data/.rubocop.yml CHANGED
@@ -63,3 +63,6 @@ Metrics/BlockLength:
63
63
  Style/MixinUsage:
64
64
  Exclude:
65
65
  - 'spec/**/*.rb'
66
+
67
+ Layout/AlignArguments:
68
+ Enabled: false
data/Dockerfile CHANGED
@@ -5,13 +5,13 @@ WORKDIR /app
5
5
  RUN apt-get update -y && apt-get install git --no-install-recommends && rm -r /var/cache/apt /var/lib/apt/lists
6
6
 
7
7
  RUN gem install bundler:2.0.2
8
- COPY docker/Gemfile Gemfile
8
+ COPY examples/docker/Gemfile Gemfile
9
9
  COPY Gemfile kube_queue.gemspec .git /vendor/kube_queue/
10
10
  COPY exe/kube_queue /vendor/kube_queue/exe/kube_queue
11
11
  COPY lib/kube_queue/version.rb /vendor/kube_queue/lib/kube_queue/version.rb
12
12
  RUN bundle install -j4
13
13
 
14
- COPY docker/test_worker.rb .
14
+ COPY examples/docker/test_worker.rb .
15
15
  COPY . /vendor/kube_queue
16
16
 
17
17
  CMD ["bundle", "exec", "kube_queue", "TestWorker", "-r", "./test_worker.rb"]
data/Dockerfile.myapp ADDED
@@ -0,0 +1,22 @@
1
+ FROM ruby
2
+
3
+ RUN apt-get update -y && apt-get install git --no-install-recommends && rm -r /var/cache/apt /var/lib/apt/lists
4
+
5
+ RUN gem install bundler:2.0.2
6
+
7
+ ENV KUBE_QUEUE_PATH /vendor/kube_queue
8
+ ENV WORKDIR /app
9
+
10
+ WORKDIR $WORKDIR
11
+
12
+ COPY .git $KUBE_QUEUE_PATH/.git
13
+ COPY Gemfile kube_queue.gemspec $KUBE_QUEUE_PATH/
14
+ COPY exe/kube_queue $KUBE_QUEUE_PATH/exe/kube_queue
15
+ COPY lib/kube_queue/version.rb $KUBE_QUEUE_PATH/lib/kube_queue/version.rb
16
+ COPY examples/myapp/Gemfile examples/myapp/Gemfile.lock $WORKDIR/
17
+ RUN bundle install -j4
18
+
19
+ COPY lib $KUBE_QUEUE_PATH/lib
20
+ COPY examples/myapp/ $WORKDIR
21
+
22
+ CMD ["bundle", "exec", "rails", "console"]
data/README.md CHANGED
@@ -1,13 +1,5 @@
1
1
  # KubeQueue
2
2
 
3
- ## Features
4
-
5
- - ActiveJob integration
6
- - Support multiple kubernetes client configuration.
7
- - Support templating and customization for kubernetes job manifest.
8
- - Job dosen't returns id. Can not track job details from code.
9
- - Logging
10
-
11
3
  ## Installation
12
4
 
13
5
  Add this line to your application's Gemfile:
@@ -44,29 +36,86 @@ class TestWorker
44
36
  end
45
37
  ```
46
38
 
47
- Setting kubernetes configuration and run:
39
+ Setting kubernetes configuration.
48
40
 
49
- ```
41
+ ```ruby
50
42
  KubeQueue.kubernetes_configure do |client|
51
43
  client.url = ENV['K8S_URL']
52
44
  client.ssl_ca_file = ENV['K8S_CA_CERT_FILE']
53
45
  client.auth_token = File.read(ENV['K8S_TOKEN'])
54
46
  end
47
+ ```
48
+
49
+ and run:
55
50
 
51
+ ```ruby
56
52
  TestWorker.perform(message: 'hello')
57
53
  ```
58
54
 
59
- ## Development
55
+ ### ActiveJob Support
56
+
57
+ Write to `application.rb`:
58
+
59
+ ```ruby
60
+ Rails.application.config.active_job.adapter = :kube_queue
61
+ ```
62
+
63
+ Just put your job into `app/jobs` . Example:
64
+
65
+ ```ruby
66
+ # app/jobs/print_message_job.rb
67
+ class PrintMessageJob < ApplicationJob
68
+ include KubeQueue::Worker
69
+
70
+ worker_name 'print-message-job'
71
+ image "your-registry/your-image"
72
+ container_name 'your-container-name'
73
+
74
+ def perform(payload)
75
+ logger.info payload[:message]
76
+ end
77
+ end
78
+ ```
79
+
80
+ and run:
81
+
82
+ ```ruby
83
+ irb(main):001:0> job = PrintMessageJob.perform_later(message: 'hello, kubernetes!')
84
+ Enqueued PrintMessageJob (Job ID: 0bf15b35-62d8-4380-9173-99839ce735ff) to KubeQueue(default) with arguments: {:message=>"hello, kubernetes!"}
85
+ => #<PrintMessageJob:0x00007fbfd00c7848 @arguments=[{:message=>"hello, kubernetes!"}], @job_id="0bf15b35-62d8-4380-9173-99839ce735ff", @queue_name="default", @priority=nil, @executions=0>
86
+ irb(main):002:0> job.status
87
+ => #<K8s::Resource startTime="2019-08-12T15:56:37Z", active=1>
88
+ irb(main):003:0> job.status
89
+ => #<K8s::Resource conditions=[{:type=>"Complete", :status=>"True", :lastProbeTime=>"2019-08-12T15:57:03Z", :lastTransitionTime=>"2019-08-12T15:57:03Z"}], startTime="2019-08-12T15:56:37Z", completionTime="2019-08-12T15:57:03Z", succeeded=1>
90
+ ```
91
+
92
+ See more examples in [here](examples).
93
+
94
+ ### Run job on locally
95
+
96
+ ```
97
+ bundle exec kube_queue runner JOB_NAME [PAYLOAD]
98
+ ```
99
+
100
+ See more information by `kube_queue help` or [here](exe/kube_queue).
101
+
102
+ ## Features
103
+
104
+ - Add tests.
105
+ - Support multiple kubernetes client configuration.
106
+ - Logging informations.
107
+
108
+ ## Development(on GCP/GKE)
60
109
 
61
110
  setup:
62
111
 
63
112
  ```
64
113
  # create service account and cluster role.
65
- kubectl apply -f k8s/service-account.yaml
114
+ kubectl apply -f examples/k8s/service-account.yaml
66
115
 
67
116
  # get ca.crt and token
68
- < k get secret -n kube-system kube-queue-test-token-xxx -o jsonpath="{['data']['token']}" | base64 -d > secrets/token
69
- < k get secret -n kube-system kube-queue-test-token-xxx -o jsonpath="{['data']['ca\.crt']}" | base64 -d > secrets/ca.crt
117
+ kubectl get secret -n kube-system kube-queue-test-token-xxx -o jsonpath="{['data']['token']}" | base64 -d > secrets/token
118
+ kubectl get secret -n kube-system kube-queue-test-token-xxx -o jsonpath="{['data']['ca\.crt']}" | base64 -d > secrets/ca.crt
70
119
 
71
120
  # build image
72
121
  gcloud builds submit --config cloudbuild.yaml .
@@ -77,5 +126,5 @@ run:
77
126
  ```
78
127
  K8S_URL=https://xx.xxx.xxx.xxx K8S_CA_CERT_FILE=$(pwd)/secrets/ca.crt K8S_TOKEN=$(pwd)/secrets/token IMAGE_NAME=gcr.io/your-project/kube-queue bin/console
79
128
 
80
- irb(main):001:0> TestWorker.perform_async(message: 'hello kubernetes')
129
+ irb(main):001:0> TestWorker.perform_async(message: 'hello, kubernetes!')
81
130
  ```
data/bin/console CHANGED
@@ -16,7 +16,7 @@ require "irb"
16
16
  KubeQueue.kubernetes_configure do |client|
17
17
  client.url = ENV['K8S_URL']
18
18
  client.ssl_ca_file = ENV['K8S_CA_CERT_FILE']
19
- client.auth_token = File.read(ENV['K8S_TOKEN'])
19
+ client.auth_token = ENV['K8S_TOKEN']
20
20
  end
21
21
 
22
22
  IRB.start(__FILE__)
data/cloudbuild.yaml CHANGED
@@ -1,5 +1,9 @@
1
- images: ['gcr.io/$PROJECT_ID/kube-queue:latest']
1
+ images: ['gcr.io/$PROJECT_ID/kube-queue:latest', 'gcr.io/$PROJECT_ID/kube-queue-test-app:latest']
2
2
 
3
3
  steps:
4
4
  - name: 'gcr.io/cloud-builders/docker'
5
5
  args: ['build', '-f', '/workspace/Dockerfile', '/workspace', '-t', 'gcr.io/$PROJECT_ID/kube-queue:latest']
6
+
7
+ - name: 'gcr.io/cloud-builders/docker'
8
+ args: ['build', '-f', '/workspace/Dockerfile.myapp', '/workspace', '-t', 'gcr.io/$PROJECT_ID/kube-queue-test-app:latest']
9
+ waitFor: ['-']
data/exe/kube_queue CHANGED
@@ -1,14 +1,63 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative '../lib/kube_queue/cli'
3
+ require 'rubygems'
4
+ require 'thor'
5
+ require 'kube_queue'
4
6
 
5
- begin
6
- cli = KubeQueue::CLI.new(ARGV)
7
- cli.run
8
- rescue StandardError => e
9
- raise e if $DEBUG
7
+ module KubeQueue
8
+ class CLI < Thor
9
+ default_task :version
10
10
 
11
- STDERR.puts e.message
12
- STDERR.puts e.backtrace.join("\n")
13
- exit 1
11
+ desc 'runner JOB_NAME [PAYLOAD]', 'run worker'
12
+ method_option :require, aliases: '-r', type: :string, desc: 'Location of Rails application with workers or file to require'
13
+ method_option :rails, aliases: '-R', type: :boolean, desc: 'Location of Rails application with workers or file to require'
14
+ def runner(job_name, payload = nil)
15
+ load_files!
16
+
17
+ # Infer application work on rails if require option does not specified.
18
+ load_rails! if !options[:require] || options[:rails]
19
+
20
+ payload ||= ENV['KUBE_QUEUE_MESSAGE_PAYLOAD']
21
+ payload = JSON.parse(payload) if payload
22
+ # Compatibility for ActiveJob serialized payload
23
+ payload = [payload] unless payload.is_a?(Array)
24
+ payload = ActiveJob::Arguments.deserialize(payload) if defined?(ActiveJob::Arguments)
25
+
26
+ job = KubeQueue.fetch_worker(job_name).new(*payload)
27
+
28
+ job.perform_now
29
+ end
30
+
31
+ desc 'version', 'Prints version'
32
+ def version
33
+ say "KubeQueue version #{KubeQueue::VERSION}"
34
+ end
35
+
36
+ private
37
+
38
+ def load_files!
39
+ return unless options[:require]
40
+
41
+ raise "#{options[:require]} dosent exist." unless File.exist?(options[:require])
42
+
43
+ files = File.directory?(options[:require]) ? Dir.glob(File.join(options[:require], '**/*.rb')) : [options[:require]]
44
+
45
+ files.each do |file|
46
+ require file
47
+ end
48
+ end
49
+
50
+ def load_rails!
51
+ require "rails"
52
+
53
+ raise "KubeQueue does not supports this version of Rails" if ::Rails::VERSION::MAJOR < 5
54
+
55
+ require 'rails'
56
+ require 'kube_queue/railties'
57
+ require File.expand_path('config/environment.rb')
58
+ Rails.application.eager_load!
59
+ end
60
+ end
14
61
  end
62
+
63
+ KubeQueue::CLI.start
data/kube_queue.gemspec CHANGED
@@ -18,13 +18,14 @@ Gem::Specification.new do |spec|
18
18
  spec.metadata["changelog_uri"] = "https://github.com/yuemori/kube_queue/CHANGELOG.md"
19
19
 
20
20
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
21
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|examples|cloudbuild.yaml)/}) }
22
22
  end
23
23
  spec.bindir = "exe"
24
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
25
  spec.require_paths = ["lib"]
26
26
 
27
27
  spec.add_dependency "k8s-client"
28
+ spec.add_dependency "thor"
28
29
  spec.add_development_dependency "bundler", "~> 2.0"
29
30
  spec.add_development_dependency "rake", "~> 10.0"
30
31
  spec.add_development_dependency "rspec", "~> 3.0"
@@ -0,0 +1,31 @@
1
+ require 'kube_queue'
2
+
3
+ module ActiveJob
4
+ module QueueAdapters
5
+ # == KubeQueue adapter for ActiveJob ==
6
+ #
7
+ # To use KubeQueue set the queue_adapter config to +:kube_queue+.
8
+ # Rails.application.config.active_job.queue_adapter = :kube_queue
9
+ class KubeQueueAdapter
10
+ class << self
11
+ # Interface for ActiveJob 4.2
12
+ def enqueue(job)
13
+ KubeQueue.executor.enqueue(job, ActiveJob::Arguments.serialize(job.arguments))
14
+ end
15
+
16
+ def enqueue_at(_job, _timestamp)
17
+ raise NotImplementedError
18
+ end
19
+ end
20
+
21
+ # Interface for ActiveJob 5.0
22
+ def enqueue(job)
23
+ KubeQueueAdapter.enqueue(job)
24
+ end
25
+
26
+ def enqueue_at(job, timestamp)
27
+ KubeQueueAdapter.enqueue_at(job, timestamp)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -5,8 +5,17 @@ module KubeQueue
5
5
  class Client
6
6
  def create_job(manifest)
7
7
  job = K8s::Resource.new(manifest)
8
- job.metadata.namespace = 'default'
9
- client.api('batch/v1').resource('jobs').create_resource(job)
8
+ job.metadata.namespace ||= 'default'
9
+ client.resource('jobs').create_resource(job)
10
+ end
11
+
12
+ def get_job(namespace, name)
13
+ client.resource('jobs', namespace: namespace).get(name)
14
+ end
15
+
16
+ def list_job(namespace = nil)
17
+ selector = { 'kube-queue-job': 'true' }
18
+ client.resource('jobs', namespace: namespace).list(labelSelector: selector)
10
19
  end
11
20
 
12
21
  attr_accessor :url, :ssl_ca_file, :auth_token
@@ -14,7 +23,7 @@ module KubeQueue
14
23
  private
15
24
 
16
25
  def client
17
- @client ||= K8s.client(url, ssl_ca_file: ssl_ca_file, auth_token: auth_token)
26
+ @client ||= K8s.client(url, ssl_ca_file: ssl_ca_file, auth_token: auth_token).api('batch/v1')
18
27
  end
19
28
  end
20
29
  end
@@ -1,10 +1,9 @@
1
- require 'erb'
2
- require 'json'
1
+ require 'kube_queue/manifest_builder'
3
2
 
4
3
  module KubeQueue
5
4
  class Executor
6
- def perform_async(job, body)
7
- manifest = job.build_manifest(body)
5
+ def enqueue(job, payload)
6
+ manifest = ManifestBuilder.new(job, payload).build
8
7
  KubeQueue.client.create_job(manifest)
9
8
  end
10
9
  end
@@ -0,0 +1,64 @@
1
+ require 'erb'
2
+ require 'yaml'
3
+
4
+ module KubeQueue
5
+ class JobSpecification
6
+ class MissingParameterError < StandardError; end
7
+
8
+
9
+ attr_reader :job_class
10
+
11
+ attr_accessor :payload, :name, :active_deadline_seconds, :backoff_limit
12
+
13
+ attr_writer :image, :namespace, :worker_name, :command,
14
+ :container_name, :restart_policy, :job_labels, :pod_labels
15
+
16
+ def initialize(job_class)
17
+ @job_class = job_class
18
+ end
19
+
20
+ def job_name(job_id)
21
+ "#{worker_name}-#{job_id}"
22
+ end
23
+
24
+ def image
25
+ @image || raise_not_found_required_parameter('image')
26
+ end
27
+
28
+ def namespace
29
+ @namespace || 'default'
30
+ end
31
+
32
+ def worker_name
33
+ @worker_name || raise_not_found_required_parameter('worker_name')
34
+ end
35
+
36
+ def container_name
37
+ @container_name || worker_name
38
+ end
39
+
40
+ def command
41
+ @command || ['bundle', 'exec', 'kube_queue', 'runner', job_class.name]
42
+ end
43
+
44
+ def restart_policy
45
+ @restart_policy || 'Never'
46
+ end
47
+
48
+ def job_labels
49
+ @job_labels || {}
50
+ end
51
+
52
+ def pod_labels
53
+ @pod_labels || {}
54
+ end
55
+
56
+ def env
57
+ KubeQueue.default_env.merge(@env || {})
58
+ end
59
+
60
+ def raise_not_found_required_parameter(field)
61
+ raise MissingParameterError, "#{field} is required"
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,25 @@
1
+ require 'erb'
2
+ require 'json'
3
+
4
+ module KubeQueue
5
+ class ManifestBuilder
6
+ attr_reader :job
7
+
8
+ def initialize(job, payload = nil)
9
+ @job = job
10
+ @payload = payload
11
+ end
12
+
13
+ def spec
14
+ job.job_spec
15
+ end
16
+
17
+ def payload
18
+ @payload ? JSON.generate(@payload, quirks_mode: true) : nil
19
+ end
20
+
21
+ def build
22
+ YAML.safe_load(ERB.new(job.template, nil, "-").result(binding))
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ ActiveSupport.on_load(:active_job) do
2
+ require "active_job/adapters/kube_queue_adapter"
3
+ end
@@ -1,3 +1,3 @@
1
1
  module KubeQueue
2
- VERSION = "0.2.0".freeze
2
+ VERSION = "0.3.0".freeze
3
3
  end
@@ -0,0 +1,49 @@
1
+ module KubeQueue
2
+ module Worker
3
+ module DSL
4
+ def job_spec
5
+ @job_spec ||= JobSpecification.new(self)
6
+ end
7
+
8
+ def worker_name(name)
9
+ job_spec.worker_name = name
10
+ end
11
+
12
+ def container_name(container_name)
13
+ job_spec.container_name = container_name
14
+ end
15
+
16
+ def image(image)
17
+ job_spec.image = image
18
+ end
19
+
20
+ def namespace(namespace)
21
+ job_spec.namespace = namespace
22
+ end
23
+
24
+ def command(*command)
25
+ job_spec.command = command
26
+ end
27
+
28
+ def restart_policy(policy)
29
+ job_spec.restart_policy = policy
30
+ end
31
+
32
+ def active_deadline_seconds(seconds)
33
+ job_spec.active_deadline_seconds = seconds
34
+ end
35
+
36
+ def backoff_limit(limit)
37
+ job_spec.backoff_limit = limit
38
+ end
39
+
40
+ def env(env)
41
+ job_spec.env = env
42
+ end
43
+
44
+ def labels(labels)
45
+ job_spec.labels = labels
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,6 +1,5 @@
1
- require 'erb'
2
- require 'yaml'
3
- require 'kube_queue/job_configuration'
1
+ require 'kube_queue/worker/dsl'
2
+ require 'kube_queue/job_specification'
4
3
 
5
4
  module KubeQueue
6
5
  module Worker
@@ -11,57 +10,55 @@ module KubeQueue
11
10
  end
12
11
 
13
12
  module ClassMethods
14
- def job_name(name)
15
- @job_name = name
16
- end
13
+ include DSL
17
14
 
18
- def container_name(container_name)
19
- @container_name = container_name
15
+ def enqueue(body = nil)
16
+ KubeQueue.executor.enqueue(new, body)
20
17
  end
18
+ alias_method :perform_async, :enqueue
21
19
 
22
- def image(image)
23
- @image = image
20
+ def enqueue_at(body = nil)
21
+ KubeQueue.executor.enqueue(new, body)
24
22
  end
25
23
 
26
- def command(*command)
27
- @command = command
24
+ def read_template
25
+ File.read(@template || File.expand_path('../../../template/job.yaml', __FILE__))
28
26
  end
27
+ end
29
28
 
30
- def template(template)
31
- @template = template
32
- end
29
+ def template
30
+ self.class.read_template
31
+ end
33
32
 
34
- def restart_policy(policy)
35
- @restart_policy = policy
36
- end
33
+ def job_spec
34
+ self.class.job_spec
35
+ end
37
36
 
38
- def backoff_limit(limit)
39
- @backoff_limit = limit
40
- end
37
+ attr_accessor :job_id, :arguments
41
38
 
42
- def build_manifest(body)
43
- spec = JobSpecification.new.configure do |s|
44
- s.id = SecureRandom.uuid
45
- s.image = @image
46
- s.name = name
47
- s.command = @command
48
- s.job_name = @job_name
49
- s.container_name = @job_name
50
- s.template = @template
51
- s.backoff_limit = @backoff_limit
52
- s.payload = JSON.generate(body, quirks_mode: true)
53
- end
39
+ def initialize(*arguments)
40
+ # Compatibility for ActiveJob interface
41
+ super
54
42
 
55
- spec.to_manifest
56
- end
43
+ @arguments = arguments
44
+ @job_id = SecureRandom.uuid
45
+ end
57
46
 
58
- def perform_sync(body)
59
- new.perform(body)
60
- end
47
+ def perform_now
48
+ # Compatibility for ActiveJob interface
49
+ return super if defined?(super)
61
50
 
62
- def perform_async(body)
63
- KubeQueue.executor.perform_async(self, body)
64
- end
51
+ perform(*arguments)
52
+ end
53
+
54
+ def perform(*)
55
+ raise NotImplementedError
56
+ end
57
+
58
+ # FIXME: improve performance
59
+ def status
60
+ res = KubeQueue.client.get_job(job_spec.namespace, job_spec.job_name(job_id))
61
+ res.status
65
62
  end
66
63
  end
67
64
  end
data/lib/kube_queue.rb CHANGED
@@ -3,9 +3,10 @@ require "kube_queue/executor"
3
3
  require "kube_queue/configuration"
4
4
  require "kube_queue/worker"
5
5
  require "kube_queue/client"
6
+ require "active_job/adapters/kube_queue_adapter" if defined?(Rails)
6
7
 
7
8
  module KubeQueue
8
- class Error < StandardError; end
9
+ class JobNotFound < StandardError; end
9
10
 
10
11
  class << self
11
12
  attr_writer :executor
@@ -34,6 +35,31 @@ module KubeQueue
34
35
  @configuration ||= Configuration.new
35
36
  end
36
37
 
38
+ def list(namespace = nil)
39
+ client.list_job(namespace).map do |job|
40
+ worker = fetch_worker(job.metadata.annotations['kube-queue-job-class'])
41
+ job_id = job.metadata.annotations['kube-queue-job-id']
42
+ payload = deserialize(job.metadata.annotations['kube-queue-message-payload'])
43
+
44
+ job = worker.new(*payload)
45
+ job.job_id = job_id
46
+ job
47
+ end
48
+ end
49
+
50
+ attr_writer :default_env
51
+
52
+ def default_env
53
+ return @default_env if @default_env
54
+
55
+ return {} unless defined?(Rails)
56
+
57
+ {
58
+ RAILS_LOG_TO_STDOUT: ENV['RAILS_LOG_TO_STDOUT'],
59
+ RAILS_ENV: ENV['RAILS_ENV']
60
+ }
61
+ end
62
+
37
63
  def fetch_worker(name)
38
64
  worker_registry.fetch(name)
39
65
  end
@@ -45,5 +71,29 @@ module KubeQueue
45
71
  def worker_registry
46
72
  @worker_registry ||= {}
47
73
  end
74
+
75
+ private
76
+
77
+ def deserialize(payload)
78
+ return payload if payload.blank?
79
+
80
+ payload = JSON.parse(payload)
81
+ # Compatibility for ActiveJob serialized payload
82
+ payload = [payload] unless payload.is_a?(Array)
83
+
84
+ if defined?(ActiveJob::Arguments)
85
+ begin
86
+ payload = ActiveJob::Arguments.deserialize(payload)
87
+ rescue ActiveJob::DeserializationError => e
88
+ logger.warn e.message
89
+ logger.warn "#{payload} can not deserialized"
90
+ end
91
+ end
92
+
93
+ payload
94
+ rescue JSON::ParseError => e
95
+ logger.warn e.message
96
+ logger.warn "#{payload} can not deserialized"
97
+ end
48
98
  end
49
99
  end
data/template/job.yaml CHANGED
@@ -1,16 +1,44 @@
1
1
  apiVersion: batch/v1
2
2
  kind: Job
3
3
  metadata:
4
- name: "<%= job_name %>-<%= id %>"
4
+ annotations:
5
+ kube-queue-message-payload: '<%= payload %>'
6
+ kube-queue-job-class: "<%= spec.job_class %>"
7
+ kube-queue-job-id: "<%= job.job_id %>"
8
+ name: "<%= spec.job_name(job.job_id) %>"
9
+ namespace: <%= spec.namespace %>
10
+ labels:
11
+ kube-queue-job: "true"
12
+ kube-queue-worker-name: "<%= spec.worker_name %>"
13
+ kube-queue-job-class: "<%= spec.job_class %>"
14
+ kube-queue-job-id: "<%= job.job_id %>"
5
15
  spec:
6
16
  template:
17
+ metadata:
18
+ annotations:
19
+ kube-queue-message-payload: '<%= payload %>'
20
+ kube-queue-job-class: "<%= spec.job_class %>"
21
+ kube-queue-job-id: "<%= job.job_id %>"
22
+ labels:
23
+ kube-queue-job: "true"
24
+ kube-queue-worker-name: "<%= spec.worker_name %>"
25
+ kube-queue-job-class: "<%= spec.job_class %>"
26
+ kube-queue-job-id: "<%= job.job_id %>"
27
+ <%- spec.job_labels.each do |key, value| %>
28
+ <%= key %>: "<%= value %>"
29
+ <%- end %>
7
30
  spec:
8
31
  containers:
9
- - name: "<%= container_name %>"
10
- image: "<%= image %>"
11
- command: <%= command %>
32
+ - name: "<%= spec.container_name %>"
33
+ image: "<%= spec.image %>"
34
+ command: <%= spec.command %>
12
35
  env:
13
36
  - name: "KUBE_QUEUE_MESSAGE_PAYLOAD"
14
37
  value: '<%= payload %>'
15
- restartPolicy: "<%= restart_policy %>"
16
- backoffLimit: <%= backoff_limit %>
38
+ <%- spec.env.each do |key, value| %>
39
+ - name: "<%= key %>"
40
+ value: "<%= value %>"
41
+ <%- end %>
42
+ restartPolicy: "<%= spec.restart_policy %>"
43
+ backoffLimit: <%= spec.backoff_limit %>
44
+ activeDeadlineSeconds: <%= spec.active_deadline_seconds %>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kube_queue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - yuemori
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-08-10 00:00:00.000000000 Z
11
+ date: 2019-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: k8s-client
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -152,6 +166,7 @@ files:
152
166
  - ".travis.yml"
153
167
  - CODE_OF_CONDUCT.md
154
168
  - Dockerfile
169
+ - Dockerfile.myapp
155
170
  - Gemfile
156
171
  - LICENSE.txt
157
172
  - README.md
@@ -159,20 +174,19 @@ files:
159
174
  - bin/console
160
175
  - bin/setup
161
176
  - cloudbuild.yaml
162
- - docker/Gemfile
163
- - docker/test_worker.rb
164
177
  - exe/kube_queue
165
- - k8s/service-account.yaml
166
178
  - kube_queue.gemspec
179
+ - lib/active_job/adapters/kube_queue_adapter.rb
167
180
  - lib/kube_queue.rb
168
- - lib/kube_queue/cli.rb
169
181
  - lib/kube_queue/client.rb
170
182
  - lib/kube_queue/configuration.rb
171
183
  - lib/kube_queue/executor.rb
172
- - lib/kube_queue/job_configuration.rb
173
- - lib/kube_queue/runner.rb
184
+ - lib/kube_queue/job_specification.rb
185
+ - lib/kube_queue/manifest_builder.rb
186
+ - lib/kube_queue/railties.rb
174
187
  - lib/kube_queue/version.rb
175
188
  - lib/kube_queue/worker.rb
189
+ - lib/kube_queue/worker/dsl.rb
176
190
  - template/job.yaml
177
191
  homepage: https://github.com/yuemori/kube_queue
178
192
  licenses:
@@ -196,8 +210,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
196
210
  - !ruby/object:Gem::Version
197
211
  version: '0'
198
212
  requirements: []
199
- rubyforge_project:
200
- rubygems_version: 2.6.11
213
+ rubygems_version: 3.0.3
201
214
  signing_key:
202
215
  specification_version: 4
203
216
  summary: A background job processing with Kubernetes job for Ruby
data/docker/Gemfile DELETED
@@ -1,3 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem 'kube_queue', path: '/vendor/kube_queue'
@@ -1,14 +0,0 @@
1
- require 'kube_queue'
2
-
3
- class TestWorker
4
- include KubeQueue::Worker
5
-
6
- job_name 'kube-queue-test'
7
- image ENV['IMAGE_NAME']
8
- container_name 'kube-queue-test'
9
- command 'bundle', 'exec', 'kube_queue', 'TestWorker', '-r', './test_worker.rb'
10
-
11
- def perform(payload)
12
- puts payload['message']
13
- end
14
- end
@@ -1,18 +0,0 @@
1
- apiVersion: v1
2
- kind: ServiceAccount
3
- metadata:
4
- name: kube-queue-test
5
- namespace: kube-system
6
- ---
7
- apiVersion: rbac.authorization.k8s.io/v1
8
- kind: ClusterRoleBinding
9
- metadata:
10
- name: kube-queue-test
11
- roleRef:
12
- apiGroup: rbac.authorization.k8s.io
13
- kind: ClusterRole
14
- name: cluster-admin
15
- subjects:
16
- - kind: ServiceAccount
17
- name: kube-queue-test
18
- namespace: kube-system
@@ -1,56 +0,0 @@
1
- require 'logger'
2
- require 'json'
3
- require 'optparse'
4
- require 'kube_queue/runner'
5
-
6
- module KubeQueue
7
- class CLI
8
- attr_reader :argv
9
-
10
- def initialize(argv = ARGV)
11
- parse_options(argv)
12
-
13
- @argv = argv
14
- end
15
-
16
- def run
17
- load_files!
18
-
19
- runner = Runner.new(job_name)
20
-
21
- runner.run(payload)
22
- end
23
-
24
- private
25
-
26
- def job_name
27
- argv[0]
28
- end
29
-
30
- def payload
31
- payload = ENV['KUBE_QUEUE_MESSAGE_PAYLOAD']
32
-
33
- return payload if payload
34
-
35
- raise 'Payload is missing. Please set payload to KUBE_QUEUE_MESSAGE_PAYLOAD environment variable'
36
- end
37
-
38
- def load_files!
39
- require options[:require] if options[:require]
40
- end
41
-
42
- def options
43
- @options ||= {}
44
- end
45
-
46
- def parse_options(argv)
47
- parser = OptionParser.new do |o|
48
- o.on '-r', '--require [PATH|DIR]', 'Location of Rails application with workers or file to require' do |arg|
49
- options[:require] = arg
50
- end
51
- end
52
-
53
- parser.parse!(argv)
54
- end
55
- end
56
- end
@@ -1,54 +0,0 @@
1
- require 'forwardable'
2
-
3
- module KubeQueue
4
- class JobSpecification
5
- extend Forwardable
6
-
7
- attr_accessor :payload, :id, :name
8
-
9
- attr_writer :image, :job_name, :command, :container_name, :template, :backoff_limit, :restart_policy
10
-
11
- def configure
12
- yield self
13
- self
14
- end
15
-
16
- def backoff_limit
17
- @backoff_limit || 0
18
- end
19
-
20
- def restart_policy
21
- @restart_policy || 'Never'
22
- end
23
-
24
- def command
25
- @command || ['bundle', 'exec', 'kube_queue', name]
26
- end
27
-
28
- def image
29
- @image || raise
30
- end
31
-
32
- def job_name
33
- @job_name || raise
34
- end
35
-
36
- def container_name
37
- @container_name || raise
38
- end
39
-
40
- def template
41
- @template || File.read(File.expand_path('../../../template/job.yaml', __FILE__))
42
- end
43
-
44
- def to_manifest
45
- YAML.safe_load(ERB.new(template, nil, "%").result(binding))
46
- end
47
-
48
- private
49
-
50
- def binding
51
- super
52
- end
53
- end
54
- end
@@ -1,17 +0,0 @@
1
- require 'logger'
2
- require 'json'
3
-
4
- module KubeQueue
5
- class Runner
6
- def initialize(job_name)
7
- @job_name = job_name
8
- end
9
-
10
- def run(payload)
11
- payload = JSON.parse(payload)
12
-
13
- worker = KubeQueue.fetch_worker(@job_name)
14
- worker.new.perform(payload)
15
- end
16
- end
17
- end