팀 프로젝트 festabook에서 학습한 내용을 정리한 글입니다.
💭 들어가며
이전 글들을 통해 Grafana Observability 스택 구축을 마무리했다. 본 글에서는 Grafana 대시보드 구성과 Observability 데이터(메트릭, 로그, 트레이스) 백업을 위한 설정을 정리한다.
✅ 백업 대상
백업 대상은 크게 두 가지다.
- Grafana 대시보드
- JSON 형태로 저장되며 용량이 작음
- Observability 데이터
- Prometheus 메트릭 데이터
- TSDB 블록 단위로 저장되어 데이터 용량이 큼
- Object Storage를 활용한 장기 보관 백업 필요
- Loki 로그 데이터
- OCI Object Storage 직접 연동
- Tempo 트레이스 데이터
- OCI Object Storage 직접 연동
- Prometheus 메트릭 데이터
✅ 사전 준비
Grafana Server를 Oracle Cloud Infrastructure(OCI)에 구축하기 전 필요한 사전 작업을 정리한다. 1편에서 언급했듯이, 프리티어 환경에서도 비교적 여유 있는 서버 자원을 사용할 수 있다는 점을 활용해 Grafana Server를 OCI에 구성했다. AWS에 비해 상대적으로 생소한 OCI 환경에서의 설정 과정을 정리하여, 이후 백업 및 스토리지 연동 과정에 대한 이해를 돕기 위해 정리했다.
▶ OCI Object Storage 버킷 생성
Grafana 스택에서 사용하는 데이터(대시보드, Metric, Log, Trace)를 백업하기 위해 Object Storage 버킷을 생성한다.

- OCI Console → Object Storage → Buckets → 버킷 생성

- Object Storage 버킷은 총 4개를 생성했다.
- Bucket Name
- Grafana 대시보드 백업: grafana-backup
- Metric 데이터 백업: mimir-backup
- Log 데이터 백업: loki-backup
- Trace 데이터 백업: tempo-backup
- Storage Tier: Standard
- Access: Private
- Encryption: OCI-managed (기본)
- Bucket Name
▶ Grafana Server에 OCI CLI 설치
Object Storage와의 연동 및 자동화를 위해 Grafana Server에 OCI CLI를 설치한다.
설치 과정은 Oracle 공식 문서를 기준으로 진행했다.
- Oracle 공식 문서
🔽 설치
bash -c "$(curl -L <https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh>)"
🔽 셸 환경 반영
exec $SHELL
🔽 설치 확인
which oci
oci -v
▶ OCI API 키 등록
OCI API를 호출하기 위해 사용자 단위의 API Key를 등록한다.
🔽 API 키 생성

- 프로필 → 사용자 설정 → 토큰 및 키 → API 키 → API Key 추가
- 프라이빗 키 파일 다운로드 (1회 제공)
- 동시에 표시되는 Configuration file preview(user, tenancy, fingerprint, region) 값을 복사해 둔다.
🔽 서버에 키 등록
OCI API Key를 서버에 업로드한 뒤, OCI CLI가 참조할 수 있도록 설정 파일을 구성한다.
mkdir -p ~/.oci
mv ~/downloaded_oci_api_key.pem ~/.oci/oci_api_key.pem
chmod 600 ~/.oci/oci_api_key.pem
cat > ~/.oci/config <<'EOF'
[DEFAULT]
user=ocid1.user.oc1..xxxxxxxxxxxxxxxx
fingerprint=aa:bb:cc:dd:ee:ff:...
tenancy=ocid1.tenancy.oc1..xxxxxxxxxxxxxxxx
region=ap-chuncheon-1
key_file=/home/ubuntu/.oci/oci_api_key.pem
EOF
chmod 600 ~/.oci/config
▶ Customer Secret Key 등록
Object Storage에 객체를 업로드하기 위한 S3 호환 API 키를 생성한다.

- 프로필 → 사용자 설정 → 토큰 및 키 → 고객 암호 키 → 암호키 생성

- 이름 입력 → 암호 키 생성

- 여기서 복사한 키가 Secret Key

