1

在本文中,我将演示如何构建一个弹性Airflow集群,当负载低于阈值时,该集群可以在高负载下安全地横向扩展。

通过水平Pod自动缩放器支持Kubernetes中的自动缩放。使用HPA,可以直接进行横向扩展,HPA可以为部署增加副本,并可以创建其他worker来共享工作负载。但是,伸缩是问题所在,伸缩过程会根据Pod在节点上的位置对它们进行排序,从而选择要终止的Pod。因此,如果有一个Pod仍在进行某些处理,则无法保证它不会被终止。

在弹性Airflow 集群中,为了扩大规模,我们需要确保进行某些处理的worker不会被终止。只有闲置的worker才应考虑终止。

为了实现这一点,我创建了两个CRD和两个控制器 - ElasticWorkerElasticWorkerAutoscaler,在本文后面将对它们进行介绍。

对于此问题,还有其他解决方案,例如,可以创建一个Kubernetes作业,该作业可以完成一组任务。随着负载的增加,将创建更多的作业。但是,这种方法不是通用解决方案,它非常适合具有类似自动缩放要求的其他用例。此处描述的方法是一种通用实现,可以用作全面生产部署的起点。

弹性 Airflow Cluster 架构

el01.png

组件说明如下,

  • Airflow scheduler解析DAG并将必要的任务添加到RabbitMQ队列。
  • PostgresDB保存有关任务,DAG,变量,连接等状态的信息。
  • RabbitMQ 将要执行的命令存储在队列中。
  • Airflow Worker 从RabbitMQ检索命令并执行它们。
  • Flower 是用于监视和管理Celery worker的基于Web的工具。在我们的设置中,flower还包含其他脚本,以获取每个airflow worker的指标并将其放入redis db中。
  • Redis DB 存储每个airflow worker Pod的负载度量以及汇总的集群总负载。它还存储了我们的自定义指标APIServer适配器的所有已注册指标。
  • Custom Metric APIServer适配器 是一个基本的自定义Metric API服务器适配器,它分别为给定的pod和airflow集群资源服务的load和total_cluster_load度量请求。它从Redis数据库检索这些指标。
  • ElasticWorker Controller 监视类型为ElasticWorker(CRD)的对象,并将集群状态与相应的Elasticworker对象中的规范协调。在较高级别,以下是该控制者的职责。

    • 创建等于minReplica的worker Pod
    • 如果变量scale> 0,则创建其他工作容器。但是要确保总的Pod数量不超过maxReplicas
    • scale <0时,删除worker Pod。Pod的删除由定义的“按比例缩放”策略控制。当前有三个策略-ScaleInImmediatelyScaleInBySelectorScaleInDisabled,我们将在此处使用ScaleInBySelector策略。这样可以确保控制器仅删除已定义标签集的Pod。它还可以确保无论是否设置标签,Pod数量都不会低于minReplicas

al02.jpg

  • ElasticWorkerAutoscalerController ElasticWorkerAutoscaler控制器监视类型为ElasticWorkerAutoscaler(CRD)的对象。下面是该控制器的职责,

    • 检索名称为引用ElasticWorker对象的资源的指标total_cluster_load
    • 如果total_cluster_load> targetValue,则向外扩展。计算新的工作单元数,以减少目标值的负担。计算方法与HPA相同。设置引用的ElasticWorker对象的scale属性。
    • 如果total_cluster_load <0.70 * targetValue,则缩容。如果负载低于阈值,则不会立即开始Scale-In,但是scaleInBackOff周期开始计时。默认情况下,它设置为30秒,如果仅在此期间完成,则执行缩容。如果平均时间total_cluster_load增加,则ScaleInBackOff周期无效。周期结束后,控制器将选择那些具有metricload = 0的worker Pod。然后,它使用请求中的这些pod调用shutdownHttpHook。hook是对此实现定制的,但可以推广。接下来,控制器用终止标签标记容器,最后用适当的值更新比例,以使ElasticWorker控制器更改集群状态。
    • load = 0,ShutdownHttpHook和TerminationLabelit确保仅终止那些不做任何事情的airflow worker。 HttpShutdown hook很重要,因为它可以确保标记为终止的airflow worker在ElasticWorker控制器终止它时不会从RabbitMQ接任任何任务。

al01.jpg

安装

ElasticWorker和ElasticWorkerAutoscaler控制器代码位于-elastic-worker-autoscaler

自定义指标APIServer适配器代码位于-elastic-worker-custommetrics-adapter

请遵循此处的安装说明-elastic-airflow-cluster-k8s-setup-manifests

al03.png

此外,我们可以使用以下命令启动Kubernetes仪表板并进行验证,

minikube dashboard

我们在minikube上的设置如下:

两个命名空间

  • elastic-worker-autoscaler-system
  • elasticworker-custommetrics

命名空间:elasticworker-custommetrics包含与自定义指标相关的pod。
命名空间:elastic-worker-autoscaler-system包含用于ElasticWorker和ElasticWorkerAutoscaler的控制器容器
其余组件在默认名称空间中创建。

