Apache Airflow 1.10+调度程序是否支持在特定时间在不同DST感知的时区中运行2个DAG?

时间:2018-12-14 16:34:15

标签: airflow airflow-scheduler

Apache Airflow 1.10+引入了对支持DST的timezones的本机支持。

这使我认为(也许是错误的)应该可以在相同的Airflow调度程序上创建2个DAG,如下所示:

  • 每天Pacific/Auckland的06:00开始
  • 每天America/New_York的21:00开始

无需引入在需要的开始时间之前“睡眠”的任务。该文档明确排除了cron计划程序以进行DST感知的计划,但仅说明了如何设置DAG在该时区(默认为午夜)每天运行。

关于此主题的先前问题仅考虑使用cron scheduler或基于pre-1.10 airflow的问题,而implementation configuration works并未引入对DST感知时区的本机支持。

在“ airflow.cfg”中,我将default_timezone更新为系统时区。然后,我尝试像这样安排DAG:

DAG('NZ_SOD',
    description='New Zealand Start of Day',
    start_date=datetime(2018, 12, 11, 06, 00, tzinfo=pendulum.timezone('Pacific/Auckland')),
    catchup=False)

并且:

DAG('NAM_EOD',
    description='North Americas End of Day',
    start_date=datetime(2018, 12, 11, 21, 00, tzinfo=pendulum.timezone('America/New_York')),
    catchup=False)

但是似乎在Apache Airflow中没有明确考虑传递给start_date的datetime对象的“ Time”部分,并且会产生意外的行为。

Airflow是否有任何内置选项可以产生所需的行为,或者我正在尝试使用错误的工具来完成工作?

2 个答案:

答案 0 :(得分:3)

答案是肯定的,cron计划支持在DST感知时区中运行DAG。

但是有很多警告,所以我不得不假设Airflow的维护者没有将此作为支持的用例。首先,在撰写本文时,documentation在以下情况下明确错误

  

日程安排

     

如果您设置了cron时间表,Airflow会假设您始终希望完全在同一时间运行。然后它将忽略夏令时。因此,如果您有一个时间表说每天在间隔08:00 GMT + 1时运行,则无论是否设置了夏时制,它总是在间隔08:00 GMT + 1时运行。

我已经编写了这个有点怪异的代码,可以让您了解时间表如何运行,而无需运行Airflow实例(请注意,您已经安装了Penulum 1.x,如果运行则使用正确的documentation或编辑此代码):

import pendulum
from airflow import DAG
from datetime import timedelta


# Set-up DAG
test_dag = DAG(
    dag_id='foo',
    start_date=pendulum.datetime(year=2019, month=4, day=4, tz='Pacific/Auckland'),
    schedule_interval='00 03 * * *',
    catchup=False
)

# Check initial schedule
execution_date = test_dag.start_date
for _ in range(7):
    next_execution_date = test_dag.following_schedule(execution_date)
    if next_execution_date <= execution_date:
        execution_date = test_dag.following_schedule(execution_date + timedelta(hours=2))
    else:
        execution_date = next_execution_date
    print('Execution Date:', execution_date)

这为我们提供了7天的新西兰DST体验期:

Execution Date: 2019-04-03 14:00:00+00:00
Execution Date: 2019-04-04 14:00:00+00:00
Execution Date: 2019-04-05 14:00:00+00:00
Execution Date: 2019-04-06 14:00:00+00:00
Execution Date: 2019-04-07 15:00:00+00:00
Execution Date: 2019-04-08 15:00:00+00:00
Execution Date: 2019-04-09 15:00:00+00:00

正如我们看到的那样,使用cron计划会观察到DST,此外,如果您编辑我的代码以删除cron计划,则会看到观察到DST。

但是要注意,即使cron时间表遵守夏令时,您仍然可能会有1天的错误,并且在夏令时更改的那天,因为Airflow提供的是上一个日期而不是当前日期(例如,日历中的星期日)但在Airflow中,执行日期为星期六)。在我看来,这不是follow_schedule逻辑所解释的。

最后,如@dlamblin指出的那样,如果DAG的本地执行日期与UTC不同,则通过模板字符串或provide_context=True为Python可调用对象提供Airflow提供给作业的变量将是错误的执行日期。可以在使用self.execution_date的{​​{3}}中观察到这一点,而无需将其修改为本地时间。而且我们可以在TaskInstance.get_template_context中看到self.execution_date已转换为UTC。

