InfoGrab DocsInfoGrab Docs

GitLab 백업 문제 해결

요약

GitLab을 백업할 때 다음 문제가 발생할 수 있습니다. 시크릿 파일을 백업하지 않았다면 GitLab이 다시 올바르게 작동하도록 여러 단계를 완료해야 합니다. 시크릿 파일은 필수 민감 정보가 담긴 칼럼의 암호화 키를 저장하는 역할을 합니다.

GitLab을 백업할 때 다음 문제가 발생할 수 있습니다.

시크릿 파일이 손실된 경우#

시크릿 파일을 백업하지 않았다면 GitLab이 다시 올바르게 작동하도록 여러 단계를 완료해야 합니다.

시크릿 파일은 필수 민감 정보가 담긴 칼럼의 암호화 키를 저장하는 역할을 합니다. 키가 손실되면 GitLab은 해당 칼럼을 복호화할 수 없어 다음 항목에 대한 액세스가 차단됩니다:

CI/CD 변수와 러너 인증 같은 경우에는 다음과 같은 예기치 않은 동작이 발생할 수 있습니다:

  • 중단된 job.
  • 500 오류.

이 경우 CI/CD 변수와 러너 인증의 모든 토큰을 재설정해야 하며, 이에 대한 자세한 내용은 다음 섹션에서 설명합니다. 토큰을 재설정하면 프로젝트에 접속할 수 있고 job이 다시 실행되기 시작합니다.

Warning

이 섹션의 단계는 앞에서 나열한 항목에서 데이터 손실로 이어질 수 있습니다. Premium 또는 Ultimate 고객이라면 지원 요청 등록을 고려합니다.

모든 값을 복호화할 수 있는지 확인#

Rake 태스크를 사용하여 데이터베이스에 복호화할 수 없는 값이 있는지 확인할 수 있습니다.

백업 만들기#

손실된 시크릿 파일을 우회하려면 GitLab 데이터를 직접 수정해야 합니다.

Warning

어떤 변경이든 시도하기 전에 반드시 전체 데이터베이스 백업을 만듭니다.

사용자 이중 인증(2FA) 비활성화#

2FA가 활성화된 사용자는 GitLab에 로그인할 수 없습니다. 이 경우 모든 사용자에 대해 2FA를 비활성화해야 하며, 그 후 사용자는 2FA를 다시 활성화해야 합니다.

CI/CD 변수 재설정#

  1. 데이터베이스 콘솔로 진입합니다:

    Linux 패키지(Omnibus)의 경우:

    sudo gitlab-rails dbconsole --database main
    

    자체 컴파일 설치의 경우:

    sudo -u git -H bundle exec rails dbconsole -e production --database main
    
  2. ci_group_variables 및 ci_variables 테이블을 살펴봅니다:

    SELECT * FROM public."ci_group_variables";
    SELECT * FROM public."ci_variables";
    

    이들이 삭제해야 하는 변수입니다.

  3. 모든 변수를 삭제합니다:

    DELETE FROM ci_group_variables;
    DELETE FROM ci_variables;
    
  4. 변수를 삭제할 특정 그룹이나 프로젝트를 알고 있다면 DELETE에 WHERE 문을 포함해 이를 지정할 수 있습니다:

    DELETE FROM ci_group_variables WHERE group_id = <GROUPID>;
    DELETE FROM ci_variables WHERE project_id = <PROJECTID>;
    

변경 사항을 적용하려면 GitLab을 재구성하거나 재시작해야 할 수 있습니다.

러너 등록 토큰 재설정#

  1. 데이터베이스 콘솔로 진입합니다:

    Linux 패키지(Omnibus)의 경우:

    sudo gitlab-rails dbconsole --database main
    

    자체 컴파일 설치의 경우:

    sudo -u git -H bundle exec rails dbconsole -e production --database main
    
  2. 프로젝트, 그룹, 인스턴스 전체의 모든 토큰을 지웁니다:

    [!warning] 마지막 UPDATE 작업은 러너가 새 job을 가져올 수 없게 만듭니다. 새 러너를 등록해야 합니다.

    -- Clear project tokens
    UPDATE projects SET runners_token = null, runners_token_encrypted = null;
    -- Clear group tokens
    UPDATE namespaces SET runners_token = null, runners_token_encrypted = null;
    -- Clear instance tokens
    UPDATE application_settings SET runners_registration_token_encrypted = null;
    -- Clear key used for JWT authentication
    -- This may break the $CI_JWT_TOKEN job variable:
    -- https://gitlab.com/gitlab-org/gitlab/-/issues/325965
    UPDATE application_settings SET encrypted_ci_jwt_signing_key = null;
    -- Clear runner tokens
    UPDATE ci_runners SET token_encrypted = null;
    

대기 중인 파이프라인 job 재설정#

  1. 데이터베이스 콘솔로 진입합니다:

    Linux 패키지(Omnibus)의 경우:

    sudo gitlab-rails dbconsole --database main
    

    자체 컴파일 설치의 경우:

    sudo -u git -H bundle exec rails dbconsole -e production --database main
    
  2. 대기 중인 job의 모든 토큰을 지웁니다:

    -- Clear build tokens
    UPDATE p_ci_builds SET token_encrypted = null;
    

나머지 기능에도 비슷한 전략을 사용할 수 있습니다. 복호화할 수 없는 데이터를 제거하면 GitLab을 다시 운영 상태로 되돌릴 수 있고, 손실된 데이터는 수동으로 대체할 수 있습니다.

통합 및 웹훅 수정#

시크릿을 손실했다면 통합 설정 및 웹훅 설정 페이지에 500 오류 메시지가 표시될 수 있습니다. 이전에 구성한 통합이나 웹훅이 있는 프로젝트에서 리포지터리에 액세스하려고 할 때도 손실된 시크릿이 500 오류를 일으킬 수 있습니다.

해결 방법은 영향을 받은 테이블(암호화된 칼럼이 있는 테이블)을 잘라내는 것입니다. 이렇게 하면 구성한 모든 통합, 웹훅, 관련 메타데이터가 삭제됩니다. 데이터를 삭제하기 전에 시크릿이 근본 원인인지 확인해야 합니다.

  1. 데이터베이스 콘솔로 진입합니다:

    Linux 패키지(Omnibus)의 경우:

    sudo gitlab-rails dbconsole --database main
    

    자체 컴파일 설치의 경우:

    sudo -u git -H bundle exec rails dbconsole -e production --database main
    
  2. 다음 테이블을 잘라냅니다:

    -- truncate web_hooks table
    TRUNCATE integrations, chat_names, issue_tracker_data, jira_tracker_data, slack_integrations, web_hooks, zentao_tracker_data, web_hook_logs_daily CASCADE;
    

컨테이너 레지스트리가 복원되지 않음#

