11. Ubuntu 환경 pm2 / nginx / MongoDB / Next.js 프로젝트 배포

박준혁·2023년 6월 7일
0
post-thumbnail

프로젝트 제작의 최종 목표인 서버를 제작해, 해당 서버에서 배포하는 과정을 진행해보고자 한다. 굉장히 중요한 과정이면서도 가장 어려웠던 과정이라고 말할 수 있을 것 같다. Ubuntu 환경을 모르는 것은 아니지만 익숙하지 않았고, 개발 과정에서 에러가 발생하는 것과 달리 배포 과정은 정말 다양한 시행착오가 있기 때문이다. 그럼 어떠한 방식으로 배포 과정이 이루어졌는지 정리해보고자 한다.

최종 결과는 http://www.snuvess.kro.kr 이다.

Ubuntu 환경 세팅

Ubuntu 설치

먼저 동아리 방에 서버로 사용될 컴퓨터를 세팅해야 한다. 서버용 컴퓨터를 따로 이용하면 좋겠지만 아쉽게도 동비가 여유롭지는 않아 컴퓨터를 사기는 어려웠다. 따라서 집에서 사용하지 않는 노트북을 가져와서 동아리방에 설치한 뒤에 사용하기로 했다.
기본 노트북의 OS는 윈도우 베이스여서 파티션을 분리하여 Ubuntu를 따로 설치하여야 했다. 해당 과정은 아래 링크의 글을 참고하였다.

윈도우 파티션 분리 후 Ubuntu 설치

환경 설정

Ubuntu를 설치한 뒤 가장 중요한 것은 홈페이지 서버로 활용해야하기 때문에 고정 IP 설정이라고 생각한다. 그 외 다른 것들도 추가로 설치해보고자 한다.

IP 설정

먼저 IP 설정이다. 학교에서 IP는 정보화본부 사이트에서 직접 신청을 통해 받을 수 있다. 아래와 같은 사이트에서 신청하여 고정 IP를 받을 수 있었다.

신청을 통해 얻은 IP 주소를 바탕으로 고정 IP를 설정하면 된다. 직접 네트워크 관련 파일을 이용해 변경해도 되지만 나는 설정에서 네트워크 변경 탭에서 직접 고정 IP를 설정하는 방법을 이용하였다. 네트워크 연결에서 주어진 고정 IP 주소와 다른 것들을 입력하면 인터넷 연결이 원활하게 됨을 확인할 수 있었다.

VS Code 설치

코드를 수정할 때 사용하는 VS Code는 비교적 간단하다. 공식 홈페이지에서 해당 설치 파일을 받아서 설치하면 된다.

Node.js 설치

Node.js를 설치하는 과정은 아래와 같다. 해당 과정에서 curl을 사용할 때 setup 뒤 숫자를 변경하면 버전을 다르게 설치할 수 있다.

$ sudo apt-get update
$ sudo curl -sL https://deb.nodesource.com/setup_20.x | sudo -E bash -
$ sudo apt-get install -y nodejs

Git (Project Clone)

다음은 Node.js를 설치한 것을 바탕으로 Git에서 Project 파일을 Pull하여 컴퓨터에 세팅하는 과정을 거쳤다. git clone 명령어를 이용해 다음과 같이 프로젝트를 복제해 가져올 수 있다.

git clone [REPO_URL]

해당 프로젝트를 앞서 설치한 VS Code를 이용해 열면 개발한 Project가 잘 나타남을 확인할 수 있다. 이때 터미널을 열어 프로젝트에 next.js를 install 해야한다.

npm install next

이후 npm run dev를 실행하면 프로젝트가 잘 나타남을 확인할 수 있고, npm run build 명령어를 통해 배포할 프로젝트를 build 하면 된다.

pm2

pm2는 node.js 기반으로 한 앱을 바탕으로한 프로세스 관리를 간단하게 수행할 수 있도록 도와준다. pm2를 이용하면 '무중단 서비스'라고 이야기하는 배포를 진행할 수 있고, 프로세스가 중단 없이 계속 실행할 수 있도록 도와주는 기능을 수행한다고 이해하면 된다.

pm2 설치 방법

pm2는 npm으로 간단하게 설치할 수 있다.

$ npm install pm2 -g

pm2 실행 방법

pm2를 실행하는 방법은 아래와 같다. 먼저 프로젝트 디렉토리로 이동한 다음에 npm 명령어를 포함하여 pm2 start를 진행하면 된다. SERVICE_NAME이라고 작성된 이름으로 프로세스가 시작되게 되고, 지금까지 개발 과정에서 이용한 npm run dev와 같은 역할을 자동으로 수행한다.

