스파크 애플리케이션 제출하기 (spark-submit)
스파크 애플리케이션 제출하기 (spark-submit)
스파크 애플리케이션을 클러스터에서 돌리려면 여러 클러스터 매니저를 외우고 각자 설정을 다르게 해야 할 것 같지만, spark-submit 스크립트가 그 불편을 통째로 덜어줘요. 지원하는 모든 클러스터 매니저(local, 스탠드얼론, YARN, 쿠버네티스)를 하나의 통일된 인터페이스로 제어할 수 있거든요. 이 페이지에서는 애플리케이션을 묶는 방법부터 spark-submit으로 실제로 제출하는 흐름, 마스터 URL의 종류, 설정 파일 로딩까지 차례로 살펴볼게요.
본문
애플리케이션 의존성 묶기
코드가 다른 프로젝트에 의존하고 있다면 그 의존성을 애플리케이션과 함께 묶어 클러스터로 배포해야 해요. 이때 만들게 되는 게 assembly jar(일명 "uber" jar)인데요, 내 코드와 의존성을 하나의 jar로 합친 파일이에요. sbt와 Maven 모두 assembly 플러그인을 제공하니 편하게 쓸 수 있어요. 단, 생성할 때 Spark과 Hadoop은 provided 의존성으로 지정해야 해요 — 이 둘은 런타임에 클러스터 매니저가 이미 제공하기 때문에 번들에 넣을 필요가 없거든요.
Python이라면 --py-files 인자로 .py, .zip, .egg 파일을 애플리케이션과 함께 전달할 수 있어요. Python 파일이 여러 개면 한 .zip이나 .egg로 묶는 걸 권장하고요, 서드파티 의존성은 Python 패키지 관리 문서를 참고하면 돼요.
spark-submit으로 애플리케이션 실행하기
사용자 애플리케이션을 묶었다면 이제 bin/spark-submit 스크립트를 통해 실행할 수 있어요. 이 스크립트가 Spark과 그 의존성으로 클래스패스를 구성해 주고, 스파크가 지원하는 다양한 클러스터 매니저와 디플로이 모드를 처리해 줘요:
./bin/spark-submit \
--class <main-class> \
--master <master-url> \
--deploy-mode <deploy-mode> \
--conf <key>=<value> \
... # other options
<application-jar> \
[application-arguments]
자주 쓰이는 옵션은 이런 것들이에요.
--class: 애플리케이션의 진입점 (예:org.apache.spark.examples.SparkPi)--master: 클러스터의 마스터 URL (예:spark://23.195.26.187:7077)--deploy-mode: 드라이버를 워커 노드에 배치할지(cluster), 외부 클라이언트로 로컬에 둘지(client) 결정해요. 기본값은client예요.--conf:key=value형태의 임의 Spark 설정. 값에 공백이 있으면"key=value"처럼 따옴표로 감싸고, 여러 설정은 각각 별도 인자로 전달해요.application-jar: 애플리케이션과 모든 의존성을 담은 번들 jar 경로. 클러스터 안 모든 노드에서 접근 가능한hdfs://또는file://경로여야 해요.application-arguments: main 클래스의 main 메서드에 전달할 인자(있을 경우).
디플로이 모드 선택은 네트워크 거리와 밀접한 관련이 있어요. 워커 머신과 물리적으로 가까운 게이트웨이 머신(예: 스탠드얼론 EC2 클러스터의 마스터 노드)에서 제출한다면 client 모드가 어울려요 — 드라이버가 spark-submit 프로세스 안에서 바로 뜨고, 입력·출력이 콘솔에 붙어서 REPL(스파크 셸) 같은 애플리케이션에 특히 적합하죠. 반대로 노트북처럼 워커 머신과 먼 곳에서 제출한다면 cluster 모드가 권장돼요. 드라이버와 익스큐터 사이 네트워크 지연을 줄일 수 있거든요. 참고로 스탠드얼론 모드는 현재 Python 애플리케이션의 cluster 모드를 지원하지 않아요.
Python 애플리케이션이라면 <application-jar> 자리에 그냥 .py 파일을 넘기고, 검색 경로에 넣을 Python .zip/.egg/.py 파일은 --py-files로 추가하면 돼요.
클러스터 매니저별로만 있는 옵션도 있어요. 예를 들어 스탠드얼론 클러스터에 cluster 모드로 제출할 때 --supervise를 주면 드라이버가 0이 아닌 종료 코드로 실패할 때 자동으로 재시작돼요. spark-submit에 가능한 옵션 전체는 --help로 확인할 수 있어요. 자주 쓰는 예시 몇 가지를 볼게요.
# Run application locally on 8 cores
./bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master "local[8]" \
/path/to/examples.jar \
100
# Run on a Spark standalone cluster in client deploy mode
./bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master spark://207.184.161.138:7077 \
--executor-memory 20G \
--total-executor-cores 100 \
/path/to/examples.jar \
1000
# Run on a Spark standalone cluster in cluster deploy mode with supervise
./bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master spark://207.184.161.138:7077 \
--deploy-mode cluster \
--supervise \
--executor-memory 20G \
--total-executor-cores 100 \
/path/to/examples.jar \
1000
# Run on a YARN cluster in cluster deploy mode
export HADOOP_CONF_DIR=XXX
./bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master yarn \
--deploy-mode cluster \
--executor-memory 20G \
--num-executors 50 \
/path/to/examples.jar \
1000
# Run a Python application on a Spark standalone cluster
./bin/spark-submit \
--master spark://207.184.161.138:7077 \
examples/src/main/python/pi.py \
1000
# Run on a Kubernetes cluster in cluster deploy mode
./bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master k8s://xx.yy.zz.ww:443 \
--deploy-mode cluster \
--executor-memory 20G \
--num-executors 50 \
http://path/to/examples.jar \
1000
마스터 URL
--master에 넘기는 URL은 아래 형식 중 하나로 적을 수 있어요.
local— 워커 스레드 1개로 로컬 실행(병렬성 없음).local[K]— 워커 스레드 K개로 로컬 실행(가급적 머신의 코어 수로 설정).local[K,F]— 워커 스레드 K개, 최대 실패 허용 F개로 로컬 실행(spark.task.maxFailures참고).local[*]— 논리 코어 수만큼 워커 스레드로 로컬 실행.local[*,F]— 논리 코어 수만큼의 워커 스레드와 최대 실패 F개로 로컬 실행.local-cluster[N,C,M]— 단위 테스트 전용 모드. 단일 JVM에서 N개 워커, 워커당 C개 코어, 워커당 M MiB 메모리로 분산 클러스터를 흉내 내요.spark://HOST:PORT— 주어진 스탠드얼론 클러스터 마스터에 연결. 포트는 마스터 설정값을 따르는데 기본이 7077이에요.spark://HOST1:PORT1,HOST2:PORT2— Zookeeper로 스탠드바이 마스터를 둔 스탠드얼론 클러스터에 연결. HA로 설정된 모든 마스터 호스트를 나열해야 하고, 포트는 각 마스터 설정값(기본 7077)을 따르죠.yarn—--deploy-mode값에 따라 YARN 클러스터에client또는cluster모드로 연결. 클러스터 위치는HADOOP_CONF_DIR또는YARN_CONF_DIR변수를 기반으로 찾아요.k8s://HOST:PORT—--deploy-mode에 따라 쿠버네티스 클러스터에client/cluster모드로 연결.HOST와PORT는 쿠버네티스 API 서버를 가리켜요. 기본적으로 TLS로 연결하고, 보안 없는 연결을 강제하려면k8s://http://HOST:PORT를 쓰면 돼요.
설정 파일에서 로딩하기
spark-submit은 properties 파일에서 기본 Spark 설정을 읽어 애플리케이션에 전달할 수 있어요. --properties-file 인자로 파일을 지정하고, 지정하지 않으면 기본적으로 SPARK_HOME의 conf/spark-defaults.conf를 읽어요. 추가로 --load-spark-defaults 플래그를 주면 --properties-file로 파일을 지정해도 conf/spark-defaults.conf까지 함께 읽도록 할 수 있어요 — 시스템 전체 기본값과 사용자/클러스터별 설정을 나눠 담고 싶을 때 유용하죠.
이렇게 기본 설정을 불러오면 spark-submit의 일부 플래그를 생략할 수 있어요. 예를 들어 spark.master 속성이 설정돼 있으면 --master 플래그를 빼도 안전해요. 우선순위를 정리하면, SparkConf에 명시적으로 설정한 값이 가장 높고, 그다음 spark-submit 플래그, 마지막으로 defaults 파일 순서예요. 설정이 어디서 온 건지 헷갈리면 --verbose 옵션으로 상세 디버그 정보를 출력해 볼 수 있어요.
고급 의존성 관리
spark-submit을 쓰면 애플리케이션 jar와 --jars 옵션으로 지정한 jar가 자동으로 클러스터에 전송돼요. --jars 뒤의 URL은 쉼표로 구분하고, 그 목록은 드라이버와 익스큐터의 클래스패스에 포함돼요. 단 --jars에서는 디렉터리 확장이 동작하지 않아요. jar를 배포하는 방식은 URL 스킴에 따라 달라져요.
file:— 절대 경로와file:/URI는 드라이버의 HTTP 파일 서버가 제공하고, 각 익스큐터가 드라이버 HTTP 서버에서 파일을 내려받아요.hdfs:,http:,https:,ftp:— URI에서 파일과 jar를 그대로 내려받아요.local:—local:/로 시작하는 URI는 각 워커 노드에 이미 존재하는 로컬 파일로 취급해요. 네트워크 I/O가 없어서 커다란 파일·jar를 각 워커에 밀어 넣거나 NFS, GlusterFS 등으로 공유할 때 잘 맞아요.
주목할 점은 그 jar와 파일들이 익스큐터 노드의 각 SparkContext 작업 디렉터리로 복사된다는 거예요. 시간이 지나면 공간을 꽤 차지하니 정리가 필요하죠. YARN은 자동으로 정리하고, 스탠드얼론은 spark.worker.cleanup.appDataTtl 속성으로 자동 정리를 설정할 수 있어요.
또한 --packages로 Maven 좌표를 쉼표로 나열해 다른 의존성을 포함시킬 수도 있어요. 이때 전이 의존성은 모두 자동 처리되고, 추가 저장소는 --repositories 플래그로 쉼표 구분해 추가할 수 있어요. (비밀번호 보호 저장소의 경우 https://user:password@host/...처럼 URI에 자격 증명을 넣을 수 있는데, 이 방식은 조심해서 쓰길 권해요.) 이 명령들은 pyspark, spark-shell, spark-submit 모두에서 Spark 패키지를 포함하는 데 쓸 수 있어요. Python에는 --py-files 옵션이 같은 역할을 해요.
더 알아보기
- Cluster Mode Overview - 스파크 공식 문서 — 분산 실행에 참여하는 컴포넌트와 모니터링·디버깅 방법
- Spark Configuration - 스파크 공식 문서