nyx is a lean process monitoring tool written in C.
You can easily configure numerous processes that should be monitored and watched to be in a running and healthy state and restarted if necessary. You should be up and running with almost zero configuration at all as nyx tries to ship with sane default configuration values.
- applications are being watched and kept alive (i.e. restarted) automatically
- simple YAML configuration
- observe processes' CPU/memory utilization
- check for opened ports if configured
- validate configurable HTTP endpoints
- docker compatible
init
replacement - control applications via command interface (unix domain socket)
- minimal dependencies
- small memory footprint
- C plugin architecture (disabled by default)
The project is inspired by god - the great ruby process monitor.
You might think, 'why would you build another process manager when there are numerous working ones already?'
Yes, you are right - there are several ones out there but none of it fits exactly what I am looking for:
-
god: this is the closest one. Sadly you need to have ruby installed on your machine and the configuration quickly gets complicated when you just want to say: 'this is my service - just keep it running!'
-
supervisor: also a nice one! This time you need to have a working python installation in place. Moreover it has become rather huge with a lot of functionality that has to be incorporated in a messy configuration format: like INI
-
systemd (and other init systems): obviously too much! Often a full blown init system is just too much although some functionalities do overlap.
I wanted to have an application I can simply put on a machine that just works without much dependency or configuration hassle to keep a couple of services up and running. If really necessary I can get notified on status changes via some plugin's functionality like XMPP.
On linux a nyx daemon running with root privileges may utilize the kernel userspace connector to retrieve process events. That way there is no need for polling the program's running status so that nyx recognizes a program shutdown/failure immediately without any delay.
So you should consider checking the CONFIG_CONNECTOR
kernel configuration for
this mechanism to work. Otherwise nyx will fallback to a polling approach (use
the polling_interval
setting to modify the interval which defaults to 5
seconds).
Instead of building nyx by yourself you can download ready-to-use binaries for 64-bit Linux:
- 64-bit static binary (built using musl libc): nyx-1.9.8.tar.gz
(SHA1:
0eca5cea573a10713bcf6f2e03ce77c4e9333525
)
- 64-bit static binary (built using musl libc): nyx-1.8.0.tar.gz
(SHA1:
6edb94b02e0a86af69660e37223f1cfc3b3e6dd9
)
- 64-bit static binary (built using musl libc): nyx-1.7.1.tar.gz
(SHA1:
ca7ff793253775629089df3d7219e38598f9ac92
)
-
64-bit Linux RPM: nyx-1.6.1-1.x86_64.rpm (SHA1:
d3667aaf3ff17053b51e5bf6ea672a0eeb289bdc
) -
64-bit static binary (built using musl libc): nyx-1.6.1.tar.gz (SHA1:
1c23bd10b3f5fe71476d527158f498cc0e294f7b
)
On MacOS you can also use homebrew to install nyx
via the custom
tap:
$ brew install kongo2002/nyx/nyx
nyx is built especially with docker usage in mind. This means nyx
is designed to be used as a docker ENTRYPOINT that mimics the
behavior of the usual init
process. That way you won't have issues with
zombie processes and such while keeping your applications monitored.
You could use something like the following in your base docker image:
ADD ./config.yaml /config.yaml
# it is important you use the 'exec' form of ENTRYPOINT
# for the process to be run directly (PID 1) without being
# invoked through /bin/sh -c
ENTRYPOINT ["nyx", "-c", "/config.yaml"]
The main nyx application is the so-called daemon that is started by specifying the configuration file:
$ nyx -c config.yaml
The daemon can be configured via a YAML file. An examplary configuration file might look like this:
# list of applications that will be watched by nyx
watches:
# the name of the application
app_name:
# arbitrary executable
start: /usr/bin/app -f /etc/app/some.config
# user (optional)
uid: user
# group (optional)
gid: user
# working directory (optional)
dir: /home/user
# log (stdout) file (optional)
log_file: /tmp/app.log
# error (stderr) file (optional)
error_file: /tmp/app.err
# environment variables (optional)
env:
SOME: value
FOO: bar
# general nyx settings
nyx:
# log file location of the nyx daemon process
# (optional)
log_file: /var/log/nyx.log
# interval between consecutive application checks (in sec)
# this setting is used only in case the event interface
# using the kernel userspace connector cannot be used
# (optional)
polling_interval: 5
# size of the history of per-application states
# (which can be observed via the 'history' command)
# (optional)
history_size: 20
# you may configure nyx to open an additional port
# that serves an HTTP endpoint similar to the local unix
# domain socket
# (optional)
http_port: 8080
# processes might require some startup time until ports
# or http endpoints are spawned
# the additional process checks will respect this delay (in sec)
# (optional)
startup_delay: 30
The program arguments of the start
and stop
configuration values may be
specified in a YAML list style as well (which is especially useful with
arguments containing whitespace):
watches:
app:
start: [
'/usr/bin/app',
'-f',
'/etc/app/config.file'
]
In order to prevent surprises in terms of e.g. escaping or whitespace it is even recommended to use above YAML list style in favor of the more simple string syntax.
When providing the program and its arguments for start
and stop
, keep in
mind that you have to specify a binary executable or an execuable shell script
file (starting with the interpreter script notation commonly known as
shebang). Please refer to execve(2)
documentation for details.
By default processes are stopped by sending a SIGTERM
until after a timeout of
5 seconds a SIGKILL
will finally terminate the process.
However you may configure a custom stop
command that should handle the
process' termination. Please remember to adjust the stop_timeout
setting
accordingly in case the stop process takes a while to finish because the
process will be spawned asynchronously.
For convenience the magic environment variable $NYX_PID
will be passed to
the spawned process that contains the pid of the process that is requested to be
stopped. This makes writing of custom wrapper scripts that execute some
pre-termination cleanup tasks especially easy.
watches:
app:
start: /bin/app
stop: /bin/app -terminate
stop_timeout: 30
In any case after stop_timeout
seconds are elapsed the SIGKILL
signal will
be fired to finally terminate the process.
Additional to your processes being monitored by its running state you may configure threshold values for CPU and/or memory usage. As soon as your process exceeds the limit for a few consecutive snapshots the process is being restarted.
watches:
app:
start: /bin/app
# maximum CPU usage of 98%
max_cpu: 98
# maximum memory usage of 2G
max_memory: 2G
As of now a snapshot is taken every 30 seconds and the restart action is executed a soon as at least 8 out of 10 snapshots exceed the configured threshold.
Apart from watching the process itself you may instruct nyx to check if a
specified port is opened on the localhost. Use the setting port_check
for that
purpose:
watches:
app:
start: /usr/bin/mongod
# i.e. watch the default mongodb port
port_check: 27017
A process restart will be triggered if the port is not opened. This check will
be active after startup_delay
seconds only (30
by default) in order to
account for application initialization.
In case the observed port is not listening on localhost, you may also specify an
optional hostname or IP in the format hostname:port
or x.x.x.x:port
:
watches:
app:
start: /usr/bin/mongod
port_check: dev.zone:27017
Similarly to the port_check
setting you may advise nyx to test for a
process' health using a configured HTTP endpoint. Use the http_check
setting(s) to configure the endpoint that is expected to return a 200 OK
response:
watches:
app1:
start: /usr/bin/app1
http_check:
url: /status
port: 80
method: GET
# in case of a GET request on port 80 you may
# use the shortened form of http_check as well:
app2:
start: /usr/bin/app2
http_check: /status
This check respects the startup_delay
configuration value as well (see
above).
You may specify an ad-hoc executable to nyx instead of passing a
configuration file with the --run
command argument. This is a shortcut usage
that will initialize exactly one watch using the default configuration options:
$ nyx --run "mongod -f /etc/mongod.conf"
You must not use both ad-hoc executable and configuration file at the same time.
You may also specify a directory with the -c
switch for nyx to read multiple
YAML files in the given folder. You must not rely on the order in which the
files should be read. Meaning in order to prevent surprises do not configure
duplicate config values or watches at all.
This feature may be especially useful in automated/provisioned environments like ansible deployments for example.
# this way nyx will try to load all yaml files in '/etc/nyx.d'
$ nyx -c /etc/nyx.d
Usually nyx is expected to be run in a "one daemon per machine" fashion. In most cases this is the most intuitive user experience since it does not make any assumptions or requirements on the location of the executable or where it is run or started from.
However there may be scenarios where you want to have multiple independent nyx instances to run separately from each other. This is where nyx's "local" mode comes into play: a "local" nyx daemon will spawn from the current working directory and will start all processes based on that directory (if not specified otherwise in the configuration).
$ nyx --local -c config.yaml
You may pass --passive
to the nyx daemon in order to have it not
automatically start all processes on startup. That way you are able to
selectively start your services to your liking or delay and/or start the
processes in a specific order.
You can interact with a running nyx daemon instance using the same executable:
$ nyx version
<<< version
>>> 1.9.8
Right now the following commands are implemented:
ping
: ping a running daemonversion
: return the daemon versionstatus <watch>
: get the status of the specified watchstart <watch>
: send a start command to the specified watchstop <watch>
: send a stop command to the specified watchrestart <watch>
: send a restart command to the specified watchhistory <watch>
: get the latest events of the specified watchconfig <watch>
: print the configuration of the specified watchwatches
: get all currently configured watchesreload
: reload the nyx configurationterminate
: terminate the nyx daemonquit
: stop the nyx daemon and all watched processes
The nyx command-line interface communicates with the daemon process via a UNIX
domain socket which is by default located at /tmp/nyx.sock
(if not using
local-mode). In case you want or have to configure that file to
be located somewhere different you have to pass the --socket
option for both
the daemon start and the command interface.
# start the daemon
$ nyx -c config.yaml --socket /opt/nyx.sock
# use the command interface
$ nyx --socket /opt/nyx.sock ping
<<< ping
>>> pong
Similar to the default unix domain socket command interface you may configure
nyx with a http_port
. That port serves an HTTP endpoint that responds to
GET requests like this:
$ curl localhost:8080/ping
>>> pong
$ curl localhost:8080/version
>>> 1.9.8
$ curl localhost:8080/stop/app
>>> requested stop for watch 'app'
The HTTP interface supports all commands of the usual command interface as well.
On most linux systems building should be as simple as cloning the git repository
and running make
:
$ git clone git://github.com/kongo2002/nyx.git
$ cd nyx
$ make
$ ./nyx --help
System-wide installation can be achieved with a regular:
$ sudo make install
In order to run the test suite afterwards you may run:
$ make check
The debug build can be compiled with:
$ make DEBUG=1
The following libraries are necessary to build and run nyx:
yaml
cmocka
(for unit tests only)
You may build a plugin-enabled nyx version by passing PLUGINS=1
:
$ make PLUGINS=1
Moreover you have to configure the directory where nyx will search for dynamic
plugin libraries. nyx tries to load every .so
plugin file on startup.
# where to look for plugin files
nyx:
plugin_dir: /var/lib/nyx/plugins
# as plugins may require some config values
# the whole 'plugins' key/values are passed
# to every successfully loaded plugin
plugins:
test_key: value
Every C plugin needs to contain at least the plugin_init
function that is
expected to return a non-zero return code:
int
plugin_init(plugin_manager_t *manager)
{
return 1;
}
You can have a look in the /plugins/ subdirectory of this repository to see some example plugins.
The project is written by Gregor Uhlenheuer. You can reach me at [email protected]
nyx is licensed under the Apache license, Version 2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.