DevOps/Github

Github Actions 데이터베이스 연동 및 설정 파일 자동 배포

NCOOKIE_ 2022. 11. 4. 22:13

서론

 

지난번에 Github Actions을 사용하여 로컬에서 master 브랜치에 푸시를 하면 aws 서버에 배포까지 되도록 해보았다. 그 때 사용했던 프로젝트는 start.spring.io에서 받은 파일을 거의 그대로 사용했었다 (DB 관련 dependency가 포함되지 않은 상태였음). 그런 프로젝트를 기반으로 기능들을 조금씩 추가해가며 스프링에 익숙해지려고 하는데, DB 정보를 추가한 이후에 발생한 에러 때문에 3일 동안 시행착오를 많이 겪었다. 익숙하지 않은 툴과 프레임워크 투성이에, 예제도 잘 되있는게 거의 없어서 많이 헤맸다. 그래서 데이터베이스 연동을 포함한 프로젝트를 배포하기 위해서 어떤 작업을 해야하는지 설명하고, 그 뒤에 내가 봤던 문제들에 대해 적어보려고 한다.

 

 

처음에 내가 헷갈렸던게, github action이 실행되면 도대체 어디서 실행되는지였다. 찾아보니 Runner라는 서버 위에서 돌아가더라. Runenr는 Github Actions workflow의 작업을 실행하는 어플리케이션으로, 호스팅된 가상 환경의 Github Actions에서 사용하거나, 자체 환경에서 자체 호스팅하여 사용할 수 있다.

 

이 때문에 actions에서 따로 설정파일(.properties, .yml)을 추가해주고 디비도 설치해줘야 한다. 그러지 않으면 이런 에러를 맞이하게 된다.

 

 

[properties 파일 추가 전]

SpringPracticeApplicationTests > contextLoads() FAILED
    java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:98
        Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException at ConstructorResolver.java:800
            Caused by: org.springframework.beans.factory.BeanCreationException at ConstructorResolver.java:658
                Caused by: org.springframework.beans.BeanInstantiationException at SimpleInstantiationStrategy.java:185
                    Caused by: org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException at DataSourceProperties.java:182

 

[properties 파일 추가 후]

SpringPracticeApplicationTests > contextLoads() FAILED
    java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:98
        Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException at ConstructorResolver.java:800
            Caused by: org.springframework.beans.factory.BeanCreationException at ConstructorResolver.java:658
                Caused by: org.springframework.beans.BeanInstantiationException at SimpleInstantiationStrategy.java:185
                    Caused by: java.lang.IllegalStateException at Assert.java:97

 

 

build.gradle 수정

github actions이 돌아가는 runner에서 mysql을 사용할 것이기 때문에 해당 라이브러리를 추가해주었다.

plugins {
	id 'org.springframework.boot' version '2.7.5'
	id 'io.spring.dependency-management' version '1.0.15.RELEASE'
	id 'java'
}

group = 'com.ncookie'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'mysql:mysql-connector-java'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}

jar {
	enabled = false
}

 

 

Secret 등록

 

깃허브의 프로젝트 - Settings - Secrets - Actions 에 들어가서 키-값을 등록할 수 있다. 외부에 유출되면 안 되는 계정 정보 등을 넣어두고 github actions에서 불러올 수 있다.

 

 

 

나는 여기서 DATABASE_PROPERTIES와 MYSQL_PASSWORD를 등록해주었다. 이 둘은 runner 위에서 돌아갈 mysql 서버에서 사용할 키값들이고, AWS_DATABASE_PROPERTIES는 AWS의 RDS DB 연동 정보이다. 비밀번호는 알아서 설정해주면 되고, properties 파일을 사용할 것이기 때문에 그 형식에 맞게 데이터를 넣어주면 된다. 

 

