Skip to main content

A modern Cron replacement that is Docker-friendly

Project description

Yet Another Cron

A modern Cron replacement that is Docker-friendly

  • Free software: MIT license

https://travis-ci.org/gjcarneiro/yacron.svg?branch=master&maxAge=600 https://coveralls.io/repos/github/gjcarneiro/yacron/badge.svg?branch=master&maxAge=600

Features

  • “Crontab” is in YAML format;

  • Builtin sending of Sentry and Mail outputs when cron jobs fail;

  • Flexible configuration: you decide how to determine if a cron job fails or not;

  • Designed for running in Docker, Kubernetes, or 12 factor environments:

    • Runs in the foreground;

    • Logs everything to stdout/stderr [1];

  • Option to automatically retry failing cron jobs, with exponential backoff.

Status

The project is in beta stage: essential features are complete, and the focus is finding and fixing bugs before the first stable release.

Installation

yacron requires Python >= 3.5. It is advisable to install it in a Python virtual environment, for example:

virtualenv -p python3 yacronenv
. yacronenv/bin/activate
pip install yacron

Usage

Configuration is in YAML format. To start yacron, give it a configuration file or directory path as the -c argument. For example:

yacron -c /tmp/my-crontab.yaml

This starts yacron (always in the foreground!), reading /tmp/my-crontab.yaml as configuration file. If the path is a directory, any *.yaml or *.yml files inside this directory are taken as configuration files.

Configuration basics

This configuration runs a command every 5 minutes:

jobs:
  - name: test-01
    command: echo "foobar"
    shell: /bin/bash
    schedule: "*/5 * * * *"

The command can be a string or a list of strings. If command is a string, yacron runs it through a shell, which is /bin/bash in the above example, but is /bin/sh by default.

If the command is a list of strings, the command is executed directly, without a shell. The ARGV of the command to execute is extracted directly from the configuration:

jobs:
  - name: test-01
    command:
      - echo
      - foobar
    schedule: "*/5 * * * *"

The schedule option can be a string in the traditional crontab format (including @reboot, which will only run the job when yacron is initially executed), or can be an object with properties. The following configuration runs a command every 5 minutes, but only on the specific date 2017-07-19, and doesn’t run it in any other date:

jobs:
  - name: test-01
    command: echo "foobar"
    schedule:
      minute: "*/5"
      dayOfMonth: 19
      month: 7
      year: 2017
      dayOfWeek: "*"

Important: by default all time is interpreted to be in UTC, but you can request to use local time instead. For instance, the cron job below runs every day at 19h27 local time because of the utc: false option:

jobs:
  - name: test-01
    command: echo "hello"
    schedule: "27 19 * * *"
    utc: false
    captureStdout: true

You can ask for environment variables to be defined for command execution:

jobs:
  - name: test-01
    command: echo "foobar"
    shell: /bin/bash
    schedule: "*/5 * * * *"
    environment:
      - key: PATH
        value: /bin:/usr/bin

Specifying defaults

There can be a special defaults section in the config. Any attributes defined in this section provide default values for cron jobs to inherit. Although cron jobs can still override the defaults, as needed:

defaults:
    environment:
      - key: PATH
        value: /bin:/usr/bin
    shell: /bin/bash
    utc: false
jobs:
  - name: test-01
    command: echo "foobar"  # runs with /bin/bash as shell
    schedule: "*/5 * * * *"
  - name: test-02  # runs with /bin/sh as shell
    command: echo "zbr"
    shell: /bin/sh
    schedule: "*/5 * * * *"

Note: if the configuration option is a directory and there are multiple configuration files in that directory, then the defaults section in each configuration file provides default options only for cron jobs inside that same file; the defaults have no effect beyond any individual YAML file.

Reporting

Yacron has builtin support for reporting jobs failure (more on that below) by email and Sentry (additional reporting methods might be added in the future):

- name: test-01
  command: |
    echo "hello" 1>&2
    sleep 1
    exit 10
  schedule:
    minute: "*/2"
  captureStderr: true
  onFailure:
    report:
      sentry:
        dsn:
          value: example
          # Alternatively:
          # fromFile: /etc/secrets/my-secret-dsn
          # fromEnvVar: SENTRY_DSN
        fingerprint:  # optional, since yacron 0.6
          - yacron
          - "{{ environment.HOSTNAME }}"
          - "{{ name }}"
      mail:
        from: example@foo.com
        to: example@bar.com
        smtpHost: 127.0.0.1

