Mafia Game - 역대글
▶ Next.js Docker로 배포하기
지난번에는 Node.js 서버를 Docker를 활용해서 배포하는 작업을 진행했다. 이번에는 Next.js를 Docker를 통해 배포하는 방법을 알아보려고 한다. 또한, 배포 과정에서 Nginx를 추가로 사용하려고 한다.
Next.js는 Node.js 기반의 서버를 내장하고 있어 React와 달리 Nginx가 배포를 위해서 필수는 아니다. 하지만 Nginx를 도입하면 도메인 설정이 용이하고, CORS 처리를 대신할 수 있어 배포 환경을 더욱 효율적으로 구성할 수 있다. 이러한 이유로 이번 배포에서는 Nginx를 함께 사용할 계획이다.
현재 진행하고 있는 프로젝트를 간단히 소개하면, Node.js 서버를 시용해서 API 통신을 처리하진 않고, WebSocket 통신을 통해 클라이언트 간 소통을 진행한다. 따라서, 이번 배포는 Next.js와 WebSocket 기반의 통신 구조를 고려하여 설정할 것이다.
Next.js 배포
Dockerfile
1. 빌드 단계
FROM node:20.16-alpine3.19 as builder
WORKDIR /builder
COPY package.json yarn.lock ./
RUN yarn install
COPY . .
RUN yarn build
빌드 단계에서는 최종 이미지를 최정화하기 위해 빌드 파일을 구성하기 위한 작업을 수행한다.
- builder 디렉토리를 작업 폴더로 설정한다.
- package.json과 yarn.lock 파일을 복사한 뒤 의존성을 설치한다.
- 프로젝트 파일 전체를 복사한 뒤 yarn build 명령어로 빌드한다.
빌드 단계와 배포 단계를 분리하는 이유는 불필요한 파일과 개발용 의존성을 포함하지 않아 최종 이미지의 용량을 줄이기 위함이다.
2. 배포 단계
FROM node:20.16-alpine3.19 as production
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --production
COPY --from=builder /builder/.next ./.next
COPY --from=builder /builder/public ./public
EXPOSE 3000
CMD ["yarn", "start"]
배포 단계에서는 빌드 결과물을 가져와 실행 가능한 상태로 준비한다.
- app 디렉토리를 작업 폴더로 설정한다.
- package.json과 yarn.lock 파일을 복사해 의존성을 설치한다. 이때 --production 옵션을 사용해 개발용 의존성(devDependencies)를 제외한다.
- 빌드 단계에서 생성된 .next와 public 디렉토리를 복사한다.
- 컨테이너를 3000번 포르를 열고 yarn start 명령어로 Next.js 서버를 싱핸다.
이 방식으로 설정하면 Next.js 애플리케이션을 배포할 수 있다.
FROM node:20.16-alpine3.19 as builder
WORKDIR /builder
COPY package.json yarn.lock ./
RUN yarn install
COPY . .
RUN yarn build
FROM node:20.16-alpine3.19 as production
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --production
COPY --from=builder /builder/.next ./.next
COPY --from=builder /builder/public ./public
EXPOSE 3000
CMD ["yarn", "start"]
docker-compose
Next.js 배포는 Dockerfile로 끝낼 수 있지만, Nginx와의 연계를 위해 docker-compose를 사용하여 설정을 구성한다. 이를 통해 재배포 시 반복적인 명령어 입력을 줄이고 관리 효율성을 높일 수 있다.
version: "3.8"
services:
mafia-client:
build:
context: .
container_name: mafia-client
ports:
- "3000:3000"
environment:
- NODE_ENV=production
networks:
- shared_network
networks:
shared_network:
external: true
- services
실행할 컨테이너 단위를 정의한다.- mafia-client : 배포하려는 Next.js 서버스의 이름이다.
- build : Dockerfile의 위치를 지정하며, 현재 디렉토리(.)를 사용한다.
- container_name : 생성될 컨테이너의 이름이다.
- ports : 컨테이너와 호스트 간 네트워크 포트를 연결하며, 여기서는 3000:3000으로 설정한다.
- environment : 환경 변수를 설정한다. NODE_ENV=production은 명시적으로 설정했지만, Next.js에서 yarn start를 실행하면 기본적으로 프로덕션 모드로 동작하므로 크게 중요하지 않다.
- networks
컨테이너 간의 네트워크를 연결하는 데 사용된다.- shared_network : Nginx와 Next.js 컨테이너가 동일한 네트워크에서 통신할 수 있도록 설정한다.
- external: true : 이미 존재하는 네트워크를 사용하도록 지정했다.
Next.js 컨테이너는 기본적으로 Nginx 컨테이너의 네트워크 정보를 알 수 없다. Nginx는 특정 도메인의 80번 포트에 연결된 요청을 Next.js 컨테이너로 라우팅해야 하며, 이를 위해 동일한 네트워크로 연결해야 한다.
shared_network 설정을 통해 두 컨테이너가 서로 통신할 수 있는 환경을 제공한다.
Node.js 설정
앞서 Node.js 처리를 진행했지만 networks 설정과 재배포를 간단하게 하기 위해서 docker-compose를 추가해준다.
version: "3.8"
services:
mafia-server:
build:
context: .
container_name: mafia-server
ports:
- "4000:4000"
networks:
- shared_network
networks:
shared_network:
external: true
CORS 설정을 위해서 Node.js 서버도 Nginx와 동일한 네트워크를 공유해야 할 필요가 있기 때문에 다음과 같이 설정했다.
Nginx 설정
Conf 파일
1. 트래픽 설정
listen 80;
server_name [domain];
- listen 80 : 80번 포트에서 요청을 수신한다.
- server_name : 도메인을 설정하며, 하나 또는 여러 도메인을 설정할 수 있다.
2. Next.js 서버 설정
location / {
proxy_pass http://mafia-client:3000;
proxy_http_version 1.1;
}
- location / : 80번 포트의 / 경로 요청을 처리한다.
- proxy_pass
/ 경로로 접근하면 mafia-client의 3000번 포트로 프록시 요청을 전달한다.
이를 위해 Docker Compose에서 설정한 공통 네트워크(shared_network)가 필요하다. - proxy_http_version 1.1
HTTP/1.1 이상에서만 웹소켓 프로토콜을 지원하므로 명시적으로 설정한다.
3. Node.js 설정
location /socket.io {
proxy_pass http://mafia-server:4000;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
# CORS 설정 (특정 도메인만 허용)
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
# OPTIONS 요청에 대한 처리
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
}
- location /socker.io : /socket.io 경로에서 웹소켓 요청을 처리한다.
- proxy_pass : 요청을 mafia-server의 4000번 포트로 전달한다.
- 웹소켓 설정
- proxy_http_version 1.1
웹소켓 프로토콜은 HTTP/1.1 이상에서만 지원된다. - proxy_set_header Upgrade $http_upgrade
클라이언트가 웹소켓 연결을 요청하면, HTTP 요청의 Upgrade 헤더가 설정된다.
이 헤더를 Nginx가 벡엔드 서버로 전달해 웹소켓 연결로 업그레이드가 가능하도록 한다. - proxy_set_header Connection 'upgrade'
HTTP/1.1에서는 Connection 헤더가 'upgrade'로 설정되어야 웹소켓 연결로 전환할 수 있다. - proxy_cache_bypass $http_upgrade
웹소켓 요청은 상태 유지(stateful) 통신이기 때문에, 캐시를 우회해야만 백엔드와 실시간 통신이 가능하다.
- proxy_http_version 1.1
- CORS 설정 : 모든 출처에서 요청을 허용하며, 허용 메서드 및 헤더를 명시한다.
- OPTIONS 요청 처리 : 프리플라이트 요청에 대한 응답을 반환하며, 상태 코드 204로 처리한다.
OPTIONS
브라우저에서 CORS 요청이 발생할 때, Preflight Request라는 요청이 전송된다.
Preflight Request는 클라이언트(브라우저)가 서버에 요청을 보내기 전에 서버가 특정 조건을 수락할 준비가 되었는지 확인하는 과정이다. 이 요청은 OPTIONS 메서드로 전송된다.
204 No Content는 성공적인 요청이지만 응답 본문이 필요하지 않음을 나타낸다.
OPTIONS 요청은 응답을 요구하지 않기 때문이다.
server {
listen 80;
server_name seojaewan.com www.seojaewan.com;
# 마피아 게임 서버
location / {
proxy_pass http://mafia-client:3000;
proxy_http_version 1.1;
}
location /socket.io {
proxy_pass http://mafia-server:4000;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
# CORS 설정 (특정 도메인만 허용)
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
# OPTIONS 요청에 대한 처리
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
}
}
Dockerfile
FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
- FROM nginx:alpine : Nginx의 경량화된 alpine 기반 이미지를 사용해 컨테이너 크기를 최소화한다.
- COPY nginx.conf /etc/nginx/conf.d/default.conf
- 로컬에서 작성한 nginx.conf파일을 컨테이너 내부의 Nginx 설정 디렉토리로 복사한다.
- default.conf는 기본적으로 사용되는 설정 파일 이름이다. 기존 기본 설정을 덮어쓴다.
docker-compose
version: "3.8"
services:
nginx:
build:
context: .
container_name: nginx-proxy
ports:
- "80:80"
networks:
- shared_network
networks:
shared_network:
external: true
마지막으로 docker-compose 작성까지 끝나면 배포를 위한 준비는 모두 끝나게 된다.
배포하기
1. 네트워크 만들기
docker network create shared_network
- Nginx, Next.js 서버, Node.js 서버가 동일한 네트워크에서 통신하도록 설정한다.
- Docker Compose 파일에서 external: true로 설정된 네트워크를 직접 생성해야 한다.
2. 배포
docker-compose up --build -d
- --build: 각 서비스의 이미지를 빌드한다.
- -d: 백그라운드에서 컨테이너를 실행한다.
- 이 명령어로 모든 컨테이너(Nginx, Next.js, Node.js)가 실행되고 배포가 완료된다.
3. 네트워크 확인
docker network inspect shared_network
- 생성된 네트워크에 컨테이너가 제대로 연결되었는지 확인한다.
- 네트워크에 Nginx, Next.js, Node.js 총 3개의 컨테이너가 포함되어야 한다.
{
"Containers": {
"nginx-proxy": { ... },
"mafia-client": { ... },
"mafia-server": { ... }
}
}
4. 재배포
기존 컨테이너 내리기
docker-compose down
- 현재 실행 중인 모든 Docker Compose 컨테이너와 네트워크를 종료 및 제거한다.
새로 배포
기존 컨테이너를 종료한 후, 다시 배포를 진행한다:
docker-compose up --build -d
- --build 옵션을 사용하여 이미지를 다시 빌드한다.
- 모든 서비스가 최신 상태로 배포된다.
'Next.js > 실험실' 카테고리의 다른 글
첫 번째 토이 프로젝트를 완료하고 (3) | 2024.12.22 |
---|---|
XSS with Editor (1) | 2024.10.26 |
Next.js metadata (1) | 2024.07.10 |
Next.js Loading.js & Suspense (1) | 2024.07.07 |
Next.js Layout.js (1) | 2024.07.04 |