- 처음 해본 CI/CD
세명이서 머리 맞대고 하루 내내 씨름했다 그래도 성공(너무 뿌-듯)
이번 프로젝트에서 CI/CD가 필요한 이유
- 여러명이 여러 변경사항들을 빌드하고 테스트하며
repository에 올려 통합하는 작업을 반복하게 될텐데
이를 수동으로 계속 한다면 이는 단순한 반복작업이다
시간적, 비용적 측면을 고려하여 개발에만 집중할 수 있도록, 반복되는 과정의 자동화 필요성을 느꼈다
가볍게 용어 정리
- 컴파일 : 소스코드(사람) → 기계어
= 영어 번역 - 빌드 : 소스코드 파일 → 소프트웨어 산출물(실행 할 수 있음)
= 책 - 배포 : 빌드의 결과물 → 사용자 접근가능한 환경에 배치
= 서점에 배치
CI(Continuous Integration)
지속적 통합
- 여러명의 개발자가 하나의 코드를 수정해도 통합하면서 관리 가능 = 개발진행 + 품질관리 같이되는
- 분기 소스코드 병합
- CI tool에 코드병합 요청 → CI tool이 build, test → 결과에 따라 코드 병합 or 피드백
Github Action vs Jenkins
Github Actio | Jenkins | |
---|---|---|
서버 설치 | 필요 | 불필요(클라우드에서 동작) |
환경 호환성 | 도커 이미지에서 실행해야 함 | 모든 환경과 호환 |
설정(어려운정도) | 학습 비용 필요(호스팅부터 해야함) | 쉬운편(workflow만 설정하면 됨) |
동기/비동기 | 동기적(제품 배포하는데 더 많은 시간 소요) | 비동기 CI/CD 가능 |
캐싱 | 플러그인 제공 | 캐싱 매커니즘 직접 작성해야함 |
비용 | 무료 | public 무료, private 사용량에 따라 과금 |
관련 문서 | 많은 편 | 적은 편 |
Jenkins가 후보가 된 이유 & 제외한 이유
- 후보가 된 이유
- 유명하고 오랜시간 사용되어 왔다. 그렇기 때문에 관련 문서가 많아 도움을 받기가 쉽다.
- 제외한 이유
- 프로젝트의 규모에 비해 설정하는데 드는 시간적 리소스가 크다.
- 별도의 서버 설치가 필요하다.
- 그리고 설정에 학습비용이 많이 들어갈 것으로 보인다.
- 이번 프로젝트를 위해 학습해야 할 양이 이미 많아, 서버를 띄우고 설정 공부를 하는데에 투자하기 어려울 것이라 판단했다.
- 프로젝트의 규모에 비해 설정하는데 드는 시간적 리소스가 크다.
Github Actions를 선택한 이유
- 선택한 이유
- 첫번째 이유는 클라우드에서 동작하기 때문에 별도의 서버 설치가 불필요해 편하고 시간을 아낄 수 있다.
- 두번째 이유는 Github에서 직접 제공하는 CI/CD 도구이기 때문에 보유한 repository에서 바로 진행이 가능하며 Github과 사용하기 좋다. 우리는 형상관리 툴로 Github을 사용할 것이기 때문에 큰 장점이라고 생각했다.
- 세번째 이유는 Jenkins에 비해 설정이 쉽다. workflow만 설정하면 CI 기능을 쉽게 구현할 수 있다. 이 때, 많은 언어와 프레임 워크를 지원하기도 하고, 무엇보다 YAML로도 작성할 수 있다.
- 네번째 이유는 Jenkins와 비교해 상대적으로 문서는 적지만 도움을 받을 수 있는 문서의 양으로는 충분하다고 판단했다. 또한, GitHub marketplace에 출시되는 GitHub Actions의 수를 보며 빠르게 성장하고 있음을 느꼈다.
CD
- Continuous Delivery 지속적 제공
- staging 환경에 release 자동 + (QA) + Deploy 수동
- Continuous Deployment 지속적 배포
- 둘 다 자동
=> 마지막 production으로 release하는 deploy 작업의 자동화 여부
AWS CodeDeploy를 선택한 이유
- 선택한 이유
- AWS에서 제공하는 서비스이기 때문에 우리가 사용할 EC2와 연동이 쉽다. 간단한 명령으로 코드를 옮길 수 있어 효율적이라고 판단했다.
CI/CD 과정
- 개발자가 GitHub repository로 source code push
- GitHub Actions에서 build와 test(CI) 후 압축해서 s3로 업로드
- 업로드한 압축파일을 넘겨주면서 CodeDeploy에 배포 요청(CD)
- EC2에서 SpringBoot Server 실행
직접 해보기
CI test
일단, CI test를 먼저 진행했다
- CI test 시의 최종 ci-cd.yml
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Generate environment file
run: |
echo "$" >> db.env
echo "$" >> spring.env
- name: Run docker-compose
uses: isbang/compose-action@v1.4.1
with:
compose-file: "./docker-compose.yml"
- name: Get execution permission to gradlew
run: chmod +x ./gradlew
- name: Build with Gradle
run: ./gradlew clean build
변수들은 github secrets로 보관했다
이슈1 :: Github Action Workflow에서 MySQL이 없는 문제
.github/workflows/ci-cd.yml
에서 ubuntu-latest 환경에서 빌드- 빌드 과정에서 본 프로젝트의 데이터베이스 의존성이 MySQL 밖에 없기 때문에 MySQL 연결 시도
해결 :: docker-compose를 통한 MySQL 이미지 실행
- docker-compose.yml
version : "3"
services:
db:
container_name: wumo-db
image: mysql
ports:
- "3306:3306"
volumes:
- ./mysqldata:/var/lib/mysql
restart: always
- ci-cd.yml에 step 추가
- name: Run docker-compose
uses: isbang/compose-action@v1.4.1
with:
compose-file: "./docker-compose.yml"
이슈 2 :: MySQL 및 Spring Application Build Test 시 읽을 환경변수 설정 누락
- MySQL 이미지를 다운로드 받고 실행하는 과정에서 데이터베이스 이름이나 비밀번호 초기 설정 누락
해결 :: 환경변수를 위한 설정 추가 및 환경변수 파일 추출
- Settings - Secrets and variables - Secrets - New에서 DB_ENV, SPRING_ENV 파일에 변수 담기
- name: Generate environment file
run: |
echo "$" >> db.env
echo "$" >> spring.env
- docker-compose.yml
version : "3"
services:
db:
container_name: wumo-db
image: mysql
#추가
env_file:
- db.env
ports:
- "3306:3306"
volumes:
- ./mysqldata:/var/lib/mysql
restart: always
- application.yml
- local과 github action 서버에서 사용할 yml 분리 필요
spring:
config:
import: optional:file:spring.env[.properties]
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: ${DATASOURCE_URL}
username: ${DATASOURCE_USERNAME}
password: ${DATASOURCE_PASSWORD}
CD test
1. AWS EC2 인스턴스 생성
AWS EC2 인스턴스 ssh 접속하기
2. Amazon RDS 인스턴스 생성
- private subnet
- public access 허용하기 <- 이 부분때문에 계속 rds 연결이 안되었다
3. Amazon S3
4. AWS CodeDeploy
- CodeDeploy에게 역할 부여(AWS CodeDeployRole)
- AWS CodeDeploy의 애플리케이션 생성
- 배포그룹 생성(live, qa, dev)
- 환경 구성 : EC2
5. EC2에 CodeDeploy, S3에 접근할 수 있는 역할 설정
- AmazonS3FullAccess
- AWSCodeDeployFullAccess
6. CodeDeploy 관련 코드 추가(appspec.yml)
- appspec.yml : CodeDeploy의 설정 파일
- 배포 시점에 특정한 스크립트 실행시킬 수 있음
- deploy.sh
- 배포 시점에 특정한 스크립트 실행시킬 수 있음
version: 0.0
os: linux
files:
- source: /
destination: /home/ec2-user/app # 인스턴스에서 파일이 저장될 위치
overwrite: yes
permissions:
- object: /
pattern: "**"
owner: ec2-user
group: ec2-user
mode: 755
hooks:
AfterInstall:
- location: deploy.sh
timeout: 200
runas: ec2-user
- deploy.sh : 인스턴스 배포하는 쉘 스크립트
- (현재 인스턴스에서 애플리케이션이 구동중인지 확인하고, 구동 중이면 종료 후) 애플리케이션 실행
#!/usr/bin/env bash
REPOSITORY=/home/ec2-user/app
cd $REPOSITORY
APP_NAME=wumo # 애플리케이션이 구동 중인지 확인하기 위해 사용
JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep '.jar' | tail -n 1)
JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME
CURRENT_PID=$(pgrep -f $APP_NAME)
echo "> Kill Previous Process" >> /home/ec2-user/app/deploy.log
if [ -z "$CURRENT_PID" ]
then
echo "> No Process to Kill" >> /home/ec2-user/app/deploy.log
else
echo "> Kill $CURRENT_PID" >> /home/ec2-user/app/deploy.log
kill -15 "$CURRENT_PID" >> /home/ec2-user/app/deploy.log 2>&1
sleep 5
fi
echo "> $JAR_PATH Deploy"
nohup java -jar $JAR_PATH >> /home/ec2-user/nohup.log 2>&1 &
7. EC2 인스턴스에 Java, CodeDeploy agent 설치
$ sudo yum list | grep jdk
$ sudo yum install -y java-17
$ sudo yum update
$ sudo yum install ruby
$ sudo yum install wget
$ cd /home/ec2-user
$ wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
$ chmod +x ./install
$ sudo ./install auto
$ sudo service codedeploy-agent status
# 실행이 되고 있지 않다면
$ sudo service codedeploy-agent start
8. Github에 AWS access key와 secret key 변수화
- Github Repository - Setting - Secrets
9. Giuthub Action workflow에 CD 추가
- ci-cd.yml
$GITHUB_SHA
은 자체적으로 제공해주는 변수 -> 실제 진행 시에는 고정적으로 할 예정
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Generate environment file
run: |
echo "$" >> db.env
echo "$" >> spring.env
- name: Run docker-compose
uses: isbang/compose-action@v1.4.1
with:
compose-file: "./docker-compose.yml"
- name: Get execution permission to gradlew
run: chmod +x ./gradlew
- name: Build with Gradle
run: ./gradlew clean build
- name: Make zip file # 파일 압축
run: sudo zip -qq -r ./$GITHUB_SHA.zip .
- name: Setting for AWS # Github Sercrets에 설정한 값들을 가져와 AWS에 인증
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: $
aws-secret-access-key: $
aws-region: ap-northeast-2
- name: Upload to S3 # S3에 해당 압축 파일을 업로드
run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://wumo-bucket/$GITHUB_SHA.zip
- name: Code Deploy # CodeDeploy 시작
run: aws deploy create-deployment --application-name wumo-deploy
--deployment-config-name CodeDeployDefault.OneAtATime # 배포 방법 설정(한 번에 배포하는 방식)
--deployment-group-name wumo-deploy-group
--s3-location bucket=wumo-bucket,bundleType=zip,key=$GITHUB_SHA.zip
결과
develop branch는 CI만 되도록 설정했고
main branch는 CICD까지 되도록 설정했다
ec2에서는 환경변수를 서버에 직접 들어가서 설정해주었다
이것도 github에는 안올라가게 하면서 파일로 관리하는 방법이 있을듯한데 알아봐야겠다
Reference
카카오엔터프라이즈가 GitHub Actions를 사용하는 이유
무신사 스토어 watcher CI/CD 도입기
Github Actions냐 Jenkins냐! 올바른 선택을 해봅시다