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

다음 섹션으로 들어가기 전에 기본적인 문제 해결 방법은 다음과 같습니다.

  1. Docker 클라이언트 및 GitLab 서버의 시스템 시계가 동기화되었는지 확인하십시오(NTP 등을 통해).

  2. S3를 백업으로 사용하는 레지스트리를 사용하는 경우 IAM 권한 및 S3 자격 증명(영역 포함)이 올바른지 다시 한 번 확인하십시오. 자세한 내용은 샘플 IAM 정책을 참조하십시오.

  3. 레지스트리 로그(예: /var/log/gitlab/registry/current) 및 GitLab 프로덕션 로그(예: /var/log/gitlab/gitlab-rails/production.log)를 확인하여 오류를 찾아볼 수 있습니다.

컨테이너 레지스트리에서 셀프 사인 인증서 사용

컨테이너 레지스트리에서 셀프 사인 인증서를 사용하는 경우 CI 작업 중 다음과 같은 문제가 발생할 수 있습니다.

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

명령을 실행 중인 Docker 데몬이 인식된 CA에 의해 서명된 인증서를 기대하기 때문에 위의 오류가 발생합니다.

GitLab은 컨테이너 레지스트리에서 셀프 사인 인증서 사용을 기본적으로 지원하지는 않지만, Docker 데몬에 자체 서명된 인증서를 신뢰하도록 하는 방법을 찾아 Docker 데몬에 셀프 사인 인증서 신뢰 설정하고, GitLab Runner config.toml 파일에서 privileged = false를 설정하여 작동시킬 수 있습니다. privileged = true로 설정하면 Docker 데몬이 우선합니다.

  [runners.docker]
    image = "ruby:2.6"
    privileged = false
    volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]

이와 관련된 추가 정보: 이슈 18239.

도커 로그인 시도가 ‘신뢰할 수 없는 키로 서명된 토큰’과 함께 실패하는 경우

레지스트리는 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

    ## 컨테이너 레지스트리 인증서
       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 '컨테이너 레지스트리' /var/opt/gitlab/gitlab-rails/etc/gitlab.yml

    ## 컨테이너 레지스트리 키
       registry:
         enabled: true
         host: gitlab.company.com
         port: 4567
         api_url: http://127.0.0.1:5000 # 레지스트리에 직접 통신하기 위해 GitLab에서 사용하는 내부 주소
         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’ (25MB)에서 ‘50000000’ (50MB) 사이의 값을 시작으로 지정하십시오.

Linux 패키지 (Omnibus)
  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을 다시 시작하십시오.

이전 Docker 클라이언트 지원

GitLab에 포함된 Docker 컨테이너 레지스트리는 기본적으로 schema1 매니페스트를 비활성화합니다. 이전 Docker 클라이언트(1.9 또는 그 이하 버전)를 계속 사용 중인 경우 이미지를 푸시하는 중에 오류가 발생할 수 있습니다. 자세한 내용은 이슈 4145를 참조하세요.

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

Linux 패키지(Omnibus)
  1. /etc/gitlab/gitlab.rb 파일을 편집합니다.

    registry['compatibility_schema1_enabled'] = true
    
  2. 파일을 저장하고 변경 사항이 적용되려면 GitLab을 다시 구성하십시오.

직접 컴파일한 경우(소스)
  1. 레지스트리를 배포할 때 생성한 YAML 구성 파일을 편집합니다. 다음 스니펫을 추가합니다.

    compatibility:
        schema1:
            enabled: true
    
  2. 변경 사항이 적용되려면 레지스트리를 다시 시작하십시오.

Docker 연결 오류

그룹, 프로젝트 또는 브랜치 이름에 특수 문자가 포함되어 있는 경우 Docker 연결 오류가 발생할 수 있습니다. 특수 문자로는 다음이 포함될 수 있습니다.

  • 선행 하이픈
  • 후행 언더스코어/하이픈
  • 두 개의 하이픈

이를 해결하기 위해 그룹 경로를 변경, 프로젝트 경로를 변경하거나, 브랜치 이름을 변경할 수 있습니다. 또 다른 옵션으로는 전체 인스턴스에서 이 오류를 방지하기 위해 푸시 규칙을 생성할 수 있습니다.

이미지 푸시 오류

이미지를 푸시하는 중에 오류 또는 “다시 시도 중” 루프가 발생하지만 docker login은 정상적으로 작동하는 경우, NGINX에 의해 레지스트리로 전달된 헤더에 문제가 있을 가능성이 있습니다. 기본 권장 NGINX 구성은 이를 처리해야 하지만 SSL을 타사 역방향 프록시로 오프로드하는 사용자 정의 설정에서 발생할 수 있습니다.

이 문제는 Docker 프로젝트 이슈에서 논의되었으며, 레지스트리에서 상대적인 URL을 사용하도록하는 간단한 해결책이 있습니다.

Linux 패키지(Omnibus)
  1. /etc/gitlab/gitlab.rb 파일을 편집합니다.

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

직접 컴파일한 경우(소스)
  1. 레지스트리를 배포할 때 생성한 YAML 구성 파일을 편집합니다. 다음 스니펫을 추가합니다.

    http:
        relativeurls: true
    
  2. 파일을 저장하고 변경 사항이 적용되려면 GitLab을 다시 시작하십시오.

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

