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 +5 -5
- data/.gcloudignore +3 -0
- data/.rubocop.yml +3 -0
- data/Dockerfile +2 -2
- data/Dockerfile.myapp +22 -0
- data/README.md +64 -15
- data/bin/console +1 -1
- data/cloudbuild.yaml +5 -1
- data/exe/kube_queue +58 -9
- data/kube_queue.gemspec +2 -1
- data/lib/active_job/adapters/kube_queue_adapter.rb +31 -0
- data/lib/kube_queue/client.rb +12 -3
- data/lib/kube_queue/executor.rb +3 -4
- data/lib/kube_queue/job_specification.rb +64 -0
- data/lib/kube_queue/manifest_builder.rb +25 -0
- data/lib/kube_queue/railties.rb +3 -0
- data/lib/kube_queue/version.rb +1 -1
- data/lib/kube_queue/worker/dsl.rb +49 -0
- data/lib/kube_queue/worker.rb +38 -41
- data/lib/kube_queue.rb +51 -1
- data/template/job.yaml +34 -6
- metadata +23 -10
- data/docker/Gemfile +0 -3
- data/docker/test_worker.rb +0 -14
- data/k8s/service-account.yaml +0 -18
- data/lib/kube_queue/cli.rb +0 -56
- data/lib/kube_queue/job_configuration.rb +0 -54
- data/lib/kube_queue/runner.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1c864d0acb58ed1b538edeebcdec417d6284b9a4df1d151a9c008eb190e9b9f3
|
4
|
+
data.tar.gz: 49d9aff0aca53a00836f9358149741694173b19df4ea1e16aef40ff0a23441df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af4c1d02900ac0e864e59ee4fa81dcb43771ddafcdd50bf2dfc4956f2e37b04f121ca6bf13c7be185d2876f74ab582425022fe687f7ca9993e4261fd6b65bedb
|
7
|
+
data.tar.gz: 314585e098b2578c124ce28b3accf08e6f51c05058a0b2590ee5ca47c65ef0b58cabe6c5e105372693c986d6c2dc1ec51a8c3fb5a4052323f11ce4897419528d
|
data/.gcloudignore
CHANGED
data/.rubocop.yml
CHANGED
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
|
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
|
-
|
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
|
-
|
69
|
-
|
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 =
|
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
|
-
|
3
|
+
require 'rubygems'
|
4
|
+
require 'thor'
|
5
|
+
require 'kube_queue'
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
rescue StandardError => e
|
9
|
-
raise e if $DEBUG
|
7
|
+
module KubeQueue
|
8
|
+
class CLI < Thor
|
9
|
+
default_task :version
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
data/lib/kube_queue/client.rb
CHANGED
@@ -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
|
9
|
-
client.
|
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
|
data/lib/kube_queue/executor.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
require '
|
2
|
-
require 'json'
|
1
|
+
require 'kube_queue/manifest_builder'
|
3
2
|
|
4
3
|
module KubeQueue
|
5
4
|
class Executor
|
6
|
-
def
|
7
|
-
manifest =
|
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
|
data/lib/kube_queue/version.rb
CHANGED
@@ -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
|
data/lib/kube_queue/worker.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
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
|
-
|
15
|
-
@job_name = name
|
16
|
-
end
|
13
|
+
include DSL
|
17
14
|
|
18
|
-
def
|
19
|
-
|
15
|
+
def enqueue(body = nil)
|
16
|
+
KubeQueue.executor.enqueue(new, body)
|
20
17
|
end
|
18
|
+
alias_method :perform_async, :enqueue
|
21
19
|
|
22
|
-
def
|
23
|
-
|
20
|
+
def enqueue_at(body = nil)
|
21
|
+
KubeQueue.executor.enqueue(new, body)
|
24
22
|
end
|
25
23
|
|
26
|
-
def
|
27
|
-
@
|
24
|
+
def read_template
|
25
|
+
File.read(@template || File.expand_path('../../../template/job.yaml', __FILE__))
|
28
26
|
end
|
27
|
+
end
|
29
28
|
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
def template
|
30
|
+
self.class.read_template
|
31
|
+
end
|
33
32
|
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
def job_spec
|
34
|
+
self.class.job_spec
|
35
|
+
end
|
37
36
|
|
38
|
-
|
39
|
-
@backoff_limit = limit
|
40
|
-
end
|
37
|
+
attr_accessor :job_id, :arguments
|
41
38
|
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
56
|
-
|
43
|
+
@arguments = arguments
|
44
|
+
@job_id = SecureRandom.uuid
|
45
|
+
end
|
57
46
|
|
58
|
-
|
59
|
-
|
60
|
-
|
47
|
+
def perform_now
|
48
|
+
# Compatibility for ActiveJob interface
|
49
|
+
return super if defined?(super)
|
61
50
|
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
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
|
-
|
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
|
-
|
16
|
-
|
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.
|
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-
|
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/
|
173
|
-
- lib/kube_queue/
|
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
|
-
|
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
data/docker/test_worker.rb
DELETED
@@ -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
|
data/k8s/service-account.yaml
DELETED
@@ -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
|
data/lib/kube_queue/cli.rb
DELETED
@@ -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
|
data/lib/kube_queue/runner.rb
DELETED
@@ -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
|