我们可以使用以下命令检索ElasticWorker对象,
kubectl get elasticworkers

我们可以使用以下命令检索ElasticWorkerAutoscaler对象,
kubectl get elasticworkerautoscalers

测试

安装时,我们已经使用dag_1测试了airflow cluster。如果没有,请转到此处使用此DAG进行测试。我们将在此处使用相同的DAG进行测试。

为了进行测试,我在ElasticWorkerAutoscaler对象中将targetValue设置为60。这意味着,一旦集群总负载超过60,则将开始向外扩展,如果负载低于〜30,则将开始向内扩展。

我们将通过登录调度程序Pod来触发DAG。

我们将从测试横向扩展方案开始。

在我们的安装中,每个airflow worker的并发设置为2,这意味着我们总共有2个(并发)* 2(工作人员数量)= 4个可用插槽。因此,触发4个DAG将使群集负载达到100%。

在此测试案例中,我们将同时触发10个以上的DAG(即,我们需要> 10个插槽)。这将导致airflow worker群集扩展到maxReplica(即5个副本)。即使负载保持在100%,ElasticWorker控制器也将确保工人数不会超过maxReplica。

屏幕截图下方是开始测试之前的airflow worker集群。目前,我们有2个worker,所有插槽都空着。

al04.png

al05.png

让我们通过登录调度程序Pod来触发DAG。如果尚未完成,请记住取消暂停DAG。

#Login to Scheduler POD
kubectl exec -it airflow-scheduler-76d5df7b9b-mjp25 bash
cd dags
#If you have not unpaused dag_1 already
airflow unpause dag_1
export COUNT=0
while [[ $COUNT -lt 12 ]];
do
airflow trigger_dag dag_1
COUNT=`expr $COUNT + 1`
done;

随着负载的增加,我们看到创建了额外的airflow worker来处理负载。

al06.png

al07.png

当我们触发12个DAG时,创建的其他airflow worker应该不止3个,但是由于将maxReplicas设置为5,ElasticWorker控制器不会创建5个以上的airflow worker。

我们的横向扩展方案可行!!

接下来我们测试一下缩容。

基本的扩展方案已通过先前的测试验证。如果我们等待一两分钟,然后检查集群状态,我们可以看到工作线程数已缩减至minReplicas。这是因为负载降至30以下。

al08.png

al09.png

我们还想验证它是否是安全的横向扩展,即触发横向扩展时,控制器将终止负载为0的worker,而不是仍在进行某些工作的worker。

为了测试这种情况,我们将使用dag_2,它的任务将休眠30秒,然后将消息HI记录到文件/home/airflow/logs/count_hi.txt中。我们将触发DAG 12次,每触发4次,我们将等待40+秒,然后再次触发。

我们在两者之间等待以触发缩容。 Scale-In的默认退避时间为30秒,这是为了避免抖动。

为了最终验证所有任务是否运行正常,并且实际进行处理的所有worker均未终止,我们仅在输出文件中计算消息HI。如果它等于我们触发的DAG数量(12),则我们的测试用例将通过。

dag_2如下:

# Lets copy the dag_2.py file into minikube VM  
**minikube ssh  
cd dags/  
cat>dag_2.py  
....PASTE CONTENT FROM SAMPLE DAG....  
ctrl-d  
logout

从调度程序Pod触发DAG。

#Login to Scheduler POD
kubectl exec -it airflow-scheduler-76d5df7b9b-mjp25 bash
cd dags
#If you have not unpaused dag_2 already
airflow unpause dag_2
export COUNT=0
while [[ $COUNT -lt 4 ]];
do
airflow trigger_dag dag_2
COUNT=`expr $COUNT + 1`
done;
sleep 40
export COUNT=0
while [[ $COUNT -lt 4 ]];
do
airflow trigger_dag dag_2
COUNT=`expr $COUNT + 1`
done;
sleep 45
export COUNT=0
while [[ $COUNT -lt 4 ]];
do
airflow trigger_dag dag_2
COUNT=`expr $COUNT + 1`
done;

处理完所有任务后,我们将对消息HI计数。我们将使用调度程序Pod检查输出文件。

al10.png

从上面的屏幕截图可以看出,消息HI被打印12次,与任务数相同。

结论

在本文中,我们看到了如何构建一个弹性airflow集群,该集群可以在负载增加到特定阈值以上时横向扩展,并在负载低于特定阈值时安全地横向扩展。

我们使用了两个新的CRD-ElasticWorker和ElasticWorkerAutoscaler以及它们各自的控制器来实现此目的。
ElasticWorker Controller管理airflow工作程序副本,并确保它在minReplica和maxReplica之间。 ElasticWorkerAutoscaler控制器轮询度量的集群总负载,并计算将集群负载达到指定targetValue所需的副本。然后,它将引用的ElasticWorker对象更新为按比例放大或按比例缩小。


iyacontrol
1.4k 声望2.7k 粉丝

专注kubernetes,devops,aiops,service mesh。