메모리를 증설하고 CI/CD가 dev에서 정상 동작하는 것을 확인한 후 master에 PR, marge해 주었다.
PR까지는 CI/CD가 문제가 없었지만, marge가 완료된 후 동작하는 Actions의 CD부분에서 다시 문제가 발생했다.
쭉 내용을 살펴보니 build과정에서 JavaScript heap out of memory에러가 발생해서 스크립트가 중지되었다.
스크립트 중지시 다음 스크립트를 실행하지 않는 설정을 해두었기 때문에 잘못된 로직의 서버가 restart로 다시켜지는 경우는 방지해서 서버가 다운되지는 않았지만, PR때까진 잘 되던 스크립트가 marge에서 안된다니 문제가 무었일까?
JavaScript heap out of memory
딱 보면 메모리가 모자라서 발생하는 현상처럼 보인다.
분명 스왑공간을 이용해서 메모리를 증설해 주었는데도 동작이 왜 멈추었을까? 다시 bash에가서 npm run build
를 실행하며 메모리 사용량을 모니터링 해보았다.
이게 왠걸? npm ci
동작에서는 available 메모리가 부족해지면 Swap영역에서 공간을 끌어와서 사용하였지만,
npm run build
과정에서는 available이 60까지 내려가다가 동작이 바로 끊기고 에러를 내뿜었다.
문제에 대해서 검색해보니, 인스턴스의 메모리 용량에 대한 제한이 아니라, node에서 빌드를 할때 쓰는 엔진의 가용 메모리용량이 한정되어 있기 때문이었다.
기본적으로 node에서 사용할 수 있는 최대 메모리가 512MIB의 기본값으로 고정되어 있기 때문에 Build과정에서 해당 메모리 용량 이상을 사용하게 된다면 아무리 인스턴스의 메모리가 남았더라도, npm 프로세스가 종료되어 버리는 경우라고 한다.
이전 버전상태에서는 CD가 정상 동작하다가, 이번에 단위테스트를 위한 파일을 많이 추가해서 build과정에서 컴파일해야할 양이 늘어나면서 marge이후의 CD가 동작하지 않게 된 것 같았다.
문제의 간단한 해결방법은 아무래도 node에서 가용할 메모리용량의 제한점을 더 올려주는 것이다.
export NODE_OPTIONS="--max-old-space-size=2048"
위 명령어를 통해서 가용 메모리용량의 제한점을 늘려주고 아래의 명령어로 확인한다.
node -e 'console.log(v8.getHeapStatistics().heap_size_limit/(1024*1024))'
해당 명령어들을 bash에서 실행하고 npm run build
를 실행해 보니 정상적으로 스왑공간을 사용하면서 동작되는 것을 확인할 수 있었다.
"와~! 문제해결!"이라고 했으면 좋겠지만, export로 설정한 옵션은 이번에 ssh로 접속했을 때만 적용된다.
따라서 해당 스크립트를 CD 스크립트 로직에 추가하여 매 CD가 동작할 때마다 실행하거나, ~/.bashrc 환경설정 파일에 직접적으로 작성해서, 매번 로드될 때마다 해당 환경설정이 적용될 수 있도록 해주어야한다.
스크립트에 추가하는게 더 간단할 수도 있지만, 필자는 이미 master브렌치에 marge한 이후라서, hotfix를 다시 만들어 넣기 귀찮아져서 그냥 직접 ~/.bashrc설정을 바꿔주기로 하였다.
vi ~/.bashrc
vi편집기를 이용해서 ~/.bashrc인 환경변수 파일에 들어가 주고 가장아래에 새로 export NODE_OPTIONS="--max-old-space-size=2048"
를 추가해 주었다.
배쉬창을 끄고 다시 ssh로 재접속 해서 node에서 가용할 메모리용량의 제한점을 확인해 보았다.
그대로 2060이 나오는 것을 확인할 수 있었다. 그렇다면 이제 CD에서 Build가 문제가 발생하지 않아야 한다고 생각한다. github로 돌아가서 CD를 굴려보자.
윽.. 여전히 CD의 스크립트에서 같은 에러를 뿜고 있다.
결국 다시 원점을 돌아와서 CD를 위한 appleboy/ssh-action@master의 스크립트에 위의 내용을 추가해보았다.
CD:
needs: CI
runs-on: ubuntu-20.04
steps:
- name: Run scripts in server
uses: appleboy/ssh-action@master
with:
key: ${{ secrets.KEY }}
host: ${{ secrets.HOST }}
username: ${{ secrets.USER_NAME }}
port: ${{ secrets.PORT }}
script_stop: true
script: |
export NVM_DIR=~/.nvm
source ~/.nvm/nvm.sh
export NODE_OPTIONS="--max-old-space-size=2048"
cd thbnb
cd TaeHyeongBnb
git pull
npm ci
npm run build
pm2 restart main
hotfix브렌치를 새로파서 master에 PR을 날려보았다. 그런데 이게 왠걸? CD가 정상적으로 동작한다.
문제자체는 스크립트를 추가하면서 해결하였지만, 의문이 들었다. 왜 첫번째 방법은 동작하지 않고 두번째 방법은 정상동작했을까?
추측은 github actions에서 appleboy/ssh-action이 동작하는 환경이 bash쉘 환경이 아니기 때문이라고 추측한다.
~/.bashrc 파일은 bash+rc 로 bash쉘에서 사용되는 환경설정파일이다. bash셀이 시작될 때마다 자동으로 해당파일에서 환경변수를 읽고 해당 환경파일을 적용시킨다.
만약 github actions에서 appleboy/ssh-action이 동작하는 환경이 bash쉘 환경이 아니라면 당연히 ~/.bashrc파일에 작성해 주어도 동작하지 않는게 당연하지 않을까?
bash쉘이하면 usr/bin/bash로 가겠지만 이미지와 같이 action은 docker환경으로 가기 때문에 아마 첫번째 방법이 적용되지 않았던 것 같다.