通过Django的admin轻松控制cron作业。
项目描述
Chroniker - Django受控cron作业
概述
Django Chroniker是一个Python包,允许您通过Django的admin使用cron调度Django管理命令。
为Django应用程序创建cron作业可能很痛苦、令人烦恼且重复。使用django-chroniker,您只需创建一个每分钟运行一次的cron作业,将其指向您的站点目录并运行manage.py cron
。然后,您可以通过Django的admin创建、更新和删除作业。
这是Weston Nielson的Chronograph项目的分支。
功能
与父Chronograph项目相比,此包包含以下改进
-
允许Django管理命令记录它们的百分比进度并在admin中显示它。例如。
from chroniker.models import Job Job.update_progress(total_parts=77, total_parts_complete=13)
-
改进了管理命令的stdout和stderr的记录,并有效地在admin中显示这些信息。
-
创建了一个名为
Monitor
的模型,它是Job
模型的代理,以便更容易地设置系统和数据库状态监控。 -
添加了一个模型来记录作业之间的依赖关系,以及基于这些依赖关系控制作业行为的标志。例如,您可以配置一个作业,直到另一个作业成功运行后才运行,或者在未来某个日期运行。
-
改进了在多服务器环境中协调作业执行的支持。例如,您可以配置一个作业仅在特定主机上或任何主机上运行。
与一些调度系统不同,Chroniker试图确保在任何给定时间最多只有一个作业运行实例。这大大简化了调度,但必然会施加一些限制。如果您需要同时调度多个任务实例,尤其是在实时情况下,请考虑使用类似Celery的系统。
安装
使用pip从PyPI安装包:
pip install django-chroniker
或者直接从GitHub安装(警告:这可能比PyPI上的官方版本不稳定):
pip install https://github.com/chrisspen/django-chroniker/tarball/master
将'chroniker'和'django.contrib.sites'添加到您的settings.py
中的INSTALLED_APPS
列表中,如下所示:
INSTALLED_APPS = (
...
'django.contrib.sites',
'chroniker',
...
)
如果您使用的是Django 1.7或更高版本(您应该这样做),通过运行以下命令安装Chroniker的模型:
python manage.py migrate
否则运行:
python manage.py syncdb
用法
在您的管理界面中,在Chroniker部分创建和配置任务。
如果您处于开发环境,可以通过首先在您的任务上检查“强制运行”,然后运行以下命令来测试基于Chroniker的cron作业:
python manage.py cron
此外,您可以使用以下命令模拟一个简单的cron服务器,该服务器将每N秒自动运行任何挂起的cron作业:
python manage.py cronserver
为了允许Chroniker发送电子邮件,请确保您在settings.py中设置了有效的电子邮件参数。以下是一个使用Gmail的非常基本的示例:
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'myusername@gmail.com'
EMAIL_HOST_PASSWORD = os.environ['GMAILPASS']
您可以使用以下方式自定义Chroniker在其电子邮件中使用的“名称”:
CHRONIKER_EMAIL_SENDER = 'Jon Doe'
您还可以使用以下方式为Chroniker指定单独的电子邮件用户:
CHRONIKER_EMAIL_HOST_USER = 'someotherusername@otherdomain.com'
在生产环境中安装Chroniker时,您需要添加一个调用bin/chroniker
或python manage.py cron
的单个cron作业。在调用中,您需要指定此脚本安装的位置,您的Python虚拟环境的位置(如果您正在使用),以及您的Django项目的位置。以下是一个示例:
* * * * * /usr/local/myproject/bin/chroniker -e /usr/local/myproject/.env/bin/activate_this.py -p /usr/local/myproject
运行bin/chroniker --help
以获取完整选项列表。
设置
根据您的使用情况,有一些选项可能会极大地帮助或妨碍作业调度。
CHRONIKER_USE_PID
- 如果设置为True,则
cron
管理命令将等待上一次运行完成,使用本地PID文件。
CHRONIKER_SELECT_FOR_UPDATE
- 如果设置为True,则更新数据库中的作业状态时,作业记录将锁定(可能不支持所有数据库后端)。
CHRONIKER_CHECK_LOCK_FILE
- 如果设置为True,Chroniker将检查本地锁文件以确定作业是否正在运行。
- 您应该在单服务器环境中将其设置为True,在多服务器环境中设置为False。
CHRONIKER_DISABLE_RAW_COMMAND
- 如果设置为True,Chroniker将不会运行原始命令。这减少了在不信任的访问者可以访问管理界面时攻击面。
维护
如果您想要一种简单的方法来删除旧作业日志,有一个管理命令可以为您完成:cron_clean
。您可以像这样使用它:
python manage.py cron_clean [weeks|days|hours|minutes] [integer]
因此,如果您想删除所有一周以上的作业,可以这样做:
python manage.py cron_clean weeks 1
由于这只是简单的管理命令,您也可以很容易地将其添加到chroniker
中,通过管理界面,这样它将自动清除旧日志。
工具
还有一个包含的管理命令cronserver
,可以用来测试作业的定期运行。它会将有关哪些作业即将到期以及运行它们的信息打印到屏幕上。以下是您可以使用它的方法:
python manage.py cronserver
这将启动一个进程,该进程将每60秒检查并运行任何即将到期的作业。间隔可以通过简单地传递两次运行之间的秒数来更改。例如,要使进程每2分钟检查一次即将到期的作业,您将运行:
python manage.py cronserver 120
架构
在 Chroniker
中,最难正确处理的事情是管理 Job
的状态,即可靠地确定一个作业是否正在运行,或者它是否已经被终止或提前终止。在 Chroniker
的第一个版本中,这个问题通过跟踪每个正在运行的作业的 PID 并使用 ps
命令让操作系统告诉我们作业是否仍在运行来“解决”。然而,这种方法并不理想,原因有几个,但最重要的是它不是跨平台的。此外,使用一系列 subprocess.Popen
调用导致一些用户在“支持的平台”上出现路径相关的问题。
较新版本的 Chroniker
尝试以下方法解决这个问题
1. Get a list of ``Job``\s that are "due"
2. For each ``Job``, launch a ``multiprocessing.Process`` instance, which
internally calls ``django.core.management.call_command``
3. When the ``Job`` is run, we spawn a ``threading.Thread`` instance whose
sole purpose is to keep track of a lock file. This thread exists only
while the Job is running and updates the file every second. We store
the path to this temporary file (an instance of
``tempfile.NamedTemporaryFile``) on the ``Job`` model (which is then
stored in the database). When we want to check if a ``Job`` is running
we do the following:
1. If ``is_running`` equals ``True``, and ``lock_file`` point to a
file, then:
1. If the lock file actually exists and has been updated more
recently than ``CHRONIKER_LOCK_TIMEOUT`` seconds, then we
can assume that the ``Job`` is still running
2. Else we assume the ``Job`` is not running and update the database
accordingly
这种新方法应该会在所有支持线程和多进程库的平台上有更高的可靠性。
开发
要跨多个 Python 版本运行单元测试,请安装
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get update
sudo apt-get install python-dev python3-dev python3.3-minimal python3.3-dev python3.4-minimal python3.4-dev python3.5-minimal python3.5-dev python3.6 python3.6-dev
要运行所有 测试
export TESTNAME=; tox
要为特定环境(例如 Python 3.8 与 Django 3.0)运行测试
export TESTNAME=; tox -e py38-django30
要运行特定的测试
export TESTNAME=.testTimezone2; tox -e py38-django30
要本地运行 文档服务器
mkdocs serve -a :9999
要 部署文档,请运行
mkdocs gh-deploy --clean
要构建并部署带有版本号的包到 PyPI,请验证 所有单元测试都通过,然后运行
python setup.py sdist
python setup.py sdist upload
要提交时跳过 pre-commit 检查钩子
SKIP=yapf git commit -m "foo"