- 여기서 복사한 키가 Access Key
▶ Grafana 서버 환경 변수(.env) 등록
이후 실행될 백업 스크립트와 컨테이너 설정에서 공통으로 사용할 환경 변수를 사전에 정의한다.
OCI_S3_ENDPOINT=<namespace>.compat.objectstorage.<region>.oraclecloud.com
OCI_S3_REGION={버킷생성리전}
OCI_S3_ACCESS_KEY={고객암호키액세스키}
OCI_S3_SECRET_KEY={고객암호키시크릿키}
GRAFANA_BUCKET=grafana-backup
MIMIR_BUCKET=mimir-backup
LOKI_BUCKET=loki-backup
TEMPO_BUCKET=tempo-backup
✅ Grafana 대시보드 백업
Grafana 대시보드를 주기적으로 백업하여 Object Storage에 보관하는 과정을 정리한다. 백업은 Grafana API를 통해 대시보드를 JSON 형태로 export한 뒤, OCI Object Storage에 업로드하는 방식으로 구성했다.
▶ Grafana API Key 생성
Grafana API를 호출하기 위해 Service Account 기반 API Key를 생성한다.

- Grafana UI → Administration → Users and access → Service Accounts → Add service account

- 생성한 Service Account에 Editor 이상 권한 부여

- Add service account token을 통해 API Key 발급

- 이름 입력 후 Generate token

- 해당 키를 복사 (생성된 토큰은 한 번만 노출)
🔽 API 정상 동작 확인
curl -H "Authorization: Bearer $GRAFANA_TOKEN" \\
https://{그라파나URL}/api/health
▶ 백업 스크립트
🔽 백업 디렉토리 및 로그 준비
- 백업 스크립트: /home/ubuntu/grafana-backup.sh
- 임시 백업 디렉토리: /var/backups/grafana
- 로그 파일: /var/log/grafana-backup.log
sudo mkdir -p /var/backups/grafana
sudo chown -R ubuntu:ubuntu /var/backups/grafana
sudo touch /var/log/grafana-backup.log
sudo chown ubuntu:ubuntu /var/log/grafana-backup.log
🔽 백업 스크립트
#!/bin/bash
export PATH=/usr/local/bin:/usr/bin:/bin
set -euo pipefail
# ====== 설정 ======
GRAFANA_URL="{그라파나URL}"
GRAFANA_TOKEN="{그라파나API키}"
BUCKET_NAME="grafana-backup"
OCI_PREFIX="grafana"
BACKUP_ROOT="/var/backups/grafana"
TODAY="$(date +%F)"
BACKUP_DIR="${BACKUP_ROOT}/${TODAY}"
LOG_FILE="/var/log/grafana-backup.log"
# ====== 실행 ======
mkdir -p "$BACKUP_DIR"
echo "[$(date -Is)] start backup: $BACKUP_DIR" | tee -a "$LOG_FILE"
# 대시보드 UID 목록 조회
dashboards=$(curl -fsS \\
-H "Authorization: Bearer ${GRAFANA_TOKEN}" \\
"${GRAFANA_URL}/api/search?type=dash-db" | jq -r '.[].uid')
# 각 대시보드 export
for uid in $dashboards; do
curl -fsS \\
-H "Authorization: Bearer ${GRAFANA_TOKEN}" \\
"${GRAFANA_URL}/api/dashboards/uid/${uid}" \\
> "${BACKUP_DIR}/${uid}.json"
done
# Object Storage 업로드
oci os object bulk-upload \\
--bucket-name "${BUCKET_NAME}" \\
--src-dir "${BACKUP_DIR}" \\
--prefix "${OCI_PREFIX}/${TODAY}/" \\
--overwrite
echo "[$(date -Is)] done backup" | tee -a "$LOG_FILE"
🔽 실행 권한 부여
sudo chmod +x /home/ubuntu/grafana-backup.sh
▶ Cron 등록
🔽 필수 패키지 설치
sudo apt-get update
sudo apt-get install -y jq cron
🔽 매일 새벽 3시 백업 실행
crontab -e
0 3 * * * /home/ubuntu/grafana-backup.sh >> /var/log/grafana-backup.log 2>&1
🔽 등록 확인
crontab -l
▶ 백업 결과 확인

