개발놀이터

Docker + Nginx 를 이용해 스프링 프로젝트 무중단 배포하기 : 방법 본문

배포/Docker

Docker + Nginx 를 이용해 스프링 프로젝트 무중단 배포하기 : 방법

마늘냄새폴폴 2023. 6. 12. 01:24

이번 포스팅에선 Docker + Nginx를 이용해 무중단 배포에 대해 포스팅해보도록 하겠습니다. 

 

무중단 배포는 CI / CD를 통틀어 웹 서비스에 모두 적용되어 있는 가장 기본이라고 할 수 있습니다. 도커 컨테이너를 띄워 보신 분들은 아시겠지만 도커 컨테이너를 내렸다가 다시 올리는 과정이 길게는 1분정도 걸립니다. 

 

그 때 우리의 애플리케이션을 이용하고 있는 사용자가 있다면 갑자기 서비스를 이용할 수 없는 상황이 될겁니다. 만약 회원수가 정말 많아서 1분에 결제가 수백 수천만원이 왔다갔다하는 상황이라면 회사 입장에서 당연히 꺼려질 수 밖에 없습니다. 

 

그래서 이번엔  Docker 와 Nginx를 이용해 무중단 배포에 대해서 알아봅니다!

 

무중단 배포

 

무중단 배포에는 두가지 배포가 있습니다. 

 

  • Rolling update 배포 : 새로 배포되어야 하는 버전을 하나씩 순차적으로 적용시키면서 배포하는 방식입니다. 한 번에 모두 배포되는게 아니기 때문에 배포가 되는 과정에서 옛날 버전과 새로운 버전이 공존합니다. 그렇기 때문에 잘못하면 배포하는 과정 중 호환성 문제가 발생할 수 있습니다. 
  • Blue / Green 배포 : Blue 혹은 Green 버전 둘 중 하나로 배포되어 있는 상태에서 새로운 버전을 동시에 띄우고 로드 밸런서를 통해서 스위칭하는 방식이며, 한 번에 두 개의 버전을 동시에 띄우기 때문에 시스템 자원이 두배로 든다는 단점이 있습니다. 

이 배포 말고도 Canary 배포도 있는데 일부에게만 새로운 버전을 배포하고 테스트하는 배포라고 합니다. 제가 생각하는 무중단 배포와 다르기 때문에 소개해드리진 않습니다. 

 

블루 그린 배포에 대해서 이미지로 도식화하면 다음과 같습니다. 

 

8080 포트 그린으로 배포되어있는 상태
8081 포트의 블루 버전 컨테이너 띄워두기
로드 밸런서 (Nginx) 로 블루 버전 배포
8080 포트 그린버전 배포 중단

 

개념은 정말 간단하죠?

 

새로 배포할 때마다 새로운 컨테이너를 띄우고 Nginx 연결을 새로 띄운 컨테이너 포트로 연결한 뒤 이전 포트의 컨테이너는 버립니다. 

 

이제 본격적으로 과정에 대해서 알아볼까요? 순서는 다음과 같이 진행됩니다. 이번 포스팅역시 EC2 인스턴스가 존재한다고 가정하고 시작합니다. 

 

  1. docker, docker-compose 설치
  2. docker hub 리포지토리 생성
  3. docker-compose-green.yml / docker-compose-blue.yml 생성
  4. deploy.sh 작성
  5. nginx-green.conf / nginx-blue.conf 작성
  6. deploy.sh 실행

 

순서대로 알아보죠!

 

1. docker, docker-compose 설치

$ apt-get update
$ sudo apt-get install docker
$ sudo apt-get install docker-compose

 

2. docker hub 리포지토리 생성

도커 허브는 아래의 링크에서 자세히 확인할 수 있습니다. 어떻게 만드는지에 대해서는 포스팅하지 않습니다. 

 

https://coding-review.tistory.com/407

 

Docker Hub

이번 포스팅에선 Docker Hub (이하 도커 허브) 리포지토리를 만들고 사용하는 방법에 대해서 알아보도록 하겠습니다. 도커 허브는 도커 생태계에서 이미지를 따로 보관할 수 있는 저장소와 같은 역

coding-review.tistory.com

 

3. docker-compose 작성

먼저 블루부터 만들어보겠습니다. 

 

version: "3.8"
services:
  api:
    image: docker.io/garlicpollpoll/capston:1
    container_name: garlicpollpoll-capston-blue
    environment:
      - LANG=ko_KR.UTF-8
      - UWSGI_PORT=8080
    ports:
      - "8080:8080"

 

이미지는 도커 허브에서 이미지를 가져와서 컨테이너로 띄울겁니다. 하나씩 코드를 설명해보겠습니다. 

 

image : docker.io/${도커허브 계정명}/${도커허브 리포지토리명}:${태그명} 도커 허브에서 이미지를 가져오겠다는 의미입니다. 

container_name : 컨테이너 이름을 블루 버전이라고 명시해줍니다. 

environment : UTF-8의 한국어를 사용하겠다고 적어줍니다. 

ports : 블루의 포트는 8080입니다. 라고 컨테이너를 만들 때의 속성으로 적어줍니다. 

 

