GitLab 백업 문제 해결

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

secrets 파일을 잃어버렸을 때

만약 secrets 파일을 백업하지 않았다면, GitLab을 다시 제대로 작동시키기 위해 몇 가지 단계를 완료해야 합니다.

secrets 파일은 필요한 중요 정보를 포함하는 열의 암호화 키를 저장하는 데 책임이 있습니다. 키를 잃어버리면 GitLab은 해당 열을 해독할 수 없어 다음 항목에 액세스할 수 없게 됩니다:

CI/CD 변수 및 러너(runner) 인증과 같은 경우 예상치 못한 동작을 경험할 수 있습니다. 예를 들어 다음과 같습니다:

  • 작업이 멈춤.
  • 500 에러.

이 경우, CI/CD 변수와 러너(runner) 인증에 대한 모든 토큰을 재설정해야 하며, 이에 대해 더 자세히 설명된 내용은 다음 섹션에서 설명합니다. 토큰을 재설정한 후 프로젝트를 방문하고 작업을 다시 시작해야 합니다.

caution
이 섹션의 단계가 위에 나열된 항목들에서 데이터 손실을 유발할 수 있습니다. Premium 또는 Ultimate 고객이라면 지원 요청을 열어보는 것을 고려해 보세요.

모든 값을 해독할 수 있는지 확인

현재의 secrets를 사용하여 데이터베이스에 해독할 수 없는 값이 있는지 확인할 수 있습니다.

Rake 작업을 사용하면 됩니다.

백업 만들기

잃어버린 secrets 파일을 대체하기 위해 GitLab 데이터를 직접 수정해야 합니다.

caution
어떠한 변경도 시도하기 전에 데이터베이스의 완전한 백업을 생성해야 합니다.

