InfoGrab DocsInfoGrab Docs

컨테이너 레지스트리 문제 해결

요약

특정 문제를 조사하기 전에 다음 문제 해결 단계를 시도합니다: Docker 클라이언트와 GitLab 서버의 시스템 시계가 동기화되어 있는지 확인합니다(예: NTP 사용). S3 기반 레지스트리의 경우 IAM 권한과 S3 자격 증명(리전 포함)이 올바른지 확인합니다.

특정 문제를 조사하기 전에 다음 문제 해결 단계를 시도합니다:

  1. Docker 클라이언트와 GitLab 서버의 시스템 시계가 동기화되어 있는지 확인합니다(예: NTP 사용).

  2. S3 기반 레지스트리의 경우 IAM 권한과 S3 자격 증명(리전 포함)이 올바른지 확인합니다. 자세한 내용은 샘플 IAM 정책을 참고합니다.

  3. 레지스트리 로그(예: /var/log/gitlab/registry/current)와 GitLab 프로덕션 로그 (예: /var/log/gitlab/gitlab-rails/production.log)에서 오류를 확인합니다.

  4. 컨테이너 레지스트리의 NGINX 구성 파일(예: /var/opt/gitlab/nginx/conf/gitlab-registry.conf)을 검토해 어떤 포트가 요청을 수신하는지 확인합니다.

  5. 요청이 컨테이너 레지스트리로 올바르게 전달되는지 확인합니다:

    curl --verbose --noproxy "*" https://<hostname>:<port>/v2/_catalog
    

    응답에는 service="container_registry"를 포함하는 Www-Authenticate: Bearer 줄이 있어야 합니다. 예를 들어:

    < HTTP/1.1 401 Unauthorized
    < Server: nginx
    < Date: Fri, 07 Mar 2025 08:24:43 GMT
    < Content-Type: application/json
    < Content-Length: 162
    < Connection: keep-alive
    < Docker-Distribution-Api-Version: registry/2.0
    < Www-Authenticate: Bearer realm="https://<hostname>/jwt/auth",service="container_registry",scope="registry:catalog:*"
    < X-Content-Type-Options: nosniff
    <
    {"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":
    [{"Type":"registry","Class":"","Name":"catalog","ProjectPath":"","Action":"*"}]}]}
    * Connection #0 to host <hostname> left intact
    

오류: There are pending migrations#

보류 중인 레지스트리 데이터베이스 마이그레이션 문제를 해결할 때는 먼저 현재 마이그레이션 상태를 확인합니다. 알려진 모든 마이그레이션의 상태와 각 마이그레이션의 적용 여부를 확인하려면 다음을 실행합니다:

sudo gitlab-ctl registry-database migrate status

모든 마이그레이션이 적용되었는지 확인하려면 다음을 실행합니다:

sudo gitlab-ctl registry-database migrate status --up-to-date

이 명령은 true 또는 false만 반환합니다:

  • true: 알려진 모든 레지스트리 데이터베이스 마이그레이션이 적용되었습니다.
  • false: 하나 이상의 레지스트리 데이터베이스 마이그레이션이 아직 보류 중입니다.

--up-to-date 옵션은 업그레이드 전후로 마이그레이션 상태를 검증할 때, 또는 보류 중인 마이그레이션 때문에 레지스트리가 차단되었는지 확인할 때 유용합니다.

출력이 false이면 데이터베이스 마이그레이션 적용 단계를 따릅니다.

오류: ... x509: certificate signed by unknown authority#

컨테이너 레지스트리에 자체 서명 인증서를 사용하면 CI/CD 파이프라인 job에서 다음과 유사한 오류가 발생할 수 있습니다:

Error response from daemon: Get registry.example.com/v1/users/: x509: certificate signed by unknown authority

이 오류는 명령을 실행하는 Docker 데몬이 자체 서명 인증서가 아니라 인정된 인증 기관이 서명한 인증서를 요구하기 때문에 발생합니다.

이 오류를 해결하려면 자체 서명 인증서를 신뢰하도록 Docker를 구성합니다. Docker 구성에 관한 도움말은 자체 서명 인증서 구성을 참고합니다.

추가 정보는 이슈 18239를 참고합니다.

Docker 로그인 시도 실패: 'token signed by untrusted key'#

레지스트리는 자격 증명 검증을 GitLab에 의존합니다 레지스트리가 유효한 로그인 시도를 인증하지 못하면 다음 오류 메시지가 표시됩니다:

# docker login gitlab.company.com:4567
Username: user
Password:
Error response from daemon: login attempt to https://gitlab.company.com:4567/v2/ failed with status: 401 Unauthorized

더 구체적으로는 /var/log/gitlab/registry/current 로그 파일에 다음과 같이 나타납니다:

level=info
msg="token signed by untrusted key with ID: "TOKE:NL6Q:7PW6:EXAM:PLET:OKEN:BG27:RCIB:D2S3:EXAM:PLET:OKEN""
level=warning msg="error authorizing context: invalid token" go.version=go1.12.7 http.request.host="gitlab.company.com:4567"
http.request.id=74613829-2655-4f96-8991-1c9fe33869b8 http.request.method=GET http.request.remoteaddr=10.72.11.20
http.request.uri="/v2/" http.request.useragent="docker/19.03.2 go/go1.12.8 git-commit/6a30dfc
kernel/3.10.0-693.2.2.el7.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.2 \(linux\))"

(가독성을 위해 줄바꿈을 추가했습니다.)

GitLab은 인증서 키 쌍 양쪽의 내용을 사용해 레지스트리용 인증 토큰을 암호화합니다. 이 메시지는 그 내용이 서로 맞지 않는다는 뜻입니다.

어떤 파일이 사용되고 있는지 확인합니다:

  • grep -A6 'auth:' /var/opt/gitlab/registry/config.yml

    ## Container registry certificate
       auth:
         token:
           realm: https://gitlab.my.net/jwt/auth
           service: container_registry
           issuer: omnibus-gitlab-issuer
      -->  rootcertbundle: /var/opt/gitlab/registry/gitlab-registry.crt
           autoredirect: false
    
  • grep -A9 'Container Registry' /var/opt/gitlab/gitlab-rails/etc/gitlab.yml

    ## Container registry key
       registry:
         enabled: true
         host: gitlab.company.com
         port: 4567
         api_url: http://127.0.0.1:5000 # internal address to the registry, is used by GitLab to directly communicate with API
         path: /var/opt/gitlab/gitlab-rails/shared/registry
    -->  key: /var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key
         issuer: omnibus-gitlab-issuer
         notification_secret:
    

다음 openssl 명령의 출력은 서로 일치해야 하며, 일치하면 인증서와 키 쌍이 맞는다는 것이 증명됩니다:

/opt/gitlab/embedded/bin/openssl x509 -noout -modulus -in /var/opt/gitlab/registry/gitlab-registry.crt | /opt/gitlab/embedded/bin/openssl sha256
/opt/gitlab/embedded/bin/openssl rsa -noout -modulus -in /var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key | /opt/gitlab/embedded/bin/openssl sha256

인증서의 두 조각이 서로 맞지 않으면 파일을 제거하고 gitlab-ctl reconfigure를 실행해 쌍을 다시 생성합니다. /etc/gitlab/gitlab-secrets.json에 기존 값이 있으면 그 값으로 쌍이 다시 만들어집니다. 새 쌍을 생성하려면 gitlab-ctl reconfigure를 실행하기 전에 /etc/gitlab/gitlab-secrets.json의 registry 섹션을 삭제합니다.

자동 생성된 자체 서명 쌍을 직접 준비한 인증서로 덮어썼고 그 내용이 서로 맞는 것을 확인했다면, /etc/gitlab/gitlab-secrets.json의 'registry' 섹션을 삭제하고 gitlab-ctl reconfigure를 실행할 수 있습니다.

GitLab 레지스트리로 대용량 이미지를 푸시할 때 AWS S3 오류#

GitLab 레지스트리와 AWS S3를 함께 사용할 때 대용량 이미지를 푸시하면 오류가 발생할 수 있습니다. 레지스트리 로그에서 다음 오류를 확인합니다:

level=error msg="response completed with error" err.code=unknown err.detail="unexpected EOF" err.message="unknown error"

이 오류를 해결하려면 레지스트리 구성에서 chunksize 값을 지정합니다. 25000000(25 MB)에서 50000000(50 MB) 사이의 값으로 시작합니다.

  1. /etc/gitlab/gitlab.rb를 편집합니다:

    registry['storage'] = {
      's3' => {
        'accesskey' => 'AKIAKIAKI',
        'secretkey' => 'secret123',
        'bucket'    => 'gitlab-registry-bucket-AKIAKIAKI',
        'chunksize' => 25000000
      }
    }
    
  2. 파일을 저장하고 변경 사항을 적용하려면 GitLab을 재구성합니다.

  1. config/gitlab.yml을 편집합니다:

    storage:
      s3:
        accesskey: 'AKIAKIAKI'
        secretkey: 'secret123'
        bucket: 'gitlab-registry-bucket-AKIAKIAKI'
        chunksize: 25000000
    
  2. 파일을 저장하고 변경 사항을 적용하려면 GitLab을 재시작합니다.

GitLab 19.0으로 업그레이드한 후 이미지를 가져올 때 발생하는 403 Forbidden#

GitLab 19.0에서 s3 및 s3aws 컨테이너 레지스트리 스토리지 드라이버 이름은 AWS SDK v2를 사용하는 s3_v2 드라이버의 별칭이 되었습니다. AWS S3에서는 클라이언트가 blob을 다운로드할 때 따라가는 사전 서명 URL의 호스트 이름이 전역 s3.amazonaws.com에서 s3.us-east-1.amazonaws.com과 같은 리전 호스트 이름으로 바뀔 수 있습니다.

