우당탕탕 무중단 배포 CI/CD

chiyongs·2022년 11월 10일
1

이 글은 프로젝트에서 CI/CD를 구현하면서 겪은 경험을 기록한 글입니다.

배포 자동화 🤖

GitLab에서 master branch에 push event가 발생하면 webhook을 보내고 이를 jenkins가 받아 Backend와 Frontend 코드를 빌드합니다.

Jenkins는 push event를 받아 GitLab 코드를
/home/ubuntu/compose/jenkins/workspace/{설정한 디렉토리 이름}로 가져옵니다.
(위 디렉토리는 Ubuntu를 사용했을 때의 예시입니다)

그래서 해당 빌드파일의 경로,
ex) /home/ubuntu/compose/jenkins/workspace/infomansion-jenkins/backend/build/libs 를 Docker volume을 통해 컨테이너에서 공유해서 사용할 수 있도록 Docker-compose를 작성했습니다.

이로써 배포 자동화를 달성했습니다.

배포 프로세스를 자동화했지만, 한 가지 문제점이 발견되었습니다.
서버가 배포되면서 Docker-compose를 종료하고 다시 시작하는 과정에서 서비스가 중단되는 것이였죠.

이를 해결하기 위해 무중단 배포를 도입했습니다.

무중단 배포 💫

무중단 배포에는 여러가지 방식이 존재합니다.
그 중 저는 블루-그린 방식의 무중단 배포를 선택했습니다.
그 이유로는 블루-그린 방식이 배포속도가 빠르며, 장애가 발생했을 때 로드 밸런서가 기존 서버를 가리키면 되기 때문에 롤백이 쉽다는 점 때문입니다.

당시 React 컨테이너 2개, Spring 컨테이너 2개를 사용하고 앞단에 Nginx로 로드밸런싱과 리버스 프록시 설정을 해두었기 때문에 위와 같은 장점을 누릴 수 있다고 생각했습니다.

블루-그린 무중단 배포 ♻️

블루-그린 무중단 배포를 계획하면서 처음 시도한 방법은 블루용 docker-compose 파일과 그린용 docker-compose 파일을 만드는 것이었습니다.

이렇게 docker-compose.blue.yml, docker-compose.green.yml을 만들었지만 배포 시 라이브 서버에서 어떤 docker-compose를 사용하여 빌드해야 하는지 판단할 수 있어야 합니다.
이렇게 라이브 서버가 현재 블루인지 그린인지 판단하기 위한 쉘 스크립트 파일이 필요합니다.

#!/bin/bash
# Blue 를 기준으로 현재 떠있는 컨테이너를 체크한다.
EXIST_BLUE=$(docker-compose -f docker-compose.blue.yml ps | grep blue)
 
# 컨테이너 스위칭
if [ -z "$EXIST_BLUE" ]; then
    echo "[Up] Blue Up"
    docker-compose -f docker-compose.blue.yml up -d
    BEFORE_COMPOSE_COLOR="green"
    AFTER_COMPOSE_COLOR="blue"
else
    echo "[Up] Green Up"
    docker-compose -f docker-compose.green.yml up -d
    BEFORE_COMPOSE_COLOR="blue"
    AFTER_COMPOSE_COLOR="green"
fi
 
sleep 10

# 새로운 컨테이너가 제대로 떴는지 확인
EXIST_AFTER=$(docker-compose -f docker-compose.${AFTER_COMPOSE_COLOR}.yml ps | grep ${AFTER_COMPOSE_COLOR})
if [ -n "$EXIST_AFTER" ]; then
    # 이전 컨테이너 종료
    sudo cp /etc/nginx/nginx.${AFTER_COMPOSE_COLOR}.conf /etc/nginx/nginx.conf
    sudo nginx -s reload
    docker-compose -f docker-compose.${BEFORE_COMPOSE_COLOR}.yml down
    echo "[Down] $BEFORE_COMPOSE_COLOR Down"
fi

쉘 스크립트를 통해 라이브 서버의 상태가 블루인지 그린인지 판단하여 이에 맞는 docker-compose를 실행할 수 있게 되었습니다.

하지만, 또 하나의 문제가 발생했습니다.(문제의 연속 😢)
Redis를 인증용으로 Docker-compose에 추가하여 사용하고 있었는데 배포를 할 때마다 같은 서비스명(redis)를 가진 Redis 컨테이너가 종료되는 것이었습니다.
이를 해결하기 위해 단순하게 블루용 Redis와 그린용 Redis로 분리해보았지만, 그렇다면 Spring에서 사용하는 yml 설정파일도 2개를 만들어야 했죠.
하지만, 데이터를 다루는 DB가 배포로 인해 꺼졌다 켜졌다 한다면 이는 데이터베이스가 배포 작업에 영향을 받는다고 판단하여 좋지 않다는 결론을 내렸습니다.

그렇게 고민하던 도중, Redis를 아예 블루-그린에서 제외하면 되지 않을까 생각이 들었고 Redis를 독자적으로 띄우는 것으로 해결했습니다.

그 결과는 대성공. 😎
블루와 그린이 잘 번갈아가며 실행이 되었습니다.

하지만, 여기서도 아쉬운 점이 존재했습니다.
바로 진정한 무중단 배포처럼 동작하지 않았기 때문입니다.
제가 원하는 무중단 배포의 모습은 아니였죠.

Jenkins가 빌드한 파일을 Docker volume을 통해 컨테이너들이 공유하는 구조다보니 Jenkins에서 새로운 Push event를 받아 빌드를 다시 할 때 기존에 공유되던 빌드 파일들이 사라져 서버가 잠시 중단되는 것이었습니다.

진정한 무중단 배포를 향해... ✈️

Docker volume을 사용하지 않고 빌드를 진행하기 위해서 Docker hub를 사용하기로 했습니다.
그 이유로는

  • Jenkins와 라이브 서버가 같은 EC2 인스턴스에 존재함
  • Jenkins를 분리하여 Jenkins 플러그인 Publish Over SSH를 사용하려 해도 라이브 서버 EC2의 pem 키의 passphrase를 알지 못해 불가능함

위와 같이 2가지 이유가 있습니다.

따라서, 테스트 성공 후 Docker hub로 올라갈 Docker image를 만들어 푸쉬하고 올린 Docker image를 docker-compose에서 사용하는 방법으로 진행했습니다.

이를 통해 Docker volume을 사용하여 발생하던 서비스 중단 현상을 해결하고 위와 같은 CI/CD를 구현했습니다 👍

0개의 댓글