Open kakasoo opened 1 year ago
npm i -g pm2
pm2를 설치했다는 가정 하에 아래와 같은 shell script를 작성합니다.
# script.sh
cd /server/folder
git fetch
local=$(git rev-parse HEAD)
echo $local
target=$(git rev-parse origin/main)
echo $target
if [ $local != $target ]
then
git pull origin main
echo '풀 완료'
npm install
npm run build
pm2 reload 0
fi
이 스크립트는 git fetch 후 main origin branch의 가장 마지막 커밋이 무엇인지 확인하고, 지금 현재 local의 가장 마지막 커밋과 서로 다르다면 Pull을 받은 다음 build, 서버 재실행을 하게 하는 명령어가 작성되어 있습니다. 재실행이라는 말은, 이전 최초의 서버 실행은 개발자가 직접 ssh로 접속해서 실행해줘야 한다는 의미겠죠?
리눅스 crontab에는 아래와 같이 작성합니다.
crontab은 리눅스 운영체제에 크론식을 작성해서 특정 시간마다 특정 스크립트를 실행하게 할 수 있어요.
아래는 * * * * *
라는 크론식으로 매 분 마다 라는 의미를 가지고 있는데, 매 분 마다 스크립트를 실행하라고 작성한 거에요.
SHELL=/bin/bash
PATH=/root/.nvm/versions/node/v14.15.5/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
* * * * * /server/folder/script.sh
저도 쉘 스크립트는 짜는 법을 다 까먹었기 때문에 대충 이런 흐름이겠거니 하고만 기억해두고 있습니다. 쉘 스크립트로 배포를 하지도 않지만, 서버 ( = 하드웨어 ) 만 가지고도 가능한 가장 간단한 방법이기 때문에 한 번 적용해봤으면 해서 공유합니다.
배포 단계까지 가게 되면 직접 적용해보고, 그 다음에는 github action
으로 다시 옮겨봅시다.
Node.js의 Cluster Module과, 로드 밸런싱
제가 잘 이해했는지 모르겠지만... 읽어보니 왜 CI/CD 서비스들이 등장했는지 알 것 같습니다. 현대의 분산처리 시스템에서 배포는 정말 복잡한 문제군요.
이해좋네요. 근데 로드밸런싱도 "너 더 견딜 수 있어?" 라고 계속 서버에게 헬스체크하다보니 운영체제처럼 맡기는 것보다 부하가 있긴 합니다. 장단이 명확해요.
저는 노출되더라도 바로 삭제할 거니까 상관없지만, 절대 이 아이피나 서버의 정보를 드러내지마세요. 이 스레드는 인스턴스의 생성, 탄력적 IP의 등록까지 설명합니다.
인스턴스를 생성합니다.
인스턴스가 생성되었고, 아이피가 할당된 것을 볼 수 있습니다. 하지만 이것은 탄력적 아이피가 아니기 때문에 서버를 껐다 켤 때마다, 또는 서버가 문제가 발생하여 강제 재부팅될 때마다 아이피가 바뀝니다. 그러면 환경 변수를 연동해서 써놓을 수 없기 때문에 문제가 되겠죠. 그래서 고정 아이피를 할당할 것이고 이걸 탄력적 아이피라고 합니다.
EC2 대시보드 사이드 탭에서 [네트워크 보안] - [탄력적 IP]로 가 탄력적 IP를 생성합니다. 생성 버튼을 누르고 나서 별로 클릭할 것은 없습니다.
방금 만든 EC2 인스턴스에 고정된 아이피를 할당하기 위해 일단 작업 탭에서 [탄력적 IP 주소 연결]을 클릭합니다.
주소를 할당합니다. 재연결은 아이피 주소를 다른 인스턴스에게 뺏겨도 되냐는 질문이므로 체크를 해제합니다.
할당 하고 다시 대시보드로 이동하면 아이피 주소에 EIP의 아이피가 기록되어 있는 게 보일겁니다.
저는 키를 Desktop에 두고, 퍼블릭 아이피로 접속을 했습니다. 명령어는 아래와 같습니다.
> $ ssh -i /Users/kakasoo/Desktop/testKey.pem ec2-user@3.37.199.24
The authenticity of host '3.37.199.24 (3.37.199.24)' can't be established.
ED25519 key fingerprint is SHA256:ovF2U9tTO7TnYgByIge2nxvMIPdE0O3337Npa2+vAlk.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '3.37.199.24' (ED25519) to the list of known hosts.
위와 같은 문장이 나와 yes를 입력했습니다만,
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/Users/kakasoo/Desktop/testKey.pem' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/Users/kakasoo/Desktop/testKey.pem": bad permissions
ec2-user@3.37.199.24: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
키가 너무 개방되어 있기 때문에 숨기라는 말이 나오네요. 이거는 파일 접근 권한을 수정하라는 말입니다.
$ chmod 400 Desktop/testKey.pem # 읽기만 가능하게 파일을 수정합니다.
$ ssh -i /Users/kakasoo/Desktop/testKey.pem ec2-user@3.37.199.24 # 재접속
, #_
~\_ ####_ Amazon Linux 2023
~~ \_#####\
~~ \###|
~~ \#/ ___ https://aws.amazon.com/linux/amazon-linux-2023
~~ V~' '->
~~~ /
~~._. _/
_/ _/
_/m/'
[ec2-user@ip-172-31-36-135 ~]$
성공해서 ec2 인스턴스에 접속했습니다.
여기서부터는 EC2 인스턴스 내부입니다.
[ec2-user@ip-172-31-36-135 ~]$ sudo yum update -y
[ec2-user@ip-172-31-36-135 ~]$ sudo yum install git -y
먼저 linux update를 진행하고 git을 설치합니다.
[ec2-user@ip-172-31-36-135]$ mkdir github # github 폴더 생성
[ec2-user@ip-172-31-36-135]$ cd github # github 폴더로 이동
[ec2-user@ip-172-31-36-135 github]$ git clone https://github.com/rimo030/nestjs-e-commerce-frame # 이 레포를 clone합니다.
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash
$ . ~/.nvm/nvm.sh
$ nvm install --lts
$ node -e "console.log('Running Node.js ' + process.version)"
설치 후 아래와 같은 메시지들이 나옵니다.
[ec2-user@ip-172-31-36-135 nestjs-e-commerce-frame]$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash
. ~/.nvm/nvm.sh
nvm install --lts
node -e "console.log('Running Node.js ' + process.version)"
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 15916 100 15916 0 0 53042 0 --:--:-- --:--:-- --:--:-- 53230
=> Downloading nvm from git to '/home/ec2-user/.nvm'
=> Cloning into '/home/ec2-user/.nvm'...
remote: Enumerating objects: 365, done.
remote: Counting objects: 100% (365/365), done.
remote: Compressing objects: 100% (314/314), done.
remote: Total 365 (delta 43), reused 159 (delta 25), pack-reused 0
Receiving objects: 100% (365/365), 364.78 KiB | 13.51 MiB/s, done.
Resolving deltas: 100% (43/43), done.
* (HEAD detached at FETCH_HEAD)
master
=> Compressing and cleaning up git repository
=> Appending nvm source string to /home/ec2-user/.bashrc
=> Appending bash_completion source string to /home/ec2-user/.bashrc
=> Close and reopen your terminal to start using nvm or run the following to use it now:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
Installing latest LTS version.
Downloading and installing node v20.10.0...
Downloading https://nodejs.org/dist/v20.10.0/node-v20.10.0-linux-x64.tar.xz...
################################################################################################### 100.0%
Computing checksum with sha256sum
Checksums matched!
Now using node v20.10.0 (npm v10.2.3)
Creating default alias: default -> lts/* (-> v20.10.0)
Running Node.js v20.10.0
npm i # 해당 레포의 패키지들을 설치합니다.
npm run start:dev # 서버를 실행합니다.
브라우저에 가서 3.37.199.24:3000
를 실행해봅니다. app.controller.ts
에 ROOT 경로에 대한 API가 있다면 hello 라는 문장이 나올 겁니다. 만약 없더라도, 어쨌든 API 응답이 없다는 것이니 404 라도 나오기는 하겠죠?
퍼블릭한 IP로 접속이 가능한지 체크합니다.
깜빡하고 보안그룹 설정을 안했네요.
보안그룹 설정으로 들어갑니다.
TEST라는 이름으로 보안그룹을 만듭니다. 저는 3천번 포트를 곧장 열어버리지만, EC2를 사용한다면 내부에 nginx 와 같이 웹서버를 두고 80번 포트를 열어버릴 수 있습니다. 이게 더 안전할 겁니다. 다만 여기서는 빠르게 하기 위해 바로 3000번 포트를 쓰도록 할게요.
3000번 포트를 열어놓은 보안 그룹을 연결했기 때문에 바깥쪽에서 접근이 가능해졌습니다.
여기까지 하고 난 다음 서버를 실행시키고 나니 저는 EC2 서버가 죽어 있었습니다. 보통 이런 경우에는 프리티어의 메모리 부족으로 인한 것일 확률이 큽니다. EC2에서 무료로 제공하는 메모리를 늘려줄 수는 없으니, 디스크 공간을 메모리로 활용할 수 있게 스왑 메모리를 설정할 겁니다.
echo "start!"
sudo dd if=/dev/zero of=/mnt/swapfile bs=1M count=4096
echo "메모리할당 성공"
sudo chmod 600 /mnt/swapfile
echo "스왑 파일 권한 변경 성공"
sudo mkswap /mnt/swapfile
echo "스왑 영역 설정"
sudo swapon /mnt/swapfile
echo "스왑 영역 사용 추가"
sudo swapon -s
echo "스왑 영역 사용 체크"
혹시나 해제할 일도 생기면 아래 스크립트를 사용해주세요.
sudo swapoff -v /mnt/swapfile
# echo "스왑 메모리 해제"
sudo rm /mnt/swapfile
# echo "해제 완료"
빌드 과정에서 서버가 죽는 것이기 때문에, 프리티어를 사용할 때 저는 빌드 레포지토리를 따로 만들기도 했습니다. 서버를 빌드한 dist 폴더만 따로 업로드해서, EC2는 빌드 없이 서버를 실행할 수 있게 하곤 했습니다.
$ sudo yum install docker -y
$ sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ sudo systemctl start docker # 방금 설치한 도커를 실행합니다.
$ sudo docker-compose up -d # 도커 컴포즈 파일을 이용해 DB를 생성합니다.
우리는 데이터베이스에 자동 실행되는 스크립트를 넣진 않았죠.
$ sudo docker ps;
$ sudo docker exec -it CommerceDB bash;
$ mysql -u root -p;
$ Password: # 패스워드 입력 후 실행
$ mysql> create database commerce;
생성을 마저 해줍시다.
$ npm run schema:sync:local # package.json에 명시된 대로 스키마 생성!
ec2 내부에서 서버를 실행해봅시다.
$ npm run build # 빌드
$ npm run start:prod # dist 파일의 main.js를 실행합니다.
앞으로 프로덕션에서는 ts 파일이 아닌, main.js 파일을 실행함으로써 서버를 열 겁니다. 여기는 더 이상 개발이 되는 환경이 아니기 때문이죠.
404긴 하지만 서버 접속이 되는 걸 확인했습니다!
SHELL=/bin/bash
PATH=/root/.nvm/versions/node/v14.15.5/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
* * * * * /home/github/repo-name/init.sh
echo $PATH
를 입력해서 본인의 환경변수를 기입해야 합니다. ( EC2 내에서 )>
기호로 sh 파일 이후에 다른 폴더를 지정할 경우 로그를 남길 폴더를 지정할 수 있습니다.
cd /home/github/repo-name # 이게 없으면 제대로 실행될 수가 없습니다! ( 절대 경로로 넣으셔야 합니다. )
git fetch
local=$(git rev-parse HEAD)
echo $local
target=$(git rev-parse origin/main)
echo $target
if [ $local != $target ]
then
git stash # 혹시라도, 다른 파일들이 추가되었을까봐 날리는 로직을 추가
git pull origin main
echo '풀 완료'
npm install # 새로운 패키지가 추가되어 있을지 모르므로 install을 다시 합니다.
echo "npm install 완료"
npm run build # 빌드를 하여 dist/main.js 파일을 생성합니다.
echo "빌드 완료"
pm2 reload app # app 이라는 이름으로 pm2 서버가 실행되어 있다고 가정하고, app을 다시 실행합니다.
fi
이 파일을 1분마다 실행하면, 서버는 자동으로 재배포됩니다. 추후에는 github action을 사용한 방식을 배워봅시다.
이 방식에서는 Pm2를 쓰는데 Pm은 프로세스 매니저라는 뜻입니다. 서버를 자동으로 재실행해서, 이전 서버를 갈아끼우는 데에 도움을 주는데요, reload는 서버를 순차적으로 재실행하는 것이고 restart는 동시에 껐다 키라는 명령어입니다. 여기서는 서버가 1대고, 내부에 코어가 1개라 내부 서버도 1개만 실행되고 있어서 사실 reload, restart 구분이 무의미합니다.
추후에 pm2를 사용할 것은 아니기 때문에 개념만 이해해둡시다.
요즘은 배포를 전부 github action을 이용해서 합니다만, 과거에는 어떤 걸로 했을까요?