家用Airflow搭建指南
前言
本文会介绍一种使用Docker Compose + Git + S3启动一个完全可用的Airflow的方法和过程中踩的坑。
首先说说为什么需要在家里搭Airflow,正常好人家里是不需要Airflow的,但是我最近遇到了一些需要管理的定时任务。
所谓管理,也就是任务的定时启动、手动启动、历史日志(以及任务的状态)的管理和并发控制。
其中最痛的当属并发控制了。比如:
从IBKR下载数据的时候最多使用32个Client ID
使用yfinance下载数据的时候最多单线程,多线程就容易报429(Too Many Requests)的错误
迁移数据库表结构的时候最多只能有一个实例运行,否则容易乱套
……
我以前是怎么做的呢? 定时任务用crontab,手动启动那就直接执行,每次启动自动用日期创建日志文件,历史日志则写了个脚本定期删除过期日志。 并发控制都是采用文件锁的方式完成这些任务的,写脚本费心费力不说,而且也不好管理。
我很早就知道Airflow,工作中也一直在用,那么为什么我的HomeLab不用Airflow呢?
当然是因为Airflow的配置本身也很难顶,特别是工作中的时候我们用的是K8S,更增加了部署的难度,给我留下了深刻的心理阴影。 这也就是为什么我前面说:“正常好人家里是不需要Airflow的”。
但是随着手动+脚本管理任务的难度逐渐高于配置Airflow的痛苦程度,我最终还是选择了Airflow。
最终成品
首先给出最终成品,你拿着这个脚本就可以自己运行了。
配合.env文件:
踩过的坑
API Base URL
这个不设置好,跳转的时候会有问题:
按照道理我应该放到.env中去设置,但是我太懒了,所以请将就一下。
文件系统权限问题
相信你已经注意到配置中的 user: "${AIRFLOW_UID:-50000}:0" 了,这里的AIRFLOW_UID不是乱设置的,和你现在宿主机系统的当前用户(最好非Root)保持一致。 这样做的话,本机用非Root用户身份直接可以编辑Airflow里面的文件,其次,和git-sync的文件权限也保持一致还可以防止某个Container读写不了另一个Container的文件。
推荐运行echo -e "AIRFLOW_UID=$(id -u)"并且将输出复制到.env文件。
还需要获取DOCKER_GID:getent group docker | cut -d: -f3,你会得到一个数字,把它设置为你的DOCKER_GID去。不这么设置git-sync就会遇到权限不足的问题。
git-sync的SSH Key问题
我们这里的方案是使用Git Repo管理所有的Airflow DAG,git sync的方式是给一个deploy key。无论GitHub还是我用的Gogs都支持这个功能。
生成密钥的方式如下:
随后把Public Key添加到Git中去。
生成known_hosts文件防止连接的时候需要手动确认:
权限设置
然后你就可以用下列命令验证是不是好用:
我在实测的时候遇到一个问题,Deploy Key添加了但是命令不生效,这个时候用SSH Config的方式Clone Repo一次,再测就通了。
而且一定要启用新的shell(有空我确认一下这里到底是怎么回事)。
在Airflow内使用Docker
由于我们使用LocalExecutor执行代码,所以负责连接Docker的是Scheduler Node。
我们只需要在Scheduler Node上挂载如下的Volume即可: /var/run/docker.sock:/var/run/docker.sock
之后我也考虑在内网使用TCP管理多个Docker节点,这样可以部署负载到多个机器上。
使用S3存储日志
你首先要准备一个JSON配置,必须是一行,可以用这个命令压缩:cat your-file.json | jq -c .
随后配置 .env:
最后,通过以下设置启用远程日志。
踩过坑又忘了的FERNET
一定要设置AIRFLOW__CORE__FERNET_KEY,这个Key会用来加密所有的敏感信息,比如Variable或者Connection之类的。
如果不设置,重启之后就无法读取之前设置的Variable或者Connection,而且API也会返回500。
实在忘记了就得把数据库的端口透出来,连上去以后删除connection和variable表里所有的数据才行。
生成一个随机FERNET Key的方式为:
当然你也可以直接执行里面的Python代码。
JWT 设置
JWT虽说不设置也行,但是如果每次都使用Airflow自己随机创建的JWT密钥的话,每次重启后之前的登录就会失效。
设置这两个环境变量即可:
AIRFLOW__API_AUTH__JWT_SECRETAIRFLOW__API_AUTH__JWT_ISSUER
一定要添加DAG Processor
Airflow 3如果不设置DAG Processor的话就不会自动加载DAG的变化。
手动重新加载DAG的代码如下: docker compose exec airflow-scheduler airflow dags reserialize
技术选型和思考
为什么使用Local Executor
所谓LocalExecutor就是在Scheduler Node上直接执行。
其实多个Worker才是Airflow并发的杀器,但是考虑到我的任务并发不大,而且大多数时候是连接Docker去执行代码,本身的负载并不大,所以Local的完全够用,还能节约资源。
为什么选择S3存储日志
可以看到,我这里用的是另一个S3而没有在本地启动RustFS或者Minio。
其原因是我有一个NAS,NAS用来存储文件比我的电脑要牢靠一些(我设置了RAID以避免突发的硬盘损坏)。
NAS当然也可以运行Docker,但是硬件有局限,不适合运行一些太重的任务。所以最终的选择是NAS运行数据库(当然我因为别的原因,数据库没有放在NAS上),Git(我用的是Gogs)和S3服务器这种需要可靠存储数据的服务,而服务器(是一台旧电脑)的任务则尽可能的保持无状态。
为什么是PostgreSQL
简单来说,Airflow需要的数据库并不大(至少我的数据库如此)。理论上MySQL也不是不能胜任,但是两者的启动内存其实差不多,CPU也用不了多少。 在性能更优的情况下,PostgreSQL并没有占用更多的资源,那么我肯定选择PostgreSQL了。
虽然我没有真正地去横向比较过MySQL和PostgreSQL的性能,但是以我的使用经验来看,任何MySQL的服务如果遇到了数据、计算的问题,切换到PostgreSQL可以解决掉90%(暴论)。
其次,PostgreSQL提供真正的数据库,数据库里是带schema的,权限控制也更细致。这么一比MySQL就好像个残废了。当然,Airflow并没有用到PostgreSQL和MySQL有差异的这部分功能(毕竟还是要考虑兼容性)。
至于为什么不是SQLite,其实SQLite本身是非常牢靠的,但是仅限于单进程的场景。而Airflow至少要部署API Server,Scheduler等等服务,通过挂载文件夹共享的方式来共同使用总让人觉得不是很牢靠。如果Airflow能通过配置在一个容器中同时启动API Server,Scheduler等等,我可能会选择SQLite。
为什么不用K8S
这个问题有些可笑,问为什么不在家用K8S部署Airflow仿佛是在问自己为什么吃的苦不够多。
但是正好借着这个机会说一下家庭部署服务的事情,很多人的Home Lab上了K8S(或者K3S吧),然后配合ArgoCD自动部署(完全就是企业级的那一套)。其实大多数人用不到企业级需要的那些功能,反而因为K8S增加了负担。
K8S在设计上就比较适合跑无状态的服务——你有N个无状态的POD,需要规模化部署和管理,自动管理网络,那么K8S非常合适。但是很少有人的Home Lab要扛高负荷的。大部分的需求是相册、音乐、博客、笔记等等,抛开其中有状态的那个数据库不谈,基本没有几个QPS,所以K8S就是杀鸡用牛刀了。
相反,如果真的有高流量访问的需求,比起家庭网络,部署到云上是更好的选择。
当然了,是否上云可以参考这篇文章:为什么我劝你不要使用云计算?
本文的局限性
我虽然以前在K8S部署过Airflow 2,但是时隔多年已经忘了一部分,加上这是我第一次用Docker部署Airflow 3,所以很多配置都不一定是最优的,可能会有历史的痕迹。
其次,我放在airflow-common-env中的那些未必是全局都需要的变量,只是我太懒了,懒得去逐一分辨哪些是公共的,哪些是某些Container特有的。
而且,这个Airflow只有我一个人使用,所以很多配置从简了,比如没有配置OIDC Login,比如从GitHub或者Google登录。
如果你发现文章有任何让你不爽之处,欢迎给我提PR修改这篇文章。
最后更新于