프록시, 방화벽, 보안 웹 게이트웨이가 s3.amazonaws.com만 허용하면 리전 호스트 이름으로의 리디렉션이 차단되어 이미지 풀이 403 Forbidden으로 실패합니다. 이 403 Forbidden 응답은 Amazon S3나 레지스트리가 아니라 필터링 장치에서 오는 것이므로, 레지스트리 로그에는 blob 요청이 정상적으로 완료된 것으로 표시됩니다.

이 원인을 확인하려면 blob을 요청하고 리디렉션 대상을 살펴봅니다:

curl --head "https://gitlab.example.com:5050/v2/mygroup/myproject/myimage/blobs/<digest>"

location 응답 헤더를 확인합니다. 그 헤더의 호스트 이름이 이그레스 제어에서 허용되지 않으면, 버킷 리전에 해당하는 리전 호스트 이름을 필터링 장치의 허용 목록에 추가합니다. Amazon S3 전체를 포괄하는 와일드카드가 아니라 구체적인 리전 호스트 이름을 사용합니다.

또는 regionendpoint를 S3 VPC 엔드포인트나 다른 고정 엔드포인트로 설정해 사전 서명 URL이 직접 제어하는 호스트 이름을 사용하도록 합니다. 자세한 내용은 오브젝트 스토리지 사용을 참고합니다.

이전 Docker 클라이언트 지원#

GitLab과 함께 제공되는 Docker 컨테이너 레지스트리는 기본적으로 schema1 매니페스트를 비활성화합니다. 여전히 이전 Docker 클라이언트(1.9 이하)를 사용하고 있다면 이미지를 푸시할 때 오류가 발생할 수 있습니다. 자세한 내용은 이슈 4145를 참고합니다.

하위 호환성을 위해 구성 옵션을 추가할 수 있습니다.

  1. /etc/gitlab/gitlab.rb를 편집합니다:

    registry['compatibility_schema1_enabled'] = true
    
  2. 파일을 저장하고 변경 사항을 적용하려면 GitLab을 재구성합니다.

  1. 레지스트리를 배포할 때 만든 YAML 구성 파일을 편집합니다. 다음 스니펫을 추가합니다:

    compatibility:
        schema1:
            enabled: true
    
  2. 변경 사항을 적용하려면 레지스트리를 재시작합니다.

Docker 연결 오류#

그룹, 프로젝트, 브랜치 이름에 특수 문자가 있으면 Docker 연결 오류가 발생할 수 있습니다. 특수 문자에는 다음이 포함됩니다:

  • 앞에 붙은 밑줄
  • 끝에 붙은 하이픈
  • 이중 하이픈

이 문제를 피하려면 그룹 경로를 변경하거나 프로젝트 경로를 변경하거나 브랜치 이름을 변경할 수 있습니다. 또 다른 방법은 푸시 규칙을 만들어 인스턴스 전체에서 이 오류를 방지하는 것입니다.

이미지 푸시 오류#

docker login이 성공해도 Docker 이미지를 푸시할 때 재시도 루프에 갇힐 수 있습니다.

이 문제는 NGINX가 헤더를 레지스트리로 제대로 전달하지 않을 때 발생하며, 주로 SSL을 서드파티 리버스 프록시로 오프로드하는 사용자 지정 설정에서 나타납니다.

자세한 내용은 Docker push through NGINX proxy fails trying to send a 32B layer #970을 참고합니다.

이 문제를 해결하려면 레지스트리에서 상대 URL을 사용하도록 NGINX 구성을 업데이트합니다:

  1. /etc/gitlab/gitlab.rb를 편집합니다:

    registry['env'] = {
      "REGISTRY_HTTP_RELATIVEURLS" => true
    }
    
  2. 파일을 저장하고 변경 사항을 적용하려면 GitLab을 재구성합니다.

  1. 레지스트리를 배포할 때 만든 YAML 구성 파일을 편집합니다. 다음 스니펫을 추가합니다:

    http:
        relativeurls: true
    
  2. 파일을 저장하고 변경 사항을 적용하려면 GitLab을 재시작합니다.

  1. docker-compose.yaml 파일을 편집합니다:

    GITLAB_OMNIBUS_CONFIG: |
      registry['env'] = {
        "REGISTRY_HTTP_RELATIVEURLS" => true
      }
    
  2. 문제가 계속되면 두 URL 모두 HTTPS를 사용하는지 확인합니다:

    GITLAB_OMNIBUS_CONFIG: |
      external_url 'https://git.example.com'
      registry_external_url 'https://git.example.com:5050'
    
  3. 파일을 저장하고 컨테이너를 재시작합니다:

    sudo docker restart gitlab
    

레지스트리 디버그 서버 활성화#

컨테이너 레지스트리 디버그 서버로 문제를 진단할 수 있습니다. 디버그 엔드포인트는 메트릭과 상태를 모니터링할 수 있고 프로파일링도 수행할 수 있습니다.

Warning

디버그 엔드포인트에서 민감한 정보가 노출될 수 있습니다. 프로덕션 환경에서는 디버그 엔드포인트에 대한 접근을 반드시 차단해야 합니다.

gitlab.rb 구성에서 레지스트리 디버그 주소를 설정하면 선택 사항인 디버그 서버를 활성화할 수 있습니다.

registry['debug_addr'] = "localhost:5001"

설정을 추가한 후 변경 사항을 적용하려면 GitLab을 재구성합니다.

curl로 디버그 서버에 디버그 출력을 요청합니다:

curl "localhost:5001/debug/health"
curl "localhost:5001/debug/vars"

Prometheus 메트릭#

Prometheus는 컨테이너 레지스트리의 성능 문제를 모니터링하고 해결하는 데 사용할 수 있는 메트릭을 제공합니다.

다음 섹션에서 다루는 내용입니다:

  • Prometheus 메트릭을 활성화하는 방법
  • 컨테이너 레지스트리가 내보내는 모든 Prometheus 메트릭을 구성 요소별로 정리한 목록

Prometheus 메트릭 활성화#

사전 요구 사항:

Prometheus 메트릭을 활성화하려면 gitlab.rb에 다음 구성을 추가합니다:

# Enable Prometheus metrics
registry['debug'] = {
  'prometheus' => {
    'enabled' => true,
    'path' => '/metrics'
  }
}

변경 사항을 적용하려면 GitLab을 재구성합니다.

curl로 디버그 서버에 메트릭을 요청합니다:

curl "localhost:5001/metrics"

알림 메트릭#

카운터#

메트릭 이름 설명 레이블 레이블 값
registry_notifications_events_total 총 이벤트 수. type, action, artifact, endpoint type: Successes, Failures, Events, Dropped
registry_notifications_status_total 알림 엔드포인트에서 수신한 HTTP 상태 코드별 응답 수. code, endpoint code: HTTP 상태 코드(예: 200 OK 또는 404 Not Found)
registry_notifications_errors_total 전송 중 오류가 발생한 이벤트 수. 전송 요청은 재시도될 수 있습니다. endpoint string: '...'
registry_notifications_delivery_total 전달되었거나 손실된 이벤트 수. 재시도 횟수가 소진되면 이벤트는 손실됩니다. endpoint, delivery_type delivery_type: delivered, lost

게이지#

메트릭 이름 설명 레이블 레이블 값
registry_notifications_pending 큐 길이로 표시되는 큐의 보류 중인 이벤트 게이지. endpoint string: '...'

히스토그램#

메트릭 이름 설명 레이블 버킷
registry_notifications_retries_count 누적 전달 재시도 횟수 히스토그램. endpoint [0, 1, 2, 3, 5, 10, 15, 20, 30, 50]
registry_notifications_http_latency_seconds HTTP 전달 지연 시간 히스토그램. endpoint [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 25, 50, 100] (초)
registry_notifications_total_latency_seconds 총 전달 지연 시간 히스토그램. endpoint [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 25, 50, 100] (초)

일괄 백그라운드 마이그레이션(BBM) 메트릭#

카운터#
메트릭 이름 설명 레이블 레이블 값
registry_bbm_runs_total 일괄 마이그레이션 워커 실행 카운터. 없음 없음
registry_bbm_migrated_tuples_total 마이그레이션된 총 일괄 마이그레이션 레코드 카운터. migration_name, migration_id string: '...'
게이지#
메트릭 이름 설명 레이블 레이블 값
registry_bbm_job_batch_size 일괄 마이그레이션 job의 배치 크기 게이지. migration_name, migration_id string: '...'
registry_database_bbm_progress_percent 백그라운드 마이그레이션 진행 비율(0-100). migration_id, migration_name, status string: '...'
히스토그램#
메트릭 이름 설명 레이블 버킷
registry_bbm_run_duration_seconds 일괄 마이그레이션 워커 실행 지연 시간 히스토그램. 없음 [0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600] (0.5초~1시간)
registry_bbm_job_duration_seconds 일괄 마이그레이션 job의 지연 시간 히스토그램. migration_name, migration_id [0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600] (0.5초~1시간)
registry_bbm_query_duration_seconds 일괄 마이그레이션 데이터베이스 쿼리 지연 시간 히스토그램. migration_name, migration_id [0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600] (0.5초~1시간)
registry_bbm_sleep_duration_seconds BBM 워커 실행 사이의 슬립 시간 히스토그램. worker [0.5, 1, 5, 15, 30, 60, 300, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200, 86400] (500ms~24시간)

데이터베이스 메트릭#

