-
[pipeline] 백엔드(spring boot) 프로젝트에 다양한 ci/cd 방식 적용하기
intro : 백엔드(spring boot) 프로젝트에 다양한 ci/cd 방식을 적용해보자.
하기 글은 다음과 같은 과정이 선행되어야 합니다. 다른 글들에 비해 생략된 과정이 많습니다.
[aws] ec2를 통해 백엔드 api 서버 배포하기 (1)
[aws] ec2를 통해 백엔드 api 서버 배포하기 (2)
[pipeline] ci/cd와 github actions의 기본 개념
개인 프로젝트에서 많이 쓰는 CI/CD 구축 방법
이 방법은 주로 개인 및 소규모 프로젝트에서 CI/CD를 심플하고 빠르게 적용시키고 싶을 때 사용한다. 해당 방식은 다음과 같은 장점과 단점을 가진다.
(전체적인 흐름)
장점
git pull을 활용해서 변경된 부분의 프로젝트 코드에 대해서만 업데이트 하기 때문에 CI/CD 속도가 빠르다. 대부분의 CI/CD 방식들은 전체 프로젝트를 통째로 갈아끼우는 방식을 사용한다. CI/CD 툴로 Github Actions만 사용하기 때문에 인프라 구조가 복잡하지 않고 간단하다.
단점
빌드 작업을 EC2에서 직접 진행하기 때문에 운영하고 있는 서버의 성능에 영향을 미칠 수 있다. Github 계정 정보가 해당 EC2에 저장되기 때문에 개인 프로젝트 또는 믿을만한 사람들과 같이 진행하는 토이 프로젝트에서만 사용해야 한다.
CI/CD를 적용하기 전의 과정
CI/CD를 도입하기 전의 배포 과정을 살펴보자. 일반적으로 AWS EC2를 사용하는 환경을 가정하면 기본적인 아키텍처와 배포 절차는 다음과 같다.
1. 코드 Push : 개발자가 GitHub의 Main Branch에 코드를 Push 한다.
2. 소스 Pull : AWS EC2에 SSH로 접속한 후, GitHub에서 최신 Main Branch 코드를 Pull 한다.
3. 빌드 및 실행 : EC2 내에서 코드를 빌드하고 애플리케이션을 재 실행 한다.
이 과정의 가장 큰 문제는 최신 코드를 배포할 때마다 개발자가 직접 EC2에 접속하여 수작업으로 Pull, 빌드, 실행을 반복해야 한다는 점이다. 이는 번거롭고, 효율성이 떨어지며, 실수 발생 가능성도 높다. 이러한 문제를 해결하기 위해 우리는 CI/CD를 도입하여 배포 프로세스를 자동화할 것이다.
하기 단계를 진행하기전에 반드시 진행해야 합니다.
sample 프로젝트 로컬에 설치
해당 프로젝트 AWS EC2에 직접 Git Pull 받고 Build하여 실행
정상적으로 Public IP로 접근되는지 확인하기
GitHub Actions에서 EC2에 직접 접근 후 빌드 및 실행
주의사항
AWS EC2 구축시에, 인스턴스 유형으로 t3a.small을 선택하여 구성하자, 프리티어의 t2.micro는 빌드시에 엄청난 리소스에 서버가 먹통이돤다.
Step1. sample 프로젝트 다운로드
기존의 단점이었던 문제를 해결하기 위해서 GitHub Actions에서 AWS의 EC2에 SSH로 접근하여 빌드 후 실행하는 과정을 yml파일로 작성해 보자. 서버파일은 sample 프로젝트를 사용하는것을 권장한다. 해당 프로젝트를 로컬에 다운받고 먼저 실행해 보자. 별다른 문제없이 다음과 같은 화면이 나온다면 문제없이 실행된 것을 알 수 있다.
(정상실행 화면)
Step2. sample 프로젝트 deploy.yml 파일 생성
sample 프로젝트 루트에 .github/workflows 폴더를 생성하고 deploy.yml 파일을 생성한다. 그렇다면 다음과 같은 구조가 되어있을 것이다.
(정상실행 화면)
Step3. deploy.yml 파일에 코드 작성
이어서 deploy.yml 파일에 다음과 같은 코드를 작성한다.
{% raw %}
name: Deploy To EC2
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: SSH로 EC2에 접속하기
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.EC2_HOST }} # EC2의 주소
username: ${{ secrets.EC2_USERNAME }} # EC2 접속 username
key: ${{ secrets.EC2_PRIVATE_KEY }} # EC2의 Key 파일의 내부 텍스트
script_stop: true
script: |
cd /home/ubuntu/api-server-pipeline # 여기 경로는 자신의 EC2에 맞는 경로로 재작성하기
git pull origin main
./gradlew clean build
sudo fuser -k -n tcp 8080 || true
nohup java -jar build/libs/*SNAPSHOT.jar > ./output.log 2>&1 &
{% endraw %}
secrets 값 설정은 아래 공간에서 해야 합니다
(secrets 값 설정)
Step4. Code를 GitHub에 Push 하기
위 단계에서 deploy.yml 파일에 코드를 작성하였는데, 해당 코드를 push한다. 그럼 GitHub Actions 탭에서 다음과 같은 화면을 확인 할 수 있다.
(정상적으로 GitHub Actions으로 빌드 및 배포가 된 모습1)
(정상적으로 GitHub Actions으로 빌드 및 배포가 된 모습2)
이 방법의 단점
가장 처음에는 AWS EC2의 유형으로 t2.micro를 사용하여 똑같은 deploy.yml파일의 설정으로 CI/CD를 진행해 보았는데, 서버가 먹통이 되었다. 아무래도 빌드하는 과정이 생각보다 리소스를 크게 잡아먹는듯 하여 타임아웃 오류가 났는데, 인스턴스 유형을 t3a.small 변경하고 재 진행하여보니 정상적으로 실행된 것을 확인할 수 있었다. 또다른 해결책으로는 swap을 통해서 메모리 사용량을 늘리는 방법이 있다고 한다. 무료 인스턴스 유형으로 해결하기에는 좀 버거워 보인다.
일반 프로젝트에서 많이 쓰는 CI/CD 구축 방법
이 방법은 주로 현업에서 초기 서비스를 구축할 때 이 방법을 많이 활용한다. 처음 서비스를 구현할 때는 대규모 서비스에 적합한 구조로 구현하지 않는다. 확장의 필요성이 있다고 느끼는 시점에 인프라를 고도화하기 시작한다. 왜냐하면 복잡한 인프라 구조를 갖추고 관리하는 건 생각보다 여러 측면에서 신경(비용)쓸 게 많아지기 때문이다. 해당 방식은 다음과 같은 장점과 단점을 가진다.
(전체적인 흐름)
장점
빌드 작업을 Github Actions에서 하기 때문에 운영하고 있는 서버의 성능에 영향을 거의 주지 않는다.
CI/CD 툴로 Github Actions만 사용하기 때문에 인프라 구조가 복잡하지 않고 간단하다.
단점
무중단 배포를 구현하거나 여러 EC2 인스턴스에 배포를 해야 하는 상황이라면, 직접 Github Actions에 스크립트를 작성해서 구현해야 한다. 직접 구현을 해보지는 않았는데 꽤 복잡하다고 한다.
하기 단계를 진행하기 전에 위 단계를 꼭 거쳐주세요
lsof -i :8080 // 8080 포트로 실행중인 프로세스 찾기
kill -9 PID // pid 값에 8080 포트로 실행중인 프로세스 종료하기
rm -rf api-server-pipeline // 기존에 EC2에 설치되어있는 sample 프로젝트 삭제하기
GitHub Actions에서 빌드 후 EC2에 빌드 파일 실행
기존에는 GitHub Actions의 역할이 EC2에 접근하여 직접 빌드 및 실행 하는 역할이었다. 이번에는 EC2에서 빌드하는 것이 아닌, GitHub Actions에서 직접 빌드하고, 빌드된 파일을 EC2에 전달만하여 빌드파일을 EC2는 실행만 하는 역할로 CI/CD를 구축해볼 것이다.
Step1. deploy.yml 파일 수정
deploy.yml 파일을 다음과 같이 수정한다. 중요한 부분은 SCP를 통해서 GitHub Action에서 빌드한 파일을 EC2에 전달한다는 점이다.
{% raw %}
name: Deploy To EC2
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Github Repository 파일 불러오기
uses: actions/checkout@v4
- name: JDK 17버전 설치
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
- name: 테스트 및 빌드하기
run: ./gradlew clean build
- name: 빌드된 파일 이름 변경하기
run: mv ./build/libs/*SNAPSHOT.jar ./project.jar
- name: SCP로 EC2에 빌드된 파일 전송하기
uses: appleboy/scp-action@v0.1.7 # 빌드 파일 전송 라이브러리
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_PRIVATE_KEY }}
source: project.jar
target: /home/ubuntu/api-server-pipeline/tobe
- name: SSH로 EC2에 접속하기
uses: appleboy/ssh-action@v1.0.3 # SSH 접속 라이브러리
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_PRIVATE_KEY }}
script_stop: true
script: |
rm -rf /home/ubuntu/api-server-pipeline/current
mkdir /home/ubuntu/api-server-pipeline/current
mv /home/ubuntu/api-server-pipeline/tobe/project.jar /home/ubuntu/api-server-pipeline/current/project.jar
cd /home/ubuntu/api-server-pipeline/current
sudo fuser -k -n tcp 8080 || true
nohup java -jar project.jar > ./output.log 2>&1 &
rm -rf /home/ubuntu/api-server-pipeline/tobe
{% endraw %}
Step2. Controller 파일의 return 값을 수정
정상적으로 반영되었는지 확인을 하기위헤서 return 값을 수정해보자.
package com.example.api_server_pipeline.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AppController {
@GetMapping("/")
public String index() {
return "Hello World, GitHub Actions CI/CD Success! + 변경하였습니다!";
}
}
Step3. 변경된 파일들을 git push 하고 결과 확인
수정된 deploy.yml 파일과 Controller 파일을 git push 하여 정상적으로 EC2에 빌드하여 실행되었는지 확인해보자.
(GitHub Actions에서 deploy.yml 파일이 정상적으로 실행된 모습 1)
(GitHub Actions에서 deploy.yml 파일이 정상적으로 실행된 모습 2)
SCP를 통한 CI/CD 구축
위 방법은 굉장히 개인적으로 진행하는 서비스에 당장 적용해보고싶은 방법이다. EC2 성능에 영향을 미치지도 않고, 빌드도 GitHub 에서 진행하는거라서 부담도 없는거 같다.
-
[pipeline] ci/cd와 github actions의 기본 개념
intro : ci/cd의 개념에 대해서 이해하고, ci/cd의 대표적인 github actions 방식에 대해서 알아보자.
하기 글에 대해 참고할수 있는 sample project 입니다.
sample project click!
CI/CD란?
CI/CD는 Continuous Integration, Continuous Deployment의 약어로, 지속적인 통합과 지속적인 배포의 의미를 담은 단어이다.
CI/CD를 왜 배워야 할까?
우리가 어떤 웹 서비스(서버)를 AWS EC2에 배포하고, 특정 기능을 개발 중이라고 가정해보자. 이 경우 보통 다음과 같은 과정을 거치게 된다. 먼저, 새로 개발한 기능을 GitHub에 push한 뒤 EC2에 접속하여 main 브랜치의 최신 코드를 가져오기 위해 git pull을 실행한다. 이후 build와 실행 과정을 거쳐 변경된 서비스를 배포하게 된다.
이 과정이 코드 수정이 발생할 때마다 반복적으로 수행된다고 생각해보자. 상당히 번거롭고 비효율적이지 않은가? 이런 비효율적인 과정을 해결하고자 CI/CD라는 개념이 탄생했다.
CI/CD는 코드 변경 사항을 테스트하고, 빌드 및 배포 과정을 자동화하여 개발 생산성을 크게 향상시킨다. 이를 통해 개발자는 반복적인 작업에서 벗어나 본질적인 개발 업무에 집중할 수 있다. 특히, 빠른 배포와 안정성을 유지해야 하는 현대 소프트웨어 개발 환경에서는 필수적인 기술이라 할 수 있다.
(CI/CD의 일반적인 과정)
CI/CD 흐름을 이해하기 위한 Github Actions 개념
Github Actions는 로직을 실행시킬 수 있는 별도의 컴퓨터라고 생각하면 된다. CI/CD 과정에서 Github Actions는 빌드, 테스트, 배포에 대한 로직을 실행시키는 역할을 하게 된다.
Github Actions을 통한 CI/CD 전체 흐름
CI/CD의 구성방식은 다양하지만 일반적으로 다음의 흐름을 거친다. 개발자 GitHub애 커밋 > GitHub Actions의 Event Trigger 실행 (commit 감지) > AWS EC2로 배포
(전체 흐름)
Github Actions 기본 문법 및 사용법 정리
Step1. 프로젝트 생성 및 폴더, 파일 생성
가장 먼저 Intelij에 Empty Project를 생성한다. 프로젝트명은 다음과 같이 github-actions-study로 지정하였는데, 본인이 원하는 프로젝트 명으로 설정하여도 무방하다. 프로젝트 루트 경로에 .github 라는 이름의 폴더를 만들고 해당 폴더안에 workflows 라는 폴더를 만들어보자. 그 뒤에 deploy.yml 파일을 생성하자.
폴더 및 파일 생성시 주의 사항
.github > workflows 의 폴더 이름들은 철자가 틀리면 안된다. 사용자가 원하는 이름으로 변경해서는 안되고 꼭 지정된 이름으로 생성해 주어야 한다. 다만 확장자 .yml 파일의 이름은 사용자가 원하는 이름으로 생성하여도 무방하다.
(주의 사항 잘 읽고 폴더와 파일을 생성하자.)
Step2. deploy.yml 코드 작성하기
deploy.yml 파일에 다음과 같이 코드를 작성해보자.
name: GitHub Actions 실행시켜보기
on:
push:
branches:
- main
jobs:
My-Deploy-job:
runs-on: ubuntu-latest
steps:
- name: Hello World 출력
run: echo "Hello World"
Step3. github에 프로젝트 push 하기
그 뒤에, github에 repository를 생성하고 push 해준다. 그 뒤에 github 페이지의 Actions tab으로 이동해보면 검정색 화면에서 무언가 실행되고 있는것을 확인할 수 있다. 물론 너무빠르게 실행이 되어서 완료가 된 화면만 볼 수도 있다.
(github-actions-study 프로젝트가 github의 repository에 잘 저장되었다.)
(yml 파일에 입력하였던 echo "Hello World"가 잘 실행된 모습.)
지금까지의 과정속의 deploy.yml 파일의 코드 해석하기
deploy.yml 파일의 최상단 name 부분을 보면 GitHub Actions 실행시켜보기 라고 작성하였었다. 이 부분은 Github Actions에서 workflows의 이름이 된다.
(코드를 확인하세요)
(name: GitHub Actions 실행시켜보기)
on push ~ main의 해당하는 코드 부분의 내용은 해석하자면, 브랜치명이 main에 push가 일어난다면, yml파일에 작성한 로직(jobs)을 실행하겠다 라는 뜻이다.
(코드를 확인하세요)
이어서 jobs ~ run의 해당하는 코드 부분의 내용을 해석해보자.
(코드를 확인하세요)
가장 먼저 jobs에 대해 짚고 넘어가자. 하나의 workflow는 반드시 1개 이상의 job으로 구성되어야 한다. 만약 여러 개의 job으로 구성된 workflow라면, 이들은 기본적으로 병렬적으로 수행된다. 그러나 현재 코드는 단 하나의 job으로만 이루어져 있어, 실행 시 이 단일 job만 수행된다.
My-Deploy-Job 부분의 내용은 job을 식별할 수 있는 이름이라고 볼 수 있는데, 다음과 같이 Actions tab에서 이름이 지정되어 있는것을 볼 수 있다.
(job을 식별하는 ID라고도 볼 수 있음.)
runs-on 부분은 어떤 서버(컴퓨터)에서 Github Actions를 실행시킬 것인지를 작성하게 되는데, 보통 이런 OS는 리눅스의 Ubuntu를 사용하고 최신 버전임을 알리는 latest 키워드가 뒤에 붙게된다.
마지막으로 steps에 대해 살펴보자. 하나의 job은 여러 개의 step으로 구성될 수 있다. 마치 하나의 workflow에 여러 개의 job이 포함될 수 있는 것처럼, 하나의 job 안에도 여러 개의 step이 존재할 수 있다. 각 step은 특정 작업을 수행하며, 순차적으로 실행된다.
Step4. deploy.yml파일에 step 추가하기
기존에 작성한 deploy.yml 파일의 내용을 어느정도 알아보았다. 우리는 이번에 step 부분에 여러개의 명령어를 한번에 입력해 볼것이다. 코드를 다음과 같이 수정해보자.
name: GitHub Actions 실행시켜보기
on:
push:
branches:
- main
jobs:
My-Deploy-job:
runs-on: ubuntu-latest
steps:
- name: Hello World 출력
run: echo "Hello World"
- name: 여러 명령어 문장 출력하기
run: |
echo "Good"
echo "Morning"
여러 명령어 문장 출력하기라는 step이 추가되었다. 기존의 step > run과 다른 점은, 이번에는 명령어가 한 줄이 아닌 여러 개로 구성되어 있다는 것이다. 때로는 여러 개의 명령어를 하나의 step에서 실행하고 싶을 때가 있는데, 이 경우 | 를 사용하여 여러 명령어를 하나의 run 블록 안에 작성할 수 있다. 이를 통해 한 step에서 여러 작업을 순차적으로 처리할 수 있다.
Step5. 수정된 deploy.yml파일 push 하기
정말로 step 안의 여러 명령어가 실행되는지 확인해보자. 이전에 결과를 확인했던 것과 같이 github의 Actions의 tab에 들어가서 확인 할 수 있다. 우리가 예상한것과 같이 여러 명령어(echo Good, echo Morning)가 실행이 잘 되었다.
(Good Morning이 잘 출력되었다.)
Step6. GitHub Actions 환경변수 사용해보기
Github Actions 자체에는 저장되어 있는 변수가 있다. 대표적으로 아래 테이블에 존재하는 지정된 예약어가 존재하는데 우리는 그것 중 몇개를 deploy.yml 파일에 작성하여 ehco 명령어로 출력해 볼것이다.
변수
설명
GITHUB_REPOSITORY
리포지토리 이름 (owner/repo) 형태입니다.
GITHUB_SHA
실행 중인 커밋의 SHA 값입니다.
GITHUB_REF
실행 중인 브랜치 또는 태그의 참조명입니다.
GITHUB_EVENT_NAME
워크플로를 실행시킨 이벤트 이름입니다 (예: push, pull_request).
RUNNER_OS
실행 환경의 운영체제입니다 (Linux, Windows, macOS).
코드를 다음과 같이 수정한다.
name: GitHub Actions 실행시켜보기
on:
push:
branches:
- main
jobs:
My-Deploy-job:
runs-on: ubuntu-latest
steps:
- name: Hello World 출력
run: echo "Hello World"
- name: 여러 명령어 문장 출력하기
run: |
echo "Good"
echo "Morning"
- name: Github Actions 자체에 저장되어 있는 변수 사용해 보기
run: |
echo $GITHUB_SHA
echo $GITHUB_REPOSITORY
해당 코드를 수정한 뒤에 git push를 진행해보자. 아마 다음과 같은 값이 Actions tab에서 출력될 것이다.
($GITHUB_SHA, $GITHUB_REPOSITORY 값이 잘 출력된 것을 볼 수 있다.)
참고로 $GITHUB_SHA 값은 우리가 방금 커밋한 commit hashcode 값과 일치한다.
Step7. GitHub Actions Secret 환경변수 사용해보기
이전 단계에서 살펴본 것처럼, GitHub Actions는 자체적으로 제공하는 환경 변수(예: GITHUB_REPOSITORY, GITHUB_SHA 등)가 존재한다. 하지만 이 환경 변수들은 코드 실행과 관련된 정보만 제공되며, DB 비밀번호나 API 키처럼 민감한 값들을 관리하는 용도로는 적합하지 않다. 이를 해결하기 위해 GitHub에서는 Actions Secrets 기능을 제공합니다. Secrets를 사용하면 민감한 데이터를 안전하게 저장하고 워크플로에서 참조할 수 있다.
아래의 이미지를 참고하여 GitHub > Setting > Secrets and variables > Actions 으로 이동해보자.
(secrets and variables로 잘 이동해보자)
그 뒤에 Repository secrets의 New Repository Secret버튼을 클릭해보자.
(New Repository Secret 버튼 클릭)
다음과 같이 Name과 Secret에 값을 입력하였는데, Name은 아무렇게나 내가 인식할수 있는 값으로 입력하고 Secret에는 민감한 데이터 값을 입력하면된다. 나는 테스트용도의 값들이기에 무작위의 값을 입력하였다. 그 뒤 Add secret 버튼을 클릭하여 저장해주자.
(잘 입력해보자.)
자 이제 다시 deploy.yml 파일로 와서 우리가 Secret 값으로 저장한 API_KEY_PASSWORD 값에 대해서 출력해보자. 아래 코드를 참고해서 수정 후에 git push 하여 어떻게 Actions 에서 출력되는지 확인해보자.
주의사항
secret 값을 사용할때는 secrets를 앞에 붙이고 . 후에 Name 값을 입력해야한다.
{% raw %}
name: GitHub Actions 실행시켜보기
on:
push:
branches:
- main
jobs:
My-Deploy-job:
runs-on: ubuntu-latest
steps:
- name: Hello World 출력
run: echo "Hello World"
- name: 여러 명령어 문장 출력하기
run: |
echo "Good"
echo "Morning"
- name: Github Actions 자체에 저장되어 있는 변수 사용해 보기
run: |
echo $GITHUB_SHA
echo $GITHUB_REPOSITORY
- name: 아무한테나 노출이 되면 안되는 값
run: |
echo ${{ secrets.API_KEY_PASSWORD }}
{% endraw %}
출력하고자 하였던 API_KEY_PASSWORD 값이 Actions에서 *** 로 출력된다. 민감한 값을 보안을 지키면서 사용 할 수 있음을 알 수 있다.
(*** 으로 민감한 값이 출력된다.)
다음으로
이렇게 하여 기본적인 CI/CD 개념과 GitHub Actions 사용법을 알아보았다. 다음 단계로 Spring Boot 프로젝트에 CI/CD를 적용하는 실습을 진행해보겠다.
Touch background to close