여기서 주의할 점은 username을 root로 하지 말아야 한다. 아래에서 설명하겠지만, 불러오는 action에서 유저를 새로 등록하는데, 이름이 root이면 기존의 root 계정의 이름과 충돌해서 에러가 발생한다. 내가 이것 때문에 몇 시간 동안 뻘짓을 했었다.

 

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:{포트번호}/{DB 이름}
spring.datasource.username={사용자 이름}
spring.datasource.password={비밀번호}

 

 

gradle.yml 파일 수정

지난번에 사용했던 yml 파일을 수정한다.

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle

name: Spring Boot & Gradle CI/CD

on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ master ]

# 해당 코드에서 사용될 변수 설정
env:
  AWS_REGION: ap-northeast-2
  PROJECT_NAME: spring-practice
  S3_BUCKET_NAME: ncookie-github-actions-s3-bucket
  CODE_DEPLOY_APP_NAME: codedeploy-app
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: codedeploy-deployment-group

permissions:
  contents: read

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'
          
      # application.properties 파일 생성
      - name: Make application.properties
        run: |
          cd ./src/main/resources
          touch ./application.properties
          
          echo "${{ secrets.DATABASE_PROPERTIES }}" >> ./application.properties
        shell: bash
          
      - name: Setup MySQL
        uses: mirromutth/mysql-action@v1.1
        with:
          mysql database: 'board' 
          mysql user: 'test'
          mysql password: ${{ secrets.MYSQL_PASSWORD }}

      # gradlew 파일 실행권한 설정
      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew
        shell: bash

      # Gradle build (Test 제외)
      - name: Build with Gradle
        run: ./gradlew clean --stacktrace --info build
        shell: bash

      # create application-aws.properties
      - name: Make application-aws.properties
        if: contains(github.ref, 'master')
        run: |
          cd ./src/main/resources
          rm ./application.properties
          
          ls -al
          touch ./application.properties
          
          echo "copy properties"
          echo "${{ secrets.AWS_DATABASE_PROPERTIES }}" >> ./application.properties
        shell: bash

      # AWS 인증 (IAM 사용자 Access Key, Secret Key 활용)
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      # 빌드 결과물을 S3 버킷에 업로드
      - name: Upload to AWS S3
        run: |
          aws deploy push \
            --application-name ${{ env.CODE_DEPLOY_APP_NAME }} \
            --ignore-hidden-files \
            --s3-location s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip \
            --source .

      # S3 버킷에 있는 파일을 대상으로 CodeDeploy 실행
      - name: Deploy to AWS EC2 from S3
        run: |
          aws deploy create-deployment \
            --application-name ${{ env.CODE_DEPLOY_APP_NAME }} \
            --deployment-config-name CodeDeployDefault.AllAtOnce \
            --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
            --s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip

 

추가된 부분에 대해서 살펴보자.

 

Make application.properties

보통 properties 파일은 .gitignore에 등록해서 깃헙에 없기 때문에 우리가 임의로 작성해줘야 한다. 경로에 맞게 application.properties 파일을 추가해주고, 아까 등록해줬던 Secret 값을 파일에 삽입한다.

 

Setup MySQL

처음 봤을 때 이해가 잘 가지 않는 부분이었다. 아니, db를 설치해줘야 하는데 그런 명령어는 어디가고 설정값만 넣어줘? 라고 생각했었는데 저건 이미 다른 사람이 작성해둔 action을 가져오는 것이기 때문에 우리가 신경 쓸 필요는 없다. 이거 때문에 이전 글을 포스팅했었다. 대충 알고 넘어가려고 하는 버릇이 항상 이럴 때 발목을 붙잡는다.

 

저건 필요한 최소한의 정보만 기입한 것이고, 필요하다면 다른 옵션들도 넣을 수 있다. 자세한 내용은 해당 action의 marketplace를 확인하자.

 

주의할 점은 위 2개 job들은 모두 BUILD 이전에 실행되어야 한다. 그렇지 않으면 DB 관련 에러를 뱉어내는 광경을 볼 수 있다.

