4P by GN⁺ | ★ favorite | 댓글 1개
  • Unregistry는 Docker 데몬 저장소에서 이미지를 직접 저장·제공하는 경량 컨테이너 이미지 레지스트리이며, docker pussh 명령으로 원격 Docker 서버에 이미지를 SSH로 바로 전송함
  • 기존 선택지는 Docker Hub/GitHub Container Registry, 자체 호스팅 레지스트리, docker save | ssh... docker load, 원격 재빌드인데, 각각 공개·유료 저장소, 운영 부담, 전체 이미지 전송, 서버 자원 낭비 문제가 있음
  • docker pussh myapp:latest user@server는 SSH 터널을 만들고 원격 서버에서 임시 unregistry 컨테이너를 띄운 뒤, 포워딩된 localhost 포트로 docker push를 수행해 원격에 없는 레이어만 전송함
  • 원격 서버에는 Docker 실행 환경, docker 명령 권한, 최초 실행 시 ghcr.io/psviderski/unregistry:latest 접근이 필요하며, unregistry 컨테이너는 /run/containerd/containerd.sock 접근 때문에 root로 실행됨
  • Docker의 containerd image store를 활성화하면 전송된 이미지가 Docker에서 즉시 사용 가능하고 중복 저장을 피할 수 있지만, 기존 classic storage driver로 만든 이미지와 컨테이너가 일시적으로 보이지 않을 수 있음

Unregistry가 해결하는 배포 문제

  • Unregistry는 외부 레지스트리 없이 Docker 이미지를 원격 서버로 옮기기 위한 경량 컨테이너 이미지 레지스트리임
  • 포함된 Docker CLI 플러그인 명령은 docker pussh이며, 이름의 추가 s는 SSH를 뜻함
  • 로컬에서 만든 Docker 이미지를 서버로 옮길 때 일반적인 선택지는 다음과 같은 제약이 있음
    • Docker Hub / GitHub Container Registry: 코드가 공개되거나 private 저장소 비용을 부담해야 함
    • Self-hosted registry: 유지보수, 보안, 저장소 비용이 필요한 별도 서비스가 생김
    • Save/Load: docker save | ssh <remote server> docker load는 서버에 이미 90%가 있어도 전체 이미지를 전송함
    • Remote rebuild: 시간과 서버 자원을 쓰며, 프로덕션에서 빌드 실패 원인을 디버깅해야 할 수 있음

docker pussh 동작 방식

  • 기본 사용법은 다음 한 줄임
docker pussh myapp:latest user@server
  • 이 명령은 레지스트리 설정, 구독, 중간 저장소, 공개 포트 없이 누락된 레이어만 SSH로 직접 전송함
  • 내부 동작은 다음 순서로 진행됨
    • 원격 서버로 SSH 터널을 생성함
    • 서버에서 임시 unregistry 컨테이너를 시작함
    • 임의의 localhost 포트를 터널을 통해 unregistry 포트로 포워딩함
    • 포워딩된 포트로 docker push를 수행해 원격에 없는 레이어만 전송함
    • 전송된 이미지는 원격 Docker 데몬에서 즉시 사용 가능함
    • unregistry 컨테이너를 중지하고 SSH 터널을 닫음
  • 프로젝트는 Docker 이미지를 위한 rsync 같은 방식으로 단순하고 효율적인 전송을 목표로 함
  • Unregistry는 여러 Docker 호스트에 컨테이너를 배포하는 경량 도구 Uncloud를 위해 만들어졌으며, 전체 레지스트리보다 단순하고 save/load보다 효율적인 방식이 필요했음

