resque-kubernetes 0.5.0 → 0.6.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +4 -0
- data/README.md +116 -52
- data/lib/resque/kubernetes/context_factory.rb +22 -1
- data/lib/resque/kubernetes/job.rb +64 -20
- data/lib/resque/kubernetes/version.rb +1 -1
- metadata +2 -3
- data/.travis.yml +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: acdff0738bfd9d4f3aeb8c2ba1f8eb744b5d3f59
|
4
|
+
data.tar.gz: 80bafaa288815eb1ddfdea7041e0edc302b5e7f0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a542e73f31e4e9eff7194ef03e356185d4e0cd89820b3f59b043b5521c58b557493ec76c37d2cd6c838e640daa66581aff8a5e7a44d212886743210f943733f8
|
7
|
+
data.tar.gz: 46132bfd101f9353cdc0eddd3777825ee93f893f2d3ddb9c0c33f645a75b3a477898f6f77c9bff02bb62c02d0ecbb5c5fc615a9f6355c37acb5edbccfa148c68
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# 0.6.0
|
2
|
+
- Add support for ActiveJob when configured to be backed by Resque
|
3
|
+
- When authorizing with `~/.kube/config` use Google Default Application Credentials rather than require a
|
4
|
+
forked version of `kubeclient`
|
1
5
|
# 0.5.0
|
2
6
|
- Maximum workers can no be configured per job type
|
3
7
|
- Fix a crash when cleaning up a job that was removed by another process
|
data/README.md
CHANGED
@@ -1,28 +1,28 @@
|
|
1
1
|
# Resque::Kubernetes
|
2
2
|
|
3
|
-
Run Resque
|
3
|
+
Run Resque (and ActiveJob) Workers as Kubernetes Jobs!
|
4
4
|
|
5
5
|
Kubernetes has a concept of "Job" which is a pod that runs a container until
|
6
6
|
the container finishes and then it terminates the pod (as opposed to trying to
|
7
7
|
restart the container).
|
8
8
|
|
9
|
-
This gem takes advantage of that feature by starting up a Kubernetes Job
|
10
|
-
a Resque
|
11
|
-
terminate when there are no more jobs in the queue.
|
9
|
+
This gem takes advantage of that feature by starting up a Kubernetes Job to
|
10
|
+
run a worker when a Resque job or ActiveJob is enqueued. It then allows the
|
11
|
+
Resque worker to be modified to terminate when there are no more jobs in the queue.
|
12
12
|
|
13
13
|
Why would you do this?
|
14
14
|
|
15
15
|
We have unpredictable, resource-intensive jobs. Rather than dedicating large
|
16
16
|
nodes in our cluster to run the resque workers, where the resources would be
|
17
17
|
idle when there are no jobs to run, we can use auto-scaling to add nodes when
|
18
|
-
Kubernetes Job gets created and shut them down when those jobs are complete.
|
18
|
+
a Kubernetes Job gets created and shut them down when those jobs are complete.
|
19
19
|
|
20
20
|
## Installation
|
21
21
|
|
22
22
|
Add this line to your application's Gemfile:
|
23
23
|
|
24
24
|
```ruby
|
25
|
-
gem
|
25
|
+
gem "resque-kubernetes"
|
26
26
|
```
|
27
27
|
|
28
28
|
And then execute:
|
@@ -35,42 +35,96 @@ Or install it yourself as:
|
|
35
35
|
|
36
36
|
## Usage
|
37
37
|
|
38
|
-
|
39
|
-
|
38
|
+
This works with native/pure Resque jobs and with ActiveJob _backed by Resque_.
|
39
|
+
Under ActiveJob, the workers are still Resque workers, so the same set up
|
40
|
+
applies. You just configure the job class differently.
|
40
41
|
|
41
|
-
|
42
|
-
|
42
|
+
### Pure Resque
|
43
|
+
|
44
|
+
For any Resque job that you want to run in a Kubernetes job, you'll need
|
45
|
+
to modify the class with two things:
|
46
|
+
|
47
|
+
- `extend` the class with `Resque::Kubernetes::Job`
|
48
|
+
- add a class method `job_manifest` that returns the Kubernetes manifest for the job
|
49
|
+
as a `Hash`
|
43
50
|
|
44
51
|
```ruby
|
45
52
|
class ResourceIntensiveJob
|
46
53
|
extend Resque::Kubernetes::Job
|
47
|
-
|
54
|
+
|
55
|
+
class << self
|
56
|
+
def perform
|
57
|
+
# ... your existing code
|
58
|
+
end
|
59
|
+
|
60
|
+
def job_manifest
|
61
|
+
YAML.safe_load(
|
62
|
+
<<~MANIFEST
|
63
|
+
apiVersion: batch/v1
|
64
|
+
kind: Job
|
65
|
+
metadata:
|
66
|
+
name: worker-job
|
67
|
+
spec:
|
68
|
+
template:
|
69
|
+
metadata:
|
70
|
+
name: worker-job
|
71
|
+
spec:
|
72
|
+
containers:
|
73
|
+
- name: worker
|
74
|
+
image: us.gcr.io/project-id/some-resque-worker
|
75
|
+
env:
|
76
|
+
- name: QUEUE
|
77
|
+
value: high-memory
|
78
|
+
MANIFEST
|
79
|
+
)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
### ActiveJob (on Resque)
|
86
|
+
|
87
|
+
For any ActiveJob that you want to run in a Kubernetes job, you'll need to
|
88
|
+
modify the class with two things:
|
89
|
+
|
90
|
+
- `include` `Resque::Kubernetes::Job` in the class
|
91
|
+
- add an instance method `job_manifest` that returns the Kubernetes manifest for the job
|
92
|
+
as a `Hash`
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
class ResourceIntensiveJob < ApplicationJob
|
96
|
+
include Resque::Kubernetes::Job
|
97
|
+
|
48
98
|
def perform
|
49
99
|
# ... your existing code
|
50
100
|
end
|
51
|
-
|
101
|
+
|
52
102
|
def job_manifest
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
103
|
+
YAML.safe_load(
|
104
|
+
<<~MANIFEST
|
105
|
+
apiVersion: batch/v1
|
106
|
+
kind: Job
|
107
|
+
metadata:
|
108
|
+
name: worker-job
|
109
|
+
spec:
|
110
|
+
template:
|
111
|
+
metadata:
|
112
|
+
name: worker-job
|
113
|
+
spec:
|
114
|
+
containers:
|
115
|
+
- name: worker
|
116
|
+
image: us.gcr.io/project-id/some-resque-worker
|
117
|
+
env:
|
118
|
+
- name: QUEUE
|
119
|
+
value: high-memory
|
120
|
+
MANIFEST
|
121
|
+
)
|
70
122
|
end
|
71
123
|
end
|
72
124
|
```
|
73
125
|
|
126
|
+
### Workers (for both)
|
127
|
+
|
74
128
|
Make sure that the container image above, which is used to run the resque
|
75
129
|
worker, is built to include the `resque-kubernetes` gem as well. The gem will
|
76
130
|
add `TERM_ON_EMPTY` to the environment variables. This tells the worker that
|
@@ -78,6 +132,13 @@ whenever the queue is empty it should terminate the worker. Kubernetes will
|
|
78
132
|
then terminate the Job when the container is done running and will release the
|
79
133
|
resources.
|
80
134
|
|
135
|
+
### Job manifest
|
136
|
+
|
137
|
+
In the example above we show the manifest as a HEREDOC, just to make it
|
138
|
+
simple. But you could also read this from a file, parse a template and insert
|
139
|
+
values, or anything else you want to do in the method, as long as you return
|
140
|
+
a valid Kubernetes Job manifest as a `Hash`.
|
141
|
+
|
81
142
|
## Configuration
|
82
143
|
|
83
144
|
You can modify the configuration of the gem by creating an initializer in
|
@@ -87,28 +148,26 @@ your project:
|
|
87
148
|
# config/initializers/resque-kubernetes.rb
|
88
149
|
|
89
150
|
Resque::Kubernetes.configuration do |config|
|
90
|
-
|
91
151
|
config.environments << "staging"
|
92
152
|
config.max_workers = 10
|
93
|
-
|
94
153
|
end
|
95
154
|
```
|
96
155
|
|
97
156
|
### `environments`
|
98
157
|
|
158
|
+
> This only works under Rails, when `Rails.env` is set.
|
159
|
+
|
99
160
|
By default `Resque::Kubernetes` will only manage Kubernetes Jobs in
|
100
161
|
`:production`. If you want to add other environments you can update this list
|
101
162
|
(`config.environments << "staging"`) or replace it (`config.environments =
|
102
163
|
["production", "development"]`).
|
103
164
|
|
104
|
-
Note that this only works under Rails, when `Rails.env` is set.
|
105
|
-
|
106
165
|
### `max_workers`
|
107
166
|
|
108
167
|
`Resque::Kubernetes` will spin up a Kuberentes Job each time you enqueue a
|
109
168
|
Resque Job. This allows for parallel processing of jobs using the resources
|
110
|
-
available to your cluster. By default this is limited to 10 workers,
|
111
|
-
|
169
|
+
available to your cluster. By default this is limited to 10 workers, to prevent
|
170
|
+
run-away cloud resource usage.
|
112
171
|
|
113
172
|
You can set this higher if you need massive scaling and your structure supports
|
114
173
|
it.
|
@@ -116,7 +175,7 @@ it.
|
|
116
175
|
If you don't want more than one job running at a time then set this to 1.
|
117
176
|
|
118
177
|
Beyond this global scope you can adjust the total number of workers on each
|
119
|
-
individual Resque Job type by overriding the `max_workers`
|
178
|
+
individual Resque Job type by overriding the `max_workers` method for the job.
|
120
179
|
If you change this, the value returned by that method takes precedence over the
|
121
180
|
global value.
|
122
181
|
|
@@ -124,38 +183,43 @@ global value.
|
|
124
183
|
class ResourceIntensiveJob
|
125
184
|
extend Resque::Kubernetes::Job
|
126
185
|
|
127
|
-
|
128
|
-
|
129
|
-
|
186
|
+
class << self
|
187
|
+
def perform
|
188
|
+
# ...
|
189
|
+
end
|
130
190
|
|
131
|
-
|
132
|
-
|
133
|
-
|
191
|
+
def job_manifest
|
192
|
+
# ...
|
193
|
+
end
|
134
194
|
|
135
|
-
|
136
|
-
|
137
|
-
|
195
|
+
def max_workers
|
196
|
+
# Simply return an integer value, or do something more complicated if needed.
|
197
|
+
105
|
198
|
+
end
|
138
199
|
end
|
139
200
|
end
|
140
201
|
```
|
141
202
|
|
142
203
|
## To Do
|
143
204
|
|
144
|
-
- We probably need better namespace support, particularly for reaping
|
145
|
-
finished jobs and pods.
|
146
205
|
- Support for other authentication and server URL options for `kubeclient`.
|
147
206
|
See [the many examples](https://github.com/abonas/kubeclient#usage) in their
|
148
207
|
README.
|
208
|
+
- We probably need better namespace support, particularly for reaping
|
209
|
+
finished jobs and pods.
|
149
210
|
|
150
211
|
## Development
|
151
212
|
|
152
213
|
After checking out the repo, run `bin/setup` to install dependencies. Then,
|
153
|
-
run `rake spec` to run the tests.
|
154
|
-
|
214
|
+
run `rake spec` to run the tests.
|
215
|
+
|
216
|
+
You can run `bin/console` for an interactive prompt that will allow you to experiment.
|
217
|
+
|
218
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
155
219
|
|
156
|
-
To
|
157
|
-
|
158
|
-
|
220
|
+
To release a new version, update the version number in
|
221
|
+
`lib/resque/kubernetes/version.rb` and the `CHANGELOG.md`, then run
|
222
|
+
`bundle exec rake release`, which will create a git tag for the version,
|
159
223
|
push git commits and tags, and push the `.gem` file to
|
160
224
|
[rubygems.org](https://rubygems.org).
|
161
225
|
|
@@ -32,17 +32,38 @@ module Resque
|
|
32
32
|
|
33
33
|
def kubectl_context
|
34
34
|
config = Kubeclient::Config.read(kubeconfig)
|
35
|
+
auth_options = config.context.auth_options
|
36
|
+
|
37
|
+
auth_options = google_default_application_credentials(config) if auth_options.empty?
|
38
|
+
|
35
39
|
Kubeclient::Config::Context.new(
|
36
40
|
config.context.api_endpoint,
|
37
41
|
config.context.api_version,
|
38
42
|
config.context.ssl_options,
|
39
|
-
|
43
|
+
auth_options
|
40
44
|
)
|
41
45
|
end
|
42
46
|
|
43
47
|
def kubeconfig
|
44
48
|
File.join(ENV["HOME"], ".kube", "config")
|
45
49
|
end
|
50
|
+
|
51
|
+
# TODO: Move this logic to kubeclient. See abonas/kubeclient#213
|
52
|
+
def google_default_application_credentials(config)
|
53
|
+
return unless defined?(Google) && defined?(Google::Auth)
|
54
|
+
|
55
|
+
_cluster, user = config.send(:fetch_context, config.instance_variable_get(:@kcfg)["current-context"])
|
56
|
+
return {} unless user["auth-provider"] && user["auth-provider"]["name"] == "gcp"
|
57
|
+
|
58
|
+
{bearer_token: new_google_token}
|
59
|
+
end
|
60
|
+
|
61
|
+
def new_google_token
|
62
|
+
scopes = ["https://www.googleapis.com/auth/cloud-platform"]
|
63
|
+
authorization = Google::Auth.get_application_default(scopes)
|
64
|
+
authorization.apply({})
|
65
|
+
authorization.access_token
|
66
|
+
end
|
46
67
|
end
|
47
68
|
end
|
48
69
|
end
|
@@ -6,39 +6,83 @@ module Resque
|
|
6
6
|
module Kubernetes
|
7
7
|
# Resque hook to autoscale Kubernetes Jobs for workers.
|
8
8
|
#
|
9
|
-
# To use, extend your Resque job class with this module
|
10
|
-
# class method `job_manifest` that produces the
|
9
|
+
# To use with pure Resque, extend your Resque job class with this module
|
10
|
+
# and then define a class method `job_manifest` that produces the
|
11
|
+
# Kubernetes Job manifest.
|
11
12
|
#
|
12
|
-
#
|
13
|
+
# To use with ActiveJob, include this module in your ActiveJob class
|
14
|
+
# and then define an instance method `job_manifest` that produces the
|
15
|
+
# Kubernetes Job manifest.
|
16
|
+
#
|
17
|
+
# Example (pure Resque):
|
13
18
|
#
|
14
19
|
# class ResourceIntensiveJob
|
15
20
|
# extend Resque::Kubernetes::Job
|
21
|
+
# class << self
|
22
|
+
# def perform
|
23
|
+
# # ... your existing code
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# def job_manifest
|
27
|
+
# YAML.safe_load(
|
28
|
+
# <<~MANIFEST
|
29
|
+
# apiVersion: batch/v1
|
30
|
+
# kind: Job
|
31
|
+
# metadata:
|
32
|
+
# name: worker-job
|
33
|
+
# spec:
|
34
|
+
# template:
|
35
|
+
# metadata:
|
36
|
+
# name: worker-job
|
37
|
+
# spec:
|
38
|
+
# containers:
|
39
|
+
# - name: worker
|
40
|
+
# image: us.gcr.io/project-id/some-resque-worker
|
41
|
+
# env:
|
42
|
+
# - name: QUEUE
|
43
|
+
# value: high-memory
|
44
|
+
# MANIFEST
|
45
|
+
# )
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
# end
|
16
49
|
#
|
50
|
+
# Example (ActiveJob backed by Resque):
|
51
|
+
#
|
52
|
+
# class ResourceIntensiveJob < ApplicationJob
|
53
|
+
# include Resque::Kubernetes::Job
|
17
54
|
# def perform
|
18
55
|
# # ... your existing code
|
19
56
|
# end
|
20
57
|
#
|
21
58
|
# def job_manifest
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
59
|
+
# YAML.safe_load(
|
60
|
+
# <<~MANIFEST
|
61
|
+
# apiVersion: batch/v1
|
62
|
+
# kind: Job
|
63
|
+
# metadata:
|
64
|
+
# name: worker-job
|
65
|
+
# spec:
|
66
|
+
# template:
|
67
|
+
# metadata:
|
68
|
+
# name: worker-job
|
69
|
+
# spec:
|
70
|
+
# containers:
|
71
|
+
# - name: worker
|
72
|
+
# image: us.gcr.io/project-id/some-resque-worker
|
73
|
+
# env:
|
74
|
+
# - name: QUEUE
|
75
|
+
# value: high-memory
|
76
|
+
# MANIFEST
|
77
|
+
# )
|
39
78
|
# end
|
40
79
|
# end
|
41
80
|
module Job
|
81
|
+
def self.included(base)
|
82
|
+
return unless base.respond_to?(:before_enqueue)
|
83
|
+
base.before_enqueue :before_enqueue_kubernetes_job
|
84
|
+
end
|
85
|
+
|
42
86
|
# A before_enqueue hook that adds worker jobs to the cluster.
|
43
87
|
def before_enqueue_kubernetes_job(*_)
|
44
88
|
if defined? Rails
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resque-kubernetes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Wadsack
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-04-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -127,7 +127,6 @@ files:
|
|
127
127
|
- ".rubocop.yml"
|
128
128
|
- ".ruby-gemset"
|
129
129
|
- ".ruby-version"
|
130
|
-
- ".travis.yml"
|
131
130
|
- CHANGELOG.md
|
132
131
|
- Gemfile
|
133
132
|
- LICENSE.txt
|