TITEDIOS 편한 코딩

[MLOps] 6강 - Docker 설치 및 docker-compose.yaml 작성 (1) (Airflow Multi-Node System 구축 가이드 및 사례) 본문

MLOps

[MLOps] 6강 - Docker 설치 및 docker-compose.yaml 작성 (1) (Airflow Multi-Node System 구축 가이드 및 사례)

TitediosKW 2025. 1. 3. 19:00
반응형

목차

  1. Docker 설치
  2. Docker 설치 후 과정
  3. Airflow Multi-node를 위한 docker-compose.yaml 작성
  4. 결론

Multi-node 환경에서 Airflow를 설정하면 작업 부하를 분산하고 시스템 성능을 향상할 수 있습니다. MLOps 시스템을 만들기 위한 우리의 목적이기도 합니다. 이번 포스팅에서는 Docker를 활용하여 Airflow를 Multi-node로 구성하는 과정을 단계별로 설명하겠습니다.


1. Docker 설치

설치 전 virtualbox 가상머신에 한가지 설정이 필요합니다. 아래 그림처럼 설정하여 복사-붙여넣기 기능을 이용할 수 있도록 합니다. 하셔야.... 편합니다. 아니라면 한 땀 한 땀 작성을 해야 하기 때문입니다....

Docker 설치는 공식 문서를 참조하였습니다.

 

Install

Learn how to choose the best method for you to install Docker Engine. This client-server application is available on Linux, Mac, Windows, and as a static binary.

docs.docker.com

 

1.1. 기존 설치된 Docker 삭제

지금 우리는 Linux를 새로 설치해서 발생할 가능성이 적지만, 실제 환경에서는 custom된 docker가 설치되어 있을지도 모릅니다. 그럴 경우 충돌 문제가 발생할 수 있습니다. 이런 문제를 미연에 방지하고자 기존에 설치된 docker를 미리 삭제하는 것이 좋습니다. 물론, 삭제 전에는 확인 작업이 필수적입니다(그렇지 않다면 기존에 사용하고 있는 사용자들에게 폐가 될테니까요).

sudo dnf remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine \
                  podman \
                  runc

1.2. Docker 리포지토리 설정

이번 실습에서는 패키지 관리자를 이용해 docker를 설치할 것입니다. 패키지 관리자를 통해 docker를 설치하기 위해서는 먼저 docker의 위치를 알려주는 작업이 필요합니다.

sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo

1.3. Docker 패키지 설치

이제 docker를 설치합니다. 아래 명령어를 수행하면 docker에 관련한 패키지들을 모두 설치하게 됩니다. 그리고 docker 그룹을 자동으로 생성합니다.

sudo dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

1.4. Docker 엔진 시작

아래 명령어로 OS가 시작되면 자동으로 docker가 실행될 수 있도록 합니다. 혹시 항상 수작업으로 실행하고 싶으시다면 sudo systemctl start docker 명령어로 실행시키시면 됩니다.

sudo systemctl enable --now docker

1.5. hello-world image 실행을 통해 검증하기

간단한 Hello world 이미지를 실행시켜 봄으로써 docker가 잘 설치되고 동작하는지 확인해 봅니다.

sudo docker run hello-world

실행하면 아래와 같은 화면을 확인하실 수 있을 겁니다.

반응형

2. Docker 설치 후 과정

Docker는 관리자 권한이 필요합니다. 따라서 일반 사용자, 즉 non-root user가 직접 사용하려면 아래와 같은 과정을 수행해야 합니다. 역시 도커의 공식문서를 참조하였습니다.

 

Post-installation steps

Find the recommended Docker Engine post-installation steps for Linux users, including how to run Docker as a non-root user and more.

docs.docker.com

2.1. docker group 생성

이전에 설치에서 group이 생성된다고 했는데 또 생성하는 이유는 알 수 없지만, 공식 문서에는 group 생성을 먼저 하라고 하고 있습니다.

sudo groupadd docker

2.2. 일반 사용자를 docker 그룹에 등록

여러분의 계정을 docker 그룹에 등록해야 합니다. 아래 명령을 통해 docker 그룹에 등록해 주세요.$USER에 여러분의 계정 이름을 넣어 주시면 됩니다.

sudo usermod -aG docker $USER

2.3. 재접속 또는 그룹 멤버십 갱신