Here, the onFailure object indicates that what to do when a job failure is detected. In this case we ask for it to be reported both to sentry and by sending an email.

The captureStderr: true part instructs yacron to capture output from the the program’s standard error, so that it can be included in the report. We could also turn on standard output capturing via the captureStdout: true option. By default, yacron captures only standard error. If a cron job’s standard error or standard output capturing is not enabled, these streams will simply write to the same standard output and standard error as yacron itself.

It is possible also to report job success, as well as failure, via the onSuccess option.

- name: test-01
  command: echo "hello world"
  schedule:
    minute: "*/2"
  captureStdout: true
  onSuccess:
    report:
      mail:
        from: example@foo.com
        to: example@bar.com
        smtpHost: 127.0.0.1

Since yacron 0.5, it is possible to customise the format of the report. For mail reporting, the option subject indicates what is the subject of the email, while body formats the email body. For Sentry reporting, there is only body. In all cases, the values of those options are strings that are processed by the jinja2 templating engine. The following variables are available in templating:

  • name(str): name of the cron job

  • success(bool): whether or not the cron job succeeded

  • stdout(str): standard output of the process

  • stderr(str): standard error of the process

  • exit_code(int): process exit code

  • command(str): cron job command

  • shell(str): cron job shell

  • environment(dict): subprocess environment variables

Example:

- name: test-01
  command: |
    echo "hello" 1>&2
    sleep 1
    exit 10
  schedule:
    minute: "*/2"
  captureStderr: true
  onFailure:
    report:
      mail:
        from: example@foo.com
        to: example@bar.com
        smtpHost: 127.0.0.1
        subject: Cron job '{{name}}' {% if success %}completed{% else %}failed{% endif %}
        body: |
          {{stderr}}
          (exit code: {{exit_code}})

Metrics

Yacron has builtin support for writing job metrics to Statsd:

jobs:
  - name: test01
    command: echo "hello"
    schedule: "* * * * *"
    statsd:
      host: my-statsd.exemple.com
      port: 8125
      prefix: my.cron.jobs.prefix.test01

With this config Yacron will write the following metrics over UDP to the Statsd listening on my-statsd.exemple.com:8125:

my.cron.jobs.prefix.test01.start:1|g  # this one is sent when the job starts
my.cron.jobs.prefix.test01.stop:1|g   # the rest are sent when the job stops
my.cron.jobs.prefix.test01.success:1|g
my.cron.jobs.prefix.test01.duration:3|ms|@0.1

Handling failure

By default, yacron considers that a job has failed if either the process returns a non-zero code or if it generates output to standard error (and standard error capturing is enabled, of course).

You can instruct yacron how to determine if a job has failed or not via the failsWhen option:

failsWhen:
  producesStdout: false
  producesStderr: true
  nonzeroReturn: true
  always: false
producesStdout

If true, any captured standard output causes yacron to consider the job as failed. This is false by default.

producesStderr

If true, any captured standard error causes yacron to consider the job as failed. This is true by default.

nonzeroReturn

If true, if the job process returns a code other than zero causes yacron to consider the job as failed. This is true by default.

always

If true, if the job process exits that causes yacron to consider the job as failed. This is false by default.

It is possible to instruct yacron to retry failing cron jobs by adding a retry option inside onFailure:

- name: test-01
  command: |
    echo "hello" 1>&2
    sleep 1
    exit 10
  schedule:
    minute: "*/10"
  captureStderr: true
  onFailure:
    report:
      mail:
        from: example@foo.com
        to: example@bar.com
        smtpHost: 127.0.0.1
    retry:
      maximumRetries: 10
      initialDelay: 1
      maximumDelay: 30
      backoffMultiplier: 2

The above settings tell yacron to retry the job up to 10 times, with the delay between retries defined by an exponential backoff process: initially 1 second, doubling for every retry up to a maximum of 30 seconds. A value of -1 for maximumRetries will mean yacron will keep retrying forever, this is mostly useful with a schedule of “@reboot” to restart a long running process when it has failed.

If the cron job is expected to fail sometimes, you may wish to report only in the case the cron job ultimately fails after all retries and we give up on it. For that situation, you can use the onPermanentFailure option:

- name: test-01
  command: |
    echo "hello" 1>&2
    sleep 1
    exit 10
  schedule:
    minute: "*/10"
  captureStderr: true
  onFailure:
    retry:
      maximumRetries: 10
      initialDelay: 1
      maximumDelay: 30
      backoffMultiplier: 2
  onPermanentFailure:
    report:
      mail:
        from: example@foo.com
        to: example@bar.com
        smtpHost: 127.0.0.1

