본문 바로가기
Nginx

Docker(CentOS 7) + Nginx + Spring Boot + Vue.js 배포하기 - ④

by WangTak 2021. 12. 28.
반응형

①, ②, ③ 편은 배포에 쓰일 준비물을 만드는 시간이었습니다. ④ 편에서는 본격적으로 배포하는 방법에 대해서 알아보도록 하겠습니다.

 

1. Spring Boot

② 편에서 만든 Spring 애플리케이션을 배포하기 위해서는 Jar를 생성해야 합니다. IntelliJ를 사용하시면 아래 사진처럼 Jar를 편리하게 생성할 수 있습니다.

 

Spring Jar 만들기

 

[우측 상단 Gradle 클릭 > Tasks > build > bootJar 클릭]

위와 같은 프로세스를 진행 후에 build > libs로 가면 "member-0.0.1-SNAPSHOT.jar"가 생성된 것을 알 수 있습니다. 그런데 이름이 너무 길기 때문에 build.gradle에서 다음 구문을 추가한 후에 다시 jar 파일을 생성하도록 하겠습니다.

 

build.gradle에 다음 구문을 추가합니다.

bootJar {
   archiveFileName = 'member.jar'
}

 

bootJar를 다시 실행해보면 기존에 생성했던 긴 이름의 jar와 설정을 통해 이름을 부여한 jar가 같이 있는 것을 확인할 수 있습니다.

생성된 jar 파일

 

"3. CentOS 7 + Nginx"에서 member.jar를 사용하도록 하겠습니다.

 

2. Vue.js

③ 편에서 만든 Vue 애플리케이션을 배포하기 위해서는 npm run build 명령어를 사용해야 합니다. ③ 편에서 만든 Vue 애플리케이션으로 이동하여 애플리케이션을 실행하기 위해 사용(npm run serve)했던 터미널을 켜서 npm run build 명령어를 입력합니다.

 

npm run build

 

그러면 다음과 같이 프로젝트에 dist라는 폴더가 생긴 것을 확인할 수 있습니다.

npm run build 이후 생긴 dist 폴더

 

"3. CentOS 7 + Nginx"에서 dist 디렉토리 안에 있는 js, favicon.ico, index.html을 사용하도록 하겠습니다.

 

3. CentOS 7 + Nginx

개발 서버가 따로 있으신 분들은 SFTP Tool을 사용하여 개발 서버에 위 1, 2번에서 준비한 member.jar, js, favicon.ico, index.html을 개발 서버로 옮겨주시면 됩니다. 

 

본 글에서는 개발 서버 대신 Docker 컨테이너인 CentOS 7에 배포하도록 하겠습니다. ① 편에서 Docker 컨테이너를 생성하면서 -v 옵션을 사용하여 볼륨 마운트를 진행했습니다.

 

docker run --privileged --restart always --name prod -p 80:80 -p 443:443 -v D:\Docker\prod:/mnt -dt centos:7.8.2003 /sbin/init

 

콜론(:) 기준 왼쪽이 마운트 된 호스트의 디렉토리 , 오른쪽이 컨테이너의 디렉토리입니다. 터미널을 실행하여 docker exec -itu wangtak prod /bin/bash를 사용하여 컨테이너에 접속합니다. 1, 2번에서 준비된 member.jar, js, favicon.ico. index.htmlD:\Docker\prod에 옮겨 놓으면 다음과 같이 컨테이너의 /mnt에 파일이 있음을 확인할 수 있습니다.

 

Docker Volume Mount

 

※ -v 옵션을 줬을 때 호스트와 컨테이너의 위치는 편한 곳으로 두시면 됩니다.

 

그럼 이제 파일을 각자 위치로 이동하고 jar 실행과 Nginx + Vue.js 설정을 하도록 하겠습니다.

 

[CentOS 7]

member.jar의 위치: /home/wangtak/member - [member 디렉토리 생성]

js, favicon.ico, index.html의 위치: /usr/share/nginx/html - [sudo 권한 필요]

 

/usr/share/nginx/html에는 기존에 nginx에서 생성한 index.html이 존재할 텐데 mv 명령어를 사용하여 덮어 버리거나, 지우고 옮기면 됩니다.

 

스프링을 실행하도록 하겠습니다. ① 편에서 이미 JDK를 설치하였기 때문에 member.jar가 있는 /home/wangtak/member로 이동하여 실행해주기만 하면 됩니다. 로컬에서는 h2 DB로 사용하였지만, Docker 컨테이너에서는 MySQL을 사용할 것이기 때문에 dev로 실행합니다. [application.yml, applicaiton-dev.yml]

 

java -Dspring.profiles.active=dev -jar member.jar # 스크립트 배포 대신에 직접 실행

 

Spring 애플리케이션이 실행되고 있는 터미널은 그대로 유지시킨 채 새로운 터미널로 컨테이너에 접속하여 nginx 설정을 하도록 하겠습니다.

 