- /var/log/grafana-backup.log에서 실행 로그 확인
- Object Storage 버킷 내 날짜별 디렉토리 생성 여부 확인
✅ Prometheus 메트릭 데이터 백업
Prometheus는 단독으로 Object Storage 기반 장기 보관에 적합하지 않다. 장기 보관을 위해서는 Thanos 또는 Mimir 같은 외부 스토리지가 필요하다. 본 구성에서는 Grafana Mimir를 사용해 메트릭 데이터를 Object Storage에 백업했다.
Mimir 설정은 Grafana 공식 문서를 기준으로 진행했다.
- Grafana 공식 문서
- Grafana 공식 문서
▶ Mimir 구성
🔽 디렉터리 구조
/grafana-docker
├─ docker-compose.yml
├─ grafana/
│ └─ datasources/
│ ├─ loki.yml
│ ├─ prometheus.yml
│ └─ tempo.yml
├─ prometheus/
│ └─ prometheus.yml
├─ loki/
│ └─ loki.yml
├─ tempo/
│ └─ tempo.yml
├─ nginx/
│ └─ conf.d
│ └─ grafana.conf
└─ mimir/
└─ mimir.yaml # 추가
- Mimir는 Grafana Server에서 실행 중인 Docker Compose 환경에 추가한다.
🔽 Mimir Docker (docker-compose.yml)
services:
mimir:
image: grafana/mimir:latest
container_name: mimir
restart: unless-stopped
env_file: .env
command:
- -config.file=/etc/mimir/mimir.yaml
- -config.expand-env=true
ports:
- "9009:9009"
volumes:
- ./mimir/mimir.yaml:/etc/mimir/mimir.yaml:ro
- ./mimir/data:/data
networks:
- grafana_network
- env_file, config.expand-env=true
- Object Storage 관련 설정을 환경 변수로 주입하기 위해 사용
🔽 Mimir Configuration (mimir.yaml)
target: all
multitenancy_enabled: false
server:
http_listen_port: 9009
common:
storage:
backend: s3
s3:
endpoint: ${OCI_S3_ENDPOINT}
region: ${OCI_S3_REGION}
access_key_id: ${OCI_S3_ACCESS_KEY}
secret_access_key: ${OCI_S3_SECRET_KEY}
bucket_name: ${MIMIR_BUCKET}
blocks_storage:
storage_prefix: mimirblocks
ruler_storage:
storage_prefix: mimirruler
alertmanager_storage:
storage_prefix: mimiralert
compactor:
data_dir: /data/compactor
ingester:
ring:
replication_factor: 1
limits:
compactor_blocks_retention_period: 2160h # 90일
- Object Storage를 S3 호환 backend로 사용
- 단일 노드 환경이므로 replication_factor: 1로 설정
- 메트릭 보존 기간은 90일로 제한
▶ Prometheus 설정 추가
Prometheus에서 수집한 메트릭을 Mimir로 전달하기 위해 remote_write 설정을 추가한다. 이 설정을 통해 Prometheus는 로컬 TSDB 저장과 동시에 Mimir로 메트릭을 전송한다.
remote_write:
- url: <http://mimir:9009/api/v1/push>
▶ Grafana 데이터 소스 변경

- Grafana GUI에서 기존 Prometheus 데이터 소스를 Mimir endpoint 기준으로 변경
- 이를 통해 Grafana는 Object Storage에 저장된 메트릭 데이터를 직접 조회
▶ 백업 결과 확인


