[프로젝트] SpringBoot + React 웹 서비스 Docker(Nginx, SSL/Reverse Proxy, Redis)로 배포하기

김민우·2022년 8월 30일
6

Docker

목록 보기
4/4

기존의 프로젝트 Repo에서는 Github Actions를 이용하여 빌드파일을 압축하여 S3로 전송한 뒤, CodeDeploy를 통하여 EC2 서버 내에서 Nginx를 통해 배포하였다.

하지만, CI/CD가 복잡한 점이 아쉬워서 도커 컴포즈를 이용하여 여러개의 컨테이너(Nginx, React, Spring, Redis)를 실행시켜 서버를 배포(리버스 프록시)하도록 도전했다.😊

백엔드, 프론트엔드 깃허브 레포지토리에서 develop 브랜치로 푸쉬하면, CI/CD가 이루어져 EC2 내의 도커 이미지가 실행되어 자동으로 배포된다!😊

[CI/CD]

[참고자료1]
[참고자료2]
[참고자료3]


[공통(EC2)]

🏷 EC2 내 docker-compose.yaml 생성

version: '3'
services:

  web:
    container_name: web
    image: {docker ID}/moamoa-web
    expose:
      - 8080
    volumes:
      - ./secret:/secret
    ports:
      - 8080:8080

  react:
    container_name: front
    image: {docker ID}/moamoa-front
    expose:
      - 3000

  nginx:
    container_name: nginx
    image: nginx:latest
    restart: always
    volumes:
      - ./conf/:/etc/nginx/conf.d
      - /data/certbot/conf:/etc/letsencrypt
      - /data/certbot/www:/var/www/certbot
    ports:
      - 80:80
      - 443:443
    depends_on:
      - web
      - react
  
  redis:
      image: redis:alpine
      command: redis-server --port 6379
      container_name: redis_boot
      hostname: redis_boot
      labels:
        - "name=redis"
        - "mode=standalone"
      ports:
        - 6379:6379

{docker ID}는 자신의 도커 ID로 수정해주세요!

  • docker-compose 파일을 EC2 내에서 생성하여 실행하였다. web 컨테이너에서는 로컬 서버에 있는 ./secret/application.yml 파일을 도커에 마운트하여 저장하였다.

  • nginx에서는 로컬 파일에 ./conf/ 내 nginx.conf (nginx 구성파일)를 통해 리버스 프록시를 설정하였다. /data/certbot/ 내의 인증서를 마운트하여, nginx 내에 구성하였다. depends_on은 web 컨테이너와 react 컨테이너를 먼저 실행하도록 설정했다.

  • redis 이미지를 불러와 컨테이너화 (port 6379)하여 구성했다.

🏷 EC2내 conf/nginx.conf 생성

EC2 내에서 /api 내로 들어오는 요청이면 8080포트(스프링)으로, / 내로 들어오는 요청이면 3000포트(리액트)리버스 프록시를 구성하도록 nginx.conf를 수정했다.

# conf/nginx.conf 생성

server {
       listen 80;
       client_max_body_size 20M;

       server_name moamoadev.shop;
       return 301 https://moamoadev.shop$request_uri;   # http로 들어오면 https로 redirect 해주는 부분 
}

server {
       listen 443 ssl;
       client_max_body_size 20M;
server_name moamoadev.shop;
       # Certificate
       ssl_certificate /etc/letsencrypt/live/moamoadev.shop/fullchain.pem;

       # Private Key
       ssl_certificate_key /etc/letsencrypt/live/moamoadev.shop/privkey.pem;
       location /api {
               
	       proxy_pass http://web:8080; # 자신의 springboot app이사용하는 포트
	       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;
               }
       location / {
               proxy_pass http://front:3000; # 자신의 springboot app이사용하는 포>트
               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;

            
       }

}

nginx.conf를 살펴보자.

  • 우선 SSL 적용을 위해 80포트로 들어오면, 443 포트로 리다이렉트 하였다.

  • location /api는 web 컨테이너(스프링):8080로 리다이렉트하였고, location /는 front 컨테이너(리액트):3000로 리다이렉트 하였다.


[프론트엔드(React)]

🏷 도커 파일 생성

# Dockerfile

FROM node:alpine as builder
WORKDIR /usr/src/app
COPY package.json .
RUN npm install
COPY ./ ./
RUN npm run build

FROM nginx 
EXPOSE 3000
COPY ./default.conf /etc/nginx/conf.d/default.conf 
COPY --from=builder usr/src/app/build  /usr/share/nginx/html 
  • node:-alpine 이미지를 베이스로 한다. WORKDIR을 usr/src/app으로 설정한다.

  • 워킹 디렉토리 내부로 package.json 파일을 복사한 뒤, 그 안에서 npm install & build 한다.

  • Nginx 이미지를 베이스로 하여 프로젝트 내부의 default.conf 파일을 복사하고, 빌드 파일을 도커 내에 복사한다. 3000 포트를 열어준다.

🏷 .dockerignore 생성

# .dockerignore
.git
node_modules
.gitignore
  • 이미지 생성시 용량을 줄이기 위해 프로젝트 내의 필요 없는 node_modules를 ignore 해주었다.

🏷 default.conf 생성

server {
    listen 3000; 

    location / {

        root /usr/share/nginx/html; 
        index index.html index.htm; 
        try_files $uri  $uri/ /index.html; 

    }
}
  • Nginx를 통해 3000 포트를 열고, 빌드 파일을 배포하도록 Nginx의 conf 파일을 추가 해주었다.
  • 이전의 과정(Dockerfile)에서 COPY --from=builder usr/src/app/build /usr/share/nginx/html 를 통해 빌드 파일을 usr/src/nginx/html로 복사하였기 때문에 root 경로를 위와 같이 설정했다.