그룹을 등록한 후 재접속을 통해 그룹을 갱신할 수 있습니다. 아니면 아래의 명령어로도 그룹 멤버십을 갱신할 수 있습니다.

newgrp docker

2.4. hello-world image 실행을 통해 검증하기

다시 한번 hello-world 이미지를 실행시켜 동작이 잘하는지 확인합니다. sudo 명령어가 없는 것에 주의합니다.

docker run hello-world

에러 발생 시

만약에 아래와 같은 에러 메시지가 나타난다면 권한 문제를 해결해야 합니다.

WARNING: Error loading config file: /home/user/.docker/config.json -
stat /home/user/.docker/config.json: permission denied

아래 명령어를 통해 ~/.docker의 권한을 변경해 줌으로써 문제를 해결할 수 있습니다.

sudo chown "$USER":"$USER" /home/"$USER"/.docker -R
sudo chmod g+rwx "$HOME/.docker" -R

 


3. Airflow Multi-node를 위한 docker-compose.yaml 작성

Docker와 Docker Compose 설치를 완료했다면, 이제 Multi-node Airflow 구성을 위한 docker-compose.yaml 파일을 작성합니다.

기본 디렉토리 구조

Airflow는 기본적으로 아래와 같은 구조를 가집니다. Dag라고 하는 workflow를 가지고 있는 파일들의 집합이 있고 docker-copmose.yaml 파일이 있습니다.

.
|-- dags/
|-- docker-compose.yaml
  • dags: DAG 파일을 저장하는 디렉토리
  • docker-compose.yaml: Docker Compose 설정 파일

원하는 디렉토리에 dags/ 폴더 하나만 만드시면 됩니다. 저는 $HOME/works/에서 작업하기로 했습니다. 저와 같은 위치에서 작업하실 분들은 아래 명령어를 실행하시면 되겠습니다.

cd ~
mkdir -p works/dags && cd works/

docker-compose.yaml 작성

curl 명령어를 통해 docker-compose.yaml을 다운로드합니다.

curl -LfO 'https://airflow.apache.org/docs/apache-airflow/2.10.4/docker-compose.yaml'

아니면 만들어 놓은 docker-compose.yaml 파일을 아래 내용으로 채워 넣습니다. 사실, 이 내용을 넣을지 고민이 많았습니다. 너무 길거든요. 복사하시는 게 편하시다면 복사를 해도 좋고 아니라면 curl 명령어를 통해 다운로드를 하여도 괜찮습니다.

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
#under the License.
#