카운터#
메트릭 이름 설명 레이블 레이블 값
registry_database_queries_total 데이터베이스 쿼리 카운터. name string: '...'
registry_database_lb_lsn_cache_hits_total 데이터베이스 로드 밸런싱 LSN 캐시 히트 및 미스 카운터. result result: hit, miss
registry_database_lb_pool_events_total 데이터베이스 로드 밸런서 풀에 추가되거나 풀에서 제거된 복제본 카운터. event, reason event: replica_added, replica_removed, replica_quarantined, replica_reintegrated
reason: replication_lag, connectivity, removed_from_dns, discovered
registry_database_lb_targets_total 데이터베이스 로드 밸런싱 중 기본 및 복제본 타깃 선출 카운터. target_type, fallback, reason target_type: primary, replica
fallback: true, false
reason: selected, no_cache, no_replica, error, not_up_to_date, all_quarantined
게이지#
메트릭 이름 설명 레이블 레이블 값
registry_database_lb_pool_size 로드 밸런서 풀에 있는 현재 복제본 수 게이지. 없음 없음
registry_database_lb_pool_status 로드 밸런서 풀에 있는 각 복제본의 현재 상태 게이지. replica, status status: online, quarantined
registry_database_lb_lag_bytes 각 복제본의 복제 지연을 바이트로 나타내는 게이지. replica string: '...'
registry_database_migrations_total 데이터베이스 마이그레이션 총 수(적용 + 보류) 게이지 migration_type migration_type: pre_deployment, post_deployment
registry_database_rows query_name 레이블로 정의된 데이터베이스 테이블의 행 수 게이지. query_name query_name: gc_blob_review_queue, gc_manifest_review_queue, gc_blob_review_queue_overdue, gc_manifest_review_queue_overdue, applied_pre_migrations, applied_post_migrations
히스토그램#
메트릭 이름 설명 레이블 버킷
registry_database_query_duration_seconds 데이터베이스 쿼리 지연 시간 히스토그램. name Prometheus 기본 버킷.1
registry_database_lb_lsn_cache_operation_duration_seconds 데이터베이스 로드 밸런싱 LSN 캐시 작업 지연 시간 히스토그램. operation, error operation: set, get
error: true, false
Prometheus 기본 버킷.1
registry_database_lb_lookup_seconds 데이터베이스 로드 밸런싱 DNS 조회 지연 시간 히스토그램. lookup_type, error lookup_type: srv, host
error: true, false
Prometheus 기본 버킷.1
registry_database_lb_lag_seconds 각 복제본의 복제 지연을 초 단위로 나타낸 히스토그램. replica [0.001, 0.01, 0.1, 0.5, 1, 5, 10, 20, 30, 60] (1ms~60초)
registry_database_row_count_collection_duration_seconds 한 번의 실행에서 모든 데이터베이스 행 수 쿼리를 수집하는 데 걸린 총 시간 히스토그램. 없음 [0.1, 0.5, 1, 2, 5, 10, 30, 60] (100ms~60초)

가비지 수집(GC) 메트릭#

카운터#
메트릭 이름 설명 레이블 레이블 값
registry_gc_runs_total 온라인 GC 워커 실행 카운터. worker, noop, error, dangling, event noop: true, false
error: true, false
dangling: true, false
registry_gc_deletes_total 온라인 GC 중 삭제된 아티팩트 카운터. backend, artifact backend: storage, database
artifact: blob, manifest
registry_gc_storage_deleted_bytes_total 온라인 GC 중 스토리지에서 삭제된 바이트 카운터. media_type string: '...'
registry_gc_postpones_total 온라인 GC 검토 연기 카운터. worker string: '...'
히스토그램#
메트릭 이름 설명 레이블 버킷
registry_gc_run_duration_seconds 온라인 GC 워커 실행 지연 시간 히스토그램. worker, noop, error, dangling, event noop: true, false
error: true, false
dangling: true, false
Prometheus 기본 버킷.2
registry_gc_delete_duration_seconds 온라인 GC 중 아티팩트 삭제 지연 시간 히스토그램. backend, artifact, error backend: storage, database
artifact: blob, manifest
error: true, false
Prometheus 기본 버킷.2
registry_gc_sleep_duration_seconds 온라인 GC 워커 실행 사이의 슬립 시간 히스토그램. worker [0.5, 1, 5, 15, 30, 60, 300, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200, 86400] (500ms~24시간)

스토리지 메트릭#

카운터#
메트릭 이름 설명 레이블 레이블 값
registry_storage_cdn_redirects_total blob 다운로드에 대한 CDN 리디렉션 카운터. backend, bypass, bypass_reason bypass: true, false
registry_storage_rate_limit_total 속도 제한에 도달한 스토리지 드라이버 요청 카운터. 없음 없음
registry_storage_storage_backend_retries_total 스토리지 백엔드와 통신하는 중 발생한 재시도 카운터. retry_type retry_type: native, custom
registry_storage_urlcache_requests_total URL 캐시 미들웨어 요청 카운터. result, reason result: hit, miss
registry_storage_access_tracker_dropped_events 시간 초과로 액세스 추적기에서 삭제된 이벤트 카운터. 없음 없음
게이지#
메트릭 이름 설명 레이블 레이블 값
registry_storage_object_accesses_topn 가장 자주 액세스되는 상위 N개 객체의 총 액세스 수. top_n top_n: 1, 10, 100, 1000, 10000, all
히스토그램#
메트릭 이름 설명 레이블 버킷
registry_storage_blob_download_bytes 스토리지 백엔드의 blob 다운로드 크기 히스토그램. redirect redirect: true, false
[524288, 1048576, 67108864, 134217728, 268435456, 536870912, 1073741824, 2147483648, 3221225472, 4294967296, 5368709120, 6442450944, 7516192768, 8589934592, 9663676416, 10737418240, 21474836480, 32212254720, 42949672960, 53687091200] (512KiB~50GiB)
registry_storage_blob_upload_bytes 스토리지 백엔드의 새 blob 업로드 바이트 히스토그램. 없음 [524288, 1048576, 67108864, 134217728, 268435456, 536870912, 1073741824, 2147483648, 3221225472, 4294967296, 5368709120, 6442450944, 7516192768, 8589934592, 9663676416, 10737418240, 21474836480, 32212254720, 42949672960, 53687091200] (512KiB~50GiB)
registry_storage_urlcache_object_size URL 캐시의 객체 크기 히스토그램. 없음 [100, 250, 500, 750, 1000, 1500, 2048, 3072, 5120, 10240] (100바이트~10KiB)
registry_storage_object_accesses_distribution 모든 객체에 걸친 액세스 수 분포. 없음 지수 버킷: [10, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 10240]

레지스트리 디버그 로그 활성화#

디버그 로그를 활성화하면 컨테이너 레지스트리 문제를 해결하는 데 도움이 됩니다.

Warning

디버그 로그에는 인증 정보, 토큰, 리포지터리 정보와 같은 민감한 정보가 포함될 수 있습니다. 필요한 경우에만 디버그 로그를 활성화하고, 문제 해결이 끝나면 비활성화합니다.

  1. /var/opt/gitlab/registry/config.yml을 편집합니다:

    level: debug
    
  2. 파일을 저장하고 레지스트리를 재시작합니다:

    sudo gitlab-ctl restart registry
    

이 구성은 임시이며 gitlab-ctl reconfigure를 실행하면 사라집니다.

  1. Helm 값을 내보냅니다:

    helm get values gitlab > gitlab_values.yaml
    
  2. gitlab_values.yaml을 편집합니다:

    registry:
      log:
        level: debug
    
  3. 파일을 저장하고 새 값을 적용합니다:

    helm upgrade -f gitlab_values.yaml gitlab gitlab/gitlab --namespace <namespace>
    

레지스트리 Prometheus 메트릭 활성화#

디버그 서버가 활성화되어 있으면 Prometheus 메트릭도 활성화할 수 있습니다. 이 엔드포인트는 거의 모든 레지스트리 작업에 관한 매우 상세한 텔레메트리를 노출합니다.

registry['debug'] = {
  'prometheus' => {
    'enabled' => true,
    'path' => '/metrics'
  }
}

curl로 Prometheus에 디버그 출력을 요청합니다:

curl "localhost:5001/debug/metrics"

이름이 빈 태그#

AWS DataSync를 사용해 레지스트리 데이터를 S3 버킷으로 또는 S3 버킷 사이에 복사하면, 대상 버킷의 각 컨테이너 리포지터리 루트 경로에 빈 메타데이터 객체가 생성됩니다. 이로 인해 레지스트리는 이러한 파일을 GitLab UI와 API에서 이름 없이 표시되는 태그로 해석합니다. 자세한 내용은 이 이슈를 참고합니다.

이를 수정하려면 다음 두 가지 중 하나를 수행할 수 있습니다:

  • AWS CLI rm 명령으로 영향받은 각 리포지터리의 루트에서 빈 객체를 제거합니다. 끝에 오는 /에 특히 주의하고 --recursive 옵션은 사용하지 않습니다:

    aws s3 rm s3://<bucket>/docker/registry/v2/repositories/<path to repository>/
    
  • AWS CLI sync 명령으로 레지스트리 데이터를 새 버킷에 복사하고 그 버킷을 사용하도록 레지스트리를 구성합니다. 이 방법은 빈 객체를 그대로 남겨 둡니다.

고급 문제 해결#

S3 설정과 관련된 문제를 진단하는 방법을 구체적인 예로 설명합니다.

정리 정책 조사#

정리 정책이 태그를 삭제했거나 삭제하지 않은 이유가 확실하지 않으면, Rails 콘솔에서 아래 스크립트를 실행해 정책을 한 줄씩 실행합니다. 이 방법은 정책의 문제를 진단하는 데 도움이 됩니다.

repo = ContainerRepository.find(<repository_id>)
policy = repo.project.container_expiration_policy

tags = repo.tags
tags.map(&:name)

tags.reject!(&:latest?)
tags.map(&:name)

regex_delete = ::Gitlab::UntrustedRegexp.new("\\A#{policy.name_regex}\\z")
regex_retain = ::Gitlab::UntrustedRegexp.new("\\A#{policy.name_regex_keep}\\z")

tags.select! { |tag| regex_delete.match?(tag.name) && !regex_retain.match?(tag.name) }

tags.map(&:name)

now = DateTime.current
tags.sort_by! { |tag| tag.created_at || now }.reverse! # Lengthy operation

tags = tags.drop(policy.keep_n)
tags.map(&:name)

older_than_timestamp = ChronicDuration.parse(policy.older_than).seconds.ago

