이번에 쓸 글은 spark자체를 커스터마이징 한다기보다 삽질과, bitnami spark helm chart를 구성하는 것들을 살펴보는데 중점을 둔 글입니다.
전에 쓴글 중에 https://qkqhxla1.tistory.com/1164?category=698045 에서 스파크에서도 airflow와 python 세팅이 동일해야 하는 이유를 적었었다.(spark의 python udf 사용 불가능) 최근에 회사에서 이슈가 있어서 k8s 서버를 통째로 옮겨가야 할 일이 생겼다. 랜쳐도 새로 설치했고, 내부의 App들을 옮기는 중인데 spark를 옮기던 도중 지금이 spark에 airflow와 같은 python 버전을 적용해야 할 적기라고 생각해서 세팅한다.
확인했다. 그런데 libcomponent.sh라는 스크립트에서 component_unpack을 사용해서 어떻게 작동을 하는것같다. libcomponent.sh 를 살펴보자.
#!/bin/bash
#
# Library for managing Bitnami components
# Constants
CACHE_ROOT="/tmp/bitnami/pkg/cache"
DOWNLOAD_URL="https://downloads.bitnami.com/files/stacksmith"
# Functions
########################
# Download and unpack a Bitnami package
# Globals:
# OS_NAME
# OS_ARCH
# OS_FLAVOUR
# Arguments:
# $1 - component's name
# $2 - component's version
# Returns:
# None
#########################
component_unpack() {
local name="${1:?name is required}"
local version="${2:?version is required}"
local base_name="${name}-${version}-${OS_NAME}-${OS_ARCH}-${OS_FLAVOUR}"
local package_sha256=""
local directory="/opt/bitnami"
# Validate arguments
shift 2
while [ "$#" -gt 0 ]; do
case "$1" in
-c|--checksum)
shift
package_sha256="${1:?missing package checksum}"
;;
*)
echo "Invalid command line flag $1" >&2
return 1
;;
esac
shift
done
echo "Downloading $base_name package"
if [ -f "${CACHE_ROOT}/${base_name}.tar.gz" ]; then
echo "${CACHE_ROOT}/${base_name}.tar.gz already exists, skipping download."
cp "${CACHE_ROOT}/${base_name}.tar.gz" .
rm "${CACHE_ROOT}/${base_name}.tar.gz"
if [ -f "${CACHE_ROOT}/${base_name}.tar.gz.sha256" ]; then
echo "Using the local sha256 from ${CACHE_ROOT}/${base_name}.tar.gz.sha256"
package_sha256="$(< "${CACHE_ROOT}/${base_name}.tar.gz.sha256")"
rm "${CACHE_ROOT}/${base_name}.tar.gz.sha256"
fi
else
curl --remote-name --silent "${DOWNLOAD_URL}/${base_name}.tar.gz"
fi
if [ -n "$package_sha256" ]; then
echo "Verifying package integrity"
echo "$package_sha256 ${base_name}.tar.gz" | sha256sum --check -
fi
tar --directory "${directory}" --extract --gunzip --file "${base_name}.tar.gz" --no-same-owner --strip-components=2 "${base_name}/files/"
rm "${base_name}.tar.gz"
}
소스를 적당히 해석해보면 component_unpack함수에서 name, version, base_name을 받는다. 함수 실행시 RUN . /opt/bitnami/scripts/libcomponent.sh && component_unpack "python" "3.6.12-10" --checksum cc3e62e05463929529214f65cb7922794c758a3db5ca29bf5c9a59fe66e95b80
로 실행하는데, name=python, version=3.6.12-10, base_name=python-3.6.12-10-linux-amd64-debian-10으로 세팅됨을 알 수 있다. base_name같은경우는 $OS_NAME같은 환경변수가 사용되는데, 다운받은 스파크 도커 이미지 bitnami/spark:3.0.1-debian-10-r139 로 들어가서 환경변수를 출력해서 확인할수 있었다. 변수가 세팅되고, 이후 package_sha256은cc3e62e05463929529214f65cb7922794c758a3db5ca29bf5c9a59fe66e95b80로 세팅될거다.
그리고 아래 파트에서 curl로 python을 받는데, 변수를 조합해서 스크립트를 만들어보면 아래와 같다.
spark의 Dockerfile에서 3.8.12-2로 변경하고 ./bitnami-docker-spark-3.0.1-debian-10-r129/3/debian-10/prebuildfs/opt/bitnami/.bitnami_components.json 에도 3.6이 있던데 이것까지 바꾸고 build스크립트를 넣은 후 spark 도커 이미지를 재빌드했다.
이제 helm으로 위의 values.yaml을 참조해서 spark app을 만들면, {private docker registry}에 있는 내가 push한 이미지를 다운받아서 spark master, worker를 만드는데, 내가 설정했던 파이썬 버전이 3.8이어야 한다.
성공했다. spark의 python버전은 바꿔줬으니, 이제는 requirements.txt를 받아서 설치해야 한다. airflow의 경우는 sidecar가 있어서 그거로 git 버전을 관리한다.
이런식인데, clone-repositories는 init container로써, worker가 실행되기 전에 실행되어 git의 모든 dag들을 가져온다. 이후 sync-repositories가 설정한 시간마다(위 그림에서는 60초) 변동사항을 가져오기 위해 pull을 한다. 어떻게 60초마다 pull을 할까? 원리가 궁금했는데 그냥 쉘 스크립트로 60초마다 sleep 하면서 git pull을 한다. airflow worker는 dag folder만 참조하여 작업을 하므로 sync-repositories가 깃을 가져와서 dag를 업데이트해주면 airflow에도 자동으로 업데이트가 된다.
spark도 airflow의 clone-repositories를 가져와서 동일하게 설정하면 될것같다.
대충 이런식으로 만들어보면 되지 않을까? 생각해서 만들었다. 그럼 이제 helm chart에서 initContainer관련 옵션을 찾아보자! https://artifacthub.io/packages/helm/bitnami/spark/5.1.2 ??????? 내가 사용하는 spark 3.0.2인데 container를 새로 만드는 옵션이 없다. 근데 최신 버전에는 있다(initContainers) : https://artifacthub.io/packages/helm/bitnami/spark/5.7.6 -_-..... 이왕 한김에 최신버전으로 해보기로 하고.. 기존에 있던 airflow 내부의 spark submit용 spark버전도 최신으로 바꾸고,(현재 airflow가 kubernetes executor가 아니라 celery executor를 사용중이라 spark3.0.2를 워커에 직접 설치하는방식으로 운영하고있었음.) spark 3.1.2로 이미지를 재빌드했다.
자.. 이제 spark helm에 master.initContainers과 worker.initContainers를 세팅해보자. 멀리왔는데 다시적자면initContainer를 세팅하는 이유는, spark 워커나 마스터가 올라왔을때 처음 한번 airflow git을 clone해서 requirements.txt를 가져오기 위함이다.
spark가 사용할 values.yml파일에 image이외에도 master와 worker에 airflow와 동일하게 /bitnami/python 경로로 airflow의 git을 clone해줬다. 그리고 master와 worker 둘다 airflow에서 clone-repositories용으로 사용하던 사이드 컨테이너를 그대로, command도 그대로 가져왔다.
이걸 받아다 컨테이너가 실행하는 run.sh를 찾아서 중간에 pip install 파트를 추가해준다.
.................
# Install custom python package if requirements.txt is present
if [[ -f "/bitnami/python/requirements.txt" ]]; then
pip install -r /bitnami/python/requirements.txt
fi
.................
.........................
# uid 1001 to root group
RUN echo "spark:x:1001:0:root:/root:/bin/bash" >> /etc/passwd
# for pip install.
RUN mkdir -m775 /.local
.........................
이미지를 재빌드하고, 이제 worker와 master를 새로 만들었을때 pip가 잘 실행되었음을 확인할 수 있다.