Data Engineering/Airflow

Dockeroperator의 Bind mount을 활용한 Airflow 운영

신수동탈곡기 2021. 9. 5. 16:13

Docker operator


나는 Airflow에서 Dockeroperator를 활용한다. Dockeroperator는 scheduling된 job을 수행할 때 미리 구성해놓은 docker image로 container를 생성하고, 그 안에서 job을 수행한다. 이러한 방식을 활용한 주요한 이유는 의존성 문제이다. 각각의 job이 요구하는 환경을 docker image로 미리 구성해놓고, 필요할 때 마다 job에 맞는 환경을 생성하는 방식으로 예기치 않게 발생할 수 있는 문제에 대처하기 위해 Dockeroperator를 활용했다.

Mount 방식에 대한 고민


Docker image를 만들며, 컨테이너 내부에 job을 위한 코드는 어떻게 담을 것인지에 대한 고민을 시작했다. Dockerfile의 COPY 커맨드나 Volume 커맨드로 이미지에 소스코드를 반영할 수는 있었지만 1) 지속적으로 유지보수가 진행되는 프로젝트이기 때문에 코드를 최신화시켜주기 어렵다 2) 이미지 크기가 너무 커져 디스크 관리가 어려워질 가능성이 존재한다는 이유로 Bind mount를 활용하고 싶었다. Bind mount는 Host의 데이터에 의존적이라 예기치 못한 변경으로 container 내부에 영향을 주고 scheduling 된 job을 정상적으로 수행하지 못할 가능성이 있다는 단점이 있었지만 나의 경우에는 신경쓰지 않아도 되는 부분이었다.

Docker Volume & Bind mount

Docker container 내부의 데이터를 보존하거나, container 내부에 데이터를 넣고(?) 싶을 때 대게 활용하는 방법 두 가지다. Docker Volume은 도커 영역에서 관리하는 Volume을 만들어 container에 mount하는 방식이다. 한 container에 복수의 Volume을 mount 할 수도 있고, 하나의 Volume을 복수의 container에 mount하여 데이터를 공유할 수도 있다. 하지만 상술했듯이 Docker Volume을 이용한 방법은 코드의 최신화와 디스크 관리 부분에서 불편함이 있었다.
Bind mount는 Host의 특정 디렉토리를 container에 mount하는 방식이다. Host에서 데이터가 변경될 경우 container 내부에도 즉시 반영되며, Host 영역의 데이터이기 때문에 Docker 영역의 용량에 부담도 가지 않았다.

Docker image의 역할


A Dockerfile defines how an image is built, not how it's used - so you can't specify the bind mount in a Dockerfile.

Docker image를 처음 만들 때, Dockerfile에서 Bind mount하여 이미지를 생성하는 방법을 탐색했다. 하지만 stackoverflow에서 위와 같은 답변을 찾았고, Docker image가 정확히 어떤 역할을 하는지, 그리고 왜 bind mount를 할 수 없는지를 알게되었다.

 

사실 어찌보면 당연한 이야기였다. image를 run하는 환경이 각각 다를텐데 어떻게 bind mount를 할 수 있겠나.

Dockeroperator의 mounts argument


Bind mount한 Docker image를 생성할 수 없다는 것을 알게되었고, Dockeroperator를 살펴봤다. Airflow Official Document를 살펴보니 mounts라는 argument가 있었고, 이는 Docker operator가 지정된 image를 run할 때, host의 directory를 container directory에 bind mount 시켜주는 역할을 했다.

예제

from airflow.models import DAG
from airflow.operators.docker_operator import DockerOperator
from docker.types import Mount

from datetime import datetime

args = {
    "owner": "heosm",
    "start_date": datetime(2021, 9, 2, tzinfo=tz)
}

dag = DAG(
    dag_id="dag_id",
    default_args=args,
    schedule_interval="0 1 * * *"
)

cmd = f"/usr/bin/python3 /python_project/get_data_af.py --start_date {ts}"
task_something = DockerOperator(
    task_id="get_data",
    image="etl:1.0.6",
    environment={"PYTHONPATH": "/home/Project"},
    command=cmd,
    auto_remove=True,
    api_version="auto",
    container_name=f"airflow_get_data_{now}",
    network_mode="bridge",
    mounts=[
        Mount(
            source="/mount/path/on/host",
            target="/mount/path/on/container",
            type="bind")
    ],
    dag=dag
)

task_something

결과


1) 지속적으로 유지보수되는 소스코드를 복잡한 과정없이 최신화 시킬 수 있었고,
2) Docker image를 최소화하여 디스크 관리에서도 이점을 챙길 수 있었다.
3) 부수적인 내용이지만 Docker의 Mount 방식, Docker image의 역할도 알 수 있었다.
4) Host에서 변경되는 데이터는 container 내부에도 즉시 반영되기 때문에, 부주의로 인한 코드 수정을 유의해야 하긴 한다.
5) CI/CD 관련 지식 공부가 필요할 것 같음. Jenkins나 Ansible 등과 같은 컴포넌트로 이미지 빌드 자동화를 할 수 있을 수 있을 것 같음 (2022. 05. 26)

'Data Engineering > Airflow' 카테고리의 다른 글

Airflow의 parallelism과 dag_concurrency  (0) 2022.05.26
airflow-webserver-monitor.pid is already locked  (0) 2022.05.26
Airflow tutorial - 1  (0) 2021.07.10