회사 보안 정책상 외부 인터넷 접속이 제한적이고 SSL 인증서 관리가 엄격한 Windows 환경에서, Astro CLI를 활용해 Airflow와 Spark 연동 환경을 구축하면서 트러블슈팅 한 과정을 정리함
Astro CLI(
astro dev start
)를 통해 로컬 Airflow 환경을 띄우고, 외부 Docker Container로 Spark를 실행하여 연동하려는 시도를 했음. 그러나 다음과 같은 두 가지 치명적인 네트워크/환경 문제에 봉착함.
Docker Image Pull 실패: 등의 외부 이미지를 받아오려 했으나, 에러가 발생하며 이미지를 찾지 못함.
bitnami/spark:latest
manifest unknown
SSL Handshake 및 Proxy 에러: 사내망의 SSL Inspection(패킷 감청) 장비 또는 Proxy로 인해 등 외부 레지스트리 접속 시 네트워크 에러 발생함.
quay.io
connectex
결정 사항: 외부에서 별도의 Spark 클러스터 이미지를 가져오는 것은 네트워크 제약상 유지보수가 어렵다고 판단함. 따라서 Airflow 컨테이너 내부(Local)에 Spark 엔진을 내장(Embedded)시켜 네트워크 의존성을 제거하고 구성을 단순화하기로 결정함.
Spark는 JVM 기반으로 동작하므로 Java 런타임이 필수적임. 또한 Airflow에서 별도의 설정을 최소화하기 위해
pip을 통해 pyspark 라이브러리를 설치하면 Spark 바이너리까지 함께 포함되어 설치된다는 점을 이용함.
아래는 수정한 Dockerfile의 내용임.
dockerfile
FROM quay.io/astronomer/astro-runtime:12.6.0
USER root
# 1. Java 17(JDK) 및 필수 유틸리티 설치
# Spark 구동을 위해 OpenJDK 17을 설치하고 불필요한 캐시는 제거함
RUN apt-get update && \
apt-get install -y --no-install-recommends openjdk-17-jdk wget && \
rm -rf /var/lib/apt/lists/*
# 2. PySpark 설치 (Airflow 환경 내 Spark 엔진 포함)
# pip로 설치 시 Spark 엔진이 내장되므로 별도로 Spark 바이너리를 다운받을 필요가 없음
RUN pip install --no-cache-dir pyspark
# 3. 환경 변수 설정
# 시스템이 Java와 Spark의 위치를 인지할 수 있도록 경로를 지정함
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
ENV PATH=$PATH:$JAVA_HOME/bin
# 보안상 다시 airflow 유저로 권한을 돌려줌
USER airflow
핵심 포인트: 외부 Spark 클러스터와 통신하는 복잡한 설정(Hadoop XML 등)을 제거하고, Docker 이미지 하나에 실행 환경을 모두 패키징하여 이식성을 높였음.
Dockerfile에서
JAVA_HOME
을 선언했지만, Astro CLI가 컨테이너를 실행할 때
scheduler
,
webserver
,
triggerer
등 각 서비스 컨테이너에 환경 변수가 제대로 전파되지 않는 문제가 발생함. 이로 인해
Java not found
에러가 발생할 수 있음.
이를 해결하기 위해
docker-compose.override.yml에서 각 서비스에 명시적으로 환경 변수를 주입함.
yaml
# version: "3.1" <-- 경고 메시지를 유발하는 구식 속성이므로 제거함
services:
scheduler:
environment:
-JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
webserver:
environment:
-JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
triggerer:
environment:
-JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
해결 원리: Docker Compose 오버라이드 기능을 통해 Airflow의 모든 구성 요소가 동일한 Java 런타임 경로를 바라보도록 강제하여 실행 환경의 일관성을 확보함.
환경 구축 후 DAG를 실행하려 하자 다음과 같은 에러가 발생함.
ModuleNotFoundError: No module named 'airflow.providers.apache'
원인은 Airflow 기본 이미지에 Spark 연동을 위한 Provider 패키지가 포함되어 있지 않기 때문임.
requirements.txt에 해당 패키지를 명시해야 함.
text
# requirements.txt
# DB 연결을 위한 Provider
apache-airflow-providers-microsoft-mssql
# Spark Core 엔진
pyspark
# Airflow와 Spark를 연결해주는 핵심 Provider (SparkSubmitOperator 포함)
apache-airflow-providers-apache-spark
주의 사항: 사내망 환경에서는
pyspark와 같은 대용량 패키지(약 300MB 이상)를 다운로드할 때 Proxy나 네트워크 속도 문제로 빌드 시간이 10분 이상 소요될 수 있음. 타임아웃이 발생하지 않도록 인내심을 갖고
astro dev restart
를 수행해야 함.
환경 설정이 끝난 후 코드 레벨에서
SparkSubmitOperator
를 찾을 수 없다는(
NameError
) 에러가 발생함. 디버깅 결과 Import 구문이 누락되었고, 실행하려는 Python 파일의 경로가 실제와 다른 것을 확인함.
수정 전 (에러 발생 코드):
python
# Import 누락됨
# 파일명 불일치: ncst_to_mssql.py (실제 파일은 ncst_json_to_mssql.py)
spark_to_mssql = SparkSubmitOperator(
application="/usr/local/airflow/include/spark_jobs/ncst_to_mssql.py",
...
)
수정 후 (정상 코드):
python
import pendulum
# [중요] Operator를 사용하기 위해 반드시 Import 해야 함
from airflow.providers.apache.spark.operators.spark_submitimport SparkSubmitOperator
...
spark_to_mssql = SparkSubmitOperator(
task_id="spark_ncst_to_mssql",
# [중요] 실제 Docker 컨테이너 내부의 파일 경로와 정확히 일치시켜야 함
application="/usr/local/airflow/include/spark_jobs/ncst_json_to_mssql.py",
verbose=True,
env_vars={
"MSSQL_HOST":"host.docker.internal",# Docker에서 호스트 OS(Localhost) 접근
...
},
...
)
폐쇄망 대응: 외부 이미지를 Pull 하는 방식은 사내망에서 매우 취약함. Dockerfile 커스터마이징을 통해 필요한 런타임(Java, Spark)을 이미지 내부에 포함하는 것이 가장 안정적임.
환경 변수: Dockerfile의 설정이 런타임(Docker Compose)까지 보장되지 않을 수 잇으므로, override.yml 파일에서 확실하게 주입하는 것이 안전함.
ENV
정확한 경로 설정: Airflow Operator에서 호출하는 파일 경로는 로컬 PC 경로()가 아니라 Docker 컨테이너 내부 경로() 기준이어야 함.
C:\...
/usr/local/airflow/...
이 과정을 통해 네트워크 제약이 심한 Windows 환경에서도 안정적인 Airflow-Spark 데이터 파이프라인 개발 환경을 성공적으로 구축하였음.