한눈에 보기
- 문제: 증권사 실거래 API 키를 코드와 문서에 평문으로 둘 수 없었다. 그리고 관측 스택을 배포하고도 47시간 동안 아무 화면도 보지 못했다.
- 선택: HashiCorp Vault(Prod 모드) + Vault Agent/AppRole 패턴. 관측은 신규 인프라 추가 전에 이미 있는 신호부터 쓰는 3단계 롤아웃.
- 결과: 시크릿 rotation 시 15~25초 내 프로세스 자동 재기동을 실측으로 확인. 관측은 “수집은 되는데 계기판이 없는” 상태였음을 진단.
- 핵심 교훈: 시크릿 관리의 완성은 저장이 아니라 갱신 경로다. 관측의 완성은 배포가 아니라 볼 화면이다.
1부 — 시크릿: 평문 문서에서 Vault까지
초기에는 MinIO 자격증명이 아키텍처 문서에 평문으로 적혀 있었다. 읽기 전용 스토리지 계정 수준에서는 넘어갈 수 있어도, 증권사 실거래 API 키를 연동하는 순간 이 방식은 끝이다. 주문을 낼 수 있는 자격증명이다.
왜 Vault였나
가장 가벼운 대안은 GitHub Secrets였지만, 이건 CI/CD 실행 시점에 주입되는 모델이다. 이 플랫폼의 소비자는 로컬에서 24/7 도는 Dagster와 컨슈머들 — 배포 시점이 아니라 런타임에 키가 필요하다. GitHub Secrets로 하려면 우회 스크립트가 필요해지고, rotation·TTL·감사 로그·세밀한 ACL도 없다. 상시 구동 앱에는 시크릿 “저장소”가 아니라 시크릿 “서버”가 필요했고, 그게 Vault다.
Vault 안에서도 갈림길이 있다. Dev 모드는 명령 한 줄로 뜨지만 시크릿이 메모리에만 있어 재시작 시 전부 소실되고 root 토큰이 콘솔에 노출된다. 로컬이라도 실거래 키를 태우는 순간 Dev 모드는 금지라고 판단했고, Prod 모드 + Raft 스토리지로 구성했다. 1인 운영이라 unseal 키는 shares=1/threshold=1로 단순화했다(팀 환경이면 5/3이 맞다).
저장보다 어려운 건 전달이다
각 서비스가 Vault에서 키를 받는 방식으로는 Vault Agent + AppRole 패턴을 썼다. 컨테이너 기동 시 Agent가 AppRole로 인증하고, 템플릿으로 시크릿을 렌더한 뒤 exec로 실제 프로세스를 띄운다. 시크릿이 바뀌면 Agent가 프로세스를 자동 재시작한다.
여기에 함정이 하나 있었다. Vault KV v2는 변경을 push해주지 않는다. 정적 시크릿은 폴링인데 기본 간격이 5분이라, 키를 rotate해도 최대 5분간 옛 키로 도는 창이 생긴다. render interval을 15초로 명시하고, 실제로 더미 값을 put해 15~25초 안에 프로세스가 재기동하는 것까지 테스트했다(2026-07-12). rotation은 설정이 아니라 실측으로 확인해야 하는 동작이다.
운영 원칙도 하나 세웠다. AppRole 자격증명 발급은 스크립트가 터미널 출력 없이 파일로 직행시키고, AI 코딩 세션은 그 파일을 절대 읽지 않는다. LLM에게 코드베이스를 열어주는 환경에서는 “시크릿이 어디에 존재할 수 있는가”까지 위협 모델에 넣어야 한다.
남은 부채도 분명하다. 컨테이너 재시작마다 수동 unseal이 필요하고, TLS는 LAN 내부 한정을 조건으로 미활성이다. 외부 노출 전 필수 과제로 적어뒀다. 여담으로, 브로커 연동 자체에서도 한 번 회군했다 — 대신증권은 REST API 미제공이 확인돼 만들던 연동 일체를 드랍하고 토스로 대체했다.
2부 — 관측: 파이프는 있는데 계기판이 없었다
Prometheus, Grafana, Loki, Promtail, node-exporter, cAdvisor. 관측 스택은 이미 배포돼 있었고 uptime 47시간을 찍고 있었다. 그런데 “시스템 리소스와 로그가 대시보드에서 안 보인다”는 게 증상이었다.
조사해 보니 수집 실패가 아니었다. Promtail은 컨테이너 24개의 로그를 걷고 있었고 Prometheus도 돌고 있었다. 원인은 허무하게도 프로비저닝된 대시보드가 0개 — 파이프는 깔려 있고 밸브도 열려 있는데 계기판이 안 걸려 있었다. 배포 완료와 관측 가능은 다른 상태다.
그 밑에서 비자명한 함정도 몇 개 나왔다.
- cAdvisor가 cgroup v2 + systemd driver 환경에서 컨테이너별 메트릭을 못 걷고 있었다. 메모리 메트릭 106개 시리즈가 전부 호스트 것이었고 컨테이너 이름 라벨은 0회 등장 — 컨테이너 모니터링이 사실상 무의미한 상태.
- dbt의 severity=warn 테스트가 Dagster 백엔드에는 FAILED로 저장된다. warn/error 구분이 JSON 안에만 있어서, DB만 보면 경고가 실패로 보이는 오탐이 생긴다.
- Dagster 로그에 ANSI 컬러코드가 그대로 저장돼 로그 레벨 정규식이 오작동하고 있었다(WARNING 다수 누락을 실측).
대응은 3단계 롤아웃으로 잡았다. 1단계는 신규 인프라 0으로 대시보드 프로비저닝과 cAdvisor 마운트 수정 — 사용자 증상을 직접 해소한다. 2단계는 Grafana에 Postgres datasource 하나만 추가 — Dagster OSS는 Prometheus 엔드포인트가 없지만 모든 실행 상태가 이미 Postgres에 있으므로, exporter를 새로 만들 게 아니라 있는 신호를 SQL로 읽으면 된다. 3단계에서야 Kafka exporter 같은 신규 컴포넌트를 논한다. exporter를 사기 전에, 이미 데이터베이스에 있는 신호부터 쓴다.
정리
시크릿과 관측은 기능이 아니라 상태다. “Vault를 도입했다”는 rotation이 실측될 때 완성되고, “관측 스택을 배포했다”는 볼 화면이 생길 때 완성된다. 둘 다 그 마지막 구간에서 한 번씩 걸려 넘어졌고, 걸려 넘어진 지점이 정확히 문서와 체크리스트에 남을 가치가 있는 지점이었다.
관련 글: 전체 구조는 오버뷰, 쿼리 엔진의 원격 분리는 사라지는 산출물 - dbt와 Trino Iceberg 레이크하우스.