GitLab 백업 문제 해결

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

Secrets 파일 분실 시

만약 비밀 파일을 백업하지 않았다면, GitLab을 다시 올바르게 작동시키기 위해 몇 가지 단계를 완료해야 합니다.

Secrets 파일은 필수 민감 정보를 포함하는 열의 암호화 키를 저장하는 데 책임이 있습니다. 이 키가 분실되면 GitLab은 해당 열을 복호화할 수 없어 다음과 같은 항목에 대한 액세스를 방해합니다:

CI/CD 변수 및 러너 인증과 같은 경우 예기치 않은 동작을 경험할 수 있습니다. 예를 들어:

  • 작업이 멈춤.
  • 500 오류.

이 경우 CI/CD 변수와 러너 인증을 모두 재설정해야 하며, 이에 대한 자세한 내용은 다음 섹션에서 설명하겠습니다. 토큰을 재설정한 후에는 프로젝트를 방문하고 작업을 다시 시작할 수 있어야 합니다.

caution
이 섹션의 단계는 위에 나열된 항목들에서 잠재적으로 데이터 손실로 이어질 수 있습니다. 만약 Premium 또는 Ultimate 고객이라면 지원 요청을 열어 보세요.

모든 값이 복호화될 수 있는지 확인

현재 Secrets를 사용하여 복호화할 수 있는 값을 데이터베이스에 포함되어 있는지를 결정하기 위해 레이크 태스크를 사용할 수 있습니다.

백업 수행

분실된 Secrets 파일을 해결하기 위해 GitLab 데이터를 직접 수정해야 합니다.

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

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

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

CI/CD 변수 재설정

  1. 데이터베이스 콘솔에 들어갑니다:

    Linux 패키지 (Omnibus) GitLab 14.1 이전의 경우:

    sudo gitlab-rails dbconsole
    

    Linux 패키지 (Omnibus) GitLab 14.2 이후의 경우:

    sudo gitlab-rails dbconsole --database main
    

    자체 컴파일된 설치의 경우, GitLab 14.1 이전:

    sudo -u git -H bundle exec rails dbconsole -e production
    

    자체 컴파일된 설치의 경우, GitLab 14.2 이후:

    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. 특정 그룹 또는 프로젝트를 알고 있다면 해당 변수를 삭제하려면 WHERE 문을 포함하여 DELETE에 지정해야 합니다:

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

변경 사항이 적용되도록 GitLab을 다시 구성하거나 다시 시작해야할 수 있습니다.

러너 등록 토큰 재설정

  1. 데이터베이스 콘솔에 들어갑니다:

    Linux 패키지 (Omnibus) GitLab 14.1 이전의 경우:

    sudo gitlab-rails dbconsole
    

    Linux 패키지 (Omnibus) GitLab 14.2 이후의 경우:

    sudo gitlab-rails dbconsole --database main
    

    자체 컴파일된 설치의 경우, GitLab 14.1 이전:

    sudo -u git -H bundle exec rails dbconsole -e production
    

    자체 컴파일된 설치의 경우, GitLab 14.2 이후:

    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) GitLab 14.1 이전의 경우:

    sudo gitlab-rails dbconsole
    

    Linux 패키지 (Omnibus) GitLab 14.2 이후의 경우:

    sudo gitlab-rails dbconsole --database main
    

    자체 컴파일된 설치의 경우, GitLab 14.1 이전:

    sudo -u git -H bundle exec rails dbconsole -e production
    

    자체 컴파일된 설치의 경우, GitLab 14.2 이후:

    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을 다시 운영 가능한 상태로 복원하고 손실된 데이터를 매뉴얼으로 대체할 수 있습니다.

통합 및 웹훅 수정하기

만약 시크릿을 분실한 경우, 통합 설정웹훅 설정 페이지에서는 500 오류 메시지가 표시될 수 있습니다. 시크릿 손실은 이전에 구성된 통합이나 웹훅이 있는 프로젝트의 리포지터리에 액세스하려고 할 때 또한 500 오류를 발생시킬 수 있습니다.

