AWS

👩🏻‍🌾 AWS, Docker, Nginx로 신나게 배포하기 - 1편

ಠಿ_ಠ 💻 2021. 7. 1. 01:15

이걸 쓰는 이유는 CICD 환경을 구축하기 전에 그 복잡하고 귀찮음을 겪어보는 시간을 갖기 위해서이다.
직접 느껴보면 CICD를 왜 쓰는건지 이해가 잘 된다. 귀찮아도 해보자.
(스크립트에 대한 설명이 빈약해보여 조금씩 추가하겠습니다)

🚩오늘의 목표
프론트와 백엔드로 설계되어있는 웹 서비스를 ec2 서버에 컨테이너화해서 직접 배포시켜보자(각각 실행)!
그리고 그 불편함을 깨닫자..


정말 간단한 프론트와 백엔드를 직접 서버에서 실행해보는 과정을 진행하겠다. 모든 완성된 코드는 깃헙에 있다.
https://github.com/yuzin9712/web-deploy-ec2

 

yuzin9712/web-deploy-ec2

프론트와 백엔드로 설계된 애플리케이션을 ec2 서버에 간단히 배포해보는 프로젝트. Contribute to yuzin9712/web-deploy-ec2 development by creating an account on GitHub.

github.com


(+ CI 전까지 테스트 코드도 맨 아래 요구사항에 만족하는 테스트 코드를 작성하겠다. 지금은 없다.)

기술스택

Frontend Backend Infra
Vue Vuetify Vuex (cli로 생성하였다) Spring Boot, JPA, H2(나중에 RDS로 변경 예정) EC2, Docker, Nginx

 

사전 조건

- EC2 인스턴스를 생성했다.
- EC2 에 도커를 설치했다.
- 깃헙에 배포하고자 하는 코드가 있다.
- 도커에 대해서 쬐끔 안다.
- 리눅스에 자바 설치하기, 깃 설치하기, .. 등등 알아서 찾아서 할 마음이 있다.
(사실 여기 2021.07.01 - [AWS] - 🐥 Linux에서 필요했던 내용 에 좋은 링크를 정리하고 있어요.)
- 아주아주 기본적인 명령어를 안다. (cd, ls -a ..)
- vim을 사용할 줄 안다.

구조

다음과 같은 구조를 갖도록 하겠다. 현재까지 두 컨테이너가 실행 중이다. 이번 글에서는 두 컨테이너간 통신은 신경쓰지 않도록하겠다.

가운데 nginx를 두고 통신하도록 나머지 부분도 이번 주 안에 추가하고 다음주 까지 글을 올리겠다.


파란색 박스가 도커 컨테이너라고 보면 된다.
프론트는 Nginx 컨테이너를 실행하고, 빌드된 정적파일들을 보여주게 할 것이다.

백엔드는 컨테이너 안에서 우리가 만든 api 서버가 실행되도록 하겠다.

프론트엔드 배포하기 , 근데 이제 Nginx를 곁들인 ...

일단 Nginx에다가 프론트 빌드 파일을 배포할 것인데, 왜 이렇게 하는지는 WAS 와 웹 서버 의 차이에 대해서 글 작성할 때 함께 쓰겠다.

우선 깃헙에 올려진 프로젝트를 리눅스에 클론 한다.
프로젝트는 다음과 같은 구조를 갖는다.

|-- web-deploy-ec2
|-------frontend
|-----------Vue 프로젝트 코드
|-------backend
|-----------스프링부트 프로젝트 코드


프론트 코드를 배포하기 위해서 frontend 디렉터리 안에서 먼저 로컬에서 했던 것과 같이 실행을 해봐야 한다.
이를 위해 ec2에 node를 설치하고 원래했던 것과 같이 디펜던시 설치와 npm run serve로 실행해보자. 잘 돌아간다면 성공이다.

성공했다는 가정 하에 다음으로 넘어간다.

위 그림에서 봤듯이 Nginx가 빌드파일을 보여주도록 할 것이다.
이를 위해 방금 직접 node 설치, 디펜던시 설치, npm run build로 빌드 파일 생성의 과정이 필요하다. 그리고 Nginx에서 해당 정적 파일을 보여주도록 설정하는 과정이 필요하다. 정말 귀찮은데 이 작업을 우리는 도커의 이미지를 활용해서 해결 할 수 있다.

frontend의 루트에 총 3개의 파일을 만들 것이다.

Dockerfile 만들기

# node 이미지를 받는다 build이미지이구나 
# node 기반의 이미지로 생성된다. 
FROM node:latest as build-stage 

# RUN, CMD, ENTRYPOINT의 명령이 실행될 디렉터리 
WORKDIR /app 

# package*.json이 WORKDIR에 복사된다 
COPY package*.json ./ 

# 복사했으니 디펜던시 설치가 가능하다 
RUN npm install 

# 소스코드를 복사한다 
COPY ./ . 

