웹 Web

Heroku에서 Koyeb으로 웹 페이지 이전하기

한비 2023. 2. 6. 01:01
반응형

Heroku의 프리 티어 종료

때는 작년 초, 21년 여름에 만든 게임을 배포하기 위해 웹 페이지를 만들고 2월 경에 Heroku를 이용해 배포했었다. 첫 웹 페이지 배포였기 때문에 하루종일 컴퓨터랑 씨름한 끝에 해냈던 기억이 난다. 그런데 그 해 11월에 한 통의 메일이 날아왔다. 곧 Heroku의 프리 티어를 종료할 예정이고, 요금제를 업그레이드하지 않는다면 당신의 웹 페이지는 막힐 것이라는 내용이었다. 당황스러웠지만 당시 미국에 교환학생으로 파견된 상태여서 재배포를 알아볼 여유가 없었기에 우선은 그냥 내버려두었다.

하지만 올해 1월... MongoDB에서도 메일이 날아왔다. 당신의 cluster에 약 두 달간 접속이 없었기 때문에 미접속일이 60일이 되는 날(메일을 받은 시점 기준으로 일주일 후) 자동으로 DB가 정지될 거라는 내용이었다. 알아보니 무료 티어인 M0 cluster의 경우에는 미접속일이 60이 되면 자동으로 정지되고, 다른 요금제는 다른 조건에 따라 pause/resume되는 모양이었다. 자세한 내용은 이 페이지를 참조하면 좋다. 처음에는 한번 정지되면 영원히 막히는건가 하고 걱정했는데, 다행히 cluster를 프로젝트에 연결하기만 하면 자동으로 resume되는 형식이었다. 그래서 며칠 전에 Koyeb으로 재배포하면서 DB도 연결해 다시 작동하도록 했다.

Heroku 프리 티어 종료에 대해 찾아보니 비슷하게 토이 프로젝트를 Heroku로 배포한 사람들이 많았고, 대체제로 Koyeb이 가장 많이 언급되는 것 같았다. 깃허브 레포지토리와 연동하여 바로 배포할 수 있고 UI도 깔끔해서 나는 Koyeb으로 이전하기로 했다.

Koyeb으로 이전하기

 

The fastest way to deploy applications globally

Koyeb is a developer-friendly serverless platform to deploy apps globally. No-ops, servers, and infrastructure management.

www.koyeb.com

Koyeb은 완전 무료인 것은 아니고 시간당 0._ _ 달러씩 요금이 부과되는 형식인데, 매달 5.5불의 크레딧을 주기 때문에 그 범위 안의 요금제를 사용한다면 무료로 배포할 수 있다. 참고로 나는 무료로 사용할 수 있는 요금제에서는 메모리 초과 에러가 나서 deploy가 불가능했기 때문에 그 바로 위 요금제를 선택해야 했다...

Koyeb은 깃허브 연동을 통해 가입할 수 있어 로그인이 쉽고 빨랐다. 최초 회원가입 시에 카드 등록 화면이 떠서 아주 소액의 돈을 결제/결제 취소하는 과정을 거쳐 카드를 등록해야 했다.

회원가입 및 로그인을 하고 나면 이렇게 생긴 대시보드가 보인다. 우측 상단 create app을 눌러 프로젝트를 추가하자.

Koyeb은 깃허브 또는 도커로 배포할 수 있는데 나는 깃허브를 선택했다.

배포할 레포지토리를 서치한 후 Koyeb과 연동하자. 나의 경우 처음에는 조직에 속한 여러 레포지토리 중 내가 작업한 레포지토리에만 권한을 부여하려고 했는데 자꾸 에러가 나서, 그냥 그 레포를 fork해온 레포지토리와 연동했다.

그러면 이렇게 생긴 화면이 뜨는데, 여기서 빌드/런 커맨드 및 환경변수와 포트번호를 설정할 수 있다. 우선 윗 부분부터 보면, Configure Service에서는 deploy할 레포지토리와 브랜치를 선택해야 한다. 그 아래 Build and deployment settings 토글을 열어 빌드와 런 커맨드를 오버라이드 할 수 있다. Autodeploy를 활성화하면 이 deploy setting창에 변경이 생기거나 레포지토리에 업데이트가 있을 때마다 자동으로 deploy해준다. 앞의 레포지토리 선택에서 public repository를 선택하고 url을 넣은 경우에는 이 기능을 사용할 수 없다고 안내되어 있다. Regions는 이 앱을 deploy & run 할 지역을 고르라고 되어있는데 선택지가 두 개밖에 없어서 나는 워싱턴을 선택했다.

