홈랩에서 Grafana를 계속 쓰다 보니 대시보드 53개뿐 아니라 데이터 소스, 즐겨찾기, 사용자 설정과 수정 이력도 쌓였습니다. DB를 바꾸면서 대시보드 JSON만 다시 가져오면 제가 쓰던 상태를 온전히 이어가기 어려웠습니다. 그래서 Grafana의 설정 DB를 SQLite에서 PostgreSQL로 옮기고, CloudNativePG로 관리하는 기존 DB 기반에 연결했습니다.
설정 파일에서 DB 종류를 바꾸는 일 자체는 어렵지 않았습니다. 시간을 들인 부분은 기존 데이터가 같은 의미로 옮겨졌는지 확인하고, DB가 준비된 뒤 Grafana가 시작하도록 만드는 것이었습니다. 2026년 9월 29일에 전환과 검증을 마쳤습니다.
Grafana DB에 들어 있는 것부터 구분하기
여기서 옮긴 DB는 Grafana의 설정 DB입니다. Prometheus가 수집하는 시계열 데이터를 이관한 작업은 아닙니다. Grafana에서 만든 대시보드, 사용자, 데이터 소스 설정과 변경 이력 등을 보존하는 작업입니다.
대시보드를 JSON으로 내보냈다가 다시 가져오는 방법도 있지만, 이번 목표는 기존 사용자의 즐겨찾기와 설정까지 유지하는 것이었습니다. 그래서 DB의 애플리케이션 데이터를 직접 옮기고 검증하는 경로를 택했습니다.
| 보존 대상 | 전환 후 확인한 수 |
|---|---|
| 대시보드 | 53개 |
| 데이터 소스 | 2개 |
| 사용자 | 1명 |
| 즐겨찾기 | 4개 |
| 대시보드 버전 기록 | 77개 |
같은 Grafana 버전으로 PostgreSQL 스키마 준비하기
SQLite에서 SQL을 덤프해 그대로 PostgreSQL에 넣는 방식은 사용하지 않았습니다. 두 DB는 자료형과 스키마 처리 방식이 다릅니다. Grafana가 관리하는 마이그레이션 기록도 단순히 원본 DB의 내용을 덮어쓰면 안 되는 부분입니다.
먼저 기존과 같은 Grafana 버전으로 PostgreSQL에 스키마를 만들었습니다. PostgreSQL에서 수행한 마이그레이션 기록은 그대로 두고, 그 밖의 애플리케이션 데이터를 복사했습니다. 이관 중에는 기존 Grafana의 쓰기를 멈춰 복사 대상이 계속 변하지 않도록 했습니다.
기존 Grafana의 쓰기 중단 · SQLite 백업
→ 같은 Grafana 버전으로 PostgreSQL 스키마 생성
→ PostgreSQL의 migration log 관련 테이블 3개 유지
→ 나머지 애플리케이션 테이블 81개 복사
→ 행 수와 내용 비교
→ 검증한 PostgreSQL로 운영 연결 전환자료형도 확인했습니다. SQLite에서 BLOB으로 읽힌 JSON은 UTF-8 문자열로 변환했고, 시간값은 PostgreSQL의 마이크로초 정밀도에 맞췄습니다. 겉으로 대시보드가 보이는 것만으로는 이런 차이를 확인하기 어렵습니다.
81개 테이블의 행 수와 내용 비교하기
애플리케이션 테이블 81개를 한 트랜잭션에서 복사하고 검증했습니다. 행 수가 같아도 값이 잘못 변환됐을 수 있으므로 실제 내용까지 비교했습니다. 사용자 정보와 암호화된 설정도 이 범위에 포함했습니다.
DB 준비와 복사 검증은 클러스터 안에서 수행했습니다. 이후 별도로 보관한 SQLite 백업은 전환 시점의 복원용입니다. 이 백업과 전환 후 PostgreSQL에 쌓이는 데이터를 구분해 두는 것도 필요했습니다.
암호화된 데이터 소스 설정을 옮길 때는 Grafana의 암호화 설정도 함께 고려해야 합니다. DB 파일만 보존하고 키를 바꿔 버리면 기존 설정을 읽는 데 문제가 생길 수 있습니다. 관련 설정은 Grafana의 secret_key 설명을 참고할 수 있습니다.
DB 포트가 열린 것보다 실제 쿼리 성공을 기다리기
Grafana 앞에는 DB 준비를 기다리는 init container를 두었습니다. TCP 연결만 확인하는 대신, TLS 인증서 검증과 DB 인증을 거쳐 SELECT 1이 성공해야 Grafana를 시작하도록 했습니다.
Grafana Pod 시작
→ init container에서 DB 연결
→ TLS verify-full과 사용자 인증
→ SELECT 1 성공
→ Grafana 시작verify-full을 사용하므로 서버 인증서와 접속 이름이 맞아야 합니다. CA는 공개 인증서만 ConfigMap으로 제공하고, DB 비밀번호는 namespace별 SealedSecret으로 관리했습니다. CA를 교체할 때는 이 ConfigMap도 함께 갱신해야 합니다.
Grafana Pod의 컨테이너 3개가 모두 Ready인 것을 확인한 뒤, /api/health에서도 database: ok를 확인했습니다. 기존 PVC는 플러그인 저장용으로 계속 사용했습니다.
복제 구성과 유지보수 방식 정리하기
공유 PostgreSQL 클러스터 두 개는 각각 primary 하나와 replica 두 개로 구성했습니다. 서로 다른 세 노드에 분산했고, 메모리가 작은 노드는 배치에서 제외했습니다. 최종 확인 시 두 클러스터 모두 3/3 Ready였으며, 네 standby의 복제 상태는 streaming, replay lag는 0 bytes였습니다.
| 클러스터 | 인스턴스와 저장소 | 전환 당시 확인 |
|---|---|---|
| 운영 데이터를 담는 main | primary 1 + replica 2, 20Gi PVC 3개 | 3/3 Ready |
| 재생성 가능한 데이터를 담는 vector | primary 1 + replica 2, 10Gi PVC 3개 | 3/3 Ready |
| 전체 저장소·복제 | 인스턴스별 PVC 6개 | 모두 Bound, standby 4개의 replay lag 0 bytes |
이 수치와 용량은 전환 당시 구성입니다. 복제본은 서로 다른 노드에 배치했지만, 같은 잘못된 변경이 복제될 수 있으므로 복제와 백업의 역할은 따로 봤습니다.
복제본 수만 늘려 놓고 유지보수 때 primary를 그대로 재시작하면 애플리케이션의 연결이 끊길 수 있습니다. 실제 설정 작업 중에도 primary 재시작으로 DB를 사용하는 서비스가 잠시 영향을 받았습니다. 이후 rolling update에서는 준비된 복제본으로 primary를 옮기는 primaryUpdateMethod: switchover를 사용하도록 설정했습니다. 동작은 CloudNativePG rolling update 문서에 설명돼 있습니다.
이 구성의 복제는 비동기입니다. 복제본이 정상이고 확인 시 지연이 없었다는 결과와, 장애 때 최신 트랜잭션을 반드시 보존한다는 보장은 다릅니다. 이번 작업에서는 노드를 강제로 중단하는 failover 시험까지 수행하지 않았습니다.
SQLite 백업과 전환 이후 백업 나누기
전환 전 SQLite 스냅샷은 별도 백업 디렉터리로 옮겼습니다. 원본과 checksum이 같은지, SQLite의 integrity_check가 정상인지 확인한 뒤 PVC에 남아 있던 두 SQLite 파일을 정리했습니다. PVC와 플러그인은 유지했고, 정리 후에도 PostgreSQL 연결이 정상임을 확인했습니다.
이 SQLite 파일은 전환 시점으로 돌아가기 위한 백업입니다. 이후에 수정한 대시보드와 설정은 PostgreSQL에 저장됩니다. 운영 데이터를 담은 main 클러스터의 기존 WAL·S3 백업은 유지했고, 재생성 가능한 데이터를 담는 vector 클러스터에는 기존 운영 원칙대로 별도 백업을 추가하지 않았습니다.
| 최종 검증 | 결과 |
|---|---|
| 애플리케이션 데이터 | 81개 테이블의 행 수·내용 비교 통과 |
| Grafana | Pod 내부 컨테이너 3/3 Ready, /api/health의 database: ok |
| Fleet Bundle·BundleDeployment | 56/56 Ready |
| Fleet GitRepo | 2/2 Ready |
설정을 Git에 반영하고 Fleet 적용까지 확인했습니다. 대시보드가 보이는지, 즐겨찾기가 남았는지, DB 내용이 같은지, 다음 기동에서도 올바른 DB를 기다리는지까지 확인한 뒤 이관을 마무리했습니다.

답글 남기기