背景
使用Airflow官方镜像+miniconda搭建任务调度环境,之前使用的是airflow-python3.8的镜像,用root在miniconda中安装了一个python3.8的环境,任务调度时没有激活环境,直接用虚拟环境中的python路径执行python脚本的。一直那么用着,一年多了都没啥问题。 但是最近把重新建了一个3.9的虚拟环境就炸了。
报错是找不到psutil包,但其实我已经在虚拟环境中安装了,不过是用pip装的。我的依赖分两个途径安装,一个是conda install,一个是pip install。出问题的包是用pip安装的。
猜想
于是合理怀疑:
- 和安装miniconda的用户有关。因为镜像build的时候,虚拟环境是用root安装的,但是Airflow在执行任务时应该用的是airflow用户,用户不一样可能导致找不到包。
- pip install时安装用户不一致导致问题。用root安装pip依赖,但是切换到airflow,执行同一个虚拟环境,会找不到刚才root安装的包。pip好像和conda不一样,不同的用户调用同一pyhton路径也不是完全共享的,理由是pip安装的位置默认是在当前用户目录下,例如root就是
/root/.local/lib/python3.9 ,airflow用户的就是/home/airflow/.local/lib/python3.8/site-packages/ 。 - 没有激活虚拟环境。这个很奇怪,之前那么久一直没有激活,直接调用路径的都没问题。
- 官方镜像自带python版本与虚拟环境python版本不一致导致的问题。这个就更奇怪了,只是猜想。原来镜像自带环境与我自己建的虚拟环境路径虽然不一样,但是都是python3.8,然后pip依赖可能都安装在用户目录下的…/python3.8里面,所以虚拟环境用pip安装的依赖,自带环境也能共用(这里有一个隐含假设是:虽然我用的是虚拟环境,但是在某些情况下,例如joblib多进程,依旧会用到镜像自带的环境)。但是后面换成python3.9,在用户目录下的路径就成…/python3.9,不一样了。理由是我在
/root/.local/lib 中发现了两个文件夹,一个是/root/.local/lib/python3.9 ,一个是/root/.local/lib/python3.8 。 - 环境变量的问题。这个我之前一直都忽略了,是同事提醒的我。
针对猜想测试的结果
1.针对猜想一:我做了两种测试,一是改为使用airflow用户安装依赖,二是依旧使用root安装虚拟环境,但是在dockerfile最后把用户切换为airflow(看到官方文档说要记得切换回airflow用户)。二的结果是没影响,一的结果是更糟糕了,具体的记不太清,但在我所有任务的执行方式都不变的情况下,已经报错,而且错误和之前完全一样,所以这条路我没继续走。后续解决问题之后我想可能这个不太重要,因为在激活环境+设置好环境变量+pip安装好的情况下,用哪个用户安装的miniconda不重要了。 2.针对猜想二:我用root和airlfow用户分别安装了pip依赖,结果是最开始找不到包的报错消失了,变成缺别的包,但这个包是用conda安装的。至少,证明pip这条路是对的。 3.针对猜想三:我也做了两种实验,一是进到docker容器中,为root和airflow都激活了我的虚拟环境。二是在每个task调度的时候都写明了激活虚拟环境。一的结果证明是无影响,镜像内的环境激活和airflow具体的任务调度没关系。二的刚开始没有影响,但是后续结合环境变量的思考,我发现虽然直接激活没用,但是环境变量变多了,大部分都是对的。所以我觉得这条路对,但是不能只走这条路。 4.针对猜想四:这个猜想我觉得很不合理,python发展那么多年了,怎么可能会有这种环境冲突或者重合的情况发生呢,哪怕之前是侥幸能用,但我也不打算继续保持“临时正确”。 5.针对猜想五:我发现在激活虚拟环境后,环境变量中的PYTHONPATH指向的是镜像自带的python,而不是应该激活的那个环境。于是我通过export修改PYTHONPATH,令其等于虚拟python环境,问题就解决了,不再报错。
最终方案
在每次执行task的时候,都执行如下的代码:
source /miniconda/envs/virenv/activate;
export PYTHONPATH=/miniconda/envs/virenv/python;
python test.py
结论
猜想5是表象,猜想2、3、4是内因,猜想1没影响。 我的意思是感觉根本没有彻底解决问题,如果真的彻底解决了,只需要激活虚拟环境就好,为什么每次执行都要修改环境变量呢? 当然还有一种可能是因为,我的Airflow executor选用的是celeryexecutor,相当于每个任务都开了一个“容器”,可能这些子容器的内的环境变量和原本的docker容器有些许不一样,所以导致每个任务都需要给这个子容器设置环境变量。毕竟airflow官方鼓励的是使用镜像自带的环境来执行task,而不是自己建一个虚拟环境。
|