다음은 요금제 선택인데, 두 개의 Nano 프로젝트 또는 하나의 Micro 프로젝트는 free credit 범위 내에 들어가는 것으로 알고 있다. 필요한 요금제를 선택한 후 Advanced 토글을 열면 Environment variables에서 환경 변수를 설정할 수 있다. 나는 몽고 디비 연결용 주소와 production 모드로 빌드할 것을 알리는 환경변수 두 개를 입력했다. Exposing your service에는 노출할 포트번호 (나의 경우 3000번)을 입력했다.

Scaling은 그대로 1로 두었고, App name을 변경하여 도메인 주소를 수정했다. 기본 값은 연동한 레포지토리명으로 되어있다. 이렇게 모든 설정을 마치고 Deploy버튼을 누르면 자동으로 빌드가 시작된다.

빌드에 문제가 없다면 build log에 build succeeded가 뜨고 상태가 Healthy로 변한다. Runtime log도 제대로 찍히면 url 주소해서 배포된 웹페이지를 확인할 수 있다.

트러블 슈팅

한번에 deploy가 되면 좋았겠지만 역시 그렇게 호락호락하지 않았다. 진심 30번째에 성공한듯... 그런데 모아놓고 보면 원인이 아주 거창한 것도 아니고 그냥 자잘한 실수들 때문이었다. 어떤 문제가 있었는지 하나씩 살펴보자.

1) Node.js 버전 확인

첫 시도는 빌드부터 막혔다. 프로젝트 배포 전에 로컬에서 구동해볼때부터 겪었던 문제인데, 컴퓨터에 있는 Node.js 버전과 프로젝트의 버전이 다르면 Error: error:0308010C:digital envelope routines::unsupported \[1\] at new Hash 와 같은 에러가 나면서 서버가 켜지지 않았다. 분명히 package.json에 버전을 명시해두었는데도 빌드 로그를 보니 버전을 인식하지 못하고 자동으로 node js를 18.* 버전으로 설치하여 계속 에러가 났다. 구글링 끝에 찾아낸 해답은 ... "engines" 로 버전명을 명시해야 한다는 거였다. 이유는 모르겠으나 우리 프로젝트에는 "engine"으로 되어있었고 ... s를 하나 더 붙이자마자 귀신같이 버전 인식이 제대로 작동했다. 비슷한 문제를 겪고 있는 경우 다음 사이트를 참고하면 좋다. 참고로 아래 사이트에서는 버전이 명시되지 않은 경우 빌드 시 14.x 버전을 설치한다고 되어있었으나 옛날 문서인지 지금은 18.x가 자동으로 설치되더라.

2) bcrypt 버전 변경

\[HPM\] Error occurred while trying to proxy request /api/users/auth from localhost:3000 to http://localhost:5000 (ECONNREFUSED) ([https://nodejs.org/api/errors.html#errors\_common\_system\_errors)](https://nodejs.org/api/errors.html#errors_common_system_errors))

이 부분은 로컬에서 구동 시도할 때 노드 버전 다음으로 고친 문젠데, bcrypt 버전이 너무 낮으면 (나의 경우 3.x.x) node js 버전과 호환되지 않아 설치할 수 없다는 에러였다. 구글링 결과 dependencies에서 bcrypt 버전을 5.0.0 이상으로 수정하면 된다는 글을 보았고 그렇게 수정하니 바로 에러 메시지가 사라졌다.

3) 빌드 & 런 커맨드 설정

위 두개를 수정하니 드디어 build succeeded가 떴다. 기쁨도 잠시... 프로젝트 상태는 계속 Unhealthy에 머물렀고, url에 접속하면 502 에러가 났다. Runtime log를 확인해보니 빌드와 런에 계속 문제가 있는 걸 알 수 있었고, 이 부분 커맨드를 수정하기로 했다.3-1) 커맨드 설정 위치 빌드 커맨드는 Koyeb의 app setting에서 빌드 커맨드를 오버라이드하여 사용했다. 런 커맨드는 오버라이드하는 대신 헤로쿠로 배포할 때 만들어둔 Procfile 파일에 작성하였는데, 런 커맨드란에 오버라이드하여 사용해도 문제 없을 것 같다.3-2) 커맨드 내용 빌드 커맨드는 헤로쿠로 배포할 때 "heroku-postbuild"라는 이름으로 package.json의 script에 적어둔 커맨드를 활용했다. NPM\_CONFIG\_PRODUCTION=false npm install --prefix client && npm run build --prefix client 이었는데, dependency 관련해서 여러 문제가 있었다. 일단 저 커맨드에는 client의 디펜던시 설치만 있고 root의 디펜던시 설치가 없으므로 다음과 같이 수정했다. NPM\_CONFIG\_PRODUCTION=false npm install --prefix client && npm install && npm run build --prefix client
그럼에도 불구하고 자꾸 에러가 났는데, devDependencies 때문이었다. devDependencies는 개발용으로만 필요하고 배포용으로는 필요하지 않은 모듈을 적어두는 부분인데, 나는 여기에 run에 필요한 concurrently가 있었기 때문에 devDependencies도 설치하도록 NPM\_CONFIG\_PRODUCTION=false 코드를 빌드 커맨드 앞에 추가했다. 하지만 무슨 이유인지 Koyeb은 이를 인식하지 못했고, Runtime log에 자꾸 sh: 1: concurrently: not found 에러 메시지가 떴다. 결국 로컬에서 concurrently를 uninstall한 다음에 devDependencies가 아닌 dependencies에 다시 install하여 push해서 문제를 해결할 수 있었다. 단순히 package.json의 dependencies에 concurrently를 추가하는 것으로는 (텍스트 복붙) 문제가 해결되지 않았다. package-lock.json의 값도 같이 변경해야하기 때문인 것 같다.

런 커맨드는 다음과 같다. 순서대로 Procfile, package.json이다.

web: npm run dev
  "scripts": {
    "start": "node server/index.js",
    "backend": "node server/index.js",
    "frontend": "npm run start --prefix client",
    "dev": "concurrently \"npm run backend\" \"npm run start --prefix client\""
  },

참고로 concurrently를 안 쓰면 안되나 하고 단순하게 run 커맨드를 &으로 연결해서 수정해봤는데 (& 하나만 쓰면 parallel하게 명령어를 실행한다는 스택오버플로우 글을 보고...) 서버만 켜지고 프론트가 안 켜지는 문제가 있어서 다시 원래 커맨드로 돌려놨다. &&으로 연결했을 때도 동일한 문제가 있었다.

4) 메모리 초과 (요금제 관련)

런 커맨드가 잘 돌아가는 것을 확인한 기쁨도 잠시... 새로운 에러메시지와 마주했다.

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

찾아보니 과도한 메모리 점유로 생긴 문제고 해결책은 더 큰 메모리를 할당하거나 메모리 누수를 개선하는 것이라는데... 이미 하루종일 씨름하느라 지친 나에게 메모리 누수를 개선할 기력은 없었기에 그냥 단순하게 메모리가 더 큰 요금제로 바꿨더니 정상적으로 run 되었다. 몇 불 더 내고 말지라는 마음으로 했으나... 나중에 보수해야지...

5) MongoDB 연결

여기까지 오니까 드디어 프로젝트의 상태가 Healthy로 변했고 url에서 페이지가 정상적으로 배포된 것을 확인할 수 있었다. 그런데... DB와 연결된 페이지가 제대로 작동하지 않았다. F12로 로그를 확인해보니 DB에서 데이터를 읽어올 때 uncaught (in promise) Error: Request failed with status code 502 에러가 난 것이었다. 프록시 문제임은 금방 알았으나 url을 어떻게 수정해도 에러가 사라지지 않았다. 로컬에서 로컬호스트 5000번 포트로 하면 정말 잘 돌아가는데... 도메인을 http로 썼다 https로 썼다 마지막에 /를 붙였다 떼었다 난리를 쳤으나 죽어도 돌아가지 않았다. 모든걸 포기한 심정으로 로컬호스트 그대로 코드를 푸시했는데 웬걸... 잘 돌아간다. 그렇게 모든 문제는 해결되었다...

설명을 덧붙이자면 heroku로 배포할 때는 target에 배포할 도메인 주소를 적어야 작동했기에 이번에도 그렇게 접근했는데 로컬호스트로 세팅하는게 정답이었다. 맨 끝의 /는 이론상 붙이면 안 돌아가야 할 것 같은데 붙여도 돌아가긴 하더라. 그렇지만 빼는게 의도대로 작동하는 것이므로 (/api 부분을 설정한 path - ex. /page로 변경하게 됨) 빼고 적었다.

아래는 client/src/setupProxy.js의 코드다. 파일명을 반드시 setupProxy로 해야하는 걸로 알고 있다.

const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
    app.use(
        createProxyMiddleware('/api', {
            target: 'http://localhost:5000',
            changeOrigin: true,
        })
    );
};

마침내...

이렇게 Koyeb으로 이전하기가 끝이 났다. 그래도 하루 날 잡고 끝낼 수 있어서 다행이었다. 적어두고 보면 정말 사소한 문제들인데 에러 하나 뜰 때마다 왜 이렇게 미칠거 같은지... 프론트만 배포하는 거였으면 netlify로 해도 됐을텐데 db를 포함한 서버라서 Koyeb으로 배포할 수 밖에 없었다. 그래도 좋은 경험이었다. 이제 모바일 최적화랑 메모리 누수만 해결하면 ...

... 미래의 나에게 맡기겠다. 다들 화이팅!

반응형