steps:
- uses: mirromutth/mysql-action@v1.1
  with:
    host port: 3800 # Optional, default value is 3306. The port of host
    container port: 3307 # Optional, default value is 3306. The port of container
    character set server: 'utf8' # Optional, default value is 'utf8mb4'. The '--character-set-server' option for mysqld
    collation server: 'utf8_general_ci' # Optional, default value is 'utf8mb4_general_ci'. The '--collation-server' option for mysqld
    mysql version: '8.0' # Optional, default value is "latest". The version of the MySQL
    mysql database: 'some_test' # Optional, default value is "test". The specified database which will be create
    mysql root password: ${{ secrets.RootPassword }} # Required if "mysql user" is empty, default is empty. The root superuser password
    mysql user: 'developer' # Required if "mysql root password" is empty, default is empty. The superuser for the specified database. Can use secrets, too
    mysql password: ${{ secrets.DatabasePassword }} # Required if "mysql user" exists. The password for the "mysql user"

 

Make application-aws.properties

사용 목적은 위와 동일하다. 다만 이건 AWS EC2 서버에서 사용할 값들이라는거다. 이것도 Secret에 값을 등록해서 불러오자.

 

 

번외 1

 

만약 기존에 다른 사람이 만들어둔 action을 사용하지 않고 직접 DB를 설치하려고 하거나 동작이 안 된다면 직접하는 방법도 있는 것 같다.

 

https://ovirium.com/blog/how-to-make-mysql-work-in-your-github-actions/

 

How to make MySQL work in your GitHub Actions // Slava Abakumov

There are a ton of tutorials out there how to make MySQL service work inside your Ubuntu environment in GitHub Actions. But all of them returned "Failed to initialize, mysql service is unhealthy." error for me. Let's fix that.

ovirium.com

https://velog.io/@rmswjdtn/Spring-Docker-Github-Action-Spring-Boot-%EC%9E%90%EB%8F%99%EB%B0%B0%ED%8F%AC%ED%99%98%EA%B2%BD-%EB%A7%8C%EB%93%A4%EA%B8%B0

 

[CICD] Docker + Github Action + Spring Boot 자동배포환경 만들기

최근에 github action을 이용해서 spring boot서버의 자동배포환경을 구축했습니다. docker-compose와 nginx도 함께사용하니 비슷한 설정을 원하시는 분들께 도움이 될 것같네요👍 어렵고 성가신(?) 과정이

velog.io

 

 

번외 2

프로젝트를 빌드할 때 --stacktrace와 --info 옵션을 켜주는게 좋을 것 같다. 이것저것 로그가 많이 뜨기 하지만 기본으로 나오는 에러 메세지는 너무 불친절하고 두루뭉실해서 문제 원인을 찾는데 힘들었다.

 

./gradlew clean --stacktrace --info build

 

마치며

 

찾아보니 github actions의 runner를 로컬 컴퓨터에서 돌리는 방법도 있고, 배포 시 Docker를 사용하는 경우도 있는 것 같다. 우선 이번 프로젝트까지는 이 방법을 사용해보고 다음부터 다른 시도들도 해보자. 우선은 이렇게 세팅해둔 인프라?를 가지고 이것저것 기능을 시험해보자.

 

 

참고링크

 

https://callmemaru.com/posts/github-actions-learning-1/

 

[GitHub Action Learning] #1 GitHub Actions 소개

최근에 개인 프로젝트를 여러 개 시작 했다. 배포에 대한 걱정을 좀 줄여보고자 처음으로 CI/CD를 공부해보자 마음 먹었다. CI/CD에 대해서 아는게 하나도 없어서, 서칭을 하다가 그저 탭으로만 마

callmemaru.com

https://enant.tistory.com/29

 

Github Action 빌드시 contextLoads Failed 오류

spring server를 ec2에 배포하는 과정을 자동화할 방법을 찾다가, github Action을 사용해보기로 했다. Github Action? Github Action을 간단히 설명하면 빌드, 테스트, 배포 등의 작업을 자동화 시켜주는 도구이

enant.tistory.com