상세 컨텐츠

본문 제목

AWS, EC2, SSH, Jenkins, Express, Github, CI/CD, BTS, Let's go

Study/Web

by 2 Mir 2023. 3. 19. 00:58

본문

시작하기 앞서

이 글은 개념이나 방법을 설명하기 위한 것이 아닌, 삽질했던 것을 조각조각 기록해 비슷한 상황에 놓인 사람들에게 팁이 되고자 하는 용도임을 알린다.

 

배경

흔히 캡스톤(Capstone)이라고 부르는 졸업 프로젝트를 할 때가 되었다. 이번 팀에서의 목표는 실제로 서비스를 하는 것이며, 본인은 풀스택으로 참여한다. 기획은 올해 초부터 시작했던 터라 순조롭게 진행하고 있고, 디자인은 나오려면 아직 좀 걸리기에 그동안 할 수 있는 프로젝트 기본 세팅부터 CI/CD까지는 끝마치려고 한다.

FE는 Vercel과 같은 CDN 서비스가 워낙 잘 되어있기 때문에 크게 신경 쓰지 않고, 오랜만에 BE를 EC2에 배포하다가 특수한 상황이 겹쳐 이런저런 일을 겪었다.

 

(참고) BE의 주요 스택은 다음과 같다.

- Typescript
- Express (Node.js)
- MySQL
- Prisma

 


AWS에 Express 배포하는 방법

AWS의 EC2라는 서비스를 이용한다. 쉽게 생각하면 서버 컴퓨터를 대여하는 개념이고, EC2에서는 이 서버 컴퓨터를 인스턴스라고 명칭한다. 당연히 유료지만 AWS는 가입하면 1년 동안 프리티어(free-tier)라는 요금제가 적용되는데, EC2에는 한 달마다 750시간을 제공한다. 이는 두 개의 인스턴스를 사용하면 두 배로 할당량을 빨리 쓰는 개념이라 하나의 인스턴스만 무료로 쓸 수 있다고 생각해야 한다.

대여한 다음에는 ssh로 해당 인스턴스에 원격 접속해서 로컬에서 하듯이 git clone 받고, localhost를 띄우면 된다. 그러면 해당 인스턴스의 퍼블릭 IP로 서버가 열리게 된다. 퍼블릭 IP가 FE에서 호출하는 엔드포인트 주소고, 프라이빗 IPssh로 접근하는 주소라고 생각하면 된다.

 

디펜던시 설치에서 문제가 생겼다면

분명 로컬에서는 npm install 또는 yarn이 잘만 되다가 인스턴스에서는 오류가 나고, 당연히 돼야만 하는 JS / TS 문법에 대해서 트집을 잡을 경우 Node.js 버전을 확인해봐야 한다. 현재 Node.js의 LTS 버전은 18.15.0인데, apt-get으로 설치했다면 상당히 예전 버전일 가능성이 높다. nvm이나 n을 사용해 버전을 LTS로 맞춰주면 해결된다.

 

EC2에 무언가 설치하는 데 오래 걸리거나 멈춘다면

EC2에 Node.js나 MySQL, 프로젝트 디펜던시 등등 설치하고 세팅하다 보면 생각 외로 너무 오래 걸리거나 멈춰버리는 경우가 있다. 이는 프리티어에서 지원하는 인스턴스가 램 용량이 최대 1GB기 때문일 가능성이 높다. 돈을 내면 되지만, 스왑 메모리라는 방법을 쓰면 돈 안 쓰고도 총 3GB까지 램 용량을 높일 수 있다. 다만 이 방법은 저장 공간을 램처럼 사용하는 방법이기 때문에 성능 저하가 올 수 있다고 하는데, 적어도 개복치가 되지는 않기 때문에 나름 쓸만하다.

 

서버를 띄웠는데 퍼블릭 IP로 접근이 안된다면

일단 본인이 띄운 서버의 포트를 확인해 xx.xx.xx.xx:pppp로 접근한 것이 맞는지 확인해 본다. 맞는 포트인데 접근이 안된다면 인스턴스의 인바운드 규칙을 확인한다. HTTP(80)와 HTTPS(443) 외에도 띄운 서버의 포트까지 열려있어야 접근이 가능하다.

 

EC2 IP 고정하기

인스턴스는 재부팅할 때마다 IP가 바뀐다. 그러면 그 때마다 FE에서 엔드포인트를 바꿔줘야 하는가? 전혀 그렇지 않다. EC2에서는 탄력적 IP라는 IP 대여 서비스를 지원해 주며, 탄력적 IP에 인스턴스를 연결하면 엔드포인트를 고정시킬 수 있다. 이렇게 사용하면 탄력적 IP를 무료로 사용할 수 있으나, EC2가 아닌 다른 용도로 쓰면 요금이 청구될 수 있으니 주의해야 한다. 탄력적 IP를 발급한 상태에서 아무것도 연결 안해도 요금이 청구되니 조심해야 한다.

 

EC2에 NGINX 붙이기

