- 자체 서명된 인증서를 사용한 컨테이너 레지스트리
- Docker 로그인 시도 실패: ‘token signed by untrusted key’
- AWS S3와 GitLab 레지스트리의 대형 이미지 푸시 오류
- 구형 Docker 클라이언트 지원
- Docker 연결 오류
- 이미지 푸시 오류
- 레지스트리 디버그 서버 활성화
- 빈 이름의 태그
- 고급 문제 해결
- 누락된
gitlab-registry.key
로 인해 컨테이너 리포지토리 삭제 방지 - 레지스트리 서비스가 IPv4 대신 IPv6 주소에서 수신 대기
컨테이너 레지스트리 문제 해결
다음 섹션에 들어가기 전에, 기본적인 문제 해결 정보를 제공합니다:
-
Docker 클라이언트와 GitLab 서버의 시스템 시계가 동기화되었는지 확인하세요 (예: NTP를 통해).
-
S3 기반 레지스트리를 사용하는 경우, IAM 권한 및 S3 자격 증명(영역 포함)이 정확한지 다시 확인하세요. 자세한 내용은 샘플 IAM 정책을 참조하세요.
-
레지스트리 로그(예:
/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를 참조하세요.
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
## 컨테이너 레지스트리 인증서 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
## 컨테이너 레지스트리 키 registry: enabled: true host: gitlab.company.com port: 4567 api_url: http://127.0.0.1:5000 # 내부 레지스트리에 대한 주소, GitLab이 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
에 기존 값이 있는 경우 해당 값을 사용하여 다시 생성됩니다. 새 쌍을 생성하려면, /etc/gitlab/gitlab-secrets.json
의 registry
섹션을 삭제한 후 gitlab-ctl reconfigure
를 실행하세요.
자동 생성된 자체 서명된 쌍을 사용자 인증서로 덮어쓴 경우 및 내용이 일치하는지 확인한 후, /etc/gitlab/gitlab-secrets.json
에서 ‘registry’ 섹션을 삭제하고 gitlab-ctl reconfigure
를 실행할 수 있습니다.
AWS S3와 GitLab 레지스트리의 대형 이미지 푸시 오류
AWS S3를 GitLab 레지스트리와 함께 사용할 때 대형 이미지를 푸시할 때 오류가 발생할 수 있습니다. 다음 오류를 레지스트리 로그에서 확인하세요:
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) 사이의 값으로 시작하세요.
-
/etc/gitlab/gitlab.rb
를 수정합니다:registry['storage'] = { 's3' => { 'accesskey' => 'AKIAKIAKI', 'secretkey' => 'secret123', 'bucket' => 'gitlab-registry-bucket-AKIAKIAKI', 'chunksize' => 25000000 } }
-
파일을 저장하고 GitLab 재구성을 통해 변경 사항을 적용하세요.
-
config/gitlab.yml
을 수정합니다:storage: s3: accesskey: 'AKIAKIAKI' secretkey: 'secret123' bucket: 'gitlab-registry-bucket-AKIAKIAKI' chunksize: 25000000
-
파일을 저장하고 GitLab 재시작을 통해 변경 사항을 적용하세요.
구형 Docker 클라이언트 지원
GitLab과 함께 제공되는 Docker 컨테이너 레지스트리는 기본적으로 schema1 매니페스트를 비활성화합니다.
여전히 구형 Docker 클라이언트(1.9 이하)를 사용하는 경우 이미지 푸시 시 오류가 발생할 수 있습니다.
자세한 내용은 issue 4145를 참조하세요.
하위 호환성을 위해 구성 옵션을 추가할 수 있습니다.
-
/etc/gitlab/gitlab.rb
를 수정합니다:registry['compatibility_schema1_enabled'] = true
-
파일을 저장하고 GitLab 재구성을 통해 변경 사항을 적용하세요.
-
레지스트리를 배포할 때 생성한 YAML 구성 파일을 수정합니다. 다음 스니펫을 추가합니다:
compatibility: schema1: enabled: true
-
변경 사항을 적용하기 위해 레지스트리를 재시작하세요.
Docker 연결 오류
그룹, 프로젝트 또는 브랜치 이름에 특수 문자가 포함된 경우 Docker 연결 오류가 발생할 수 있습니다. 특수 문자는 다음을 포함할 수 있습니다:
- 선행 밑줄
- 후행 하이픈/대시
- 이중 하이픈/대시
이를 해결하려면 그룹 경로 변경,
프로젝트 경로 변경 또는 브랜치 이름을 변경할 수 있습니다.
또한 전체 인스턴스에 대해 이 오류를 방지하기 위해 푸시 규칙을 생성하는 방법도 있습니다.
이미지 푸시 오류
이미지를 푸시할 때 오류가 발생하거나 “재시도” 루프가 발생하지만 docker login
은 정상적으로 작동하는 경우, NGINX가 레지스트리에 전달한 헤더에 문제가 있을 가능성이 높습니다.
추천하는 기본 NGINX 구성은 이를 처리해야 하지만, SSL이 타사 리버스 프록시에 오프로딩될 경우 사용자 지정 설정에서 발생할 수 있습니다.
이 문제는 Docker 프로젝트 이슈에서 논의되었으며 간단한 해결책으로 레지스트리에서 상대 URL을 활성화하는 것입니다.
-
/etc/gitlab/gitlab.rb
를 수정합니다:registry['env'] = { "REGISTRY_HTTP_RELATIVEURLS" => true }
-
파일을 저장하고 GitLab 재구성을 통해 변경 사항을 적용하세요.
-
레지스트리를 배포할 때 생성한 YAML 구성 파일을 수정합니다. 다음 스니펫을 추가합니다:
http: relativeurls: true
-
파일을 저장하고 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'
}
}
Prometheus에서 디버그 출력을 요청하려면 curl을 사용하세요:
curl "localhost:5001/debug/metrics"
빈 이름의 태그
AWS DataSync를 사용하여 레지스트리 데이터를 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! # 긴 작업
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)
은 제거할 태그 목록을 출력합니다. 이는 시간이 오래 걸릴 수 있습니다. -
각 필터 후, 삭제할 태그가 포함되어 있는지 확인하세요.
푸시 중 예상치 못한 403 오류
사용자가 S3 기반 레지스트리를 활성화하려고 시도했습니다. docker login
단계는
잘 진행되었습니다. 그러나 이미지를 푸시할 때 출력은 다음과 같이 나타났습니다:
푸시는 다음 레포지토리를 참조합니다 [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
오류 HTTP 403 응답 본문을 파싱하는 중: 예기치 않은 JSON 입력의 끝: ""
이 오류는 모호합니다. 403 오류가 GitLab Rails 애플리케이션에서 발생한 것인지, Docker 레지스트리에서 발생한 것인지, 아니면 다른 곳에서 발생한 것인지 명확하지 않습니다. 로그인이 성공했음을 알고 있는 경우, 클라이언트와 레지스트리 간의 통신을 살펴봐야 할 것 같습니다.
Docker 클라이언트와 레지스트리 간의 REST API는 Docker 문서에 설명되어 있습니다. 보통은 Wireshark나 tcpdump를 사용하여 트래픽을 캡처하고 문제가 발생한 지점을 확인합니다. 그러나 Docker 클라이언트와 서버 간의 모든 통신이 HTTPS로 이루어지기 때문에 비공식 키를 알고 있더라도 트래픽을 빠르게 복호화하기는 다소 어렵습니다. 대신 어떻게 할 수 있을까요?
한 가지 방법은 안전하지 않은 레지스트리를 설정하여 HTTPS를 비활성화하는 것입니다. 이는 보안 구멍을 초래할 수 있으며, 로컬 테스트에만 권장됩니다. 생산 시스템이 있고 이를 수행할 수 없거나 원하지 않는 경우 다른 방법이 있습니다: 중간자 프록시인 mitmproxy를 사용하세요.
mitmproxy
mitmproxy는 클라이언트와 서버 간에 프록시를 두고 모든 트래픽을 검사할 수 있게 해줍니다. 한 가지 주의할 점은 이 작업을 수행하려면 시스템이 mitmproxy SSL 인증서를 신뢰해야 한다는 것입니다.
다음 설치 지침은 Ubuntu를 실행하고 있다고 가정합니다:
- mitmproxy 설치하기.
-
mitmproxy --port 9000
를 실행하여 인증서를 생성합니다. 종료하려면 CTRL-C를 누르세요. -
~/.mitmproxy
에서 인증서를 시스템에 설치합니다:sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy-ca-cert.crt sudo update-ca-certificates
성공하면 출력에서 인증서가 추가되었다고 표시해야 합니다:
/etc/ssl/certs의 인증서 업데이트... 1개 추가됨, 0개 제거됨; 완료.
/etc/ca-certificates/update.d의 후크 실행 중.... 완료.
인증서가 올바르게 설치되었는지 확인하려면 다음을 실행하세요:
mitmproxy --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를 실행하는 것입니다. 루트로 다음을 실행하십시오:
export HTTP_PROXY="http://localhost:9000"
export HTTPS_PROXY="http://localhost:9000"
docker daemon --debug
이 명령은 Docker 데몬을 실행하고 모든 연결을 mitmproxy를 통해 프록시합니다.
Docker 클라이언트 실행하기
이제 mitmproxy와 Docker가 실행되고 있으므로, 로그인하고 컨테이너 이미지를 푸시해 볼 수 있습니다. 이를 수행하려면 루트로 실행해야 할 수 있습니다. 예를 들어:
docker login s3-testing.myregistry.com:5050
docker push s3-testing.myregistry.com:5050/root/docker-test/docker-image
위의 예에서 mitmproxy 창에서 다음과 같은 추적을 볼 수 있습니다:
위 이미지는 다음을 보여줍니다:
- 초기 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
이 경우, 다음 단계를 따르세요:
-
gitlab.rb
에서 인스턴스 전체 설정을 임시로 활성화합니다:gitlab_rails['registry_enabled'] = true
- 파일을 저장한 후 GitLab 재구성하기를 통해 변경 사항이 적용되도록 합니다.
- 다시 제거를 시도합니다.
일반적인 방법으로 리포지토리를 삭제할 수 없는 경우, 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 주소에서 수신 대기
localhost
호스트 이름이 GitLab 서버에서 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를 참조하세요.