- Prometheus 컨테이너를 중지해도 Mimir + Object Storage에 저장된 메트릭을 기반으로 Grafana 대시보드가 정상적으로 렌더링되는 것을 확인
✅ Loki 로그 데이터 백업
Loki 로그 데이터를 OCI Object Storage에 직접 저장하도록 구성한다.
Loki 설정은 Grafana 공식 문서를 기준으로 진행했다.
- Grafana 공식 문서
▶ Loki 컨테이너에 env 확장 설정 추가
services:
loki:
image: grafana/loki:latest
container_name: loki
restart: unless-stopped
env_file: .env # 추가
ports:
- "3100:3100"
command:
- -config.file=/etc/loki/loki.yml
- -config.expand-env=true # 추가
volumes:
- ./loki/config:/etc/loki
- ./loki/data:/var/loki
networks:
- grafana_network
- env_file, config.expand-env=true
- Object Storage 관련 설정을 환경 변수로 주입하기 위해 추가
▶ Loki 스토리지 백엔드 변경 (Filesystem -> OCI Object Storage)
🔽 기존 loki.yml
auth_enabled: false
server:
http_listen_address: 0.0.0.0
http_listen_port: 3100
grpc_listen_port: 9096
log_level: info
common:
path_prefix: /var/loki
storage:
filesystem:
chunks_directory: /var/loki/chunks
rules_directory: /var/loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
schema_config:
configs:
- from: 2025-01-01
store: tsdb
object_store: filesystem # 로컬 파일 시스템 사용
schema: v13
index:
prefix: index_
period: 24h
pattern_ingester:
enabled: true
metric_aggregation:
loki_address: localhost:3100
ruler:
enable_alertmanager_discovery: true
enable_api: true
frontend:
encoding: protobuf
compactor:
working_directory: /var/loki/retention
delete_request_store: filesystem
retention_enabled: false
limits_config:
reject_old_samples: true
reject_old_samples_max_age: 168h
metric_aggregation_enabled: true
allow_structured_metadata: true
volume_enabled: true
🔽 변경된 loki.yml
auth_enabled: false
server:
http_listen_address: 0.0.0.0
http_listen_port: 3100
grpc_listen_port: 9096
log_level: info
common:
path_prefix: /var/loki
storage:
s3:
endpoint: https://${OCI_S3_ENDPOINT}
region: ${OCI_S3_REGION}
bucketnames: ${LOKI_BUCKET}
access_key_id: ${OCI_S3_ACCESS_KEY}
secret_access_key: ${OCI_S3_SECRET_KEY}
s3forcepathstyle: true
insecure: false
replication_factor: 1
ring:
kvstore:
store: inmemory
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
schema_config:
configs:
- from: 2025-01-01
store: tsdb
object_store: s3
schema: v13
index:
prefix: index_
period: 24h
pattern_ingester:
enabled: true
metric_aggregation:
loki_address: localhost:3100
ruler:
storage:
type: s3
s3:
endpoint: https://${OCI_S3_ENDPOINT}
region: ${OCI_S3_REGION}
bucketnames: ${LOKI_BUCKET}
access_key_id: ${OCI_S3_ACCESS_KEY}
secret_access_key: ${OCI_S3_SECRET_KEY}
s3forcepathstyle: true
insecure: false
enable_alertmanager_discovery: true
enable_api: true
frontend:
encoding: protobuf
compactor:
working_directory: /var/loki/retention
delete_request_store: s3
retention_enabled: false
limits_config:
reject_old_samples: true
reject_old_samples_max_age: 168h
metric_aggregation_enabled: true
allow_structured_metadata: true
volume_enabled: true
🔽 common.storage
# 변경 전
common:
path_prefix: /var/loki
storage:
filesystem:
chunks_directory: /var/loki/chunks
rules_directory: /var/loki/rules~~
# 변경 후
common:
path_prefix: /var/loki
storage:
s3:
endpoint: https://${OCI_S3_ENDPOINT}
region: ${OCI_S3_REGION}
bucketnames: ${LOKI_BUCKET}
access_key_id: ${OCI_S3_ACCESS_KEY}
secret_access_key: ${OCI_S3_SECRET_KEY}
s3forcepathstyle: true
insecure: false
🔽 schema_config
# 변경 전
schema_config:
configs:
- from: 2025-01-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
# 변경 후
schema_config:
configs:
- from: 2025-01-01
store: tsdb
object_store: s3
schema: v13
index:
prefix: index_
period: 24h
🔽 compactor
# 변경 전
compactor:
working_directory: /var/loki/retention
delete_request_store: filesystem
# 변경 후
compactor:
working_directory: /var/loki/retention
delete_request_store: s3
🔽 ruler
# 변경 전
ruler:
enable_alertmanager_discovery: true
enable_api: true
# 변경 후
ruler:
storage:
type: s3
s3:
endpoint: https://${OCI_S3_ENDPOINT}
region: ${OCI_S3_REGION}
bucketnames: ${LOKI_BUCKET}
access_key_id: ${OCI_S3_ACCESS_KEY}
secret_access_key: ${OCI_S3_SECRET_KEY}
s3forcepathstyle: true
insecure: false
enable_alertmanager_discovery: true
enable_api: true
▶ 백업 결과 확인