컨테이너 레지스트리를 사용하는 환경의 백업을 컨테이너 레지스트리가 활성화되지 않은 새로 설치한 환경으로 복원하면 컨테이너 레지스트리는 복원되지 않습니다.

컨테이너 레지스트리까지 복원하려면 백업을 복원하기 전에 새 환경에서 이를 활성화해야 합니다.

백업에서 복원한 후 컨테이너 레지스트리 푸시 실패#

컨테이너 레지스트리를 사용하는 경우, Linux 패키지(Omnibus) 인스턴스에서 레지스트리 데이터를 복원한 다음 백업을 복원하면 레지스트리로의 푸시가 실패할 수 있습니다.

이러한 실패는 레지스트리 로그에서 다음과 비슷한 권한 문제로 나타납니다:

level=error
msg="response completed with error"
err.code=unknown
err.detail="filesystem: mkdir /var/opt/gitlab/gitlab-rails/shared/registry/docker/registry/v2/repositories/...: permission denied"
err.message="unknown error"

이 문제는 권한이 없는 git 사용자로 복원이 실행되어, 복원 과정에서 레지스트리 파일에 올바른 소유권을 할당할 수 없기 때문에 발생합니다(이슈 #62759).

레지스트리를 다시 작동시키려면:

sudo chown -R registry:registry /var/opt/gitlab/gitlab-rails/shared/registry/docker

레지스트리의 기본 파일 시스템 위치를 변경했다면 /var/opt/gitlab/gitlab-rails/shared/registry/docker 대신 사용자 지정 위치를 대상으로 chown을 실행합니다.

Gzip 오류로 인한 백업 완료 실패#

백업을 실행할 때 Gzip 오류 메시지가 표시될 수 있습니다:

sudo /opt/gitlab/bin/gitlab-backup create
...
Dumping ...
...
gzip: stdout: Input/output error

Backup failed

이런 경우 다음을 확인합니다:

  • Gzip 작업에 충분한 디스크 공간이 있는지 확인합니다. 기본 전략을 사용하는 백업이 백업 생성 중에 인스턴스 크기의 절반에 해당하는 여유 디스크 공간을 요구하는 일은 드물지 않습니다.
  • NFS를 사용하는 경우 마운트 옵션 timeout이 설정되어 있는지 확인합니다. 기본값은 600이며, 이를 더 작은 값으로 변경하면 이 오류가 발생합니다.

File name too long 오류로 인한 백업 실패#

백업 중에 File name too long 오류가 발생할 수 있습니다(이슈 #354984). 예를 들면 다음과 같습니다:

Problem: <class 'OSError: [Errno 36] File name too long:

이 문제로 인해 백업 스크립트가 완료되지 못합니다. 이 문제를 해결하려면 문제를 일으키는 파일 이름을 잘라내야 합니다. 파일 확장자를 포함해 최대 246자까지 허용됩니다.

Warning

이 섹션의 단계는 데이터 손실로 이어질 수 있습니다. 모든 단계를 주어진 순서대로 엄격히 따라야 합니다. Premium 또는 Ultimate 고객이라면 지원 요청 등록을 고려합니다.

오류를 해결하기 위해 파일 이름을 잘라내는 작업은 다음으로 구성됩니다:

  • 데이터베이스에서 추적되지 않는 원격 업로드 파일 정리.
  • 데이터베이스의 파일 이름 잘라내기.
  • 백업 태스크 다시 실행.

원격 업로드된 파일 정리#

알려진 이슈로 인해 상위 리소스가 삭제된 후에도 오브젝트 스토어 업로드가 남아 있었습니다. 이 이슈는 해결되었습니다.

이러한 파일을 바로잡으려면 스토리지에는 있지만 uploads 데이터베이스 테이블에서 추적되지 않는 모든 원격 업로드 파일을 정리해야 합니다.

  1. GitLab 데이터베이스에 존재하지 않는 경우 lost and found 디렉터리로 이동할 수 있는 모든 오브젝트 스토어 업로드 파일을 나열합니다:

    bundle exec rake gitlab:cleanup:remote_upload_files RAILS_ENV=production
    
  2. 이러한 파일을 삭제하고 참조되지 않는 모든 업로드 파일을 제거하려는 것이 확실하면 다음을 실행합니다:

    [!warning] 다음 작업은 되돌릴 수 없습니다.

    bundle exec rake gitlab:cleanup:remote_upload_files RAILS_ENV=production DRY_RUN=false
    

데이터베이스에서 참조하는 파일 이름 잘라내기#

문제를 일으키는, 데이터베이스가 참조하는 파일을 잘라내야 합니다. 데이터베이스가 참조하는 파일 이름은 다음에 저장됩니다:

  • uploads 테이블.
  • 발견된 참조. 다른 데이터베이스 테이블과 칼럼에서 발견된 모든 참조.
  • 파일 시스템.

uploads 테이블의 파일 이름을 잘라냅니다:

  1. 데이터베이스 콘솔로 진입합니다:

    Linux 패키지(Omnibus)의 경우:

    sudo gitlab-rails dbconsole --database main
    

    자체 컴파일 설치의 경우:

    sudo -u git -H bundle exec rails dbconsole -e production --database main
    
  2. uploads 테이블에서 246자보다 긴 파일 이름을 검색합니다:

    다음 쿼리는 파일 이름이 246자보다 긴 uploads 레코드를 0에서 10000까지의 배치로 선택합니다. 이렇게 하면 레코드가 수천 개인 테이블을 가진 대규모 GitLab 인스턴스에서 성능이 향상됩니다.

    CREATE TEMP TABLE uploads_with_long_filenames AS
    SELECT ROW_NUMBER() OVER(ORDER BY id) row_id, id, path
    FROM uploads AS u
    WHERE LENGTH((regexp_match(u.path, '[^\\/:*?"<>|\r\n]+$'))[1]) > 246;
    
    CREATE INDEX ON uploads_with_long_filenames(row_id);
    
    SELECT
       u.id,
       u.path,
       -- Current filename
       (regexp_match(u.path, '[^\\/:*?"<>|\r\n]+$'))[1] AS current_filename,
       -- New filename
       CONCAT(
          LEFT(SPLIT_PART((regexp_match(u.path, '[^\\/:*?"<>|\r\n]+$'))[1], '.', 1), 242),
          COALESCE(SUBSTRING((regexp_match(u.path, '[^\\/:*?"<>|\r\n]+$'))[1] FROM '\.(?:.(?!\.))+$'))
       ) AS new_filename,
       -- New path
       CONCAT(
          COALESCE((regexp_match(u.path, '(.*\/).*'))[1], ''),
          CONCAT(
             LEFT(SPLIT_PART((regexp_match(u.path, '[^\\/:*?"<>|\r\n]+$'))[1], '.', 1), 242),
             COALESCE(SUBSTRING((regexp_match(u.path, '[^\\/:*?"<>|\r\n]+$'))[1] FROM '\.(?:.(?!\.))+$'))
          )
       ) AS new_path
    FROM uploads_with_long_filenames AS u
    WHERE u.row_id > 0 AND u.row_id <= 10000;
    

    출력 예시:

    -[ RECORD 1 ]----+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    id               | 34
    path             | public/@hashed/loremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelitsedvulputatemisitloremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelitsedvulputatemisit.txt
    current_filename | loremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelitsedvulputatemisitloremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelitsedvulputatemisit.txt
    new_filename     | loremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelitsedvulputatemisitloremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelits.txt
    new_path         | public/@hashed/loremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelitsedvulputatemisitloremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelits.txt
    

    각 항목의 의미는 다음과 같습니다:

    • current_filename: 246자보다 긴 파일 이름.
    • new_filename: 최대 246자로 잘린 파일 이름.
    • new_path: 잘린 new_filename을 고려한 새 경로.

    배치 결과를 검증한 후에는 다음 숫자 순서(10000에서 20000)를 사용하여 배치 크기(row_id)를 변경해야 합니다. uploads 테이블의 마지막 레코드에 도달할 때까지 이 과정을 반복합니다.

  3. uploads 테이블에서 찾은 파일의 긴 파일 이름을 새로 잘린 파일 이름으로 변경합니다. 다음 쿼리는 업데이트를 롤백하므로 트랜잭션 래퍼 안에서 결과를 안전하게 확인할 수 있습니다:

    CREATE TEMP TABLE uploads_with_long_filenames AS
    SELECT ROW_NUMBER() OVER(ORDER BY id) row_id, path, id
    FROM uploads AS u
    WHERE LENGTH((regexp_match(u.path, '[^\\/:*?"<>|\r\n]+$'))[1]) > 246;
    
    CREATE INDEX ON uploads_with_long_filenames(row_id);
    
    BEGIN;
    WITH updated_uploads AS (
       UPDATE uploads
       SET
          path =
          CONCAT(
             COALESCE((regexp_match(updatable_uploads.path, '(.*\/).*'))[1], ''),
             CONCAT(
                LEFT(SPLIT_PART((regexp_match(updatable_uploads.path, '[^\\/:*?"<>|\r\n]+$'))[1], '.', 1), 242),
                COALESCE(SUBSTRING((regexp_match(updatable_uploads.path, '[^\\/:*?"<>|\r\n]+$'))[1] FROM '\.(?:.(?!\.))+$'))
             )
          )
       FROM
          uploads_with_long_filenames AS updatable_uploads
       WHERE
          uploads.id = updatable_uploads.id
       AND updatable_uploads.row_id > 0 AND updatable_uploads.row_id  <= 10000
       RETURNING uploads.*
    )
    SELECT id, path FROM updated_uploads;
    ROLLBACK;
    

    배치 업데이트 결과를 검증한 후에는 다음 숫자 순서(10000에서 20000)를 사용하여 배치 크기(row_id)를 변경해야 합니다. uploads 테이블의 마지막 레코드에 도달할 때까지 이 과정을 반복합니다.

  4. 이전 쿼리에서 얻은 새 파일 이름이 예상한 것인지 검증합니다. 이전 단계에서 찾은 레코드를 246자로 잘라내려는 것이 확실하면 다음을 실행합니다:

    [!warning] 다음 작업은 되돌릴 수 없습니다.

    CREATE TEMP TABLE uploads_with_long_filenames AS
    SELECT ROW_NUMBER() OVER(ORDER BY id) row_id, path, id
    FROM uploads AS u
    WHERE LENGTH((regexp_match(u.path, '[^\\/:*?"<>|\r\n]+$'))[1]) > 246;
    
    CREATE INDEX ON uploads_with_long_filenames(row_id);
    
    UPDATE uploads
    SET
    path =
       CONCAT(
          COALESCE((regexp_match(updatable_uploads.path, '(.*\/).*'))[1], ''),
          CONCAT(
             LEFT(SPLIT_PART((regexp_match(updatable_uploads.path, '[^\\/:*?"<>|\r\n]+$'))[1], '.', 1), 242),
             COALESCE(SUBSTRING((regexp_match(updatable_uploads.path, '[^\\/:*?"<>|\r\n]+$'))[1] FROM '\.(?:.(?!\.))+$'))
          )
       )
    FROM
    uploads_with_long_filenames AS updatable_uploads
    WHERE
    uploads.id = updatable_uploads.id
    AND updatable_uploads.row_id > 0 AND updatable_uploads.row_id  <= 10000;
    

    배치 업데이트를 마친 후에는 다음 숫자 순서(10000에서 20000)를 사용하여 배치 크기(updatable_uploads.row_id)를 변경해야 합니다. uploads 테이블의 마지막 레코드에 도달할 때까지 이 과정을 반복합니다.

발견된 참조에서 파일 이름을 잘라냅니다:

  1. 해당 레코드가 어딘가에서 참조되는지 확인합니다. 한 가지 방법은 데이터베이스를 덤프한 다음 상위 디렉터리 이름과 파일 이름을 검색하는 것입니다:

    1. 데이터베이스를 덤프하려면 예시로 다음 명령을 사용할 수 있습니다:

      pg_dump -h /var/opt/gitlab/postgresql/ -d gitlabhq_production > gitlab-dump.tmp
      
    2. 그 다음 grep 명령으로 참조를 검색할 수 있습니다. 상위 디렉터리와 파일 이름을 조합하는 것이 좋습니다. 예를 들면 다음과 같습니다:

      grep public/alongfilenamehere.txt gitlab-dump.tmp
      
  2. uploads 테이블을 쿼리하여 얻은 새 파일 이름으로 이러한 긴 파일 이름을 교체합니다.

파일 시스템의 파일 이름을 잘라냅니다. 파일 시스템의 파일을 uploads 테이블을 쿼리하여 얻은 새 파일 이름으로 수동으로 변경해야 합니다.

백업 태스크 다시 실행#

이전의 모든 단계를 따른 후 백업 태스크를 다시 실행합니다.

오류: 대규모 리포지터리의 서버 측 백업 중 pack-objects died#

서버 측 백업(REPOSITORIES_SERVER_SIDE=true)을 실행할 때 하나 이상의 대규모 리포지터리가 계속 실패하면 다음 오류가 표시될 수 있습니다:

backup repository: manager: write bundle: local repository: create bundle:
create bundle: exit status 1: stderr: "error: pack-objects died"

다음과 같은 경우에도 이 오류가 표시될 수 있습니다:

  • 같은 프로젝트의 더 작은 리포지터리와 위키 리포지터리는 성공적으로 백업됩니다.
  • Gitaly 노드에서 git fsck --full과 git bundle create를 수동으로 실행하면 오류 없이 통과합니다.

이 문제는 Gitaly가 멀티파트 업로드를 사용하여 리포지터리 번들을 오브젝트 스토리지로 직접 스트리밍하기 때문에 발생합니다.

기본적으로 파트 크기는 5MB이고, AWS S3(및 호환 공급자)는 멀티파트 업로드당 최대 10,000개의 파트를 허용합니다.

이 기본값에서 실질적인 최대 번들 크기는 약 50GB입니다. 리포지터리의 번들이 이 한도를 초과하면 오브젝트 스토리지 공급자가 업로드를 중단합니다. 이 중단은 git pack-objects로 이어지는 파이프를 끊고, 그 결과 업로드 실패가 아니라 오해를 일으키는 pack-objects died 오류가 보고됩니다.

이 문제를 해결하려면 Gitaly 백업 구성에서 buffer_size를 늘립니다. 실질적인 최대 번들 크기는 buffer_size × 10,000이므로, 가장 큰 리포지터리를 수용할 수 있는 값으로 buffer_size를 설정합니다.

  1. Gitaly 노드에서 가장 큰 팩파일을 식별하려면:

    sudo find /var/opt/gitlab/git-data -name "*.pack" -size +10G -ls
    
  2. buffer_size를 설정합니다.

  3. 각 Gitaly 노드에서 /etc/gitlab/gitlab.rb를 편집하고 기존 백업 구성에 buffer_size를 추가합니다:

    gitaly['configuration'] = {
      backup: {
        go_cloud_url: 's3://<bucket>?region=<region>',
        # Increase part size to raise the S3 multipart upload limit.
        # buffer_size (bytes) x 10,000 = effective maximum bundle size.
        # Example: 10485760 (10 MB) supports bundles up to ~100 GB.
        buffer_size: 10485760,
      },
    }
    
    1. 각 노드에서 Gitaly를 재구성하고 재시작합니다:

      sudo gitlab-ctl reconfigure
      sudo gitlab-ctl restart gitaly
      
  1. Helm 값을 내보냅니다:

    helm get values gitlab > gitlab_values.yaml
    
    1. gitlab_values.yaml을 편집하고 필요한 값을 설정합니다:

      gitlab:
        gitaly:
          configuration:
            backup:
              buffer_size: 10485760
      
    2. 파일을 저장하고 새 값을 적용합니다:

      helm upgrade -f gitlab_values.yaml gitlab gitlab/gitlab
      

pg_stat_statements가 이전에 활성화된 경우 데이터베이스 백업 복원 실패#

PostgreSQL 데이터베이스의 GitLab 백업에는 데이터베이스에서 이전에 활성화된 확장을 활성화하는 데 필요한 모든 SQL 문이 포함됩니다.

pg_stat_statements 확장은 superuser 역할을 가진 PostgreSQL 사용자만 활성화하거나 비활성화할 수 있습니다. 복원 프로세스는 권한이 제한된 데이터베이스 사용자를 사용하므로 다음 SQL 문을 실행할 수 없습니다:

DROP EXTENSION IF EXISTS pg_stat_statements;
CREATE EXTENSION IF NOT EXISTS pg_stat_statements WITH SCHEMA public;

pg_stat_statements 확장이 없는 PostgreSQL 인스턴스에서 백업을 복원하려고 하면 다음 오류 메시지가 표시됩니다:

ERROR: permission denied to create extension "pg_stat_statements"
HINT: Must be superuser to create this extension.
ERROR: extension "pg_stat_statements" does not exist

pg_stat_statements 확장이 활성화된 인스턴스에서 복원하려고 하면 정리 단계가 다음과 비슷한 오류 메시지와 함께 실패합니다:

rake aborted!
ActiveRecord::StatementInvalid: PG::InsufficientPrivilege: ERROR: must be owner of view pg_stat_statements
/opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/db.rake:42:in `block (4 levels) in <top (required)>'
/opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/db.rake:41:in `each'
/opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/db.rake:41:in `block (3 levels) in <top (required)>'
/opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/backup.rake:71:in `block (3 levels) in <top (required)>'
/opt/gitlab/embedded/bin/bundle:23:in `load'
/opt/gitlab/embedded/bin/bundle:23:in `<main>'
Caused by:
PG::InsufficientPrivilege: ERROR: must be owner of view pg_stat_statements
/opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/db.rake:42:in `block (4 levels) in <top (required)>'
/opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/db.rake:41:in `each'
/opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/db.rake:41:in `block (3 levels) in <top (required)>'
/opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/backup.rake:71:in `block (3 levels) in <top (required)>'
/opt/gitlab/embedded/bin/bundle:23:in `load'
/opt/gitlab/embedded/bin/bundle:23:in `<main>'
Tasks: TOP => gitlab:db:drop_tables
(See full trace by running task with --trace)

덤프 파일에 pg_stat_statements가 포함되지 않도록 방지#

백업 번들에 포함되는 PostgreSQL 덤프 파일에 이 확장이 포함되지 않도록 하려면 public 스키마를 제외한 다른 스키마에서 확장을 활성화합니다:

CREATE SCHEMA adm;
CREATE EXTENSION pg_stat_statements SCHEMA adm;

확장이 이전에 public 스키마에서 활성화되었다면 새 스키마로 이동합니다:

CREATE SCHEMA adm;
ALTER EXTENSION pg_stat_statements SET SCHEMA adm;

스키마를 변경한 후 pg_stat_statements 데이터를 쿼리하려면 뷰 이름 앞에 새 스키마를 붙입니다:

SELECT * FROM adm.pg_stat_statements limit 0;

public 스키마에서 활성화되어 있기를 기대하는 서드파티 모니터링 솔루션과 호환되게 하려면 이를 search_path에 포함해야 합니다:

set search_path to public,adm;

pg_stat_statements에 대한 참조를 제거하도록 기존 덤프 파일 수정#

기존 백업 파일을 수정하려면 다음과 같이 변경합니다:

  1. 백업에서 다음 파일을 추출합니다: db/database.sql.gz.

  2. 파일의 압축을 풀거나, 압축된 상태로 처리할 수 있는 편집기를 사용합니다.

  3. 다음 줄 또는 비슷한 줄을 제거합니다:

    CREATE EXTENSION IF NOT EXISTS pg_stat_statements WITH SCHEMA public;
    
    COMMENT ON EXTENSION pg_stat_statements IS 'track planning and execution statistics of all SQL statements executed';
    
  4. 변경 사항을 저장하고 파일을 다시 압축합니다.

  5. 수정된 db/database.sql.gz로 백업 파일을 업데이트합니다.

GitLab 백업 문제 해결

GitLab v19.5
원문 보기

요약

GitLab을 백업할 때 다음 문제가 발생할 수 있습니다. 시크릿 파일을 백업하지 않았다면 GitLab이 다시 올바르게 작동하도록 여러 단계를 완료해야 합니다. 시크릿 파일은 필수 민감 정보가 담긴 칼럼의 암호화 키를 저장하는 역할을 합니다.

GitLab을 백업할 때 다음 문제가 발생할 수 있습니다.

시크릿 파일이 손실된 경우#

시크릿 파일을 백업하지 않았다면 GitLab이 다시 올바르게 작동하도록 여러 단계를 완료해야 합니다.

시크릿 파일은 필수 민감 정보가 담긴 칼럼의 암호화 키를 저장하는 역할을 합니다. 키가 손실되면 GitLab은 해당 칼럼을 복호화할 수 없어 다음 항목에 대한 액세스가 차단됩니다:

CI/CD 변수와 러너 인증 같은 경우에는 다음과 같은 예기치 않은 동작이 발생할 수 있습니다:

  • 중단된 job.
  • 500 오류.

이 경우 CI/CD 변수와 러너 인증의 모든 토큰을 재설정해야 하며, 이에 대한 자세한 내용은 다음 섹션에서 설명합니다. 토큰을 재설정하면 프로젝트에 접속할 수 있고 job이 다시 실행되기 시작합니다.

Warning

이 섹션의 단계는 앞에서 나열한 항목에서 데이터 손실로 이어질 수 있습니다. Premium 또는 Ultimate 고객이라면 지원 요청 등록을 고려합니다.

모든 값을 복호화할 수 있는지 확인#

Rake 태스크를 사용하여 데이터베이스에 복호화할 수 없는 값이 있는지 확인할 수 있습니다.

백업 만들기#

손실된 시크릿 파일을 우회하려면 GitLab 데이터를 직접 수정해야 합니다.

Warning

어떤 변경이든 시도하기 전에 반드시 전체 데이터베이스 백업을 만듭니다.

사용자 이중 인증(2FA) 비활성화#

2FA가 활성화된 사용자는 GitLab에 로그인할 수 없습니다. 이 경우 모든 사용자에 대해 2FA를 비활성화해야 하며, 그 후 사용자는 2FA를 다시 활성화해야 합니다.

CI/CD 변수 재설정#

  1. 데이터베이스 콘솔로 진입합니다:

    Linux 패키지(Omnibus)의 경우:

    sudo gitlab-rails dbconsole --database main
    

    자체 컴파일 설치의 경우:

    sudo -u git -H bundle exec rails dbconsole -e production --database main
    
  2. ci_group_variables 및 ci_variables 테이블을 살펴봅니다:

    SELECT * FROM public."ci_group_variables";
    SELECT * FROM public."ci_variables";
    

    이들이 삭제해야 하는 변수입니다.

  3. 모든 변수를 삭제합니다:

    DELETE FROM ci_group_variables;
    DELETE FROM ci_variables;
    
  4. 변수를 삭제할 특정 그룹이나 프로젝트를 알고 있다면 DELETE에 WHERE 문을 포함해 이를 지정할 수 있습니다:

    DELETE FROM ci_group_variables WHERE group_id = <GROUPID>;
    DELETE FROM ci_variables WHERE project_id = <PROJECTID>;
    

변경 사항을 적용하려면 GitLab을 재구성하거나 재시작해야 할 수 있습니다.

러너 등록 토큰 재설정#

  1. 데이터베이스 콘솔로 진입합니다:

    Linux 패키지(Omnibus)의 경우:

    sudo gitlab-rails dbconsole --database main
    

    자체 컴파일 설치의 경우:

    sudo -u git -H bundle exec rails dbconsole -e production --database main
    
  2. 프로젝트, 그룹, 인스턴스 전체의 모든 토큰을 지웁니다:

    [!warning] 마지막 UPDATE 작업은 러너가 새 job을 가져올 수 없게 만듭니다. 새 러너를 등록해야 합니다.

    -- Clear project tokens
    UPDATE projects SET runners_token = null, runners_token_encrypted = null;
    -- Clear group tokens
    UPDATE namespaces SET runners_token = null, runners_token_encrypted = null;
    -- Clear instance tokens
    UPDATE application_settings SET runners_registration_token_encrypted = null;
    -- Clear key used for JWT authentication
    -- This may break the $CI_JWT_TOKEN job variable:
    -- https://gitlab.com/gitlab-org/gitlab/-/issues/325965
    UPDATE application_settings SET encrypted_ci_jwt_signing_key = null;
    -- Clear runner tokens
    UPDATE ci_runners SET token_encrypted = null;
    

대기 중인 파이프라인 job 재설정#

  1. 데이터베이스 콘솔로 진입합니다:

    Linux 패키지(Omnibus)의 경우:

    sudo gitlab-rails dbconsole --database main
    

    자체 컴파일 설치의 경우:

    sudo -u git -H bundle exec rails dbconsole -e production --database main
    
  2. 대기 중인 job의 모든 토큰을 지웁니다:

    -- Clear build tokens
    UPDATE p_ci_builds SET token_encrypted = null;
    

나머지 기능에도 비슷한 전략을 사용할 수 있습니다. 복호화할 수 없는 데이터를 제거하면 GitLab을 다시 운영 상태로 되돌릴 수 있고, 손실된 데이터는 수동으로 대체할 수 있습니다.

통합 및 웹훅 수정#

시크릿을 손실했다면 통합 설정 및 웹훅 설정 페이지에 500 오류 메시지가 표시될 수 있습니다. 이전에 구성한 통합이나 웹훅이 있는 프로젝트에서 리포지터리에 액세스하려고 할 때도 손실된 시크릿이 500 오류를 일으킬 수 있습니다.

해결 방법은 영향을 받은 테이블(암호화된 칼럼이 있는 테이블)을 잘라내는 것입니다. 이렇게 하면 구성한 모든 통합, 웹훅, 관련 메타데이터가 삭제됩니다. 데이터를 삭제하기 전에 시크릿이 근본 원인인지 확인해야 합니다.

  1. 데이터베이스 콘솔로 진입합니다:

    Linux 패키지(Omnibus)의 경우:

    sudo gitlab-rails dbconsole --database main
    

    자체 컴파일 설치의 경우:

    sudo -u git -H bundle exec rails dbconsole -e production --database main
    
  2. 다음 테이블을 잘라냅니다:

    -- truncate web_hooks table
    TRUNCATE integrations, chat_names, issue_tracker_data, jira_tracker_data, slack_integrations, web_hooks, zentao_tracker_data, web_hook_logs_daily CASCADE;
    

컨테이너 레지스트리가 복원되지 않음#

컨테이너 레지스트리를 사용하는 환경의 백업을 컨테이너 레지스트리가 활성화되지 않은 새로 설치한 환경으로 복원하면 컨테이너 레지스트리는 복원되지 않습니다.

컨테이너 레지스트리까지 복원하려면 백업을 복원하기 전에 새 환경에서 이를 활성화해야 합니다.

백업에서 복원한 후 컨테이너 레지스트리 푸시 실패#

컨테이너 레지스트리를 사용하는 경우, Linux 패키지(Omnibus) 인스턴스에서 레지스트리 데이터를 복원한 다음 백업을 복원하면 레지스트리로의 푸시가 실패할 수 있습니다.

이러한 실패는 레지스트리 로그에서 다음과 비슷한 권한 문제로 나타납니다:

level=error
msg="response completed with error"
err.code=unknown
err.detail="filesystem: mkdir /var/opt/gitlab/gitlab-rails/shared/registry/docker/registry/v2/repositories/...: permission denied"
err.message="unknown error"

이 문제는 권한이 없는 git 사용자로 복원이 실행되어, 복원 과정에서 레지스트리 파일에 올바른 소유권을 할당할 수 없기 때문에 발생합니다(이슈 #62759).

레지스트리를 다시 작동시키려면:

sudo chown -R registry:registry /var/opt/gitlab/gitlab-rails/shared/registry/docker

레지스트리의 기본 파일 시스템 위치를 변경했다면 /var/opt/gitlab/gitlab-rails/shared/registry/docker 대신 사용자 지정 위치를 대상으로 chown을 실행합니다.

Gzip 오류로 인한 백업 완료 실패#

백업을 실행할 때 Gzip 오류 메시지가 표시될 수 있습니다:

sudo /opt/gitlab/bin/gitlab-backup create
...
Dumping ...
...
gzip: stdout: Input/output error

Backup failed

이런 경우 다음을 확인합니다:

  • Gzip 작업에 충분한 디스크 공간이 있는지 확인합니다. 기본 전략을 사용하는 백업이 백업 생성 중에 인스턴스 크기의 절반에 해당하는 여유 디스크 공간을 요구하는 일은 드물지 않습니다.
  • NFS를 사용하는 경우 마운트 옵션 timeout이 설정되어 있는지 확인합니다. 기본값은 600이며, 이를 더 작은 값으로 변경하면 이 오류가 발생합니다.

File name too long 오류로 인한 백업 실패#

백업 중에 File name too long 오류가 발생할 수 있습니다(이슈 #354984). 예를 들면 다음과 같습니다:

Problem: <class 'OSError: [Errno 36] File name too long:

이 문제로 인해 백업 스크립트가 완료되지 못합니다. 이 문제를 해결하려면 문제를 일으키는 파일 이름을 잘라내야 합니다. 파일 확장자를 포함해 최대 246자까지 허용됩니다.

Warning

이 섹션의 단계는 데이터 손실로 이어질 수 있습니다. 모든 단계를 주어진 순서대로 엄격히 따라야 합니다. Premium 또는 Ultimate 고객이라면 지원 요청 등록을 고려합니다.

오류를 해결하기 위해 파일 이름을 잘라내는 작업은 다음으로 구성됩니다:

  • 데이터베이스에서 추적되지 않는 원격 업로드 파일 정리.
  • 데이터베이스의 파일 이름 잘라내기.
  • 백업 태스크 다시 실행.

원격 업로드된 파일 정리#

알려진 이슈로 인해 상위 리소스가 삭제된 후에도 오브젝트 스토어 업로드가 남아 있었습니다. 이 이슈는 해결되었습니다.

이러한 파일을 바로잡으려면 스토리지에는 있지만 uploads 데이터베이스 테이블에서 추적되지 않는 모든 원격 업로드 파일을 정리해야 합니다.

  1. GitLab 데이터베이스에 존재하지 않는 경우 lost and found 디렉터리로 이동할 수 있는 모든 오브젝트 스토어 업로드 파일을 나열합니다:

    bundle exec rake gitlab:cleanup:remote_upload_files RAILS_ENV=production
    
  2. 이러한 파일을 삭제하고 참조되지 않는 모든 업로드 파일을 제거하려는 것이 확실하면 다음을 실행합니다:

    [!warning] 다음 작업은 되돌릴 수 없습니다.

    bundle exec rake gitlab:cleanup:remote_upload_files RAILS_ENV=production DRY_RUN=false
    

데이터베이스에서 참조하는 파일 이름 잘라내기#

문제를 일으키는, 데이터베이스가 참조하는 파일을 잘라내야 합니다. 데이터베이스가 참조하는 파일 이름은 다음에 저장됩니다:

  • uploads 테이블.
  • 발견된 참조. 다른 데이터베이스 테이블과 칼럼에서 발견된 모든 참조.
  • 파일 시스템.

uploads 테이블의 파일 이름을 잘라냅니다:

  1. 데이터베이스 콘솔로 진입합니다:

    Linux 패키지(Omnibus)의 경우:

    sudo gitlab-rails dbconsole --database main
    

    자체 컴파일 설치의 경우:

    sudo -u git -H bundle exec rails dbconsole -e production --database main
    
  2. uploads 테이블에서 246자보다 긴 파일 이름을 검색합니다:

    다음 쿼리는 파일 이름이 246자보다 긴 uploads 레코드를 0에서 10000까지의 배치로 선택합니다. 이렇게 하면 레코드가 수천 개인 테이블을 가진 대규모 GitLab 인스턴스에서 성능이 향상됩니다.

    CREATE TEMP TABLE uploads_with_long_filenames AS
    SELECT ROW_NUMBER() OVER(ORDER BY id) row_id, id, path
    FROM uploads AS u
    WHERE LENGTH((regexp_match(u.path, '[^\\/:*?"<>|\r\n]+$'))[1]) > 246;
    
    CREATE INDEX ON uploads_with_long_filenames(row_id);
    
    SELECT
       u.id,
       u.path,
       -- Current filename
       (regexp_match(u.path, '[^\\/:*?"<>|\r\n]+$'))[1] AS current_filename,
       -- New filename
       CONCAT(
          LEFT(SPLIT_PART((regexp_match(u.path, '[^\\/:*?"<>|\r\n]+$'))[1], '.', 1), 242),
          COALESCE(SUBSTRING((regexp_match(u.path, '[^\\/:*?"<>|\r\n]+$'))[1] FROM '\.(?:.(?!\.))+$'))
       ) AS new_filename,
       -- New path
       CONCAT(
          COALESCE((regexp_match(u.path, '(.*\/).*'))[1], ''),
          CONCAT(
             LEFT(SPLIT_PART((regexp_match(u.path, '[^\\/:*?"<>|\r\n]+$'))[1], '.', 1), 242),
             COALESCE(SUBSTRING((regexp_match(u.path, '[^\\/:*?"<>|\r\n]+$'))[1] FROM '\.(?:.(?!\.))+$'))
          )
       ) AS new_path
    FROM uploads_with_long_filenames AS u
    WHERE u.row_id > 0 AND u.row_id <= 10000;
    

    출력 예시:

    -[ RECORD 1 ]----+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    id               | 34
    path             | public/@hashed/loremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelitsedvulputatemisitloremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelitsedvulputatemisit.txt
    current_filename | loremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelitsedvulputatemisitloremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelitsedvulputatemisit.txt
    new_filename     | loremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelitsedvulputatemisitloremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelits.txt
    new_path         | public/@hashed/loremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelitsedvulputatemisitloremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelits.txt
    

    각 항목의 의미는 다음과 같습니다:

    • current_filename: 246자보다 긴 파일 이름.
    • new_filename: 최대 246자로 잘린 파일 이름.
    • new_path: 잘린 new_filename을 고려한 새 경로.

    배치 결과를 검증한 후에는 다음 숫자 순서(10000에서 20000)를 사용하여 배치 크기(row_id)를 변경해야 합니다. uploads 테이블의 마지막 레코드에 도달할 때까지 이 과정을 반복합니다.

  3. uploads 테이블에서 찾은 파일의 긴 파일 이름을 새로 잘린 파일 이름으로 변경합니다. 다음 쿼리는 업데이트를 롤백하므로 트랜잭션 래퍼 안에서 결과를 안전하게 확인할 수 있습니다:

    CREATE TEMP TABLE uploads_with_long_filenames AS
    SELECT ROW_NUMBER() OVER(ORDER BY id) row_id, path, id
    FROM uploads AS u
    WHERE LENGTH((regexp_match(u.path, '[^\\/:*?"<>|\r\n]+$'))[1]) > 246;
    
    CREATE INDEX ON uploads_with_long_filenames(row_id);
    
    BEGIN;
    WITH updated_uploads AS (
       UPDATE uploads
       SET
          path =
          CONCAT(
             COALESCE((regexp_match(updatable_uploads.path, '(.*\/).*'))[1], ''),
             CONCAT(
                LEFT(SPLIT_PART((regexp_match(updatable_uploads.path, '[^\\/:*?"<>|\r\n]+$'))[1], '.', 1), 242),
                COALESCE(SUBSTRING((regexp_match(updatable_uploads.path, '[^\\/:*?"<>|\r\n]+$'))[1] FROM '\.(?:.(?!\.))+$'))
             )
          )
       FROM
          uploads_with_long_filenames AS updatable_uploads
       WHERE
          uploads.id = updatable_uploads.id
       AND updatable_uploads.row_id > 0 AND updatable_uploads.row_id  <= 10000
       RETURNING uploads.*
    )
    SELECT id, path FROM updated_uploads;
    ROLLBACK;
    

    배치 업데이트 결과를 검증한 후에는 다음 숫자 순서(10000에서 20000)를 사용하여 배치 크기(row_id)를 변경해야 합니다. uploads 테이블의 마지막 레코드에 도달할 때까지 이 과정을 반복합니다.

  4. 이전 쿼리에서 얻은 새 파일 이름이 예상한 것인지 검증합니다. 이전 단계에서 찾은 레코드를 246자로 잘라내려는 것이 확실하면 다음을 실행합니다:

    [!warning] 다음 작업은 되돌릴 수 없습니다.

    CREATE TEMP TABLE uploads_with_long_filenames AS
    SELECT ROW_NUMBER() OVER(ORDER BY id) row_id, path, id
    FROM uploads AS u
    WHERE LENGTH((regexp_match(u.path, '[^\\/:*?"<>|\r\n]+$'))[1]) > 246;
    
    CREATE INDEX ON uploads_with_long_filenames(row_id);
    
    UPDATE uploads
    SET
    path =
       CONCAT(
          COALESCE((regexp_match(updatable_uploads.path, '(.*\/).*'))[1], ''),
          CONCAT(
             LEFT(SPLIT_PART((regexp_match(updatable_uploads.path, '[^\\/:*?"<>|\r\n]+$'))[1], '.', 1), 242),
             COALESCE(SUBSTRING((regexp_match(updatable_uploads.path, '[^\\/:*?"<>|\r\n]+$'))[1] FROM '\.(?:.(?!\.))+$'))
          )
       )
    FROM
    uploads_with_long_filenames AS updatable_uploads
    WHERE
    uploads.id = updatable_uploads.id
    AND updatable_uploads.row_id > 0 AND updatable_uploads.row_id  <= 10000;
    

    배치 업데이트를 마친 후에는 다음 숫자 순서(10000에서 20000)를 사용하여 배치 크기(updatable_uploads.row_id)를 변경해야 합니다. uploads 테이블의 마지막 레코드에 도달할 때까지 이 과정을 반복합니다.

발견된 참조에서 파일 이름을 잘라냅니다:

  1. 해당 레코드가 어딘가에서 참조되는지 확인합니다. 한 가지 방법은 데이터베이스를 덤프한 다음 상위 디렉터리 이름과 파일 이름을 검색하는 것입니다:

    1. 데이터베이스를 덤프하려면 예시로 다음 명령을 사용할 수 있습니다:

      pg_dump -h /var/opt/gitlab/postgresql/ -d gitlabhq_production > gitlab-dump.tmp
      
    2. 그 다음 grep 명령으로 참조를 검색할 수 있습니다. 상위 디렉터리와 파일 이름을 조합하는 것이 좋습니다. 예를 들면 다음과 같습니다:

      grep public/alongfilenamehere.txt gitlab-dump.tmp
      
  2. uploads 테이블을 쿼리하여 얻은 새 파일 이름으로 이러한 긴 파일 이름을 교체합니다.

파일 시스템의 파일 이름을 잘라냅니다. 파일 시스템의 파일을 uploads 테이블을 쿼리하여 얻은 새 파일 이름으로 수동으로 변경해야 합니다.

백업 태스크 다시 실행#

이전의 모든 단계를 따른 후 백업 태스크를 다시 실행합니다.

오류: 대규모 리포지터리의 서버 측 백업 중 pack-objects died#

서버 측 백업(REPOSITORIES_SERVER_SIDE=true)을 실행할 때 하나 이상의 대규모 리포지터리가 계속 실패하면 다음 오류가 표시될 수 있습니다:

backup repository: manager: write bundle: local repository: create bundle:
create bundle: exit status 1: stderr: "error: pack-objects died"

다음과 같은 경우에도 이 오류가 표시될 수 있습니다:

  • 같은 프로젝트의 더 작은 리포지터리와 위키 리포지터리는 성공적으로 백업됩니다.
  • Gitaly 노드에서 git fsck --full과 git bundle create를 수동으로 실행하면 오류 없이 통과합니다.

이 문제는 Gitaly가 멀티파트 업로드를 사용하여 리포지터리 번들을 오브젝트 스토리지로 직접 스트리밍하기 때문에 발생합니다.

기본적으로 파트 크기는 5MB이고, AWS S3(및 호환 공급자)는 멀티파트 업로드당 최대 10,000개의 파트를 허용합니다.

이 기본값에서 실질적인 최대 번들 크기는 약 50GB입니다. 리포지터리의 번들이 이 한도를 초과하면 오브젝트 스토리지 공급자가 업로드를 중단합니다. 이 중단은 git pack-objects로 이어지는 파이프를 끊고, 그 결과 업로드 실패가 아니라 오해를 일으키는 pack-objects died 오류가 보고됩니다.

이 문제를 해결하려면 Gitaly 백업 구성에서 buffer_size를 늘립니다. 실질적인 최대 번들 크기는 buffer_size × 10,000이므로, 가장 큰 리포지터리를 수용할 수 있는 값으로 buffer_size를 설정합니다.

  1. Gitaly 노드에서 가장 큰 팩파일을 식별하려면:

    sudo find /var/opt/gitlab/git-data -name "*.pack" -size +10G -ls
    
  2. buffer_size를 설정합니다.

  3. 각 Gitaly 노드에서 /etc/gitlab/gitlab.rb를 편집하고 기존 백업 구성에 buffer_size를 추가합니다:

    gitaly['configuration'] = {
      backup: {
        go_cloud_url: 's3://<bucket>?region=<region>',
        # Increase part size to raise the S3 multipart upload limit.
        # buffer_size (bytes) x 10,000 = effective maximum bundle size.
        # Example: 10485760 (10 MB) supports bundles up to ~100 GB.
        buffer_size: 10485760,
      },
    }
    
    1. 각 노드에서 Gitaly를 재구성하고 재시작합니다:

      sudo gitlab-ctl reconfigure
      sudo gitlab-ctl restart gitaly
      
  1. Helm 값을 내보냅니다:

    helm get values gitlab > gitlab_values.yaml
    
    1. gitlab_values.yaml을 편집하고 필요한 값을 설정합니다:

      gitlab:
        gitaly:
          configuration:
            backup:
              buffer_size: 10485760
      
    2. 파일을 저장하고 새 값을 적용합니다:

      helm upgrade -f gitlab_values.yaml gitlab gitlab/gitlab
      

pg_stat_statements가 이전에 활성화된 경우 데이터베이스 백업 복원 실패#

PostgreSQL 데이터베이스의 GitLab 백업에는 데이터베이스에서 이전에 활성화된 확장을 활성화하는 데 필요한 모든 SQL 문이 포함됩니다.

pg_stat_statements 확장은 superuser 역할을 가진 PostgreSQL 사용자만 활성화하거나 비활성화할 수 있습니다. 복원 프로세스는 권한이 제한된 데이터베이스 사용자를 사용하므로 다음 SQL 문을 실행할 수 없습니다:

DROP EXTENSION IF EXISTS pg_stat_statements;
CREATE EXTENSION IF NOT EXISTS pg_stat_statements WITH SCHEMA public;

pg_stat_statements 확장이 없는 PostgreSQL 인스턴스에서 백업을 복원하려고 하면 다음 오류 메시지가 표시됩니다:

ERROR: permission denied to create extension "pg_stat_statements"
HINT: Must be superuser to create this extension.
ERROR: extension "pg_stat_statements" does not exist

pg_stat_statements 확장이 활성화된 인스턴스에서 복원하려고 하면 정리 단계가 다음과 비슷한 오류 메시지와 함께 실패합니다:

rake aborted!
ActiveRecord::StatementInvalid: PG::InsufficientPrivilege: ERROR: must be owner of view pg_stat_statements
/opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/db.rake:42:in `block (4 levels) in <top (required)>'
/opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/db.rake:41:in `each'
/opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/db.rake:41:in `block (3 levels) in <top (required)>'
/opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/backup.rake:71:in `block (3 levels) in <top (required)>'
/opt/gitlab/embedded/bin/bundle:23:in `load'
/opt/gitlab/embedded/bin/bundle:23:in `<main>'
Caused by:
PG::InsufficientPrivilege: ERROR: must be owner of view pg_stat_statements
/opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/db.rake:42:in `block (4 levels) in <top (required)>'
/opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/db.rake:41:in `each'
/opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/db.rake:41:in `block (3 levels) in <top (required)>'
/opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/backup.rake:71:in `block (3 levels) in <top (required)>'
/opt/gitlab/embedded/bin/bundle:23:in `load'
/opt/gitlab/embedded/bin/bundle:23:in `<main>'
Tasks: TOP => gitlab:db:drop_tables
(See full trace by running task with --trace)

덤프 파일에 pg_stat_statements가 포함되지 않도록 방지#

백업 번들에 포함되는 PostgreSQL 덤프 파일에 이 확장이 포함되지 않도록 하려면 public 스키마를 제외한 다른 스키마에서 확장을 활성화합니다:

CREATE SCHEMA adm;
CREATE EXTENSION pg_stat_statements SCHEMA adm;

확장이 이전에 public 스키마에서 활성화되었다면 새 스키마로 이동합니다:

CREATE SCHEMA adm;
ALTER EXTENSION pg_stat_statements SET SCHEMA adm;

스키마를 변경한 후 pg_stat_statements 데이터를 쿼리하려면 뷰 이름 앞에 새 스키마를 붙입니다:

SELECT * FROM adm.pg_stat_statements limit 0;

public 스키마에서 활성화되어 있기를 기대하는 서드파티 모니터링 솔루션과 호환되게 하려면 이를 search_path에 포함해야 합니다:

set search_path to public,adm;

pg_stat_statements에 대한 참조를 제거하도록 기존 덤프 파일 수정#

기존 백업 파일을 수정하려면 다음과 같이 변경합니다:

  1. 백업에서 다음 파일을 추출합니다: db/database.sql.gz.

  2. 파일의 압축을 풀거나, 압축된 상태로 처리할 수 있는 편집기를 사용합니다.

  3. 다음 줄 또는 비슷한 줄을 제거합니다:

    CREATE EXTENSION IF NOT EXISTS pg_stat_statements WITH SCHEMA public;
    
    COMMENT ON EXTENSION pg_stat_statements IS 'track planning and execution statistics of all SQL statements executed';
    
  4. 변경 사항을 저장하고 파일을 다시 압축합니다.

  5. 수정된 db/database.sql.gz로 백업 파일을 업데이트합니다.