Spring Boot 프로젝트 GitHub Actions CI/CD 구축하기
GitHub Actions CI/CD 구축하기(Spring Boot 프로젝트)
CI/CD는 단순히 번거로운 배포 과정을 자동으로 만들 뿐만 아니라, 수동으로 배포했을 때 발생할 수 있는 사용자의 실수를 줄일 수 있고, 자동화된 테스트 과정을 통해 코드의 품질을 검증할 수 있으며, 일관된 빌드 환경을 통해 환경의 차이로 발생하는 문제를 최소화할 수 있는 등의 장점을 가지고 있습니다.
* CI/CD: 지속적 통합(Continuous Integration) / 지속적 배포(Continuous Deployment)
GitHub Actions 외에도 Jenkins, GitLab CI, CircleCI 등 CI/CD를 구축할 수 있는 다양한 도구들이 있지만, 우선은 사용하고 있는 GitHub 저장소와 연동하여 쉽게 사용할 수 있다는 점에서 GitHub Actions을 통한 CI/CD를 구축해 보았습니다.
아래 예시에서는 spring boot 프로젝트를 gcp 인스턴스에 github actions를 통해 ci/cd 되도록 구축한 내용을 정리하였으며, 전체적인 순서는 다음과 같습니다.
1. CI/CD 구축할 대상 프로젝트
2. GitHub Actions 설정
3. Workflow 파일의 구성
4. 수정된 파일 및 세부 내용
5. CI/CD 동작 확인
1. CI/CD 구축할 대상 프로젝트(spring boot + gradle)
@RestController
@RequestMapping("/cicd")
public class CicdController {
@GetMapping("/test")
public String cicdTest() {
return "cicd 'version 1.0.0'";
}
}
(cicd test controller)
Spring Web, Lombok 정도의 Dependencies만 추가하여 간단하게 프로젝트를 생성하였으며, ci/cd 파이프라인을 통한 배포가 정상적으로 이뤄졌는지 확인하기 위해 다음과 같은 간단한 Controller를 만들었습니다.
(gradle 기반 프로젝트)
### properties (gradle-wrapper.properties 파일 제외) ###
**/*.properties
!gradle/wrapper/gradle-wrapper.properties
application.properties 파일과 같은 '*.properties' 파일의 경우 데이터베이스 암호 등 보안적으로 민감한 정보들이 포함되어 있기 때문에 '.gitignore' 파일을 통해 git 저장소에서 관리되지 않도록 하였는데요.
이렇게 되면 application.properties 파일이 git 저장소에 올라가 있지 않기 때문에 ci/cd 과정에서 해당 파일을 참조할 수 없게 되는데, 해당 내용과 관련해서는 아래 github actions 설정 부분에서 다시 이야기하도록 하겠습니다.
(프로젝트 생성 후 git repository push 하였으며, ci/cd 구축 대상 프로젝트는 git repository에 올라가 있는 상태여야 합니다.)
2. GitHub Actions 설정
먼저 해당 프로젝트의 git repository에서 Actions 부분으로 이동합니다.
'(해당 repository)/.github/workflows/' 하위에 ci/cd를 위한 'workflow' 파일을 생성할 수 있으며, GitHub Actions에서는 다양한 자동화 작업을 위한 기본적인 workflow 양식을 제공해 주는데요.
gradle 기반 프로젝트의 ci/cd를 위해 제공되는 'Java with Gradle'을 선택하고 기본 양식에서 필요한 부분을 수정하여 사용하였습니다.
아래 과정에 따라 해당 파일을 수정 후 우측 'Commit changes...' 버튼을 통해 수정 내용을 반영할 수 있습니다.
3. Workflow 파일의 구성
이어서 전체적인 workflow 파일의 구성에 대해서 살펴본 뒤, 세부 내용이 수정된 워크플로 파일과 각각의 부분에 대한 상세 내용을 살펴보겠습니다.
- Workflow
프로세스를 구성하는 가장 큰 단위인 워크플로는 jobs(하나 이상의 작업)를 실행할 구성 가능한 자동화된 프로세스입니다.
workflow는 repository의 .github/workflows 디렉터리에 위치하며, YAML 형식(.yml)으로 정의됩니다.
필요한 작업에 따라 여러 개의 workflow가 존재할 수 있습니다.
- Event
이벤트는 workflow의 실행을 트리거하는 git repository의 특정 활동입니다.
아래 예시에서는 자주 사용되는 'push'와 'pull_request'만 workflow의 실행 트리거로 사용되었는데, actions 문서를 살펴보면 트리거로 사용될 수 있는 이벤트가 상당히 많다는 것을 알 수 있습니다.
(check_run, check_suite, deployment, deployment_status, fork, issues, merge_group 등)
- Job
어떤 작업을 할지 정의할 수 있으며, job은 여러 개의 step으로 구성되어 있습니다.
job 간 종속성을 구성할 수 있으며, 기본적으로 작업은 종속성이 없으며 병렬로 실행됩니다.
- Step
step은 job 내부적으로 실행되는 각각의 단계로, 각각의 step은 순차적으로 진행됩니다.
- Action
action은 step에서 실행되는 개별적인 동작으로, GitHub Actions에서는 자주 사용되는 복잡한 명령(동작)들을 action으로 만들어 사용자들이 사용하기 쉽게 하였습니다.
예를 들어 'actions/checkout'은 repository의 코드를 체크아웃하는 action이며, 'actions/upload-artifact'는 빌드 결과물 또는 테스트 아티팩트를 업로드하는 action, 'actions/setup-java'는 java 환경을 설정하는 action입니다.
(외에도 GitHub Marketplace를 통해 step에서 사용할 수 있는 다양한 action을 확인할 수 있습니다.)
여기까지가 workflow의 구성에 대한 간략한 내용이며, 외에 추가적인 내용과 더 자세한 내용은 아래 GitHub Action 공식 문서를 참고하시면 좋을 것 같습니다.
(https://docs.github.com/ko/actions/about-github-actions/understanding-github-actions)
4. 수정된 파일 및 세부 내용
name: Java CI with Gradle
# 0. workflow 실행 트리거
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
# 1. 빌드
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
# 1-1. repository checkout
- uses: actions/checkout@v4
# 1-2. jdk 환경 설치
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
# 1-3. '*.properties' 파일 세팅
- name: application.properties 파일 설정
run: |
mkdir -p src/main/resources
mkdir -p src/test/resources
echo "${{ secrets.APPLICATION_PROPERTIES }}" > ./src/main/resources/application.properties
echo "${{ secrets.TEST_APPLICATION_PROPERTIES }}" > ./src/test/resources/application.properties
# 1-4. gradle 환경 설치
- name: Setup Gradle
uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
# 1-5. 빌드
- name: Build with Gradle Wrapper
run: ./gradlew clean build
# 1-6. 빌드된 파일 이름 변경
- name: 빌드된 파일 이름 변경하기
run: mv ./build/libs/*SNAPSHOT.jar ./cicdtest.jar
# 1-7. 빌드 및 이름 변경된 cicdtest.jar 파일 EC2 전송
- name: SCP로 EC2에 빌드된 파일 전송하기
uses: appleboy/scp-action@master
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: cicdtest.jar
target: /home/ubuntu
# 2. 배포
deploy:
needs: build
runs-on: ubuntu-latest
steps:
# 2-1. EC2 접속 및 jar 파일 실행
- name: SSH로 EC2에 접속하기
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script_stop: true
script: |
sudo fuser -k -n tcp 8080 || true
sudo nohup java -jar cicdtest.jar > ./output.log 2>&1 &
(수정된 workflow 파일)
해당 내용은 ci/cd 파이프라인 구축을 위해 최종 수정된 workflow 파일입니다.
각각의 프로젝트 및 서버 환경에 따라 세부적인 내용이 달라질 수 있기 때문에 각 부분에 대한 내용을 참고하여 적절하게 수정해 주시면 됩니다.
# 0. workflow 실행 트리거
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
가장 먼저 workflow가 실행되는 트리거 이벤트를 정의하는 부분입니다.
main branch에 대해 push 또는 pull_request가 발생했을 때 workflow가 동작되도록 정의하였으며, 외에도 다양한 이벤트 트리거가 존재하는데, 필요한 트리거 및 사용 방법은 아래 공식 문서를 참고하시면 좋을 것 같습니다.
jobs:
# 1. 빌드
build:
runs-on: ubuntu-latest
permissions:
contents: read
해당 workflow는 크게 'build' job과 'deploy' job으로 나눠져 있으며, 먼저 'build' job 부분에 대한 정의입니다.
'runs-on'은 job을 실행시키는 인스턴스 OS와 버전으로 각 job들은 독립적인 runner(container)에서 실행됩니다.
'permissions'는 git repository의 contents에 대한 권한으로 읽기 권한만 부여함을 의미합니다.
steps:
# 1-1. repository checkout
- uses: actions/checkout@v4
# 1-2. jdk 환경 설치
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
runner에서 repository의 코드를 체크아웃하고 jdk 환경을 설치합니다.
이때 GitHub Actions에서 제공되는 action이 사용되었습니다.
# 1-3. '*.properties' 파일 세팅
- name: application.properties 파일 설정
run: |
mkdir -p src/main/resources
mkdir -p src/test/resources
echo "${{ secrets.APPLICATION_PROPERTIES }}" > ./src/main/resources/application.properties
echo "${{ secrets.TEST_APPLICATION_PROPERTIES }}" > ./src/test/resources/application.properties
앞서 프로젝트 부분에서 이야기한 것처럼 '.properties' 파일의 경우 보안적으로 민감한 정보가 포함되어 있을 수 있기 때문에 gitignore 처리가 되어 있고 git repository에서 관리되지 않습니다.
하지만 프로젝트를 build 하기 위해서는 해당 파일을 필요로 하는데요.
repository 상단의 'Settings' -> 'Secrets and variables' -> 'New repository secret'을 통해 각각의 properties 파일을 git에서 등록, 관리하여 ci/cd 과정에서 사용할 수 있습니다.
# 1-4. gradle 환경 설치
- name: Setup Gradle
uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
# 1-5. 빌드
- name: Build with Gradle Wrapper
run: ./gradlew clean build
runner에서 gradle 환경 설치와 build 작업을 수행합니다.
빌드 과정에서 test의 실패로 빌드가 되지 않는 경우 'run: ./gradlew clean build -x test'와 같이 -x test 내용을 추가하여 test가 실행되지 않도록 제외할 수 있습니다.
# 1-6. 빌드된 파일 이름 변경
- name: 빌드된 파일 이름 변경하기
run: mv ./build/libs/*SNAPSHOT.jar ./cicdtest.jar
# 1-7. 빌드 및 이름 변경된 cicdtest.jar 파일 EC2 전송
- name: SCP로 EC2에 빌드된 파일 전송하기
uses: appleboy/scp-action@master
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: cicdtest.jar
target: /home/ubuntu
빌드된 jar 파일 이름을 변경하고 해당 파일을 배포할 서버(EC2)로 전송합니다.
전송 과정에서 해당 서버의 host, username, key 등이 필요한데, 해당 정보 역시 properties 파일에서 처럼 'Secrets and variables'를 통해 외부에 노출하지 않고 관리하는 것이 보안적인 측면에서 좋습니다.
'source'는 scp-action으로 전송할 대상 파일이며, 'target'은 파일을 전송할 대상 서버의 저장 경로입니다.
(서버에서 해당 디렉터리가 생성되어 있어야 하며, 해당 user의 쓰기 권한이 필요합니다.)
private key의 경우 ssh key 생성 및 등록 과정이 필요한데, 해당 부분은 아래 GCP 인스턴스 접속하는 방법에 대한 내용을 참고해 주시면 됩니다.
2022.07.29 - [Programming/Web] - GCP 인스턴스 SSH 접속하는 방법
# 2. 배포
deploy:
needs: build
runs-on: ubuntu-latest
'deploy' job에서는 'needs'를 통해 'build' job이 완료된 후 해당 작업이 실행되도록 종속성을 설정하였습니다.
steps:
# 2-1. EC2 접속 및 jar 파일 실행
- name: SSH로 EC2에 접속하기
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script_stop: true
script: |
sudo fuser -k -n tcp 8080 || true
sudo nohup java -jar cicdtest.jar > ./output.log 2>&1 &
간단하게 8080 포트에서 실행 중인 프로세스를 종료하고 대상 jar 파일을 백그라운드에서 실행하도록 처리하였습니다.
필요에 따라 배포 스크립트(.sh 파일)를 따로 만들고 해당 스크립트를 실행하는 방식도 적용할 수 있습니다.
(서버에서는 프로젝트에 필요한 java 외 다른 환경들이 세팅되어 있어야 합니다.)
5. CI/CD 동작 확인
위 과정과 같이 workflow(gradle.yml) 파일을 수정하게 되면 이후 이벤트 트리거 발생 시 workflow가 실행되고 ci/cd 파이프라인이 동작하게 되는데요.
(gradle.yml 파일 저장 시에도 main branch에 바로 커밋하게 되면 워크플로가 동작하게 됩니다.)
repository 상단 'Actions' 탭에서 workflow의 동작 과정을 확인할 수 있으며, 아래 이미지와 같이 각각의 job에 대한 세부 동작 내용도 확인할 수 있습니다.
각각의 job에서는 각 step이 순차적으로 진행되는 것을 확인할 수 있으며, 각 step 역시 클릭하여 상세 내용을 확인할 수 있습니다.
@RestController
@RequestMapping("/cicd")
public class CicdController {
@GetMapping("/test")
public String cicdTest() {
return "cicd 'version 1.0.1'";
}
}
작업 완료 후 테스트용 api의 return 값을 'version 1.0.0' -> 'version 1.0.1'로 수정하여 push 하여 workflow가 정상적으로 동작하고 서버에 배포까지 되는 것을 확인하였습니다.
여기까지 Spring Boot 프로젝트 GitHub Actions CI/CD 구축하기 내용이며, 내용 중 잘못된 부분이나 궁금하신 부분은 댓글 남겨주시면 확인하고 답변드리도록 하겠습니다. 감사합니다.