48

Is there any way to configure supervisor to run some command every X seconds(like CRON)?

I see example with eventlistener and TICK_ event

[eventlistener:memmon]
command=memmon -a 200MB -m bob@example.com
events=TICK_60

But it runs the command only once.

barbushin
  • 5,165
  • 5
  • 37
  • 43

5 Answers5

28

Why invent the wheel? You can use cron and supervisord together.

In supervisord, create a task with autostart=false

In cron, use * * * * * supervisorctl start <taskname> to start the task every minute

pumbo
  • 3,646
  • 2
  • 25
  • 27
  • Alex, following you solution, how we can manage task running schedule? – barbushin Aug 25 '17 at 08:02
  • Does that have an advantage over just using cron? – Jimmy T. Jun 22 '21 at 13:12
  • @JimmyT. yes, in case you need some of the `supervisord` functionality (like logging, execution configuration, etc). – pumbo Jun 23 '21 at 07:57
  • It's not reinventing the wheel. If you have a container running an application (under supervisord), you may want periodic functions to be managed by supervisord itself (thus avoiding the need to also run "crond" within the container). – FelipeFR Sep 15 '21 at 14:41
  • Cron is not a good wheel to begin with. Especially in Docker-containers where it has all sorts of problems with trivial matters such as envs, logs and shutdowns. It can't even execute task more often than each minute. I would prefer it gone for good. – Gherman Jul 16 '22 at 17:06
27

Problem

As you are seeing in the memmon example, supervisord is not executing memmon -a 200MB -m bob@example.com at each event. Rather, it is starting this event listener once (or potentially a few times if you configure a pool) and then sends each new event over standard input of an existing process.

Solution

Consequently, you really need to find or write a supervisor compatible event listener for each additional type of capability you want to trigger on events.

Example Method of Implementation

Setup the configuration and write a listener

write a supervisord.cfg event section

[eventlistener:passthru]
command=/tmp/simple.py /bin/date -u +"%%s %%S:%%H:%%d:%%m"
events=TICK_60

(note- the escaping of % for configParser )

write a simple.py Event Listener

Create this simple.py listener by making changes to the example listener from the docs so it executes its first argument with any remaining arguments:

#! /usr/bin/python
import sys
import subprocess

def write_stdout(s):
    sys.stdout.write(s)
    sys.stdout.flush()

def write_stderr(s):
    sys.stderr.write(s)
    sys.stderr.flush()

def main(args):
    while 1:
        write_stdout('READY\n') # transition from ACKNOWLEDGED to READY
        line = sys.stdin.readline()  # read header line from stdin
        write_stderr(line) # print it out to stderr
        headers = dict([ x.split(':') for x in line.split() ])
        data = sys.stdin.read(int(headers['len'])) # read the event payload
        res = subprocess.call(args, stdout=sys.stderr); # don't mess with real stdout
        write_stderr(data)
        write_stdout('RESULT 2\nOK') # transition from READY to ACKNOWLEDGED

if __name__ == '__main__':
    main(sys.argv[1:])
    import sys

Make sure the supervisor configuration works

$ supervisorctl [-c cfg]
supervisor> status
passthru                         RUNNING   pid 4471, uptime 0:00:32
supervisor> tail passthru
  OKREADY
  RESULT 2
  OKREADY
  ...
supervisor> tail passthru stderr
supervisor> tail passthru stderr
  ver:3.0 server:supervisor serial:0 pool:passthru poolserial:0 eventname:TICK_60 len:15
  1451411161 01:17:29:12 <--- output
  when:1451411160ver:3.0 server:supervisor serial:1 pool:passthru poolserial:1 eventname:TICK_60 len:15
  1451411220 00:17:29:12 <--- output
  when:1451411220

Now date -u +"%s %S:%H:%d:%m" is running every 60 seconds.

Swapping in the desired command

create an executable script

/tmp/hiworld.php:

#! /usr/bin/php
<?= "hiya\n"; 

(chmod +x ...)

change the listener's arguments in supervisord.cfg

[eventlistener:passthru]
command=/tmp/simple.py /tmp/hiworld.php
;stdout_logfile=/tmp/passthru 
events=TICK_60
;autorestart=true
;startsecs=0

