모듈 관리
모듈 관리 (Modules Management)
이 페이지는 Airflow에서 나만의 Python 모듈을 DAG와 Airflow 설정에 사용하는 방법을 설명해요. Python의 sys.path·PYTHONPATH 작동 원리, 패키지의 일반적인 구조, Airflow가 자동으로 추가하는 PYTHONPATH 항목, 코드 이름 짓기의 모범 사례, PYTHONPATH에 디렉터리 추가하는 방법, 그리고 Python 패키지 만들기까지 다뤄요.
출처: 문서
본문
Airflow는 DAG와 Airflow 설정에서 나만의 Python 모듈을 사용할 수 있게 해줘요. 다음 글은 Airflow가 올바르게 로드할 수 있도록 나만의 모듈을 만드는 방법과, 모듈이 제대로 로드되지 않을 때 문제를 진단하는 방법을 설명해요.
종종 Airflow 배포에서 나만의 Python 코드를 사용하고 싶을 때가 있어요. 예를 들어 공통 코드, 라이브러리, 공유 Python 코드로 DAG를 생성하고 여러 DAG Python 파일을 가지려 할 수 있죠.
다음 중 한 가지 방법으로 할 수 있어요:
- Airflow가 자동으로
PYTHONPATH에 추가하는 폴더 중 하나에 나의 모듈을 추가하기 - 나의 코드를 보관하는 추가 폴더를
PYTHONPATH에 추가하기 - 나의 코드를 Python 패키지로 패키징해 Airflow와 함께 설치하기
다음 장에서는 Python이 패키지와 모듈을 로드하는 방법을 일반적으로 설명하고, 위 세 가지 방법 각각의 구체적인 내용을 더 깊이 다뤄요.
Python에서 패키지/모듈 로딩이 작동하는 원리
Python이 모듈을 로드하려 시도하는 디렉터리 목록은 sys.path 변수로 주어져요. Python은 운영 체제, Python 설치 방식, 사용하는 Python 버전에 따라 이 변수의 내용을 지능적으로 결정하려 하죠.
현재 Python 환경에 대한 이 변수의 내용은 아래 예제처럼 인터랙티브 터미널을 실행해 확인할 수 있어요:
>>> import sys
>>> from pprint import pprint
>>> pprint(sys.path)
['',
'/home/arch/.pyenv/versions/3.9.4/lib/python37.zip',
'/home/arch/.pyenv/versions/3.9.4/lib/python3.9',
'/home/arch/.pyenv/versions/3.9.4/lib/python3.9/lib-dynload',
'/home/arch/venvs/airflow/lib/python3.9/site-packages']
sys.path는 프로그램 시작 시 초기화돼요. 첫 번째 우선순위는 현재 디렉터리, 즉 path[0]이 현재 스크립트(호출에 사용된)를 포함하는 디렉터리이거나, 인터랙티브 셸일 경우 빈 문자열이에요. 두 번째 우선순위는 제공된 경우 PYTHONPATH이고, 그다음 site 모듈이 관리하는 설치 의존적 기본 경로가 이어져요.
sys.path는 Python 세션 중에 append를 사용해(예: sys.path.append("/path/to/custom/package")) 수정할 수도 있어요. 추가된 후에는 Python이 최신 경로에서 패키지를 검색하기 시작해요. Airflow는 디렉터리를 PYTHONPATH에 추가하기 섹션에서 설명하는 대로 이 기능을 활용해요.
sys.path 변수에는 설치된 외부 패키지를 포함하는 site-packages 디렉터리가 있는데, 이는 pip이나 anaconda로 패키지를 설치하면 Airflow에서 사용할 수 있다는 뜻이에요. 다음 섹션에서는 나만의 간단한 설치 가능한 패키지를 만드는 방법과, PYTHONPATH 환경 변수를 사용해 sys.path에 추가할 추가 디렉터리를 지정하는 방법을 배울 수 있어요.
또한 폴더에 init 파일을 추가하는 것도 잊지 마세요.
패키지의 일반적인 구조
dags 폴더에 있을 수 있는 구조의 예는 다음과 같아요:
<DIRECTORY ON PYTHONPATH>
| .airflowignore -- only needed in ``dags`` folder, see below
| -- my_company
| __init__.py
| common_package
| | __init__.py
| | common_module.py
| | subpackage
| | __init__.py
| | subpackaged_util_module.py
|
| my_custom_dags
| __init__.py
| my_dag1.py
| my_dag2.py
| base_dag.py
위 경우 python 파일을 import하는 방법은 다음과 같아요:
from my_company.common_package.common_module import SomeClass
from my_company.common_package.subpackage.subpackaged_util_module import AnotherClass
from my_company.my_custom_dags.base_dag import BaseDag
폴더 루트에 .airflowignore 파일이 보여요. 이 파일은 dags 폴더에 넣어 Airflow 스케줄러가 DAG를 찾을 때 그 폴더의 어떤 파일을 무시해야 하는지 알려주는 파일이에요. 무시할 경로에 대한 정규식(기본) 또는 glob 표현식이 포함되어야 해요. 다른 PYTHONPATH 폴더에는 이 파일이 필요 없어요 (그리고 다른 폴더에는 공유 코드만 두고 실제 DAG는 두지 않아도 돼요).
위 예제에서 DAG는 my_custom_dags 폴더에만 있고, common_package는 스케줄러가 DAG를 검색할 때 스캔되면 안 되므로 common_package 폴더를 무시해야 해요. 또한 my_dag1.py와 my_dag2.py가 파생되는 base DAG를 거기에 둔다면 base_dag.py도 무시하고 싶을 거예요. 그러면 .airflowignore는 (기본 glob 구문을 사용해) 이렇게 보여야 해요:
my_company/common_package/
my_company/my_custom_dags/base_dag.py
Airflow의 내장 PYTHONPATH 항목
Airflow는 실행될 때 동적으로 sys.path에 세 개의 디렉터리를 추가해요:
dags폴더:[core]섹션의dags_folder옵션으로 구성돼요.config폴더: 기본적으로AIRFLOW_HOME변수({AIRFLOW_HOME}/config)로 구성돼요.plugins폴더:[core]섹션의plugins_folder옵션으로 구성돼요.
Warning
sys.path에만 추가되는dags와config와 달리,plugins폴더의.py파일은 시작 시 적극적으로 import되어 하위 디렉터리 구조와 무관하게 파일 이름만으로 최상위 모듈로 등록돼요. 이는AirflowPlugin서브클래스를 정의하는 파일뿐 아니라 plugins 트리의 모든.py파일에 적용돼요.예를 들어
plugins/my_company/utils/logging.py파일은logging모듈로 등록되어 표준 라이브러리 모듈을 전역으로 가리고, Airflow가 시작되지 못하게 해요. 서드파티 패키지 이름과의 충돌은 추적하기 더 어려운 미묘한 실패를 만들어내요. 플러그인 시스템의 개요는 Plugins를 참고해요.
Note
Airflow 2와 3의 Dags 폴더는 webserver와 공유하면 안 돼요. 할 수는 있지만, Airflow 1.10과 달리 Airflow는 Dags 폴더가 webserver에 있다고 기대하지 않아요. 실제로
dags폴더를 webserver와 공유하는 것은 보안 위험이 좀 있는데, DAG를 작성하는 사람이 webserver가 실행할 수 있는 코드를 작성할 수 있게 되기 때문이에요(이상적으로 webserver는 DAG 작성자가 수정할 수 있는 코드를 절대 실행하지 않아야 해요). 따라서 webserver와 일부 코드를 공유해야 한다면config나plugins폴더를 통하거나 설치된 Airflow 패키지(아래 참고)를 통해 공유하는 것을 적극 권장해요. 그 폴더들은 보통 DAG 폴더(보통 데이터 과학자)와 다른 사용자(Admin/DevOps)가 관리하고 접근할 수 있어서, Airflow 설치 설정의 일부이고 설치를 관리하는 사람들이 제어하므로 안전한 것으로 간주돼요.
코드 이름 짓기의 모범 사례
코드를 import할 때 주의해야 할 함정이 몇 가지 있어요.
가끔 Airflow나 사용하는 다른 라이브러리 코드에서 module 'X' has no attribute 'Y' 예외가 발생하는 걸 볼 수 있어요. 이는 보통 PYTHONPATH 최상위에 'X'라는 모듈이나 패키지가 있어서, 원래 코드가 기대하는 모듈 대신 그 모듈이 import되기 때문이에요.
패키지와 모듈에는 항상 고유한 이름을 사용해야 하며, 그 고유성을 강제하는 방법은 아래에 설명돼 있어요.
고유한 최상위 패키지 이름 사용
무엇보다 PYTHONPATH 최상위에 직접 추가하는 것에는 일반적인 이름을 피하는 것이 중요해요. 예를 들어 __init__.py가 있는 airflow 폴더를 DAGS_FOLDER에 추가하면 Airflow 패키지와 충돌해서 Airflow 패키지에서 어떤 것도 import할 수 없게 돼요. 마찬가지로 airflow.py 파일을 거기에 직접 추가하지 마세요. 또한 표준 라이브러리 패키지가 쓰는 multiprocessing이나 logging 같은 일반적인 이름도 패키지(__init__.py가 있는 폴더)나 모듈(.py 파일)로 최상위에 사용하면 안 돼요.
PYTHONPATH에도 있는 config와 plugins 폴더, 그리고 수동으로 PYTHONPATH에 추가하는 모든 것에도 동일하게 적용돼요 (자세한 내용은 다음 장들 참고).
Dags/공통 파일은 항상 배포에 고유한 서브패키지(아래 예제의 my_company)에 넣는 것이 좋아요. 시스템에 이미 있는 다른 패키지와 충돌할 일반적인 이름을 폴더에 사용하기가 너무 쉬워요. 예를 들어 airflow/operators 서브폴더를 만들면, Airflow에 이미 airflow.operators라는 패키지가 있어서 from airflow.operators를 import할 때 거기서 찾으므로 접근할 수 없어요.
Warning
plugins폴더의 경우 이 조언은 최상위 이름을 넘어 어떤 깊이든.py파일까지 확장돼요 — 위의 경고를 참고해요. 하위 디렉터리에 파일을 배치해도 충돌을 막지 못해요.plugins트리의 모든.py파일 이름은 고유해야 하고 어떤 표준 라이브러리나 설치된 서드파티 모듈 이름과도 일치하면 안 돼요.Airflow가 이미 실행 중일 때 충돌하는 파일이 추가되면, 다음 재시작 때까지 충돌이 발생하지 않아 근본 원인 진단이 어려워져요.
상대 import 사용하지 않기
Python 3에서 추가된 상대 import(.으로 시작하는)를 절대 사용하지 마세요.
my_dag1.py에서 이렇게 하는 것이 유혹적일 수 있어요:
from .base_dag import BaseDag # NEVER DO THAT!!!!
이런 공유 DAG는 전체 경로(PYTHONPATH에 추가된 디렉터리부터 시작해)를 사용해 import해야 해요:
from my_company.my_custom_dags.base_dag import BaseDag # This is cool
상대 import는 직관에 반하며, Python 코드를 어떻게 시작하느냐에 따라 다르게 동작할 수 있어요. Airflow에서 같은 Dag 파일은 다른 컨텍스트(스케줄러, 워커, 테스트 중)에서 파싱될 수 있으며, 그런 경우 상대 import가 다르게 동작할 수 있어요. Airflow DAG에서 무엇이든 import할 때는 항상 전체 Python 패키지 경로를 사용하세요. 그러면 많은 골칫거리를 피할 수 있어요. 상대 import의 주의점에 대해 더 읽어보려면 이 Stack Overflow 스레드를 참고해요.
패키지 폴더에 __init__.py 추가하기
폴더를 만들 때 폴더에 빈 파일로 __init__.py 파일을 추가해야 해요. Python 3에는 그 파일을 폴더에 추가할 필요가 없는 암묵적 네임스페이스라는 개념이 있지만, Airflow는 추가한 모든 패키지에 파일이 추가되길 기대해요.
자신의 PYTHONPATH 로딩 설정 살펴보기
airflow info 명령을 사용해 정확한 경로를 볼 수도 있고, PYTHONPATH 환경 변수로 지정된 디렉터리처럼 사용할 수 있어요. 이 명령이 지정하는 sys.path 변수 내용의 예는 다음과 같아요:
Python PATH: [/home/rootcss/venvs/airflow/bin:/usr/lib/python38.zip:/usr/lib/python3.9:/usr/lib/python3.9/lib-dynload:/home/rootcss/venvs/airflow/lib/python3.9/site-packages:/home/rootcss/airflow/dags:/home/rootcss/airflow/config:/home/rootcss/airflow/plugins]
아래는 airflow info 명령의 샘플 출력이에요.
See also
Apache Airflow: 2.0.0b3
System info
OS | Linux
architecture | x86_64
uname | uname_result(system='Linux', node='85cd7ab7018e', release='4.19.76-linuxkit', version='#1 SMP Tue May 26 11:42:35 UTC 2020', machine='x86_64', processor='')
locale | ('en_US', 'UTF-8')
python_version | 3.9.6 (default, Nov 25 2020, 02:47:44) [GCC 8.3.0]
python_location | /usr/python/bin/python
Tools info
git | git version 2.20.1
ssh | OpenSSH_7.9p1 Debian-10+deb10u2, OpenSSL 1.1.1d 10 Sep 2019
kubectl | NOT AVAILABLE
gcloud | NOT AVAILABLE
cloud_sql_proxy | NOT AVAILABLE
mysql | mysql Ver 8.0.22 for Linux on x86_64 (MySQL Community Server - GPL)
sqlite3 | 3.27.2 2019-02-25 16:06:06 bd49a8271d650fa89e446b42e513b595a717b9212c91dd384aab871fc1d0alt1
psql | psql (PostgreSQL) 11.9 (Debian 11.9-0+deb10u1)
Paths info
airflow_home | /root/airflow
system_path | /usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
python_path | /usr/local/bin:/opt/airflow:/files/plugins:/usr/local/lib/python38.zip:/usr/local/lib/python3.9:/usr/
| local/lib/python3.9/lib-dynload:/usr/local/lib/python3.9/site-packages:/files/dags:/root/airflow/conf
| ig:/root/airflow/plugins
airflow_on_path | True
Config info
executor | LocalExecutor
task_logging_handler | airflow.utils.log.file_task_handler.FileTaskHandler
sql_alchemy_conn | postgresql+psycopg2://postgres:airflow@postgres/airflow
dags_folder | /files/dags
plugins_folder | /root/airflow/plugins
base_log_folder | /root/airflow/logs
Providers info
apache-airflow-providers-amazon | 1.0.0b2
apache-airflow-providers-apache-cassandra | 1.0.0b2
apache-airflow-providers-apache-druid | 1.0.0b2
apache-airflow-providers-apache-hdfs | 1.0.0b2
apache-airflow-providers-apache-hive | 1.0.0b2
PYTHONPATH에 디렉터리 추가하기
PYTHONPATH 환경 변수를 사용해 sys.path에 추가할 추가 디렉터리를 지정할 수 있어요. 다음 명령으로 프로젝트 루트의 경로를 제공해 python 셸을 시작해요:
PYTHONPATH=/home/arch/projects/airflow_operators python
sys.path 변수는 아래처럼 보일 거예요:
>>> import sys
>>> from pprint import pprint
>>> pprint(sys.path)
['',
'/home/arch/projects/airflow_operators'
'/home/arch/.pyenv/versions/3.9.4/lib/python37.zip',
'/home/arch/.pyenv/versions/3.9.4/lib/python3.9',
'/home/arch/.pyenv/versions/3.9.4/lib/python3.9/lib-dynload',
'/home/arch/venvs/airflow/lib/python3.9/site-packages']
제공한 디렉터리가 이제 경로에 추가된 것을 볼 수 있어요. 이제 패키지를 import해 볼까요:
>>> import airflow_operators
Hello from airflow_operators
>>>
또한 airflow 명령과 함께 PYTHONPATH 변수를 사용할 수 있어요. 예를 들어 다음 Airflow 명령을 실행하면:
PYTHONPATH=/home/arch/projects/airflow_operators airflow info
아래처럼 언급한 PYTHONPATH 값으로 Python PATH가 업데이트된 것을 볼 수 있어요:
Python PATH: [/home/arch/venv/bin:/home/arch/projects/airflow_operators:/usr/lib/python38.zip:/usr/lib/python3.9:/usr/lib/python3.9/lib-dynload:/home/arch/venv/lib/python3.9/site-packages:/home/arch/airflow/dags:/home/arch/airflow/config:/home/arch/airflow/plugins]
Python에서 패키지 만들기
이것은 나만의 커스텀 코드를 추가하는 가장 체계적인 방법이에요. 패키지를 사용하면 버전 관리 접근을 조직화하고, 설치된 공유 코드의 버전을 제어하며, 모든 인스턴스·컨테이너에 코드를 통제된 방식으로 배포할 수 있어요 — 모두 DAG 작성자가 아니라 시스템 관리자/DevOps가 하게 되죠. 보통 이 공유 코드를 관리하는 별도 팀이 있을 때 적합하지만, Python에 능숙하다면 더 작은 배포에서도 이런 방식으로 코드를 배포할 수 있어요. 또한 Plugins와 Providers를 Python 패키지로 설치할 수 있으므로, 패키지를 빌드하는 법을 배워두면 유용해요.
패키지를 만드는 방법은 다음과 같아요:
-
시작하기 전에 사용할 build/packaging 도구를 선택해 설치해요. 다른 도구로 쉽게 전환하려면 PEP-621을 준수하는 것이 이상적이에요. 인기 있는 선택지는 setuptools, poetry, hatch, flit이에요.
-
직접 패키지를 만들 시점을 정했다면, 패키지 디렉터리를 만들어요 — 여기서는
airflow_operators라고 부르겠어요.
mkdir airflow_operators
- 패키지 안에
__init__.py파일을 만들고 다음 코드를 추가해요:
print("Hello from airflow_operators")
이 패키지를 import하면 위 메시지가 출력돼야 해요.
-
pyproject.toml을 만들고 원하는 빌드 도구 설정으로 채워요. The pyproject.toml specification 참고. -
원하는 도구로 프로젝트를 빌드해요. 예를 들어 hatch의 경우:
hatch build -t wheel
그러면 dist 폴더에 .whl 파일이 생성돼요.
- pip으로 .whl 파일을 설치해요:
pip install dist/airflow_operators-0.0.0-py3-none-any.whl
- 패키지를 이제 사용할 준비가 됐어요!
>>> import airflow_operators
Hello from airflow_operators
>>>
pip 명령으로 패키지를 제거할 수 있어요:
pip uninstall airflow_operators
Python 패키지를 만들고 배포하는 방법에 대한 자세한 내용은 Packaging Python Projects를 참고해요.