GitLab Helm Chart 업그레이드 시 DB Migration 실패 트러블슈팅 (9.8.0→9.9.0)
📌 핵심 요약
GitLab Helm chart를 9.8.0에서 9.9.0 (앱 버전 v18.8 → v18.9)으로 업그레이드하는 과정에서 DB migration job이 실패하고, 새로 배포된 sidekiq/webservice pod들이 Init:CrashLoopBackOff 상태에 빠지는 장애가 발생했다.
근본 원인은 pool_repositories 테이블의 organization_id 컬럼에 NULL 값이 존재하는 데이터 정합성 문제였다. v18.9의 migration이 해당 컬럼에 NOT NULL 제약조건을 추가하려다 PG::CheckViolation 에러가 발생했다.
해결책: NULL 값을 올바른 organization_id(1, Default org)로 UPDATE하고, 실패한 migration job을 삭제하여 ArgoCD self-heal로 재실행하면 된다.
🏗️ 환경
- Kubernetes: RKE2
- GitOps: ArgoCD
- Database: PostgreSQL 17 (Zalando PostgreSQL Operator로 관리, Spilo 4.0-p2)
- GitLab Helm chart: 9.8.0 → 9.9.0
- GitLab App: v18.8 → v18.9
- 로그 수집: Loki + Grafana
🚨 상황
ArgoCD에서 GitLab Helm chart 버전을 9.9.0으로 올리고 sync를 실행했다. 그런데 정상적으로 완료되어야 할 업그레이드가 다음 증상을 보이며 실패했다.
증상 1: DB Migration Job 실패
NAME STATUS
gitlab-migrations-x-xxxxxxxx-xxxxx BackoffLimitExceeded
migration job의 백오프 제한을 초과하여 pod가 계속 재시작되다 최종 실패 상태로 전환되었다.
증상 2: 새 버전 Pod들의 Init:CrashLoopBackOff
gitlab-sidekiq-all-in-1-v2-xxx-xxxxx 0/1 Init:CrashLoopBackOff 8 15m
gitlab-webservice-default-xxx-xxxxx 0/2 Init:CrashLoopBackOff 8 15m
새 버전으로 배포된 sidekiq과 webservice pod들의 dependencies init container가 migration이 완료될 때까지 기다리는 로직으로 인해 CrashLoopBackOff에 빠졌다.
🔍 원인 분석
1단계: Loki로 Migration 로그 확인
Grafana에서 Loki를 통해 migration pod의 로그를 조회했다.
{namespace="gitlab", pod=~"gitlab-migrations.*"}
로그에서 다음과 같은 에러를 확인했다:
== 20250101000000 AddNotNullConstraintOnOrganizationIdForPoolRepositories: migrating =====
-- execute("ALTER TABLE pool_repositories VALIDATE CONSTRAINT check_96233d37c0")
PG::CheckViolation: ERROR: new row for relation "pool_repositories" violates check constraint "check_96233d37c0"
DETAIL: Failing row contains (1, ..., null, ...).
rake aborted!
StandardError: An error has occurred, all later migrations canceled:
PG::CheckViolation: ERROR: new row for relation "pool_repositories" violates check constraint "check_96233d37c0"
2단계: 근본 원인 파악
v18.9의 migration AddNotNullConstraintOnOrganizationIdForPoolRepositories는 pool_repositories.organization_id 컬럼에 NOT NULL 제약조건을 추가하려 했다. 그런데 해당 테이블에 organization_id가 NULL인 행이 1건 존재하고 있었다.
이는 이전 버전에서 organization_id가 NULL을 허용하던 시절에 생성된 데이터로, 정상적으로는 존재하지 않아야 하는 고아 데이터였다.
-- PostgreSQL에서 직접 확인
SELECT id, organization_id FROM pool_repositories WHERE organization_id IS NULL;
id | organization_id
----+------------------
1 | (null)
✅ 해결 과정
1. NULL 데이터 수정
Zalando PostgreSQL Operator로 관리되는 PostgreSQL의 primary pod에 접속해서 NULL 값을 수정했다.
# PostgreSQL primary pod 접속
kubectl exec -it -n gitlab \
$(kubectl get pods -n gitlab -l application=spilo,cluster-name=gitlab-postgresql \
-o jsonpath='{.items[?(@.labels.spilo-role=="master")].metadata.name}') \
-- psql -U gitlab -d gitlabhq_production
# NULL 데이터 수정
UPDATE pool_repositories SET organization_id = 1 WHERE organization_id IS NULL;
-- UPDATE 1
# 확인
SELECT id, organization_id FROM pool_repositories WHERE organization_id IS NULL;
-- (0 rows)
2. 실패한 Migration Job 삭제
kubectl delete job -n gitlab gitlab-migrations-x-xxxxxxxx
ArgoCD의 self-heal 기능이 자동으로 migration job을 재생성했다. 이번에는 데이터 정합성 문제가 해결되었으므로 17개의 migration이 모두 성공적으로 완료되었다.
== 20250101000000 AddNotNullConstraintOnOrganizationIdForPoolRepositories: migrating ====
-- execute("ALTER TABLE pool_repositories VALIDATE CONSTRAINT check_96233d37c0")
-> 0.0234s
== 20250101000000 AddNotNullConstraintOnOrganizationIdForPoolRepositories: migrated (0.0235s) ==
...
Migrated 17 files.
3. CrashLoopBackOff Pod 재시작
CrashLoopBackOff 상태에 빠진 pod들을 삭제하면 Kubernetes가 즉시 재생성한다. 이미 migration이 완료된 상태이므로 init container가 정상 통과하고 pod들이 Running 상태로 전환된다.
kubectl delete pods -n gitlab -l app=sidekiq
kubectl delete pods -n gitlab -l app=webservice
4. 최종 상태 확인
ArgoCD: Synced / Healthy ✅
kubectl get pods -n gitlab | grep -v Running
# (출력 없음 - 모든 pod 정상)
🛡️ 재발 방지
migration 실패 시 데이터 정합성 확인
일반적인 GitLab 업그레이드에서는 별도의 데이터 검증이 필요하지 않다. 다만 이번처럼 NOT NULL 제약조건 추가 migration이 실패했을 때는, 해당 테이블에 NULL 데이터가 남아있는지 확인하는 것이 빠른 진단 방법이다.
-- migration 실패 시 NULL 데이터 확인 예시
SELECT 'pool_repositories' as table_name, COUNT(*) as null_count
FROM pool_repositories
WHERE organization_id IS NULL;
이러한 고아 데이터는 이전 버전에서 허용되던 형식이 새 버전에서 엄격해지면서 발생하는 것으로, GitLab의 공식 업그레이드 가이드에서도 background migration 완료 여부 확인을 권장하고 있다.
Migration 실패 알림 강화
Kubernetes Job 실패 시 즉시 알림을 받을 수 있도록 모니터링 규칙을 추가한다.
# Prometheus AlertManager 규칙 예시
- alert: KubernetesJobFailed
expr: kube_job_status_failed > 0
for: 5m
labels:
severity: critical
annotations:
summary: "Job {{ $labels.job_name }} 실패"
GitLab 업그레이드 체크리스트
- 공식 업그레이드 경로 확인
- 스테이징 환경에서 업그레이드 테스트
- 업그레이드 후 migration 실패 시 데이터 정합성 확인
- 업그레이드 전 DB 백업 확인
- ArgoCD sync 중 migration job 로그 실시간 모니터링
💬 마치며
이번 장애의 핵심 교훈은 “데이터 정합성 문제는 언제든 migration 실패로 이어질 수 있다”는 점이다.
GitLab처럼 복잡한 애플리케이션은 버전이 올라가면서 기존에 허용되던 데이터 형식에 더 엄격한 제약을 추가하는 경우가 있다. 특히 Kubernetes 환경에서는 이러한 실패가 init container의 무한 루프로 이어져 증상이 복잡해 보일 수 있지만, 결국 근본 원인은 단순한 NULL 데이터 1건이었다.
Loki와 같은 로그 수집 도구가 있어 migration pod의 정확한 에러 메시지를 빠르게 파악할 수 있었고, ArgoCD의 self-heal 기능 덕분에 job 재생성을 별도로 처리할 필요가 없었다. 셀프호스팅 GitLab을 운영한다면 업그레이드 전 데이터 정합성 점검 루틴을 꼭 추가해두는 게 좋다.
📚 참고 자료
- GitLab 공식 업그레이드 가이드
- GitLab Helm Chart 문서
- ArgoCD Resource Hooks
- Zalando PostgreSQL Operator
- GitLab: Background Migrations 확인 방법
- Kubernetes Jobs 공식 문서
조회수: 67