reload supervisord and test (reread seems not to detect this change)

supervisor> reload
   Really restart the remote supervisord process y/N? y
   Restarted supervisord
supervisor> status
   passthru                         RUNNING   pid 6017, uptime 0:00:10
supervisor> tail passthru stderr
supervisor> status
   passthru                         RUNNING   pid 6017, uptime 0:00:21
supervisor> status
   passthru                         RUNNING   pid 6017, uptime 0:01:01
supervisor> tail passthru stderr
   ver:3.0 server:supervisor serial:316 pool:passthru poolserial:0 eventname:TICK_60 len:15
    hiya
   when:1418926740
supervisor> 

End

Now the desired command is running every 60 seconds. You are now read to adjust particulars of permissions, locations, logs, etc.

mkobit
  • 43,979
  • 12
  • 156
  • 150
lossleader
  • 13,182
  • 2
  • 31
  • 45
  • I tried `command=python /home/supervisor.py "php -f /home/test.php"` and it does not work. I mean `supervisorctl` says that it's RUNNING, but `test.php` is never called. Can you provide some working example? – barbushin Dec 18 '14 at 00:51
  • In my example I would change: command=/tmp/simple.py /bin/date => command=/tmp/simple.py /home/test.php and put #! /.../php at the top of test.php. But removing the quotes and using the full path of php would probably work too. – lossleader Dec 18 '14 at 08:59
  • It works fine with a command and its arguments, where the command is the first argument. As I said in my last comment, your attempt to quote a command and its arguments makes a single argument that is not an executable if you insist on quotes you must unwrap that with a shell. `command= listener mycommand arg1 arg2` or `command=listener /bin/sh -c "mycommand arg1 arg2"` make sense, `command=listener "mycommand arg1 arg2"` does not. – lossleader Dec 20 '14 at 13:46
  • I still can't make it works with command containing arguments. Could you please update your answer with some command including arguments? – barbushin Dec 29 '15 at 11:58
  • @barbushin, I've added some arguments to date that show the only escaping issue I can think of. – lossleader Dec 29 '15 at 17:54
20

Supervisor does not support this easily.

But to achieve your goal you can just use supervisor to start cron (for a docker container for instance):

https://gist.github.com/martinrusev/7015e393d46647dbad15

Install cron in your docker (apt-get install cron with a debian-like docker)

In supervisor config:

[program:cron]
command=cron -f -L 15
autostart=true
autorestart=true

-f is for foreground, -L 15 is to have all cron logs output.

Then use a user crontab, the global /etc/crontab or any crontab special directories (/etc/cron.hourly, /etc/cron.daily, …)

Cyrille Pontvieux
  • 2,356
  • 1
  • 21
  • 29
wid
  • 341
  • 2
  • 5
14

You can call bash sleep command:

[program:mycmd]
command=bash -c 'sleep 300 && exec <your command here>'
autorestart=true

This will run your command every 5 minutes. Do not forget exec part to replace bash process with your command so supervisor will get correct exit code.

mixel
  • 25,177
  • 13
  • 126
  • 165
  • 2
    Probably is the simples tsolution if cronjob but you miss the autorestart option or the while loop to run it again an again! So: Option A) [program:cleanup] command=bash -c 'date >> /var/log/prueba.log && sleep 5' autorestart=true Option B) command=bash -c 'while true; do date >> /var/log/prueba.log; sleep 5; done' autorestart=true – Gonzalo Cao Jul 06 '20 at 18:04
  • If you want to run it at the start and THEN wait, just swap the sleep and exec - `exec && sleep 300` – Tomáš Fejfar Dec 22 '22 at 12:16
2

You can use crobtab to manage and schedule your supervisor programs.

Use command supervisorctl start <program_name>

Note: This will start only single instance of supervisor program. If it's already running and crontab tries to trigger it again, supervisorctl start command will not start a new instance.

Raman
  • 39
  • 4
  • This was already part of an answer nearly a year ago - is there anything additional you'd like to add? – Nico Haase Jun 21 '18 at 08:53
  • 4
    Yes, the part that cron will be able to run only one instance of supervisor program at any given time. Even if cron is triggered while supervisor program is still running, cron will not be able to start a new instance. – Raman Jun 21 '18 at 09:03