디스코드 베스트 포스트 추천 봇 만들기 5편 - 클라우드와 크론잡을 통해 24시간 서비스 생성하기

Description: 24시간 서비스를 위해 클라우드 서비스와 최신 데이터를 받아오기위한 cronjob을 설정하고 이 과정에서의 트러블슈팅까지 해봅니다.

현재 노트: KR-110.00 a 디스코드 베스트 포스트 추천 봇 만들기 5편 - 클라우드와 크론잡을 통해 24시간 서비스 생성하기
상위 분류: KR-110.00 디스코드 베스트 포스트 추천 봇 제작기

#프로젝트경험 #개인프로젝트 #디스코드봇

디스코드 베스트 포스트 추천 봇 만들기 5편 - 클라우드와 크론잡을 통해 24시간 서비스 생성하기

본 글의 내용은 일반적으로 다음과 같은 흐름으로 진행됩니다.
원하는 것을 구현 시도 -> 문제가 생김 -> 해결
또다른 기능등 시도-> 다시 새로운 문제가 생김 -> 해결
... 구현 또는 수정이나 해결될 때까지 적절하게 반복

결론
매번 디스코드 봇을 로그인한상태의 컴퓨터를 켜놓을 수는 없기에 클라우드 24시간 서비스 탐색 후 설정. 유명한 pm2의 활용과 cron을 통한 자동 데이터 업데이트 까지 구현

클라우드로 24시간 봇호스팅 만들기

무료 호스팅 서치.
처음에 생각해본 것은 디스코드봇만 무료 호스팅하기, 서버리스하기, 클라우드 VM 대여

gcp에서 낮은 스펙은 무료인것 확인. 한번 띄어놓으면 계속 돌아가고, VM이라서 한번만 세팅하면 이후 관리도 쉬움.
GCP로 결정

클라우드에서 돌아가던 프로젝트를 clone 했는데 이후 모듈 import문제가 생겼다. 다음 중 원인은?

  1. git에서는 파일을 대소문자 구분하지 않아서
  2. IDE에서 자동으로 파일경로를 수정해서
  3. 리눅스는 윈도우보다 엄격하게 파일경로를 관리해서

node의 require는 대소문자 구분하지만, git은 운영체제에따라 대소문자를 구분하지 않는다. (git clone시 다른환경에서 영어대소문자로 문제가 발생한 경우) . 엄밀히 말해서 git의경우다음과 같은 느낌이다

로컬과 원격에 comparedata.js라는 파일이 올라가있다
이때 로컬에서 compareData.js라고 대문자를 수정한 다음, git status를 하면 어떻게 될까? mac과 window에서는 인지 하지 못한다.

클라우드 환경에서 디스코드 봇을 돌리기 위해 기존의 코드를 clone해서 실행 하였다.
하지만 발생한 모듈을 찾을 수 없다는 에러
saveMetadata

nargene@discord-librarian-bot:~/discordbot-bestpost$ node getMetadata.js 
node:internal/modules/cjs/loader:1228
  throw err;
  ^

Error: Cannot find module './savemetadata'
Require stack:
- /home/nargene/discordbot-bestpost/getMetadata.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1225:15)
    at Module._load (node:internal/modules/cjs/loader:1051:27)
    at Module.require (node:internal/modules/cjs/loader:1311:19)
    at require (node:internal/modules/helpers:179:18)
    at Object.<anonymous> (/home/nargene/discordbot-bestpost/getMetadata.js:5:94)
    at Module._compile (node:internal/modules/cjs/loader:1469:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1548:10)
    at Module.load (node:internal/modules/cjs/loader:1288:32)
    at Module._load (node:internal/modules/cjs/loader:1104:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:174:12) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '/home/nargene/discordbot-bestpost/getMetadata.js' ]
}

Node.js v20.18.0
nargene@discord-libr