🏷 깃허브 액션 파일 설정(.github/workflows/npm-publish.yml)

#npm-publish.yml

name: Deploy 

on:
  push:
    branches:
      - develop 

jobs: 
  build:
    runs-on: ubuntu-latest
    
    strategy: 
      matrix: 
        node-version: [16.14.x] 

    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v1
        with: 
          node-version: ${{ matrix.node-version }} 
      
      - name: web docker build and push
        run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
          docker build -t ${{ secrets.DOCKER_REPO }}/moamoa-front .
          docker push ${{ secrets.DOCKER_REPO }}/moamoa-front
          
      - name: executing remote ssh commands using password
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST_ID }}
          username: ec2-user
          key: ${{ secrets.PRIVATE_KEY }}
          script: |
            sudo docker rm -f $(docker ps -qa)
            sudo docker pull ${{ secrets.DOCKER_REPO }}/moamoa-front
            docker-compose up -d
            docker image prune -f
  • 파일 내 steps를 살펴보면, web docker build and push 에서 도커 허브에 로그인하고, 빌드한 이미지를 푸쉬 한다.
  • executing remote ssh commands using password 과정에서는 appleboy/ssh-action을 통해 EC2에 로그인하였고, 빌드한 이미지를 pull 한 후 docker-compose를 통해 컨테이너를 실행해주었다.

[백엔드]

🏷 Dockerfile 생성

FROM openjdk:11
ARG JAR_FILE=build/libs/moamoa-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java", "-Dspring.config.location=/secret/application.yml","-jar","/app.jar"]
  • openjdk 11 버전을 기반으로, 빌드파일을 도커내의 ./app.jar로 복사한다.

  • ENTRYPOINT를 통해 빌드된 app.jar 파일을 /secret/application.yml을 외부 주입하여 jar 파일을 실행한다.

[application.yml을 외부 주입 한 이유]

이전 시도에서 application.yml을 깃허브 secret을 통해 내용을 넣은 후, 깃허브 액션에서 application.yml을 만드는 과정을 시도했다.
하지만 깃허브 액션내에 컴파일 과정(테스트 과정)에서 오류가 나서 외부 주입하는 방식으로 변경하였다.

🏷 .github/workflows/gradle.yml 생성

# This workflow will build a Java project with Gradle
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle

# Repo Action 페이지에 나타날 이름 
name: Spring Boot & Gradle CI/CD

# Event Trigger
# master branch에 push 또는 pull request가 발생할 경우 동작
# branch 단위 외에도, tag나 cron 식 등을 사용할 수 있음
on:
  push:
    branches: [ develop ]

jobs:
  build:
    # 실행 환경 지정
    runs-on: ubuntu-18.04

    # Task의 sequence를 명시한다.
    steps:
      - uses: actions/checkout@v2

      - name: Set up JDK 11
        uses: actions/setup-java@v1
        with:
          java-version: 11

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      # Build
      - name: Build with Gradle
        run: ./gradlew clean build
      
      # 웹 이미지 빌드 및 도커허브에 push
      - name: web docker build and push
        run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
          docker build -t ${{ secrets.DOCKER_REPO }}/moamoa-web .
          docker push ${{ secrets.DOCKER_REPO }}/moamoa-web
          
      # docker compose up
      - name: executing remote ssh commands using password
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST_ID }}
          username: ec2-user
          key: ${{ secrets.PRIVATE_KEY }}
          script: |
            sudo docker rm -f $(docker ps -qa)
            sudo docker pull ${{ secrets.DOCKER_REPO }}/moamoa-web
            docker-compose up -d
            docker image prune -f
  • 파일 내 steps를 살펴보면 프론트와 똑같이, web docker build and push 에서 도커 허브에 로그인하고, 빌드한 이미지를 푸쉬 한다.
  • executing remote ssh commands using password 과정에서는 appleboy/ssh-action을 통해 EC2에 로그인하였고, 빌드한 이미지를 pull 한 후 docker-compose를 통해 컨테이너를 실행해주었다.

🏷 EC2 내 application.yml 생성

EC2내에 docker-compose.yaml을 생성한 디렉터리 내에서 secret/application.yml을 따로 주입했다.

  • application.yml 파일 내에는 민감한 정보인 EC2 인스턴스의 비밀번호나 RDS의 비밀번호가 담겨 있으므로 깃허브에 공개하지 않고 EC2내 따로 업로드했다.😊

[설정]

🏷 [깃허브 시크릿 등록하기]

시크릿은 repository settings에 Secrets>Actions에 등록해주면 된다. 위의 DOCKER_USERNAME, DOCKER_REPO에는 각각 도커 유저 네임, 도커 레포지토리 이름을 시크릿으로 등록해주자.

[EC2 ssh-action으로 접속하기]

✔️ 터미널에서 작업을 자동화 할 수 있다.

ssh 키를 우선 세팅한다. 내 컴퓨터에서 키를 만들기 위해 CMD 터미널을 띄운다.
배포할 서버에 public 키를 넣어두고 private 키로 접근한다.

ssh-keygen -t rsa -b 4096 -C "[내메일]" -f {이름}

공개키({이름})의 내용은 EC2 서버의 ~/.ssh 경로의 authorized_keys 에다가 붙여 넣어주자.

spring-cicd 파일에 담긴 개인키의 내용은 GITHUB SECRET의 PRIVATE_KEY로 등록한다.


profile
Backend Developer

0개의 댓글