Trino 를 Kubernetes 에 배포하기 — Helm, 오토스케일링, Graceful Shutdown
공식 Helm 차트로 Trino 클러스터를 Kubernetes 에 올리는 실전 가이드. 코디네이터/워커 구성, 카탈로그 주입, 리소스·HPA 오토스케일링, graceful shutdown, FTE 를 위한 exchange manager, 스팟 인스턴스 운영까지 정리합니다.
Trino 는 코디네이터 1대와 워커 N대로 구성되는 stateless 에 가까운 분산 엔진입니다. 이 특성은 Kubernetes 와 잘 맞습니다 — 워커를 Pod 로 늘리고 줄이기 쉽고, 부하에 따라 오토스케일링할 수 있으며, 스팟 인스턴스로 비용을 낮출 수 있거든요. 다만 "쿼리 도중 워커가 사라지면?" 같은 분산 시스템 특유의 문제도 함께 다뤄야 하죠.
이 글은 공식 Helm 차트를 기준으로 Trino 를 Kubernetes 에 안정적으로 배포하는 방법을, 오토스케일링과 graceful shutdown 같은 운영 관점까지 포함해 정리합니다.
이 글에서 배우는 것
- 공식 Helm 차트로 Trino 클러스터를 Kubernetes 에 올리는 방법
- 코디네이터/워커 리소스 설정과 JVM 힙 헤드룸 계산법
- HPA와 KEDA를 활용한 워커 오토스케일링 전략
- 쿼리를 중단시키지 않고 워커를 내리는 graceful shutdown 설정
- 스팟 인스턴스를 안심하고 쓸 수 있는 Fault-tolerant Execution(FTE) 구성
1. 아키텍처 — Kubernetes 위의 Trino
Trino 의 각 구성 요소가 Kubernetes 리소스에 어떻게 대응되는지 살펴봅시다.
| Trino 개념 | Kubernetes 리소스 |
|---|---|
| 코디네이터 | Deployment (replica 1) + Service |
| 워커 | Deployment (replica N) |
| 노드 디스커버리 | 코디네이터 Service DNS |
| 카탈로그/설정 | ConfigMap |
| 비밀번호·시크릿 | Secret |
| 오토스케일링 | HPA (또는 KEDA) |
| FTE 스풀 저장소 | 외부 오브젝트 스토리지 |
2. 공식 Helm 차트로 시작
Trino 는 공식 Helm 차트(trinodb/charts)를 제공하므로, 몇 가지 명령만으로 클러스터를 바로 띄울 수 있습니다.
helm repo add trino https://trinodb.github.io/charts
helm repo update
# 기본 설치
helm install my-trino trino/trino --namespace trino --create-namespace기본값으로도 코디네이터 1 + 워커 2가 뜨지만, 프로덕션에서는 values.yaml 을 직접 작성해서 세밀하게 조정하면 됩니다.
3. values.yaml — 핵심 구성
image:
tag: "version-pin" # 항상 버전을 고정. latest 금지
server:
workers: 4
config:
query:
maxMemoryPerNode: "12GB"
# 공유 시크릿(내부 통신) — 실제로는 Secret 으로 주입
coordinatorExtraConfig: |
query.max-memory=80GB
exchangeManager:
name: filesystem # FTE 용. 아래 7장 참고
coordinator:
jvm:
maxHeapSize: "16G"
resources:
requests:
cpu: 4
memory: 18Gi
limits:
memory: 18Gi
worker:
jvm:
maxHeapSize: "24G"
resources:
requests:
cpu: 8
memory: 28Gi
limits:
memory: 28Gi
# 워커가 쿼리 중 죽지 않도록 graceful shutdown (아래 6장)
terminationGracePeriodSeconds: 120
additionalCatalogs:
iceberg: |
connector.name=iceberg
iceberg.catalog.type=rest
iceberg.rest-catalog.uri=http://iceberg-rest:8181
fs.native-s3.enabled=true
s3.endpoint=https://s3.example.com
postgresql: |
connector.name=postgresql
connection-url=jdbc:postgresql://pg:5432/crm
connection-user=trino
connection-password=${ENV:PG_PASSWORD}JVM 힙과 컨테이너 메모리의 관계
처음 설정할 때 가장 자주 실수하는 부분이 JVM 힙을 컨테이너 메모리 limit 과 같게 잡는 것입니다. Trino 는 힙 외에도 네이티브 메모리·메타스페이스·OS 버퍼를 함께 사용하기 때문에, 컨테이너가 OOMKill 되지 않도록 반드시 여유를 둬야 합니다.
컨테이너 memory limit ≈ JVM maxHeapSize + 헤드룸(보통 20~30%)
예) limit 28Gi → maxHeapSize 24G (약 4Gi 헤드룸)추가로 query.max-memory-per-node 는 JVM 힙보다 작게 설정해야 하며, memory.heap-headroom-per-node 로 힙 안에 예약 영역을 따로 남겨 두는 것도 잊지 마세요.
한 문장으로: 컨테이너 limit = JVM 힙 + 헤드룸(20~30%) 으로 잡아 OOMKill 을 예방하는 것이 메모리 설정의 핵심입니다.
4. 카탈로그와 시크릿 주입
카탈로그 설정은 ConfigMap(위 additionalCatalogs)으로, 비밀번호 같은 민감 정보는 Secret 으로 분리해서 관리하면 됩니다.
# 워커/코디네이터에 환경변수로 Secret 주입 (values.yaml)
worker:
envFrom:
- secretRef:
name: trino-secrets
coordinator:
envFrom:
- secretRef:
name: trino-secretskubectl create secret generic trino-secrets -n trino \
--from-literal=PG_PASSWORD='***' \
--from-literal=LDAP_BIND_PASSWORD='***'카탈로그 properties 안에서 ${ENV:PG_PASSWORD} 처럼 환경변수를 참조하면, Git 에 올라가는 ConfigMap 에는 실제 비밀번호가 남지 않아 안전하게 관리할 수 있습니다.
5. 오토스케일링 — 워커를 부하에 맞춰
워커는 stateless 에 가깝기 때문에 수평 확장이 자연스럽습니다. 방법은 크게 두 가지가 있는데, 하나씩 살펴봅시다.
5.1 HPA (CPU 기반)
# values.yaml
worker:
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 20
targetCPUUtilizationPercentage: 70CPU 사용률 70% 를 넘으면 워커 Pod 를 자동으로 늘려 줍니다. 설정이 간단하다는 장점이 있지만 한계도 있습니다 — Trino 쿼리는 버스트성이 강해서 CPU 가 순간적으로 치솟았다 가라앉는 사이에 스케일이 따라오지 못하는 경우가 있거든요.
5.2 KEDA (쿼리/스케줄 기반)
더 정교한 스케일링을 원한다면 KEDA 를 사용하면 됩니다. 대기 중인 쿼리 수나 큐 길이를 직접 메트릭으로 삼거나, 업무 시간대에만 워커를 늘리는 스케줄 기반 스케일링도 구성할 수 있습니다.
# KEDA ScaledObject 예시 (개념)
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus:9090
query: trino_queued_queries # 대기 쿼리 수
threshold: "5"
- type: cron # 업무시간 최소 보장
metadata:
start: "0 8 * * 1-5"
end: "0 20 * * 1-5"
desiredReplicas: "8"스케일 아웃은 쉽지만, 스케일 인(축소)이 위험합니다. 줄어드는 워커가 쿼리를 실행 중이면 그 쿼리가 실패하기 때문입니다. 그래서 graceful shutdown 이 반드시 필요합니다.
6. Graceful Shutdown — 쿼리를 죽이지 않고 워커 내리기
Trino 워커는 graceful shutdown 을 지원합니다. 종료 신호를 받으면 새 태스크 수신을 멈추고(SHUTTING_DOWN 상태), 진행 중인 작업이 끝날 때까지 기다린 뒤 정상 종료하는 방식이죠.
Helm 차트의 워커 Pod 는 preStop 훅과 terminationGracePeriodSeconds 로 이를 구현합니다. 여러분이 챙겨야 할 핵심은 유예 시간을 충분히 주는 것입니다.
worker:
# 진행 중 쿼리가 마무리될 시간. 워크로드의 최대 쿼리 시간보다 길게
terminationGracePeriodSeconds: 300
gracefulShutdown:
enabled: true
gracePeriodSeconds: 120종료가 어떤 순서로 이뤄지는지 흐름으로 정리하면 다음과 같습니다.
유예 시간이 쿼리 실행 시간보다 짧으면 K8s 가 SIGKILL 로 강제 종료해 버려 쿼리가 실패합니다. 장시간 ETL 워크로드가 있다면 유예 시간을 넉넉히 잡거나, 바로 아래 7장의 FTE 를 함께 활용하면 됩니다.
7. Fault-tolerant Execution — 스팟 인스턴스를 안심하고 쓰려면
Graceful shutdown 은 "예고된 종료"를 안전하게 처리해 주지만, 스팟 인스턴스 회수처럼 갑작스러운 워커 소실은 막지 못합니다. 이때 필요한 것이 바로 Fault-tolerant Execution(FTE) 입니다. 중간 결과를 외부 스토리지(exchange manager)에 스풀해 두었다가, 워커가 갑자기 사라지더라도 해당 태스크만 다른 워커에서 재시도할 수 있습니다.
한 문장으로: FTE 는 워커가 예고 없이 사라져도 쿼리를 살려 내는 안전망입니다.
# values.yaml
server:
config:
retryPolicy: "TASK" # 또는 QUERY
exchangeManager:
name: filesystem
baseDir: "s3://trino-exchange/spool"# exchange-manager.properties 로 생성됨
exchange-manager.name=filesystem
exchange.base-directories=s3://trino-exchange/spool| retryPolicy | 동작 | 적합 |
|---|---|---|
QUERY | 쿼리 전체를 재시도 | 짧은 대화형 쿼리 |
TASK | 실패한 태스크만 재시도 | 장시간 ETL 배치 |
FTE 를 켜면 워커를 100% 스팟으로 운영하면서도 배치 안정성을 확보할 수 있습니다. 비용 절감 효과가 상당하죠. 다만 스풀 I/O 오버헤드가 추가되므로, 초저지연 대화형 쿼리 전용 클러스터에는 끄는 편이 낫습니다.
8. 스케줄링 — 노드 배치와 안정성
# 코디네이터는 안정적인 온디맨드 노드에, 워커는 스팟에
coordinator:
nodeSelector:
node-pool: on-demand
worker:
nodeSelector:
node-pool: spot
tolerations:
- key: "spot"
operator: "Equal"
value: "true"
effect: "NoSchedule"
# 워커를 여러 노드에 분산해 동시 소실 위험 분산
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway노드 배치를 구성할 때 여러분이 반드시 지켜야 할 원칙이 있습니다.
- 코디네이터는 스팟에 두지 않습니다. 코디네이터가 죽으면 클러스터 전체가 멈추는 SPOF 가 되거든요. 온디맨드 노드와 충분한 리소스를 반드시 보장하세요.
- 워커는 스팟 + FTE 조합이 비용 효율 면에서 가장 좋습니다.
topologySpreadConstraints로 워커를 여러 노드/가용 영역에 분산해 두면, 한 번에 다수가 사라지는 최악의 상황을 완화할 수 있습니다.
9. 관측성 — Prometheus 와 헬스체크
Trino 는 JMX 메트릭을 노출하므로, JMX exporter 사이드카나 내장 메트릭 엔드포인트를 통해 Prometheus 에서 수집할 수 있습니다.
serviceMonitor: # Prometheus Operator 사용 시
enabled: true
labels:
release: prometheus특히 주목해야 할 모니터링 지표는 다음과 같습니다.
| 지표 | 의미 |
|---|---|
trino_running_queries / trino_queued_queries | 실행/대기 쿼리 수 (스케일 트리거) |
| 클러스터 메모리 사용률 | OOM 위험 |
| 워커 노드 수 | 디스커버리 정상 여부 |
| 실패 쿼리율 | 안정성 |
| GC pause | JVM 힙 압박 |
코디네이터의 /v1/info 엔드포인트로 readiness 를 확인하고, starting 상태인 노드에는 트래픽이 들어오지 않도록 readiness probe 도 꼭 구성해 두세요.
10. 배포 체크리스트
- 이미지 태그 고정(
latest금지) - 코디네이터는 온디맨드, 워커는 스팟 노드풀 분리
- JVM 힙 < 컨테이너 limit (헤드룸 20~30%)
- 카탈로그는 ConfigMap, 비밀은 Secret 으로 분리
- HPA/KEDA 오토스케일링 구성
- graceful shutdown 유예 시간 ≥ 최대 쿼리 시간
- 장시간 배치는 FTE(retryPolicy=TASK) + exchange manager
- 내부 통신 공유 시크릿 + TLS
- Prometheus 메트릭·readiness probe 구성
- PodDisruptionBudget 으로 동시 축소 제한
11. 정리
| 운영 과제 | Kubernetes 에서의 해법 |
|---|---|
| 부하 변동 | HPA(CPU) 또는 KEDA(쿼리/스케줄) |
| 예고된 워커 종료 | graceful shutdown + 충분한 유예 시간 |
| 갑작스러운 워커 소실(스팟) | FTE(retryPolicy=TASK) + exchange manager |
| 코디네이터 SPOF | 온디맨드 노드 + 리소스 보장 |
| 비밀 관리 | ConfigMap/Secret 분리, 환경변수 치환 |
| 동시 다수 소실 | topologySpread + PodDisruptionBudget |
Trino 의 stateless 한 구조는 Kubernetes 와 궁합이 좋지만, "분산 쿼리 도중 워커가 사라질 수 있다"는 현실을 graceful shutdown 과 FTE 두 장치로 다루는 것이 프로덕션 안정성의 핵심입니다. 여기에 스팟 인스턴스 + FTE 조합을 더하면, 안정성을 지키면서도 컴퓨트 비용을 크게 낮출 수 있습니다.
마치며 — 핵심 요약
- Helm 차트 한 줄로 클러스터를 바로 띄울 수 있지만, 프로덕션에서는
values.yaml을 직접 작성해 리소스·카탈로그·보안 설정을 세밀하게 조정해야 합니다. - JVM 힙은 컨테이너 limit 의 70~80% 이하로 잡아 OOMKill 을 예방하세요. 남은 여유는 네이티브 메모리와 OS 버퍼가 사용합니다.
- 오토스케일링은 HPA(CPU 기반)로 시작하고, 쿼리 수 기반 또는 스케줄 기반 제어가 필요하면 KEDA 로 전환하면 됩니다.
- Graceful shutdown 의 유예 시간은 클러스터에서 가장 오래 걸리는 쿼리보다 길게 잡아야 스케일 인 시 쿼리 실패를 막을 수 있습니다.
- FTE(retryPolicy=TASK) 를 켜면 스팟 인스턴스에서 워커가 갑자기 사라져도 해당 태스크만 재시도하므로, 장시간 ETL 배치를 안심하고 운영할 수 있습니다.
- 코디네이터는 반드시 온디맨드 노드에, 워커는 스팟 + FTE 조합에 배치하는 것이 비용과 안정성을 동시에 잡는 가장 검증된 패턴입니다.
지금 당장 완벽한 설정을 갖추지 않아도 괜찮습니다. 기본 Helm 설치로 시작한 뒤 모니터링 지표를 보면서 하나씩 다듬어 가면, 여러분만의 안정적인 Trino on Kubernetes 클러스터를 만들 수 있습니다.
이 글은 공식 Trino Helm 차트와 Trino 440번대 기준으로 작성되었습니다. Kubernetes 위에서의 Trino 배포·오토스케일링·비용 최적화가 필요하시면 언제든 문의해 주세요.
— Data Dynamics 엔지니어링 팀