정글에서 온 개발자

Docker registry 운영으로 더 편한 배포 본문

구현 혹은 적용

Docker registry 운영으로 더 편한 배포

dev-diver 2025. 4. 15. 16:26

기존 시스템, 개선과 문제

우리회사의 레거시 배포 시스템은 zip 파일을 서버에 전달해 배포하는 방식이였다.
대략 아래와 같다.

1. `build.bat` 파일을 실행하면 ng build 와 go build 를 실행하고, build된 파일에 날짜를 붙여 zip 파일로 만든다.
2. 해당 zip 파일을 배포하고자 하는 머신으로 ftp 전송한다.
3. 옮긴 파일을 unzip 한다 (덮어쓰기)
4. 미리 설정해준 systemctl 을 이용하여 `sudo systemctl restart myservice` 명령어로 재시작한다.

4단계밖에 안 돼 보이지만, 정확한 경로에 복사하고 unzip해야하기 때문에 귀찮아서 개선해보려 한 적이 있다.

2024.11.22 - [구현 혹은 적용] - 웹앱 Delivery 자동화. Github Action - Self Hosted Runner

 

웹앱 Delivery 자동화. Github Action - Self Hosted Runner

도입 이유기존의 local build → zip 파일을 호스팅 서버로 ftp 전송 → 압축 풀기 → systemctl 실행 의 프로세스를 단축하는 배포 자동화를 구축하고자 함버전별 아카이빙 혹은 추후 버전 롤백을 염두

krafton-jungle-essay.tistory.com

쓰다보니 단점이 있었는데, 바로 'rollback' 이 필요한 경우였다.
운영환경에서 방금 배포한 코드에서 에러가 나니, 급하게 최근 버전으로라도 돌려달라는 요청이 있었다.
또 최근 작업은 개발 중간에 배포를 해버리면 데이터 정합성이 크게 깨질 것이 예상되어, staging 서버와 production 서버의 버전을 나눠서 가기도 했다.
github action 에서 branch별로 최신 버전을 자동 배포하도록 할 수도 있었겠지만, 설정이 복잡하고 버전이 좀 더 나눠지는 경우에 대응이 힘들 것으로 보여 다른 방법을 찾기로 했다.

기존 방식 장단점

기존 zip 방식의 장점은, zip한 날짜가 적혀있어서 문제가 생겼을 때 잘 됐던 버전 날짜 압축본만 해제하면 바로 production에 반영이 된다는 점이였다. 
-> git으로 버전관리를 하고 있어서 문제가 되지 않은 시점의 버전만 알면 그때로 commit 해서 다시 배포하는 것도 마찬가지다.

또 다른 장점은, git은 버전이 많아서 배포본이 언제 있었는지 찾기 어려운 반면 zip 파일은 쌓여있는 파일 하나하나가 배포를 나타내기 때문에 알아보기 쉬웠다.
-> git에 tag를 다는 방법으로 해결할 수 있다.

단점은 물론 쓸 데 없는 용량 차지다.

왜 Docker image를 쓰기로 했나?

일단 Docker 명령어가 너무 편하다. docker를 사용하면 두단계로 단축된다.

1. local에서 Docker image build해 registry에 push
2. 배포 환경에서 docker image pull 해 run

docker-compose 파일이 있는 경로로 찾아 들어가는 수고도 bash와 systemctl 조합이면 해결할 수 있다.

그리고 궁극적으로는 kubernetis를 통한 배포 자동화도 생각하고 있었기 때문에 docker 이미지화 시켜놓으면 다음 스텝을 밟기 편할 것으로 보였다.

Docker registry

문제는 개인 프로젝트는 내 아이디로 Docker hub에 등록했는데, 회사에서는 Docker hub id를 따로 파지 않았다는 것이다.
그러나 회사에는 낭낭한 개발 서버가 있었다. docker registry도 docker image로 나와 있어서 쉽게 구성했다.

더보기

docker run -d -p 5000:5000 --restart=always --name registry registry:2

진짜 이렇게만 하면 끝이다. (방화벽 설정 등은 따로 해줘야 함)

그리고 Window에서는 Docker registry를 신뢰해줘야 한다.

배포용 makefile

DOCKER_BUILDKIT=1
REGISTRY=192.XXX.124.67:5000
FRONTEND_IMAGE=$(REGISTRY)/frontend:latest
BACKEND_IMAGE=$(REGISTRY)/backend:latest

.PHONY: help deploy build push build-frontend build-backend push-frontend push-backend
.DEFAULT_GOAL := help

deploy: ## 배포
	make build
	make push

build: ## 빌드
	make build-frontend
	make build-backend

build-frontend: ## 프론트엔드 빌드
	docker build --cache-from $(FRONTEND_IMAGE) -t $(FRONTEND_IMAGE) -f ./web_src/Dockerfile ./web_src

build-backend: ## 백엔드 빌드
	docker build --cache-from $(BACKEND_IMAGE) -t $(BACKEND_IMAGE) -f ./Dockerfile ./

push: ## 푸시
	make push-frontend
	make push-backend

push-frontend: ## 프론트엔드 푸시
	docker push $(FRONTEND_IMAGE)

push-backend: ## 백엔드 푸시
	docker push $(BACKEND_IMAGE)

help: ## 옵션 보기
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
	  | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'

make deploy를 하면 현재 버전이 registry에 build되어 바로 올라간다.

배포되는 서버

docker-compose.yml 을 다음과 같이 image 주소를 잡아주고

version: "3.8"
services:
  frontend-builder:
    image: ${REGISTRY}/frontend:latest
    volumes:
      - shared-dist:/dist
    command: sh -c "cp -r web/* /dist"

  backend:
    image: ${REGISTRY}/backend:latest
    volumes:
      - shared-dist:/app/web
      - ./conf.json:/app/conf.json
      - ./logs:/app/logs
      - ./uploads:/app/uploads
    ports:
      - 8090:8081
    restart: always

volumes:
  shared-dist:

.env 로 REGISTRY를 잡아주면 된다.

REGISTRY=xxx.xxx.xxx.xxx:5000

localhost라면 따로 insecure-registry가 필요 없지만, 다른 서버의 레지스트리를 사용할 경우, 서버에서도 해당 registry에 대한 권한 허용이 필요하다.

linux  insecure-registry

sudo mkdir -p /etc/docker
sudo nano /etc/docker/daemon.json

{
  "insecure-registries": ["192.XXX.XXX.67:5000"]
}

sudo systemctl restart docker  로 한번 재시작해줘야 한다.

더 나아가서 docker pull과 함께 다시 강제로 재생성 up을 하는 다음과 같은 shell script도 작성해줬다.

#!/bin/bash

set -e

echo "🔄 Pulling latest images..."
docker compose pull frontend-builder backend


echo "🚀 Starting services with updated images..."
docker compose up -d --force-recreate frontend-builder backend


echo "✅ Done!"

결과

이제 배포시에 로컬에서 `make deploy`
배포받을 서버에서 `sh deploy.sh` 만 실행하면, 해당 서버에서 최신 이미지로 업데이트가 된다.

production은 더 엄격하게 테스트 코드를 거치는 등의 과정을 거쳐 배포해야겠지만, staging의 경우나 우리 팀처럼 2명이 간단한 기능 수정한 걸 바로바로 올리고 싶을 때는 일반 zip파일을 올리는 것보다 훨씬 빠르게 할 수 있다.

추후 목표

latest 로 버전을 통일하고 있는데, 의미있는 버전의 경우 tag를 따로 달아서 관리하는 게 더 좋을 것 같다.