tags.select! { |tag| tag.created_at && tag.created_at < older_than_timestamp }

tags.map(&:name)
  • 스크립트는 삭제할 태그 목록(tags)을 만듭니다.
  • tags.map(&:name)은 제거할 태그 목록을 출력합니다. 시간이 오래 걸리는 작업일 수 있습니다.
  • 각 필터를 적용한 후 tags 목록에 삭제하려는 태그가 들어 있는지 확인합니다.

푸시 중 예기치 않은 403 오류#

어떤 사용자가 S3 기반 레지스트리를 활성화하려고 했습니다. docker login 단계는 정상적으로 진행되었습니다. 그러나 이미지를 푸시할 때 출력은 다음과 같았습니다:

The push refers to a repository [s3-testing.myregistry.com:5050/root/docker-test/docker-image]
dc5e59c14160: Pushing [==================================================>] 14.85 kB
03c20c1a019a: Pushing [==================================================>] 2.048 kB
a08f14ef632e: Pushing [==================================================>] 2.048 kB
228950524c88: Pushing 2.048 kB
6a8ecde4cc03: Pushing [==>                                                ] 9.901 MB/205.7 MB
5f70bf18a086: Pushing 1.024 kB
737f40e80b7f: Waiting
82b57dbc5385: Waiting
19429b698a22: Waiting
9436069b92a3: Waiting
error parsing HTTP 403 response body: unexpected end of JSON input: ""

이 오류는 403이 GitLab Rails 애플리케이션에서 온 것인지, Docker 레지스트리에서 온 것인지, 아니면 다른 곳에서 온 것인지 명확하지 않아 모호합니다. 이 사례에서는 로그인이 성공했다는 것을 알고 있으므로, 클라이언트와 레지스트리 사이의 통신을 살펴봐야 할 가능성이 큽니다.

Docker 클라이언트와 레지스트리 사이의 REST API는 Docker 문서에 설명되어 있습니다. 보통은 Wireshark나 tcpdump로 트래픽을 캡처해 무엇이 잘못되었는지 확인합니다. 그러나 Docker 클라이언트와 서버 사이의 모든 통신이 HTTPS로 이루어지므로, 개인 키를 알고 있어도 트래픽을 빠르게 복호화하기는 다소 어렵습니다. 그래서 대신 다음 방법을 사용합니다.

한 가지 방법은 비보안 레지스트리를 설정해 HTTPS를 비활성화하는 것입니다. 이 방법은 보안 구멍을 만들 수 있어 로컬 테스트에만 권장됩니다. 프로덕션 시스템이 있어 이 방법을 쓸 수 없거나 쓰고 싶지 않다면 다른 방법이 있습니다. 중간자 프록시(Man-in-the-Middle Proxy)를 뜻하는 mitmproxy를 사용합니다.

mitmproxy#

mitmproxy를 사용하면 클라이언트와 서버 사이에 프록시를 두어 모든 트래픽을 검사할 수 있습니다. 한 가지 번거로운 점은 이 방법이 동작하려면 시스템이 mitmproxy SSL 인증서를 신뢰해야 한다는 것입니다.

다음 설치 지침은 Ubuntu를 사용한다고 가정합니다:

  1. mitmproxy를 설치합니다.

  2. mitmproxy --port 9000을 실행해 인증서를 생성합니다. 종료하려면 Control-C를 입력합니다.

  3. ~/.mitmproxy의 인증서를 시스템에 설치합니다:

    sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy-ca-cert.crt
    sudo update-ca-certificates
    

성공하면 인증서가 추가되었다는 내용이 출력됩니다:

Updating certificates in /etc/ssl/certs... 1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d....done.

인증서가 제대로 설치되었는지 확인하려면 다음을 실행합니다:

mitmproxy --listen-port 9000

이 명령은 포트 9000에서 mitmproxy를 실행합니다. 다른 창에서 다음을 실행합니다:

curl --proxy "http://localhost:9000" "https://httpbin.org/status/200"

모두 올바르게 설정되었다면 mitmproxy 창에 정보가 표시되고 curl 명령에서 오류가 발생하지 않습니다.

프록시를 통해 Docker 데몬 실행#

Docker가 프록시를 통해 연결하려면 적절한 환경 변수와 함께 Docker 데몬을 시작해야 합니다. 가장 쉬운 방법은 Docker를 종료하고(예: sudo initctl stop docker) Docker를 직접 실행하는 것입니다. root 권한으로 다음을 실행합니다:

export HTTP_PROXY="http://localhost:9000"
export HTTPS_PROXY="http://localhost:9000"
docker daemon --debug # or dockerd --debug

이 명령은 Docker 데몬을 시작하고 모든 연결을 mitmproxy를 통해 프록시합니다.

Docker 클라이언트 실행#

이제 mitmproxy와 Docker가 실행 중이므로 로그인하고 컨테이너 이미지를 푸시해 볼 수 있습니다. 이 작업에는 root 권한이 필요할 수 있습니다. 예를 들어:

docker login example.s3.amazonaws.com:5050
docker push example.s3.amazonaws.com:5050/root/docker-test/docker-image

앞의 예시에서 mitmproxy 창에 다음 추적이 표시됩니다:

PUT https://example.s3.amazonaws.com:4567/v2/root/docker-test/blobs/uploads/(UUID)/(QUERYSTRING)
    ← 201 text/plain [no content] 661ms
HEAD https://example.s3.amazonaws.com:4567/v2/root/docker-test/blobs/sha256:(SHA)
    ← 307 application/octet-stream [no content] 93ms
HEAD https://example.s3.amazonaws.com:4567/v2/root/docker-test/blobs/sha256:(SHA)
    ← 307 application/octet-stream [no content] 101ms
HEAD https://example.s3.amazonaws.com:4567/v2/root/docker-test/blobs/sha256:(SHA)
    ← 307 application/octet-stream [no content] 87ms
HEAD https://amazonaws.example.com/docker/registry/vs/blobs/sha256/dd/(UUID)/data(QUERYSTRING)
    ← 403 application/xml [no content] 80ms
HEAD https://amazonaws.example.com/docker/registry/vs/blobs/sha256/dd/(UUID)/data(QUERYSTRING)
    ← 403 application/xml [no content] 62ms

이 출력이 보여 주는 내용입니다:

  • 초기 PUT 요청은 201 상태 코드로 정상 처리되었습니다.
  • 201은 클라이언트를 Amazon S3 버킷으로 리디렉션했습니다.
  • AWS 버킷에 대한 HEAD 요청은 403 Unauthorized를 보고했습니다.

이것이 뜻하는 바는 다음과 같습니다. 이는 S3 사용자에게 HEAD 요청을 수행할 권한이 없다는 것을 강하게 시사합니다. 해결 방법은 IAM 권한을 다시 확인하는 것입니다. 올바른 권한을 설정한 후 오류가 사라졌습니다.

gitlab-registry.key 누락으로 컨테이너 리포지터리 삭제 불가#

GitLab 인스턴스의 컨테이너 레지스트리를 비활성화한 상태에서 컨테이너 리포지터리가 있는 프로젝트를 제거하려고 하면 다음 오류가 발생합니다:

Errno::ENOENT: No such file or directory @ rb_sysopen - /var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key

이 경우 다음 단계를 따릅니다:

  1. gitlab.rb에서 컨테이너 레지스트리의 인스턴스 전체 설정을 일시적으로 활성화합니다:

    gitlab_rails['registry_enabled'] = true
    
  2. 파일을 저장하고 GitLab을 재구성해 변경 사항을 적용합니다.

  3. 제거를 다시 시도합니다.

일반적인 방법으로 리포지터리를 제거할 수 없으면 GitLab Rails 콘솔을 사용해 프로젝트를 강제로 제거할 수 있습니다:

# Path to the project you'd like to remove
prj = Project.find_by_full_path(<project_path>)

# The following will delete the project's container registry, so be sure to double-check the path beforehand!
if prj.has_container_registry_tags?
  prj.container_repositories.each { |p| p.destroy }
end

레지스트리 서비스가 IPv4 대신 IPv6 주소에서 수신#

GitLab 서버에서 localhost 호스트 이름이 IPv6 루프백 주소(::1)로 해석되고 GitLab은 레지스트리 서비스가 IPv4 루프백 주소(127.0.0.1)에서 사용 가능하다고 예상하는 경우, 다음 오류가 표시될 수 있습니다:

request: "GET /v2/ HTTP/1.1", upstream: "http://[::1]:5000/v2/", host: "registry.example.com:5005"
[error] 1201#0: *13442797 connect() failed (111: Connection refused) while connecting to upstream, client: x.x.x.x, server: registry.example.com, request: "GET /v2/<path> HTTP/1.1", upstream: "http://[::1]:5000/v2/<path>", host: "registry.example.com:5005"

이 오류를 해결하려면 /etc/gitlab/gitlab.rb에서 registry['registry_http_addr']를 IPv4 주소로 변경합니다. 예를 들어:

registry['registry_http_addr'] = "127.0.0.1:5000"

자세한 내용은 이슈 5449를 참고합니다.

Google Cloud Storage(GCS)에서 푸시 실패 및 높은 CPU 사용량#

GCS를 백엔드로 사용하는 레지스트리에 컨테이너 이미지를 푸시할 때 502 Bad Gateway 오류가 발생할 수 있습니다. 대용량 이미지를 푸시할 때 레지스트리에서 CPU 사용량이 급증할 수도 있습니다.

이 문제는 레지스트리가 HTTP/2 프로토콜로 GCS와 통신할 때 발생합니다.

해결 방법은 GODEBUG 환경 변수를 http2client=0으로 설정해 레지스트리 배포에서 HTTP/2를 비활성화하는 것입니다.

자세한 내용은 이슈 1425를 참고합니다.

Footnotes

  1. Prometheus 기본 버킷 값: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] (초) ↩ ↩2 ↩3

  2. Prometheus 기본 버킷 값: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] (초) ↩ ↩2