문제를 진단하기 위해 컨테이너 레지스트리 디버그 서버를 사용할 수 있습니다. 디버그 엔드포인트는 메트릭 및 상태를 모니터링하거나 프로파일링할 수 있습니다.

경고: 생산 환경에는 민감한 정보가 디버그 엔드포인트에서 사용 가능할 수 있습니다. 디버그 엔드포인트의 액세스 권한이 제한되어야 합니다.

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

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

설정을 추가한 후 GitLab을 다시 구성하여 변경 사항을 적용하십시오.

디버그 서버에서 디버그 출력을 요청하려면 curl을 사용하십시오.

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

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

디버그 서버가 활성화된 경우 Prometheus 메트릭도 활성화할 수 있습니다. 이 엔드포인트는 거의 모든 레지스트리 작업과 관련된 매우 자세한 통계 정보를 노출합니다.

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

이제 curl을 사용하여 Prometheus에서 디버그 출력을 요청할 수 있습니다.

curl "localhost:5001/debug/metrics"

이름이 없는 태그

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)은 제거할 태그 목록을 출력합니다. 이는 길이가 길어질 수 있습니다.
  • 각 필터링 후에 의도한 태그를 삭제했는지 확인하기 위해 목록을 확인하십시오.
PUSH 중 예기치 않은 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 레지스트리 또는 다른 곳에서 발생하는지 명확하지 않습니다. 
이 경우에는 로그인이 성공했으므로 클라이언트와 레지스트리 간의 통신을 살펴봐야 합니다.

도커 클라이언트와 레지스트리 간의 REST API는 [Docker 문서](https://distribution.github.io/distribution/spec/api/)에 설명되어 있습니다. 일반적으로 트래픽을 캡처하여 문제가 발생한 위치를 확인하는 데 Wireshark나 tcpdump를 사용할 수 있습니다. 
그러나 도커 클라이언트와 서버 간의 모든 통신이 HTTPS로 이루어지기 때문에 비록 개인 키를 알고 있더라도 트래픽을 빠르게 해독하는 것은 약간 어렵습니다. 대신 어떻게 할 수 있을까요?

하나의 방법은 [안전하지 않은 레지스트리를 설정](https://distribution.github.io/distribution/about/insecure/)하여 HTTPS를 비활성화하는 것입니다. 
이는 보안 문제를 야기할 수 있고 로컬 테스트에만 권장되는 방법입니다. 
만약 이를 원하지 않거나 할 수 없는 상용 시스템이 있다면, 대안이 있습니다: Man-in-the-Middle Proxy인 mitmproxy 사용하기.

### mitmproxy

[mitmproxy](https://mitmproxy.org/)를 사용하면 클라이언트와 서버 사이에 프록시를 놓고 전체 트래픽을 검사할 수 있습니다. 
이때 한 가지 주의할 점은 시스템이 mitmproxy SSL 인증서를 신뢰해야만 작동한다는 것입니다.

다음 설치 방법은 Ubuntu를 사용하는 것을 가정합니다:

1. [mitmproxy 설치](https://docs.mitmproxy.org/stable/overview-installation/).
1. `mitmproxy --port 9000`를 실행하여 인증서를 생성합니다.
   종료하려면 <kbd>CTRL</kbd>-<kbd>C</kbd>를 누릅니다.
1. `~/.mitmproxy`의 인증서를 시스템에 설치합니다:

   ```shell
   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 --port 9000

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

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

모든 것이 올바르게 설정되었을 경우 mitmproxy 창에 정보가 표시되고 curl 명령으로 오류가 발생하지 않습니다.

프록시를 사용하여 Docker 데몬 실행

도커가 프록시를 통해 연결하려면 올바른 환경 변수를 설정하여 도커 데몬을 시작해야 합니다. 가장 쉬운 방법은 Docker를 중지(예: sudo initctl stop docker)하고 그 후 수동으로 Docker를 실행하는 것입니다. root로 다음을 실행합니다:

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

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

Docker 클라이언트 실행

이제 mitmproxy와 Docker를 실행했으므로 컨테이너 이미지에 로그인하고 푸시를 시도할 수 있습니다. 이를 수행하려면 root로 실행해야 할 수도 있습니다. 예를 들어 다음과 같이 실행합니다:

docker login s3-testing.myregistry.com:5050
docker push s3-testing.myregistry.com:5050/root/docker-test/docker-image

위의 예에서 mitmproxy 창에 다음과 같은 추적이 나타납니다:

mitmproxy output from Docker

위 이미지에서는 다음과 같은 내용이 표시됩니다.

  • 초기 PUT 요청은 201 상태 코드로 정상적으로 진행되었습니다.
  • 201은 클라이언트를 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 콘솔을 사용하여 프로젝트를 강제로 삭제할 수 있습니다:

# 삭제하려는 프로젝트의 경로
prj = Project.find_by_full_path(<project_path>)

# 위의 경우 경로를 반드시 재확인한 후 실행하세요!
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`)에서 사용 가능하다고 예상하면 다음과 같은 오류가 발생할 수 있습니다:

```plaintext
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를 참조하십시오.