CI란?
지속적 통합(Continuous Integration)은 자동화된 빌드 및 테스트가 수행된 후, 개발자가 코드 변경 사항을 중앙 리포지토리에 정기적으로 병합하는 DevOps 소프트웨어 개발 방식입니다. 지속적 통합은 소프트웨어 릴리스 프로세스 중 빌드 또는 통합 단계를 주로 가리키며, 자동화 구성 요소(예: CI 또는 빌드 서비스)와 문화적 구성 요소(예: 빈번하게 통합하도록 학습) 모두를 포함합니다. 지속적 통합의 핵심 목표는 버그를 신속하게 찾아 해결하고, 소프트웨어 품질을 개선하고, 새로운 소프트웨어 업데이트를 검증 및 릴리스하는 데 걸리는 시간을 단축하는 것입니다.
대표적인 서비스 AWS CodeBuild
만약 CI없이 그냥 배포했다가 빌드 에러가 발생한다면...?
테스트 한 번도 안해보고 그냥 배포한다면...?
CD란?
지속적 전달(Continuous Delivery)은 프로덕션에 릴리스하기 위한 코드 변경이 자동으로 준비되는 소프트웨어 개발 방식입니다. 현대 애플리케이션 개발의 기반인 지속적 전달은 빌드 단계 이후의 모든 코드 변경을 테스트 환경 및/또는 프로덕션 환경에 배포함으로써 지속적 통합을 확장합니다. 적절하게 구현할 경우, 개발자는 언제나 즉시 배포할 수 있고 표준화된 테스트 프로세스를 통과한 빌드 아티팩트를 보유할 수 있습니다.
대표적인 서비스
- AWS CodePipeline
- AWS CodeDeploy
CI / CD를 구축함에 따른 이점
- 소프트웨어 릴리즈 자동화
- 개발자 생산성 증대
- 버그를 보다 빠르게 발견 및 해결
- 보다 빠른 업데이트 제공
CI/CD를 도입함으로써, 개발자는 코드를 더 자주, 더 신속하게 통합할 수 있게 되어 버그를 빨리 발견하고 수정할 수 있습니다
현재 동아리 사이드 프로젝트를 진행 중입니다.
JAVA / Spring boot로 개발 진행 중이며 CI/CD를 어떻게 적용시킬 수 있는지 간략하게 설명하도록 하겠습니다.
우선 pipeline은 Github actions를 사용 하였으며 인프라는 EC2, code deploy를 사용하였습니다.
CI (지속적 통합) 과정
- GitHub Actions 설정
- Triggers: push와 pull_request 이벤트에 반응하여, dev와 main 브랜치에 대한 코드 변경사항이 있을 때마다 CI 프로세스가 시작됩니다.
- 환경 설정: JDK 17 설치, MySQL 컨테이너 실행, Gradle 빌드 권한 설정 등을 포함합니다.
- 빌드 실행: ./gradlew build -Plocal 명령을 사용하여 Gradle을 통해 프로젝트를 빌드합니다. 이 과정에서 자동화된 테스트가 실행되어 코드의 안정성을 검증합니다.
- 주요 포인트
- Cache 사용: Gradle 패키지 캐시를 사용하여 빌드 속도와 효율성을 향상시킵니다.
name: CI
on:
push:
branches: [ "dev", "main" ]
pull_request:
types: [ opened, synchronize, reopened ]
jobs:
build:
name: Test Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 17
distribution: 'zulu'
- name: Setup MySQL
run: docker-compose up -d
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build -Plocal
- name: Cache Gradle packages
uses: actions/cache@v1
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-gradle
.github/workflows/ci.yml
CD (지속적 배포) 과정
- GitHub Actions 설정
- Triggers: main 브랜치에 대한 push 이벤트에 반응하여 CD 프로세스가 시작됩니다.
- AWS Credentials 설정: AWS 서비스 (S3, CodeDeploy)에 액세스하기 위해 필요한 인증 정보를 설정합니다.
- 빌드 및 배포: 빌드를 진행하고, 결과물을 zip 파일로 압축한 뒤, AWS S3 버킷으로 업로드합니다. 그 후, AWS CodeDeploy를 이용해 애플리케이션을 EC2 인스턴스에 배포합니다.
- Github secrets: Github secrets를 사용하여 중요한 정보가 절대 노출되지 않도록 주의합니다.
- appspec.yml
- 파일 및 권한 설정: 배포할 파일과 대상 경로, 필요한 권한을 정의합니다.
- 훅 설정: 배포 단계에서 실행할 스크립트(deploy.sh)를 지정합니다.
- deploy.sh 스크립트
- 애플리케이션 실행: nohup 명령어를 사용하여 백그라운드에서 Java 애플리케이션을 실행합니다. 이는 터미널 세션이 종료되어도 프로세스가 계속 실행되게 합니다.
- 현재 실행 중인 애플리케이션 종료: 이전에 실행된 애플리케이션 인스턴스가 있으면 종료시키고, 새로운 빌드로 교체합니다.
위 과정을 준비하기 위해서는 먼저 깃헙 액션에서 S3로 올리기 위해서는 먼저 S3 버킷이 필요합니다.
따라서 적절한 S3 버킷을 생성해줍니다.
또한 Codedeploy에서 애플리케이션에 대한 설정을 해주어야합니다.
일반적으로 애플리케이션 하위에는 여러 개의 배포 그룹을 설정해줄 수 있는데 여기서 배포 그룹은 일반적으로 prod, dev 각각의 환경에 따라 분리하면 될 것 같습니다!
각 배포그룹마다 서비스 역할 ARN을 부여해주어야하는데 이 때 role을 만들어 주어서 아래와 같이 AmazonEC2RoleforAWSCodeDeploy 권한을 부여해주면 됩니다.
그 다음 CodeDeploy가 정상적으로 배포가 되기 위해서는 인스턴스에 CodeDeploy agent가 설치되어있어야합니다.
저희는 ASG를 미리 생성해두어 이 때 시작 템플릿의 user-data에 agenet 설치 스크립트를 적어둡시다!
#!/bin/bash
sudo yum update -y
sudo yum install -y ruby
sudo yum install -y wget
cd /home/ec2-user
wget https://aws-codedeploy-{본인의 Region}.s3.{본인의 Region}.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto
user data에 위와 같이 입력을 해둔다면 매번 인스턴스가 새로 띄워질 때마다 agent를 설치할 필요 없이 자동으로 해당 스크립트를 실행해줍니다!
name: CD
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- 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: ${{ secrets.AWS_REGION }}
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'zulu'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build -x test -Pprofile=prod
- name: Make zip file
run: zip -r ./$GITHUB_SHA.zip .
- name: Copy script
run: |
mkdir -p deploy
cp ./scripts/*.sh ./deploy
- name: Deploy to S3
run: aws s3 cp --region ${{ secrets.AWS_REGION }} $GITHUB_SHA.zip s3://${{ secrets.AWS_DEPLOY_S3_BUCKET }}/$GITHUB_SHA.zip
- name: Deploy to AWS CodeDeploy
run: |
aws deploy create-deployment \
--application-name ${{ secrets.AWS_CODEDEPLOY_APPLICATION }} \
--deployment-group-name ${{ secrets.AWS_CODEDEPLOY_DEPLOYMENT_GROUP }} \
--file-exists-behavior OVERWRITE \
--s3-location bucket=${{ secrets.AWS_DEPLOY_S3_BUCKET }},bundleType=zip,key=$GITHUB_SHA.zip \
--region ${{ secrets.AWS_REGION }}
.github/workflows/cd.yml
- 미리 구축해둔 code deploy의 group명과 application-name을 github secrets에 정의해둡니다!!
version: 0.0
os: linux
files:
- source: /
destination: /home/ec2-user/action/
overwrite: true
permissions:
- object: /home/ec2-user
owner: ec2-user
group: ec2-user
type:
- directory
- file
hooks:
ApplicationStart:
- location: scripts/deploy.sh
timeout: 300
runas: ec2-user
appspec.yml
#!/bin/bash
BUILD_JAR=$(ls /home/ec2-user/action/build/libs/*.jar)
JAR_NAME=$(basename $BUILD_JAR)
echo "> build 파일명: $JAR_NAME" >> /home/ec2-user/action/deploy.log
echo "> build 파일 복사" >> /home/ec2-user/action/deploy.log
DEPLOY_PATH=/home/ec2-user/action/
cp $BUILD_JAR $DEPLOY_PATH
echo "> 현재 실행중인 애플리케이션 pid 확인" >> /home/ec2-user/action/deploy.log
CURRENT_PID=$(pgrep -f $JAR_NAME)
if [ -z $CURRENT_PID ]
then
echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다." >> /home/ec2-user/action/deploy.log
else
echo "> kill -15 $CURRENT_PID"
kill -15 $CURRENT_PID
sleep 5
fi
DEPLOY_JAR=$DEPLOY_PATH$JAR_NAME
echo "> DEPLOY_JAR 배포" >> /home/ec2-user/action/deploy.log
nohup java -jar $DEPLOY_JAR >> /home/ec2-user/deploy.log 2>/home/ec2-user/action/deploy_err.log &
scripts/deploy.sh
nohup이란?
- nohup은 "No Hang Up"의 약자로, 터미널 세션 종료 시에도 명령어가 계속 실행되도록 하는 리눅스/유닉스 명령어입니다. 이를 사용하여 백그라운드에서 장시간 동안 실행되어야 하는 프로세스를 관리할 수 있습니다.
- nohup으로 실행된 프로세스는 터미널이 닫혀도 계속 실행됩니다.
실행 순서
CI 과정
- 코드 변경이 dev 또는 main 브랜치에 푸시되거나, 관련된 Pull Request가 열리면, GitHub Actions에 의해 CI 과정이 시작됩니다
- 이 과정에서는 코드 통합, 빌드, 자동화된 테스트가 수행됩니다.
CD 과정
- main 브랜치에 코드가 푸시되면, CD 과정이 시작됩니다.
- 빌드 결과물을 S3로 업로드하고, AWS CodeDeploy를 통해 EC2 인스턴스에 배포합니다.
- appspec.yml과 deploy.sh 스크립트를 통해 애플리케이션이 EC2에서 실행됩니다.
이후 main브랜치에 merge가 된다면 CodeDeploy를 통해 EC2에 정상적으로 배포가 되는 것을 확인할 수 있습니다!
맺음글
CI/CD 파이프라인을 구축하는 과정은 백엔드 개발 방식에서 필수적인 부분입니다!!!
GitHub Actions와 AWS를 통한 자동화된 CI/CD 파이프라인 구축은 코드의 통합부터 배포까지의 과정을 자동화하여 개발 효율성을 극대화하고, 배포 과정에서의 실수를 최소화할 수 있는 효과적인 방법입니다.
이를 통해 개발자는 반복적이고 실증이나는 부분에 리소스를 덜 투자하고 개발에만 집중하게 해주는 아주 크나큰 이점이 있으니 배포가 필요한 프로젝트의 경우 필수적으로 구축해둔다면 개발자 생산성이 상당히 향상될 것입니다 :)
참고
https://aws.amazon.com/ko/devops/continuous-integration/
https://aws.amazon.com/ko/devops/continuous-delivery/