실행 요구사항과 제약

  • 로컬 머신에는 Docker CLI 플러그인 지원이 필요하며, Docker 19.03 이상과 OpenSSH 클라이언트가 필요함
  • 원격 서버에는 Docker가 설치되어 실행 중이어야 함
  • SSH 사용자는 docker 명령을 실행할 권한이 있어야 함
    • 사용자가 root이거나 non-root 사용자가 docker 그룹에 있어야 함
    • 관련 문서는 Docker의 Manage Docker as a non-root user를 참고함
    • sudo가 필요하면 비밀번호 프롬프트 없이 sudo docker를 실행할 수 있어야 함
  • 최초 docker pussh 실행 시 원격 서버가 ghcr.io에서 ghcr.io/psviderski/unregistry:latest 이미지를 가져올 수 있어야 함
    • 프록시가 필요한 서버는 Docker의 Daemon proxy configuration 안내에 따라 설정해야 함
    • air-gapped 환경이나 ghcr.io 접근이 제한된 환경에서는 필요한 unregistry 이미지 버전을 확인한 뒤 수동으로 preload할 수 있음
  • unregistry 컨테이너는 /run/containerd/containerd.sock에 접근해야 하므로 필요한 권한을 위해 root로 실행됨

설치와 지원 플랫폼

  • macOS/Linux에서는 Homebrew로 docker-pussh를 설치할 수 있음
brew install psviderski/tap/docker-pussh
  • Homebrew 설치 후 Docker CLI 플러그인으로 docker pussh를 쓰려면 ~/.docker/cli-plugins/docker-pussh 심볼릭 링크를 만들어야 함
  • macOS/Linux에서는 직접 다운로드 방식도 제공됨
    • 현재 버전 예시는 v0.4.3docker-pussh 스크립트를 Docker 플러그인 디렉터리에 내려받아 실행 권한을 부여하는 방식임
    • main 브랜치의 최신 스크립트를 내려받는 방식도 제공됨
  • Debian은 @dariogriffo가 만들고 관리하는 비공식 패키지 저장소 unregistry-debian을 통해 설치할 수 있음
  • Windows는 현재 지원되지 않지만, WSL 2와 Linux 설치 절차를 시도할 수 있음
  • 설치 확인은 다음 명령으로 수행함
docker pussh --help

containerd image store 설정

  • Unregistry는 Docker가 사용하는 하위 컨테이너 런타임인 containerd의 image store에 이미지를 직접 저장함
  • 기본 Docker 동작에서는 Docker가 별도 저장 계층을 유지하며 containerd의 이미지를 직접 사용하지 않음
  • Docker에서 containerd image store를 활성화하면 Docker가 unregistry가 저장한 이미지를 직접 사용할 수 있어 중복을 제거함
  • containerd image store 활성화 시

    • unregistry로 push한 이미지가 Docker에서 즉시 사용 가능함
    • 이미지는 containerd에 한 번만 저장되어 추가 저장 공간을 쓰지 않음
    • unregistry에서 classic Docker image store로 추가 pull하는 단계가 없어 pussh 작업이 더 빨라짐
  • 기본 Docker 동작 유지 시

    • push 이후 pussh가 원격 호스트에서 추가 docker pull을 실행해 이미지를 Docker에서 사용할 수 있게 만듦
    • 이미지는 containerd와 classic Docker image store에 각각 저장되어 두 번 저장
    • containerd의 unmanaged image가 시간이 지나며 디스크 공간을 채울 수 있음
    • 수동 관리는 다음 명령으로 수행함
    sudo ctr -n moby images ls
    sudo ctr -n moby images rm <image>
    
  • 설정 시 주의점

    • Docker Engine에서 containerd image store를 활성화하는 방법은 공식 Docker 문서를 따름
    • containerd image store로 전환하면 classic storage driver로 만든 이미지와 컨테이너가 일시적으로 보이지 않음
    • 해당 리소스는 파일시스템에 남아 있으며, containerd image store 기능을 끄면 다시 가져올 수 있음

사용 예시

  • 기본 전송은 이미지 이름과 원격 SSH 대상만 지정함
docker pussh myapp:latest user@server.example.com
  • SSH agent에 private key가 등록되지 않은 경우 -i로 키를 지정할 수 있음
docker pussh myapp:latest ubuntu@192.168.1.100 -i ~/.ssh/id_rsa
  • 커스텀 SSH 포트는 대상 뒤에 포트를 붙여 지정함
docker pussh myapp:latest user@server:2222
  • 커스텀 SSH config 파일은 -F로 지정함
docker pussh myapp:latest prod-server -F ~/.ssh/config.prod
  • 멀티 플랫폼 이미지에서 특정 플랫폼만 push하려면 --platform을 사용함