# Basic Airflow cluster configuration for CeleryExecutor with Redis and PostgreSQL.
#
# WARNING: This configuration is for local development. Do not use it in a production deployment.
#
# This configuration supports basic configuration using environment variables or an .env file
# The following variables are supported:
#
# AIRFLOW_IMAGE_NAME           - Docker image name used to run Airflow.
#                                Default: apache/airflow:2.10.4
# AIRFLOW_UID                  - User ID in Airflow containers
#                                Default: 50000
# AIRFLOW_PROJ_DIR             - Base path to which all the files will be volumed.
#                                Default: .
# Those configurations are useful mostly in case of standalone testing/running Airflow in test/try-out mode
#
# _AIRFLOW_WWW_USER_USERNAME   - Username for the administrator account (if requested).
#                                Default: airflow
# _AIRFLOW_WWW_USER_PASSWORD   - Password for the administrator account (if requested).
#                                Default: airflow
# _PIP_ADDITIONAL_REQUIREMENTS - Additional PIP requirements to add when starting all containers.
#                                Use this option ONLY for quick checks. Installing requirements at container
#                                startup is done EVERY TIME the service is started.
#                                A better way is to build a custom image or extend the official image
#                                as described in https://airflow.apache.org/docs/docker-stack/build.html.
#                                Default: ''
#
# Feel free to modify this file to suit your needs.
---
x-airflow-common:
  &airflow-common
  # In order to add custom dependencies or upgrade provider packages you can use your extended image.
  # Comment the image line, place your Dockerfile in the directory where you placed the docker-compose.yaml
  # and uncomment the "build" line below, Then run `docker-compose build` to build the images.
  image: ${AIRFLOW_IMAGE_NAME:-apache/airflow:2.10.4}
  # build: .
  environment:
    &airflow-common-env
    AIRFLOW__CORE__EXECUTOR: CeleryExecutor
    AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow
    AIRFLOW__CELERY__RESULT_BACKEND: db+postgresql://airflow:airflow@postgres/airflow
    AIRFLOW__CELERY__BROKER_URL: redis://:@redis:6379/0
    AIRFLOW__CORE__FERNET_KEY: ''
    AIRFLOW__CORE__DAGS_ARE_PAUSED_AT_CREATION: 'true'
    AIRFLOW__CORE__LOAD_EXAMPLES: 'true'
    AIRFLOW__API__AUTH_BACKENDS: 'airflow.api.auth.backend.basic_auth,airflow.api.auth.backend.session'
    # yamllint disable rule:line-length
    # Use simple http server on scheduler for health checks
    # See https://airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/logging-monitoring/check-health.html#scheduler-health-check-server
    # yamllint enable rule:line-length
    AIRFLOW__SCHEDULER__ENABLE_HEALTH_CHECK: 'true'
    # WARNING: Use _PIP_ADDITIONAL_REQUIREMENTS option ONLY for a quick checks
    # for other purpose (development, test and especially production usage) build/extend Airflow image.
    _PIP_ADDITIONAL_REQUIREMENTS: ${_PIP_ADDITIONAL_REQUIREMENTS:-}
    # The following line can be used to set a custom config file, stored in the local config folder
    # If you want to use it, outcomment it and replace airflow.cfg with the name of your config file
    # AIRFLOW_CONFIG: '/opt/airflow/config/airflow.cfg'
  volumes:
    - ${AIRFLOW_PROJ_DIR:-.}/dags:/opt/airflow/dags
    - ${AIRFLOW_PROJ_DIR:-.}/logs:/opt/airflow/logs
    - ${AIRFLOW_PROJ_DIR:-.}/config:/opt/airflow/config
    - ${AIRFLOW_PROJ_DIR:-.}/plugins:/opt/airflow/plugins
  user: "${AIRFLOW_UID:-50000}:0"
  depends_on:
    &airflow-common-depends-on
    redis:
      condition: service_healthy
    postgres:
      condition: service_healthy