- 로그 수집과 동시에 OCI Object Storage에 실시간 저장
✅ Tempo 트레이스 데이터 백업
Tempo 트레이스 데이터를 OCI Object Storage에 직접 저장하도록 구성한다.
Tempo 설정은 Grafana 공식 문서를 기준으로 진행했다.
- Grafana 공식 문서
▶ Tempo 컨테이너에 env 확장 설정 추가
services:
tempo:
image: grafana/tempo:latest
container_name: tempo
restart: unless-stopped
env_file: .env # 추가
ports:
- "3200:3200"
- "4317:4317"
- "4318:4318"
command:
- -config.file=/etc/tempo/tempo.yml
- -config.expand-env=true # 추가
volumes:
- ./tempo/config:/etc/tempo
- ./tempo/data:/var/tempo
networks:
- grafana_network
- env_file, config.expand-env=true
- Object Storage 관련 설정을 환경 변수로 주입하기 위해 추가
▶ Tempo 스토리지 백엔드 변경 (Filesystem -> OCI Object Storage)
🔽 기존 tempo.yml
stream_over_http_enabled : true
server:
http_listen_address: 0.0.0.0
http_listen_port: 3200
grpc_listen_port: 9095
log_level: info
query_frontend:
mcp_server:
enabled: true
search:
duration_slo: 5s
throughput_bytes_slo: 1.073741824e+09
metadata_slo:
duration_slo: 5s
throughput_bytes_slo: 1.073741824e+09
trace_by_id:
duration_slo: 100ms
metrics:
max_duration: 168h
query_backend_after: 5m
duration_slo: 5s
throughput_bytes_slo: 1.073741824e+09
distributor:
usage:
cost_attribution:
enabled: true
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
metrics_generator:
registry:
external_labels:
source: tempo
cluster: docker-compose
storage:
path: /var/tempo/generator/wal
remote_write:
- url: http://prometheus:9090/api/v1/write
send_exemplars: true
traces_storage:
path: /var/tempo/generator/traces
processor:
local_blocks:
filter_server_spans: false
flush_to_storage: true
storage:
trace:
backend: local
wal: # 임시 저장소 위치
path: /var/tempo/wal
local: # 영구 저장소 위치
path: /var/tempo/blocks
compactor:
compaction:
block_retention: 876000h
overrides:
defaults:
cost_attribution:
dimensions:
service.name: ""
span.http.target: "service_route"
metrics_generator:
processors: [service-graphs, span-metrics, local-blocks]
generate_native_histograms: both
🔽 변경된 tempo.yml
stream_over_http_enabled : true
server:
http_listen_address: 0.0.0.0
http_listen_port: 3200
grpc_listen_port: 9095
log_level: info
query_frontend:
mcp_server:
enabled: true
search:
duration_slo: 5s
throughput_bytes_slo: 1.073741824e+09
metadata_slo:
duration_slo: 5s
throughput_bytes_slo: 1.073741824e+09
trace_by_id:
duration_slo: 100ms
metrics:
max_duration: 168h
query_backend_after: 5m
duration_slo: 5s
throughput_bytes_slo: 1.073741824e+09
distributor:
usage:
cost_attribution:
enabled: true
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
metrics_generator:
registry:
external_labels:
source: tempo
cluster: docker-compose
storage:
path: /var/tempo/generator/wal
remote_write:
- url: http://prometheus:9090/api/v1/write
send_exemplars: true
traces_storage:
path: /var/tempo/generator/traces
processor:
local_blocks:
filter_server_spans: false
flush_to_storage: true
storage:
trace:
backend: s3
wal:
path: /var/tempo/wal
s3:
bucket: ${TEMPO_BUCKET}
endpoint: ${OCI_S3_ENDPOINT}
region: ${OCI_S3_REGION}
access_key: ${OCI_S3_ACCESS_KEY}
secret_key: ${OCI_S3_SECRET_KEY}
insecure: false
forcepathstyle: true
compactor:
compaction:
block_retention: 876000h
overrides:
defaults:
cost_attribution:
dimensions:
service.name: ""
span.http.target: "service_route"
metrics_generator:
processors: [service-graphs, span-metrics, local-blocks]
generate_native_histograms: both
🔽 storage
# 변경 전
storage:
trace:
backend: local
wal:
path: /var/tempo/wal
local:
path: /var/tempo/blocks
# 변경 후
storage:
trace:
backend: s3
wal:
path: /var/tempo/wal
s3:
bucket: ${TEMPO_BUCKET}
endpoint: ${OCI_S3_ENDPOINT}
region: ${OCI_S3_REGION}
access_key: ${OCI_S3_ACCESS_KEY}
secret_key: ${OCI_S3_SECRET_KEY}
insecure: false
forcepathstyle: true
▶ 백업 결과 확인

- 트레이스 수집과 동시에 OCI Object Storage에 블록 저장
📍 참고 자료
'DevOps > Log&Monitoring' 카테고리의 다른 글
| [Log&Monitoring] Grafana Observability 구축 (3) - Grafana 대시보드 (0) | 2025.12.29 |
|---|---|
| [Log&Monitoring] Grafana Observability 구축 (2) - Alloy 기반 수집 파이프라인 (0) | 2025.12.28 |
| [Log&Monitoring] Grafana Observability 구축 (1) - Metric, Log, Trace 아키텍처 (0) | 2025.12.28 |
| [Log&Monitoring] 관측 가능성(Observability) (6) | 2025.08.08 |