사실 실제 트래픽을 받아본 경험이 전무하기 때문에 NGINX의 필요성을 느껴보진 않았다. 솔직한 이유는 FE에서 API 호출할 때 포트번호가 붙는 게 싫어서 NGINX를 쓰는 건데, 아무튼 캐싱이나 로드밸런싱 등 성능을 높이기 위해서 필요하다고 하니 붙인다. 설치 과정이 크게 어렵지 않고, 따로 설정을 안 건드렸다면 Ubuntu 기준으로 /etc/nginx/에 설치되니 여기서 설정 파일을 작성하면 된다.

정석적인 방법은 /etc/nginx/sites-available에서 설정 파일을 만들고, /etc/nginx/sites-enabled에 그 파일의 symlink(바로가기)를 생성해 놓으면 설정대로 적용이 된다. 기본적으로 /etc/nginx/sites-available/default 파일이 존재해 다른 필요가 없다면 이걸 수정해서 쓴다.

설정한 게 적용이 안된 것 같으면 sudo service nginx restart 한 번 입력해주면 된다.

NGINX를 붙이면 그때부터 외부에서 잘못된 경로로 접근했을 때 NGINX에서 자체적으로 응답을 보내주게 되는데, 만약 그렇지 않으면 sudo service nginx status를 입력해서 NGINX가 잘 실행되고 있는지 확인해 보자.

 

SSH 접근이 안된다면

AWS SSH Permission denied (PublicKey) 가 뜰 때가 있다. 이때 확인해 볼 것은 다음과 같다.

1. Key 파일(.pem)의 경로가 정확한지
2. 방화벽(ufw)을 enable 했는데, SSH 포트(22)를 여는 것을 깜빡했는지
3. chmod로 디렉토리나 파일 퍼미션을 이것저것 건드렸는지

 

1번의 경우는 경로를 잘 지정하면 되니 상관없고, 2번의 경우는 새로운 인스턴스를 만들어서 복사를 하라던데 꽤나 복잡하다. 본인은 3번 문제에 직면해 해결했는데, 2번 문제도 이걸로 해결이 될 수도 있을 것 같다고 생각은 하나 해보지는 않았으니 일단 적어본다.

결국 커맨드를 입력하다가 생긴 문제고, 다른 커맨드를 입력하면 해결할 수 있는데 SSH 접근 자체가 안되기 때문에 문제인 것이라고 생각될 때 커맨드를 입력할 수 있는 방법이 있다.

 

EC2에서는 인스턴스에 대해 사용자 데이터 편집(Edit User Data)이라는 기능을 지원한다. 이름만 봐서는 쉽게 연상되는 기능은 아닌데, 인스턴스를 부팅할 때 강제로 실행시킬 쉘 스크립트를 작성할 수 있는 기능이다. AWS FAQ가 설명이 친절하진 않지만, 아래 링크에서는 나름 잘 설명하고 있다.

 

https://aws.amazon.com/ko/premiumsupport/knowledge-center/ec2-linux-fix-permission-denied-errors/

 

* 비슷한 경우로 네이버 클라우드에서는 화면 공유 형식의 원격 접속 서비스를 지원한다.

 

배포 자동화 (CI / CD)

배포 자동화를 한다고 하면 보통 Github에서 main 브랜치에 push가 발생했을 때 인스턴스에서 자동으로 pull 받고 서버를 재시작해 변경 내용을 적용하게 한다. FE를 배포할 때 사용하는 AWS S3나 Vercel, Netlify와 같은 CDN 서비스를 이용할 경우 클릭 몇 번으로 세팅이 가능하거나 Github Action 선에서 정리가 가능하다. 다만 EC2에 배포 자동화를 걸어두려면 AWS에서는 CodeDeploy라는 또 다른 서비스를 이용하라고 한다. 원리는 다음과 같다.

 

1. (Github Action) 레포를 압축해 S3에 업로드
2. (CodeDeploy) S3에 업로드된 것을 EC2에 자동으로 다운로드
3. (CodeDeploy) 설치 전 / 설치 후 등 여러 상황을 지정한 후 각 상황에 맞게 작성한 쉘 스크립트를 실행

 

CodeDeploy를 설정할 때에는 주로 AWS IAM에서 이름도 비슷한 여러 정책들을 건드려야 하다 보니 실수할 확률이 높다. 본인은 1번까지는 빠르게 설정했고, IAM까지 나름 잘 설정했다고 생각했는데 테스트 배포를 할 때 CodeDeploy에서 아예 접근도 하지 못했다는 AccessDeniedException이라는 에러를 만나 결국 해결하지 못했다.

 

