scrun(1)
β¨ scrun(1) β Slurm Commands β¨
okay bestie, sit down, hydrate, because this is scrun from the Slurm Commands corner of the universe and she is a lot π
πΈ NAME πΈ
scrun β an OCI runtime proxy for Slurm. no cap, that's the whole job description.
π SYNOPSIS π
So there's not one single way to call her, there's a whole little family of operations. Let's go one by one, mwah π
π Create Operation
scrun [GLOBAL OPTIONS...] create [CREATE OPTIONS] <container-id>
Prepares a new container with container-id in current working directory.
π¦ Start Operation
scrun [GLOBAL OPTIONS...] start <container-id>
Request to start and run container in job.
β Query State Operation
scrun [GLOBAL OPTIONS...] state <container-id>
Output OCI defined JSON state of container.
π Kill Operation
scrun [GLOBAL OPTIONS...] kill <container-id> [signal]
Send signal (default: SIGTERM) to container.
π Delete Operation
scrun [GLOBAL OPTIONS...] delete [DELETE OPTIONS] <container-id>
Release any resources held by container locally and remotely.
Perform OCI runtime operations against container-id per: https://github.com/opencontainers/runtime-spec/blob/main/runtime.md
scrun attempts to mimic the commandline behavior as closely as possible to crun and runc in order to maintain in place replacement compatibility with DOCKER and podman. All commandline arguments for crun and runc will be accepted for compatibility but may be ignored depending on their applicability. (iykyk β she's being polite, she's nodding along, she's not always listening π)
π DESCRIPTION π
scrun is an OCI runtime proxy for Slurm. It acts as a common interface to DOCKER or podman to allow container operations to be executed under Slurm as jobs. scrun will accept all commands as an OCI compliant runtime but will proxy the container and all STDIO to Slurm for scheduling and execution. The containers will be executed remotely on Slurm compute nodes according to settings in oci.conf(5). lowkey iconic behavior honestly.
scrun requires all containers to be OCI image compliant per: https://github.com/opencontainers/image-spec/blob/main/spec.md
π RETURN VALUE π
On successful operation, scrun will return 0. For any other condition scrun will return any non-zero number to denote a error. simple as that, bestie.
β¨ GLOBAL OPTIONS β¨
the big-girl flags. they go before the operation name, remember that π«Ά
--cgroup-managerβ Ignored. she's literally just standing there, menacingly π§--debugβ Activate debug level logging.-f<slurm_conf_path> β Use specifiedslurm.conffor configuration. Default:sysconfdirfrom configure during compilation--usageβ Show quick help on how to callscrun--log-format=<json|text>β Optional select format for logging. May be"json"or"text". Default:text--root=<root_path>β Path to spool directory to communication sockets and temporary directories and files. This should be atmpfsand should be cleared on reboot. Default:/run/user/{user_id}/scrun/--rootlessβ Ignored. Allscruncommands are always rootless. π--systemd-cgroupβ Ignored.-vβ Increase logging verbosity. Multiple-v's increase verbosity. stack 'em like bracelets.-V,--versionβ Print version information and exit.
π CREATE OPTIONS π
these only hit different when you're doing the create operation, bestie.
-b <bundle_path>,--bundle=<bundle_path>β Path to the root of the bundle directory. Default: caller's working directory--console-socket=<console_socket_path>β Optional path to anAF_UNIXsocket which will receive a file descriptor referencing the master end of the console's pseudoterminal. Default: ignored--no-pivotβ Ignored.--no-new-keyringβ Ignored.--pid-file=<pid_file_path>β Specify the file to lock and populate with process ID. Default: ignored--preserve-fdsβ Ignored.
π₯ DELETE OPTIONS π₯
--forceβ Ignored. All delete requests are forced and will kill any running jobs. yeah. she's not asking π«’
π INPUT ENVIRONMENT VARIABLES π
env vars she reads on the way in. each one takes one of these levels:
quiet|fatal|error|info|verbose|debug|debug2|debug3|debug4|debug5
SCRUN_DEBUG=<quiet|fatal|error|info|verbose|debug|debug2|debug3|debug4|debug5>β Set logging level.SCRUN_STDERR_DEBUG=<quiet|fatal|error|info|verbose|debug|debug2|debug3|debug4|debug5>β Set logging level for standard error output only.SCRUN_SYSLOG_DEBUG=<quiet|fatal|error|info|verbose|debug|debug2|debug3|debug4|debug5>β Set logging level for syslogging only.SCRUN_FILE_DEBUG=<quiet|fatal|error|info|verbose|debug|debug2|debug3|debug4|debug5>β Set logging level for log file only.
π§ JOB INPUT ENVIRONMENT VARIABLES π§
okay so here's the thing β this list is LONG. but every single one of these is just scrun being like "yeah I read that one, go check srun(1)". so take a breath, it's a vibe, not a quiz πβ¨
SCRUN_ACCOUNTβ SeeSLURM_ACCOUNTfromsrun(1).SCRUN_ACCTG_FREQβ SeeSLURM_ACCTG_FREQfromsrun(1).SCRUN_BURST_BUFFERβ SeeSLURM_BURST_BUFFERfromsrun(1).SCRUN_CLUSTER_CONSTRAINTβ SeeSLURM_CLUSTER_CONSTRAINTfromsrun(1).SCRUN_CLUSTERSβ SeeSLURM_CLUSTERSfromsrun(1).SCRUN_CONSTRAINTβ SeeSLURM_CONSTRAINTfromsrun(1).SLURM_CORE_SPECβ SeeSLURM_ACCOUNTfromsrun(1).SCRUN_CPU_BINDβ SeeSLURM_CPU_BINDfromsrun(1).SCRUN_CPU_FREQ_REQβ SeeSLURM_CPU_FREQ_REQfromsrun(1).SCRUN_CPUS_PER_GPUβ SeeSLURM_CPUS_PER_GPUfromsrun(1).SCRUN_CPUS_PER_TASKβ SeeSRUN_CPUS_PER_TASKfromsrun(1).SCRUN_DELAY_BOOTβ SeeSLURM_DELAY_BOOTfromsrun(1).SCRUN_DEPENDENCYβ SeeSLURM_DEPENDENCYfromsrun(1).SCRUN_DISTRIBUTIONβ SeeSLURM_DISTRIBUTIONfromsrun(1).SCRUN_EPILOGβ SeeSLURM_EPILOGfromsrun(1).SCRUN_EXACTβ SeeSLURM_EXACTfromsrun(1).SCRUN_EXCLUSIVEβ SeeSLURM_EXCLUSIVEfromsrun(1).SCRUN_GPU_BINDβ SeeSLURM_GPU_BINDfromsrun(1).SCRUN_GPU_FREQβ SeeSLURM_GPU_FREQfromsrun(1).SCRUN_GPUSβ SeeSLURM_GPUSfromsrun(1).SCRUN_GPUS_PER_NODEβ SeeSLURM_GPUS_PER_NODEfromsrun(1).SCRUN_GPUS_PER_SOCKETβ SeeSLURM_GPUS_PER_SOCKETfromsalloc(1).SCRUN_GPUS_PER_TASKβ SeeSLURM_GPUS_PER_TASKfromsrun(1).SCRUN_GRES_FLAGSβ SeeSLURM_GRES_FLAGSfromsrun(1).SCRUN_GRESβ SeeSLURM_GRESfromsrun(1).SCRUN_HINTβ SeeSLURM_HISTfromsrun(1).SCRUN_JOB_NAMEβ SeeSLURM_JOB_NAMEfromsrun(1).SCRUN_JOB_NODELISTβ SeeSLURM_JOB_NODELISTfromsrun(1).SCRUN_JOB_NUM_NODESβ SeeSLURM_JOB_NUM_NODESfromsrun(1).SCRUN_LABELIOβ SeeSLURM_LABELIOfromsrun(1).SCRUN_MEM_BINDβ SeeSLURM_MEM_BINDfromsrun(1).SCRUN_MEM_PER_CPUβ SeeSLURM_MEM_PER_CPUfromsrun(1).SCRUN_MEM_PER_GPUβ SeeSLURM_MEM_PER_GPUfromsrun(1).SCRUN_MEM_PER_NODEβ SeeSLURM_MEM_PER_NODEfromsrun(1).SCRUN_MPI_TYPEβ SeeSLURM_MPI_TYPEfromsrun(1).SCRUN_NCORES_PER_SOCKETβ SeeSLURM_NCORES_PER_SOCKETfromsrun(1).SCRUN_NETWORKβ SeeSLURM_NETWORKfromsrun(1).SCRUN_NSOCKETS_PER_NODEβ SeeSLURM_NSOCKETS_PER_NODEfromsrun(1).SCRUN_NTASKSβ SeeSLURM_NTASKSfromsrun(1).SCRUN_NTASKS_PER_COREβ SeeSLURM_NTASKS_PER_COREfromsrun(1).SCRUN_NTASKS_PER_GPUβ SeeSLURM_NTASKS_PER_GPUfromsrun(1).SCRUN_NTASKS_PER_NODEβ SeeSLURM_NTASKS_PER_NODEfromsrun(1).SCRUN_NTASKS_PER_TRESβ SeeSLURM_NTASKS_PER_TRESfromsrun(1).SCRUN_OPEN_MODEβ SeeSLURM_MODEfromsrun(1).SCRUN_OVERCOMMITβ SeeSLURM_OVERCOMMITfromsrun(1).SCRUN_OVERLAPβ SeeSLURM_OVERLAPfromsrun(1).SCRUN_PARTITIONβ SeeSLURM_PARTITIONfromsrun(1).SCRUN_POWERβ SeeSLURM_POWERfromsrun(1).SCRUN_PROFILEβ SeeSLURM_PROFILEfromsrun(1).SCRUN_PROLOGβ SeeSLURM_PROLOGfromsrun(1).SCRUN_QOSβ SeeSLURM_QOSfromsrun(1).SCRUN_REMOTE_CWDβ SeeSLURM_REMOTE_CWDfromsrun(1).SCRUN_REQ_SWITCHβ SeeSLURM_REQ_SWITCHfromsrun(1).SCRUN_RESERVATIONβ SeeSLURM_RESERVATIONfromsrun(1).SCRUN_SIGNALβ SeeSLURM_SIGNALfromsrun(1).SCRUN_SLURMD_DEBUGβ SeeSLURMD_DEBUGfromsrun(1).SCRUN_SPREAD_JOBβ SeeSLURM_SPREAD_JOBfromsrun(1).SCRUN_TASK_EPILOGβ SeeSLURM_TASK_EPILOGfromsrun(1).SCRUN_TASK_PROLOGβ SeeSLURM_TASK_PROLOGfromsrun(1).SCRUN_THREAD_SPECβ SeeSLURM_THREAD_SPECfromsrun(1).SCRUN_THREADS_PER_COREβ SeeSLURM_THREADS_PER_COREfromsrun(1).SCRUN_THREADSβ SeeSLURM_THREADSfromsrun(1).SCRUN_TIMELIMITβ SeeSLURM_TIMELIMITfromsrun(1).SCRUN_TRES_BINDβ Same as--tres-bindSCRUN_TRES_PER_TASKβ SeeSLURM_TRES_PER_TASKfromsrun(1).SCRUN_UNBUFFEREDIOβ SeeSLURM_UNBUFFEREDIOfromsrun(1).SCRUN_USE_MIN_NODESβ SeeSLURM_USE_MIN_NODESfromsrun(1).SCRUN_WAIT4SWITCHβ SeeSLURM_WAIT4SWITCHfromsrun(1).SCRUN_WCKEYβ SeeSLURM_WCKEYfromsrun(1).SCRUN_WORKING_DIRβ SeeSLURM_WORKING_DIRfromsrun(1).
we survived. hydrating rn ππ§
π¦ OUTPUT ENVIRONMENT VARIABLES π¦
these are the ones she sets, so your container gets the tea when it wakes up:
SCRUN_OCI_VERSIONβ Advertised version of OCI compliance of container.SCRUN_CONTAINER_IDβ Value based ascontainer_idduring create operation.SCRUN_PIDβ PID of process used to monitor and control container on allocation node.SCRUN_BUNDLEβ Path to container bundle directory.SCRUN_SUBMISSION_BUNDLEβ Path to container bundle directory before modification by Lua script.SCRUN_ANNOTATION_*β List of annotations from container'sconfig.json.SCRUN_PID_FILEβ Path to pid file that is locked and populated with PID ofscrun.SCRUN_SOCKETβ Path to control socket forscrun.SCRUN_SPOOL_DIRβ Path to workspace for all temporary files for current container. Purged by deletion operation.SCRUN_SUBMISSION_CONFIG_FILEβ Path to container'sconfig.jsonfile at time of submission.SCRUN_USERβ Name of user that called create operation.SCRUN_USER_IDβ Numeric ID of user that called create operation.SCRUN_GROUPβ Name of user's primary group that called create operation.SCRUN_GROUP_IDβ Numeric ID of user primary group that called create operation.SCRUN_ROOTβ See--root.SCRUN_ROOTFS_PATHβ Path to container's root directory.SCRUN_SUBMISSION_ROOTFS_PATHβ Path to container's root directory at submission time.SCRUN_LOG_FILEβ Path toscrun's log file during create operation.SCRUN_LOG_FORMATβ Log format type during create operation.
π«Ά JOB OUTPUT ENVIRONMENT VARIABLES π«Ά
okay so when your job actually runs, these are the little env vars slurm drops into its environment so the job knows what it got. per-component edition for het jobs, bestie π
SLURM_*_HET_GROUP_#β for a heterogeneous job allocation, these environment variables get set separately for each component. every component gets its own lil copy, no sharing πSLURM_CLUSTER_NAMEβ the name of the cluster where the job is actually executing. the one it's living its life on β¨SLURM_CONTAINERβ the OCI Bundle for the job.SLURM_CONTAINER_IDβ the OCI id for the job.SLURM_CPUS_PER_GPUβ how many CPUs you asked for per allocated GPU.SLURM_CPUS_PER_TASKβ how many CPUs you requested per task.SLURM_DIST_PLANESIZEβ the plane distribution size. ngl it's only set for plane distributions, so if you're not doing that, don't come looking for it πSLURM_DISTRIBUTIONβ the distribution type used for the allocated jobs.SLURM_GPU_BINDβ the requested binding of tasks to GPU.SLURM_GPU_FREQβ the requested GPU frequency.SLURM_GPUSβ how many GPUs were requested. the count, the whole count π₯SLURM_GPUS_PER_NODEβ requested GPU count per allocated node.SLURM_GPUS_PER_SOCKETβ requested GPU count per allocated socket.SLURM_GPUS_PER_TASKβ requested GPU count per allocated task.SLURM_HET_SIZEβ set to the count of components in a heterogeneous job.SLURM_JOB_ACCOUNTβ the account name associated with the job allocation.SLURM_JOB_CPUS_PER_NODEβ the count of CPUs available to the job, across the nodes in the allocation, using the formatCPU_count[(xnumber_of_nodes)][,CPU_count [(xnumber_of_nodes)] ...].
for example: SLURM_JOB_CPUS_PER_NODE='72(x2),36' means that on the first and second nodes (as listed by SLURM_JOB_NODELIST) the allocation has 72 CPUs, while the third node has 36 CPUs.
NOTE: theselect/linearplugin allocates entire nodes to jobs, so the value indicates the total count of CPUs on the allocated nodes. theselect/cons_tresplugin allocates individual CPUs to jobs, so this number indicates the number of CPUs allocated to the job. two different vibes, two different meanings, read carefully bestie π
SLURM_JOB_END_TIMEβ the UNIX timestamp for a job's projected end time.SLURM_JOB_GPUSβ the global GPU IDs of the GPUs allocated to this job. these IDs are not relative to any device cgroup, even if devices are constrained withtask/cgroup. it's only set in batch and interactive jobs. β¨SLURM_JOB_IDβ the ID of the job allocation.SLURM_JOB_NODELISTβ the list of nodes allocated to the job.SLURM_JOB_NUM_NODESβ the total number of nodes in the job allocation.SLURM_JOB_PARTITIONβ the name of the partition where the job is running.SLURM_JOB_QOSβ the Quality Of Service (QOS) of the job allocation.SLURM_JOB_RESERVATIONβ the advanced reservation containing the job allocation, if there is one.SLURM_JOB_START_TIMEβ the UNIX timestamp for a job's start time.SLURM_MEM_BINDβ bind tasks to memory.SLURM_MEM_BIND_LISTβ set to the bit mask used for memory binding.SLURM_MEM_BIND_PREFERβ set to"prefer"if theSLURM_MEM_BINDoption includes the prefer option.SLURM_MEM_BIND_TYPEβ set to the memory binding type specified with theSLURM_MEM_BINDoption. possible values are"none","rank","map_mem:","mask_mem:"and"local".SLURM_MEM_BIND_VERBOSEβ set to"verbose"if theSLURM_MEM_BINDoption includes the verbose option. set to"quiet"otherwise.SLURM_MEM_PER_CPUβ the minimum memory required per usable allocated CPU.SLURM_MEM_PER_GPUβ the requested memory per allocated GPU.SLURM_MEM_PER_NODEβ specifies the real memory required per node.SLURM_NTASKSβ specifies the number of tasks to run.SLURM_NTASKS_PER_COREβ requests the maximum ntasks be invoked on each core.SLURM_NTASKS_PER_GPUβ requests that there are ntasks tasks invoked for every GPU.SLURM_NTASKS_PER_NODEβ requests that ntasks be invoked on each node.SLURM_NTASKS_PER_SOCKETβ requests the maximum ntasks be invoked on each socket.SLURM_OVERCOMMITβ overcommit resources.SLURM_PROFILEβ enables detailed data collection by theacct_gather_profileplugin.SLURM_SHARDS_ON_NODEβ the number of GPU Shards available to the step on this node.SLURM_SUBMIT_HOSTβ the hostname of the computer from whichscrunwas invoked. hi bestie, this is you πSLURM_TASKS_PER_NODEβ the number of tasks to be initiated on each node. values are comma separated and in the same order asSLURM_JOB_NODELIST. if two or more consecutive nodes are to have the same task count, that count is followed by"(x#)"where"#"is the repetition count.
for example, SLURM_TASKS_PER_NODE=2(x3),1 means the first three nodes will each execute two tasks and the fourth node will execute one task. slay, compression π
SLURM_THREADS_PER_COREβ this is only set if--threads-per-coreorSCRUN_THREADS_PER_COREwere specified. the value will be set to the value specified by--threads-per-coreorSCRUN_THREADS_PER_CORE. this is used by subsequentsruncalls within the job allocation. β¨SLURM_TRES_PER_TASKβ set to the value of--tres-per-task. if--cpus-per-taskor--gpus-per-taskis specified, it is also set inSLURM_TRES_PER_TASKas if it were specified in--tres-per-task.
π§ SCRUN.LUA π§
so /etc/slurm/scrun.lua must be present on every single node where scrun will be invoked. no file, no vibes. and scrun.lua has to be a compliant lua script β the good kind π
πΈ Required functions πΈ
these ones are non-negotiable, they must be defined:
function slurm_scrun_stage_in(id, bundle, spool_dir, config_file, job_id, user_id, group_id, job_env)
called right after job allocation to stage the container into the job node(s). it must return SLURM.success or the job will be cancelled π it is required that the function will prepare the container for execution on job node(s) as required to run as configured in oci.conf(1). the function may block as long as required until the container has been fully prepared (up to the job's max wall time). so yeah, take your time, but not more than that β³
idβ Container IDbundleβ OCI bundle pathspool_dirβ temporary working directory for containerconfig_fileβ path toconfig.jsonfor containerjob_idβ jobid of job allocationuser_idβ resolved numeric user id of job allocation. it is generally expected that the lua script will be executed inside of a user namespace running under theroot(0)user.group_idβ resolved numeric group id of job allocation. it is generally expected that the lua script will be executed inside of a user namespace running under theroot(0)group.job_envβ table with each entry ofKey=ValueorValueof each environment variable of the job.
function slurm_scrun_stage_out(id, bundle, orig_bundle, root_path, orig_root_path, spool_dir, config_file, jobid, user_id, group_id)
called right after container step completes to stage out files from job nodes. it must return SLURM.success or the job will be cancelled π₯ it is required that the function will pull back any changes and clean up the container on job node(s). the function may block as long as required until the container has been fully prepared (up to the job's max wall time).
idβ Container IDbundleβ OCI bundle pathorig_bundleβ originally submitted OCI bundle path before modification byset_bundle_path().root_pathβ path to directory root of container contents.orig_root_pathβ original path to directory root of container contents before modification byset_root_path().spool_dirβ temporary working directory for containerconfig_fileβ path toconfig.jsonfor containerjob_idβ jobid of job allocationuser_idβ resolved numeric user id of job allocation. it is generally expected that the lua script will be executed inside of a user namespace running under theroot(0)user.group_idβ resolved numeric group id of job allocation. it is generally expected that the lua script will be executed inside of a user namespace running under theroot(0)group.
π¦ Provided functions π¦
these are handed to you already, so any Lua function can just call them as needed. free real estate π
slurm.set_bundle_path(PATH)β call this to notifyscrunto usePATHas the new OCI container bundle path. depending on the filesystem layout, cloning the container bundle may be required to allow execution on job nodes.slurm.set_root_path(PATH)β call this to notifyscrunto usePATHas the new container root filesystem path. depending on the filesystem layout, cloning the container bundle may be required to allow execution on job nodes. the script must also update#/root/pathinconfig.jsonwhen changing root path. don't forget that part ok πSTATUS,OUTPUT = slurm.remote_command(SCRIPT)β runSCRIPTin a new job step on all job nodes. returns numeric job status asSTATUSand job stdio asOUTPUT. blocks untilSCRIPTexits. it will simply wait for you, patiently π«ΆSTATUS,OUTPUT = slurm.allocator_command(SCRIPT)β runSCRIPTas a forked child process ofscrun. returns numeric job status asSTATUSand job stdio asOUTPUT. blocks untilSCRIPTexits.slurm.log(MSG, LEVEL)β logMSGat logLEVEL. valid range of values forLEVELis[0, 4].slurm.error(MSG)β log errorMSG.slurm.log_error(MSG)β log errorMSG.slurm.log_info(MSG)β logMSGat log level INFO.slurm.log_verbose(MSG)β logMSGat log level VERBOSE.slurm.log_verbose(MSG)β logMSGat log level VERBOSE. (yes bestie, it's listed twice in the original. we keep both. no notes π )slurm.log_debug(MSG)β logMSGat log level DEBUG.slurm.log_debug2(MSG)β logMSGat log level DEBUG2.slurm.log_debug3(MSG)β logMSGat log level DEBUG3.slurm.log_debug4(MSG)β logMSGat log level DEBUG4.MINUTES = slurm.time_str2mins(TIME_STRING)β parseTIME_STRINGinto a number of minutes asMINUTES. valid formats:days-[hours[:minutes[:seconds]]]hours:minutes:secondsminutes[:seconds]-1INFINITEUNLIMITED
π§ͺ Example scrun.lua scripts π§ͺ
okay bestie, this is the part where it gets juicy β actual working-ish example scripts. π
π¦ Full Container staging example using rsync π¦
This full example will stage a container as given by docker or podman. No cap, it takes the container's config.json and modifies it to remove unwanted functions that may cause the container run to under crun or runc. Then it uses rsync to move the container to a shared filesystem under the scratch_path variable. it's giving "relocating your whole vibe to a different address" π
NOTE: Support for JSON in liblua must generally be installed before Slurm is compiled.scrun.lua's syntax and ability to load JSON support should be tested by directly calling the script usingluaoutside of Slurm. fr fr, test it outside first or you're gonna have a bad time π
local open = io.open
local scratch_path = "/run/user"
local function read_file(path)
local file = open(path, "rb")
if not file then return nil end
local content = file:read "*all"
file:close()
return content
end
local function write_file(path, contents)
local file = open(path, "wb")
if not file then return nil end
file:write(contents)
file:close()
return
end
function slurm_scrun_stage_in(id, bundle, spool_dir, config_file, job_id, user_id, group_id, job_env)
slurm.log_debug(string.format("stage_in(%s, %s, %s, %s, %d, %d, %d)",
id, bundle, spool_dir, config_file, job_id, user_id, group_id))
local status, output, user, rc
local config = slurm.from_json(read_file(config_file))
local src_rootfs = config["root"]["path"]
rc, user = slurm.allocator_command(string.format("id -un %d", user_id))
user = string.gsub(user, "%s+", "")
local root = scratch_path.."/"..math.floor(user_id).."/slurm/scrun/"
local dst_bundle = root.."/"..id.."/"
local dst_config = root.."/"..id.."/config.json"
local dst_rootfs = root.."/"..id.."/rootfs/"
if string.sub(src_rootfs, 1, 1) ~= "/"
then
-- always use absolute path
src_rootfs = string.format("%s/%s", bundle, src_rootfs)
end
status, output = slurm.allocator_command("mkdir -p "..dst_rootfs)
if (status ~= 0)
then
slurm.log_info(string.format("mkdir(%s) failed %u: %s",
dst_rootfs, status, output))
return slurm.ERROR
end
status, output = slurm.allocator_command(string.format("/usr/bin/env rsync --exclude sys --exclude proc --numeric-ids --delete-after --ignore-errors --stats -a -- %s/ %s/", src_rootfs, dst_rootfs))
if (status ~= 0)
then
-- rsync can fail due to permissions which may not matter
slurm.log_info(string.format("WARNING: rsync failed: %s", output))
end
slurm.set_bundle_path(dst_bundle)
slurm.set_root_path(dst_rootfs)
config["root"]["path"] = dst_rootfs
-- Always force user namespace support in container or runc will reject
local process_user_id = 0
local process_group_id = 0
if ((config["process"] ~= nil) and (config["process"]["user"] ~= nil))
then
-- resolve out user in the container
if (config["process"]["user"]["uid"] ~= nil)
then
process_user_id=config["process"]["user"]["uid"]
else
process_user_id=0
end
-- resolve out group in the container
if (config["process"]["user"]["gid"] ~= nil)
then
process_group_id=config["process"]["user"]["gid"]
else
process_group_id=0
end
-- purge additionalGids as they are not supported in rootless
if (config["process"]["user"]["additionalGids"] ~= nil)
then
config["process"]["user"]["additionalGids"] = nil
end
end
if (config["linux"] ~= nil)
then
-- force user namespace to always be defined for rootless mode
local found = false
if (config["linux"]["namespaces"] == nil)
then
config["linux"]["namespaces"] = {}
else
for _, namespace in ipairs(config["linux"]["namespaces"]) do
if (namespace["type"] == "user")
then
found=true
break
end
end
end
if (found == false)
then
table.insert(config["linux"]["namespaces"], {type= "user"})
end
-- Provide default user map as root if one not provided
if (true or config["linux"]["uidMappings"] == nil)
then
config["linux"]["uidMappings"] =
{{containerID=process_user_id, hostID=math.floor(user_id), size=1}}
end
-- Provide default group map as root if one not provided
-- mappings fail with build???
if (true or config["linux"]["gidMappings"] == nil)
then
config["linux"]["gidMappings"] =
{{containerID=process_group_id, hostID=math.floor(group_id), size=1}}
end
-- disable trying to use a specific cgroup
config["linux"]["cgroupsPath"] = nil
end
if (config["mounts"] ~= nil)
then
-- Find and remove any user/group settings in mounts
for _, mount in ipairs(config["mounts"]) do
local opts = {}
if (mount["options"] ~= nil)
then
for _, opt in ipairs(mount["options"]) do
if ((string.sub(opt, 1, 4) ~= "gid=") and (string.sub(opt, 1, 4) ~= "uid="))
then
table.insert(opts, opt)
end
end
end
if (opts ~= nil and #opts > 0)
then
mount["options"] = opts
else
mount["options"] = nil
end
end
-- Remove all bind mounts by copying files into rootfs
local mounts = {}
for i, mount in ipairs(config["mounts"]) do
if ((mount["type"] ~= nil) and (mount["type"] == "bind") and (string.sub(mount["source"], 1, 4) ~= "/sys") and (string.sub(mount["source"], 1, 5) ~= "/proc"))
then
status, output = slurm.allocator_command(string.format("/usr/bin/env rsync --numeric-ids --ignore-errors --stats -a -- %s %s", mount["source"], dst_rootfs..mount["destination"]))
if (status ~= 0)
then
-- rsync can fail due to permissions which may not matter
slurm.log_info("rsync failed")
end
else
table.insert(mounts, mount)
end
end
config["mounts"] = mounts
end
-- Force version to one compatible with older runc/crun at risk of new features silently failing
config["ociVersion"] = "1.0.0"
-- Merge in Job environment into container -- this is optional!
if (config["process"]["env"] == nil)
then
config["process"]["env"] = {}
end
for _, env in ipairs(job_env) do
table.insert(config["process"]["env"], env)
end
-- Remove all prestart hooks to squash any networking attempts
if ((config["hooks"] ~= nil) and (config["hooks"]["prestart"] ~= nil))
then
config["hooks"]["prestart"] = nil
end
-- Remove all rlimits
if ((config["process"] ~= nil) and (config["process"]["rlimits"] ~= nil))
then
config["process"]["rlimits"] = nil
end
write_file(dst_config, slurm.to_json(config))
slurm.log_info("created: "..dst_config)
return slurm.SUCCESS
end
function slurm_scrun_stage_out(id, bundle, orig_bundle, root_path, orig_root_path, spool_dir, config_file, jobid, user_id, group_id)
if (root_path == nil)
then
root_path = ""
end
slurm.log_debug(string.format("stage_out(%s, %s, %s, %s, %s, %s, %s, %d, %d, %d)",
id, bundle, orig_bundle, root_path, orig_root_path, spool_dir, config_file, jobid, user_id, group_id))
if (bundle == orig_bundle)
then
slurm.log_info(string.format("skipping stage_out as bundle=orig_bundle=%s", bundle))
return slurm.SUCCESS
end
status, output = slurm.allocator_command(string.format("/usr/bin/env rsync --numeric-ids --delete-after --ignore-errors --stats -a -- %s/ %s/", root_path, orig_root_path))
if (status ~= 0)
then
-- rsync can fail due to permissions which may not matter
slurm.log_info("rsync failed")
else
-- cleanup temporary after they have been synced backed to source
slurm.allocator_command(string.format("/usr/bin/rm --preserve-root=all --one-file-system -dr -- %s", bundle))
end
return slurm.SUCCESS
end
slurm.log_info("initialized scrun.lua")
return slurm.SUCCESS
π₯ SIGNALS π₯
the ways you can tell it to wrap it up π«Ά
SIGINTβ attempt to gracefully cancel any related jobs (if any) and cleanup. polite exit energy, love that for us β¨SIGCHLDβ wait for all children, cleanup anchor and gracefully shutdown. it's giving "let everyone finish their sentence before the group chat dies" ππΈ
π COPYING (the legal tea, bestie, don't skip it)
Copyright (C) 2023 SchedMD LLC. β¨
This file is part of Slurm, a resource management program. For details, see https://slurm.schedmd.com/. π
Slurm is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. π
Slurm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. π
ngl the all-caps part is not me being dramatic for once, that's literally how it's written and it literally means it. she's free, she's open, she's got zero promises about working for your specific use case. read the license, bestie, it's giving responsible queen. π
π¦ SEE ALSO (your little reading list, mwah)
slurm(1)β the mothership, the blueprint, the origin story π«Άoci.conf(5)β how scrun talks to your container runtime, iconic config file energysrun(1)β launching jobs, the classic, we stancrunruncDOCKERandpodmanπ
okay so that's literally the whole page bestie, you made it to the end, slay π this is Slurm 26.05 from the Slurm Commands collection, and it's all about scrun(1) β go containerize something cute, run your OCI bundles, and remember: no cap, the manpage was right the whole time. ππΈβ¨
π¦ where this page comes from
- arch rolling: slurm-llnl 26.05.5.1-1 Β· GPL-2.0-or-later
- epel 10: slurm 26.05.5-1.el10_3 Β· GPL-2.0-or-later AND BSD-2-Clause AND BSD-1-Clause
- nixpkgs unstable: slurm 26-05-5-1
- ubuntu stonking: slurm-client 26.05.4-1ubuntu1 Β· GPL-2.0-or-later WITH OpenSSL exception
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms