[pm2-logrotate]PM2 로그 관리를 통한 서버 메모리 이슈 해결

Olivia·2025년 4월 2일
1

[Trouble Shooting👾]

목록 보기
4/5

배경

현재 운영 중인 프로젝트는 Kafka Consumer로 구성되어 있어, 다른 회사(Producer)로 부터 주기적으로 메세지를 전달받고 있다.

꽤나 방대한 양의 메세지를 받는 중이다🧚‍♀️✨
현재 서버 마이그레이션 작업이 진행 중이라 모니터링하는 과정에서 서버의 메모리 이슈가 발생했다.
꽤나 중요한 문제였기때문에 그동안 삽질하면서 해결했던 과정에 대해서 정리하려고 한다.

.pm2 로그 관리를 통한 서버 메모리 이슈 해결 내용이 필요한 분들은 가장 하단으로 이동해주길 바란다.

기타 환경

node.js 애플리케이션의 상용 프로세스를 관리하는 도구로 현 프로젝트는 pm2를 사용하고 있다.
pm2를 이용해서 애플리케이션이 종료되지 않도록 유지시켜주며, 다운타임 없이 재시작할 수 있게 된다.


🚨 문제 상황 1: git pull or build이슈

💡 현상

테스트 중 소스 코드 변경이 필요해서 WAS 서버에서 git pull을 시도했으나, 서버 용량 부족으로 인해 git pull이 되지 않았다.

git pull로 당겨서 업데이트 된 소스코드를 받았지만, build가 안되는 문제 또한 발생했다.

$ npm run build
npm ERR! code ENOSPC
npm ERR! syscall write
npm ERR! errno -28
npm ERR! nospc ENOSPC: no space left on device, write
npm ERR! nospc There appears to be insufficient space on your system to finish.
npm ERR! nospc Clear up some disk space and try again.

npm ERR! A complete log of this run can be found in:
npm ERR!    .../.log

🛠️ 조치

급하게 소스 코드를 업데이트해서 빌드해야했으므로 임시 방편으로 임시 파일들을 지우는 방법으로 조치하고 작업을 수행했다.

급하게 처리한 것이라 정리가 안되었다.
순서가 바뀔 수 있을텐데 그래도 필요한 분이 있다면 참고가 되었으면 하는 마음에 사용했던 방법들을 아래에 정리해놨다.