# build 해서 /dist 폴더에 빌드 파일이 생성된다. 
RUN npm run build 

# nginx 이미지를 받는다. 실행 이미지이구나 
FROM nginx as production-stage 

# app 디렉터리 생성 
RUN mkdir /app 

# builder에서 빌드한 바이너리를 실행할 이미지로 전달해주기 위해 copy --from 옵션을 사용하여 실행 이미지로 전달한다 
COPY --from=build-stage /app/dist /app 

# 작성한 nginx 설정파일을 복사한다. 
COPY nginx.conf /etc/nginx/nginx.conf 

# 변경된 설정을 잘 적용하기 위해 재시작한다. 
RUN service nginx restart 


multi-stage builds 를 이용한 것이다. 이 내용은 따로 정리하고, 이번에는 달아놓은 주석을 보고 해당 스크립트를 이해하도록 하자.
node가 빌드 이미지 nginx가 실행 이미지이다.

.dockerignore 만들기

**/node_modules **/dist

이름만 들어도 알겠지만, .gitignore과 비슷한 역할을 한다.

nginx.conf 만들기

user  nginx;
worker_processes  1;
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;
  keepalive_timeout  65;
  server {
    listen       80;
    server_name  localhost;
    location / {
      root   /app;
      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 설정에 관한 스크립트이다. 가장 중요하게 봐야 할 (다 중요하지만^^!) 부분인 server 부분을 보자.

listen 80
80번 포트에서 들어오는 요청을 듣고 있다.

server_name
localhost의 80번 포트이겠다.

location /
/로 들어오는 모든 요청에 대해서 다음과 같은 설정을 하겠다.

root /app
루트 디렉터리를 app으로 설정한다.

index index.html
해당 디렉터리의 index.html 을 보여주겠다.

 

백엔드 배포하기

먼저 EC2에 자바와 메이븐을 설치해야 한다. 이는 다른 글에서 보고 위와 같이 파일을 어떻게 설정하는지 보겠다.
현재 h2 를 사용하기 때문에 프론트보다 훨씬 간단하다. (아직은)

프론트와 똑같이 해당 프로젝트가 실행되는지 먼저 확인하겠다.
backend 디렉토리로 가서 다음과 같이 실행해본다. (maven wrapper는 나도 잘 몰라서 공부해봐야겠다..ㅎㅎ)

$ mvn -N io.takari:maven:wrapper $ ./mvnw clean install

나의 경우 잘 실행되었다. 이제 얘를 컨테이너화 시키면 된다.

루트에 1개의 파일을 작성한다.

Dockerfile 만들기

# java8 기반의 이미지로 만든다. 
FROM openjdk:8-jdk-alpine 

# 만들어진 jar 파일의 경로를 값으로 준다. 
ARG JAR_FILE=target/*.jar 

# 해당 파일을 app.jar로 복사한다. 
COPY ${JAR_FILE} app.jar 

# 다음과 같은 커맨드를 입력해서 서버가 실행되도록 한다. 
ENTRYPOINT ["java","-jar","/app.jar"]

주석을 통해 내용을 익혀보자.

주의할 점은 ENTRYPOINT와 RUN이 좀 다르다는 것인데, 이는 따로 정리하겠다.

결과

현재 두 컨테이너가 각각 잘 실행되고 있다. (아까말했듯이 연동은 nginxf를 두고 해보겠다.)

코드가 변경될 때마다 우리는 클론받고 컨테이너 실행의 과정을 거친다.

 

매번 클론 받아서 스크립트 실행시키기 귀찮지 않은가?!? 매번 이렇게 하면 효율성이 떨어질 것 같은데..
그렇다면 이제 자동화를 경험해 볼 기회를 가져도 된다.

그리고 느낀 것은 공식 문서가 참 친절하다는 것....

기능 요구 사항(이건 제가 나중에 할 거에요.....)

1. 게시물 작성
1) 글 제목과 내용, 작성자 정보를 등록한다. (1글자 이상)
- 제목이 비어있는 경우 경고창을 보여준다.
- 글 내용이 비어있는 경우 경고창을 보여준다.
- 작성자 정보가 비어있는 경우 경고창을 보여준다.

2. 게시물 조회
- 게시물이 없는 경우, "등록된 게시물이 없습니다"라고 보여준다.
- 게시물을 누르면 상세 페이지를 보여준다.
- 전체 게시물을 등록 시간 순으로 보여준다.

출처

https://cli.vuejs.org/guide/deployment.html#references

 

Deployment | Vue CLI

Deployment General Guidelines If you are using Vue CLI along with a backend framework that handles static assets as part of its deployment, all you need to do is make sure Vue CLI generates the built files in the correct location, and then follow the deplo

cli.vuejs.org

https://spring.io/guides/gs/spring-boot-docker/

 

Spring Boot with Docker

this guide is designed to get you productive as quickly as possible and using the latest Spring project releases and techniques as recommended by the Spring team

spring.io