Gitaly 클러스터(Praefect) 문제 해결
GitLab v19.4Offering: 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가 올바르게 동작하려면 데이터베이스 마이그레이션이 최신 상태여야 합니다.
이 검사가 실패하면 다음을 수행합니다.
- 데이터베이스의
schema_migrations테이블에서 어떤 마이그레이션이 실행됐는지 확인합니다. praefect sql-migrate를 실행해 마이그레이션을 최신 상태로 만듭니다.
노드 연결 및 디스크 액세스#
Praefect가 모든 Gitaly 노드에 연결할 수 있는지, 각 Gitaly 노드가 모든 스토리지에 읽기·쓰기 권한이 있는지 확인합니다.
이 검사가 실패하면 다음을 수행합니다.
- 네트워크 주소와 토큰이 올바르게 설정돼 있는지 확인합니다.
- Praefect 구성에서 확인합니다.
- 각 Gitaly 노드의 구성에서 확인합니다.
- Gitaly 노드에서
gitaly프로세스가git사용자로 실행되는지 확인합니다. 권한 문제로 Gitaly가 스토리지 디렉터리에 액세스하지 못할 수 있습니다. - Praefect와 Gitaly 노드를 연결하는 네트워크에 문제가 없는지 확인합니다.
데이터베이스 읽기 및 쓰기 액세스#
Praefect가 데이터베이스를 읽고 쓸 수 있는지 확인합니다.
이 검사가 실패하면 다음을 수행합니다.
-
Praefect 데이터베이스가 복구 모드인지 확인합니다. 복구 모드에서는 테이블이 읽기 전용일 수 있습니다. 확인하려면 다음을 실행합니다.
select pg_is_in_recovery() -
Praefect가 PostgreSQL에 연결할 때 사용하는 사용자에게 데이터베이스 읽기·쓰기 권한이 있는지 확인합니다.
-
데이터베이스가 읽기 전용 모드로 설정됐는지 확인합니다. 확인하려면 다음을 실행합니다.
show default_transaction_read_only
액세스할 수 없는 리포지터리#
프라이머리 할당이 없거나 프라이머리를 사용할 수 없어 액세스할 수 없는 리포지터리 수를 확인합니다.
이 검사가 실패하면 다음을 수행합니다.
- 다운된 Gitaly 노드가 있는지 확인합니다.
praefect ping-nodes를 실행해 확인합니다. - 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']스토리지 구성이 없습니다.
- GitLab 서버에서
Unable to save project. Error: 7:permission denied- GitLab 서버의
praefect['configuration'][:virtual_storage]에 있는 시크릿 토큰이 하나 이상의 Gitaly 서버에 설정된gitaly['auth_token']값과 일치하지 않습니다.
- GitLab 서버의
- 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 하위 명령으로 메타데이터를 확인합니다.
리포지터리 메타데이터는 다음 두 가지 값으로 조회할 수 있습니다.
- 가상 스토리지와 상대 경로.
- Praefect가 할당한 리포지터리 ID.
가상 스토리지와 상대 경로로 리포지터리 메타데이터를 조회하려면 다음을 수행합니다.
-
오른쪽 위에서 Admin을 선택합니다.
-
왼쪽 사이드바에서 Overview > Projects를 선택한 다음 프로젝트를 선택합니다.
-
프로젝트의 Storage name과 Relative path 값을 기록합니다.
-
이 값으로 다음 명령을 실행합니다.
sudo -u git -- /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml metadata -virtual-storage <virtual-storage> -relative-path <relative-path>
리포지터리 ID는 프로젝트 ID와 다릅니다.
Praefect가 할당한 리포지터리 ID로 리포지터리 메타데이터를 조회하려면 다음을 수행합니다.
-
리포지터리 레플리카 경로의 마지막 구성 요소를 기록합니다. 예를 들어
@cluster/repositories/6f/96/54771에서 리포지터리 ID는54771입니다. -
그 값으로 다음 명령을 실행합니다.
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 노드 중 하나에 정상적인 리포지터리 사본이 남아 있다면 다음 방법으로 해결할 수 있습니다.
이렇게 하면 authoritative Gitaly 노드의 리포지터리 사본으로 나머지 모든 Gitaly 노드의 사본을 덮어씁니다. 이 명령을 실행하기 전에 리포지터리의 최근 백업을 확보합니다.
-
손상된 리포지터리를 다른 위치로 옮깁니다.
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 -
복제를 트리거하는 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 필드가 일치하지 않는 것이 원인일 수 있습니다. 불일치를 확인하려면 다음을 수행합니다.
-
Praefect 데이터베이스에 연결합니다.
-
다음 쿼리를 실행합니다.
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
이 오류의 원인을 조사하려면 다음을 수행합니다.
-
Rails 콘솔에서 프로젝트가 풀에 속하는지 확인합니다.
project = Project.find_by_id(<project id>) project.pool_repository -
풀 리포지터리 경로가 디스크에 존재하고
alternates파일 내용과 일치하는지 확인합니다. -
alternates파일의 경로가 프로젝트의objects디렉터리에서 도달 가능한지 확인합니다.
이 확인을 마친 뒤 수집한 정보를 가지고 GitLab 지원에 문의합니다.
리포지터리 스토리지 이동 실패 후 프로젝트가 읽기 전용 상태로 남는 문제#
Sidekiq Pod에 Horizontal Pod Autoscaler(HPA)를 사용하면 job 실행 중 Pod 스케일링 때문에 리포지터리 스토리지 이동이 오류 없이 실패할 수 있습니다. 이 문제로 리포지터리 스토리지 이동이 실패하면 해당 프로젝트가 읽기 전용 상태로 남습니다.
영향을 받은 리포지터리를 복구하려면 다음을 수행합니다.
- 영향을 받은 프로젝트를 읽기·쓰기 상태로 되돌립니다.
- Sidekiq Pod의 HPA를 비활성화합니다
- 개별 프로젝트에 대해 REST API로 스토리지 이동을 다시 실행합니다.
- 마이그레이션이 완료된 후 HPA 구성을 복원합니다.