pm2 start npm --name "SERVICE_NAME" -- start

만약 실행중인 프로세스를 제거하고자 할 때는 아래와 같은 방법을 사용한다.

pm2 delete "SERVICE_NAME"

지금까지의 과정이 잘 마무리되었다면 아래와 같이 홈페이지 접근이 가능한 것을 확인할 수 있다. IP주소:3000를 입력했을 때 홈페이지가 정상적으로 나타나면 성공이다. 이를 통해 현재 3000 포트에서 프로세스가 정상적으로 구동되고 있음을 확인할 수 있다.

Nginx

앞선 과정을 통해 제작한 프로젝트가 3000포트에서 정상적으로 작동함을 확인할 수 있었다. 그러나 웹사이트에 방문하는 과정에서 사람들이 모두 IP와 :3000 포트 번호를 입력하고 들어올 수 없기 때문에 일반 http 방식으로 접근했을 때의 port 번호인 80 포트와 연결시키는 과정이 필요하다. 그 역할을 수행하는 용도로 Nginx를 사용하였다.

Nginx 설치

nginx는 아래 명령어를 통해 설치한다.

$ sudo apt-get install -y nginx 

Nginx 사용 방법

이제 Nginx를 사용하기 위해서 몇 가지 세팅을 진행해야 한다. 먼저 아래와 같은 명령어를 통해 default 파일로 접근한다.

$ sudo nano /etc/nginx/sites-enabled/default

파일을 아래와 같이 편집한다.

  • listen : LISTEN할 PORT 번호를 적는다. (http 연결을 위해 80포트 작성)
  • server_name : 사용할 도메인 명 등을 작성한다. (우선 도메인이 따로 없어 IP 주소를 넣어주었다.)
  • proxy_pass : Nginx에서 위에 작성한 주소로 들어온 요청을 자동으로 넘겨줄 목적지의 위치를 적는다. (현재 프로젝트는 3000번 포트에서 수행하고 있으므로 IP주소:3000 으로 위치를 작성한다.)
// /etc/nginx/sites-available/default
server {
  listen 80 default;
  listen [::]:80 default;

  server_name  147.46.36.133;


  location / {
    proxy_pass http://147.46.36.133:3000;
  }
}

위 파일을 저장하고 아래 코드를 입력해 테스트를 진행한다.

$ sudo nginx -t

마지막으로 아래 코드를 통해 nginx service를 시작할 수 있다.

$ sudo service nginx start
$ sudo service nginx status //nginx 상태 확인 가능

status를 확인해서 잘 실행되고 있다면 아래와 같이 IP 주소만 입력해도 이번에는 화면이 잘 나타남을 확인할 수 있다.

NextAuth URL

그러나 이렇게 프로젝트가 잘 진행되는 과정에서도 한 가지 문제점이 발생하였다. 바로 Log In을 진행할 때 자동으로 localhost:3000으로 이동한다는 것이다. 앞서 배포 과정을 진행한 Vercel에서는 NextAuth의 설정을 자동 변경하였지만, 이번에는 수동으로 제작해야한다. 따라서 NextAuth를 사용할 때 3000번 포트로 이동하는 것이 아닌 어떻게 하면 기존에 만들어 둔 URL을 기초로 할 수 있는지 찾아보았고 아래와 같이 NextAuth를 적용할 URL을 따로 설정할 수 있음을 확인할 수 있었다.

NextAuth URL 설정 방법

수동으로 NextAuth를 적용할 URL을 설정하는 방법이다.
루트 경로에 .env 파일을 만들어 준다. 해당 파일에 다음과 같이 작성한다.

NEXTAUTH_URL=http://147.46.36.133

이렇게 작성하면 NextAuth가 작동할 때 자동으로 URL을 적용시켜 준다. 아래는 해당 내용을 담고 있는 공식 문서이다.
NEXTAUTH-URL Document

도메인 추가

원래는 IP 주소를 신청하는 정보화본부 홈페이지에서 .snu.ac.kr 도메인을 받을 수 있다고 해서 신청 후 대기하고 있었지만, 담당 교수님께서 도메인 허가 연락이 잘 안되어서.....ㅠㅠ 아쉽게도 우선 무료 도메인을 받아 임시로 사용하기로 하였다. (나중에 학교 도메인 받으면 그 때 바꾸기로 하자)
무료 도메인은 (링크)에서 받았다. 나는 www.snuvess.kro.kr이라는 도메인을 받았다. 앞에서 설정한 nginx의 default 파일을 다음과 같이 변경하였다. (도메인 이름 추가)