Concurrency

Sometimes it may happen that a cron job takes so long to execute that when the moment its next scheduled execution is reached a previous instance may still be running. How yacron handles this situation is controlled by the option concurrencyPolicy, which takes one of the following values:

Allow

allows concurrently running jobs (default)

Forbid

forbids concurrent runs, skipping next run if previous hasn’t finished yet

Replace

cancels currently running job and replaces it with a new one

Execution timeout

(new in version 0.4)

If you have a cron job that may possibly hang sometimes, you can instruct yacron to terminate the process after N seconds if it’s still running by then, via the executionTimeout option. For example, the following cron job takes 2 seconds to complete, yacron will terminate it after 1 second:

- name: test-03
  command: |
    echo "starting..."
    sleep 2
    echo "all done."
  schedule:
    minute: "*"
  captureStderr: true
  executionTimeout: 1  # in seconds

When terminating a job, it is always a good idea to give that job process some time to terminate properly. For example, it may have opened a file, and even if you tell it to shutdown, the process may need a few seconds to flush buffers and avoid losing data.

On the other hand, there are times when programs are buggy and simply get stuck, refusing to terminate nicely no matter what. For this reason, yacron always checks if a process exited some time after being asked to do so. If it hasn’t, it tries to forcefully kill the process. The option killTimeout option indicates how many seconds to wait for the process to gracefully terminate before killing it more forcefully. In Unix systems, we first send a SIGTERM, but if the process doesn’t exit after killTimeout seconds (30 by default) then we send SIGKILL. For example, this cron job ignores SIGTERM, and so yacron will send it a SIGKILL after half a second:

- name: test-03
  command: |
    trap "echo '(ignoring SIGTERM)'" TERM
    echo "starting..."
    sleep 10
    echo "all done."
  schedule:
    minute: "*"
  captureStderr: true
  executionTimeout: 1
  killTimeout: 0.5

History

?.?.? (??????)

  • Added the utc option and document that times are utc by default (#17);

  • If an email body is empty, skip sending it.

0.6.0 (2017-11-24)

  • Add custom Sentry fingerprint support

  • Ability to send job metrics to statsd (thanks bofm)

  • always flag to consider any cron job that exits to be failed (thanks evanjardineskinner)

  • maximumRetries can now be -1 to never stop retrying (evanjardineskinner)

  • schedule can be the string @reboot to always run that cron job on startup (evanjardineskinner)

  • saveLimit can be set to zero (evanjardineskinner)

0.5.0

  • Templating support for reports

  • Remove deprecated smtp_host/smtp_port

0.4.3 (2017-09-13)

  • Bug fixes

0.4.2 (2017-09-07)

  • Bug fixes

0.4.1 (2017-08-03)

  • More polished handling of configuration errors;

  • Unit tests;

  • Bug fixes.

0.4.0 (2017-07-24)

  • New option executionTimeout, to terminate jobs that get stuck;

  • If a job doesn’t terminate gracefully kill it. New option killTimeout controls how much time to wait for graceful termination before killing it;

  • Switch parsing to strictyaml, for more user friendly parsing validation error messages.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

yacron-0.7.0b1.tar.gz (29.3 kB view details)

Uploaded Source

Built Distribution

yacron-0.7.0b1-py3-none-any.whl (23.1 kB view details)

Uploaded Python 3

File details

Details for the file yacron-0.7.0b1.tar.gz.

File metadata

  • Download URL: yacron-0.7.0b1.tar.gz
  • Upload date:
  • Size: 29.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No

File hashes

Hashes for yacron-0.7.0b1.tar.gz
Algorithm Hash digest
SHA256 231f85a027fffccf9f5464ad6fb5d9b1731a68bc0d132380fac6e6a2c417db22
MD5 8bc2b17f3b834e2888e0c05a60728d8e
BLAKE2b-256 50b6def90cd30cb81b14efa3deea62ea8f1857db1edbeec183ecfed89ab4e89a

See more details on using hashes here.

File details

Details for the file yacron-0.7.0b1-py3-none-any.whl.

File metadata

File hashes

Hashes for yacron-0.7.0b1-py3-none-any.whl
Algorithm Hash digest
SHA256 f73018cd6a686ec19a2776f2b2b02c46a579834b7f43254a92d93827a1f8bf2c
MD5 ea06e126583508eff36d26bbd5ad2b4e
BLAKE2b-256 0392ba933bff16399205b081cd161243e09da8e88eccd9282d415d7ede0234b6

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page