我处理此问题的方法是通过执行@dlamblin建议并使用Pendulum的local_cal_date方法来派生一个我称为convert的变量。编辑此代码以满足您的特定需求(我实际上在所有Python可调用对象的包装中使用了它,以便它们都收到local_cal_date):

import datetime

def foo(*args, dag, execution_date, **kwargs):
    # Derive local execution datetime from dag and execution_date that
    # airflow passes to python callables where provide_context is set to True
    airflow_timezone = dag.timezone
    local_execution_datetime = airflow_timezone.convert(execution_date)

    # I then add 1 day to make it the calendar day
    # and not the execution date which Airflow provides
    local_cal_datetime = local_execution_datetime + datetime.timedelta(days=1)

更新:对于模板字符串,我发现最好的方法是创建自定义运算符,该自定义运算符在呈现模板之前将自定义变量注入到上下文中。我在使用自定义宏时发现的问题是它们TaskInstance.__init__,这意味着您必须做很多额外的工作才能以有用的方式呈现它们。因此,在自定义运算符模块中,我类似于以下代码:

# Standard Library
import datetime

# Third Party Libraries
import airflow.operators.email_operator
import airflow.operators.python_operator
import airflow.operators.bash_operator


class CustomTemplateVarsMixin:
    def render_template(self, attr, content, context):
        # Do Calculations
        airflow_execution_datetime = context['execution_date']
        airflow_timezone = context['dag'].timezone
        local_execution_datetime = airflow_timezone.convert(airflow_execution_datetime)
        local_cal_datetime = local_execution_datetime + datetime.timedelta(days=1)

        # Add to contexts
        context['local_cal_datetime'] = local_cal_datetime

        # Run normal Method
        return super().render_template(self, attr, content, context)


class BashOperator(CustomTemplateVarsMixin, airflow.operators.bash_operator.BashOperator):
    pass


class EmailOperator(CustomTemplateVarsMixin, airflow.operators.email_operator.EmailOperator):
    pass


class PythonOperator(CustomTemplateVarsMixin, airflow.operators.python_operator.PythonOperator):
    pass


class BranchPythonOperator(CustomTemplateVarsMixin, airflow.operators.python_operator.BranchPythonOperator):
    pass

答案 1 :(得分:2)

前几尼特:

  • 不要将日期时间指定为0开头,例如上午6点,因为如果匆忙将其编辑为上午9点,您将发现该日期不是有效的八进制数字,并且整个DAG文件将停止解析。 / li>
  • 您也可以使用钟摆符号:start_date=pendulum.datetime(2018, 12, 11, 6, 0, tz='Pacific/Auckland')

是的,Airflow中的时区有些混乱。 The docs说,计划时间表总是在该时区的偏移量中。这并不是应该的,因为偏移量是变化的。假设您像这样设置默认配置时区:

[core]
default_timezone = America/New_York

使用start_date,例如:

start_date = datetime(2018, 12, 11, 6, 0),

您获得的offset的UTC为-18000或-5h。

start_date = datetime(2018, 4, 11, 6, 0),

您获得的offset的UTC为-14400或-4h。

第二个要点的偏移量为46800或13h,而奥克兰的四月份为43200或12h。如果我没记错的话,这些将应用于DAG的schedule_interval

文档似乎在说您的schedule_interval crontab字符串将以相同的偏移量进行永久解释。因此,如果0 5 * * *将在12月份在纽约开始运行,则将在凌晨5或6时运行;如果在4月在纽约开始,则将运行在5或4凌晨。嗯我认为是对的。我对此也感到困惑。

通过将默认设置保留为utc不能避免这种情况。不,如果您使用start_date来显示和选择具有utc偏移的区域,则不会。

现在……第二期,一天中的时间。开始日期曾经是最早的有效开始时间间隔。一天中的某个时间很棒,但是时间表默认为timedelta(days=1)。我认为它是@daily,也意味着0 0 * * *,它为您带来有趣的结果,例如从12月11日上午6点开始,这是您第一个完整的午夜至午夜时间间隔将在12月13日午夜关闭,因此,第一轮运行会在12月12日午夜日期execution_date之前通过。但是我希望将timedelta应用于start_date,它将改为于12月12日上午6点开始,而昨天的时间与execution_date相同。但是,我还没有看到它能以这种方式解决问题,这确实使我认为它可能仅在某处date中使用了datetime的{​​{1}}部分。

如记录所示,传入start_date(以及所有宏日期)的时间将采用UTC(因此,将exeucution_date时区偏移量中的午夜或凌晨6点转换为UTC)。至少它们附有tz,因此您可以根据需要在它们上使用start_date