docker pussh myapp:latest user@server --platform linux/amd64
  • 원격 호스트에서 특정 unregistry 이미지 버전을 쓰려면 UNREGISTRY_IMAGE 환경 변수를 지정함
UNREGISTRY_IMAGE=ghcr.io/psviderski/unregistry:A.B.C docker pussh myapp:latest user@server.example.com

대표 사용 사례

  • 프로덕션 서버 배포에서는 로컬에서 빌드한 이미지를 중간 레지스트리 없이 서버로 직접 push하고 실행할 수 있음
docker build --platform linux/amd64 -t myapp:1.2.3 .
docker pussh myapp:1.2.3 deploy@prod-server
ssh deploy@prod-server docker run -d myapp:1.2.3
  • CI/CD 파이프라인에서는 레지스트리 복잡성을 건너뛰고 배포 대상에 직접 이미지를 보낼 수 있음
- name: Build and deploy
  run: |
    docker build -t myapp:${{ github.sha }} .
    docker pussh myapp:${{ github.sha }} deploy@staging-server
  • homelab과 air-gapped 환경에서는 공용 레지스트리에 인터넷으로 접근할 수 없는 격리 네트워크에서 이미지를 배포할 수 있음

고급 사용과 관련 프로젝트

  • Unregistry는 독립 실행형 로컬 레지스트리로도 사용할 수 있음
    • /run/containerd/containerd.sock을 마운트하고 ghcr.io/psviderski/unregistry 컨테이너를 실행함
    • 이후 localhost:5000을 일반 레지스트리처럼 docker tagdocker push에 사용할 수 있음
  • SSH 설정은 표준 SSH config 파일을 쓰거나 -F로 별도 config 파일을 전달할 수 있음
  • 관련 서드파티 프로젝트가 제공됨
  • 구현에는 SpegelDocker Distribution이 언급됨
    • Spegel은 containerd image store를 백엔드로 쓰는 레지스트리 구현에 영감을 준 P2P 컨테이너 이미지 레지스트리임
    • Docker Distribution은 unregistry의 기반이 된 Docker registry 구현임

댓글과 토론

