Search
🌍

Github Action을 통해 빌드한 컨테이너 이미지를 홈 서버에 지속적으로 배포(CD)하는 가장 간단한 방법

프로젝트
♻️ prev note
🚀 prev note
♻️ next note
🚀 next note
7 more properties
Github Container Registry(GCR)에 업로드된 새로운 이미지를 docker compose 환경으로 자동으로 배포하기 위해서는 docker compose를 구성하는 요소가 새로운 이미지의 존재를 확인할 수 있어야 합니다. 대표적으로는 webhook 기반 방법, polling 기반 방법이 있습니다.
Webhook을 사용하는 도구들, 또는 이와 비슷한 모종의 방식들(HTTP 요청 등)은 실시간 배포가 가능하고 리소스 효율적이지만 약간의 설정 복잡성이 있을 수 있습니다. GCR에 새 이미지가 푸시되면 즉시 홈 서버의 webhook 엔드포인트로 알림을 전송하는 방식입니다.
우아한 Webhook 기반 방법들에 비해 Polling 기반 방법들은 전반적으로 GitHub API의 시간당 5,000회 요청 제한(ref2)과 업데이트 지연이라는 문제들이 있음에도 불구하고 구현이 단순하고 방화벽과 같은 걱정에서 자유로울 수 있어 홈 서버에 배포하는 토이 프로젝트, 작은 서비스에서는 더 안정적인 선택이 될 수 있습니다.
물론 보안상 권장되지 않는 방법이지만, github action에서 빌드가 완료되었을 때 SSH를 이용해 서버에 접근해 image pull을 해 버리는 방법도 있습니다. 이 글에서는 어떤 방법과 도구들이 있는지를 살펴보고, 제가 Watchover을 선택한 이유도 간단히 풀어봅니다.

Container CD 생태계

Diun

우선 정말 간단한 애플리케이션에 있어서는 CD 대신 간단히 알림을 보내주는 접근방식이 더 합리적인 경우가 많습니다. Diun는 지속적 배포가 아니라 알림을 보내는 일에 집중하는 도구입니다. 프로젝트 페이지를 보면 활발히 개발되고 있는 것을 확인할 수 있습니다. 메모리 사용량이 8MB도 되지 않아 가볍고, 15개 이상의 알림 채널을 지원합니다. 앞으로 소개할 Watchtower과 같이 사용하는 사람들도 있습니다.
# Diun 설정 예시 services: diun: image: crazymax/diun:latest volumes: - "./data:/data" - "/var/run/docker.sock:/var/run/docker.sock" environment: - "DIUN_WATCH_WORKERS=20" - "DIUN_NOTIF_TELEGRAM_TOKEN=YOUR_TOKEN" restart: always
YAML
복사
하지만 저는 개발 사정상 완전 자동 CD를 고민하고 있으므로 요구사항에 맞지 않아 이정도만 하고 넘어갑니다.

SSH 기반 배포

두 번째로 살펴볼 것은 SSH 기반의 배포입니다. 하지만 이 방법도 가볍게만 살피고 넘어가야 할 것 같습니다. 아무리 비밀번호가 걸려 있다고 한들 내 컴퓨터의 특정 포트를 외부로 노출시키는 것은 정말 생각보다 위험한 일이기 때문입니다. 예전에 킥보드를 개발할 때 Jetson 컴퓨터에 특히 중국 IP로부터 브루트 포스 공격을 쉴 새 없이 받았고, 그 결과 로그 파일이 잔뜩 쌓이다 못해 컴퓨터가 용량 부족을 호소하거나 네트워크 대역폭에 영향을 받아 쓰지 못할 지경에 이르렀던 경험을 실제로 해 버렸거든요. 해커가 내 컴퓨터 문턱까지 와서 포트를 더듬거리고 있는 것도 기분나쁜 일인데, 그 더듬음이 도를 지나쳐서 컴퓨터가 고장이 날 정도라면 보안 불감증인 저조차도 생각만 해도 끔찍합니다.
- name: Deploy via SSH uses: appleboy/ssh-action@v1.0.3 with: script: | cd /app echo "DOCKER_TAG=${{ github.sha }}" > .env docker compose pull docker compose up -d --remove-orphans docker image prune -f
YAML
복사

