[CI·CD] Docker + Github Action + Spring Boot + MYSQL + REDIS

오형상·2023년 5월 24일
3

도커

목록 보기
4/5
post-thumbnail

저번 프로젝트에서는 깃랩을 사용하여 도커로 받은 이미지를 크론탭으로 배포 스크립트를 실행시키는 식으로 ci/cd를 구현했었다.
이번에는 Github action을 통해 코드를 빌드하고, 이를 도커 레포지토리에 push, 그리고 ssh로 원격 서버에 접속하여 도커의 이미지를 pull 하여 Docker-Compose를 통해 Spring Boot와 MYSQL, REDIS를 같이 실행시키는 방식을 사용하고 정리해 보고자 한다.
이 방법을 선택한 이유는 docker-compose를 통해 mysql과 redis도 같이 관리할 수 있다는 점 편리해 보였다.

0. 사전 준비

도커가 설치된 AWS EC2와 Github Repository가 필요합니다.
아래 주소를 참고해 주세요.
1. AWS EC2 서버 띄우기
2. AWS EC2에 도커 설치

1. Docker Repository 생성

dockerhub 사이트에 접속하여 회원가입 및 Repoistory 생성

✨ Repository Name은 꼭 기억해 둘것
create reposiotry

2. Dockerfile 작성

파일 이름 Dockerfile에서 변경 ❌ -> 파일이름을 임의로 변경하면 오류 납니다.

FROM gradle:7.6-jdk17-alpine as builder
WORKDIR /build

# 그래들 파일이 변경되었을 때만 새롭게 의존패키지 다운로드 받게함.
COPY build.gradle settings.gradle /build/
RUN gradle build -x test --parallel --continue > /dev/null 2>&1 || true

# 빌더 이미지에서 애플리케이션 빌드
COPY . /build
RUN gradle build -x test --parallel

# APP
FROM openjdk:17.0-slim
WORKDIR /app