Hacker News 의견들
  • Docker 만든 입장에서 마음에 듦. 이상적인 설계는 Docker 엔진과 레지스트리 구분이 없는 단일 서버였을 것 같음
    필요에 따라 컨테이너를 저장·전송·실행할 수 있었다면 훨씬 견고한 구성 요소가 됐고, 엔진과 레지스트리가 이미지를 저장하는 방식이 엇갈리는 아쉬운 흐름도 피할 수 있었을 것임
    또 모든 프로덕션 클러스터에는 분산 이미지 저장소가 있어야 하고, 그 저장소로 이미지를 푸시하는 것이 배포를 트리거해야 했다고 봄. 지금처럼 레지스트리에 푸시하고, 클러스터를 설정하고, 각 노드가 레지스트리에서 당겨오는 방식은 취약하고 비효율적임. 더 나은 설계를 주장했지만 이미 관성이 너무 컸고, 초기 Kubernetes 커뮤니티는 Docker에서 나온 아이디어에 적대적이었음

    • 이미지에 대해 파일 시스템 배치가 최소 세 가지, 엔진 안 이미지 저장소도 두 가지나 있는 건 확실히 어수선함. 그래도 Docker가 현재 모델을 깨지 않고도 그런 방향을 달성하기엔 아직 늦지 않았다고 봄. 다만 Docker가 그걸 신경 쓸지는 모르겠고, 요즘은 어려운 시기를 보내는 듯함
      클러스터로 푸시하는 배포는 영리해 보임. 모든 노드에 unregistry를 넣어 서로 이미지를 가져오고 공유하게 하는 식의 분산 이미지 저장소는 생각 중이지만, 푸시가 배포를 트리거하는 방식은 좀 더 고민해봐야겠음
  • 좋음. pussh 명령은 정말 우아한 말장난으로 인정받을 만함. 기억하기 쉽고, 의미가 바로 보이고, 자매 표준 명령과 한 글자 차이뿐임

    • 나쁘진 않지만 docker push-over-ssh 같은 더 공식적인 별칭이 있어도 좋겠음
      협업으로 개발되는 자동화에서는 pussh가 기능을 모르는 사람에게 오타처럼 보일 수 있어 불필요한 혼란을 만들 수 있음. 반면 push-over-ssh는 의도된 이름이라는 게 명확함. 짧은 옵션과 긴 옵션처럼 생각하면 됨
    • 추가된 sssshs
      “저 extra s는 뭐야?”
      “오타야”
    • 충돌도 나기 쉬움
  • 2015년에 이 기능을 Docker 본류에 넣으려고 순진하게 PR[1]을 보냈다가, 금방 다른 영역을 돕는 쪽으로 돌려졌음. 레지스트리를 안 써도 되게 만드는 건 Docker의 비즈니스 모델을 너무 흔들었을지도 모름
    [1]: https://github.com/richardcrichardc/docker2docker

    • 원조였군요. 아직 Docker에 이미지 레이어를 탐색하는 API가 없는 건 아쉬움
      결국 기본값을 containerd 이미지 저장소로 전환하려는 계획일 거라고 봄. 로컬과 원격 모두에서 containerd 이미지 저장소를 쓰게 되면, 레지스트리 래퍼 없이도 원래 구현했던 걸 할 수 있게 될 듯함
  • Ansible 같은 푸시 배포 도구를 이미 쓰는 시스템과 잘 맞을 것 같은 멋진 아이디어임. Docker 레지스트리에 24/7 지원이 없는 회사에서는 좋은 핫픽스 배포 메커니즘으로도 쓸 수 있어 보임
    buildah 같은 OCI 도구와 깔끔하게 통합되는지, 아니면 양쪽 끝에 완전한 Docker 설치가 필요한지 궁금함. 아직 깊게 보진 않았지만, 이런 구성에서 skopeo가 동작하려면 원격 서버에 미니 레지스트리를 부트스트랩하는 게 빠진 조각처럼 보임

    • 원격 쪽에는 containerd가 필요함. Docker와 Kubernetes도 containerd를 사용함. 클라이언트 쪽에는 레지스트리 API, 즉 OCI Distribution 명세(https://github.com/opencontainers/distribution-spec)를 말하는 아무 도구나 있으면 됨
      Unregistry는 API 계층에 공식 Docker 레지스트리 코드를 재사용하므로 https://hub.docker.com/_/registry와 비슷하게 보이고 동작함
      클라이언트에서는 skopeo, crane, regclient, BuildKit 등 OCI 레지스트리를 말하는 도구를 쓸 수 있음. 다만 이들을 쓰려면 원격 호스트에서 unregistry를 수동으로 실행해야 함. docker pussh 명령은 로컬 Docker를 사용해 그 흐름을 자동화할 뿐임
      그냥 Bash 스크립트라 보면 됨: https://github.com/psviderski/unregistry/blob/main/docker-pu...
      직접 원하는 방식으로 쉽게 고칠 수 있음
    • 동의함. 관리하는 여러 서비스에서 이미지를 로컬로 빌드한 뒤 저장하고, Ansible로 아카이브를 업로드한 다음 이미지를 복원하는데, 보통 원하는 것보다 훨씬 오래 걸림
    • 양쪽 끝에 Docker 데몬이 필요함. SSH를 통해 두 데몬 사이에서 레이어를 공유하는 영리한 방법임
  • 원래부터 이렇게 됐어야 했음. 훌륭함
    Docker 레지스트리도 쓸 곳은 있지만, 전반적으로는 과하게 설계됐고 해커 정신과는 반대편에 있음

    • 벤처투자를 받은 회사였으니 Docker도 어떻게든 돈을 벌어야 했음
    • GitHub Actions와 함께 GitHub의 레지스트리인 ghcr.io를 쓰는 걸 추천함
      비공개 ghcr.io 레지스트리에 이미지를 빌드하고 푸시하는 .yaml 워크플로를 설정하는 데 20분, 서버가 거기서 이미지를 가져오게 허용하는 데 5분 정도 들었음. 꽤 실용적인 구성임
    • 복잡성은 레지스트리에 블롭을 푸시하는 절차에 있는 것 같음. 예전에 OCI 호환 읽기 전용 레지스트리를 만든 적이 있는데, 그건 그렇게 복잡하지 않았음
  • GitLab에서 이미지를 빌드하고 Artifactory에 푸시한 뒤, 배포가 트리거되어 Artifactory에서 가져와 AWS ECR로 다시 푸시하고, EKS의 배포 템플릿을 갱신하면 ECR에서 노드로 가져와 Pod 컨테이너를 띄우는 파이프라인을 보고 있음
    내 인생에 이게 필요함

    • 궁금해서 묻는데 Artifactory와 ECR을 둘 다 쓰는 이유가 뭔가요? 우리는 비용 절감 때문에 Artifactory에서 ECR로 전환하는 걸 검토 중임
    • 마지막 프로젝트의 파이프라인은 실제 앱 빌드보다 컨테이너를 당기고 푸시하는 시간이 더 길었음. 게다가 이 모든 시간도 상태 확인 대기 시간에 비하면 작았는데, 실제로 정상인지 아닌지는 시작 후 1초도 안 돼 알 수 있었음
  • 이걸 보고 uncloud를 알게 됨. 사이드프로젝트 서버 구성용으로 dokku 같은데 좀 더 강한 걸 찾고 있었는데 딱 그런 느낌임

    • https://skateco.github.io/도 있음. 대충 봤을 때 비슷해 보임
    • Portainer를 안 써봤거나 검토하지 않았다면 추천함. AWS에서 EC2 인스턴스 두 개에 Portainer Community Edition과 Portainer Agent를 돌리고 있는데 잘 동작함
      스택 기능도 정말 좋은데, 사실상 Docker Compose임. 한 EC2 인스턴스에서는 Portainer Agent가 컨테이너 안의 Caddy를 실행하고, Caddy가 로드 밸런서와 리버스 프록시 역할을 함
    • uncloud 아이디어가 와닿았다니 기쁨. 질문이나 도움이 필요하면 Discord에 들어와도 됨
  • Docker가 처음부터 이렇게 동작하지 않았다는 게 꽤 이상함. 멋져 보임

    • 이미 이미지를 아카이브로 만들고 서버로 푸시한 다음, 서버에서 그 아카이브로 실행하는 방식으로 같은 일을 할 수 있음
      아카이브 저장은 docker save -o may-app.tar my-app:latest처럼 하고, 로드는 docker load -i /path/to/my-app.tar처럼 함
      Ansible 같은 도구를 쓰면 “Unregistry”가 자동화하는 일을 쉽게 달성할 수 있음. GitHub 저장소에 따르면 save/load 방식의 단점은 전체 이미지를 네트워크로 전송한다는 점이고, 실제로 문제가 될 수 있음. 아카이브 파일 대신 이미지 자체를 관리하는 편도 더 편해 보임
  • 깔끔한 프로젝트와 접근법임. 비싼 레지스트리에 질려서 Zot[1]을 직접 호스팅하게 됐지만, 일부 용도에는 이게 훨씬 쉬워 보임
    쉽게 설정할 수 있고, 저렴하고, 사용량 기반 과금인 비공개 레지스트리 서비스가 있으면 좋겠다고 느끼는 사람이 또 있는지 궁금함
    [1]: https://zotregistry.dev

    • 혹시 모르고 있었다면 zothub.io의 SSL 인증서가 만료됐음
  • 괜찮은 아이디어임. 다만 배포가 서비스에 결합되는 단점이 있을 수 있음. 예를 들어 스케일 아웃이나 red/green 배포는 어떻게 할지 모르겠고, 그런 일을 하는 쪽이 푸시를 알아야 할 듯함
    수정: 그걸 하는 게 uncloud였음. 방금 알게 됨
    그래도 트레이드오프임. 규모가 작고 Hetzner VM 하나를 쓰며 단순함에 만족하고, 로컬에서 이미지를 빌드하는 것도 괜찮다면 훌륭함

    • 확실히 항상 트레이드오프임. 선택지가 있어서 작업마다 가장 맞는 도구를 고를 수 있다는 게 좋음