services:
  postgres:
    image: postgres:13
    environment:
      POSTGRES_USER: airflow
      POSTGRES_PASSWORD: airflow
      POSTGRES_DB: airflow
    volumes:
      - postgres-db-volume:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "airflow"]
      interval: 10s
      retries: 5
      start_period: 5s
    restart: always

  redis:
    # Redis is limited to 7.2-bookworm due to licencing change
    # https://redis.io/blog/redis-adopts-dual-source-available-licensing/
    image: redis:7.2-bookworm
    expose:
      - 6379
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 30s
      retries: 50
      start_period: 30s
    restart: always

  airflow-webserver:
    <<: *airflow-common
    command: webserver
    ports:
      - "8080:8080"
    healthcheck:
      test: ["CMD", "curl", "--fail", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

  airflow-scheduler:
    <<: *airflow-common
    command: scheduler
    healthcheck:
      test: ["CMD", "curl", "--fail", "http://localhost:8974/health"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

  airflow-worker:
    <<: *airflow-common
    command: celery worker
    healthcheck:
      # yamllint disable rule:line-length
      test:
        - "CMD-SHELL"
        - 'celery --app airflow.providers.celery.executors.celery_executor.app inspect ping -d "celery@$${HOSTNAME}" || celery --app airflow.executors.celery_executor.app inspect ping -d "celery@$${HOSTNAME}"'
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    environment:
      <<: *airflow-common-env
      # Required to handle warm shutdown of the celery workers properly
      # See https://airflow.apache.org/docs/docker-stack/entrypoint.html#signal-propagation
      DUMB_INIT_SETSID: "0"
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

  airflow-triggerer:
    <<: *airflow-common
    command: triggerer
    healthcheck:
      test: ["CMD-SHELL", 'airflow jobs check --job-type TriggererJob --hostname "$${HOSTNAME}"']
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

  airflow-init:
    <<: *airflow-common
    entrypoint: /bin/bash
    # yamllint disable rule:line-length
    command:
      - -c
      - |
        if [[ -z "${AIRFLOW_UID}" ]]; then
          echo
          echo -e "\033[1;33mWARNING!!!: AIRFLOW_UID not set!\e[0m"
          echo "If you are on Linux, you SHOULD follow the instructions below to set "
          echo "AIRFLOW_UID environment variable, otherwise files will be owned by root."
          echo "For other operating systems you can get rid of the warning with manually created .env file:"
          echo "    See: https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html#setting-the-right-airflow-user"
          echo
        fi
        one_meg=1048576
        mem_available=$$(($$(getconf _PHYS_PAGES) * $$(getconf PAGE_SIZE) / one_meg))
        cpus_available=$$(grep -cE 'cpu[0-9]+' /proc/stat)
        disk_available=$$(df / | tail -1 | awk '{print $$4}')
        warning_resources="false"
        if (( mem_available < 4000 )) ; then
          echo
          echo -e "\033[1;33mWARNING!!!: Not enough memory available for Docker.\e[0m"
          echo "At least 4GB of memory required. You have $$(numfmt --to iec $$((mem_available * one_meg)))"
          echo
          warning_resources="true"
        fi
        if (( cpus_available < 2 )); then
          echo
          echo -e "\033[1;33mWARNING!!!: Not enough CPUS available for Docker.\e[0m"
          echo "At least 2 CPUs recommended. You have $${cpus_available}"
          echo
          warning_resources="true"
        fi
        if (( disk_available < one_meg * 10 )); then
          echo
          echo -e "\033[1;33mWARNING!!!: Not enough Disk space available for Docker.\e[0m"
          echo "At least 10 GBs recommended. You have $$(numfmt --to iec $$((disk_available * 1024 )))"
          echo
          warning_resources="true"
        fi
        if [[ $${warning_resources} == "true" ]]; then
          echo
          echo -e "\033[1;33mWARNING!!!: You have not enough resources to run Airflow (see above)!\e[0m"
          echo "Please follow the instructions to increase amount of resources available:"
          echo "   https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html#before-you-begin"
          echo
        fi
        mkdir -p /sources/logs /sources/dags /sources/plugins
        chown -R "${AIRFLOW_UID}:0" /sources/{logs,dags,plugins}
        exec /entrypoint airflow version
    # yamllint enable rule:line-length
    environment:
      <<: *airflow-common-env
      _AIRFLOW_DB_MIGRATE: 'true'
      _AIRFLOW_WWW_USER_CREATE: 'true'
      _AIRFLOW_WWW_USER_USERNAME: ${_AIRFLOW_WWW_USER_USERNAME:-airflow}
      _AIRFLOW_WWW_USER_PASSWORD: ${_AIRFLOW_WWW_USER_PASSWORD:-airflow}
      _PIP_ADDITIONAL_REQUIREMENTS: ''
    user: "0:0"
    volumes:
      - ${AIRFLOW_PROJ_DIR:-.}:/sources

  airflow-cli:
    <<: *airflow-common
    profiles:
      - debug
    environment:
      <<: *airflow-common-env
      CONNECTION_CHECK_MAX_COUNT: "0"
    # Workaround for entrypoint issue. See: https://github.com/apache/airflow/issues/16252
    command:
      - bash
      - -c
      - airflow

  # You can enable flower by adding "--profile flower" option e.g. docker-compose --profile flower up
  # or by explicitly targeted on the command line e.g. docker-compose up flower.
  # See: https://docs.docker.com/compose/profiles/
  flower:
    <<: *airflow-common
    command: celery flower
    profiles:
      - flower
    ports:
      - "5555:5555"
    healthcheck:
      test: ["CMD", "curl", "--fail", "http://localhost:5555/"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

volumes:
  postgres-db-volume:

결론

이제 Docker와 Docker Compose를 사용하여 Multi-node Apache Airflow를 설정하는 방법을 어느 정도 이해하셨을 것입니다. 실제 Airflow를 실행하여 보지는 않았지만 여기까지 왔다면 거의 대부분 완료된 것이므로 너무 걱정하지 않으셔도 됩니다. 다음 포스팅에서는 Multi-node를 사용하기 위해 설정을 조정하여 프로젝트 요구 사항에 맞게 최적화하는 작업을 수행할 것입니다.

 

궁금한 점이나 추가로 알고 싶은 내용이 있다면 댓글로 알려주세요!

 

도움이 되셨다면 공감 부탁드리겠습니다. 여러분의 공감이 정말 큰 힘이 됩니다.

 

감사합니다!

반응형