문제를 해결하려면 영향을 받는 테이블(암호화된 열을 포함하는 테이블)을 잘라내면 됩니다. 이렇게 함으로써 구성된 통합, 웹훅 및 관련 메타데이터가 모두 삭제됩니다. 어떤 데이터도 삭제하기 전에 시크릿이 꼭 원인인지 확인해야 합니다.

  1. 데이터베이스 콘솔에 입력하세요:

    Linux 패키지(Omnibus) GitLab 14.1 및 이전:

    sudo gitlab-rails dbconsole
    

    Linux 패키지(Omnibus) GitLab 14.2 및 이후:

    sudo gitlab-rails dbconsole --database main
    

    자체 컴파일된 설치, GitLab 14.1 및 이전:

    sudo -u git -H bundle exec rails dbconsole -e production
    

    자체 컴파일된 설치, GitLab 14.2 및 이후:

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

    -- web_hooks 테이블 잘라내기
    TRUNCATE integrations, chat_names, issue_tracker_data, jira_tracker_data, slack_integrations, web_hooks, zentao_tracker_data, web_hook_logs CASCADE;
    

백업에서 컨테이너 레지스트리 푸시 실패

만약 리눅스 패키지(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 오류가 발생할 수 있습니다 (이슈 #354984). 예를 들면:

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

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

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

오류를 해결하고 파일 이름을 줄이려면 다음 단계를 따릅니다:

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

원격 업로드 파일 정리

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

이러한 파일을 수정하려면 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) GitLab 14.2 및 이후:

    sudo gitlab-rails dbconsole --database main
    

    Linux 패키지(Omnibus) GitLab 14.1 및 이전:

    sudo gitlab-rails dbconsole
    

    자체 컴파일된 설치, GitLab 14.2 및 이후:

    sudo -u git -H bundle exec rails dbconsole -e production --database main
    

    자체 컴파일된 설치, GitLab 14.1 및 이전:

    sudo -u git -H bundle exec rails dbconsole -e production
    
  2. 246자보다 긴 파일 이름을 가진 업로드 레코드를 검색하세요:

    다음 쿼리는 0부터 10000까지 일괄 처리로 246자 이상의 파일 이름을 가진 업로드 레코드를 선택합니다. 이렇게 하면 수천 개의 레코드가 있는 큰 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,
          -- 현재 파일 이름
          (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     | loremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelitsedvulputatemisitloremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelits.txt
       new_path         | public/@hashed/loremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelitsedvulputatemisitloremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaauctorelits.txt

    여기서:

    • current_filename: 현재 246자보다 긴 파일 이름
    • new_filename: 246자로 잘랐을 때의 새 파일 이름
    • new_path: 새 경로, new_filename (잘라낸)을 고려한 것

    일괄결과를 확인한 후, 다음 배치 결과를 변경하려면(row_id) 다음 순차 번호를 사용하세요(10000에서 20000까지). 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;
    

    일괄 업데이트 결과를 확인한 후, 다음 배치 크기(row_id)를 사용하여 변경하세요(10000부터 20000까지). uploads 테이블의 마지막 레코드에 도달할 때까지 이러한 프로세스를 반복하세요.

  4. 이전 쿼리에서 새로운 파일 이름이 예상대로 되었는지 확인하세요. 이전 단계에서 찾은 레코드를 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
       
    (중략)
    

백업 작업 다시 실행

이전 단계를 모두 수행한 후 백업 작업을 다시 실행합니다.

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

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

pg_stat_statements 익스텐션은 PostgreSQL 사용자 중 superuser 권한을 가진 사용자만이 활성화하거나 비활성화할 수 있습니다. 복원 프로세스는 권한이 제한된 데이터베이스 사용자를 사용하므로 다음과 같은 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로 백업 파일을 업데이트합니다.