컨테이너 레지스트리 문제 해결

GitLab v19.5
원문 보기

요약

특정 문제를 조사하기 전에 다음 문제 해결 단계를 시도합니다: Docker 클라이언트와 GitLab 서버의 시스템 시계가 동기화되어 있는지 확인합니다(예: NTP 사용). S3 기반 레지스트리의 경우 IAM 권한과 S3 자격 증명(리전 포함)이 올바른지 확인합니다.

특정 문제를 조사하기 전에 다음 문제 해결 단계를 시도합니다:

  1. Docker 클라이언트와 GitLab 서버의 시스템 시계가 동기화되어 있는지 확인합니다(예: NTP 사용).

  2. S3 기반 레지스트리의 경우 IAM 권한과 S3 자격 증명(리전 포함)이 올바른지 확인합니다. 자세한 내용은 샘플 IAM 정책을 참고합니다.

  3. 레지스트리 로그(예: /var/log/gitlab/registry/current)와 GitLab 프로덕션 로그 (예: /var/log/gitlab/gitlab-rails/production.log)에서 오류를 확인합니다.

  4. 컨테이너 레지스트리의 NGINX 구성 파일(예: /var/opt/gitlab/nginx/conf/gitlab-registry.conf)을 검토해 어떤 포트가 요청을 수신하는지 확인합니다.

  5. 요청이 컨테이너 레지스트리로 올바르게 전달되는지 확인합니다:

    curl --verbose --noproxy "*" https://<hostname>:<port>/v2/_catalog
    

    응답에는 service="container_registry"를 포함하는 Www-Authenticate: Bearer 줄이 있어야 합니다. 예를 들어:

    < HTTP/1.1 401 Unauthorized
    < Server: nginx
    < Date: Fri, 07 Mar 2025 08:24:43 GMT
    < Content-Type: application/json
    < Content-Length: 162
    < Connection: keep-alive
    < Docker-Distribution-Api-Version: registry/2.0
    < Www-Authenticate: Bearer realm="https://<hostname>/jwt/auth",service="container_registry",scope="registry:catalog:*"
    < X-Content-Type-Options: nosniff
    <
    {"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":
    [{"Type":"registry","Class":"","Name":"catalog","ProjectPath":"","Action":"*"}]}]}
    * Connection #0 to host <hostname> left intact
    

오류: There are pending migrations#

보류 중인 레지스트리 데이터베이스 마이그레이션 문제를 해결할 때는 먼저 현재 마이그레이션 상태를 확인합니다. 알려진 모든 마이그레이션의 상태와 각 마이그레이션의 적용 여부를 확인하려면 다음을 실행합니다:

sudo gitlab-ctl registry-database migrate status

모든 마이그레이션이 적용되었는지 확인하려면 다음을 실행합니다:

sudo gitlab-ctl registry-database migrate status --up-to-date

이 명령은 true 또는 false만 반환합니다:

  • true: 알려진 모든 레지스트리 데이터베이스 마이그레이션이 적용되었습니다.
  • false: 하나 이상의 레지스트리 데이터베이스 마이그레이션이 아직 보류 중입니다.

--up-to-date 옵션은 업그레이드 전후로 마이그레이션 상태를 검증할 때, 또는 보류 중인 마이그레이션 때문에 레지스트리가 차단되었는지 확인할 때 유용합니다.

출력이 false이면 데이터베이스 마이그레이션 적용 단계를 따릅니다.

오류: ... x509: certificate signed by unknown authority#

컨테이너 레지스트리에 자체 서명 인증서를 사용하면 CI/CD 파이프라인 job에서 다음과 유사한 오류가 발생할 수 있습니다:

Error response from daemon: Get registry.example.com/v1/users/: x509: certificate signed by unknown authority

이 오류는 명령을 실행하는 Docker 데몬이 자체 서명 인증서가 아니라 인정된 인증 기관이 서명한 인증서를 요구하기 때문에 발생합니다.

이 오류를 해결하려면 자체 서명 인증서를 신뢰하도록 Docker를 구성합니다. Docker 구성에 관한 도움말은 자체 서명 인증서 구성을 참고합니다.

추가 정보는 이슈 18239를 참고합니다.

Docker 로그인 시도 실패: 'token signed by untrusted key'#

레지스트리는 자격 증명 검증을 GitLab에 의존합니다 레지스트리가 유효한 로그인 시도를 인증하지 못하면 다음 오류 메시지가 표시됩니다:

# docker login gitlab.company.com:4567
Username: user
Password:
Error response from daemon: login attempt to https://gitlab.company.com:4567/v2/ failed with status: 401 Unauthorized

더 구체적으로는 /var/log/gitlab/registry/current 로그 파일에 다음과 같이 나타납니다:

level=info
msg="token signed by untrusted key with ID: "TOKE:NL6Q:7PW6:EXAM:PLET:OKEN:BG27:RCIB:D2S3:EXAM:PLET:OKEN""
level=warning msg="error authorizing context: invalid token" go.version=go1.12.7 http.request.host="gitlab.company.com:4567"
http.request.id=74613829-2655-4f96-8991-1c9fe33869b8 http.request.method=GET http.request.remoteaddr=10.72.11.20
http.request.uri="/v2/" http.request.useragent="docker/19.03.2 go/go1.12.8 git-commit/6a30dfc
kernel/3.10.0-693.2.2.el7.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.2 \(linux\))"

(가독성을 위해 줄바꿈을 추가했습니다.)

GitLab은 인증서 키 쌍 양쪽의 내용을 사용해 레지스트리용 인증 토큰을 암호화합니다. 이 메시지는 그 내용이 서로 맞지 않는다는 뜻입니다.

어떤 파일이 사용되고 있는지 확인합니다:

  • grep -A6 'auth:' /var/opt/gitlab/registry/config.yml

    ## Container registry certificate
       auth:
         token:
           realm: https://gitlab.my.net/jwt/auth
           service: container_registry
           issuer: omnibus-gitlab-issuer
      -->  rootcertbundle: /var/opt/gitlab/registry/gitlab-registry.crt
           autoredirect: false
    
  • grep -A9 'Container Registry' /var/opt/gitlab/gitlab-rails/etc/gitlab.yml

    ## Container registry key
       registry:
         enabled: true
         host: gitlab.company.com
         port: 4567
         api_url: http://127.0.0.1:5000 # internal address to the registry, is used by GitLab to directly communicate with API
         path: /var/opt/gitlab/gitlab-rails/shared/registry
    -->  key: /var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key
         issuer: omnibus-gitlab-issuer
         notification_secret:
    

다음 openssl 명령의 출력은 서로 일치해야 하며, 일치하면 인증서와 키 쌍이 맞는다는 것이 증명됩니다:

/opt/gitlab/embedded/bin/openssl x509 -noout -modulus -in /var/opt/gitlab/registry/gitlab-registry.crt | /opt/gitlab/embedded/bin/openssl sha256
/opt/gitlab/embedded/bin/openssl rsa -noout -modulus -in /var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key | /opt/gitlab/embedded/bin/openssl sha256

인증서의 두 조각이 서로 맞지 않으면 파일을 제거하고 gitlab-ctl reconfigure를 실행해 쌍을 다시 생성합니다. /etc/gitlab/gitlab-secrets.json에 기존 값이 있으면 그 값으로 쌍이 다시 만들어집니다. 새 쌍을 생성하려면 gitlab-ctl reconfigure를 실행하기 전에 /etc/gitlab/gitlab-secrets.json의 registry 섹션을 삭제합니다.

자동 생성된 자체 서명 쌍을 직접 준비한 인증서로 덮어썼고 그 내용이 서로 맞는 것을 확인했다면, /etc/gitlab/gitlab-secrets.json의 'registry' 섹션을 삭제하고 gitlab-ctl reconfigure를 실행할 수 있습니다.

GitLab 레지스트리로 대용량 이미지를 푸시할 때 AWS S3 오류#

GitLab 레지스트리와 AWS S3를 함께 사용할 때 대용량 이미지를 푸시하면 오류가 발생할 수 있습니다. 레지스트리 로그에서 다음 오류를 확인합니다:

level=error msg="response completed with error" err.code=unknown err.detail="unexpected EOF" err.message="unknown error"

이 오류를 해결하려면 레지스트리 구성에서 chunksize 값을 지정합니다. 25000000(25 MB)에서 50000000(50 MB) 사이의 값으로 시작합니다.

  1. /etc/gitlab/gitlab.rb를 편집합니다:

    registry['storage'] = {
      's3' => {
        'accesskey' => 'AKIAKIAKI',
        'secretkey' => 'secret123',
        'bucket'    => 'gitlab-registry-bucket-AKIAKIAKI',
        'chunksize' => 25000000
      }
    }
    
  2. 파일을 저장하고 변경 사항을 적용하려면 GitLab을 재구성합니다.

  1. config/gitlab.yml을 편집합니다:

    storage:
      s3:
        accesskey: 'AKIAKIAKI'
        secretkey: 'secret123'
        bucket: 'gitlab-registry-bucket-AKIAKIAKI'
        chunksize: 25000000
    
  2. 파일을 저장하고 변경 사항을 적용하려면 GitLab을 재시작합니다.

GitLab 19.0으로 업그레이드한 후 이미지를 가져올 때 발생하는 403 Forbidden#

GitLab 19.0에서 s3 및 s3aws 컨테이너 레지스트리 스토리지 드라이버 이름은 AWS SDK v2를 사용하는 s3_v2 드라이버의 별칭이 되었습니다. AWS S3에서는 클라이언트가 blob을 다운로드할 때 따라가는 사전 서명 URL의 호스트 이름이 전역 s3.amazonaws.com에서 s3.us-east-1.amazonaws.com과 같은 리전 호스트 이름으로 바뀔 수 있습니다.

