slug: | info |
---|---|
summary: | The minimalistic Run-time Configuration (RC) system and process manager |
- Introduction
- How to get started?
- Command line options
- Run stages
- Wait policies
- Verbose levels
- Integrated functions
- Useful global variables
The minimalistic Run-time Configuration (RC) system and process manager is written in pure BASH and uses just a few external utilities like ls
, ps
, date
and sleep
. Minimally, installation of TrivialRC consists of only one file which can be downloaded directly from the Github. Originaly, it was designed for use in containers but it also can be well used for running a group of processes asynchronously and synchronously, as well as managing their running order and exit codes.
TrivialRC is not a replacement for an init process that usually resides in /sbin/init
and has a PID 1. In containers for this purpose projects like dumb-init or tini can be used, although in most cases, having only TrivialRC as a first/main process (PID 1) in containers is quite enough. In terms of Docker, the best place for it is ENTRYPOINT.
TrivialRC is an equivalent to well known /etc/rc
for xBSD users. The RC system that is used for managing startup and shutdown processes. It can start and stop one or more processes, in parallel or sequentially, on back- or foreground, react differently in case of process failures, etc. All commands can be specified in the command line if they are relatively simple, or in separate files if a more comprehensive scenario is needed. That's why it can be used as a simplest tool for managing a group of process and be a lightweight replacement for solutions like Supervisor.
For instance, in docker images when TrivialRC is used as an Entrypoint, it doesn't reveal itself by default, does not affect any configuration and behaves absolutely transparently. So, you can add it into any Dockerfiles which do not have an entrypoint yet and get by this the full control under processes with fairly detailed logs of what's is going on inside a container. Please, take a look at examples for more information.
Basically, all you need to install TrivialRC is download the latest release of the script from http://trivialrc.vorakl.name/trc
and give it an execute permission. By default, it looks for configuration files in the same directory from which it was invoked but this behavior can be changed by setting a work directory (-w|--workdir
parametr). So, if you are going to use configuration files and keep them in /etc/
, then you would probably want to install the script to /etc/ as well and simply run it without specifying any parametrs.
Another option in this case could be to install the script in a more appropriate path but don't forget to specify --workdir /etc
parametr every time when you invoke this rc system. Both options are possible and depend more on a particular use case.
For instance, in case of using in a docker container, I personaly prefer to have all configuration in separate files in trc.d/
sub-directory and copy it together with the script in /etc/
.
This is an example of how it would look in a Dockerfile with centos:latest as base image:
FROM centos:latest
RUN curl -sSLfo /etc/trc http://trivialrc.vorakl.name/trc && \
( cd /etc && curl -sSLf http://trivialrc.vorakl.name/trc.sha256 | sha256sum -c ) && \
chmod +x /etc/trc && \
/etc/trc --version
# Uncomment this if you have configuration files in trc.d/
# COPY trc.d/ /etc/trc.d/
ENTRYPOINT ["/etc/trc"]
Attention! The Alpine Linux comes with Busybox but its functionality as a shell and as a few emulated tools is not enough
for TrivialRC. To work in this distribution it requires two extra packages: bash
and procps
.
As a result, Dockerfile for the alpine:latest base image would look like:
FROM alpine:latest
RUN apk add --no-cache bash procps
RUN wget -qP /etc/ http://trivialrc.vorakl.name/trc && \
( cd /etc && wget -qO - http://trivialrc.vorakl.name/trc.sha256 | sha256sum -c ) && \
chmod +x /etc/trc && \
/etc/trc --version
# Uncomment this if you have configuration files in trc.d/
# COPY trc.d/ /etc/trc.d/
ENTRYPOINT ["/etc/trc"]
To get started and find out some features, basically, I suggest to go through all available examples and read their readmes plus comments along the code but to start from one-liners which show most common use cases and features.
It is important to notice that the order of command line options is not equal to their run order. In general it looks like:
$ trc [-h|--help] [-v|--version] [-w|--workdir 'dir'] [-B 'cmds' [...]] [-H 'cmds' [...]] [-D 'cmds' [...]] [-F 'cmds' [...]] [command [args]]
Where
-h
or--help
, prints a short help message-v
or--version
, prints a current version-w 'directory'
or--workdir 'directory'
, sets a location with configuration files-B 'command1; command2; ...'
, boot commands-H 'command1; command2; ...'
, halt commands-D 'command1; command2; ...'
, async commands-F 'command1; command2; ...'
, sync commandscommand [args]
, a sync command
So, command line options have to be supplied in the next order
-B
, zero or more-H
, zero or more-D
, zero or more-F
, zero or morecommand with arguments
(without an option), zero or only one
Examples:
$ trc -B 'name=$(id -un); echo booting...' -H 'echo halting...' -F 'echo Hello, ${name}!'
$ RC_WAIT_POLICY=wait_all trc -D 'echo Hello' -D 'sleep 2; echo World' echo waiting...
$ RC_VERBOSE=true trc -F 'echo -n "Hello "; echo World'
$ trc --workdir /opt/app
The life cycle of TrivialRC consists of different stages, with different isolation.
By default, all configuration files (or trc.d/ directory with them) are searched in the directory from which was executed trc
itself. For instance, if you've installed trc in /usr/bin/ and run it by using only its name, like trc
, then configuration will also be searched in /usr/bin/. Though, you can place configuration files anywhere you like and specify their location in the -w|--workdir
option, like trc -w /etc/
.
Let's check:
$ which trc
/usr/bin/trc
$ trc -B 'echo $dir_name'
/usr/bin
$ trc -w /etc -B 'echo $dir_name'
/etc
All stages are executed through in the next order:
boot
Execution order: trc.boot.* -> trc.d/boot.* -> [-B 'cmds' [...]]
Commands run in a same environment as the main process and that's why it has to be used with caution. It's useful for setting up global variables which are seen in all other isolated environments.
async
Execution order: trc.async.* -> trc.d/async.* -> [-D 'cmds' [...]]
Commands run in the separate environment, asynchronously (all run in parallel), in the background and do not affect the main process. If you are going to run more than one async commands, don't forget that default RC_WAIT_POLICY is set to 'wait_any' and the executing process will be stopped after the first finished command and only if there wasn't any running foreground (sync) command that could block the reaction on the TERM signal. So, there are two options:
- to wait until all async commands have finished, you need to set RC_WAIT_POLICY to 'wait_all'.
- to wait for the first finished command, do not change the default value of RC_WAIT_POLICY but run only async commands.
sync
Execution order: trc.sync.* -> trc.d/sync.* -> [-F 'cmds' [...]] -> [cmd]
Commands run in the separate environment, synchronously (one by one), in the foreground and do not affect the main process. if you are going to run more than one sync commands, don't forget to change RC_WAIT_POLICY to 'wait_all' or 'wait_err', otherwise, the executing process will be stopped after the first command.
halt
Execution order: trc.halt.* -> trc.d/halt.* -> [-H 'cmds' [...]]
Commands run in the separate environment, synchronously (one by one) when the main process is finishing (on exit). An exit status from the last halt command has precedence under an exit status from the main process which was supplied as ${_exit_status} variable. So you are able to keep a main exit status (by finishing as exit ${_exit_status}) or rewrite it to something else but anyway, if you have at least one halt command, TrivialRC will finish with an exit status of this halt command.
The rc system reacts differently when one of controlled processes finishes. Depending on the value of RC_WAIT_POLICY environment variable it makes a decision when exactly it should stop itself. The possible values are:
wait_all
- stops after exiting all commands and it doesn't matter whether they are synchronous or asynchronous. Just keep in mind, if you need to catch a signal in the main process, it doesn't have to be blocked by some foreground (sync) process. For example, this mode can be helpful if you need to troubleshoot a container (with wait_any policy) where some async task fails and the whole container gets stopped by this immediately. In this case, you can change a policy to wait_all and run BASH in the foreground like
docker -e RC_WAIT_POLICY=wait_all some-container bash
wait_any
[default]- stops after exiting any of background commands and if there are no foreground commands working at that moment. It makes sense to use this mode if all commands are asynchronous (background). For example, if you need to start more than one process in the docker container, they all have to be asynchronous. Then, the main processed will be able to catch signals (for instance, from a docker daemon) and wait for finishing all other async processes.
wait_err
- stops after the first failed command. It make sense to use this mode with synchronous (foreground) commands only. For example, if you need to iterate sequentially over the list of commands and to stop only if one of them has failed.
wait_forever
- there is a special occasion when a process has doubled forked to become a daemon, it's still running but for the parent shell such process is considered as finished. So, in this mode, TrivialRC will keep working even if all processes have finished and it has to be stopped by the signal from its parent process (such as docker daemon for example).
By default, TrivailRC doesn't print any service messages at all.
It only sends stdout
and stderr
of all isolated sub-shells to the same terminal.
If another behavior is needed, you can redirect any of them inside each sub-shell separately.
To increase the verbosity of rc system there are provided a few environment variables:
RC_DEBUG
(true|false) [false]- Prints out all commands which are being executed
RC_VERBOSE
(true|false) [false]- Prints out service information
RC_VERBOSE_EXTRA
(true|false) [false]- Prints out additional service information
You can also use some of internal functions in async/sync tasks:
say
- prints only if RC_VERBOSE is set
log
- does the same as
say
but add additional info about time, PID, namespace, etc
warn
- does the say as
log
but sends a mesage to stderr
err
- does the same as
warn
but exits with an error (exit status = 1)
debug
- does the same as
log
but only if RC_VERBOSE_EXTRA is set
run
- launches builtin or external commands without checking functions with the same name
For instance, if you wanna run only external command from the standart PATH list, use
run -p 'command'
Or, if you need to check existence of the command, tryrun -v 'command'
MAINPID
, for sending signals to the main process (see Testing of Docker images)_exit_status
, for checking or rewriting an exit status of the whole script (see Process Manager, Service Discovery)