InfoGrab Docs

GitLab 백업 문제 해결

요약

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

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

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

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

시크릿 파일은 필수 민감 정보를 포함하는 열의 암호화 키를 저장하는 역할을 합니다. 키가 손실되면 GitLab은 해당 열을 해독할 수 없어 다음 항목에 대한 액세스를 방해합니다:

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

  • 작업 중단.
  • 500 오류.

이 경우 CI/CD 변수 및 러너 인증의 모든 토큰을 재설정해야 하며 이는 다음 섹션에서 더 자세히 설명합니다. 토큰을 재설정하면 프로젝트를 방문할 수 있고 작업이 다시 실행되기 시작해야 합니다.

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_variablesci_variables 테이블을 확인합니다:

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

    삭제해야 할 변수입니다.

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

    DELETE FROM ci_group_variables;
    DELETE FROM ci_variables;
    
  4. 변수를 삭제할 특정 그룹 또는 프로젝트를 알고 있는 경우 DELETEWHERE 문을 포함하여 지정할 수 있습니다:

    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 작업은 러너가 새 작업을 가져올 수 없도록 합니다. 새 러너를 등록해야 합니다.

    -- 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 = 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. 대기 중인 작업의 모든 토큰을 지웁니다:

    -- Clear build tokens
    UPDATE 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 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 데이터베이스에 존재하지 않으면 분실 디렉토리로 이동할 수 있는 모든 개체 저장소 업로드 파일을 나열합니다:

    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. 246자보다 긴 파일 이름에 대한 uploads 테이블을 검색합니다:

    다음 쿼리는 0에서 10000 배치로 246자보다 긴 파일 이름이 있는 uploads 레코드를 선택합니다. 이렇게 하면 수천 개의 레코드가 있는 테이블이 있는 대형 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 테이블을 쿼리하여 얻은 새 파일 이름으로 파일 시스템의 파일 이름을 수동으로 변경해야 합니다.

백업 작업 다시 실행#

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

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

GitLab의 PostgreSQL 데이터베이스 백업에는 데이터베이스에서 이전에 활성화된 확장을 활성화하는 데 필요한 모든 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_stats_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로 백업 파일을 업데이트합니다.

GitLab 백업 문제 해결

원문 보기
요약

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

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

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

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

시크릿 파일은 필수 민감 정보를 포함하는 열의 암호화 키를 저장하는 역할을 합니다. 키가 손실되면 GitLab은 해당 열을 해독할 수 없어 다음 항목에 대한 액세스를 방해합니다:

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

  • 작업 중단.
  • 500 오류.

이 경우 CI/CD 변수 및 러너 인증의 모든 토큰을 재설정해야 하며 이는 다음 섹션에서 더 자세히 설명합니다. 토큰을 재설정하면 프로젝트를 방문할 수 있고 작업이 다시 실행되기 시작해야 합니다.

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_variablesci_variables 테이블을 확인합니다:

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

    삭제해야 할 변수입니다.

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

    DELETE FROM ci_group_variables;
    DELETE FROM ci_variables;
    
  4. 변수를 삭제할 특정 그룹 또는 프로젝트를 알고 있는 경우 DELETEWHERE 문을 포함하여 지정할 수 있습니다:

    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 작업은 러너가 새 작업을 가져올 수 없도록 합니다. 새 러너를 등록해야 합니다.

    -- 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 = 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. 대기 중인 작업의 모든 토큰을 지웁니다:

    -- Clear build tokens
    UPDATE 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 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 데이터베이스에 존재하지 않으면 분실 디렉토리로 이동할 수 있는 모든 개체 저장소 업로드 파일을 나열합니다:

    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. 246자보다 긴 파일 이름에 대한 uploads 테이블을 검색합니다:

    다음 쿼리는 0에서 10000 배치로 246자보다 긴 파일 이름이 있는 uploads 레코드를 선택합니다. 이렇게 하면 수천 개의 레코드가 있는 테이블이 있는 대형 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 테이블을 쿼리하여 얻은 새 파일 이름으로 파일 시스템의 파일 이름을 수동으로 변경해야 합니다.

백업 작업 다시 실행#

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

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

GitLab의 PostgreSQL 데이터베이스 백업에는 데이터베이스에서 이전에 활성화된 확장을 활성화하는 데 필요한 모든 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_stats_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로 백업 파일을 업데이트합니다.