나의 경우 좀 더 복잡한데 다음과 같은 상황
기존에 windows에서 getMetadata.js라는 이름으로 로컬과 원격에서 작업
로컬에서 getmetadata.js라고 수정후 작업. 원격에 파일내용은 같지만 파일이름은 그대로getMetadata.js유지
클라우드에서 clone시 getMetadata,js가 다운로드됨. 파일내용은 getmetadata.js임. 엄격함이 유지돼 리눅스에서는 에러발생

무중단 서버 실행하기

먼저 클라우드를 시도해도 현재 node main.js 형태로 로그인 되기에 ssh가 끊어지는 경우에도 서비스가 끊긴다. 그렇다면 백그라운드에서 계속해서 실행하기 위해 pm2라는 라이브러를 사용하면 ssh가 끊겨도 계속 동작하게만듬

추가로 vm이 다시 재기동된경우에 pm2를 다시시작해줘야하지만 pm2에서 vm 재기동시 재시작하는 기능도 있어 해결가능!

pm2의 경우 24시간 서비스용이라 package.json에 명시하여 설치할필요가있나? 싶어서 -g 형태로설치 하지만 설치 자체는 했음을 알리기위해 pacage.json의 script에 pm2 명령어 추가
즉 모든환경에 설치할 필요가 없기에 설치를 명시하진 않지만, 쓰이기는 할 수 있다는 의미를 남겨놓기

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start:prod": "pm2 node main.js"
  },

sudo npm install pm2 -g

main.js 라는 파일 시작
pm2 start main.js

해당 프로세스 로그 보기
pm2 logs main

현재 pm2로 실행중인 프로세스 목록
pm2 list

pm2로 시작한 프로세스 종료
pm2 delete main

서버 재시작후 pm2가 자동실행되게하기**(리눅스용)**
pm2 startup
이후 다음과같은 명령어에시가 출력되며, 환경마다 다르다. 복사붙여넣기후 실행해준다.
리눅스 실행시 pm2를 다시 실행해하게 해준다
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u userABC --hp /home/userABC

마무리로 pm2가 실행된 후 현재 실행중인 프로세스들이 다시 실행 해주게 하기 위해 다음의 명령어 사용
pm2 save

마지막으로 이 모든 설정이 제대로 됐는지 테스트하는 방법.
실제로 재부팅 해보기!

pm2로 vm재기동시 자동시작 설정 -> 실제 VM 리셋 -> 약 3분간 봇 작동안하는 것 확인-> 이후 자동으로 동작확인완료

save 했던 프로세스 상태로 복귀

pm2 resurrect

데이터 일정 시간 마다 최신화하기 cron사용 하지만 잘 작동하지 않는다...

현재 getmetadata와 comparemetada를 수동으로 실행해줘서 데이터를 최신화 해줘야한다. 매우 번거로우니 cron으로 일정시간마다 수행되게 설정

하지만 cron이 쉽게 수행되지 않는다

조건,. 시간, 로그가지 완벽하게 세팅했다. 근데 최신 업데이트해야할 json파일이 업데이트가 안됐는데 혹시나 이런경우 cron.이라 환경변수가 원인일 수도 있다 생각.
즉 .env변수를 cron이 읽지 못해서 문제가 생기는 것이라 추측
ai talk

그렇다면, cron에서 .env 환경 변수를 제대로 불러오지 못하는 것이 문제일 가능성이 큽니다. 이를 해결하기 위해 cron이 실행되는 환경에서도 .env 파일을 로드하도록 설정해야 합니다.

먼저 현재 크론잡이 문제없이 수행되나 테스트해보자
*/5 * * * * /usr/bin/node /home/nargene/discordbot-bestpost/getmetadata.js >> /home/nargene/discordbot-bestpost/execution.log 2>&1

바로 테스트 위해 5분마다 기존 함수 실행 후 에러 입출력 처리

그 결과는 바로