프록시, 방화벽, 보안 웹 게이트웨이가 s3.amazonaws.com만 허용하면 리전 호스트 이름으로의 리디렉션이 차단되어 이미지 풀이 403 Forbidden으로 실패합니다. 이 403 Forbidden 응답은 Amazon S3나 레지스트리가 아니라 필터링 장치에서 오는 것이므로, 레지스트리 로그에는 blob 요청이 정상적으로 완료된 것으로 표시됩니다.

이 원인을 확인하려면 blob을 요청하고 리디렉션 대상을 살펴봅니다:

curl --head "https://gitlab.example.com:5050/v2/mygroup/myproject/myimage/blobs/<digest>"

location 응답 헤더를 확인합니다. 그 헤더의 호스트 이름이 이그레스 제어에서 허용되지 않으면, 버킷 리전에 해당하는 리전 호스트 이름을 필터링 장치의 허용 목록에 추가합니다. Amazon S3 전체를 포괄하는 와일드카드가 아니라 구체적인 리전 호스트 이름을 사용합니다.

또는 regionendpoint를 S3 VPC 엔드포인트나 다른 고정 엔드포인트로 설정해 사전 서명 URL이 직접 제어하는 호스트 이름을 사용하도록 합니다. 자세한 내용은 오브젝트 스토리지 사용을 참고합니다.

이전 Docker 클라이언트 지원#

GitLab과 함께 제공되는 Docker 컨테이너 레지스트리는 기본적으로 schema1 매니페스트를 비활성화합니다. 여전히 이전 Docker 클라이언트(1.9 이하)를 사용하고 있다면 이미지를 푸시할 때 오류가 발생할 수 있습니다. 자세한 내용은 이슈 4145를 참고합니다.

하위 호환성을 위해 구성 옵션을 추가할 수 있습니다.

  1. /etc/gitlab/gitlab.rb를 편집합니다:

    registry['compatibility_schema1_enabled'] = true
    
  2. 파일을 저장하고 변경 사항을 적용하려면 GitLab을 재구성합니다.

  1. 레지스트리를 배포할 때 만든 YAML 구성 파일을 편집합니다. 다음 스니펫을 추가합니다:

    compatibility:
        schema1:
            enabled: true
    
  2. 변경 사항을 적용하려면 레지스트리를 재시작합니다.

Docker 연결 오류#

그룹, 프로젝트, 브랜치 이름에 특수 문자가 있으면 Docker 연결 오류가 발생할 수 있습니다. 특수 문자에는 다음이 포함됩니다:

  • 앞에 붙은 밑줄
  • 끝에 붙은 하이픈
  • 이중 하이픈

이 문제를 피하려면 그룹 경로를 변경하거나 프로젝트 경로를 변경하거나 브랜치 이름을 변경할 수 있습니다. 또 다른 방법은 푸시 규칙을 만들어 인스턴스 전체에서 이 오류를 방지하는 것입니다.

이미지 푸시 오류#

docker login이 성공해도 Docker 이미지를 푸시할 때 재시도 루프에 갇힐 수 있습니다.

이 문제는 NGINX가 헤더를 레지스트리로 제대로 전달하지 않을 때 발생하며, 주로 SSL을 서드파티 리버스 프록시로 오프로드하는 사용자 지정 설정에서 나타납니다.

자세한 내용은 Docker push through NGINX proxy fails trying to send a 32B layer #970을 참고합니다.

이 문제를 해결하려면 레지스트리에서 상대 URL을 사용하도록 NGINX 구성을 업데이트합니다:

  1. /etc/gitlab/gitlab.rb를 편집합니다:

    registry['env'] = {
      "REGISTRY_HTTP_RELATIVEURLS" => true
    }
    
  2. 파일을 저장하고 변경 사항을 적용하려면 GitLab을 재구성합니다.

  1. 레지스트리를 배포할 때 만든 YAML 구성 파일을 편집합니다. 다음 스니펫을 추가합니다:

    http:
        relativeurls: true
    
  2. 파일을 저장하고 변경 사항을 적용하려면 GitLab을 재시작합니다.

  1. docker-compose.yaml 파일을 편집합니다:

    GITLAB_OMNIBUS_CONFIG: |
      registry['env'] = {
        "REGISTRY_HTTP_RELATIVEURLS" => true
      }
    
  2. 문제가 계속되면 두 URL 모두 HTTPS를 사용하는지 확인합니다:

    GITLAB_OMNIBUS_CONFIG: |
      external_url 'https://git.example.com'
      registry_external_url 'https://git.example.com:5050'
    
  3. 파일을 저장하고 컨테이너를 재시작합니다:

    sudo docker restart gitlab
    

레지스트리 디버그 서버 활성화#

컨테이너 레지스트리 디버그 서버로 문제를 진단할 수 있습니다. 디버그 엔드포인트는 메트릭과 상태를 모니터링할 수 있고 프로파일링도 수행할 수 있습니다.

Warning

디버그 엔드포인트에서 민감한 정보가 노출될 수 있습니다. 프로덕션 환경에서는 디버그 엔드포인트에 대한 접근을 반드시 차단해야 합니다.

gitlab.rb 구성에서 레지스트리 디버그 주소를 설정하면 선택 사항인 디버그 서버를 활성화할 수 있습니다.

registry['debug_addr'] = "localhost:5001"

설정을 추가한 후 변경 사항을 적용하려면 GitLab을 재구성합니다.

curl로 디버그 서버에 디버그 출력을 요청합니다:

curl "localhost:5001/debug/health"
curl "localhost:5001/debug/vars"

Prometheus 메트릭#

Prometheus는 컨테이너 레지스트리의 성능 문제를 모니터링하고 해결하는 데 사용할 수 있는 메트릭을 제공합니다.

다음 섹션에서 다루는 내용입니다:

  • Prometheus 메트릭을 활성화하는 방법
  • 컨테이너 레지스트리가 내보내는 모든 Prometheus 메트릭을 구성 요소별로 정리한 목록

Prometheus 메트릭 활성화#

사전 요구 사항:

Prometheus 메트릭을 활성화하려면 gitlab.rb에 다음 구성을 추가합니다:

# Enable Prometheus metrics
registry['debug'] = {
  'prometheus' => {
    'enabled' => true,
    'path' => '/metrics'
  }
}

변경 사항을 적용하려면 GitLab을 재구성합니다.

curl로 디버그 서버에 메트릭을 요청합니다:

curl "localhost:5001/metrics"

알림 메트릭#

카운터#

메트릭 이름 설명 레이블 레이블 값
registry_notifications_events_total 총 이벤트 수. type, action, artifact, endpoint type: Successes, Failures, Events, Dropped
registry_notifications_status_total 알림 엔드포인트에서 수신한 HTTP 상태 코드별 응답 수. code, endpoint code: HTTP 상태 코드(예: 200 OK 또는 404 Not Found)
registry_notifications_errors_total 전송 중 오류가 발생한 이벤트 수. 전송 요청은 재시도될 수 있습니다. endpoint string: '...'
registry_notifications_delivery_total 전달되었거나 손실된 이벤트 수. 재시도 횟수가 소진되면 이벤트는 손실됩니다. endpoint, delivery_type delivery_type: delivered, lost

게이지#

메트릭 이름 설명 레이블 레이블 값
registry_notifications_pending 큐 길이로 표시되는 큐의 보류 중인 이벤트 게이지. endpoint string: '...'

히스토그램#

메트릭 이름 설명 레이블 버킷
registry_notifications_retries_count 누적 전달 재시도 횟수 히스토그램. endpoint [0, 1, 2, 3, 5, 10, 15, 20, 30, 50]
registry_notifications_http_latency_seconds HTTP 전달 지연 시간 히스토그램. endpoint [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 25, 50, 100] (초)
registry_notifications_total_latency_seconds 총 전달 지연 시간 히스토그램. endpoint [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 25, 50, 100] (초)

일괄 백그라운드 마이그레이션(BBM) 메트릭#

카운터#
메트릭 이름 설명 레이블 레이블 값
registry_bbm_runs_total 일괄 마이그레이션 워커 실행 카운터. 없음 없음
registry_bbm_migrated_tuples_total 마이그레이션된 총 일괄 마이그레이션 레코드 카운터. migration_name, migration_id string: '...'
게이지#
메트릭 이름 설명 레이블 레이블 값
registry_bbm_job_batch_size 일괄 마이그레이션 job의 배치 크기 게이지. migration_name, migration_id string: '...'
registry_database_bbm_progress_percent 백그라운드 마이그레이션 진행 비율(0-100). migration_id, migration_name, status string: '...'
히스토그램#
메트릭 이름 설명 레이블 버킷
registry_bbm_run_duration_seconds 일괄 마이그레이션 워커 실행 지연 시간 히스토그램. 없음 [0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600] (0.5초~1시간)
registry_bbm_job_duration_seconds 일괄 마이그레이션 job의 지연 시간 히스토그램. migration_name, migration_id [0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600] (0.5초~1시간)
registry_bbm_query_duration_seconds 일괄 마이그레이션 데이터베이스 쿼리 지연 시간 히스토그램. migration_name, migration_id [0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600] (0.5초~1시간)
registry_bbm_sleep_duration_seconds BBM 워커 실행 사이의 슬립 시간 히스토그램. worker [0.5, 1, 5, 15, 30, 60, 300, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200, 86400] (500ms~24시간)

데이터베이스 메트릭#