rm -rf .git/objects/pack/*.pack
rm -rf .git/objects/pack/*.idx

rm -f .git/objects/pack/._pack-*
rm -rf .git
git init
git remote add origin https://github.com/was.git
git pull
git remote show origin

git branch
git branch --set-upstream-to=origin/main master
git pull
git checkout -b main origin/main
// 문제 발생

$ git branch --set-upstream-to=origin/main master
fatal: branch 'master' does not exist

$ git pull origin main
From https://github.com/was
 * branch            main       -> FETCH_HEAD
error: Untracked working tree file '.dockerignore' would be overwritten by merge.
git clean -f .dockerignore
git pull origin main
git status
git diff origin/main
git branch -avv 
git remote -v 
git branch -m master main 
git branch --set-upstream-to=origin/main main 
git ls-remote origin
git remote show origin
git fetch origin --prune 
git reset HEAD .
git checkout .
git fetch --all
git reset --hard origin/main

이후에는 git pull과 build를 잘해서 새로운 소스코드를 서버에 잘 반영했다.

🚨 문제 상황 2: Kafka 메세지 수신 이슈

💡 현상

Kafka Broker IP를 변경 후 TO-BE Kafka 트랜잭선이 보이지 않는 문제가 발생했다.
WAS log를 모니터링하면 JS stacktrace 에러가 발생한 뒤에 더 이상 Kafka 메세지를 받아오지 못했다.

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory

해당 문제는 JavaScript heap out of memory로 heap 메모리가 부족해서 발생한 것이다.

🛠️ 1 차 조치 시도: Cron 작업 주기 조정

Cron을 통해서 작업해야했는데 테스트 때문에 Cron 주기를 너무 빈번하게 했다.
cron 작업은 새로운 프로세스를 생성하고 메모리를 할당받기 때문에 너무 빈번한 실행은 결국 메모리 부족을 발생시킬 수 있다.
이에 따라 cron 주기를 한달에 한 번으로 늘렸다.

해결되는 듯했으나, 일시적이였고 곧 메모리 할당 문제로 동일한 현상이 재발했다.


🛠️ 2 차 조치 시도: 로그 최적화

Kafka 메세지가 들어올 때마다 로그가 너무 빈번하게 기록되는 문제를 발견했다.
로그가 과도하게 쌓일 경우, 결국 디스크 공간이 부족할 수 있기 때문에, Kafka 메세지에 대해 한 번만 로그를 기록하도록 수정했다.

물론 어느정도 해결은 되었으나, 동일한 메모리 문제가 발생했다.


🛠️ 3 차 조치 시도: 메모리 추가 할당

다시 에러를 확인했는데, Heap 메모리가 너무 부족한 것이 계속 눈에 밟혔다.
현재 전체 용량이 어느정도인지 확인했다.

$ node -e 'console.log(v8.getHeapStatistics().heap_size_limit/(1024*1024))'

// 2096

2096이니까 약 2배전도 용량을 늘려 메모리 할당을 증가시켰다.

용량을 늘리는 것은 간단하다. ecosystem.config에서 하당의 코드를 그대로 추가했다.

module.exports = {
	apps: [
    	{
        	name: 'nest',
            script: './dist/main.js',
            node_args:  "--max-old-space-size=4096",,
            ...
        }
    ]
}

한 동안 에러 없이 Kafka 메세지가 제대로 들어오는 것을 확인했다.


🛠️ 4 차 조치 시도: DB 쿼리 최적화

계속 되는 용량 문제때문에 분석하던 중, Kafka 메세지 수신 시 비정상적으로 많은 DB쿼리가 실행되는 것을 발견했다.
문제가 된 쿼리들은 아래와 같았다.

1. 레거시 코드에 남아있는 미사용 쿼리들
2. 불 필요한 중복 조회 쿼리들 
3. 최적화 되지 않은 쿼리문 

위의 쿼리들을 제거하자 Kafka 메세지가 정상적으로 수신되었다.
하지만, 이것 역시 임시적인 해결책에 불과했다.


🛠️ 5 차 조치 시도: 근본 원인 발견

이틀 후 또 다시 메세지가 중단되어 디스크 사용량을 확인해봤다.

$ df -h

확인 결과 .pm2 디렉토리의 로그 파일이 과도하게 누적되어 있었다.

장기간 운영하던 프로젝트이기 때문에, 급격히 늘어난 Kafka 메세지 양으로 인해 로그가 지속적으로 축척되었다.

따라서 로그 파일을 백업한 후 삭제하자, 지금까지도 Kafka 메세지가 안정적으로 수신되는 것을 확인했다.

또 문제가 발생하면 '6차 시도'에 관해 글을 업데이트하겠지만, 현재까지는 로그 파일이 위의 모든 에러들의 근본적인 원인임을 파악하고,
로그 파일을 관리하는 것을 아래에 작성할 것이다.


pm2 로그 파일

기본적으로 pm2는 로그를 /home/user/.pm2에 log 폴더가 존재한다.

// .pm2/logs

$ ls
nest-error.log  nest-out.log

특별한 설정을 해두지 않았기 때문에, nest-error.lognest-out.log에 로그가 쌓여있는 것을 확인할 수 있다.

이미 로그를 한 번 삭제했기 때문에, 현재 많은 데이터가 쌓여있지 않지만, 분명 며칠이 지나면 또 가득 찰 것으로 예상된다.

[user@ip logs]$ ls
nest-error.log  nest-out.log

[user@ip logs]$ ls -alh
total 5.4G
drwxr-xr-x. 2 user user 4.0K Dec 18 13:37 .
drwxr-xr-x. 5 user user 4.0K Dec 19 11:16 ..
-rw-r--r--. 1 user user 4.7G Dec 20 06:55 nest-error.log
-rw-r--r--. 1 user user 643M Dec 20 06:55 nest-out.log

PM2 logrotate 모듈

앞서 지금까지 상황을 미루어보아 위의 로그 파일들은 현 프로젝트가 계속 운영될수록 점점 더 쌓여서 또 서버 용량을 가득 차게 만들 것이다.

그럼 분명 디스크 용량이 부족하게되면서 git, build, kafka 메세지 수신등 많은 문제를 일으킬 수 있다.

이를 위해 pm2 로그 파일을 관리하는 pm2 logrotate모듈을 사용해보기로 했다.

PM2 logrotate 모듈이란?

pm2를 실행할때마다 로그 경로에 저장되는 로그들은 직접 삭제해주지 않는다면, 계속 쌓이게 된다. 이 모듈을 설치하게 되면 로그 자동 삭제, 파일 리사이즈 등등 다양한 로그 관리 기능을 이용할 수 있게 된다.

PM2 logrotate 설치 방법

pm2 logrotate 파일은 아래 명령어를 통해 설치할 수 있다.

pm2 install pm2-logrotate

PM2 logrotate 옵션

pm2 conf

pm2 conf를 통해 logrotate에 기본적으로 설정된 옵션을 확인할 수 있다.

  • max_size: 각각의 로그파일의 최대 크기. 해당 용량이 넘어가면 다음 파일을 생성하게 된다.(기본값 10M)
  • retain: 로그 저장 갯수 (기본값 30개)
  • compress: 생성된 로그를 gzip으로 압축 유무 (기본값 false)
  • dataFormat: 로그 파일 뒤에 날짜 포맷 (기본값 YYYY-MM-DD_HH-mm-ss)
  • workerInterval: 로그 파일 사이즈를 확인하는 1초마다의 횟수. (기본값 초당 30회)
  • rotateInterval: cronjob (기본값 0 0 * * -> 매일 정각)
  • rotateModule: 새로운 로그 파일 생성 여부 (기본값 true)

기본 설정을 보면, 로그가 10MB 넘길때마다 새로운 로그 파일이 생성되며, 30개 이상 생성되지 않도록 한다. => 300MB 까지만 로그가 기록된다.

현재 꽤나 용량이 크기 때문에 설정을 아래와 같이 바꿨다.

pm2 set pm2-logrotate:rotateInterval "0 0 * * *"
pm2 set pm2-logrotate:compress true
pm2 set pm2-logrotate:retain 30
pm2 set pm2-logrotate:dateFormat YYYY-MM-DD
pm2 set pm2-logrotate:workerInterval 30
pm2 set pm2-logrotate:max_size 2000M

잘 설정되었는지 확인하려면 pm2 conf로 확인할 수 있다.

이렇게 되면 자동으로 ./pm2/logs폴더 안에 nest-out.log, nest-error.log파일들 뒤에 날짜가 붙은채로 압축된 파일이 매일 하나씩 생성될 것이다.

(현재 테스트 중)

이렇게되면 가독성이 없다고 판단해서 폴더를 따로 만들어서 거기다가 압축파일을 옮기고 싶었는데, 하루종일 삽질한 결과 pm2-logrotate에는 그런 기능은 없는듯하다..


🤯 삽질의 기록

궁금한 사람들을 위한 내 삽질 기록..

나는 댕멍청하게 config파일에 error_fileout_file을 설정하면 될것이라고 판단했다.

module.exports = {
  apps: [
    {
      name: 'nest',
      script: './dist/main.js',
      cwd: '/home/user/was',
      watch: true,
      log_date_format: 'YYYY-MM-DD',
      error_file: '/home/user/was/.pm2/logs/logError/nest-error.log',
      out_file: '/home/user/was/.pm2/logs/logOut/nest-out.log',
      env: {
      		...
            

이렇게 설정해두고서 로그를 찍어봤는데, pm2 logs nest 0을 하면 실시간으로 로그를 확인할 수 있는데,
.pm2/logs/폴더에서는 로그가 저장되지 않았다.

이미 .pm2/logs폴더 안에 nest-out.lognest-error.log가 존재하기 때문에 해당 파일을 찾지 못해서 발생하는 문제인 듯 하다.

그래서 해당 설정을 지우니까 다시 log가 잘 저장되었다.

(왜 글이 사라졌냐... 왜....)

지금 정리하면서 든 생각은 이미 있는 nest-out.log와 nest-error.log의 파일 위치를 각각 logOut과 logError 폴더로 이동시키면 되지 않을까..? 라는 생각 ??
출근해서 테스트 해보고 다시 수정해보겠다. =>cron과 script파일로 수정할 예정.


💡cron & script 설정

테스트해본 결과 pm2-logrotate-out___2024.12.22.log.gz 이런 식으로 pm2-logrotate 로그파일이 계속 생성되었다.
이렇게 될 경우, 또 로그파일이 쌓이면서 디스크 용량이 더 커질 것으로 생각되었다.
따라서 해당 로그파일은 이틀에 한 번씩 삭제하는 걸로 수정하기로 했다.
또한, nest-errornest-out 파일이 압축되면서 디렉토리 구조가 너무 지저분하게 관리되는 문제가 있었다.
한 두개의 압축파일은 괜찮겠지만, 한 달동안 둬야할 경우에는 분명 파일 찾아보는 것이 힘들 것이라 판단했다.
이 경우에도 script로 관리하는 것으로 해결했다.

🛠️ script 설정 - pm2-logrotate-out___log.gz 파일 삭제

우선, script 파일들을 저장할 script폴더를 만들었다.

cd .pm2/logs
mkdir -p scripts

이후 pm2-logrotate와 관련된 로그를 지우는 스크립트를 작성했다.

error는 당분간 모니터링을 위해서 지우지 않는 것으로 했다.

vi clean_logrotate_log.sh
#!/bin/bash

LOG_PATH="/home/user/.pm2/logs"

find "$LOG_PATH" -type f -name "pm2-logrotate-out__*.log.gz" -mtime +2 -exec rm -f {} \;

esc -> :wq!로 저장.

만약 스크립트가 잘 작동되는지 확인하려면, ./clean_logrotate_log.sh를 입력해서 해당 로그파일들이 삭제되는지 확인하면 된다.

삭제가 잘 된다면, 스크립트가 실행될 수 있도록 권한을 부여한다.

chmod +x /home/user/.pm2/logs/scripts/clean_logrotate_logs.sh

🛠️ script 설정 - nest-out & nest-error 파일 이동

.pm2/logs/script폴더 안에 스크립트 파일 생성

우선, logs 폴더 안에 errorLog와 outLog 폴더를 생성한다.

mkdir -p errorLog
mkdir -p outLog

./pm2/logs/script폴더로 이동해서 move_nest_logs.sh스크립트를 생성한다.

vi move_nest_logs.sh
#!/bin/bash

SOURCE_DIR="/home/user/.pm2/logs"
ERR_DEST="$SOURCE_DIR/errorLog"
OUT_DEST="$SOURCE_DIR/outLog"

find "$SOURCE_DIR" -type f -name "nest-error__*.log.gz" -exec mv {} "$ERR_DEST" \;

find "$SOURCE_DIR" -type f -name "nest-out__*.log.gz" -exec mv {} "$OUT_DEST" \;

🛠️ cron 설정

매일 자정 위에서 만든 스크립트가 실행되도록 cron을 설정한다.

대신 로그가 찍히는 것을 막기 위해서 /dev/null 2>&1 를 설정해줬다.

crontab -e
0 0 * * * /home/user/.pm2/logs/scripts/clean_logoutrotate_logs.sh > /dev/null 2>&1 && /home/user/.pm2/logs/scripts/move_nest_logs.sh > /dev/null 2>&1

🚨 pm2 install pm2-logroate 설치 에러

npm 네트워크 문제로 설치가 안될 경우

[user@user ~]$ pm2 install pm2-logrotate
[PM2][Module] Installing NPM pm2-logrotate module
[PM2][Module] Calling [NPM] to install pm2-logrotate ...
npm ERR! code ECONNRESET
npm ERR! syscall read
npm ERR! errno ECONNRESET
npm ERR! network request to https://registry.npmjs.org/pm2-logrotate failed, reason: read ECONNRESET
npm ERR! network This is a problem related to network connectivity.
npm ERR! network In most cases you are behind a proxy or have bad network settings.
npm ERR! network
npm ERR! network If you are behind a proxy, please make sure that the
npm ERR! network 'proxy' config is set properly.  See: 'npm help config'

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/user/.npm/_logs/2024-12-23T07_14_47_792Z-debug-0.log
[PM2][ERROR] Installation failed via NPM, module has been restored to prev version

자꾸 pm2-logrotate를 하는 과정에서 에러가 발생했다.
그래서 직접 수동으로 pm2-logrotate소스코드를 직접 다운받기로 했다.

git clone https://github.com/pm2-hive/pm2-logrotate.git

이 경우, npm 으로 설치할때 글로벌로 설치해야한다.

# sudo로 글로벌 설치
sudo npm install -g

# 또는 현재 위치에서 pm2에 직접 설치
cd pm2-logrotate
pm2 install .

나의 경우, pm2-logrotate 폴더에서 직접 pm2에 설치했다.

설치 후 pm2 conf를 했을 경우, default로 설정되어있는 것들이 설치가 안되어있어서 다시 설치를 해줬다.

pm2 set pm2-logrotate:rotateInterval "0 0 * * *"
pm2 set pm2-logrotate:compress true
pm2 set pm2-logrotate:retain 30
pm2 set pm2-logrotate:dateFormat YYYY-MM-DD
pm2 set pm2-logrotate:workerInterval 30
pm2 set pm2-logrotate:max_size 2000M
pm2 set pm2-logrotate:rotateModule true

안됨. 망할 방화벽..,
안되어서... 쉘 스크립트 또 만들었음..

#!/bin/bash

LOG_DIR="/home/user/.pm2/logs"
RETAIN_DAYS=30
DATE_FMT=$(date '+%Y-%m-%d')


TARGET_FILES=("name-out.log" "name-error.log")

for logfile in "${TARGET_FILES[@]}"
do
  FULLPATH="$LOG_DIR/$logfile"

  if [ -f "$FULLPATH" ]; then
    base_name="${logfile%.log}"
    new_name="${base_name}__${DATE_FMT}.log"

    mv "$FULLPATH" "$LOG_DIR/$new_name"

    gzip -f "$LOG_DIR/$new_name"

  fi
done

pm2 reloadLogs

find "$LOG_DIR" -type f -name "*.gz" -mtime +$RETAIN_DAYS -exec rm -f {} \;

이렇게 만들고 이 파일도 crontab에 추가해주면 된다.


🧚‍♀️ 결론:

pm2-logrotate를 사용하니까 또 pm2.log가 쌓여가서..
결국은 모든 서버에 scrtips, cron으로 셋팅하고 log-rotate를 제거했다 ^^..,

profile
👩🏻‍💻

0개의 댓글