Grafana 가 database is locked 를 내며 저장·로그인이 실패하는 것은 기본 백엔드인 SQLite 의 파일 잠금이 풀리지 않는 상태다. SQLite 는 한 파일을 여러 프로세스가 동시에 쓰는 것을 파일 잠금으로 막는데, 그 잠금이 제대로 동작하지 않거나 경쟁이 생기는 환경에서 이 오류가 난다.
grafana.db 를 담은 볼륨을 ReadWriteMany 로 잡고 레플리카를 2개 이상 띄우면 반드시 이 문제가 난다. Grafana 는 SQLite 백엔드에서 수평 확장을 지원하지 않는다. 임시로는 레플리카를 1로 줄인다.
kubectl scale deploy/grafana --replicas=1 -n monitoring
NFS · EFS · CIFS 위에 grafana.db 를 두면 POSIX 파일 잠금이 서버 구현에 따라 제대로 동작하지 않는다. 레플리카가 하나여도 잠금이 남아 있는 것처럼 보이거나, 최악의 경우 DB 파일이 손상된다. SQLite 파일은 로컬 블록 스토리지에 둔다. Kubernetes 라면 블록 기반 StorageClass 로 PVC 를 만든다.
쓰기가 실패하면서 잠금이 남는다. 노드와 PVC 양쪽을 본다.
kubectl exec -it <grafana-pod> -- df -h /var/lib/grafana
kubectl exec -it <grafana-pod> -- ls -al /var/lib/grafana/grafana.db
kubectl exec -it <grafana-pod> -- id
파일 소유자가 컨테이너 실행 UID(공식 이미지는 472)와 다르면 쓰지 못한다. securityContext.fsGroup 을 맞춘다.
대시보드와 계정이 쌓인 환경에서는 SQLite 를 그대로 두는 것이 위험하다. PostgreSQL 또는 MySQL 로 옮기면 잠금 문제와 레플리카 제약이 함께 사라진다.
# grafana.ini
[database]
type = postgres
host = postgres.monitoring.svc.cluster.local:5432
name = grafana
user = grafana
password = ${GF_DATABASE_PASSWORD}
ssl_mode = disable
환경 변수로 주는 것도 같다.
GF_DATABASE_TYPE=postgres
GF_DATABASE_HOST=postgres:5432
GF_DATABASE_NAME=grafana
GF_DATABASE_USER=grafana
GF_DATABASE_PASSWORD=${GRAFANA_DB_PASSWORD}
기존 SQLite 의 내용은 자동으로 옮겨지지 않는다. 대시보드는 JSON 으로 내보내 다시 넣거나, 처음부터 프로비저닝 파일로 관리해 두면 백엔드를 바꿔도 잃을 것이 없다. 폐쇄망이라 외부 DB 를 두기 어렵다면 최소한 레플리카 1 + 로컬 블록 볼륨 조합은 지킨다.