// /etc/nginx/sites-available/default
server {
  listen 80 default;
  listen [::]:80 default;

  server_name  www.snuvess.kro.kr snuvess.kro.kr;


  location / {
    proxy_pass http://147.46.36.133:3000;
  }
}

해당 과정을 통해 이제 도메인 이름으로도 잘 접근할 수 있음을 확인할 수 있다.

(추가) 외부 80 PORT 접근 허가 과정

원래라면 이렇게 설정했을 때 모두 잘 접근이 가능해야 한다. 그러나 이상한 점이 하나 발견되었다. 바로 학교 내부 인터넷을 연결했을 때는 정상적으로 접근이 가능하지만 학교 외부 인터넷을 연결했을 때는 접근이 불가능하다는 것이다. 더 이상한 것은 포트 3000번을 붙였을 때는 내부, 외부 무관하게 모두 접근이 가능하다는 것이다. 해당 사항에 의문을 가지고 좀 알아본 결과 학교 자체적으로 보안을 위해 학외에서 80포트 접근을 차단하고 있음을 알게 되었다. (정보화본부 페이지를 더 잘 볼걸...) 다행이도 보안 조건을 만족하면 80포트 개방 신청을 할 수 있었다. 따라서 학외에서 접근하는 80포트 개방을 신청하여 웹 사이트 보안 점검, 개인 정보 포함 여부 점검, robots.txt 제작을 통해 허가를 얻을 수 있었다. (이번에는 지도 교수님 승인이 빠르게 허가되었다. 도메인은 언제쯤....) 보안 절차 진행 과정에서 알게 된 몇 가지 사항을 작성해보고자 한다. 대부분은 가이드대로 하면 해결되었지만 좀 어려웠던 보안 사항에 대한 것이다.

Referrer policy

마찬가지로 보안과 관련된 헤더 사항이다. 구체적인 것은 공식 문서를 통해 확인하면 될 것 같고, 우선 나는 "strict-origin-when-cross-origin" 헤더를 포함할 수 있도록 제작하는 것이 목표이다
먼저 직접 헤더에 해당 내용을 추가해보았지만, 실제로 실행시켜보았을 때는 헤더가 나타나지 않는 문제가 발생하였다. 따라서 Next.js에서 어떻게 적용시킬 수 있는지 확인하던 와중 다음과 같은 문서를 발견, 아래와 같이 해결할 수 있었다.

//next.config.js
async headers(){
    return [
      {
        source: '/',
        headers : [
          {
          key: 'Referrer-Policy',
          value: 'strict-origin-when-cross-origin'
          }
        ]
      }
    ]
  }

그 결과 헤더에서 해당 내용이 바로 나타나지 않지만, 개발자 도구에서 Network 항목에 들어갔을 때 다음과 같은 항목을 찾을 수 있었다.

Content-Security-Policy

다음도 보안과 관련된 항목이다. 허용된 항목들의 script만을 실행할 수 있도록 설정하는 것이다. (Content-Security-Policy 자세한 설명)
Nginx에서 이를 설정하면 된다고 해서 Nginx 문서를 참고해 적용시키고자 하였다. 앞서 제작한 default 파일에 add_header를 이용해 적용시킬 수 있다는 것이다. 이 방식을 이용해서 적용시키던 중 Next.js에서는 다른 방식으로도 헤더를 추가할 수 있다는 것을 알았다. 위와 굉장히 동일한 방식이었다. Next.js CSP 방식 문서

//next.config.js
async headers(){
    return [
      {
        source: '/',
        headers : [
          {
              key: 'Content-Security-Policy',
              value:
                "default-src 'self';  script-src 'self'; object-src 'self'; frame-ancestors 'self';",
          },
        ]
      }
    ]
  }

앞선 것과 굉장히 유사하다. 마찬가지로 직접 해당 내용이 헤더에 나타나지는 않지만 네트워크 항목을 확인하면 다음과 같이 나타나는 것을 확인할 수 있다.

robots.txt

마지막은 크롤링과 관련된 robots.txt 이다. 검색 엔진에서 크롤링으로 정보를 수집할 때 허용하는 범위를 설정할 수 있다. (robots.txt 자세한 설명) 해당 파일은 프로젝트의 public 폴더 내부, robots.txt로 제작하면 되고 아래와 같이 작성하였다.

// /public/robots.txt
User-agent: *
Disallow: /post