Portainer

약간의 상용 도구를 찾는다면 Portainer도 살펴볼만 합니다. Portainer는 Docker 환경을 시각적으로 관리해주는 UI 도구입니다. Webhook 기반의 자동 배포 기능이 상용 라이선스에 한해(뒤늦게 알았지만-.-) 내장되어 있습니다. Portainer에서 생성한 webhook URL을 Docker Hub, GitHub Actions, GitLab CI/CD 등의 플랫폼에서 호출하여 사용하는 방식입니다. 새로운 이미지가 푸시되는 등의 이벤트가 발생하면, 해당 플랫폼이 이 webhook URL로 알림(HTTP 요청)을 보냅니다. Portainer는 알림을 받는 즉시 연결된 컨테이너를 최신 이미지로 자동으로 재시작합니다. 직접 docker pull 명령을 실행할 필요 없이, webhook 알림 하나로 CD(Continuous Delivery)를 구축할 수 있습니다.
이 가이드를 응용하여 docker compose를 구성할 수 있습니다. 더불어 저는 개인 도메인을 사용하므로, 이 이슈에 따라 TRUSTED_ORIGINS 환경 변수를 설정하여 로그인할 수 있었습니다.
services: ... portainer: image: portainer/portainer-ce:lts container_name: portainer environment: - TRUSTED_ORIGINS=portainer.mydomain.com,mydomain.com ports: - "9000:8000" - "9443:9443" volumes: - /var/run/docker.sock:/var/run/docker.sock - portainer_data:/data restart: always volumes: portainer_data:
YAML
복사
ce는 커뮤니티 에디션을 의미합니다. ce에서는 이 기능을 제공하지 않습니다.

Watchtower

CD의 완전 자동화를 원한다면 Watchtower(20k+ GitHub stars)가 가장 성숙한 선택입니다. Polling 방식과 webhook 방식(ref1: Webhook과 HTTP API는 기술적으로 동일한 HTTP 요청) 모두 지원합니다.
저는 쉽고 간단한 polling 방식으로 만들고자 하는 것을 구축해 보도록 하겠습니다. Watchtower 컨테이너를 docker-compose.yml 파일에 새 서비스로 추가해야 합니다. Docker 소켓을 마운트하여 Docker 데몬과 통신할 수 있는 권한을 부여받습니다. 그래야 호스트 시스템의 docker, docker compose에 명령을 내릴 수 있겠죠.
services: watchtower: image: containrrr/watchtower volumes: - /var/run/docker.sock:/var/run/docker.sock - ~/.docker/config.json:/config.json
YAML
복사
기본적으로 Watchtower는 모든 실행 중인 컨테이너를 감시합니다. Watchtower에게 모든 컨테이너를 감시하는 대신 특정 컨테이너만 감시하도록 지시하는 것도 가능합니다(ref4).
... command: hegemony-backend # 감시하고자 하는 컨테이너의 이름
YAML
복사
대신 업데이트를 원하는 컨테이너에 com.centurylinklabs.watchtower.enable=true 라벨을 추가하고, WATCHTOWER_LABEL_ENABLE 환경 변수를 true로 설정하는 방식도 많이 사용됩니다.
services: ... hegemony-backend: image: ghcr.io/impact-ps/hegemony-backend:latest container_name: hegemony-backend ... restart: unless-stopped labels: - "com.centurylinklabs.watchtower.enable=true" watchtower: image: containrrr/watchtower container_name: watchtower volumes: - /var/run/docker.sock:/var/run/docker.sock - ~/.docker/config.json:/config.json environment: - WATCHTOWER_LABEL_ENABLE=true
YAML
복사
Private registry를 사용하려는 경우, docker login 명령으로 인증 정보를 ~/.docker/config.json에 추가합니다. ~/.docker/config.json:/config.json를 마운팅한 이유가 바로 이것이죠. GHCR의 경우 docker login ghcr.io -u GITHUB_USERNAME과 같은 방식으로 로그인하여 추가할 수 있습니다. 이때 비밀번호로는 Personal Access Token (PAT)를 사용합니다. read:packages 권한이 필요합니다.
일반적으로는 ~/.docker/config.json에 인증 정보가 저장되나, 저의 경우에는 그렇지 않았습니다. 이런 상황에서는 환경 변수로 인증 정보를 곧바로 주입해 봅시다. github id와 pat token을 넣으면 됩니다. ~/.docker/config.json:/config.json 마운팅도 필요가 없습니다.
services: watchtower: image: containrrr/watchtower container_name: watchtower restart: unless-stopped volumes: - /var/run/docker.sock:/var/run/docker.sock environment: - REPO_USER=your_username - REPO_PASS=your_personal_access_token command: hegemony-backend
YAML
복사
이처럼 Watchtower는 상당히 편리하지만 이미지 빌드 등 예상치 못한 다운타임 위험은 항상 도사리고 있으므로 홈 서버나 개발 환경에만 적합한 솔루션입니다. containrrr/watchtower의 공식 GitHub 저장소는 "상업 또는 프로덕션 환경에서 Watchtower를 사용하는 것을 권장하지 않는다"고 명시적으로 경고합니다(ref3). 더욱 안정적인 환경을 원한다면 Watchtower의 README에서도 언급하듯 상용 서비스를 사용하거나 쿠버네티스를 선택하도록 합시다.

