전환 이유
레퍼런스들을 찾아보면, 대부분이 PM2를 통해 간편하게 인스턴스를 띄워 사용하다가, 컨테이너 단위로 전환하고 노드 환경에서는 추가로 pm2-runtime을 도커 컨테이너 내부에서 적용하는 경우를 많이 봤던 것 같다.
나는 반대로, 현재 운영중인 서비스를 계속해서 개발하면서 겪은 문제들을 개선하기 위해 배포 파이프라인을 Git Actions - Docker에서 Git Actions - PM2로 전환하였다.
1. 긴 배포 시간 (길면 3~5분, 평균 1분 후반대)
2. 간헐적인 서비스 장애 탐지의 위치 이동
서버와 백엔드 코드를 혼자 관리하다 보니 빈번하게 코드를 업데이트해야 한다. 테스트 같은 안전 장치의 통과를 위해 소요되는 시간은 언제나 수긍하지만, Docker 프로세싱의 긴 소요 시간은 점차 불쾌하게 다가왔다. 얕은 지식과 레퍼런스들을 바탕으로 이미지 최적화를 수행해도 1~2분은 기본적으로 걸렸다. 메인 브렌치에 코드를 하루에도 많게는 십 수번 씩 반영해야하는 환경이라 배포 속도가 중요하게 다가왔다.
또한 처음 입사 후에 간헐적으로 배포 후에 발생되는 서비스 장애를 방지하기 위해 코드 통합 이후 health check를 추가했다. 테스트 코드를 조금씩 적용해나가면서, 현재의 환경에서 굳이 배포 사이클에 테스트를 돌릴 필요가 없다고 판단이 되었다. 그래서 lint를 포함한 테스트와 관련된 사이클은 pre-commit 단계에서 수행할 수 있도록 husky를 도입했다.
이러한 문제들을 개선하기 위해 기존 사용하던 Docker Compose를 통한 BLUE / GREEN 배포 대신 PM2와 심볼릭 링크를 활용해 새로운 배포 방식으로 변경하게 되었다.
새 배포 프로세스 구조
BLUE / GREEN 배포
아래의 스크립트는, 같은 BLUE / GREEN 방식으로 current 심볼릭 링크가 가리키는 폴더를 기준으로 반대편 폴더에 새로운 코드를 배포한 후 심볼릭 링크를 전환하는 구조로 작성했다. PM2 인스턴스는 current 심볼릭 링크를 통해 해당 디렉토리의 코드만을 실행하게 된다.
name: iwedding_api_v2_deploy
on:
push:
branches:
- 'main'
jobs:
SSH_DEPLOY:
if: contains(github.event.head_commit.message, 'skip') != true
runs-on: [self-hosted, api_v2_action]
steps:
- name: SSH scripts
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.IWD_HOST }}
username: ${{ secrets.IWD_USERNAME }}
password: ${{ secrets.IWD_PASSWORD }}
script: |
echo "=========================================================="
echo "🤝 Integration: Pull latest code and install dependencies"
echo "=========================================================="
source /home/svc/.nvm/nvm.sh
cd /home/svc/api/v2
nvm use # .nvmrc
echo "============================================="
echo "🔎 Determine Target for Blue/Green Deployment"
echo "============================================="
if ls -l /home/svc/api/v2/current | grep -q 'blue'; then
TARGET_FOLDER="green"
else
TARGET_FOLDER="blue"
fi
echo "========================================="
echo "📁 Pulling latest code for $TARGET_FOLDER"
echo "========================================="
cd /home/nodesvc/api/v2/$TARGET_FOLDER
git pull origin main
yarn install
echo "========================================"
echo "🛠️ Compile application in $TARGET_FOLDER"
echo "========================================"
yarn build
# 테스트를 main branch에서도 한 번 더 실행해야 할지 고민중
# echo "=================="
# echo "✅ Test: Run tests"
# echo "=================="
# yarn test
echo "============================================"
echo "🚀 Switching symbolic link to $TARGET_FOLDER"
echo "============================================"
ln -sfn /home/svc/api/v2/$TARGET_FOLDER /home/nodesvc/iwedding-api/v2/current
echo "==================================="
echo "🚀 Restarting application with PM2"
echo "==================================="
pm2 restart /home/svc/api/v2/current/ecosystem.config.js
기존의 도커 방식은 블루 인스턴스와 그린 인스턴스에 포트를 서로 다르게 부여한 후 리버스 프록시에서 proxy_pass로 최신 버전의 인스턴스를 읽는 방식이다. 오래된 인스턴스는 백업 컨테이너로 copy된 후 삭제된다.
이러한 일련의 과정들을 모두 간소화하여 배포 시간은 30초 대로 단축되었고, 기존의 BLUE / GREEN에 따른 관련 설정들을 따로 독립적으로 구성하고 관리할 필요가 없게 되었다.
로그 관리
로그 관리를 위해, PLG를 프로덕션에 적용해놓은 상태이다. (관련 포스팅들)
Docker 환경에서는, 볼륨 마운트로 로그를 수집했지만, PM2로 전환하면서 심볼릭 링크가 변동이 되기 때문에 인스턴스에서 current 디렉토리가 아닌 특정 경로에 고정된 방식으로 로그를 내보내게 되었다. Promtail에서 해당 경로만 지정해준다면, 문제 없이 기존처럼 로그를 용이하게 수집하고 관리할 수 있다.
// winston settings
const dailyOption = (level: string) => {
const directory =
process.env.NODE_ENV === 'production' ? `/home/svc/api/v2/logs/${level}`
: `logs/${level}`;
return {
level,
filename: `%DATE%.${level}.log`,
datePattern: 'YYYY-MM-DD',
dirname: directory,
maxSize: '20m',
zippedArchive: true,
maxFiles: 30,
};
};
RESTORE:: 백업 인스턴스로 급 전환
급하게 반영된 프로덕션의 코드가 잘못되어 롤백해야할 때, restore을 사용해서 도커 컨테이너의 포트를 바꿔버리는 스크립트를 사용했다.
기존 액션 스크립트의 심볼릭 링크를 확인하는 명령어를 기반으로, 자바스크립트 코드로 심볼릭 링크를 교체해주면 기존의 restore와 동일하게 사용할 수 있었다.
당연한 말이지만, 내부 IP 혹은 내 노트북과 같은 특정 IP에서만 접근할 수 있도록 방화벽 설정을 호스트 서버 구성해두었다.
exec(`ln -sfn /home/svc/api/v2/${newDir} /home/svc/api/v2/current`, (switchError, switchStdout, switchStderr) => {
if (switchError) {
console.error(`스위치 오류: ${switchError.message}`);
res.end('스위칭 중 오류가 발생했습니다.');
return;
}
res.end(`스위칭 성공: ${newDir} 디렉토리로 전환되었습니다.`);
});
정리
기존 배포 프로세스(위)에서, 현재의 방향(아래)으로 교체한 뒤, 가장 큰 문제였던 배포 시간을 2분 내외에서 30초 내외로 크게 단축시킬 수 있었다. 메인 브렌치에 마구잡이로 핫픽스를 하면 안되겠지만, 현재의 조직 특성상 맞는 방향으로 배포 파이프라인을 교체하였고, 바꾼지 이틀 되었는데 이미 크게 체감이 되는 모양이다. 본인을 포함하여, 다른 구성원들까지 빨리 반영된다고 좋아하는 것 같다.
정답은 없지만 오답은 있다는 말처럼, 오답이 아닐까 노심초사 전환기를 남겨둔다. 현재 상황에 맞는 여러 방법 중 배포 시간을 줄이자!!! 라는 방향만 본 극단적인 선택일 수 있지만, 나중에 내가 다시 봤을 때 최적의 선택은 무엇이었을까? 하고 돌아볼 수 있지 않을까..
글 마무리가 조금 이상한데,,, 여튼 배포 시간을 3~4배 단축시킨 것에 현재는 만족한다.
2023.04 ~ 백엔드 개발자의 기록
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!