InfoGrab DocsInfoGrab Docs

Gitaly 클러스터(Praefect) 문제 해결

요약

Gitaly 클러스터(Praefect) 문제를 해결할 때 아래 정보를 참고합니다. 관리자 액세스 권한이 있어야 합니다. check Praefect 하위 명령은 Gitaly 클러스터(Praefect)의 상태를 확인하는 일련의 검사를 실행합니다.

Gitaly 클러스터(Praefect) 문제를 해결할 때 아래 정보를 참고합니다. Gitaly 문제 해결에 대한 정보는 Gitaly 문제 해결을 참고합니다.

사전 요구 사항#

관리자 액세스 권한이 있어야 합니다.

클러스터 상태 확인#

check Praefect 하위 명령은 Gitaly 클러스터(Praefect)의 상태를 확인하는 일련의 검사를 실행합니다.

gitlab-ctl praefect check

Praefect 차트로 Praefect를 배포한 경우에는 바이너리를 직접 실행합니다.

/usr/local/bin/praefect check

다음 섹션에서는 실행되는 검사를 설명합니다.

Praefect 마이그레이션#

Praefect 마이그레이션이 최신 상태인지 확인합니다. Praefect가 올바르게 동작하려면 데이터베이스 마이그레이션이 최신 상태여야 합니다.

이 검사가 실패하면 다음을 수행합니다.

  1. 데이터베이스의 schema_migrations 테이블에서 어떤 마이그레이션이 실행됐는지 확인합니다.
  2. praefect sql-migrate를 실행해 마이그레이션을 최신 상태로 만듭니다.

노드 연결 및 디스크 액세스#

Praefect가 모든 Gitaly 노드에 연결할 수 있는지, 각 Gitaly 노드가 모든 스토리지에 읽기·쓰기 권한이 있는지 확인합니다.

이 검사가 실패하면 다음을 수행합니다.

  1. 네트워크 주소와 토큰이 올바르게 설정돼 있는지 확인합니다.
    • Praefect 구성에서 확인합니다.
    • 각 Gitaly 노드의 구성에서 확인합니다.
  2. Gitaly 노드에서 gitaly 프로세스가 git 사용자로 실행되는지 확인합니다. 권한 문제로 Gitaly가 스토리지 디렉터리에 액세스하지 못할 수 있습니다.
  3. Praefect와 Gitaly 노드를 연결하는 네트워크에 문제가 없는지 확인합니다.

데이터베이스 읽기 및 쓰기 액세스#

Praefect가 데이터베이스를 읽고 쓸 수 있는지 확인합니다.

이 검사가 실패하면 다음을 수행합니다.

  1. Praefect 데이터베이스가 복구 모드인지 확인합니다. 복구 모드에서는 테이블이 읽기 전용일 수 있습니다. 확인하려면 다음을 실행합니다.

    select pg_is_in_recovery()
    
  2. Praefect가 PostgreSQL에 연결할 때 사용하는 사용자에게 데이터베이스 읽기·쓰기 권한이 있는지 확인합니다.

  3. 데이터베이스가 읽기 전용 모드로 설정됐는지 확인합니다. 확인하려면 다음을 실행합니다.

    show default_transaction_read_only
    

액세스할 수 없는 리포지터리#

프라이머리 할당이 없거나 프라이머리를 사용할 수 없어 액세스할 수 없는 리포지터리 수를 확인합니다.

이 검사가 실패하면 다음을 수행합니다.

  1. 다운된 Gitaly 노드가 있는지 확인합니다. praefect ping-nodes를 실행해 확인합니다.
  2. Praefect 데이터베이스의 부하가 높은지 확인합니다. Praefect 데이터베이스의 응답이 느리면 상태 검사 결과가 데이터베이스에 기록되지 못해, Praefect가 노드를 비정상으로 판단합니다.

오류: authoritative repository does not exist#

