Navisafe/Infrastructure

미니 PC 서버 Kubernetes 배포 준비 - Namespace · Secret · Spark Operator 구성

jjaehyeok 2026. 5. 12. 15:17

1. 문제 배경

k3s 기반 Kubernetes 클러스터 구성을 완료한 이후 바로 서비스를 배포하려 했지만, Kubernetes는 단순히 컨테이너를 실행하는 구조가 아니라 이미지, 설정, 권한, 저장소가 분리된 상태에서 동작한다.

이 상태에서 바로 서비스를 배포할 경우 다음과 같은 문제가 발생할 수 있었다.

  • 클러스터에서 이미지를 가져오지 못하는 문제
  • 환경 변수와 설정이 코드에 종속되는 구조
  • Pod가 외부 리소스에 접근하지 못하는 문제
  • Pod 재시작 시 데이터가 유실되는 문제
  • 서비스 간 리소스 충돌 문제

따라서 실제 NaviSafe 서비스를 배포하기 전에, Kubernetes 실행에 필요한 공통 구성 요소를 먼저 준비했다.


2. 목표

  • Kubernetes 실행에 필요한 공통 리소스 구성
  • 설정과 코드 분리
  • 서비스별 namespace 분리
  • 외부 리소스 연결 정보 분리
  • Spark 작업 실행 환경 구성
  • 데이터 유지 가능한 구조 준비

3. Docker Registry 기반 이미지 관리

k3s 환경에서는 kind처럼 로컬 이미지를 직접 주입하는 방식(kind load)을 사용할 수 없기 때문에, 클러스터가 Docker Registry에서 이미지를 직접 가져오는 방식으로 구성했다. 이를 위해 Deployment 및 SparkApplication YAML에서 이미지 경로를 DockerHub 기준으로 지정했다.


4. Helm 설치

Spark Operator를 Kubernetes에 설치하기 위해 Helm을 사용했다.

Windows 환경에서는 실행 파일을 직접 설치했지만, Ubuntu 서버에서는 공식 설치 스크립트를 사용해 구성했다.

4-1) Helm 설치

curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

4-2) 설치 확인

helm version

정상적으로 설치된 경우 Helm 버전 정보가 출력된다.


5. Spark Operator 설치

Spark 작업을 Kubernetes 리소스로 관리하기 위해 Spark Operator를 설치했다. Spark Operator를 사용하면 Spark 작업을 단순 Pod 실행이 아니라 Kubernetes 리소스 형태로 선언적으로 관리할 수 있다.

5-1) Helm 저장소 등록

helm repo add spark-operator https://kubeflow.github.io/spark-operator
helm repo update

5-2)Spark Operator 설치

helm install spark-operator spark-operator/spark-operator \
--namespace spark-operator \
--create-namespace

5-3) 왜 Operator를 사용하는가

Operator를 사용하면

  • SparkApplication 리소스로 작업 정의 가능
  • Driver / Executor 자동 관리
  • 재시작 및 상태 관리 가능
  • Spark 작업을 Kubernetes 방식으로 운영 가능

구조를 만들 수 있었다.


6. Namespace 구성

서비스 역할별로 리소스를 분리하기 위해 namespace를 구성했다.

kubectl create namespace airflow


7. Secret 구성

외부 서비스 연결 정보는 코드에 직접 포함하지 않고 Kubernetes Secret으로 분리했다.

kubectl create secret generic navisafe-producer-secret \
  --from-literal=KAFKA_BOOTSTRAP=<kafka-bootstrap-server> \
  --from-literal=REDIS_HOST=<redis-host> \
  --from-literal=MYSQL_HOST=<mysql-host> \
  --from-literal=MYSQL_USER=<username> \
  --from-literal=MYSQL_PASSWORD=<password>
 

※ 실제 계정 정보는 보안상 제외


8. ConfigMap 구성

초기 설정 파일은 ConfigMap으로 분리했다.

8-1) SQL 스크립트 ConfigMap

kubectl create configmap mysql-init-script --from-file=init.sql

 

8-2) Airflow Pod Template ConfigMap

kubectl create configmap airflow-pod-template \
--from-file=airflow-pod-template.yaml \
-n airflow

8-3) Spark Streaming 코드 ConfigMap

Spark Streaming 코드 역시 이미지 내부에 직접 포함하지 않고 ConfigMap으로 분리했다

kubectl create configmap spark-code --from-file=preprocessing/outbreak_streaming.py --from-file=preprocessing/emergency_streaming.py --from-file=utils/ -o yaml --dry-run=client | kubectl apply -f -

8-4) 왜 ConfigMap을 사용하는가

설정 파일을 이미지 내부에 포함할 경우 설정 변경 시 이미지를 다시 빌드해야 한다.

반면 ConfigMap을 사용하면

  • 설정만 별도 수정 가능
  • 운영 환경별 설정 분리 가능
  • 코드와 설정 분리 가능

구조를 만들 수 있었다.


9. ServiceAccount 구성

Spark 작업이 Kubernetes 리소스에 접근할 수 있도록 ServiceAccount를 생성했다.

kubectl create serviceaccount spark

9-1) 왜 필요한가

Spark Driver Pod는 Kubernetes API와 통신하면서

  • Executor 생성
  • Pod 상태 조회
  • 작업 상태 관리

등 작업을 수행한다. 따라서 Kubernetes 리소스 접근 권한이 필요했다.


10. StorageClass 및 PVC 확인

k3s에서는 기본적으로 local-path StorageClass를 제공한다.

kubectl get storageclass
 

확인 결과:

10-1) 왜 중요한가

NaviSafe에서는 다음 서비스들이 Persistent Volume을 사용한다.

  • PostgreSQL
  • Kafka
  • Redis 일부 구성
  • Airflow 로그 저장

따라서 PVC가 정상적으로 생성 가능한 환경이 필요했다. k3s는 기본적으로 local-path-provisioner를 포함하고 있기 때문에
별도 Storage Provisioner 설치 없이 로컬 디스크 기반 PVC를 사용할 수 있었다.


11. 마치며

이번 단계에서는 실제 서비스를 바로 배포하기보다, Kubernetes 운영에 필요한 공통 구성 요소를 먼저 준비했다.

특히 서버 환경에서는 단순 컨테이너 실행보다

  • 이미지 관리 방식
  • 설정 분리
  • 권한 관리
  • 저장소 구성
  • 서비스 분리

가 먼저 정리되어야 안정적으로 운영할 수 있었다.


다음 글

다음 단계에서는 실제 NaviSafe 서비스를 k3s 클러스터에 배포할 예정이다.

이를 위해

  • PostgreSQL / Redis / Kafka 배포
  • Spark Streaming 실행
  • Airflow 연결
  • Backend / Frontend 배포

과정을 순차적으로 진행하며, 실제 서버 환경에서 데이터 파이프라인이 정상적으로 동작하는지 검증할 예정이다.