보조 사이트를 위한 컨테이너 레지스트리
GitLab v19.5Offering: GitLab Self-Managed
요약
보조 Geo 사이트에 컨테이너 레지스트리를 설정해 기본 Geo 사이트의 컨테이너 이미지를 복제할 수 있습니다. 보조 Geo 사이트의 컨테이너 레지스트리에는 푸시하지 않습니다. 보조 사이트의 컨테이너 레지스트리 데이터는 오래된 상태일 수 있으므로 내려받지 않을 것을 권장합니다.
보조 Geo 사이트에 컨테이너 레지스트리를 설정해 기본 Geo 사이트의 컨테이너 이미지를 복제할 수 있습니다. 이 컨테이너 이미지 복제는 재해 복구 목적으로만 사용합니다.
보조 Geo 사이트의 컨테이너 레지스트리에는 푸시하지 않습니다. 해당 데이터는 기본 사이트로 전파되지 않기 때문입니다.
보조 사이트의 컨테이너 레지스트리 데이터는 오래된 상태일 수 있으므로 내려받지 않을 것을 권장합니다. 기능 요청 이슈 365864가 이 문제를 해결합니다. 관심이 있다면 해당 이슈에 추천을 남길 수 있습니다.
중요: 컨테이너 레지스트리 메타데이터 데이터베이스는 컨테이너 이미지 복제와 별개입니다. 컨테이너 이미지는 기본 사이트에서 보조 사이트로 복제되지만 메타데이터 데이터베이스는 복제되지 않습니다. 컨테이너 레지스트리 메타데이터 데이터베이스를 활성화한 상태로 GitLab Geo를 사용할 때는 각 Geo 사이트(기본 사이트와 보조 사이트 모두)에 컨테이너 레지스트리용 외부 PostgreSQL 인스턴스를 별도로 구성해야 합니다. 컨테이너 레지스트리 메타데이터 데이터베이스는 GitLab 이 관리하는 기본 PostgreSQL 데이터베이스를 사용할 수 없습니다. 각 사이트의 메타데이터 데이터베이스는 서로 복제하지 않고 독립적으로 동작합니다. 설정 방법은 외부 데이터베이스 사용을 참고합니다.
지원되는 컨테이너 레지스트리#
Geo는 다음 유형의 컨테이너 레지스트리를 지원합니다.
지원되는 이미지 형식#
Geo는 다음 컨테이너 이미지 형식을 지원합니다.
또한 Geo는 BuildKit 캐시 이미지도 지원합니다.
지원되는 스토리지#
Docker#
지원되는 레지스트리 스토리지 드라이버에 대한 자세한 내용은 Docker 레지스트리 스토리지 드라이버를 참고합니다.
레지스트리를 배포할 때는 로드 밸런싱 고려 사항 과 GitLab에 통합된 컨테이너 레지스트리의 스토리지 드라이버 설정 방법을 함께 확인합니다.
OCI 아티팩트를 지원하는 레지스트리#
다음 레지스트리는 OCI 아티팩트를 지원합니다.
- CNCF Distribution - 로컬/오프라인 검증
- Azure Container Registry (ACR)
- Amazon Elastic Container Registry (ECR)
- Google Artifact Registry (GAR)
- GitHub Packages container registry (GHCR)
- Bundle Bar
자세한 내용은 OCI Distribution Specification을 참고합니다.
컨테이너 레지스트리 복제 구성#
스토리지에 구애받지 않는 복제를 활성화하면 클라우드 스토리지와 로컬 스토리지 모두에 사용할 수 있습니다. 기본 사이트에 새 이미지가 푸시될 때마다 각 보조 사이트는 그 이미지를 자신의 컨테이너 리포지터리로 내려받습니다.
컨테이너 레지스트리 복제를 구성하는 방법은 다음과 같습니다.
기본 사이트 구성#
다음 단계를 진행하기 전에 기본 사이트에서 컨테이너 레지스트리가 설정되어 정상적으로 동작하는지 확인합니다.
새 컨테이너 이미지를 복제하려면 컨테이너 레지스트리가 푸시가 발생할 때마다 기본 사이트로 알림 이벤트를 보내야 합니다. 컨테이너 레지스트리와 기본 사이트의 웹 노드가 공유하는 토큰은 통신의 보안을 높이는 데 사용됩니다.
-
GitLab 기본 서버에 SSH로 접속해 root로 로그인합니다(GitLab HA의 경우 레지스트리 노드 하나만 있으면 됩니다).
sudo -i -
/etc/gitlab/gitlab.rb를 편집합니다.# Configure the registry to listen on the public/internal interface # Replace with the appropriate interface (for example, '0.0.0.0' for all interfaces) registry['registry_http_addr'] = '0.0.0.0:5000' registry['notifications'] = [ { 'name' => 'geo_event', 'url' => 'https://<example.com>/api/v4/container_registry_event/events', 'timeout' => '500ms', 'maxretries' => 5, 'backoff' => '1s', 'headers' => { 'Authorization' => ['<replace_with_a_secret_token>'] } } ]<example.com>은 기본 사이트의/etc/gitlab/gitlab.rb파일에 정의된external_url로 바꾸고,<replace_with_a_secret_token>은 글자로 시작하는 대소문자 구분 영숫자 문자열로 바꿉니다. 이 문자열은/dev/urandom tr -dc _A-Z-a-z-0-9 | head -c 32 | sed "s/^[0-9]*//"; echo으로 생성할 수 있습니다.[!note]
gitlab_rails['registry_notification_secret']을 명시적으로 설정하지 않는다면 이 엔드포인트의 이름을geo_event로 지정합니다./etc/gitlab/gitlab.rb에registry['notifications']블록이 있는 노드에서 Linux 패키지는 엔드포인트 이름이 일치할 때만 이 엔드포인트의Authorization헤더에서 해당 시크릿을 도출합니다. 다른 이름을 쓰면서 명시적인 설정도 없으면 GitLab에 알림 시크릿이 없어 레지스트리의 알림이401 Unauthorized로 거부됩니다.GitLab에 통합되지 않은 외부 레지스트리를 사용한다면 시크릿을 도출할
registry['notifications']블록이 없으므로/etc/gitlab/gitlab.rb에서 직접 설정해야 합니다.gitlab_rails['registry_notification_secret'] = '<replace_with_a_secret_token>' -
GitLab HA 에만 해당합니다. 모든 웹 노드에서
/etc/gitlab/gitlab.rb를 편집합니다.gitlab_rails['registry_notification_secret'] = '<replace_with_a_secret_token_generated_above>'웹 노드에는
registry['notifications']블록이 없어 시크릿을 도출할 대상이 없으므로 명시적으로 설정해야 합니다. 동일한/etc/gitlab/gitlab.rb를 모든 노드에 복사해 해당 블록을 가진 웹 노드라면 레지스트리 노드와 같은 방식으로 시크릿을 도출합니다. -
방금 갱신한 각 노드를 재구성합니다.
gitlab-ctl reconfigure
보조 사이트 구성#
다음 단계를 진행하기 전에 보조 사이트에서 컨테이너 레지스트리가 설정되어 정상적으로 동작하는지 확인합니다.
다음 단계는 컨테이너 이미지가 복제되기를 기대하는 각 보조 사이트에서 수행해야 합니다.
보조 사이트가 기본 사이트의 컨테이너 레지스트리와 안전하게 통신해야 하므로 모든 사이트가 하나의 키 쌍을 공유해야 합니다. 보조 사이트는 이 키로 기본 사이트 컨테이너 레지스트리에 접근할 수 있는, 내려받기 전용의 수명이 짧은 JWT를 생성합니다.
보조 사이트의 각 애플리케이션 노드와 Sidekiq 노드에서 다음을 수행합니다.
-
노드에 SSH로 접속해
root사용자로 로그인합니다.sudo -i -
기본 사이트의
/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key를 해당 노드로 복사합니다. -
/etc/gitlab/gitlab.rb를 편집하고 다음을 추가합니다.gitlab_rails['geo_registry_replication_enabled'] = true # Primary registry's hostname and port, it will be used by # the secondary node to directly communicate to primary registry gitlab_rails['geo_registry_replication_primary_api_url'] = 'https://primary.example.com:5050/' -
변경 사항이 적용되도록 노드를 재구성합니다.
gitlab-ctl reconfigure
컨테이너 레지스트리 복제 비활성화#
컨테이너 레지스트리 서비스를 비활성화하면 Geo는 컨테이너 리포지터리에 대한 검증 시도를 자동으로 건너뜁니다. 컨테이너 레지스트리 지표는 Geo 대시보드에서 숨겨집니다. 기존 검증 상태는 데이터베이스에 남지만 화면에는 표시되지 않습니다.
보조 사이트로의 컨테이너 이미지 복제를 중단하고 로컬 컨테이너 레지스트리를 비활성화하는 방법은 다음과 같습니다.
-
기본 서버 노드에 SSH로 접속해
root사용자로 로그인합니다.sudo -i -
/etc/gitlab/gitlab.rb를 편집하고 다음을 추가합니다.# Disable Geo replication for the container registry gitlab_rails['geo_registry_replication_enabled'] = false # Disable the container registry service itself registry['enable'] = false -
변경 사항이 적용되도록 노드를 재구성합니다.
sudo gitlab-ctl reconfigure
복제 확인#
컨테이너 레지스트리 복제가 동작하는지 확인하려면 보조 사이트에서 다음을 수행합니다.
- 오른쪽 상단에서 Admin을 선택합니다.
- 왼쪽 사이드바에서 Geo > Nodes를 선택합니다. 초기 복제, 즉 "백필"이 아직 진행 중일 수 있습니다.
각 Geo 사이트의 동기화 진행 상황은 브라우저에서 기본 사이트의 Geo Nodes 대시보드로 모니터링할 수 있습니다.
Geo 사이트의 가비지 컬렉션#
기본 사이트에서 가비지 컬렉션을 실행해도 보조 사이트의 스토리지는 비워지지 않습니다. 각 사이트가 자체 컨테이너 레지스트리 스토리지를 정리해야 합니다. 기본 사이트에서 태그를 삭제하면 삭제가 보조 사이트로 복제됩니다. 그러나 가비지 컬렉션이 제거할 때까지 이미지 레이어는 두 사이트의 스토리지에 남아 있습니다.
가비지 컬렉션을 실행하는 방법은 레지스트리 구성에 따라 다릅니다.
컨테이너 레지스트리 메타데이터 데이터베이스가 있는 경우의 가비지 컬렉션#
각 사이트에는 자체 레지스트리 메타데이터 데이터베이스가 있으며, 레지스트리가 각 사이트에서 온라인 가비지 컬렉션을 자동으로 실행합니다. 가비지 컬렉션을 수동으로 실행할 필요가 없습니다.
메타데이터 데이터베이스가 없고 사이트마다 스토리지가 분리된 경우의 가비지 컬렉션#
각 사이트에서 가비지 컬렉션을 실행합니다. 가비지 컬렉션이 실행되는 동안 보조 사이트는 컨테이너 이미지를 복제할 수 없으며, Geo는 가비지 컬렉션이 끝난 뒤 실패한 동기화를 다시 시도합니다.
메타데이터 데이터베이스가 없고 스토리지를 공유하는 경우의 가비지 컬렉션#
공유 스토리지 사용은 권장되지 않습니다. 잘못 구성하면 데이터가 손실될 수 있습니다.
스토리지를 공유하면 두 사이트가 같은 스토리지 버킷을 읽고 쓰므로 기본 사이트가 모든 레지스트리 blob을 소유합니다. 보조 사이트는 이를 건드리면 안 됩니다. 보조 사이트에서 가비지 컬렉션을 실행하지 않습니다. 기본 사이트에 최근 푸시된 이미지를 삭제할 수 있고, 복제가 blob을 자기 자신에게 복사하게 되기 때문입니다.
요약하면 다음과 같습니다.
- 보조 사이트에서 컨테이너 레지스트리 복제를 비활성화합니다.
- 기본 사이트에서 가비지 컬렉션을 실행합니다.
문제 해결#
컨테이너 레지스트리 복제가 활성화되어 있는지 확인#
Rails 콘솔로 다음과 같이 확인할 수 있습니다.
Geo::ContainerRepositoryRegistry.replication_enabled?
컨테이너 레지스트리 알림 이벤트 누락#
- 기본 사이트의 컨테이너 레지스트리에 이미지가 푸시되면 컨테이너 레지스트리 알림 이 트리거되어야 합니다.
- 기본 사이트의 컨테이너 레지스트리는 기본 사이트의 API
https://<example.com>/api/v4/container_registry_event/events를 호출합니다. - 기본 사이트는
geo_events테이블에replicable_name: 'container_repository', model_record_id:레코드를 삽입합니다. - 해당 레코드는 PostgreSQL에 의해 보조 사이트의 데이터베이스로 복제됩니다.
- Geo Log Cursor 서비스가 새 이벤트를 처리하고 Sidekiq job
Geo::EventWorker를 큐에 넣습니다.
이 과정이 올바르게 동작하는지 확인하려면 기본 사이트의 레지스트리에 이미지를 푸시한 뒤, Rails 콘솔에서 다음 명령을 실행해 알림이 수신되어 이벤트로 처리되었는지 확인합니다.
Geo::Event.where(replicable_name: 'container_repository')
geo.log에서 Geo::ContainerRepositorySyncService 항목을 확인해 추가로 검증할 수 있습니다.
레지스트리 이벤트 로그 응답 상태 401 Unauthorized 미허용#
401 Unauthorized 오류는 기본 사이트의 컨테이너 레지스트리 알림이 Rails 애플리케이션에 받아들여지지 않아, 무언가 푸시되었음을 GitLab에 알리지 못하고 있음을 의미합니다.
이 문제를 해결하려면 기본 사이트 구성 단계에서 설정한 대로, 레지스트리 알림과 함께 전송되는 Authorization 헤더가 기본 사이트에 구성된 값과 일치하는지 확인합니다.
값이 일치하는 것으로 보인다면 GitLab에 알림 시크릿이 있기는 한지 확인합니다. Rails를 실행하는 각 노드에서 다음을 실행합니다.
grep notification_secret /var/opt/gitlab/gitlab-rails/etc/gitlab.yml
값이 비어 있다면 GitLab 이 수신 헤더와 대조할 대상이 없다는 뜻이므로, 레지스트리가 무엇을 보내든 모든 알림이 거부됩니다. 값이 비는 원인은 두 가지입니다.
- 노드의
/etc/gitlab/gitlab.rb에registry['notifications']블록이 없어 시크릿을 도출할 대상이 없습니다. 웹 노드의 일반적인 상태이며, 레지스트리가 외부에 있을 때는 모든 노드가 이 상태입니다. 기본 사이트 구성에 설명된 대로gitlab_rails['registry_notification_secret']을 명시적으로 설정합니다. - 블록은 있으나 엔드포인트 이름이
geo_event가 아닙니다. 도출은 엔드포인트 이름으로 이루어지므로 다른 이름을 쓰면 시크릿이 생성되지 않습니다.
Linux 패키지는 도출한 시크릿을 /etc/gitlab/gitlab-secrets.json에 기록하지 않으므로,
gitlab-ctl reconfigure를 실행할 때마다 다시 도출합니다.
따라서 엔드포인트 이름을 잘못 지정한 문제는 이후 재구성으로 저절로 고쳐지지 않습니다.
거부된 알림은 엔드포인트 구성의 maxretries 값만큼 재시도한 뒤 폐기되므로,
푸시 자체는 성공하지만 그에 대한 Geo 이벤트는 만들어지지
않습니다.
시크릿이 비어 있는 동안에는 이후의 푸시도 복제를 시작하지 못합니다.
시크릿을 바로잡은 뒤 리포지터리에 푸시가 한 번 더 일어나면, 장애 기간에 누락된 푸시를 포함해
해당 리포지터리 전체가 재동기화됩니다.
이후 푸시가 없는 리포지터리는 곧바로 재동기화되지 않습니다.
기본 사이트의 주기적 재검증이 체크섬을 다시 계산하고 보조 사이트가 불일치를 감지한 뒤에
재동기화되며, 여기에는 최대
재검증 주기 만큼 걸릴 수 있습니다.
더 빨리 재동기화하려면
컨테이너 레지스트리 동기화 이벤트 수동 트리거에 설명된 대로 Resync all을 선택하거나,
기본 사이트의 모든 컨테이너 리포지터리를 재검증합니다.
다운타임 후 리포지터리가 재동기화되지 않음에 설명된
대상 지정 gitlab:geo:reverify_container_repositories_since 작업은
여기에 해당하지 않습니다.
이 작업은 기본 사이트가 최근에 검증한 리포지터리를 선택하는데, 알림이 거부되는 동안에는
기본 사이트가 푸시된 리포지터리를 재검증하지 않았으므로
다른 리포지터리와 구분할 수 없기 때문입니다.
GitLab 쪽에서 거부를 확인하려면
production_json.log가 아니라 api_json.log를 확인합니다.
알림 엔드포인트는 REST API의 일부이므로,
거부를 포함한 해당 요청은 production_json.log가 아니라
api_json.log에 기록됩니다.
grep container_registry_event /var/log/gitlab/gitlab-rails/api_json.log
레지스트리 오류: token from untrusted issuer: "<token>"#
Geo에서 컨테이너 이미지를 복제할 때 token from untrusted issuer: "<token>" 오류가 나타날 수 있습니다.
이 문제는 컨테이너 레지스트리 구성이 올바르지 않아 Sidekiq의 JWT 인증이 실패할 때 발생합니다.
이 문제를 해결하는 방법은 다음과 같습니다.
- 보조 사이트 구성에 설명된 대로 두 사이트가 하나의 서명 키 쌍을 공유하는지 확인합니다.
- 두 컨테이너 레지스트리와 기본·보조 사이트가 모두 동일한 토큰 발급자를 사용하도록 구성되어 있는지 확인합니다. 자세한 내용은 GitLab과 레지스트리를 별도 노드에 구성을 참고합니다.
- 다중 노드 배포에서는 Sidekiq 노드에 구성된 발급자가 레지스트리에 구성된 값과 일치하는지 확인합니다.
컨테이너 레지스트리 동기화 이벤트 수동 트리거#
문제 해결을 돕기 위해 컨테이너 레지스트리 복제 과정을 수동으로 트리거할 수 있습니다.
- 오른쪽 상단에서 Admin을 선택합니다.
- 왼쪽 사이드바에서 Geo > Sites를 선택합니다.
- Secondary Site의 Replication Details에서 Container Repositories를 선택합니다.
- 한 행에 대해 Resync를 선택하거나 Resync all을 선택합니다.
보조 사이트의 Rails 콘솔에서 다음 명령을 실행해 재동기화를 수동으로 트리거할 수도 있습니다.
registry = Geo::ContainerRepositoryRegistry.first # Choose a Geo registry entry
registry.replicator.sync # Resync the container repository
pp registry.reload # Look at replication state fields
#
state 필드는 동기화 상태를 나타냅니다.
"0": 동기화 대기(대개 한 번도 동기화되지 않았음을 의미합니다)"1": 동기화 시작(동기화 job 이 실행 중입니다)"2": 동기화 성공"3": 동기화 실패
다운타임 후 리포지터리가 재동기화되지 않음#
geo_container_repository_replication 기능 플래그나 잘못된 구성으로
일정 기간 컨테이너 레지스트리 복제가 비활성화되어 있었다면, 그 기간에
푸시된 이미지는 보조 사이트로 자동 동기화되지 않을 수
있습니다.
다운타임 중에 생성된 새 컨테이너 리포지터리는 복제를 다시 활성화한 뒤 백필 워커가 자동으로 처리합니다. 그러나 기존 컨테이너 리포지터리에 대한 갱신(예를 들어 기존 리포지터리에 푸시된 새 태그)은 자동으로 재동기화되지 않습니다. 동기화 상태는 콘텐츠 검증이 아니라 레지스트리 항목의 상태를 기준으로 하므로, Geo 관리 UI는 복제율을 계속 100% 로 표시할 수 있습니다.
갱신된 컨테이너 리포지터리는 기본 사이트의 재검증 주기가 체크섬 불일치를 감지한 뒤 결국 재동기화됩니다. 재검증 주기에 대한 자세한 내용은 리포지터리 재검증을 참고합니다.
geo_container_repository_replication 기능 플래그로 복제를 비활성화했더라도
레지스트리 알림과 기본 사이트 강제 체크섬 계산이 활성화되어 있었다면, 보조 사이트로 복제
이벤트가 전송되지 않았더라도 기본 사이트는 복제가 비활성화된 동안 푸시된 각 컨테이너
리포지터리의 체크섬을 계속 다시 계산했습니다.
따라서 이러한 리포지터리의 검증 타임스탬프(verified_at)로 다운타임 중에 변경된
리포지터리를 식별할 수 있고, 해당 리포지터리만 재검증해도 재동기화를 트리거하기에
충분합니다. 기본 사이트가 최근 N 일 동안 검증한 리포지터리를 재검증 대상으로
표시하려면 기본 사이트에서 다음 명령을 실행합니다.
sudo gitlab-rake "gitlab:geo:reverify_container_repositories_since[7]"
인수는 거슬러 올라갈 일수이며 기본값은 7 입니다. 이 작업은 표시된 리포지터리 수를
보고하고, 이후 주기적 검증 워커가 해당 리포지터리의 체크섬을 다시 계산합니다.
보조 사이트는 그 결과로 발생한 체크섬 불일치를 감지해 해당 리포지터리를 재동기화합니다.
작업이 중단되더라도 다시 실행해도 안전합니다. 이미 표시된 리포지터리는
건너뜁니다.
아무것도 변경하지 않고 몇 개의 컨테이너 리포지터리가 표시될지 미리 확인하려면
DRY_RUN=true를 설정합니다.
sudo DRY_RUN=true gitlab-rake "gitlab:geo:reverify_container_repositories_since[7]"
이 대상 지정 복구는 장애 기간에 레지스트리 알림이 기본 사이트에 도달했고
geo_container_repository_force_primary_checksumming 이 활성화된 상태로 유지되었을 때만 가능합니다.
사용자 실수 등으로 일정 기간 레지스트리 알림이 잘못 구성되어 있었다면 그 기간의 태그 푸시가 기록되지 않으므로, Geo는 어떤 컨테이너 리포지터리가 변경되었는지 알 수 없습니다. 이 경우에는 기본 사이트의 모든 컨테이너 리포지터리를 재검증하는 것을 권장합니다. 기본 사이트가 모든 체크섬을 다시 계산하면, 보조 사이트는 콘텐츠가 변경된 리포지터리만 재동기화합니다.