nargene@discord-librarian-bot:~/discordbot-bestpost$ cat execution.log 
Fri Nov  1 12:00:01 KST 2024 - Success
Fri Nov  1 18:00:02 KST 2024 - Success
Error: Error [TokenInvalid]: An invalid token was provided.
    at Client.login (/home/nargene/discordbot-bestpost/node_modules/discord.js/src/client/Client.js:216:52)
    at main (/home/nargene/discordbot-bestpost/getmetadata.js:20:22)
    at Object.<anonymous> (/home/nargene/discordbot-bestpost/getmetadata.js:44:5)
    at Module._compile (node:internal/modules/cjs/loader:1469:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1548:10)
    at Module.load (node:internal/modules/cjs/loader:1288:32)
    at Module._load (node:internal/modules/cjs/loader:1104:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:174:12)
    at node:internal/main/run_main_module:28:49 {
  code: 'TokenInvalid'
}

그렇다 역시 getmetada에서 에러가 났었던 것.
유효하지 않은 토큰으로 login실패한거봐서 환경변수 값을 못 받아오는듯.
ai 에서 말한 방법대로 source 명령어로 .env를 적용 후 명령어 테스트
*/5 * * * * source /home/nargene/discordbot-bestpost/.env && (/usr/bin/node /home/nargene/discordbot-bestpost/getmetadata.js >> /home/nargene/discordbot-bestpost/execution.log 2>&1 && echo "$(date) - Success" >> /home/nargene/discordbot-bestpost/execution.log)

성공시 명령어 가지 추가

nargene@discord-librarian-bot:~/discordbot-bestpost$ date
Fri Nov  1 19:51:53 KST 2024
nargene@discord-librarian-bot:~/discordbot-bestpost$ crontab -e
No modification made

$ crontab -e


DISCORD_TOKEN=11111111111111111111111111111111
CHANNEL_ID_0=12000000000000
CHANNEL_ID_1=12000000000000
CHANNEL_ID_2=12000000000000
CHANNEL_ID_3=12000000000000
USER_ID=555555555555555

0 */1 * * * echo "Starting metadata fetch at $(date)" >> /home/nargene/discordbot-bestpost/execution.log && /usr/bin/node /home/nargene/discordbot-bestpost/getmetadata.js && echo "getmetadata finished" $(date) >> /home/nargene/discordbot-bestpost/execution.log


0 */12 * * *  echo "Starting compare data $(date)" >> /home/nargene/discordbot-bestpost/execution.log && /usr/bin/node /home/nargene/discordbot-bestpost/comparemetadata.js && echo "comparemetadata finished" $(date) >> /home/nargene/discordbot-bestpost/execution.log

cron 로그 확인하기

직접 크론 에서생긴 문제 확인 가능
grep CRON /var/log/syslog

cron에서 환경변수 다루기 https://this-programmer.tistory.com/488

근데 잘 안된다. 트러블 슈팅하기에는 난이도가 높다.

변수가 별로없고, 중요도가 낮아서 크론에 하드코딩하는방식으로 해결함

cron 실행위치가 달라서 파일 찾기 실패한 경우

내일 일어나서 확인해보니 getmetadata.js의 경우 실행로그, 성공로그가 잘 찍혀있지만,
comparemetadata.js의 경우 실행로그만 찍혀있었다.
에러발생시 error.log에 기록해서 보니 파일을 찾을수가 없다라고 떴는데 곰곰히 생각해보니 탐색식 getcwd()를 기반으로 상대경로로 파일을 접근하는데 cron으로 실행될경우의 현재실행위치가 달라서 생기는 원인같다

AI 설명 추가

수동으로 실행할 때는 /home/user/discordbot-bestpost에서 실행되므로 process.cwd()가 예상한 대로 설정됩니다.
하지만 크론 작업에서 실행할 때는 작업 디렉토리가 다를 수 있어 process.cwd()가 잘못된 디렉토리를 반환하게 됩니다.

해결
명시적으로 위치를 이동시킨 후 명령어 수행

0 */12 * * *  cd /home/nargene/discordbot-bestpost && echo "Starting compare data $(date)" >> /home/nargene/discordbot-bestpost/execution.log && /usr/bin/node /home/nargene/discordbot-bestpost/comparemetadata.js && echo "comparemetadata finished" $(date) >> /home/nargene/discordbot-bestpost/execution.log