최종적으로 해당 보안 정책을 모두 지나 80포트에 대한 학외 접근 허가를 얻을 수 있었고 이 승인을 받고 나니 외부 네트워크에서도 잘 접근이 됨을 확인할 수 있었다.

MongoDB

추가적으로 MongoDB도 서버 컴퓨터를 사용해서 정보를 저장하면 좋을 것이라고 생각하여 서버 컴퓨터의 MongoDB도 설정하여 보았다. 지금은 MongoDB에서 온라인으로 무료로 제공하는 서비스를 이용하고 있기 때문에 많은 정보가 저장되면 과금의 우려도 있어 우선은 Ubuntu 환경에서 MongoDB를 설정해 보았다.

MongoDB 설치

아래와 같은 코드로 설치할 수 있으며, 자세한 내용은 공식 문서를 참고하면 된다.

$ wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add -
$ echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list
$ sudo apt-get install -y mongodb-org

MongoDB 실행

$ sudo systemctl start mongod
$ sudo systemctl status mongod //실행 상태 확인

status 코드를 입력했을 때 active로 나타나면 정상적으로 작동하는 것이며, 조금 더 편리한 화면으로 데이터 관리를 원하면 MongoDB Compass를 설치하면 된다.

MongoDB Compass 설치법
$ wget https://downloads.mongodb.com/compass/mongodb-compass_1.37.0_amd64.deb
$ sudo dpkg -i mongodb-compass_1.37.0_amd64.deb
$ mongodb-compass

MongoDB를 실행하면 자동으로 27017 포트에서 실행됨을 확인할 수 있다. 따라서 이 포트를 기반으로 Next.js 프로젝트의 DB 코드를 변경하면 된다. database에 접근하는 url을 localhost의 27017 포트로 변경해주면 정상적으로 서버 컴퓨터에서 작동하는 MongoDB에 연결됨을 확인할 수 있다.

// /util/database.js
const url = 'http://localhost:27017/admin'

(추가) MongoDB 중단과 그 원인 (용량 초과)

code=exited, status=1 (용량 문제)

MongoDB가 정상적으로 작동하다가 어느날 code=exited, status=1과 함께 중단되는 상황이 발생하였다. log 기록을 훑어보던 도중 용량이 해당 원인임을 알게 되었다. 확인해보니 '/' 디렉토리의 할당된 용량이 100% 차있었다. Ubuntu를 Window와 함께 사용하며 파티션이 분할되어 여유가 없는 것은 알고 있었지만 과도하게 많이 사용되었다는 생각에 용량 초과 원인을 파악하기 시작했다.
분석 결과 /var/log 디렉토리에 log 파일이 과하게 쌓였음을 알게 되었다. 다양한 log file이 있었고, 일부 log는 과거의 log 정보들이 .gz 형태로 압축되어있었는데 엄청난 용량을 차지하고 있었다. 해당 파일들을 일부 삭제하고 용량이 확보된 상태에서 다시 실행하였더니 stauts=1이 아닌 48 오류로 이동하였다. 우선 stauts=1의 원인은 용량 문제였고, 용량 부족으로 인해 중단되었음을 확인했다. 앞으로 log는 주기적으로 삭제할 계획이고 다음에 자동으로 log를 삭제하는 기능을 추가해야겠다.

code=exited, status=48 (포트 문제)

status=48의 문제는 대부분이 Port와 관련되었다. 이미 27017 port가 사용 중이라는 문제이다. 아마도 문제를 해결하면서 restart를 많이 하면서 발생한 문제 같다. compass에서는 또 mongoDB가 잘 나타나는데, systemctl status를 호출하면 active가 나타나지 않는 것으로 보아 포트를 중단시키고 다시 시작하는 것이 최선이라고 생각하였다. 따라서 아래 코드를 입력해 port kill을 진행하였다.

npx kill-port 27017

이후 systemctl start mongod를 통해 다시 active 상태로 잘 시작됨을 확인할 수 있었고, 문제를 해결하였다.

마치며

아마도 이 과정이 개발 전체를 합친 것만큼 어려웠던 것 같다. 여기에는 모든 에러들을 작성하지는 않았지만 개발 과정에서는 더 많은 에러들이 발생했었다. 따라서 에러들을 하나 하나 구글링 해보기도 하고 잘 작동되는지 확인해보고, 또 학교 정보화본부, 지도 교수님 연락까지 힘든 작업이었다. 그래도 우선 배포가 잘 마무리 된 것에 감사하고, Vercel로만 배포를 진행했다면 아쉬움이 남았을 텐데 직접 서버를 개발해 배포를 진행한 것이 좋았다.

0개의 댓글