nginx loadbalance 설정하기
환경
linux - ubuntu 20.04 LTS
react, node, docker, docker-compose, nginx, npm 설치 상태
방식
react 프론트 서버는 도커 이미지로 빌드하여 개별 실행
nodejs 백 서버는 도커컴포즈를 이용하여 3개 실행
8081,8082,8083 포트를 8002번과 매핑
컨테이너로 실행 된 nginx가 react의 요청을 받아 node 서버로 분산
FRONT 서버 설정 방법
git clone을 이용해 깃허브에 저장 된 소스를 복제
lapoem_front 폴더에서 도커 빌드 실행
$ docker build -t lapoem_front . 명령어로 리눅스 해당 프로젝트 폴더에서 실행
Dockerfile 코드
# 빌드 스테이지
FROM node:18 as build
# 작업 디렉토리 설정
WORKDIR /app
# package.json 파일과 의존성 설치
COPY package.json package-lock.json ./
RUN npm install && npm cache clean --force
RUN npm install --save-dev @babel/plugin-proposal-private-property-in-object && npm install
# 나머지 소스코드 복사 및 빌드
COPY . .
RUN npm run build
# 최종 실행 이미지 (Nginx)
FROM nginx:1.23-alpine
# Nginx 설정 디렉토리 설정
WORKDIR /usr/share/nginx/html
# 기존 Nginx 기본 파일 삭제
RUN rm -rf ./*
# 빌드된 정적 파일만 복사
COPY --from=build /app/build .
# Nginx 설정 파일 복사
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 포트 설정
EXPOSE 80
# Nginx 데몬 실행
ENTRYPOINT ["nginx", "-g", "daemon off;"]
docker run -d -p 3002:80 lapoem_front 명령어로 실행
9seebird.site:3002로 접속하면 프론트 페이지 접속 완료
BACK 서버 설정 방법
nodejs로 작성
react와 마찬가지로 git clone을 통해 깃허브 레포지토리에 있는 소스를 복제
Dockerfile 작성
Dockerfile 내용
# 베이스 이미지 설정
FROM node:alpine3.18 as build
# 작업 디렉토리 설정
WORKDIR /app
# package.json 및 package-lock.json 복사 및 의존성 설치
COPY package.json package-lock.json ./
RUN npm install --only=production && npm cache clean --force
# .env 파일을 컨테이너로 복사 (필요한 경우)
COPY .env ./
# 애플리케이션 소스 코드 복사
COPY . .
# 애플리케이션에서 사용하는 포트를 노출
EXPOSE 8002
# 애플리케이션 시작 명령어
CMD ["npm", "start"]
docker-compose.yml 파일 작성
docker-compose.yml 코드
version: '3.3' # Docker Compose 파일 버전 정의
services:
# Node.js 애플리케이션 컨테이너 정의
node-app1:
build:
context: . # Dockerfile이 있는 현재 디렉터리를 컨텍스트로 설정
dockerfile: Dockerfile # 빌드에 사용할 Dockerfile 지정
ports:
- "8081:8002" # 호스트 포트 8081 -> 컨테이너 내부의 8002 포트로 매핑
networks:
- app-network # 컨테이너가 연결될 네트워크 지정
node-app2:
build:
context: . # Dockerfile이 있는 현재 디렉터리를 컨텍스트로 설정
dockerfile: Dockerfile # 빌드에 사용할 Dockerfile 지정
ports:
- "8082:8002" # 호스트 포트 8082 -> 컨테이너 내부의 8002 포트로 매핑
networks:
- app-network # 컨테이너가 연결될 네트워크 지정
node-app3:
build:
context: . # Dockerfile이 있는 현재 디렉터리를 컨텍스트로 설정
dockerfile: Dockerfile # 빌드에 사용할 Dockerfile 지정
ports:
- "8083:8002" # 호스트 포트 8083 -> 컨테이너 내부의 8002 포트로 매핑
networks:
- app-network # 컨테이너가 연결될 네트워크 지정
# Nginx 로드밸런서 컨테이너 정의
nginx:
image: nginx:latest # 사용할 Nginx 이미지를 Docker Hub에서 가져옴
ports:
- "8002:80" # 호스트 포트 8002 -> 컨테이너 내부의 80 포트로 매핑
volumes:
- ./test-nginx.conf:/etc/nginx/nginx.conf # Nginx 설정 파일을 컨테이너 내부로 마운트
networks:
- app-network # 컨테이너가 연결될 네트워크 지정
# 네트워크 정의
networks:
app-network:
driver: bridge # 네트워크 드라이버를 bridge로 설정 (컨테이너 간 통신 가능)
설명 GPT 참고
Node.js 애플리케이션 컨테이너 (node-app1, node-app2, node-app3):
각 컨테이너는 동일한 Node.js 애플리케이션을 실행
호스트 포트(8081, 8082, 8083)를 통해 각각의 컨테이너 내부 포트(8002)와 통신 가능
app-network라는 네트워크를 사용해 Nginx와 통신 가능
Nginx 로드밸런서 컨테이너 (nginx):
Nginx가 로드밸런서를 수행하며, Node.js 애플리케이션에 요청을 분산합니다
호스트 포트 8002를 통해 외부에서 접속 가능
Nginx 설정 파일(test-nginx.conf)을 컨테이너 내부의 /etc/nginx/nginx.conf에 연결하여 Nginx 설정을 정의
네트워크 (app-network):
모든 컨테이너가 동일한 네트워크(app-network)에 연결되어 서로 통신 가능
bridge 드라이버는 기본적으로 로컬 호스트에서 컨테이너 간 통신을 허용
docker-compose에서 같은 위치에 있는 nginx.conf 파일을 컨테이너 내부로 복사하기 때문에
nginx.conf 파일도 작성해주어야 함
nginx.conf 파일 코드
# events 블록: 네트워크 연결 설정 (필수지만 현재는 비어 있음)
events {}
# http 블록: HTTP 요청 처리 관련 설정
http {
# upstream 블록: Node.js 애플리케이션 서버들을 그룹화하여 로드밸런싱
upstream node_servers {
server node-app1:8002; # Docker Compose 내부 네트워크에서 Node.js 컨테이너 1 접근
server node-app2:8002; # Docker Compose 내부 네트워크에서 Node.js 컨테이너 2 접근
server node-app3:8002; # Docker Compose 내부 네트워크에서 Node.js 컨테이너 3 접근
}
# log_format: 요청 로그의 형식 정의
log_format upstreamlog '$remote_addr - $remote_user [$time_local]' # 클라이언트 IP, 사용자, 시간 정보
'"$request" $status $body_bytes_sent' # 요청 URI, HTTP 상태 코드, 전송된 바이트
'"$http_referer" "$http_user_agent"' # 참조 URL, 사용자 에이전트
'to: $upstream_addr'; # 요청이 전달된 upstream 서버 주소
# access_log: 로그 파일 경로와 사용할 로그 형식 설정
access_log /var/log/nginx/upstream-access.log upstreamlog;
# server 블록: 서버의 기본 설정
server {
listen 80; # Nginx가 80 포트에서 HTTP 요청을 수신
server_name 9seebird.site; # 요청의 호스트명이 9seebird.site인 경우 처리
# location 블록: 경로별 요청 처리 방식 정의
location / {
proxy_pass http://node_servers; # 요청을 upstream 블록에 정의된 Node.js 서버로 전달
proxy_http_version 1.1; # HTTP 버전 설정
proxy_set_header Upgrade $http_upgrade; # WebSocket 업그레이드 요청 처리
proxy_set_header Connection 'upgrade'; # 연결 업그레이드 처리
proxy_set_header Host $host; # 원래 요청의 호스트 헤더 전달
proxy_cache_bypass $http_upgrade; # 캐시를 우회하도록 설정
}
# /stub_status 경로에 대한 처리: Nginx 상태 정보를 제공
location /stub_status {
stub_status; # Nginx의 간단한 상태 정보를 반환 (활성 연결, 요청 등)
allow all; # 모든 요청을 허용 (보안을 위해 IP 제한 설정 가능)
}
}
}
코드 설명 GPT 참고
upstream 블록:
Node.js 서버를 묶어 로드밸런싱을 수행
요청을 Node.js 컨테이너 중 하나로 분배
log_format 및 access_log:
요청 정보를 포맷팅하여 /var/log/nginx/upstream-access.log에 기록
요청 로그에 어떤 Node.js 컨테이너로 전달되었는지 기록($upstream_addr 포함
server 블록:
Nginx가 80번 포트에서 들어오는 HTTP 요청을 처리
server_name으로 지정된 호스트명을 기준으로 요청을 처리
location /:
기본 경로(/)로 들어오는 요청을 Node.js 서버로 전달
WebSocket 업그레이드 헤더 처리와 캐시 우회 설정도 포함
location /stub_status:
Nginx의 상태 정보를 제공하는 경로
보안을 위해 나중에 허용 IP를 제한하는 것 추천
도커 컴포즈 실행 명령어
docker-compose up -d : docker-compose.yml 파일이 있는 곳에서 실행해야 함
docker-compose.yml 파일 내용이 변경되었을 경우 docker-compose down 명령어 입력 후 다시 실행
k6로 트래픽 분산 테스트
k6를 직접 설치해서 사용해도 되고 도커 컨테이너로 사용해도 된다
Grapane, Prometheus 적용 docker-compose.yml 코드
version: '3.3' # Docker Compose 파일의 버전 정의
services:
# Node.js 애플리케이션 1번 컨테이너 설정
node-app1:
build:
context: . # 현재 디렉토리를 컨텍스트로 사용
dockerfile: Dockerfile # Dockerfile을 사용해 이미지 빌드
ports:
- "8081:8002" # 호스트의 8081 포트를 컨테이너의 8002 포트에 연결
networks:
- app-network # app-network 네트워크에 연결
# Node.js 애플리케이션 2번 컨테이너 설정
node-app2:
build:
context: . # 현재 디렉토리를 컨텍스트로 사용
dockerfile: Dockerfile # Dockerfile을 사용해 이미지 빌드
ports:
- "8082:8002" # 호스트의 8082 포트를 컨테이너의 8002 포트에 연결
networks:
- app-network # app-network 네트워크에 연결
# Node.js 애플리케이션 3번 컨테이너 설정
node-app3:
build:
context: . # 현재 디렉토리를 컨텍스트로 사용
dockerfile: Dockerfile # Dockerfile을 사용해 이미지 빌드
ports:
- "8083:8002" # 호스트의 8083 포트를 컨테이너의 8002 포트에 연결
networks:
- app-network # app-network 네트워크에 연결
# Nginx 로드밸런서 설정
nginx:
image: nginx:latest # 최신 Nginx 이미지를 사용
ports:
- "8002:80" # 호스트의 8002 포트를 컨테이너의 80 포트에 연결
volumes:
- ./test-nginx.conf:/etc/nginx/nginx.conf # 로컬 test-nginx.conf를 컨테이너의 Nginx 설정 파일로 매핑
networks:
- app-network # app-network 네트워크에 연결
# Prometheus 설정 (모니터링 도구)
prometheus:
image: prom/prometheus:latest # 최신 Prometheus 이미지를 사용
ports:
- "9090:9090" # 호스트의 9090 포트를 컨테이너의 9090 포트에 연결
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml # 로컬 prometheus.yml 파일을 컨테이너 설정 파일로 매핑
networks:
- app-network # app-network 네트워크에 연결
# Grafana 설정 (데이터 시각화 도구)
grafana:
image: grafana/grafana:latest # 최신 Grafana 이미지를 사용
ports:
- "3000:3000" # 호스트의 3000 포트를 컨테이너의 3000 포트에 연결
networks:
- app-network # app-network 네트워크에 연결
# 네트워크 설정
networks:
app-network:
driver: bridge # 브리지 네트워크를 사용하여 컨테이너 간 통신을 가능하게 설정
설명 GPT 참고
Node.js 애플리케이션:
각각의 컨테이너가 8081, 8082, 8083 포트를 통해 로드밸런싱 대상 서버로 동작
app-network 네트워크를 통해 Nginx와 통신
Nginx:
Nginx가 8002 포트에서 요청을 받고, test-nginx.conf 설정을 사용해 로드밸런싱을 수행
Node.js 컨테이너로 요청을 분배
Prometheus:
9090 포트에서 실행되며, Prometheus 구성 파일(prometheus.yml)을 통해 메트릭을 수집
Nginx Exporter와 연동하여 Nginx 메트릭을 수집
Grafana:
3000 포트에서 실행되며, Prometheus에서 수집된 메트릭 데이터를 시각화
웹 인터페이스를 통해 데이터 대시보드를 구성
네트워크:
app-network는 모든 컨테이너가 연결되어 서로 통신할 수 있도록 설정된 브리지 네트워크