사용자 이중 인증 (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_variablesci_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을 다시 구성하거나 다시 시작해야 할 수 있습니다.

러너(runner) 등록 토큰 재설정

  1. 데이터베이스 콘솔에 입력합니다:

    Linux 패키지(Omnibus)의 경우:

    sudo gitlab-rails dbconsole --database main
    

    직접 설치한 경우:

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

    caution
    최종 UPDATE 작업은 러너가 새 작업을 가져오지 못하게 합니다. 새로운 러너를 등록해야 합니다.
    -- 프로젝트 토큰 지우기
    UPDATE projects SET runners_token = null, runners_token_encrypted = null;
    -- 그룹 토큰 지우기
    UPDATE namespaces SET runners_token = null, runners_token_encrypted = null;
    -- 인스턴스 토큰 지우기
    UPDATE application_settings SET runners_registration_token_encrypted = null;
    -- JWT 인증에 사용되는 키 지우기
    -- 이는 $CI_JWT_TOKEN 작업 변수를 깰 수 있습니다:
    -- https://gitlab.com/gitlab-org/gitlab/-/issues/325965
    UPDATE application_settings SET encrypted_ci_jwt_signing_key = null;
    -- 러너 토큰 지우기
    UPDATE ci_runners SET token = null, token_encrypted = null;
    

보류 중인 파이프라인 작업 재설정

  1. 데이터베이스 콘솔에 입력합니다:

    Linux 패키지(Omnibus)의 경우:

    sudo gitlab-rails dbconsole --database main
    

    직접 설치한 경우:

    sudo -u git -H bundle exec rails dbconsole -e production --database main
    
  2. 보류 중인 작업에 대한 모든 토큰을 지웁니다:

    GitLab 15.3 및 이전 버전의 경우:

    -- 빌드 토큰 지우기
    UPDATE ci_builds SET token = null, token_encrypted = null;
    

    GitLab 15.4 이후 버전의 경우:

    -- 빌드 토큰 지우기
    UPDATE ci_builds SET token_encrypted = null;
    

나머지 기능에 대해서도 유사한 전략을 적용할 수 있습니다. 해독할 수 없는 데이터를 제거함으로써 GitLab을 다시 운영할 수 있으며, 손실된 데이터를 매뉴얼으로 대체할 수 있습니다.

통합 및 웹훅 수정

secrets를 잃어버렸다면, 통합 설정웹훅 설정 페이지에 500 오류 메시지가 표시될 수 있습니다. 손실된 secrets로 이전에 구성된 통합 또는 웹훅이 있는 프로젝트의 리포지터리에 액세스하려고 시도하면 500 오류가 발생할 수 있습니다.

해결 방법은 영향을 받는 테이블(암호화된 열을 포함하는 테이블)을 잘라서 삭제하는 것입니다. 이로써 구성된 통합, 웹훅 및 관련된 메타데이터가 모두 삭제됩니다. 데이터를 삭제하기 전에 secrets가 원인인지 확인해야 합니다.

  1. 데이터베이스 콘솔에 입력합니다:

    Linux 패키지(Omnibus)의 경우:

    sudo gitlab-rails dbconsole --database main
    

    직접 설치한 경우:

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

    -- 웹훅 테이블 자르기
    TRUNCATE integrations, chat_names, issue_tracker_data, jira_tracker_data, slack_integrations, web_hooks, zentao_tracker_data, web_hook_logs 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

백업 실패

이런 경우 다음을 확인하세요:

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

File name too long 오류로 백업이 실패함

백업 중에 File name too long 오류(issue #354984)를 만날 수 있습니다. 예를 들면:

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

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

caution
이 섹션의 단계는 잠재적으로 데이터 손실로 이어질 수 있습니다. 모든 단계는 엄격히 주어진 순서대로 따라야 합니다. Premium 또는 Ultimate 고객이라면 지원 요청을 열어보는 것을 고려하세요.

오류를 해결하기 위해 파일 이름을 줄이는 단계는 다음과 같습니다:

  • 데이터베이스에서 추적되지 않는 원격 업로드된 파일을 정리합니다.
  • 데이터베이스 내의 파일 이름을 줄입니다.
  • 백업 작업을 다시 실행합니다.

추적되지 않는 원격 업로드된 파일 정리

알려진 문제로 인해 부모 리소스가 삭제된 후에도 객체 리포지터리 업로드가 남아있는 경우가 있습니다. 이 문제는 해결되었습니다.

이러한 파일들을 해결하려면, uploads 데이터베이스 테이블에 추적되지 않는 모든 원격 업로드된 파일을 정리해야 합니다.

  1. GitLab 데이터베이스에 존재하지 않는 파일을 잃어버린 물품 디렉터리로 옮길 수 있는 모든 객체 리포지터리 업로드 파일을 나열합니다:

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

    caution
    다음 작업은 취소할 수 없습니다.
    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. 246자보다 긴 파일 이름을 가진 uploads 레코드를 검색합니다:

    다음 쿼리는 큰 GitLab 인스턴스에서 성능을 향상시키기 위해 0에서 10000까지의 배치로 246자보다 긴 파일 이름을 가진 uploads 레코드를 선택합니다.

       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,
          -- 현재 파일 이름
          (regexp_match(u.path, '[^\\/:*?"<>|\r\n]+$'))[1] AS current_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,
          -- 새 경로
          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     | loremipsumdolorsitametconsecteturadipisicingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelitsedvulputatemisitloremipsumdolorsitametconsecteturadipisicingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelits.txt
       new_path         | public/@hashed/loremipsumdolorsitametconsecteturadipisicingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelitsedvulputatemisitloremipsumdolorsitametconsecteturadipisicingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelits.txt

    여기서:

    • current_filename: 현재 246자보다 긴 파일 이름.
    • new_filename: 246자로 줄여진 새 파일 이름.
    • new_path: new_filename (줄인)을 고려한 새 경로.

    배치 결과를 확인한 후, uploads 테이블의 마지막 레코드에 도달할 때까지 다음 숫자 시퀀스(10000에서 20000까지)로 배치 크기(row_id)를 변경해야 합니다.

  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;
    

    배치 업데이트 결과를 확인한 후, uploads 테이블의 마지막 레코드에 도달할 때까지 다음 숫자 시퀀스(10000에서 20000까지)로 배치 크기(row_id)를 변경해야 합니다.

  4. 이전 쿼리에서 얻은 새 파일 이름이 예상대로 246자로 줄어들었는지 확인합니다. 이전 단계에서 찾은 레코드를 246자로 줄이고자 하는 경우 다음을 실행합니다:

    caution
    다음 작업은 취소할 수 없습니다.
    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;
    

    배치 업데이트를 마치면, uploads 테이블의 마지막 레코드에 도달할 때까지 다음 숫자 시퀀스(updatable_uploads.row_id)로 배치 크기를 변경해야 합니다.

데이터베이스에서 참조된 파일 이름을 줄입니다:

  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 테이블에서 얻은 새 파일 이름을 사용하여 파일 시스템에서 파일 이름을 매뉴얼으로 바꿔야 합니다.

백업 작업 다시 실행

이전 단계를 모두 따른 후, 백업 작업을 다시 실행합니다.

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_stats_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로 백업 파일을 업데이트합니다.