# 빌더 이미지에서 jar 파일만 복사
COPY --from=builder /build/build/libs/*-SNAPSHOT.jar ./app.jar

EXPOSE 8080

# root 대신 nobody 권한으로 실행
USER nobody
ENTRYPOINT [                                                \
    "java",                                                 \
    "-jar",                                                 \
    "-Djava.security.egd=file:/dev/./urandom",              \
    "-Dsun.net.inetaddr.ttl=0",                             \
    "app.jar"              \
]

3. Workflow 작성

이제 github action의 workflow를 작성해보겠습니다.
Github Actions가 무엇을 수행할지를 정의할 수 있습니다.

깃헙 레파지토리에서 Actios에 들어가 새로운 workflow를 생성할 수 있습니다.

아래와 같이 작성 후 Commit

name: CI/CD

on:
  push:
    branches: [ "main" ]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:

      - name: checkout
        uses: actions/checkout@v3

      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

        ## create application-database.yml
      - name: make application-database.yml
        run: |

          cd ./src/main/resources
          
          # application-database.yml 파일 생성
          touch ./application-database.yml
          
          # GitHub-Actions 에서 설정한 값을 application-database.yml 파일에 쓰기
          echo "${{ secrets.DATABASE }}" >> ./application-database.yml
        shell: bash

      - name: make application-redis.yml
        run: |
          cd ./src/main/resources

          # application-redis.yml 파일 생성
          touch ./application-redis.yml

          # GitHub-Actions 에서 설정한 값을 application-redis.yml 파일에 쓰기
          echo "${{ secrets.REDIS }}" >> ./application-redis.yml
        shell: bash


      - name: Build with Gradle
        run: |
          chmod +x gradlew 
          ./gradlew build
          
#          ./gradlew bootJar
      ## 웹 이미지 빌드 및 도커허브에 push
      - name: web docker build and push
        run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
          docker build -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }} . 
          docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}
      ## docker compose up
      - name: executing remote ssh commands using password
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ubuntu
          key: ${{ secrets.KEY }}
          script: |
            sudo docker rm $(sudo docker ps -a)
            sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}
            cd ~
            docker-compose up -d
            sudo docker image prune -f
            

👀 참고) ./gradlew build과 ./gradlew bootJar 차이

  • gradle build

gradle build는 bootJar를 포함하고 있어 내부 동작이 더 길다.

build는 test 코드가 있다면 테스트도 수행을 한다.

build와 bootJar가 다른 것 중 대표적인 것은 Life Cycle과 관련된 Task가 존재한다.
예를 들어 check, assemble가 있다.

  • gradle bootJar

단순히 프로젝트의 jar 파일만을 만드는데 목적을 가지고 있다.

그만큼 빌드하는데 속도도 빠를 것.

4. Github Secrets 설정

깃헙 레파지토리 -> Seetings -> Secrets and variables -> Actions -> new repository secret 에서 변수 설정이 가능합니다.

database & redis

깃헙에 올리지 못하는 db정보들을 저장할 변수입니다.
예시로 database는 아래와 같습니다.

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql:/<AWS EC2 public IP>/<DB 스키마>
    username: root
    password: 1q2w3e4r
  jpa:
    database: mysql  
    database-platform: org.hibernate.dialect.MySQLDialect
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        format_sql: true

참고로 application.yml은 아래와 같이 올라가있습니다. 프로젝트를 돌리면 application-database.yml와 application-redis.yml를 찾아서 설정정보를 참고하게됩니다.

spring:
  profiles:
    include: 
    	- mysql
        - redis

DOCKER_USERNAME & DOCKER_PASSWORD & DOCKER_REPO

  • DOCKER_USERNAME = docker hub 아이디
  • DOCKER_PASSWORD = docker hub 비밀번호
  • DOCKER_REPO = 1.에서 만든 docker repository 이름

HOST & KEY

  • HOST = AWS EC2 public Ip
  • KEY = AWS EC2 생성시 사용한 pem 키
    ✨ -----BEGIN RSA PRIVATE KEY----- 부터 -----END RSA PRIVATE KEY----- 부분을 포함

❓ pem key 확인 방법

  • windows : vscode, intellij 등 편집기를 사용하여 확인 가능
  • mac : 터미널에서 cat <pem 키 경로>로 확인 가능

5. docker-compose 설치

sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# 선택 사항 : PermissionError: [Errno 13] Permission denied 발생하면 적어주세요.
sudo chmod 666 /var/run/docker.sock

docker-compose --version 통해 설치가 잘 되었는지 확인합니다.

6. docker-compose.yml 작성

# 홈 디렉토리로 이동
cd ~

# docker-compose.yml 파일을 생성
sudo vi docker-compose.yml

👀 vi 편집기 사용법 (자세한 내용은 구글링)

i = 입력 모드
esc = 명령어 모드
:wq = 저장 후 종료

docker-compose.yml

version: '3.8'

services:

  mysql:
    container_name: mysql
    image: mysql
    ports:
      - 3306:3306
    environment:
      MYSQL_ROOT_PASSWORD: 1q2w3e4r
      MYSQL_DATABASE: shoppingmall
    restart: always
    networks:
      - testnet
  redis:
    container_name: redis
    image: redis
    ports:
      - 6379:6379
    restart: always
    networks:
      - testnet

  backend:
    container_name: backend
    image: zvyg1023/shopping-mall
    expose:
      - 8080
    ports:
      - 8080:8080
    restart: always
    depends_on:
      - mysql
      - redis
    networks:
      - testnet


networks:
  mynet:
    driver: bridge
    

7. 동작 확인하기

우선 워크플로우는 성공적으로 마쳤습니다.

docker ps를 통해 컨테이너가 돌아가는지 확인해보겠습니다.

마지막으로 배포된 주소로 스웨거 접속 해보겠습니다.

마무리

docker-compose를 사용하여 Spring과 MySQL을 배포했으나 Spring에서 MySQL과 연결이 되지 않아서 exception이 발생하며 Spring application이 종료되는 문제 발생하였다.

Spring이 MYSQL에 종속적이라 depends_on 옵션을 사용하여 작업 순서를 보장해주었지만 여진히 작동하지 않았다.

docker-compose up의 로그를 확인 해 보니 MySQL에서 ready for connections 상태가 되기 전에 Spring이 먼저 실행되며 DB connection을 시도하였다는 것을 알 수 있었다.

최종적으로 restart 옵션을 주어서 해결하였지만 맞는 방법인지는 모르겠다.

전체 코드 보러가기

0개의 댓글