7
7
, lib
8
8
, stateDir
9
9
, profileNix
10
- , clusterImage
11
- , unixHttpServerPort
10
+ , ociImages
11
+ # Needs unix_http_server.file
12
+ , supervisorConf
12
13
} :
13
14
14
15
let
15
16
16
- # Container defaults: See ./oci-images.nix
17
- container_workdir = "/tmp/cluster" ;
18
- container_supervisor_nix = "${ container_workdir } /${ stateDir } /supervisor/nix-store" ;
19
- container_supervisord_url = "unix://${ unixHttpServerPort } " ;
20
- container_supervisord_conf = "${ container_workdir } /${ stateDir } /supervisor/supervisord.conf" ;
17
+ # Container defaults:
18
+ ## Stored on the job's "meta" stanza and intended to be overrided with `jq`.
19
+ ## See ./oci-images.nix for further details.
20
+ #
21
+ # The template stanza can only generate files inside /local (NOMAD_TASK_DIR)
22
+ ## - https://developer.hashicorp.com/nomad/docs/job-specification/template#template-destinations
23
+ ## - https://developer.hashicorp.com/nomad/docs/runtime/environment#task-directories
24
+ ## - https://developer.hashicorp.com/nomad/docs/concepts/filesystem
25
+ container_workdir = "/local" ;
26
+ # Usually "/local/run/current"
27
+ container_statedir = "${ container_workdir } /${ stateDir } " ;
28
+ # A link to the supervisord nix-installed inside the OCI image.
29
+ container_supervisor_nix = "${ container_statedir } /supervisor/nix-store" ;
30
+ # The URL to the listening inet or socket of the supervisord server:
31
+ # The problem is that if we use "127.0.0.1:9001" as parameter (without the
32
+ # "http" part) the container returns:
33
+ # error: <class 'ValueError'>, Unknown protocol for serverurl 127.0.0.1:9001: file: /nix/store/izqhlj5i1x9ldyn43d02kcy4mafmj3ci-python3.9-supervisor-4.2.4/lib/python3.9/site-packages/supervisor/xmlrpc.py line: 508
34
+ # Without using the `--serverurl` parameter at all (using INI config file's
35
+ # [inet_http_server] port stanza) also without "http://":
36
+ # error: <class 'socket.gaierror'>, [Errno -2] Name or service not known: file: /nix/store/hb1lzaisgx2m9n29hqhh6yp6hasplq1v-python3-3.9.10/lib/python3.9/socket.py line: 954
37
+ # If I add the "http" part to the INI file, when starting `supervisord` inside
38
+ # the container I get (from journald):
39
+ # Nov 02 11:44:36 hostname cluster-18f3852f-e067-6394-8159-66a7b8da2ecc[1088457]: Error: Cannot open an HTTP server: socket.error reported -2
40
+ # Nov 02 11:44:36 hostname cluster-18f3852f-e067-6394-8159-66a7b8da2ecc[1088457]: For help, use /nix/store/izqhlj5i1x9ldyn43d02kcy4mafmj3ci-python3.9-supervisor-4.2.4/bin/supervisord -h
41
+ container_supervisord_url = "unix://${ supervisorConf . value . unix_http_server . file } " ;
42
+ # Location of the supervisord config file inside the container.
43
+ # This file can be mounted as a volume or created as a template.
44
+ container_supervisord_conf = "${ container_statedir } /supervisor/supervisord.conf" ;
21
45
container_supervisord_loglevel = "info" ;
22
46
47
+ # About the "template" stanza:
48
+ # The templates documentations show "left_delimiter" and "right_delimiter"
49
+ # options which default to "{{" and "}}" repectively. See:
50
+ # https://developer.hashicorp.com/nomad/docs/job-specification/template#left_delimiter
51
+ # But those don't work to escape text to avoid HCL expressions interpolation.
52
+ # We are using what says here:
53
+ # "In both quoted and heredoc string expressions, Nomad supports template
54
+ # sequences that begin with ${ and %{. These are described in more detail in
55
+ # the following section. To include these sequences literally without
56
+ # beginning a template sequence, double the leading character: $${ or %%{."
57
+ # https://developer.hashicorp.com/nomad/docs/job-specification/hcl2/expressions#string-literals
58
+ escapeTemplate = str : builtins . replaceStrings [ "\ ${" "%{" ] [ "\ $\ ${" "%%{" ] str ;
59
+
23
60
# About the JSON Job Specification and its odd assumptions:
24
61
#
25
62
# At least in Nomad version v1.4.3, the CLI command to submit new jobs
29
66
# HCL format that is heavily specified in the docs. Nice!
30
67
#
31
68
# But note that it starts saying "Nomad HCL is parsed in the command line and
32
- # sent to Nomad in JSON format via the HTTP API." and here are the API docs
33
- # with "JSON Job Specification" in its title:
69
+ # sent to Nomad in JSON format via the HTTP API." and here there are the API
70
+ # docs that have "JSON Job Specification" in its title:
34
71
# https://developer.hashicorp.com/nomad/api-docs/json-jobs
35
72
# well, this is the format that `nomad job run` expects if you use the `-json`
36
- # argument. If you don't provide the `-json` argument it expects HCL or its
37
- # JSON representation: https://github.com/hashicorp/hcl/blob/main/json/spec.md
73
+ # argument.
38
74
#
39
75
# I finally found this in the HCL overview page:
40
76
# https://developer.hashicorp.com/nomad/docs/job-specification/hcl2
44
80
# JSON format is unspecified, so the API format is preferred. You can use the
45
81
# API format with the -json command line flag."
46
82
#
83
+ # So, if you don't provide the `-json` argument it expects HCL or its JSON
84
+ # representation: https://github.com/hashicorp/hcl/blob/main/json/spec.md
85
+ #
47
86
# We are using what HashiCorp calls an unespecified format but it the same
48
87
# format the SRE team is using.
49
88
157
196
# This makes it easier to change them using `jq` inside the workbench!
158
197
meta = {
159
198
# Only top level "KEY=STRING" are allowed!
199
+ WORKING_DIRECTORY = container_workdir ;
200
+ STATE_DIRECTORY = container_statedir ;
160
201
SUPERVISOR_NIX = container_supervisor_nix ;
161
202
SUPERVISORD_URL = container_supervisord_url ;
162
203
SUPERVISORD_CONFIG = container_supervisord_conf ;
189
230
# container, web application, or batch processing.
190
231
# https://developer.hashicorp.com/nomad/docs/job-specification/task
191
232
task = let
192
- valueF = ( name : nodeSpecs : volumes : taskDefaults // {
233
+ valueF = ( name : volumes : ( taskDefaults // {
193
234
194
235
driver = "podman" ;
195
236
@@ -202,36 +243,117 @@ let
202
243
# Specifies environment variables that will be passed to the running
203
244
# process.
204
245
# `null` because we are using a "template" (see below).
205
- env = {
206
- #SUPERVISOR_NIX = container_supervisor_nix;
207
- #SUPERVISORD_URL = container_supervisord_url;
208
- #SUPERVISORD_CONFIG = container_supervisord_conf;
209
- #SUPERVISORD_LOGLEVEL = container_supervisord_loglevel;
210
- } ;
246
+ env = { } ;
211
247
212
248
# Specifies the set of templates to render for the task. Templates can
213
249
# be used to inject both static and dynamic configuration with data
214
250
# populated from environment variables, Consul and Vault.
215
- template = {
216
- # podman container input environment variables.
217
- env = true ;
218
- # File name to create inside the allocation directory.
219
- destination = "envars" ;
220
- # See runtime for available variables:
221
- # https://developer.hashicorp.com/nomad/docs/runtime/environment
222
- data = ''
223
- SUPERVISOR_NIX="{{ env "NOMAD_META_SUPERVISOR_NIX" }}"
224
- SUPERVISORD_URL="{{ env "NOMAD_META_SUPERVISORD_URL" }}"
225
- SUPERVISORD_CONFIG="{{ env "NOMAD_META_SUPERVISORD_CONFIG" }}"
226
- SUPERVISORD_LOGLEVEL="{{ env "NOMAD_META_SUPERVISORD_LOGLEVEL" }}"
227
- '' ;
228
- # Specifies the behavior Nomad should take if the rendered template
229
- # changes. Nomad will always write the new contents of the template
230
- # to the specified destination. The following possible values
231
- # describe Nomad's action after writing the template to disk.
232
- change_mode = "noop" ;
233
- error_on_missing_key = true ;
234
- } ;
251
+ template = [
252
+ # Envars
253
+ {
254
+ # podman container input environment variables.
255
+ env = true ;
256
+ # File name to create inside the allocation directory.
257
+ # Created in NOMAD_DATA_DIR/alloc/ALLOC_ID/TASK_NAME/envars
258
+ destination = "envars" ;
259
+ # See runtime for available variables:
260
+ # https://developer.hashicorp.com/nomad/docs/runtime/environment
261
+ data = ''
262
+ SUPERVISOR_NIX="{{ env "NOMAD_META_SUPERVISOR_NIX" }}"
263
+ SUPERVISORD_URL="{{ env "NOMAD_META_SUPERVISORD_URL" }}"
264
+ SUPERVISORD_CONFIG="{{ env "NOMAD_META_SUPERVISORD_CONFIG" }}"
265
+ SUPERVISORD_LOGLEVEL="{{ env "NOMAD_META_SUPERVISORD_LOGLEVEL" }}"
266
+ '' ;
267
+ # Specifies the behavior Nomad should take if the rendered
268
+ # template changes. Nomad will always write the new contents of
269
+ # the template to the specified destination. The following
270
+ # possible values describe Nomad's action after writing the
271
+ # template to disk.
272
+ change_mode = "noop" ;
273
+ error_on_missing_key = true ;
274
+ }
275
+ # supervisord configuration file.
276
+ {
277
+ env = false ;
278
+ destination = "${ container_supervisord_conf } " ;
279
+ data = escapeTemplate ( __readFile
280
+ supervisorConf . INI ) ;
281
+ change_mode = "noop" ;
282
+ error_on_missing_key = true ;
283
+ }
284
+ # Generator start.sh script.
285
+ {
286
+ env = false ;
287
+ destination = "${ container_statedir } /generator/start.sh" ;
288
+ data = escapeTemplate
289
+ profileNix . generator-service . startupScript . value ;
290
+ change_mode = "noop" ;
291
+ error_on_missing_key = true ;
292
+ }
293
+ # Generator configuration file.
294
+ {
295
+ env = false ;
296
+ destination = "${ container_statedir } /generator/run-script.json" ;
297
+ data = escapeTemplate ( __readFile
298
+ profileNix . generator-service . runScript . JSON . outPath ) ;
299
+ change_mode = "noop" ;
300
+ error_on_missing_key = true ;
301
+ }
302
+ /* TODO: Tracer still needs to use volumes because tracer.socket is shared.
303
+ # Tracer start.sh script.
304
+ {
305
+ env = false;
306
+ destination = "${container_statedir}/tracer/start.sh";
307
+ data = escapeTemplate
308
+ profileNix.tracer-service.startupScript.value;
309
+ change_mode = "noop";
310
+ error_on_missing_key = true;
311
+ }
312
+ # Tracer configuration file.
313
+ {
314
+ env = false;
315
+ destination = "${container_statedir}/tracer/config.json";
316
+ data = escapeTemplate (lib.generators.toJSON {}
317
+ profileNix.tracer-service.config.value);
318
+ change_mode = "noop";
319
+ error_on_missing_key = true;
320
+ }
321
+ */
322
+ ]
323
+ ++
324
+ ( lib . lists . flatten ( lib . mapAttrsToList
325
+ ( _ : nodeSpec : [
326
+ # Node start.sh script.
327
+ {
328
+ env = false ;
329
+ destination = "${ container_statedir } /${ nodeSpec . name } /start.sh" ;
330
+ data = escapeTemplate
331
+ profileNix . node-services . "${ nodeSpec . name } " . startupScript . value ;
332
+ change_mode = "noop" ;
333
+ error_on_missing_key = true ;
334
+ }
335
+ # Node configuration file.
336
+ {
337
+ env = false ;
338
+ destination = "${ container_statedir } /${ nodeSpec . name } /config.json" ;
339
+ data = escapeTemplate ( lib . generators . toJSON { }
340
+ profileNix . node-services . "${ nodeSpec . name } " . nodeConfig . value ) ;
341
+ change_mode = "noop" ;
342
+ error_on_missing_key = true ;
343
+ }
344
+ # Node topology file.
345
+ {
346
+ env = false ;
347
+ destination = "${ container_statedir } /${ nodeSpec . name } /topology.json" ;
348
+ data = escapeTemplate ( lib . generators . toJSON { }
349
+ profileNix . node-services . "${ nodeSpec . name } " . topology . value ) ;
350
+ change_mode = "noop" ;
351
+ error_on_missing_key = true ;
352
+ }
353
+ ] )
354
+ profileNix . node-specs . value
355
+ ) )
356
+ ;
235
357
236
358
# Specifies where a group volume should be mounted.
237
359
volume_mount = null ; #TODO
267
389
# missing), oci-archive and docker-archive. Images reference as
268
390
# short-names will be treated according to user-configured
269
391
# preferences.
270
- image = "${ clusterImage . imageName } :${ clusterImage . imageTag } " ;
392
+ image = "${ ociImages . value . clusterNode . imageName } :${ ociImages . value . clusterNode . imageTag } " ;
271
393
272
394
# Always pull the latest image on container start.
273
395
force_pull = false ;
@@ -314,19 +436,16 @@ let
314
436
# overrides any vault block set at the group or job level.
315
437
vault = null ;
316
438
317
- } ) ;
439
+ } ) ) ;
318
440
in lib . listToAttrs (
319
- [
320
- { name = "generator" ; value = valueF "generator" null [ stateDir ] ; }
321
- ]
322
- ++ lib . optionals profileNix . value . node . tracer [
323
- { name = "tracer" ; value = valueF "tracer" null [ stateDir ] ; }
441
+ lib . optionals profileNix . value . node . tracer [
442
+ { name = "tracer" ; value = valueF "tracer" [ ] ; }
324
443
]
325
444
++
326
445
( lib . mapAttrsToList
327
- ( _ : nodeSpecs : {
328
- name = nodeSpecs . name ;
329
- value = valueF nodeSpecs . name nodeSpecs [ ] ;
446
+ ( _ : nodeSpec : {
447
+ name = nodeSpec . name ;
448
+ value = valueF nodeSpec . name [ ] ;
330
449
} )
331
450
( profileNix . node-specs . value )
332
451
)
522
641
# user = null;
523
642
} ;
524
643
525
- in pkgs . writeText "nomad-job.json"
644
+ in pkgs . writeText "workbench-cluster- nomad-job.json"
526
645
( lib . generators . toJSON { } clusterJob )
0 commit comments