카운터#
메트릭 이름 설명 레이블 레이블 값
registry_database_queries_total 데이터베이스 쿼리 카운터. name string: '...'
registry_database_lb_lsn_cache_hits_total 데이터베이스 로드 밸런싱 LSN 캐시 히트 및 미스 카운터. result result: hit, miss
registry_database_lb_pool_events_total 데이터베이스 로드 밸런서 풀에 추가되거나 풀에서 제거된 복제본 카운터. event, reason event: replica_added, replica_removed, replica_quarantined, replica_reintegrated
reason: replication_lag, connectivity, removed_from_dns, discovered
registry_database_lb_targets_total 데이터베이스 로드 밸런싱 중 기본 및 복제본 타깃 선출 카운터. target_type, fallback, reason target_type: primary, replica
fallback: true, false
reason: selected, no_cache, no_replica, error, not_up_to_date, all_quarantined
게이지#
메트릭 이름 설명 레이블 레이블 값
registry_database_lb_pool_size 로드 밸런서 풀에 있는 현재 복제본 수 게이지. 없음 없음
registry_database_lb_pool_status 로드 밸런서 풀에 있는 각 복제본의 현재 상태 게이지. replica, status status: online, quarantined
registry_database_lb_lag_bytes 각 복제본의 복제 지연을 바이트로 나타내는 게이지. replica string: '...'
registry_database_migrations_total 데이터베이스 마이그레이션 총 수(적용 + 보류) 게이지 migration_type migration_type: pre_deployment, post_deployment
registry_database_rows query_name 레이블로 정의된 데이터베이스 테이블의 행 수 게이지. query_name query_name: gc_blob_review_queue, gc_manifest_review_queue, gc_blob_review_queue_overdue, gc_manifest_review_queue_overdue, applied_pre_migrations, applied_post_migrations
히스토그램#
메트릭 이름 설명 레이블 버킷
registry_database_query_duration_seconds 데이터베이스 쿼리 지연 시간 히스토그램. name Prometheus 기본 버킷.1
registry_database_lb_lsn_cache_operation_duration_seconds 데이터베이스 로드 밸런싱 LSN 캐시 작업 지연 시간 히스토그램. operation, error operation: set, get
error: true, false
Prometheus 기본 버킷.1
registry_database_lb_lookup_seconds 데이터베이스 로드 밸런싱 DNS 조회 지연 시간 히스토그램. lookup_type, error lookup_type: srv, host
error: true, false
Prometheus 기본 버킷.1
registry_database_lb_lag_seconds 각 복제본의 복제 지연을 초 단위로 나타낸 히스토그램. replica [0.001, 0.01, 0.1, 0.5, 1, 5, 10, 20, 30, 60] (1ms~60초)
registry_database_row_count_collection_duration_seconds 한 번의 실행에서 모든 데이터베이스 행 수 쿼리를 수집하는 데 걸린 총 시간 히스토그램. 없음 [0.1, 0.5, 1, 2, 5, 10, 30, 60] (100ms~60초)

가비지 수집(GC) 메트릭#

카운터#
메트릭 이름 설명 레이블 레이블 값
registry_gc_runs_total 온라인 GC 워커 실행 카운터. worker, noop, error, dangling, event noop: true, false
error: true, false
dangling: true, false
registry_gc_deletes_total 온라인 GC 중 삭제된 아티팩트 카운터. backend, artifact backend: storage, database
artifact: blob, manifest
registry_gc_storage_deleted_bytes_total 온라인 GC 중 스토리지에서 삭제된 바이트 카운터. media_type string: '...'
registry_gc_postpones_total 온라인 GC 검토 연기 카운터. worker string: '...'
히스토그램#
메트릭 이름 설명 레이블 버킷
registry_gc_run_duration_seconds 온라인 GC 워커 실행 지연 시간 히스토그램. worker, noop, error, dangling, event noop: true, false
error: true, false
dangling: true, false
Prometheus 기본 버킷.2
registry_gc_delete_duration_seconds 온라인 GC 중 아티팩트 삭제 지연 시간 히스토그램. backend, artifact, error backend: storage, database
artifact: blob, manifest
error: true, false
Prometheus 기본 버킷.2
registry_gc_sleep_duration_seconds 온라인 GC 워커 실행 사이의 슬립 시간 히스토그램. worker [0.5, 1, 5, 15, 30, 60, 300, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200, 86400] (500ms~24시간)

스토리지 메트릭#

카운터#
메트릭 이름 설명 레이블 레이블 값
registry_storage_cdn_redirects_total blob 다운로드에 대한 CDN 리디렉션 카운터. backend, bypass, bypass_reason bypass: true, false
registry_storage_rate_limit_total 속도 제한에 도달한 스토리지 드라이버 요청 카운터. 없음 없음
registry_storage_storage_backend_retries_total 스토리지 백엔드와 통신하는 중 발생한 재시도 카운터. retry_type retry_type: native, custom
registry_storage_urlcache_requests_total URL 캐시 미들웨어 요청 카운터. result, reason result: hit, miss
registry_storage_access_tracker_dropped_events 시간 초과로 액세스 추적기에서 삭제된 이벤트 카운터. 없음 없음
게이지#
메트릭 이름 설명 레이블 레이블 값
registry_storage_object_accesses_topn 가장 자주 액세스되는 상위 N개 객체의 총 액세스 수. top_n top_n: 1, 10, 100, 1000, 10000, all
히스토그램#
메트릭 이름 설명 레이블 버킷
registry_storage_blob_download_bytes 스토리지 백엔드의 blob 다운로드 크기 히스토그램. redirect redirect: true, false
[524288, 1048576, 67108864, 134217728, 268435456, 536870912, 1073741824, 2147483648, 3221225472, 4294967296, 5368709120, 6442450944, 7516192768, 8589934592, 9663676416, 10737418240, 21474836480, 32212254720, 42949672960, 53687091200] (512KiB~50GiB)
registry_storage_blob_upload_bytes 스토리지 백엔드의 새 blob 업로드 바이트 히스토그램. 없음 [524288, 1048576, 67108864, 134217728, 268435456, 536870912, 1073741824, 2147483648, 3221225472, 4294967296, 5368709120, 6442450944, 7516192768, 8589934592, 9663676416, 10737418240, 21474836480, 32212254720, 42949672960, 53687091200] (512KiB~50GiB)
registry_storage_urlcache_object_size URL 캐시의 객체 크기 히스토그램. 없음 [100, 250, 500, 750, 1000, 1500, 2048, 3072, 5120, 10240] (100바이트~10KiB)
registry_storage_object_accesses_distribution 모든 객체에 걸친 액세스 수 분포. 없음 지수 버킷: [10, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 10240]

레지스트리 디버그 로그 활성화#

디버그 로그를 활성화하면 컨테이너 레지스트리 문제를 해결하는 데 도움이 됩니다.

Warning

디버그 로그에는 인증 정보, 토큰, 리포지터리 정보와 같은 민감한 정보가 포함될 수 있습니다. 필요한 경우에만 디버그 로그를 활성화하고, 문제 해결이 끝나면 비활성화합니다.

  1. /var/opt/gitlab/registry/config.yml을 편집합니다:

    level: debug
    
  2. 파일을 저장하고 레지스트리를 재시작합니다:

    sudo gitlab-ctl restart registry
    

이 구성은 임시이며 gitlab-ctl reconfigure를 실행하면 사라집니다.

  1. Helm 값을 내보냅니다:

    helm get values gitlab > gitlab_values.yaml
    
  2. gitlab_values.yaml을 편집합니다:

    registry:
      log:
        level: debug
    
  3. 파일을 저장하고 새 값을 적용합니다:

    helm upgrade -f gitlab_values.yaml gitlab gitlab/gitlab --namespace <namespace>
    

레지스트리 Prometheus 메트릭 활성화#

디버그 서버가 활성화되어 있으면 Prometheus 메트릭도 활성화할 수 있습니다. 이 엔드포인트는 거의 모든 레지스트리 작업에 관한 매우 상세한 텔레메트리를 노출합니다.

registry['debug'] = {
  'prometheus' => {
    'enabled' => true,
    'path' => '/metrics'
  }
}

curl로 Prometheus에 디버그 출력을 요청합니다:

curl "localhost:5001/debug/metrics"

이름이 빈 태그#

AWS DataSync를 사용해 레지스트리 데이터를 S3 버킷으로 또는 S3 버킷 사이에 복사하면, 대상 버킷의 각 컨테이너 리포지터리 루트 경로에 빈 메타데이터 객체가 생성됩니다. 이로 인해 레지스트리는 이러한 파일을 GitLab UI와 API에서 이름 없이 표시되는 태그로 해석합니다. 자세한 내용은 이 이슈를 참고합니다.

이를 수정하려면 다음 두 가지 중 하나를 수행할 수 있습니다:

  • AWS CLI rm 명령으로 영향받은 각 리포지터리의 루트에서 빈 객체를 제거합니다. 끝에 오는 /에 특히 주의하고 --recursive 옵션은 사용하지 않습니다:

    aws s3 rm s3://<bucket>/docker/registry/v2/repositories/<path to repository>/
    
  • AWS CLI sync 명령으로 레지스트리 데이터를 새 버킷에 복사하고 그 버킷을 사용하도록 레지스트리를 구성합니다. 이 방법은 빈 객체를 그대로 남겨 둡니다.

고급 문제 해결#

S3 설정과 관련된 문제를 진단하는 방법을 구체적인 예로 설명합니다.

정리 정책 조사#

정리 정책이 태그를 삭제했거나 삭제하지 않은 이유가 확실하지 않으면, Rails 콘솔에서 아래 스크립트를 실행해 정책을 한 줄씩 실행합니다. 이 방법은 정책의 문제를 진단하는 데 도움이 됩니다.

repo = ContainerRepository.find(<repository_id>)
policy = repo.project.container_expiration_policy

tags = repo.tags
tags.map(&:name)

tags.reject!(&:latest?)
tags.map(&:name)

regex_delete = ::Gitlab::UntrustedRegexp.new("\\A#{policy.name_regex}\\z")
regex_retain = ::Gitlab::UntrustedRegexp.new("\\A#{policy.name_regex_keep}\\z")

tags.select! { |tag| regex_delete.match?(tag.name) && !regex_retain.match?(tag.name) }

tags.map(&:name)

now = DateTime.current
tags.sort_by! { |tag| tag.created_at || now }.reverse! # Lengthy operation

tags = tags.drop(policy.keep_n)
tags.map(&:name)

older_than_timestamp = ChronicDuration.parse(policy.older_than).seconds.ago