Praefect 구성이 TLS로 Gitaly 노드에 연결하면(주소가 tls://로 시작), git 사용자로 직접 실행한 Praefect 하위 명령은 runit 서비스 래퍼가 설정하는 SSL_CERT_DIR 환경 변수를 자동으로 상속하지 않습니다.

이 환경 변수가 없으면 리포지터리가 디스크에 있는데도 Gitaly에 연결하는 명령이 authoritative repository does not exist 같은 오류로 실패합니다.

이 오류를 피하려면 git 사용자 셸로 전환한 다음, Praefect 하위 명령을 실행하기 전에 SSL_CERT_DIR을 명시적으로 설정합니다.

sudo -u git -- env SSL_CERT_DIR=/opt/gitlab/embedded/ssl/certs/ \
  /opt/gitlab/embedded/bin/praefect \
  -config /var/opt/gitlab/praefect/config.toml <subcommand> [options]

Linux 패키지(Omnibus) 설치에서 이 경로는 Praefect의 runit 서비스 래퍼에 구성된 경로와 같습니다.

로그의 Praefect 오류#

오류가 발생하면 /var/log/gitlab/gitlab-rails/production.log를 확인합니다.

다음은 일반적인 오류와 그 원인입니다.

  • 500 응답 코드
    • ActionView::Template::Error (7:permission denied)
      • GitLab 서버에서 praefect['configuration'][:auth][:token]과 gitlab_rails['gitaly_token']이 일치하지 않습니다.
      • Sidekiq 서버에 gitlab_rails['repositories_storages'] 스토리지 구성이 없습니다.
    • Unable to save project. Error: 7:permission denied
      • GitLab 서버의 praefect['configuration'][:virtual_storage]에 있는 시크릿 토큰이 하나 이상의 Gitaly 서버에 설정된 gitaly['auth_token'] 값과 일치하지 않습니다.
  • 503 응답 코드
    • GRPC::Unavailable (14:failed to connect to all addresses)
      • GitLab이 Praefect에 연결하지 못했습니다.
    • GRPC::Unavailable (14:all SubCons are in TransientFailure...)
      • Praefect가 하위 Gitaly 노드 중 하나 이상에 연결하지 못합니다. Praefect 연결 검사기를 실행해 원인을 진단합니다.

Praefect 데이터베이스의 높은 CPU 부하#

Praefect 데이터베이스의 CPU 사용률이 높아지는 일반적인 원인은 다음과 같습니다.

  • 비용이 큰 쿼리를 실행하는 Prometheus 메트릭 스크레이프. gitlab.rb에 praefect['configuration'][:prometheus_exclude_database_from_default_metrics] = true를 설정합니다.
  • 읽기 분산 캐싱이 비활성화돼 사용자 트래픽이 많을 때 데이터베이스에 보내는 쿼리 수가 늘어납니다. 읽기 분산 캐싱이 활성화돼 있는지 확인합니다.

프라이머리 Gitaly 노드 확인#

리포지터리의 프라이머리 노드를 확인하려면 praefect metadata 하위 명령을 사용합니다.

리포지터리 메타데이터 보기#

Gitaly 클러스터(Praefect)는 클러스터에 저장된 리포지터리 정보를 담은 메타데이터 데이터베이스를 유지합니다. 문제 해결을 위해 praefect metadata 하위 명령으로 메타데이터를 확인합니다.

리포지터리 메타데이터는 다음 두 가지 값으로 조회할 수 있습니다.

가상 스토리지와 상대 경로로 리포지터리 메타데이터를 조회하려면 다음을 수행합니다.

  1. 오른쪽 위에서 Admin을 선택합니다.

  2. 왼쪽 사이드바에서 Overview > Projects를 선택한 다음 프로젝트를 선택합니다.

  3. 프로젝트의 Storage name과 Relative path 값을 기록합니다.

  4. 이 값으로 다음 명령을 실행합니다.

    sudo -u git -- /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml metadata -virtual-storage <virtual-storage> -relative-path <relative-path>
    
Note

리포지터리 ID는 프로젝트 ID와 다릅니다.

Praefect가 할당한 리포지터리 ID로 리포지터리 메타데이터를 조회하려면 다음을 수행합니다.

  1. 리포지터리 레플리카 경로의 마지막 구성 요소를 기록합니다. 예를 들어 @cluster/repositories/6f/96/54771에서 리포지터리 ID는 54771입니다.

  2. 그 값으로 다음 명령을 실행합니다.

    sudo -u git -- /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml metadata -repository-id <repository-id>
    

예시#

가상 스토리지가 default이고 상대 경로가 @hashed/b1/7e/b17ef6d19c7a5b1ee83b907c595526dcb1eb06db8227d650d5dda0a9f4ce8cd9.git인 리포지터리의 메타데이터를 조회하려면 다음을 실행합니다.

sudo -u git -- /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml metadata -virtual-storage default -relative-path @hashed/b1/7e/b17ef6d19c7a5b1ee83b907c595526dcb1eb06db8227d650d5dda0a9f4ce8cd9.git

Praefect가 할당한 리포지터리 ID가 1인 리포지터리의 메타데이터를 조회하려면 다음을 실행합니다.

sudo -u git -- /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml metadata -repository-id 1

두 예시 모두 예시 리포지터리에 대해 다음 메타데이터를 반환합니다.

Repository ID: 54771
Virtual Storage: "default"
Relative Path: "@hashed/b1/7e/b17ef6d19c7a5b1ee83b907c595526dcb1eb06db8227d650d5dda0a9f4ce8cd9.git"
Replica Path: "@hashed/b1/7e/b17ef6d19c7a5b1ee83b907c595526dcb1eb06db8227d650d5dda0a9f4ce8cd9.git"
Primary: "gitaly-1"
Generation: 1
Replicas:
- Storage: "gitaly-1"
  Assigned: true
  Generation: 1, fully up to date
  Healthy: true
  Valid Primary: true
  Verified At: 2021-04-01 10:04:20 +0000 UTC
- Storage: "gitaly-2"
  Assigned: true
  Generation: 0, behind by 1 changes
  Healthy: true
  Valid Primary: false
  Verified At: unverified
- Storage: "gitaly-3"
  Assigned: true
  Generation: replica not yet created
  Healthy: false
  Valid Primary: false
  Verified At: unverified

사용 가능한 메타데이터#

praefect metadata로 조회한 메타데이터에는 다음 표의 필드가 포함됩니다.

필드 설명
Repository ID Praefect가 리포지터리에 할당한 영구 고유 ID. GitLab이 리포지터리에 사용하는 ID와는 다릅니다.
Virtual Storage 리포지터리가 저장된 가상 스토리지의 이름.
Relative Path 가상 스토리지 내 리포지터리 경로.
Replica Path Gitaly 노드 디스크에서 리포지터리 레플리카가 저장되는 위치.
Primary 리포지터리의 현재 프라이머리.
Generation Praefect가 리포지터리 변경을 추적하는 데 사용합니다. 리포지터리에 쓰기가 발생할 때마다 리포지터리의 세대가 증가합니다.
Replicas 존재하거나 존재해야 하는 레플리카 목록.

각 레플리카에 대해 다음 메타데이터를 사용할 수 있습니다.

Replicas 필드 설명
Storage 레플리카가 저장된 Gitaly 스토리지의 이름.
Assigned 해당 스토리지에 레플리카가 존재해야 하는지 여부를 나타냅니다. Gitaly 노드가 클러스터에서 제거됐거나 리포지터리의 복제 계수를 낮춘 뒤 스토리지에 사본이 남아 있으면 false일 수 있습니다.
Generation 마지막으로 확인된 레플리카의 세대. 다음을 나타냅니다.

- 세대가 리포지터리의 세대와 같으면 레플리카가 완전히 최신 상태입니다.
- 레플리카의 세대가 리포지터리의 세대보다 낮으면 레플리카가 오래된 상태입니다.
- 스토리지에 레플리카가 아직 없으면 replica not yet created입니다.
Healthy 이 레플리카를 보유한 Gitaly 노드를 Praefect 노드들의 합의로 정상이라고 판단하는지 여부를 나타냅니다.
Valid Primary 레플리카가 프라이머리 노드 역할을 할 수 있는지 여부를 나타냅니다. 리포지터리의 프라이머리가 유효한 프라이머리가 아니고 유효한 프라이머리인 다른 레플리카가 있으면, 리포지터리에 다음 쓰기가 발생할 때 페일오버가 일어납니다. 다음 조건을 모두 충족하면 유효한 프라이머리입니다.

- 정상 상태인 Gitaly 노드에 저장돼 있습니다.
- 완전히 최신 상태입니다.
- 복제 계수 축소로 생긴 삭제 대기 job의 대상이 아닙니다.
- 할당돼 있습니다.
Verified At 검증 워커가 마지막으로 레플리카를 성공적으로 검증한 시점을 나타냅니다. 레플리카가 아직 검증되지 않았으면 마지막 검증 성공 시각 대신 unverified가 표시됩니다.

'repository not found' 오류로 명령 실패#

-virtual-storage에 잘못된 값을 지정하면 명령이 다음 오류를 반환합니다.

get metadata: rpc error: code = NotFound desc = repository not found

문서의 예시는 -virtual-storage default를 지정합니다. /etc/gitlab/gitlab.rb에서 Praefect 서버 설정 praefect['configuration'][:virtual_storage]를 확인합니다.

리포지터리 동기화 상태 확인#

일부 경우에는 Praefect 데이터베이스가 기반 Gitaly 노드와 동기화되지 않을 수 있습니다. 특정 리포지터리가 모든 노드에서 완전히 동기화됐는지 확인하려면 Rails 노드에서 gitlab:praefect:replicas Rake 태스크를 실행합니다. 이 Rake 태스크는 모든 Gitaly 노드에서 리포지터리의 체크섬을 계산합니다.

Praefect dataloss 명령은 Praefect 데이터베이스에 기록된 리포지터리 상태만 확인하므로, 이 상황에서 동기화 문제를 감지하는 용도로는 신뢰할 수 없습니다.

dataloss 명령이 @failed-geo-sync 리포지터리를 동기화되지 않음으로 표시#

@failed-geo-sync는 GitLab 16.1 이하에서 Geo가 프로젝트 동기화에 실패했을 때 사용하던 레거시 경로이며 더 이상 사용되지 않습니다.

GitLab 16.2 이상에서는 이 경로를 안전하게 삭제할 수 있습니다. @failed-geo-sync 디렉터리는 Gitaly 노드의 리포지터리 경로 아래에 있습니다.

relation does not exist 오류#

기본적으로 Praefect 데이터베이스 테이블은 gitlab-ctl reconfigure 태스크가 자동으로 생성합니다.

다만 다음 경우에는 초기 reconfigure 시 Praefect 데이터베이스 테이블이 생성되지 않아, 관계가 존재하지 않는다는 오류가 발생할 수 있습니다.

  • gitlab-ctl reconfigure 명령을 실행하지 않은 경우.
  • 실행 중에 오류가 발생한 경우.

예를 들면 다음과 같습니다.

  • ERROR: relation "node_status" does not exist at character 13

  • ERROR: relation "replication_queue_lock" does not exist at character 40

  • 다음 오류가 있습니다.

    {"level":"error","msg":"Error updating node: pq: relation \"node_status\" does not exist","pid":210882,"praefectName":"gitlab1x4m:0.0.0.0:2305","time":"2021-04-01T19:26:19.473Z","virtual_storage":"praefect-cluster-1"}
    

이 문제는 praefect 명령의 sql-migrate 하위 명령으로 데이터베이스 스키마 마이그레이션을 수행해 해결할 수 있습니다.

$ sudo -u git -- /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-migrate
praefect sql-migrate: OK (applied 21 migrations)

'repository scoped: invalid Repository' 오류로 요청 실패#

이는 Praefect 구성에 사용한 가상 스토리지 이름이 GitLab의 gitaly['configuration'][:storage][<index>][:name] 설정에 사용한 스토리지 이름과 일치하지 않는다는 뜻입니다.

Praefect와 GitLab 구성에서 사용하는 가상 스토리지 이름을 일치시켜 해결합니다.

클라우드 플랫폼에서의 Gitaly 클러스터(Praefect) 성능 문제#

Praefect는 CPU와 메모리를 많이 쓰지 않으므로 작은 가상 머신에서도 실행할 수 있습니다. 클라우드 서비스는 디스크 IO나 네트워크 트래픽처럼 작은 VM이 사용할 수 있는 리소스에 다른 제한을 두기도 합니다.

Praefect 노드는 네트워크 트래픽을 많이 생성합니다. 클라우드 서비스가 네트워크 대역폭을 제한하면 다음과 같은 증상이 나타납니다.

  • Git 작업의 성능 저하.
  • 높은 네트워크 지연 시간.
  • Praefect의 높은 메모리 사용량.

가능한 해결 방법은 다음과 같습니다.

  • 더 큰 VM을 프로비저닝해 더 많은 네트워크 트래픽 허용량을 확보합니다.
  • 클라우드 서비스의 모니터링과 로깅으로 Praefect 노드가 트래픽 허용량을 소진하지 않는지 확인합니다.

Praefect 구성 오류로 gitlab-ctl reconfigure 실패#

gitlab-ctl reconfigure가 실패하면 다음 오류가 표시될 수 있습니다.

STDOUT: praefect: configuration error: error reading config file: toml: cannot store TOML string into a Go int

이 오류는 praefect['database_port'] 또는 praefect['database_direct_port']를 정수가 아닌 문자열로 구성했을 때 발생합니다.

일반적인 복제 오류#

다음은 일반적인 복제 오류와 해결 방법입니다.

잠금 파일 존재#

잠금 파일은 같은 ref에 여러 업데이트가 동시에 적용되는 것을 막습니다. 잠금 파일이 오래된 상태로 남으면 복제가 error: cannot lock ref 오류로 실패합니다.

오래된 *.lock 파일을 정리하려면 Rails 콘솔에서 OptimizeRepositoryRequest를 트리거할 수 있습니다.

p = Project.find <Project ID>
client = Gitlab::GitalyClient::RepositoryService.new(p.repository)
client.optimize_repository

OptimizeRepositoryRequest 트리거로 해결되지 않으면 파일을 직접 확인해 생성 날짜를 보고 *.lock 파일을 수동으로 제거할지 판단합니다. 생성된 지 24시간이 지난 잠금 파일은 안전하게 제거할 수 있습니다.

Git fsck 오류#

유효하지 않은 객체가 있는 Gitaly 리포지터리는 복제 실패로 이어지며, Gitaly 로그에 다음과 같은 오류가 남습니다.

  • exit status 128, stderr: "fatal: git upload-pack: not our ref".
  • "fatal: bad object 58....e0f... ssh://gitaly/internal.git did not send all necessary objects.

Gitaly 노드 중 하나에 정상적인 리포지터리 사본이 남아 있다면 다음 방법으로 해결할 수 있습니다.

  1. Praefect 데이터베이스에서 리포지터리를 제거합니다.
  2. Praefect track-repository 하위 명령으로 다시 추적합니다.

이렇게 하면 authoritative Gitaly 노드의 리포지터리 사본으로 나머지 모든 Gitaly 노드의 사본을 덮어씁니다. 이 명령을 실행하기 전에 리포지터리의 최근 백업을 확보합니다.

  1. 손상된 리포지터리를 다른 위치로 옮깁니다.

    run `mv  .backup`
    

    예를 들면 다음과 같습니다.

    mv /var/opt/gitlab/git-data/repositories/@cluster/repositories/de/74/2335 /var/opt/gitlab/git-data/repositories/@cluster/repositories/de/74/2335.backup
    
  2. 복제를 트리거하는 Praefect 명령을 실행합니다.

    # Validate you have the correct repository.
    sudo -u git -- /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml remove-repository -virtual-storage gitaly -relative-path '<relative_path>' -db-only
    
    # Run again with '--apply' flag to remove repository from the Praefect tracking database
    sudo -u git -- /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml remove-repository -virtual-storage gitaly -relative-path '<relative_path>' -db-only --apply
    
    # Re-track the repository, overwriting the secondary nodes
    sudo -u git -- /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml track-repository -virtual-storage gitaly -authoritative-storage '<healthy_gitaly>' -relative-path '<relative_path>' -replica-path '<replica_path>'-replicate-immediately
    

복제가 오류 없이 실패#

Praefect dataloss가 일부만 사용할 수 있는 리포지터리를 표시하고 accept-dataloss 명령이 로그에 오류를 남기지 않은 채 리포지터리 동기화에 실패하면, Praefect 데이터베이스 storage_repositories 테이블의 repository_id 필드가 일치하지 않는 것이 원인일 수 있습니다. 불일치를 확인하려면 다음을 수행합니다.

  1. Praefect 데이터베이스에 연결합니다.

  2. 다음 쿼리를 실행합니다.

    select * from storage_repositories where relative_path = '<relative-path>';
    

    <relative-path>를 @hashed로 시작하는 리포지터리 경로로 바꿉니다.

alternates 디렉터리가 존재하지 않음#

GitLab은 중복 제거에 Git alternates 메커니즘을 사용합니다. alternates는 객체를 가져올 @pool 리포지터리의 objects 디렉터리를 가리키는 텍스트 파일입니다. 이 파일이 잘못된 경로를 가리키면 복제가 다음 오류 중 하나로 실패할 수 있습니다.

  • "error":"no alternates directory exists", "warning","msg":"alternates file does not point to valid git repository"
  • "error":"unexpected alternates content:
  • remote: error: unable to normalize alternate object path

이 오류의 원인을 조사하려면 다음을 수행합니다.

  1. Rails 콘솔에서 프로젝트가 풀에 속하는지 확인합니다.

    project = Project.find_by_id(<project id>)
    project.pool_repository
    
  2. 풀 리포지터리 경로가 디스크에 존재하고 alternates 파일 내용과 일치하는지 확인합니다.

  3. alternates 파일의 경로가 프로젝트의 objects 디렉터리에서 도달 가능한지 확인합니다.

이 확인을 마친 뒤 수집한 정보를 가지고 GitLab 지원에 문의합니다.

리포지터리 스토리지 이동 실패 후 프로젝트가 읽기 전용 상태로 남는 문제#

Sidekiq Pod에 Horizontal Pod Autoscaler(HPA)를 사용하면 job 실행 중 Pod 스케일링 때문에 리포지터리 스토리지 이동이 오류 없이 실패할 수 있습니다. 이 문제로 리포지터리 스토리지 이동이 실패하면 해당 프로젝트가 읽기 전용 상태로 남습니다.

영향을 받은 리포지터리를 복구하려면 다음을 수행합니다.

  1. 영향을 받은 프로젝트를 읽기·쓰기 상태로 되돌립니다.
  2. Sidekiq Pod의 HPA를 비활성화합니다
  3. 개별 프로젝트에 대해 REST API로 스토리지 이동을 다시 실행합니다.
  4. 마이그레이션이 완료된 후 HPA 구성을 복원합니다.

Gitaly 클러스터(Praefect) 문제 해결

GitLab v19.4
Tier: Free, Premium, Ultimate
Offering: GitLab Self-Managed
원문 보기

요약

Gitaly 클러스터(Praefect) 문제를 해결할 때 아래 정보를 참고합니다. 관리자 액세스 권한이 있어야 합니다. check Praefect 하위 명령은 Gitaly 클러스터(Praefect)의 상태를 확인하는 일련의 검사를 실행합니다.

Gitaly 클러스터(Praefect) 문제를 해결할 때 아래 정보를 참고합니다. Gitaly 문제 해결에 대한 정보는 Gitaly 문제 해결을 참고합니다.

사전 요구 사항#

관리자 액세스 권한이 있어야 합니다.

클러스터 상태 확인#

check Praefect 하위 명령은 Gitaly 클러스터(Praefect)의 상태를 확인하는 일련의 검사를 실행합니다.

gitlab-ctl praefect check

Praefect 차트로 Praefect를 배포한 경우에는 바이너리를 직접 실행합니다.

/usr/local/bin/praefect check

다음 섹션에서는 실행되는 검사를 설명합니다.

Praefect 마이그레이션#

Praefect 마이그레이션이 최신 상태인지 확인합니다. Praefect가 올바르게 동작하려면 데이터베이스 마이그레이션이 최신 상태여야 합니다.

이 검사가 실패하면 다음을 수행합니다.

  1. 데이터베이스의 schema_migrations 테이블에서 어떤 마이그레이션이 실행됐는지 확인합니다.
  2. praefect sql-migrate를 실행해 마이그레이션을 최신 상태로 만듭니다.

노드 연결 및 디스크 액세스#

Praefect가 모든 Gitaly 노드에 연결할 수 있는지, 각 Gitaly 노드가 모든 스토리지에 읽기·쓰기 권한이 있는지 확인합니다.

이 검사가 실패하면 다음을 수행합니다.

  1. 네트워크 주소와 토큰이 올바르게 설정돼 있는지 확인합니다.
    • Praefect 구성에서 확인합니다.
    • 각 Gitaly 노드의 구성에서 확인합니다.
  2. Gitaly 노드에서 gitaly 프로세스가 git 사용자로 실행되는지 확인합니다. 권한 문제로 Gitaly가 스토리지 디렉터리에 액세스하지 못할 수 있습니다.
  3. Praefect와 Gitaly 노드를 연결하는 네트워크에 문제가 없는지 확인합니다.

데이터베이스 읽기 및 쓰기 액세스#

Praefect가 데이터베이스를 읽고 쓸 수 있는지 확인합니다.

이 검사가 실패하면 다음을 수행합니다.

  1. Praefect 데이터베이스가 복구 모드인지 확인합니다. 복구 모드에서는 테이블이 읽기 전용일 수 있습니다. 확인하려면 다음을 실행합니다.

    select pg_is_in_recovery()
    
  2. Praefect가 PostgreSQL에 연결할 때 사용하는 사용자에게 데이터베이스 읽기·쓰기 권한이 있는지 확인합니다.

  3. 데이터베이스가 읽기 전용 모드로 설정됐는지 확인합니다. 확인하려면 다음을 실행합니다.

    show default_transaction_read_only
    

액세스할 수 없는 리포지터리#

프라이머리 할당이 없거나 프라이머리를 사용할 수 없어 액세스할 수 없는 리포지터리 수를 확인합니다.

이 검사가 실패하면 다음을 수행합니다.

  1. 다운된 Gitaly 노드가 있는지 확인합니다. praefect ping-nodes를 실행해 확인합니다.
  2. Praefect 데이터베이스의 부하가 높은지 확인합니다. Praefect 데이터베이스의 응답이 느리면 상태 검사 결과가 데이터베이스에 기록되지 못해, Praefect가 노드를 비정상으로 판단합니다.

오류: authoritative repository does not exist#

Praefect 구성이 TLS로 Gitaly 노드에 연결하면(주소가 tls://로 시작), git 사용자로 직접 실행한 Praefect 하위 명령은 runit 서비스 래퍼가 설정하는 SSL_CERT_DIR 환경 변수를 자동으로 상속하지 않습니다.

이 환경 변수가 없으면 리포지터리가 디스크에 있는데도 Gitaly에 연결하는 명령이 authoritative repository does not exist 같은 오류로 실패합니다.

이 오류를 피하려면 git 사용자 셸로 전환한 다음, Praefect 하위 명령을 실행하기 전에 SSL_CERT_DIR을 명시적으로 설정합니다.

sudo -u git -- env SSL_CERT_DIR=/opt/gitlab/embedded/ssl/certs/ \
  /opt/gitlab/embedded/bin/praefect \
  -config /var/opt/gitlab/praefect/config.toml <subcommand> [options]

Linux 패키지(Omnibus) 설치에서 이 경로는 Praefect의 runit 서비스 래퍼에 구성된 경로와 같습니다.

로그의 Praefect 오류#

오류가 발생하면 /var/log/gitlab/gitlab-rails/production.log를 확인합니다.

다음은 일반적인 오류와 그 원인입니다.

  • 500 응답 코드
    • ActionView::Template::Error (7:permission denied)
      • GitLab 서버에서 praefect['configuration'][:auth][:token]과 gitlab_rails['gitaly_token']이 일치하지 않습니다.
      • Sidekiq 서버에 gitlab_rails['repositories_storages'] 스토리지 구성이 없습니다.
    • Unable to save project. Error: 7:permission denied
      • GitLab 서버의 praefect['configuration'][:virtual_storage]에 있는 시크릿 토큰이 하나 이상의 Gitaly 서버에 설정된 gitaly['auth_token'] 값과 일치하지 않습니다.
  • 503 응답 코드
    • GRPC::Unavailable (14:failed to connect to all addresses)
      • GitLab이 Praefect에 연결하지 못했습니다.
    • GRPC::Unavailable (14:all SubCons are in TransientFailure...)
      • Praefect가 하위 Gitaly 노드 중 하나 이상에 연결하지 못합니다. Praefect 연결 검사기를 실행해 원인을 진단합니다.

Praefect 데이터베이스의 높은 CPU 부하#

Praefect 데이터베이스의 CPU 사용률이 높아지는 일반적인 원인은 다음과 같습니다.

  • 비용이 큰 쿼리를 실행하는 Prometheus 메트릭 스크레이프. gitlab.rb에 praefect['configuration'][:prometheus_exclude_database_from_default_metrics] = true를 설정합니다.
  • 읽기 분산 캐싱이 비활성화돼 사용자 트래픽이 많을 때 데이터베이스에 보내는 쿼리 수가 늘어납니다. 읽기 분산 캐싱이 활성화돼 있는지 확인합니다.

프라이머리 Gitaly 노드 확인#

리포지터리의 프라이머리 노드를 확인하려면 praefect metadata 하위 명령을 사용합니다.

리포지터리 메타데이터 보기#

Gitaly 클러스터(Praefect)는 클러스터에 저장된 리포지터리 정보를 담은 메타데이터 데이터베이스를 유지합니다. 문제 해결을 위해 praefect metadata 하위 명령으로 메타데이터를 확인합니다.

리포지터리 메타데이터는 다음 두 가지 값으로 조회할 수 있습니다.

가상 스토리지와 상대 경로로 리포지터리 메타데이터를 조회하려면 다음을 수행합니다.

  1. 오른쪽 위에서 Admin을 선택합니다.

  2. 왼쪽 사이드바에서 Overview > Projects를 선택한 다음 프로젝트를 선택합니다.

  3. 프로젝트의 Storage name과 Relative path 값을 기록합니다.

  4. 이 값으로 다음 명령을 실행합니다.

    sudo -u git -- /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml metadata -virtual-storage <virtual-storage> -relative-path <relative-path>
    
Note

리포지터리 ID는 프로젝트 ID와 다릅니다.

Praefect가 할당한 리포지터리 ID로 리포지터리 메타데이터를 조회하려면 다음을 수행합니다.

  1. 리포지터리 레플리카 경로의 마지막 구성 요소를 기록합니다. 예를 들어 @cluster/repositories/6f/96/54771에서 리포지터리 ID는 54771입니다.

  2. 그 값으로 다음 명령을 실행합니다.

    sudo -u git -- /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml metadata -repository-id <repository-id>
    

예시#

가상 스토리지가 default이고 상대 경로가 @hashed/b1/7e/b17ef6d19c7a5b1ee83b907c595526dcb1eb06db8227d650d5dda0a9f4ce8cd9.git인 리포지터리의 메타데이터를 조회하려면 다음을 실행합니다.

sudo -u git -- /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml metadata -virtual-storage default -relative-path @hashed/b1/7e/b17ef6d19c7a5b1ee83b907c595526dcb1eb06db8227d650d5dda0a9f4ce8cd9.git

Praefect가 할당한 리포지터리 ID가 1인 리포지터리의 메타데이터를 조회하려면 다음을 실행합니다.

sudo -u git -- /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml metadata -repository-id 1

두 예시 모두 예시 리포지터리에 대해 다음 메타데이터를 반환합니다.

Repository ID: 54771
Virtual Storage: "default"
Relative Path: "@hashed/b1/7e/b17ef6d19c7a5b1ee83b907c595526dcb1eb06db8227d650d5dda0a9f4ce8cd9.git"
Replica Path: "@hashed/b1/7e/b17ef6d19c7a5b1ee83b907c595526dcb1eb06db8227d650d5dda0a9f4ce8cd9.git"
Primary: "gitaly-1"
Generation: 1
Replicas:
- Storage: "gitaly-1"
  Assigned: true
  Generation: 1, fully up to date
  Healthy: true
  Valid Primary: true
  Verified At: 2021-04-01 10:04:20 +0000 UTC
- Storage: "gitaly-2"
  Assigned: true
  Generation: 0, behind by 1 changes
  Healthy: true
  Valid Primary: false
  Verified At: unverified
- Storage: "gitaly-3"
  Assigned: true
  Generation: replica not yet created
  Healthy: false
  Valid Primary: false
  Verified At: unverified

사용 가능한 메타데이터#

praefect metadata로 조회한 메타데이터에는 다음 표의 필드가 포함됩니다.

필드 설명
Repository ID Praefect가 리포지터리에 할당한 영구 고유 ID. GitLab이 리포지터리에 사용하는 ID와는 다릅니다.
Virtual Storage 리포지터리가 저장된 가상 스토리지의 이름.
Relative Path 가상 스토리지 내 리포지터리 경로.
Replica Path Gitaly 노드 디스크에서 리포지터리 레플리카가 저장되는 위치.
Primary 리포지터리의 현재 프라이머리.
Generation Praefect가 리포지터리 변경을 추적하는 데 사용합니다. 리포지터리에 쓰기가 발생할 때마다 리포지터리의 세대가 증가합니다.
Replicas 존재하거나 존재해야 하는 레플리카 목록.

각 레플리카에 대해 다음 메타데이터를 사용할 수 있습니다.

Replicas 필드 설명
Storage 레플리카가 저장된 Gitaly 스토리지의 이름.
Assigned 해당 스토리지에 레플리카가 존재해야 하는지 여부를 나타냅니다. Gitaly 노드가 클러스터에서 제거됐거나 리포지터리의 복제 계수를 낮춘 뒤 스토리지에 사본이 남아 있으면 false일 수 있습니다.
Generation 마지막으로 확인된 레플리카의 세대. 다음을 나타냅니다.

- 세대가 리포지터리의 세대와 같으면 레플리카가 완전히 최신 상태입니다.
- 레플리카의 세대가 리포지터리의 세대보다 낮으면 레플리카가 오래된 상태입니다.
- 스토리지에 레플리카가 아직 없으면 replica not yet created입니다.
Healthy 이 레플리카를 보유한 Gitaly 노드를 Praefect 노드들의 합의로 정상이라고 판단하는지 여부를 나타냅니다.
Valid Primary 레플리카가 프라이머리 노드 역할을 할 수 있는지 여부를 나타냅니다. 리포지터리의 프라이머리가 유효한 프라이머리가 아니고 유효한 프라이머리인 다른 레플리카가 있으면, 리포지터리에 다음 쓰기가 발생할 때 페일오버가 일어납니다. 다음 조건을 모두 충족하면 유효한 프라이머리입니다.

- 정상 상태인 Gitaly 노드에 저장돼 있습니다.
- 완전히 최신 상태입니다.
- 복제 계수 축소로 생긴 삭제 대기 job의 대상이 아닙니다.
- 할당돼 있습니다.
Verified At 검증 워커가 마지막으로 레플리카를 성공적으로 검증한 시점을 나타냅니다. 레플리카가 아직 검증되지 않았으면 마지막 검증 성공 시각 대신 unverified가 표시됩니다.

'repository not found' 오류로 명령 실패#

-virtual-storage에 잘못된 값을 지정하면 명령이 다음 오류를 반환합니다.

get metadata: rpc error: code = NotFound desc = repository not found

문서의 예시는 -virtual-storage default를 지정합니다. /etc/gitlab/gitlab.rb에서 Praefect 서버 설정 praefect['configuration'][:virtual_storage]를 확인합니다.

리포지터리 동기화 상태 확인#

일부 경우에는 Praefect 데이터베이스가 기반 Gitaly 노드와 동기화되지 않을 수 있습니다. 특정 리포지터리가 모든 노드에서 완전히 동기화됐는지 확인하려면 Rails 노드에서 gitlab:praefect:replicas Rake 태스크를 실행합니다. 이 Rake 태스크는 모든 Gitaly 노드에서 리포지터리의 체크섬을 계산합니다.

Praefect dataloss 명령은 Praefect 데이터베이스에 기록된 리포지터리 상태만 확인하므로, 이 상황에서 동기화 문제를 감지하는 용도로는 신뢰할 수 없습니다.

dataloss 명령이 @failed-geo-sync 리포지터리를 동기화되지 않음으로 표시#

@failed-geo-sync는 GitLab 16.1 이하에서 Geo가 프로젝트 동기화에 실패했을 때 사용하던 레거시 경로이며 더 이상 사용되지 않습니다.

GitLab 16.2 이상에서는 이 경로를 안전하게 삭제할 수 있습니다. @failed-geo-sync 디렉터리는 Gitaly 노드의 리포지터리 경로 아래에 있습니다.

relation does not exist 오류#

기본적으로 Praefect 데이터베이스 테이블은 gitlab-ctl reconfigure 태스크가 자동으로 생성합니다.

다만 다음 경우에는 초기 reconfigure 시 Praefect 데이터베이스 테이블이 생성되지 않아, 관계가 존재하지 않는다는 오류가 발생할 수 있습니다.

  • gitlab-ctl reconfigure 명령을 실행하지 않은 경우.
  • 실행 중에 오류가 발생한 경우.

예를 들면 다음과 같습니다.

  • ERROR: relation "node_status" does not exist at character 13

  • ERROR: relation "replication_queue_lock" does not exist at character 40

  • 다음 오류가 있습니다.

    {"level":"error","msg":"Error updating node: pq: relation \"node_status\" does not exist","pid":210882,"praefectName":"gitlab1x4m:0.0.0.0:2305","time":"2021-04-01T19:26:19.473Z","virtual_storage":"praefect-cluster-1"}
    

이 문제는 praefect 명령의 sql-migrate 하위 명령으로 데이터베이스 스키마 마이그레이션을 수행해 해결할 수 있습니다.

$ sudo -u git -- /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-migrate
praefect sql-migrate: OK (applied 21 migrations)

'repository scoped: invalid Repository' 오류로 요청 실패#

이는 Praefect 구성에 사용한 가상 스토리지 이름이 GitLab의 gitaly['configuration'][:storage][<index>][:name] 설정에 사용한 스토리지 이름과 일치하지 않는다는 뜻입니다.

Praefect와 GitLab 구성에서 사용하는 가상 스토리지 이름을 일치시켜 해결합니다.

클라우드 플랫폼에서의 Gitaly 클러스터(Praefect) 성능 문제#

Praefect는 CPU와 메모리를 많이 쓰지 않으므로 작은 가상 머신에서도 실행할 수 있습니다. 클라우드 서비스는 디스크 IO나 네트워크 트래픽처럼 작은 VM이 사용할 수 있는 리소스에 다른 제한을 두기도 합니다.

Praefect 노드는 네트워크 트래픽을 많이 생성합니다. 클라우드 서비스가 네트워크 대역폭을 제한하면 다음과 같은 증상이 나타납니다.

  • Git 작업의 성능 저하.
  • 높은 네트워크 지연 시간.
  • Praefect의 높은 메모리 사용량.

가능한 해결 방법은 다음과 같습니다.

  • 더 큰 VM을 프로비저닝해 더 많은 네트워크 트래픽 허용량을 확보합니다.
  • 클라우드 서비스의 모니터링과 로깅으로 Praefect 노드가 트래픽 허용량을 소진하지 않는지 확인합니다.

Praefect 구성 오류로 gitlab-ctl reconfigure 실패#

gitlab-ctl reconfigure가 실패하면 다음 오류가 표시될 수 있습니다.

STDOUT: praefect: configuration error: error reading config file: toml: cannot store TOML string into a Go int

이 오류는 praefect['database_port'] 또는 praefect['database_direct_port']를 정수가 아닌 문자열로 구성했을 때 발생합니다.

일반적인 복제 오류#

다음은 일반적인 복제 오류와 해결 방법입니다.

잠금 파일 존재#

잠금 파일은 같은 ref에 여러 업데이트가 동시에 적용되는 것을 막습니다. 잠금 파일이 오래된 상태로 남으면 복제가 error: cannot lock ref 오류로 실패합니다.

오래된 *.lock 파일을 정리하려면 Rails 콘솔에서 OptimizeRepositoryRequest를 트리거할 수 있습니다.

p = Project.find <Project ID>
client = Gitlab::GitalyClient::RepositoryService.new(p.repository)
client.optimize_repository

OptimizeRepositoryRequest 트리거로 해결되지 않으면 파일을 직접 확인해 생성 날짜를 보고 *.lock 파일을 수동으로 제거할지 판단합니다. 생성된 지 24시간이 지난 잠금 파일은 안전하게 제거할 수 있습니다.

Git fsck 오류#

유효하지 않은 객체가 있는 Gitaly 리포지터리는 복제 실패로 이어지며, Gitaly 로그에 다음과 같은 오류가 남습니다.

  • exit status 128, stderr: "fatal: git upload-pack: not our ref".
  • "fatal: bad object 58....e0f... ssh://gitaly/internal.git did not send all necessary objects.

Gitaly 노드 중 하나에 정상적인 리포지터리 사본이 남아 있다면 다음 방법으로 해결할 수 있습니다.

  1. Praefect 데이터베이스에서 리포지터리를 제거합니다.
  2. Praefect track-repository 하위 명령으로 다시 추적합니다.

이렇게 하면 authoritative Gitaly 노드의 리포지터리 사본으로 나머지 모든 Gitaly 노드의 사본을 덮어씁니다. 이 명령을 실행하기 전에 리포지터리의 최근 백업을 확보합니다.

  1. 손상된 리포지터리를 다른 위치로 옮깁니다.

    run `mv  .backup`
    

    예를 들면 다음과 같습니다.

    mv /var/opt/gitlab/git-data/repositories/@cluster/repositories/de/74/2335 /var/opt/gitlab/git-data/repositories/@cluster/repositories/de/74/2335.backup
    
  2. 복제를 트리거하는 Praefect 명령을 실행합니다.

    # Validate you have the correct repository.
    sudo -u git -- /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml remove-repository -virtual-storage gitaly -relative-path '<relative_path>' -db-only
    
    # Run again with '--apply' flag to remove repository from the Praefect tracking database
    sudo -u git -- /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml remove-repository -virtual-storage gitaly -relative-path '<relative_path>' -db-only --apply
    
    # Re-track the repository, overwriting the secondary nodes
    sudo -u git -- /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml track-repository -virtual-storage gitaly -authoritative-storage '<healthy_gitaly>' -relative-path '<relative_path>' -replica-path '<replica_path>'-replicate-immediately
    

복제가 오류 없이 실패#

Praefect dataloss가 일부만 사용할 수 있는 리포지터리를 표시하고 accept-dataloss 명령이 로그에 오류를 남기지 않은 채 리포지터리 동기화에 실패하면, Praefect 데이터베이스 storage_repositories 테이블의 repository_id 필드가 일치하지 않는 것이 원인일 수 있습니다. 불일치를 확인하려면 다음을 수행합니다.

  1. Praefect 데이터베이스에 연결합니다.

  2. 다음 쿼리를 실행합니다.

    select * from storage_repositories where relative_path = '<relative-path>';
    

    <relative-path>를 @hashed로 시작하는 리포지터리 경로로 바꿉니다.

alternates 디렉터리가 존재하지 않음#

GitLab은 중복 제거에 Git alternates 메커니즘을 사용합니다. alternates는 객체를 가져올 @pool 리포지터리의 objects 디렉터리를 가리키는 텍스트 파일입니다. 이 파일이 잘못된 경로를 가리키면 복제가 다음 오류 중 하나로 실패할 수 있습니다.

  • "error":"no alternates directory exists", "warning","msg":"alternates file does not point to valid git repository"
  • "error":"unexpected alternates content:
  • remote: error: unable to normalize alternate object path

이 오류의 원인을 조사하려면 다음을 수행합니다.

  1. Rails 콘솔에서 프로젝트가 풀에 속하는지 확인합니다.

    project = Project.find_by_id(<project id>)
    project.pool_repository
    
  2. 풀 리포지터리 경로가 디스크에 존재하고 alternates 파일 내용과 일치하는지 확인합니다.

  3. alternates 파일의 경로가 프로젝트의 objects 디렉터리에서 도달 가능한지 확인합니다.

이 확인을 마친 뒤 수집한 정보를 가지고 GitLab 지원에 문의합니다.

리포지터리 스토리지 이동 실패 후 프로젝트가 읽기 전용 상태로 남는 문제#

Sidekiq Pod에 Horizontal Pod Autoscaler(HPA)를 사용하면 job 실행 중 Pod 스케일링 때문에 리포지터리 스토리지 이동이 오류 없이 실패할 수 있습니다. 이 문제로 리포지터리 스토리지 이동이 실패하면 해당 프로젝트가 읽기 전용 상태로 남습니다.

영향을 받은 리포지터리를 복구하려면 다음을 수행합니다.

  1. 영향을 받은 프로젝트를 읽기·쓰기 상태로 되돌립니다.
  2. Sidekiq Pod의 HPA를 비활성화합니다
  3. 개별 프로젝트에 대해 REST API로 스토리지 이동을 다시 실행합니다.
  4. 마이그레이션이 완료된 후 HPA 구성을 복원합니다.