CI-CD란?
지속적 통합(Continuous Integration), 지속적 배포(Continuous Deployment)를 의미하며, 빌드, 테스트, 배포 등의 작업을 자동화하여 소프트웨어 개발 사이클을 간소화하고 가속화 해줍니다.
Github Actions, Docker의 사용 이유
Giftipie에서는 `Github Actions, Docker`를 사용하여 CI-CD를 구현하였습니다. Github와의 통합성과 yml 작성을 통해 간편하게 구현할 수 있는 점을 이유로 Github Actions을 사용하였고, 어느 환경에서나 동일하게 실행할 수 있고 컨테이너를 통한 빠른 배포 등의 장점으로 Docker를 사용하였습니다.
Giftipie의 CI-CD flow
1. github actions yml에 미리 설정해둔 대로 main 혹은 develop에 push가 일어나면 CI-CD가 시작됩니다.
2. Github actions에서 필요한 설정들을 세팅대로 자동으로 마치고, Gradle을 사용하여 애플리케이션을 빌드합니다.
3. Docker Image를 빌드하고 Docker hub에 push 합니다.
4. SSH를 통해 EC2 인스턴스에 접속하고, Docker hub의 최신 Docker Image를 Pull 받습니다.
5. EC2 인스턴스에 작성해둔 배포 스크립트(’ /deploy.sh’ )를 실행하여 애플리케이션을 배포하고 사용하지 않는 Docker 이미지들을 정리합니다.
6. 작성해둔 배포스크립트 대로 블루 그린 배포를 진행합니다.
github-actions.yml
# github repository actions 페이지에 나타날 이름
name: CI/CD using github actions & docker
# event trigger
# main이나 develop 브랜치에 push가 되었을 때 실행
on:
push:
branches: [ "main", "develop" ]
permissions:
contents: read
jobs:
CI-CD:
runs-on: ubuntu-latest
steps:
# JDK setting - github actions에서 사용할 JDK 설정
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
# gradle caching - 빌드 시간 향상
- name: Gradle Caching
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
# yml 파일 생성 - application.yml
- name: make application.yml
if: |
contains(github.ref, 'main') ||
contains(github.ref, 'develop')
run: |
cd ./src/main/resources
touch ./application.yml
echo "${{ secrets.YML }}" > ./application.yml
shell: bash
# gradle build
- name: Build with Gradle
run: ./gradlew build -x test
# docker build & push
- name: Docker build & push to dev
if: |
contains(github.ref, 'main') ||
contains(github.ref, 'develop')
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t ${{ secrets.DOCKER_USERNAME }}/giftipie_dev .
docker push ${{ secrets.DOCKER_USERNAME }}/giftipie_dev
## pull to develop
- name: Pull to dev
uses: appleboy/ssh-action@master
id: pull-dev
if: |
contains(github.ref, 'main') ||
contains(github.ref, 'develop')
with:
host: ${{ secrets.HOST_DEV }} # EC2 퍼블릭 IPv4 DNS
username: ${{ secrets.USERNAME }} # ubuntu
port: 22
key: ${{ secrets.PRIVATE_KEY }}
envs: GITHUB_SHA
script: |
sudo docker ps
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/giftipie_dev
docker-pull-and-run:
runs-on: [self-hosted, dev]
if: ${{ needs.CI-CD.result == 'success' }}
needs: [ CI-CD ]
steps:
- name: Set up AWS CLI
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2
- name: Install AWS CLI
run: |
sudo apt-get update
sudo apt-get install -y awscli
- name : 배포 스크립트 실행
run: |
sh /deploy.sh
sudo docker image prune -f
무중단배포
docker-compose로 서로 다른 포트를 사용하는 두 개의 컨테이너를 관리하여 서버를 실행하였고, nginx의 리버스 프록시 기능을 이용하여 클라이언트 요청을 Blue 환경, Green 환경으로 라우팅 해주었습니다. 새로운 배포가 일어날 때 nginx가 안전하게 트래픽을 blue, green 간 전환할 수 있도록 해줍니다.
deploy.sh
#1
EXIST_BLUE=$(sudo docker-compose -p test-blue -f /docker-compose.blue.yml ps | grep Up)
if [ -z "$EXIST_BLUE" ]; then
echo "BLUE 컨테이너 실행"
sudo docker-compose -p test-blue -f /docker-compose.blue.yml up -d
BEFORE_COLOR="green"
AFTER_COLOR="blue"
BEFORE_PORT=8081
AFTER_PORT=8080
else
echo "GREEN 컨테이너 실행"
sudo docker-compose -p test-green -f /docker-compose.green.yml up -d
BEFORE_COLOR="blue"
AFTER_COLOR="green"
BEFORE_PORT=8080
AFTER_PORT=8081
fi
echo "${AFTER_COLOR} server up(port:${AFTER_PORT})"
# 2
for cnt in $(seq 1 10)
do
echo "서버 응답 확인중(${cnt}/10)";
UP=$(curl -s http://127.0.0.1:${AFTER_PORT}/health-nginx)
# 추가: AWS ALB 헬스 체크 확인
ALB_STATUS=$(aws elbv2 describe-target-health --target-group-arn arn:aws:elasticloadbalancing:ap-northeast-2:767397884822:targetgroup/giftipie-8080/bb624839c4cb5bac --targets Id=i-01ee4cebeafcd07a4 --query 'TargetHealthDescriptions[0].TargetHealth.State' --output text)
if [ "${UP}" != "up" ] || [ "${ALB_STATUS}" != "healthy" ]; then
sleep 10
continue
else
break
fi
done
if [ $cnt -eq 10 ]
then
echo "서버가 정상적으로 구동되지 않았습니다."
exit 1
fi
# 3
sudo sed -i "s/${BEFORE_PORT}/${AFTER_PORT}/" /etc/nginx/conf.d/service-url.inc
sudo nginx -t && sudo nginx -s reload
echo "Deploy Completed!!"
# 4
echo "$BEFORE_COLOR server down(port:${BEFORE_PORT})"
# Graceful Shutdown 효과 기대
sleep 30 # 필요시 추후 값 조정필요
sudo docker-compose -p test-${BEFORE_COLOR} -f /docker-compose.${BEFORE_COLOR}.yml down
회고
해당 프로젝트의 예산상황과 유저테스트 시의 서버 부하 예상 정도를 고려하여 한대의 EC2 인스턴스로 서버를 구현하게 되어, 위와 같은 CI-CD를 구현하게 되었는데, Auto Scaling 기능을 써보지 못하여 아쉬운 점으로 남았다.
자동 병합, 배포라는 CI-CD를 구현했지만, 서버의 확장에 대하여는 여전히 수동으로 설정해줘야하는 숙제가 남아있는 것이다.
물론, 소규모였던 프로젝트 목적에는 유저테스트 시에 필요한 상황이 생겼을때만 스케일업을 해주는 것이 경제적이라 판단되었지만..
조만간 기회를 만들어 Auto Scaling Group을 사용하여 서버 확장성을 고려한 서버를 구축하고 그에 맞는 CI/CD도 구축해보고 싶다.
찾아보니 AWS CodeDeploy, S3를 사용하는 방법을 많이 쓰는 것 같던데, 너무 재밌을 것 같다.
'Experience > 항해99 18기' 카테고리의 다른 글
항해99 18기 85일차 회고 - 최종 발표를 1주일 앞두고 (2) | 2024.03.04 |
---|---|
WIL 8주차 0204 (1) | 2024.02.04 |
WIL 7주차 0128 (1) | 2024.01.28 |
[챌린지 프로젝트 사전주차] CI-CD : Github Actions, Docker, Nginx, EC2를 사용하여 블루그린 무중단배포 도전기 (1) | 2024.01.27 |
[챌린지 프로젝트 사전주차] CI-CD : Github Actions, Docker, AWS EC2로 자동배포를 해보자 (1) | 2024.01.26 |