tags.select! { |tag| tag.created_at && tag.created_at < older_than_timestamp }

tags.map(&:name)
  • 스크립트는 삭제할 태그 목록(tags)을 만듭니다.
  • tags.map(&:name)은 제거할 태그 목록을 출력합니다. 시간이 오래 걸리는 작업일 수 있습니다.
  • 각 필터를 적용한 후 tags 목록에 삭제하려는 태그가 들어 있는지 확인합니다.

푸시 중 예기치 않은 403 오류#

어떤 사용자가 S3 기반 레지스트리를 활성화하려고 했습니다. docker login 단계는 정상적으로 진행되었습니다. 그러나 이미지를 푸시할 때 출력은 다음과 같았습니다:

The push refers to a repository [s3-testing.myregistry.com:5050/root/docker-test/docker-image]
dc5e59c14160: Pushing [==================================================>] 14.85 kB
03c20c1a019a: Pushing [==================================================>] 2.048 kB
a08f14ef632e: Pushing [==================================================>] 2.048 kB
228950524c88: Pushing 2.048 kB
6a8ecde4cc03: Pushing [==>                                                ] 9.901 MB/205.7 MB
5f70bf18a086: Pushing 1.024 kB
737f40e80b7f: Waiting
82b57dbc5385: Waiting
19429b698a22: Waiting
9436069b92a3: Waiting
error parsing HTTP 403 response body: unexpected end of JSON input: ""

이 오류는 403이 GitLab Rails 애플리케이션에서 온 것인지, Docker 레지스트리에서 온 것인지, 아니면 다른 곳에서 온 것인지 명확하지 않아 모호합니다. 이 사례에서는 로그인이 성공했다는 것을 알고 있으므로, 클라이언트와 레지스트리 사이의 통신을 살펴봐야 할 가능성이 큽니다.

Docker 클라이언트와 레지스트리 사이의 REST API는 Docker 문서에 설명되어 있습니다. 보통은 Wireshark나 tcpdump로 트래픽을 캡처해 무엇이 잘못되었는지 확인합니다. 그러나 Docker 클라이언트와 서버 사이의 모든 통신이 HTTPS로 이루어지므로, 개인 키를 알고 있어도 트래픽을 빠르게 복호화하기는 다소 어렵습니다. 그래서 대신 다음 방법을 사용합니다.

한 가지 방법은 비보안 레지스트리를 설정해 HTTPS를 비활성화하는 것입니다. 이 방법은 보안 구멍을 만들 수 있어 로컬 테스트에만 권장됩니다. 프로덕션 시스템이 있어 이 방법을 쓸 수 없거나 쓰고 싶지 않다면 다른 방법이 있습니다. 중간자 프록시(Man-in-the-Middle Proxy)를 뜻하는 mitmproxy를 사용합니다.

mitmproxy#

mitmproxy를 사용하면 클라이언트와 서버 사이에 프록시를 두어 모든 트래픽을 검사할 수 있습니다. 한 가지 번거로운 점은 이 방법이 동작하려면 시스템이 mitmproxy SSL 인증서를 신뢰해야 한다는 것입니다.

다음 설치 지침은 Ubuntu를 사용한다고 가정합니다:

  1. mitmproxy를 설치합니다.

  2. mitmproxy --port 9000을 실행해 인증서를 생성합니다. 종료하려면 Control-C를 입력합니다.

  3. ~/.mitmproxy의 인증서를 시스템에 설치합니다:

    sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy-ca-cert.crt
    sudo update-ca-certificates
    

성공하면 인증서가 추가되었다는 내용이 출력됩니다:

Updating certificates in /etc/ssl/certs... 1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d....done.

인증서가 제대로 설치되었는지 확인하려면 다음을 실행합니다:

mitmproxy --listen-port 9000

이 명령은 포트 9000에서 mitmproxy를 실행합니다. 다른 창에서 다음을 실행합니다:

curl --proxy "http://localhost:9000" "https://httpbin.org/status/200"

모두 올바르게 설정되었다면 mitmproxy 창에 정보가 표시되고 curl 명령에서 오류가 발생하지 않습니다.

프록시를 통해 Docker 데몬 실행#

Docker가 프록시를 통해 연결하려면 적절한 환경 변수와 함께 Docker 데몬을 시작해야 합니다. 가장 쉬운 방법은 Docker를 종료하고(예: sudo initctl stop docker) Docker를 직접 실행하는 것입니다. root 권한으로 다음을 실행합니다:

export HTTP_PROXY="http://localhost:9000"
export HTTPS_PROXY="http://localhost:9000"
docker daemon --debug # or dockerd --debug

이 명령은 Docker 데몬을 시작하고 모든 연결을 mitmproxy를 통해 프록시합니다.

Docker 클라이언트 실행#

이제 mitmproxy와 Docker가 실행 중이므로 로그인하고 컨테이너 이미지를 푸시해 볼 수 있습니다. 이 작업에는 root 권한이 필요할 수 있습니다. 예를 들어:

docker login example.s3.amazonaws.com:5050
docker push example.s3.amazonaws.com:5050/root/docker-test/docker-image

앞의 예시에서 mitmproxy 창에 다음 추적이 표시됩니다:

PUT https://example.s3.amazonaws.com:4567/v2/root/docker-test/blobs/uploads/(UUID)/(QUERYSTRING)
    ← 201 text/plain [no content] 661ms
HEAD https://example.s3.amazonaws.com:4567/v2/root/docker-test/blobs/sha256:(SHA)
    ← 307 application/octet-stream [no content] 93ms
HEAD https://example.s3.amazonaws.com:4567/v2/root/docker-test/blobs/sha256:(SHA)
    ← 307 application/octet-stream [no content] 101ms
HEAD https://example.s3.amazonaws.com:4567/v2/root/docker-test/blobs/sha256:(SHA)
    ← 307 application/octet-stream [no content] 87ms
HEAD https://amazonaws.example.com/docker/registry/vs/blobs/sha256/dd/(UUID)/data(QUERYSTRING)
    ← 403 application/xml [no content] 80ms
HEAD https://amazonaws.example.com/docker/registry/vs/blobs/sha256/dd/(UUID)/data(QUERYSTRING)
    ← 403 application/xml [no content] 62ms

이 출력이 보여 주는 내용입니다:

  • 초기 PUT 요청은 201 상태 코드로 정상 처리되었습니다.
  • 201은 클라이언트를 Amazon S3 버킷으로 리디렉션했습니다.
  • AWS 버킷에 대한 HEAD 요청은 403 Unauthorized를 보고했습니다.

이것이 뜻하는 바는 다음과 같습니다. 이는 S3 사용자에게 HEAD 요청을 수행할 권한이 없다는 것을 강하게 시사합니다. 해결 방법은 IAM 권한을 다시 확인하는 것입니다. 올바른 권한을 설정한 후 오류가 사라졌습니다.

gitlab-registry.key 누락으로 컨테이너 리포지터리 삭제 불가#

GitLab 인스턴스의 컨테이너 레지스트리를 비활성화한 상태에서 컨테이너 리포지터리가 있는 프로젝트를 제거하려고 하면 다음 오류가 발생합니다:

Errno::ENOENT: No such file or directory @ rb_sysopen - /var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key

이 경우 다음 단계를 따릅니다:

  1. gitlab.rb에서 컨테이너 레지스트리의 인스턴스 전체 설정을 일시적으로 활성화합니다:

    gitlab_rails['registry_enabled'] = true
    
  2. 파일을 저장하고 GitLab을 재구성해 변경 사항을 적용합니다.

  3. 제거를 다시 시도합니다.

일반적인 방법으로 리포지터리를 제거할 수 없으면 GitLab Rails 콘솔을 사용해 프로젝트를 강제로 제거할 수 있습니다:

# Path to the project you'd like to remove
prj = Project.find_by_full_path(<project_path>)

# The following will delete the project's container registry, so be sure to double-check the path beforehand!
if prj.has_container_registry_tags?
  prj.container_repositories.each { |p| p.destroy }
end

레지스트리 서비스가 IPv4 대신 IPv6 주소에서 수신#

GitLab 서버에서 localhost 호스트 이름이 IPv6 루프백 주소(::1)로 해석되고 GitLab은 레지스트리 서비스가 IPv4 루프백 주소(127.0.0.1)에서 사용 가능하다고 예상하는 경우, 다음 오류가 표시될 수 있습니다:

request: "GET /v2/ HTTP/1.1", upstream: "http://[::1]:5000/v2/", host: "registry.example.com:5005"
[error] 1201#0: *13442797 connect() failed (111: Connection refused) while connecting to upstream, client: x.x.x.x, server: registry.example.com, request: "GET /v2/<path> HTTP/1.1", upstream: "http://[::1]:5000/v2/<path>", host: "registry.example.com:5005"

이 오류를 해결하려면 /etc/gitlab/gitlab.rb에서 registry['registry_http_addr']를 IPv4 주소로 변경합니다. 예를 들어:

registry['registry_http_addr'] = "127.0.0.1:5000"

자세한 내용은 이슈 5449를 참고합니다.

Google Cloud Storage(GCS)에서 푸시 실패 및 높은 CPU 사용량#

GCS를 백엔드로 사용하는 레지스트리에 컨테이너 이미지를 푸시할 때 502 Bad Gateway 오류가 발생할 수 있습니다. 대용량 이미지를 푸시할 때 레지스트리에서 CPU 사용량이 급증할 수도 있습니다.

이 문제는 레지스트리가 HTTP/2 프로토콜로 GCS와 통신할 때 발생합니다.

해결 방법은 GODEBUG 환경 변수를 http2client=0으로 설정해 레지스트리 배포에서 HTTP/2를 비활성화하는 것입니다.

자세한 내용은 이슈 1425를 참고합니다.

Footnotes

  1. Prometheus 기본 버킷 값: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] (초) ↩ ↩2 ↩3

  2. Prometheus 기본 버킷 값: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] (초) ↩ ↩2