Kubernetess

저는 굉장히 간단한 애플리케이션을 구축하고 있기 때문에 최대한 간단한 방법들을 위주로 살폈습니다. 하지만 앞서 언급했듯, 프로덕트가 커지며 조금의 다운타임이나 배포 지연을 원치 않거나, 애플리케이션이 이미 쿠버네티스 기반으로 작동한다면 ArgoCD, Flux CD 등을 고려해야 합니다. GitHub Actions에서 매니페스트 저장소를 업데이트하면 ArgoCD가 자동으로 동기화하는 패턴이 표준화되었습니다. 실제로 우리 부대도 이런 방식으로 쿠버네티스 애플리케이션을 배포하고 있습니다. Flux CD는 ArgoCD의 경량화된 대안으로, 이미지 자동 업데이트와 정책 기반 배포를 지원하기 때문에, 허들을 조금이라도 낮추고 싶다면 충분히 고려해볼 수 있을 것입니다.
parse me : 언젠가 이 글에 쓰이면 좋을 것 같은 재료을 보관해 두는 영역입니다.
1.
앞의 글에 도커 데몬에 대해 설명되어 있다. 하지만 사실 도커 데몬이 뭐냐가 이론적으로 중요하다기보다는 실제로 도커 소켓 파일과의 연관성 이야기가 앞에 오면 좋을 것 같아서 안 붙이고 있다.
from : 과거의 어떤 원자적 생각이 이 생각을 만들었는지 연결하고 설명합니다.
1.
앞의 글은 Github Action → webhook 기반의 CD에 대해 언급한다.
2.
앞의 글은 Github Action → 폴링 기반의 CD에 대해 언급한다.
supplementary : 어떤 새로운 생각이 이 문서에 작성된 생각을 뒷받침하는지 연결합니다.
1.
None
opposite : 어떤 새로운 생각이 이 문서에 작성된 생각과 대조되는지 연결합니다.
1.
None
to : 이 문서에 작성된 생각이 어떤 생각으로 발전되거나 이어지는지를 작성하는 영역입니다.
1.
None
ref : 생각에 참고한 자료입니다.