NPM Build
React / Vue 프론트엔드 프로젝트를 빌드 하기 위해선 npm run build 명령어를 통해 프로젝트를 빌드하기 위한 정적 빌드파일을 만들어야 합니다.
로컬에서 간단하게 테스트를 해보겠습니다.
NodeJS가 설치된 환경에서 React / Vue 프로젝트 루트에서 npm i로 의존성을 설치한 뒤 npm run build를 진행합니다.
빌드 명령어를 실행하면 루트 디렉토리 / build 프로젝트안에 배포를 위한 index.html 및 정적 파일이 빌드 되는것을 확인할 수 있습니다. build 폴더를 배포하는 방법에 따라 배포 방식이 두가지로 나뉠 수 있습니다.
1. Docker 컨테이너로 배포
- 빌드한 정적 파일을 Dockerfile을 통해 도커 이미지로 만들고 컨테이너로 프론트엔드를 띄우는 방식
- 클라우드 환경에서 애플리케이션을 쉽게 배포 가능, 확장성 이식성 높음
- CI/CD 파이프라인을 구축하여 지속적으로 변경 사항을 반영하고 배포할 때 유리
2. Nginx로 정적 파일 서빙
- 빌드한 정적 파일을 nginx를 통해 직접 서빙하는방식
- 단순한 배포가 필요한 경우, 특히 클라우드가 아닌 일반 서버 환경에서 사용하기 적합
- 리소스가 제한적이고 별도의 컨테이너 환경이 필요하지 않은 경우 더 경제적
Jenkins Node JS 설정
npm명령어를 젠킨스 내부에서 실행하기 위해 Node JS 플러그인 설치 및 설정을 진행합니다.
Dashboard -> Jenkins 관리 -> Plugins -> Available Plugins -> NodeJS 플러그인 설치
Dashboard -> Jenkins 관리 -> Tools -> Add NodeJS
Jenkins Pipeline
현재 한개의 레포지토리에 프론트 / 백 두가지 폴더가 모두 존재하는 상황이기에 한개의 파이프라인으로 구축을 하게 되면 백엔드에만 변경사항이 있는데 프론트까지 다시 빌드를하는 비효율적인 상황이 발생하게 됩니다.
따라서 추후에 파이프라인별로 특정 폴더에 변경사항 유무를 체크하는 스크립트를 추가하여 변화가 있는 폴더와 관련된 파이프라인만 빌드할 수 있도록 하기위해 파이프라인을 구분했습니다.
젠킨스 설정 및 파이프라인 구축
pipeline {
agent any
environment {
EC2_HOST = "ubuntu@43.203.182.118"
}
tools {
nodejs "Node JS 23.1.0" // Jenkins에서 설정한 NodeJS 설치 이름
}
stages {
stage('Clone') {
steps {
echo 'Cloning repository...'
git branch: 'main', credentialsId: 'GITHUB_PERSONAL_TOKEN', url: 'https://github.com/qoridhc/jenkins_test'
echo 'Clone finished!'
}
}
stage('FE-build') {
steps {
dir('frontend') {
sh 'npm install && npm run build'
}
}
}
}
}
파이프라인을 통해 클론 / 빌드 한 모든 파일은 젠킨스 컨테이너 내부 /var/jenkins_home/workspace 에 저장됩니다.
/home/ubuntu/jenkins-data:/var/jenkins_home
Jenkins Compose를 통해 젠킨스를 띄울 때 위와같이 볼륨마운트를 해 둔 상태이기에 /home/ubuntu/jenkins_data 경로를 통해 젠킨스 컨테이너 내부 해당 폴더에 접근이 가능합니다.
젠킨스 데이터 볼륨마운트 경로 jenkins-data/workspace/파이프라인 이름/ ... 에 들어가 클론 & npm build가 성공적으로 수행되었는지 확인할 수 있습니다.
Nginx
Nginx는 고성능의 비동기 처리를 기반으로 정적 파일 서빙, 리버스 프록시, 로드 밸런싱, 캐싱 등을 효과적으로 수행할 수 있는 웹 서버입니다. 높은 동시 연결 처리 능력과 낮은 메모리 사용으로 인해, 웹 애플리케이션의 응답 속도와 안정성을 극대화할 수 있습니다. 특히 Nginx는 현대 웹 애플리케이션이 요구하는 확장성과 유연성을 충족시키므로, 클라우드 환경에서 효과적인 배포를 가능하게 합니다.
특징
정적 파일 서빙(Static File Serving)
- 빠른 응답 시간 : 요청을 효율적으로 처리하며, 높은 동시 연결을 지원합니다. 특히 정적 파일에 대한 요청을 빠르게 처리할 수 있어, 사용자에게 즉각적인 응답을 제공합니다.
- 낮은 메모리 사용 : 가벼운 리소스 사용량으로 큰 트래픽을 처리할 수 있는 비동기 방식으로 설계되었기에 서버의 메모리를 최적화 할 수 있습니다.
리버스 프록시(Reverse Proxy)
리버스 프록시는 클라이언트 요청을 받아 백엔드 서버로 전달하고, 백엔드 서버의 응답을 클라이언트에 반환하는 역할을 합니다. 가령 ' / ' 경로로 접근하는 경우 프론트엔드 정적파일을 서빙하고 특정 백엔드 api 경로 '/api/v1 ...' 으로 접근하면 백엔드 서버로 응답을 보내는 형태로 웹 애플리케이션의 요청에 따른 진입점 역할을 할 수 있습니다.
- 보안 : Nginx는 웹 애플리케이션의 진입점 역할을합니다.
- 로드 밸런싱:여러 백엔드 서버로 요청을 분산시키는 로드 밸런서 역할을 수행할 수 있어, 트래픽이 분산되면서 서버 부하를 줄일 수 있습니다.
Frontend 배포
위에서 말한 두가지 방식 중 간단하게 구현 가능한 Nginx를 직접 서빙하는 방식으로 구현하려고 합니다.
Nginx 설치
Nginx를 도커 컨테이너로 띄우기 위해 컴포즈 파일에 Nginx를 추가해줍니다.
docker-compose.yml
services:
mysql:
...
nginx:
image: nginx:1.24.0
container_name: nginx
ports:
- "80:80"
- "443:443"
volumes:
- /home/ubuntu/jenkins-data/workspace/travelus-frontend/frontend/build:/usr/share/nginx/html
- /home/ubuntu/nginx/nginx.conf:/etc/nginx/nginx.conf
- /home/ubuntu/nginx/default.conf:/etc/nginx/conf.d/default.conf
networks:
- travelus-network
networks:
travelus-network:
external: true
/home/ubuntu/jenkins-data/workspace/travelus-frontend/frontend/build:/usr/share/nginx/htm
Nginx는 /usr/share/nginx/html 파일을 호스팅하여 동작합니다. 따라서 앞서 빌드한 결과물이 담긴 build폴더 파일들을 해당 경로에 넣어줘야합니다. 이를 쉽게 할 수 있도록 볼륨마운트를 통해 빌드폴더와 nginx 호스팅 폴더를 연결해줍니다.
대략적인 과정은 다음과 같습니다.
1. 젠킨스 파이프라인 실행 (빌드 파일생성)
2. 젠킨스 볼륨이 마운트된 /home/ubuntu/jenkins-data/workspace/... /build경로에 빌드파일이 생성
3. 해당 폴더를 다시 /usr/share/nginx/html과 연결
4. 즉, 젠킨스로 빌드가 실행되면 자동으로 nginx 내부 호스팅 폴더(/usr/share/nginx/htm)에 build 폴더가 전달됨
5. nginx가 이를 서빙하여 프론트엔드 구동
/home/ubuntu/nginx/nginx.conf:/etc/nginx/nginx.conf
/home/ubuntu/nginx/default.conf:/etc/nginx/conf.d/default.conf
마찬가지로 외부 호스트에서 쉽게 nginx 컨테이너 내부에 접근가능하도록 nginx 컨테이너 내부 설정파일을 볼륨 마운트를 진행합니다.
현재 저는 호스트 루트에 nginx라는 폴더를 만들어 그안에 nginx 설정 파일을 작성하였습니다.
default.conf
개별 사이트나 애플리케이션의 로컬 설정을 관리하는 용도로 사용되는 설정 파일입니다. 이를 통해 특정 도메인, 프록시 설정, 정적 파일 라우팅 등 애플리케이션별 설정을 개별적으로 관리할 수 있습니다.
server {
listen 80;
server_name 43.203.182.118; // 서버에 할당된 IP 주소나 도메인 이름
// host/api/v1/ 로 시작하는 요청을 리버스 프록시로 처리합니다.
// 해당 경로로 요청이 들어오면 http://43.203.182.118:8080(백엔드)로 트래픽을 전달합니다.
location /api/v1/ {
proxy_pass http://43.203.182.118:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
// 기본 경로로 들어오면/usr/share/nginx/html 경로의 프론트엔드 정적파일을 서빙합니다.
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
nginx.conf
Nginx 서버의 전역 설정을 관리합니다. 서버의 성능이나 보안, 로깅 같은 공통 설정을 여기서 정의합니다.
user www-data;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 50M;
include /etc/nginx/conf.d/*.conf;
}
nginx.conf에 모든 설정을 한꺼번에 작성하면 설정 파일이 매우 길어지기 때문에 가독성이 떨어집니다. 문제 발생 시 원인을 파악하기 어려워질 수 있습니다.
따라서 nginx.conf는 공통된 전역 설정을 담고 개별 설정은 default.conf으로 분리하여 include /etc/nginx/conf.d/*.conf로 nginx에 default.conf를 추가하는방식으로 가독성과 유지보수성을 높이는 구조를 만들 수 있습니다.
Nginx 포트에 접근 할 수 있도록 EC2 보안 인바운드 규칙에 80 / 443 포트를 추가해줍니다.
docker-compose up -d nginx 명령어로 nginx를 구동해보면 nginx 컨테이너가 정상 구동된 것을 확인할 수 있습니다.
이제 파이프라인이 실행 시 새로운 빌드 파일을 반영할 수 있도록 nginx를 재실행하는 코드를 파이프라인에 추가합니다.
pipeline {
agent any
environment {
EC2_HOST = "ubuntu@43.203.182.118"
}
tools {
nodejs "Node JS 23.1.0" // Jenkins에서 설정한 NodeJS 설치 이름
}
stages {
stage('Clone') {
steps {
echo 'Cloning repository...'
git branch: 'main', credentialsId: 'GITHUB_PERSONAL_TOKEN', url: 'https://github.com/qoridhc/jenkins_test'
echo 'Clone finished!'
}
}
stage('FE-build') {
steps {
dir('frontend') {
sh 'npm install && npm run build'
}
}
}
stage('Deploy to EC2') {
steps {
script {
echo 'Deploying to EC2...'
// 사전에 정의한 SSH Credentails 값
sshagent(['EC2_SSH_KEY']) {
sh """
ssh -o StrictHostKeyChecking=no ${EC2_HOST} << EOF
cd docker
// nginx 재실행
docker compose restart nginx
EOF
"""
}
echo 'Deployment finished!'
}
}
}
}
}
설정이 올바르게 이루어지고 젠킨스 파이프라인을 통해 프론트 npm 빌드도 정상적으로 실행 됬다면 다음과같이 프론트엔드 정적파일이 정상적으로 구동되는것을 확인할 수 있습니다.
'DevOps 공부 > CI CD' 카테고리의 다른 글
4. Jenkins CI / CD 구축하기 - Blue Green 무중단 배포 (0) | 2024.10.31 |
---|---|
2. Jenkins CI / CD 구축하기 - 젠킨스 & 깃허브 웹훅 설정 (0) | 2024.10.24 |
1. Jenkins CI / CD 구축하기 - 스프링 Boot EC2 배포하기 with Docker (0) | 2024.10.14 |