[진행 상황]

  • Vue 애플리케이션에서 빌드된 js, favicon.ico, index.html/usr/share/nginx/html로 이동
  • Spring의 member.jar/home/wangtak/member로 이동하였으며 Spring 애플리케이션이 실행된 상태

 

[Nginx]

Nginx 설정만 하면 끝이 납니다. systemctl 명령어를 사용하여서 Nginx가 구동 중인지 확인하여 Nginx를 실행시켜줍니다. 그 이후에 /etc/nginx/nginx.conf/etc/nginx/conf.d/default.conf를 수정하도록 하겠습니다.

 

# nginx 실행
sudo systemctl start nginx

cd /etc/nginx

# nginx 설정 파일 수정
sudo vi nginx.conf

# conf.d/deafult.conf 수정
sudo vi /etc/nginx/conf.d/deafult.conf

 

/etc/nginx/nginx.conf

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
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;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

 

※ default.conf가 없으시면 .conf 파일을 추가해주시면 됩니다. [이름은 자유롭게]

※ deafult.conf에 설정 내용이 다 들어가있습니다.

 

/etc/nginx/conf.d/default.conf

upstream apiserver {
    server 127.0.0.1:9000;
}

server {
    listen      80;
    server_name localhost;

    location /api {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_pass http://apiserver/api;
    }

    location / {
        root /usr/share/nginx/html;
        index index.html;
        try_files $uri $uri/ /index.html; # ⑴ vue-router mode: history
    }
}

 

그럼 위와 같이 .conf 수정을 마친 후에, sudo nginx -t로 nginx 파일의 상태를 체크하고 성공 메시지가 나오면 Nginx를 멈췄다가 다시 실행시켜줍니다.

 

Q. sudo nginx -t => sudo를 붙여야 하는 이유?

A. nginx -t 명령어 내부적으로, error.log와 nginx.pid를 읽어야 하는데 2개의 파일 모두 root 소유기 때문에 권한이 필요합니다.

 

Q. /etc/nginx/conf.d/default.conf의 (1) 번의 존재 이유

A. 기본적으로 Vue Router를 사용하여 Vue.js를 구동하면 URL에 '#'이 존재하게 됩니다. 그래서 mode: 'history'를 사용하여 '#'을 제거해줍니다. 

이 '#'이 붙은 이유는 '#'을 이용해서 서버는 이 페이지가 새로운 페이지란걸 알지 못하게 합니다. /signup으로 이동을 해도 index.html 이라는 공통 파일에서의 이동으로 인식합니다.

그러나 URL에 '#'이 있는 것은 좋지 않기 때문에 mode: 'history'를 사용하였습니다. 그렇게 되면 서버는 페이지가 이동할 때마다 새로운 페이지로 인식하고, /signup URL에서 새로 고침을 하게 되면 [404 Page Not Found]가 발생합니다. (로컬에서는 새로고침해도 문제가 없지만, 프로덕션 레벨에서는 404 Error가 발생.) Vue Router 공식 문서에서 mode: 'history'에 대한 해결 방안을 (1)번과 같이 제시하고 있습니다.

 

참조: https://router.vuejs.org/guide/essentials/history-mode.html#example-server-configurations

 

[호스트 PC에서 확인]

그럼 이제, localhost로 접속하셨을 때 ③ 편에서 만든 Vue.js 화면이 나오면 성공입니다. signup 페이지로 가서 데이터도 넣고, 리스트도 확인해보세요. - [500 Internal Server Error]가 발생하신 분들은 TroubleShooting을 확인해주세요.

 

[TroubleShooting]

배포 후에 처음 데이터를 넣었을 때, name에 한글을 적어보니 데이터가 정상적으로 들어가지 않고 [500 Internal Server Error]가 발생했습니다. java -jar로 실행 한 Spring의 Log를 보니, 한글이 깨져서 DB쪽에서 이상한 데이터가 들어왔다고 에러를 뱉었습니다. 그래서 MySQL의 encoding을 utf-8로 수정하니 정상적으로 작동했습니다. 앞으로 유념해서 encoding이나 다른 체크해야 될 사항을 생각하고 염두해둬야 할 거 같습니다.

 

MySQL encoding 확인 & 수정 명령어도 포스팅합니다.

# MySQL 서버로 접속 후에 [mysql -u username -p]
# Encoding 확인
\s

status

# Encoding 설정
SET character_set_client = utf8;
SET character_set_results = utf8;
SET character_set_connection = utf8;
ALTER DATABASE DB명 DEFAULT CHARACTER SET utf8;


출처: https://dupont3031.tistory.com/entry/MySQL-CharacterSet-변경 [꽃이 피는 시기는 꽃마다 다르다.]

 

[향후 포스트 할 내용]

Nginx에 SSL 인증서 적용하기

Nginx의 Load Balancing 기능을 이용하여 무중단 배포하기

무중단 배포했을 때 Spring Security의 Session Clustering

 

 

 

반응형