확실한 원인을 찾을 수 없었으나, 아마 리전이 원인이었을 것으로 결론 내렸다. 앞서 언급했던 특수한 상황이 바로 리전이었는데, 배포하려는 서비스가 우리나라가 아닌 UAE를 타깃으로 기획하고 있기 때문에 우리나라 리전인 ap-northeast-2가 아니라 me-central-1이라는 리전에서 EC2를 구축했다. CodeDeploy를 이용하기 위해서는 인스턴스에 CodeDeploy Agent를 설치하는 과정이 필요한데, 이때 리전을 입력해야 한다. me-central-1이 AWS에서 최근에 추가한 리전이라 그런지는 모르겠으나 CodeDeploy Agent 옵션에 해당 리전이 없었고, 혹시 몰라서 다른 리전으로 Agent를 설치했더니 이런 오류가 난 게 아닌가 싶다.

 

그래서 이걸 전부 뒤엎고, Jenkins를 도입하게 되었다.

 

EC2에 Jenkins 붙이기

Jenkins는 CI / CD를 도와주는 도구다. Github과는 Github Action이 아닌 웹훅(Webhook)으로 연결할 수 있고, 포트 하나를 잡아먹는 별도의 서비스기 때문에 상당히 무겁다. 따라서 Jenkins를 설명하고 있는 많은 블로그에서는 별도의 Jenkins용 인스턴스를 만들어 Github과 Jenkins용 인스턴스를 연결하고, Jenkins와 Express용 인스턴스를 연결해 두 개의 인스턴스가 필요한 방식으로 설명한다. 그러나 앞서 언급했다시피 인스턴스를 두 개 쓰게 되면 프리티어더라도 요금을 내야 하기 때문에 하나의 인스턴스에서 Express와 Jenkins를 모두 띄우게 됐다.

 

하나의 EC2 인스턴스에서 Jenkins와 Express 모두 띄우기

본인은 Jenkins를 도입할 계획이 없었기 때문에 인스턴스를 만들었을 때 자동으로 생성된 ubuntu 계정에서 Express 세팅을 모두 해놓았다. 그런데 이 Jenkins라는 친구는 ubuntu가 아닌 별도의 jenkins 계정을 만들어서 /var/lib/jenkins/workspace/ 경로에 프로젝트를 설치하고 있었고, 처음 세팅해 놓았던 /home/ubuntu/ 경로에 옮기는 쉘 스크립트를 짜야했다. 또한 옮기는 게 끝이 아니라 디펜던시 다시 설치하고 prisma migrate하고 pm2를 다시 띄워야 했는데, 권한 문제로 삽질 좀 했다. 앞에서 SSH 접근이 안 됐던 것도 이거 하다가 그렇게 된 것이었다. 아무튼 권한 문제를 잘 해결하면 Jenkins와 Express를 한 인스턴스에서 띄울 수 있다.

Jenkins가 많이 무거워서 걱정됐는데, 일찍이 스왑 메모리를 적용해 놔서 그런지 아직까지는 잘 버티고 있다.

 

ubuntu 계정에서는 잘 되는데 jenkins 계정에서는 안된다면

ubuntu 계정에서는 디펜던시 설치나 서버 띄우는 게 잘만 되는데 jenkins 계정으로 하니까 안된다면 sudo(root)도 안되는지 해보자. 안된다면 각 계정의 Node.js 버전이 다를 수 있다. 본인은 jenkins 계정의 Node.js를 업데이트하는 방법보다는 어차피 디렉토리를 옮기는 과정에서 권한 얻는 과정이 필요해서 sudo로 Node.js를 업데이트하고, 스크립트에 sudo를 써 해결했다.

 

jenkins 계정에서는 sudo를 쓰려면 root 비밀번호를 입력하라고 뜨기 때문에 Jenkins에 배포 스크립트를 등록할 수 없는데, root 비밀번호 입력이 안 뜨게 우회하는 방법을 쓴 거라 보안적으로 좋은 방법은 아니라고 한다.

 

Free Tier인데 EC2 요금이 부과된다면

분명 Free Tier라서 EC2 요금이 부과되지 않는다고 알고 있었어도 월말에 요금 폭탄을 맞을 수 있다. Free Tier에서 지원하는 것은 EC2의 인스턴스 활성화 시간에 대한 비용이기 때문이다. 이와 별개로, EC2는 CPU 사용량에 대한(정확히는 CPU 크레딧에 대한) 비용을 따로 부과한다. 이는 Free Tier에서 지원하지 않으며, 인스턴스 활성화 시간에 비례하겠으나 절대적으로 시간과 비례하기보다는 CPU가 일을 많이 할수록 요금이 많이 부과된다. 즉, 서버 용도로 EC2를 사용할 경우 외부에서의 호출이 많을수록 요금이 더 많이 부과될 수 있다.

 

인스턴스를 중지할 경우 CPU가 일을 하지 않기 때문에 요금이 부과되지 않는다. 다만 앞서 언급했듯이 탄력적 IP가 아무것도 연결 안된 상태면 요금이 부과되기 때문에 인스턴스가 중지되는 순간 요금이 부과된다. 그렇지만 이는 CPU 크레딧 요금과 비교했을 때 현저히 적은 수준이다.

 

자세한 내용은 AWS Billing 서비스를 참고하면 되며, AWS Cost Explorer에서 다양한 보고서를 확인할 수도 있다.

관련글 더보기

댓글 영역