Dependency Proxy
의존성 프록시는 DockerHub의 공개 레지스트리 이미지를 위한 캐시입니다. 이 문서에서는 GitLab에서 이 기능이 구성된 방법에 대해 설명합니다.
컨테이너 레지스트리
컨테이너 레지스트리의 의존성 프록시는 원격 컨테이너 레지스트리를 대신하는 역할을 합니다. 우리의 경우에는 원격 레지스트리가 공개 DockerHub 레지스트리입니다.
사용자 관점에서 보면, GitLab 인스턴스는 단순히 docker login gitlab.com
을 사용하여 이미지를 가져오기 위해 상호작용하는 컨테이너 레지스트리입니다.
docker login gitlab.com
을 사용하면 Docker 클라이언트가 v2 API를 사용하여 요청을 만듭니다.
인증을 지원하기 위해 하나의 라우트를 포함해야 합니다:
docker pull
요청을 지원하기 위해 두 가지 추가적인 라우트를 포함해야 합니다:
이러한 라우트는 gitlab-org/gitlab/config/routes/group.rb
에 정의되어 있습니다.
가장 간단한 형태에서 의존성 프록시는 세 가지 요청을 관리합니다:
- 로그인 / JWT 반환
- 매니페스트 가져오기
- 블롭 가져오기
의존성 프록시의 일반적인 요청 순서는 다음과 같습니다:
인증 및 권한 부여
Docker 클라이언트가 레지스트리와 인증할 때, 레지스트리는 클라이언트에게 JSON 웹 토큰(JWT)을 얻을 위치와 이를 향후 모든 요청에 사용하도록 요청합니다. 이를 통해 인증 서비스가 레지스트리와 별도의 애플리케이션에 있을 수 있습니다. 예를 들어, GitLab 컨테이너 레지스트리는 Docker 클라이언트에게 https://gitlab.com/jwt/auth
에서 토큰을 얻도록 지시합니다. 이 엔드포인트는 gitlab-org/gitlab
프로젝트의 일부이며, 레일 프로젝트 또는 웹 서비스로도 알려져 있습니다.
사용자가 Docker 클라이언트로 의존성 프록시에 서명하려고 할 때, 어디에서 JWT를 얻을지 알려주어야 합니다. 우리는 컨테이너 레지스트리와 동일한 엔드포인트를 사용할 수 있습니다: https://gitlab.com/jwt/auth
. 그러나 여기서는 파라미터에 service=dependency_proxy
를 지정하도록 Docker 클라이언트에게 알려주어 토큰을 생성하는 별도의 기본 서비스를 사용할 수 있습니다.
이 순서 다이어그램은 의존성 프록시에 로그인하는 요청 흐름을 보여줍니다.
의존성 프록시는 UI(ApplicationController
)와 API(ApiGuard
)에서 관리되는 인증과는 별개로 자체 인증 서비스를 사용합니다. 서비스가 JWT를 생성한 후, DependencyProxy::ApplicationController
가 나머지 요청에 대한 인증 및 권한을 관리합니다. 이는 GitHttpClientController
에서 구현된 Git 클라이언트 요청과 유사합니다.
캐싱
블롭은 로직이 없는 캐시된 아티팩트입니다. 요청을 처리할 때 해당 블롭의 다이제스트를 기반으로 캐시합니다. 새로운 블롭에 대한 요청을 받으면 해당 다이제스트로 캐시된 블롭이 있는지 확인하고 반환합니다. 그렇지 않을 경우 외부 레지스트리에서 블롭을 가져와 캐시합니다.
매니페스트는 DockerHub의 요율 제한으로 인해 더 복잡합니다. 매니페스트는 기본적으로 이미지를 만들기 위한 레시피입니다. 특정 이미지를 생성하는 데 필요한 블롭 디렉터리을 포함합니다. 예를 들어, alpine:latest
에는 alpine:latest
이미지를 만드는 데 필요한 블롭을 지정하는 관련 매니페스트가 있습니다. 흥미로운 점은 alpine:latest
이 시간이 지남에 따라 변경될 수 있다는 것입니다. 따라서 캐시된 매니페스트를 단순히 캐시하고 계속 사용해도 괜찮다고 가정할 수 없습니다. 대신 매니페스트의 ETag인 다이제스트를 확인해야 합니다. 이는 요청된 매니페스트의 다이제스트가 재검사 기능으로 얻어지는데, 요청된 매니페스트의 다이제스트가 자주 포함되지 않기 때문에 흥미로운 문제가 됩니다. 따라서 캐시된 매니페스트가 여전히 가장 최신의 alpine:latest
인지 확인할 수 있는 방법이 있어야 합니다. DockerHub는 요율 제한에 포함되지 않는 무료 HEAD 요청을 허용합니다. HEAD 요청은 매니페스트 다이제스트를 반환하므로 캐시된 매니페스트가 여전히 가장 최신의 alpine:latest
인지 여부를 파악할 수 있습니다.
이러한 지식을 바탕으로 다음과 같은 로직을 구축하여 매니페스트 요청을 관리했습니다:
파일 처리를 위한 Workhorse
파일 업로드 및 캐싱 관리는 Workhorse에서 처리됩니다. 이는 의존성 프록시를 위해 추가된
POST
routes를 설명합니다.
send_dependency
메서드는 외부 레지스트리에서 이전에 가져온 JWT를 포함하여 Workhorse에 요청을 보냅니다. 그런 다음 Workhorse는 해당 토큰을 사용하여 사용자가 원래 요청한 매니페스트 또는 블롭을 요청할 수 있습니다. Workhorse 코드는
workhorse/internal/dependencyproxy/dependencyproxy.go
에 있습니다.
모두를 통합하면 이미지 파일을 요청하는 시퀀스는 다음과 같습니다:
정리 정책
의존성 프록시의 정리 정책은 시간 제한 정책으로 작동합니다. 사용자는 파일이 읽지 않은 상태로 유지될 수 있는 일수를 설정할 수 있습니다. 이미지와 관련된 블롭을 연관시키는 방법이 없기 때문에(이를 위해 컨테이너 레지스트리팀이 구축한 메타데이터 데이터베이스를 구축해야 합니다), “이 블롭이 90일 동안 가져오지 않았다면 삭제”와 같은 규칙을 설정할 수 있습니다. 따라서 계속해서 가져오는 파일들은 캐시에서 제거되지 않지만, 예를 들어 alpine:latest
가 변경되고 하위 블롭 중 하나가 더 이상 사용되지 않는다면, 결국 정리됩니다. 우리는 read_at
속성을 사용하여 주어진 dependency_proxy_blob
또는 dependency_proxy_manifest
가 마지막으로 가져온 시간을 추적합니다.
이 작업은 DependencyProxy::CleanupDependencyProxyWorker라는 cron 작업자를 사용하여 진행되며, 블롭을 삭제하는 한 가지, 매니페스트를 삭제하는 한 가지 제한된 용량 작업자를 시작할 것입니다. 용량은 애플리케이션 설정에서 설정됩니다.
과거 참고 링크
- 개인 그룹을 위한 의존성 프록시 - 초기 인증 구현
- 매니페스트 캐싱 - 초기 매니페스트 캐싱 구현
- 블롭을 위한 Workhorse - 초기 Workhorse 구현
- 매니페스트를 위한 Workhorse - 매니페스트 캐시 로직을 Workhorse로 이동
- 배포 토큰 지원 - 대부분의 업데이트 된 인가
- SSO 지원 - 정책 확인 방식 변경