다음 그린을 만들어보죠.

version: "3.8"

services:
  api:
    image: docker.io/garlicpollpoll/capston:2
    container_name: garlicpollpoll-capston-green
    environment:
      - LAN=ko_KR.UTF-8
      - UWSGI_PORT=8081
    ports:
      - "8081:8080"

달라진건 blue를 green으로 바꾸고 포트를 8081로 바꾼것입니다. 

 

4. nginx.conf 작성

이제 nginx의 설정파일을 생성해보죠. 

 

블루부터 보겠습니다. 

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
        worker_connections 768;
}

http {
        upstream backend {
                server 43.201.47.110:8080; # blue
        }

        access_log /var/log/nginx/access.log;

        server {
                listen 80;

                location / {
                        include /etc/nginx/uwsgi_params;
                        proxy_pass http://backend;
                }
        }
}

블루 버전입니다. 문법은 대충 보면 느낌이 오실겁니다. 

 

그리고 그린 버전입니다. 

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
        worker_connections 768;
}

http {
        upstream backend {
                server 43.201.47.110:8081; # green
        }

        access_log /var/log/nginx/access.log;

        server {
                listen 80;

                location / {
                        include /etc/nginx/uwsgi_params;
                        proxy_pass http://backend;
                }
        }
}

그린 버전은 포트만 다르고 나머지는 똑같습니다. 

 

5. deploy.sh 작성

이제 알파이자 오메가인 deploy.sh 를 적어봅시다. 

#!/bin/bash

DOCKER_APP_NAME='garlicpollpoll-capston'

EXIST_BLUE=$(docker-compose -p ${DOCKER_APP_NAME}-blue -f ./docker-compose-blue.yml ps | grep Up)

if [ -z "$EXIST_BLUE" ]; then
        echo "blue up"
        docker-compose -p ${DOCKER_APP_NAME}-blue -f ./docker-compose-blue.yml pull
        docker-compose -p ${DOCKER_APP_NAME}-blue -f ./docker-compose-blue.yml up -d
        BEFORE_COMPOSE_COLOR="green"
        AFTER_COMPOSE_COLOR="blue"
else
        echo "green up"
        docker-compose -p ${DOCKER_APP_NAME}-green -f ./docker-compose-green.yml pull
        docker-compose -p ${DOCKER_APP_NAME}-green -f ./docker-compose-green.yml up -d
        BEFORE_COMPOSE_COLOR="blue"
        AFTER_COMPOSE_COLOR="green"
fi

sleep 10

EXIST_AFTER=$(docker-compose -p ${DOCKER_APP_NAME}-${AFTER_COMPOSE_COLOR} -f ./docker-compose-${AFTER_COMPOSE_COLOR}.yml ps | grep Up)
if [ -n "$EXIST_AFTER" ]; then
        cp ./nginx-${AFTER_COMPOSE_COLOR}.conf ./nginx.conf
        nginx -s reload

        docker-compose -p ${DOCKER_APP_NAME}-${BEFORE_COMPOSE_COLOR} -f ./docker-compose-$BEFORE_COMPOSE_COLOR.yml down
        echo "$BEFORE_COMPOSE_COLOR down"
else
        echo "Failed to start $AFTER_COMPOSE_COLOR"
fi

좀 깁니다. 오타가 나지 않게 잘 작성해주세요!

 

자세한 내용은 차후에 쉘 스크립트 문법에서 설명하도록 하겠습니다. 간단하게 흐름만 알아보도록 하죠. 

 

  1. 먼저 blue버전의 컨테이너가 있는지 확인합니다. 
  2. 도커 허브를 풀링해서 이미지를 업데이트하고 블루가 없으면 블루를 띄우고 있으면 그린을 띄웁니다.
  3. 새로운 컨테이너가 제대로 떴으면
  4. 그 전 컨테이너를 지웁니다. 

 

이제 deploy.sh를 실행해줘야 하는데... 권한이 없다고 뜰겁니다. 

 

그땐 쿨하게 파워권한을 주면 됩니다. 

 

$ sudo chmod 777 ./deploy.sh

 

그리고 실행해줍니다. 

$ ./deploy.sh

로그를 잘 보시면 그린버전을 올리고 블루 버전을 멈춘뒤 블루 버전을 삭제하는 것을 볼 수 있습니다. 

 

그리고 컨테이너를 확인해보면?

 

$ docker ps

 

잘 안보이는데... 8081로 잘 업로드 된 것을 볼 수 있습니다. 

 

 

마치며

처음으로 무중단 배포를 진행했는데 정말 신기하고 새롭네요. 실제로 위와 같은 방법으로 무중단 배포를 진행하시면 느끼시겠지만 여간 귀찮은게 아닙니다. 

 

그것에 대한 포스팅은 추후에 자세하게 적도록 하겠습니다. 

 

그래서 Jenkins와 같은 툴을 이용해서 배포 파이프라인을 만드는걸로 알고있습니다. 이것도 추후에 공부해서 포스팅해보도록 하겠습니다. 

 

긴 글 읽어주셔서 감사합니다. 오늘도 즐거운 하루 되세요~