diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..c00e5be --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017, Aptible, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5d9f55a --- /dev/null +++ b/README.md @@ -0,0 +1,152 @@ +# Supercronic # + +Supercronic is a crontab-compatible job runner, designed specifically to run in +containers. + + +## Why Supercronic? ## + +Crontabs are the lingua franca of job scheduling, but typical server cron +implementations are ill-suited for container environments: + +- They purge their environment before starting jobs. This is an important + security feature in multi-user systems, but it breaks a fundamental + configuration mechanism for containers. +- They capture the output from the jobs they run, and often either want to + email this output or simply discard it. In a containerized environment, + logging task output and errors to `stdout` / `stderr` is often easier to work + with. +- They often don't respond gracefully to `SIGINT` / `SIGTERM`, and may leave + running jobs orphaned when signaled. Again, this makes sense in a server + environment where `init` will handle the orphan jobs and Cron isn't restarted + often anyway, but it's inappropriate in a container environment as it'll + result in jobs being forcefully terminated (i.e. `SIGKILL`'ed) when the + container exits. +- They often try to send their logs to syslog. This conveniently provides + centralized logging when a syslog server is running, but with containers, + simply logging to `stdout` or `stderr` is preferred. + +Finally, they are often very quiet, which makes the above issues difficult to +understand or debug! + +The list could go on, but the fundamental takeaway is this: unlike typical +server cron implementations, Supercronic tries very hard to do exactly what you +expect from running `cron` in a container: + +- Your environment variables are available in jobs. +- Job output is logged to `stdout` / `stderr`. +- `SIGTERM` (or `SIGINT`, which you can deliver via CTRL+C when used + interactively) triggers a graceful shutdown +- Job return codes and schedules are also logged to `stdout` / `stderr`. + +## How does it work? ## + +- Install Supercronic (see below). +- Point it at a crontab: `supercronic CRONTAB`. +- You're done! + + +### Installation + +- If you have a `go` toolchain available: `go install github.com/aptible/supercronic` +- TODO: Docker installation instructions / packaging. + + +## Crontab format ## + +Broadly speaking, Supercronic tries to process crontabs just like Vixie cron +does. In most cases, it should be compatible with your existing crontab. + +There are, however, a few exceptions: + +- First, Supercronic supports second-resolution schedules: under the hood, + Supercronic uses [the `cronexpr` package][cronexpr], so refer to its + documentation to know exactly what you can do. +- Second, Supercronic does not support changing users when running tasks. + Again, this is something that hardly makes sense in a cron environment. This + means that setting `USER` in your crontab won't have any effect. + +Here's an example crontab: + +``` +# Run every minute +*/1 * * * * echo "hello" + +# Run every 2 seconds +*/2 * * * * * * ls 2>/dev/null +``` + + +## Environment variables ## + +Just like regular cron, Supercronic lets you specify environment variables in +your crontab using a `KEY=VALUE` syntax. + +However, this is only here for compatibility with existing crontabs, and using +this feature is generally **not recommended** when using Supercronic. + +Indeed, Supercronic does not wipe your environment before running jobs, so if +you need environment variables to be available when your jobs run, just set +them before starting Supercronic itself, and your jobs will inherit them +(unless you've used cron before, this is exactly what you expect). + +For example, if you're using Docker, Supercronic + + +## Logging ## + +Supercronic provides rich logging, and will let you know exactly what command +triggered a given message. Here's an example: + +``` +$ cat ./my-crontab +*/5 * * * * * * echo "hello from Supercronic" + +$ ./supercronic ./my-crontab +INFO[2017-07-10T19:40:44+02:00] read crontab: ./my-crontab +INFO[2017-07-10T19:40:50+02:00] starting iteration=0 job.command="echo "hello from Supercronic"" job.position=0 job.schedule="*/5 * * * * * *" +INFO[2017-07-10T19:40:50+02:00] hello from Supercronic channel=stdout iteration=0 job.command="echo "hello from Supercronic"" job.position=0 job.schedule="*/5 * * * * * *" +INFO[2017-07-10T19:40:50+02:00] job succeeded iteration=0 job.command="echo "hello from Supercronic"" job.position=0 job.schedule="*/5 * * * * * *" +INFO[2017-07-10T19:40:55+02:00] starting iteration=1 job.command="echo "hello from Supercronic"" job.position=0 job.schedule="*/5 * * * * * *" +INFO[2017-07-10T19:40:55+02:00] hello from Supercronic channel=stdout iteration=1 job.command="echo "hello from Supercronic"" job.position=0 job.schedule="*/5 * * * * * *" +INFO[2017-07-10T19:40:55+02:00] job succeeded iteration=1 job.command="echo "hello from Supercronic"" job.position=0 job.schedule="*/5 * * * * * *" +``` + + +## Debugging ## + +If your jobs aren't running, or you'd simply like to double-check your crontab +syntax, pass the `-debug` flag for more verbose logging: + +``` +$ ./supercronic -debug ./my-crontab +INFO[2017-07-10T19:43:51+02:00] read crontab: ./my-crontab +DEBU[2017-07-10T19:43:51+02:00] try parse(7): */5 * * * * * * echo "hello from Supercronic"[0:15] = */5 * * * * * * +DEBU[2017-07-10T19:43:51+02:00] job will run next at 2017-07-10 19:44:00 +0200 CEST job.command="echo "hello from Supercronic"" job.position=0 job.schedule="*/5 * * * * * *" +``` + + +## Duplicate Jobs ## + +Supercronic will wait for a given job to finish before that job is scheduled +again (some cron implementations do this, others don't). If a job is falling +behind schedule (i.e. it's taking too long to finish), Supercronic will warn +you. + +Here is an example: + +``` +# Sleep for 2 seconds every second. This will take too long. +* * * * * * * sleep 2 + +$ ./supercronic ./my-crontab +INFO[2017-07-11T12:24:25+02:00] read crontab: foo +INFO[2017-07-11T12:24:27+02:00] starting iteration=0 job.command="sleep 2" job.position=0 job.schedule="* * * * * * *" +INFO[2017-07-11T12:24:29+02:00] job succeeded iteration=0 job.command="sleep 2" job.position=0 job.schedule="* * * * * * *" +WARN[2017-07-11T12:24:29+02:00] job took too long to run: it should have started 1.009438854s ago job.command="sleep 2" job.position=0 job.schedule="* * * * * * *" +INFO[2017-07-11T12:24:30+02:00] starting iteration=1 job.command="sleep 2" job.position=0 job.schedule="* * * * * * *" +INFO[2017-07-11T12:24:32+02:00] job succeeded iteration=1 job.command="sleep 2" job.position=0 job.schedule="* * * * * * *" +WARN[2017-07-11T12:24:32+02:00] job took too long to run: it should have started 1.014474099s ago job.command="sleep 2" job.position=0 job.schedule="* * * * * * *" +``` + + [cronexpr]: https://github.com/gorhill/cronexpr