여행 관련 서비스를 개발하면서 프론트엔드와 협업을 위해 AWS EC2로 간단하게 서비스를 배포했습니다.
하지만 코드의 변경 및 기능 추가가 있을 때마다 EC2에 접속하여 실행 중인 서비스를 종료하고 jar를 최신화하고 재실행하는 과정에서
휴먼 에러와 재배포 과정에서의 시간적 딜레이가 발생했습니다.
이로 인해 CI/CD의 필요성을 느끼게 되었고, Github Actions의 CI/CD를 통해 위의 문제를 해결하고자 하였습니다.
CI/CD 란?
CI/CD는 지속적 통합(Continuous Integeration) 및 지속적 제공/배포(Continuous Delivery/Deployment)를 의미한다.
여기서 지속적 통합(CI)는 코드 변경 사항이 있을 경우 주기적으로 빌드/테스트되어 공유 레포지토리에 통합되는 것이고 이를 통해 개발자가 동시에 애플리케이션 개발과 관련된 코드 작업을 할 경우 서로 충돌할 수 있는 문제를 해결할 수 있다.
CD는 지속적인 서비스 제공 및 지속적인 배포를 의미하며 이 두 용어는 상호 교환적으로 사용된다. 두 가지 의미 모두 파이프라인의 추가 단계에 대한 자동화를 뜻하지만 때로는 얼마나 많은 자동화가 이루어지고 있는지 설명하기 위해 별도로 사용되기도 한다.
CI/CD 종류
- Jenkins
- TravisCI
- Github Actions
- etc
원래 우리 프로젝트에 Jenkins를 사용하고자 계획하였지만, 막상 강의를 보며 공부하고 실습을 해보니 복잡하고 어려웠다 ㅠㅠ
코드 개발에 더 집중해야 되는 현재 상황에 맞춰서 Jenkins보다 학습 장벽이 낮은 Github Actions를 적용하게 되었다.
Github Actions 구성 요소
-
- Workflow
- Github Repository에 들어가는 작업단위
- 어떤 이벤트에 따라 Workflow를 실행시킬지 on: 키워드를 통해 설정할 수 있다.
- Job
- 병렬적으로 수행되거나 순차적으로 수행되어야 하는 작업 단위
- Jobs를 실행하기 위해서는 작업 환경을 정의해야하는데, 이를 Runner라고 지칭한다.
- 작업 환경을 정의하는 키워드는 runs-on을 통해 설정 가능하다.
- Step
- Job이 포함되며 Shell Script나 Action이 실행된다.
- Shell Script는 terminal에서 수행되는 명령어의 집합 등
- Action은 Github Actions에서 미리 정의한 script
- Workflow
(Workflow > Job > Step)
Github Actions CI/CD 적용 순서
1. 프로젝트의 .github/workflows/제목.yml 생성
(저는 직접 생성하고 코드 작성하였습니다.)
2. 해당 yml파일에 코드 작성
3. Github에 key 설정
Settings > Secrets and variables > Actions > Repository secrets > New repository secret 입력
(AWS ec2 생성은 생략하였습니다.)
CI 코드 파헤치기
# Repository의 Actions 탭에 나타날 Workflow 이름으로 필수 옵션은 아니다.
name: Project CI/CD
# Workflow를 실행시키기 위한 Event 목록
# dev 브랜치에 대한 변경 사항(push, pull_request)을 감지하면 해당 브랜치에 CI Workflow를 실행한다.
on:
push:
branches: [ dev ]
pull_request:
branches: [ dev ]
# Workflow가 레포지토리의 콘텐츠에 대해서 읽기 권한만 가지도록 한다.
# 즉, Workflow가 레포지토리의 콘텐츠를 실수로 변경하거나 삭제하는 것을 방지할 수 있다.
permissions:
contents: read
# 해당 Workflow의 하나 이상의 Job 목록
jobs:
# Job 이름으로, build라는 이름으로 Job이 표시
build:
# Runner가 실행되는 환경을 정의하는 부분
runs-on: ubuntu-latest
# build Job 내의 step 목록
steps:
# uses 키워드를 통해 Action을 불러올 수 있다.
# 해당 레포지토리를 check-out하여 레포지토리에 접근할 수 있는 Actions을 불러온다. (@v3은 버전)
- uses: actions/checkout@v3
# 해당 서비스는 자바 스프링을 활용했기 때문에 jdk 17버전을 설정(설치)
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
# Action에 전달할 매개변수 지정
java-version: 17
distribution: 'temurin'
cache: gradle # 빌드 속도를 높이기 위해 gradle 캐시 설정 (매번 gradle 종속성을 계속 설치할 순 없으니)
# gradlew 실행 권한 얻기
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# gradle build (-x test는 테스트를 생략하고 빌드하겠다는 명렁어 옵션)
- name: Build with Gradle
# github에서 제공하는 gradle 빌드 Action
uses: gradle/gradle-build-action@v2.6.0
with:
arguments: clean build -x test
# 파일을 업로드하여 나중에 다운로드에 사용되도록 한다.
# Artifact를 통해 작업을 완료한 후 데이터를 보관하고 동일한 Workflow의 다른 작업에서 공유할 수 있다.
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: Project
# 업로드할 파일의 경로를 지정 (build/libs에 Project jar 파일을 업로드한다.)
# 빌드 단계에서 생성된 jar파일
path: build/libs/Project-0.0.1-SNAPSHOT.jar
CD 코드 파헤치기
deploy:
# needs 키워드는 현재 작업이 의존하는 이전 작업을 지정한다.
# 즉, build 작업이 성공적으로 완료된 후에만 이 작업이 실행된다.
needs: build
# Ubuntu 환경에서 실행 (Github Actions에서 제공하는 기본 실행 중 하나)
runs-on: ubuntu-latest
# Github 이벤트가 push일 때만 작업을 실행
if: github.event_name == 'push'
steps:
# CI 단계에서 업로드한 artifact를 다운로드 한다.
# CI 단계에서 artifact를 업로드했으면 그냥 그거 쓰면 되는거 아닌가?
# -> CI에서 생성된 artifact를 CD단계에서 동일한 artifact를 사용하여 배포의 일관성을 유지
# -> CI에서 생성된 결과물을 바로 사용하기때문에 시간 절약
# -> CI에서 검증한 코드를 CD에서 사용하므로 배포 안정성 높일 수 있음
- name: Download build artifact
uses: actions/download-artifact@v4
with:
# Project 이름으로 업로드된 artifact를 build/libs 경로에 다운로드
name: Project
path: build/libs/
- name: Create .ssh directory and add EC2 host key
# run은 명령을 실행하는데 사용한다.
# ssh 연결 시 해당 호스트에 연결할 때 호스트키를 확인하고, 연결을 안전하게 유지할 수 있다.
run: |
mkdir -p ~/.ssh
ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts
# ec2 접속을 위해 깃허브에서 설정한 ssh key (pem key)를 생성하고 권한을 부여
- name: Create private key file
run: |
echo "${{ secrets.EC2_SSH_KEY }}" > private_key.pem
chmod 600 private_key.pem
# scp 명령을 통해 ec2에 jar 파일을 전송한다.
- name: Upload JAR to EC2
run: |
scp -i private_key.pem build/libs/Project-0.0.1-SNAPSHOT.jar ${{ secrets.EC2_USERNAME }}@${{ secrets.EC2_HOST }}:/home/${{ secrets.EC2_USERNAME }}/Project.jar
# ec2에 이미 실행 중인 자바 프로세스를 강제종료한다. (-9는 강제종료, -15는 안전하게 종료)
# 프로젝트 내 application.yml에 들어갈 private 데이터에 대해서 .env의 환경 변수들을 읽어와 셀 환경에 설정한다.
# nohup java -jar 를 통해 백그라운드에서 java를 실행하도록 한다. 그리고 log 디렉토리에 app_log.out 파일로 로그를 저장한다.
- name: Deploy to EC2
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
pgrep java && pgrep java | xargs -r kill -9
export $(grep -v '^#' /home/${{ secrets.EC2_USERNAME }}/project/.env | xargs)
nohup java -jar /home/${{ secrets.EC2_USERNAME }}/Project.jar > /home/${{ secrets.EC2_USERNAME }}/log/app_log.out 2>&1 &
# ec2 접속을 위해 사용한 private key를 삭제해서 보안을 강화한다.
- name: Remove private key file
run: rm -f private_key.pem
(혹여나 잘못된 내용이 있거나 누락된 부분이 있다면 댓글 달아주시면 감사하겠습니다.)
참고
https://seongwon.dev/DevOps/20220713-CICD%EB%9E%80/
https://seosh817.tistory.com/104
https://ji5485.github.io/post/2021-06-06/build-ci-cd-pipeline-using-github-actions/
https://docs.github.com/ko/actions/using-workflows/storing-workflow-data-as-artifacts
'Devops' 카테고리의 다른 글
[Jenkins] jenkins와 local tomcat 연동 (0) | 2024.05.01 |
---|