Kubernetes
Bell  

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 AddNotNullConstraintOnOrganizationIdForPoolRepositoriespool_repositories.organization_id 컬럼에 NOT NULL 제약조건을 추가하려 했다. 그런데 해당 테이블에 organization_idNULL인 행이 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 업그레이드 체크리스트

  1. 공식 업그레이드 경로 확인
  2. 스테이징 환경에서 업그레이드 테스트
  3. 업그레이드 후 migration 실패 시 데이터 정합성 확인
  4. 업그레이드 전 DB 백업 확인
  5. ArgoCD sync 중 migration job 로그 실시간 모니터링

💬 마치며

이번 장애의 핵심 교훈은 “데이터 정합성 문제는 언제든 migration 실패로 이어질 수 있다”는 점이다.

GitLab처럼 복잡한 애플리케이션은 버전이 올라가면서 기존에 허용되던 데이터 형식에 더 엄격한 제약을 추가하는 경우가 있다. 특히 Kubernetes 환경에서는 이러한 실패가 init container의 무한 루프로 이어져 증상이 복잡해 보일 수 있지만, 결국 근본 원인은 단순한 NULL 데이터 1건이었다.

Loki와 같은 로그 수집 도구가 있어 migration pod의 정확한 에러 메시지를 빠르게 파악할 수 있었고, ArgoCD의 self-heal 기능 덕분에 job 재생성을 별도로 처리할 필요가 없었다. 셀프호스팅 GitLab을 운영한다면 업그레이드 전 데이터 정합성 점검 루틴을 꼭 추가해두는 게 좋다.


📚 참고 자료

조회수: 67

댓글 남기기