티스토리 뷰

CI/CD 흐름은 아는가?
수동으로 배포했을 때는 로컬에서 아래와 같은 흐름을 가졌다.
- 로컬에서 빌드
- 내 운영 서버로 scp 명령어로 jar 이동
- jar 직접 기동
그런데 처음 한두번은 "뭐 이정도면 할만하지.." 라며 재밌게 수동 배포를 하였다.
하지만, 날이 갈 수록 되게 불편해지기 시작했다.
그래서 선택한 것은 GitHub Actions 이였다.
Google에 검색을 해보면 AWS Lamda, S3를 배포를 이용하는데,
나는 가난 버전인 Self hosted를 이용하기로 했다.
그럼 자동으로 배포했을 때 나는 아래와 같은 흐름을 가졌다.
- main(master) 브랜치에 Merge
- GitHub Actions에서 코드 체크 아웃
- GitHub Actions에서 JDK 세팅 하고 빌드
- 서버에 만들어 둔 배포 쉘 스크립트 실행
- 배포 쉘 스크립트에서 기존 기동되어 있는 jar 중지
- 배포 쉘 스크립트에서 jar 백업
- 배포 쉘 스크립트에서 코드 체크해서 가져와서 빌드 해둔 jar 기동
이렇게 해도 정상적인 배포가 되었기 때문에 나는 CI/CD를 구축한 사람이 되었다.
Docker를 이용한 CI/CD는?
지속적인 통합(CI) 부터 보자
도커를 이용한 배포를 할 때 CI의 주요 작업은 아래와 같았다.
- 코드를 통합하기 전에 오류 없는지 빌드 및 테스트 코드 실행
- Merge가 되면 도커 파일을 이용해 도커 이미지 생성
- 도커 이미지를 도커 허브에 올리기


GitHub Actions Ci
env:
ACTOR: jspp
DOCKER_IMAGE_NAME: ghcr.io/jspp/my-api-server
permissions:
contents: read
actions: read
jobs:
# ==========================================
# 1. CI 작업 (코드 빌드, 테스트, 도커 이미지 푸시)
# ==========================================
ci-build:
runs-on: ubuntu-latest
# CD 작업으로 넘겨줄 이미지 태그 선언
outputs:
image_tag: ${{ steps.vars.outputs.short_sha }}
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# Step 1) 멀티모듈 전체 빌드 및 테스트 진행
- name: Build and Test with Gradle
run: ./gradlew clean build
# -----------------------------------------------------------------
# 아래 단계들은 PR이 아닌, develop에 코드가 Push(Merge) 되었을 때만 실행
# -----------------------------------------------------------------
# Step 2) GitHub 커밋 해시(Short SHA) 추출 (버전 태그용)
- name: Get short Commit SHA
if: github.event_name == 'push'
id: vars
run: echo "short_sha=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT
# Step 3) GHCR 로그인 (예전 코드의 GHCR 설정 완벽 적용)
- name: Login to GitHub Container Registry
if: github.event_name == 'push'
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ env.ACTOR }}
password: ${{ secrets.GHCR_TOKEN }}
# Step 4) Docker 이미지 빌드 및 푸시
- name: Build and Push Docker Image
if: github.event_name == 'push'
uses: docker/build-push-action@v4
with:
context: ./my-api
file: ./my-api/Dockerfile
push: true # 이 옵션으로 docker push 명령어 없이 자동으로 푸시
tags: |
${{ env.DOCKER_IMAGE_NAME }}:${{ steps.vars.outputs.short_sha }}
지속적인 배포 (CD) 는?
CI는 main, staging, develop 브랜치에 PR을 날리고 Merge를 하고 Push을 할 때,
모두 동일하게 빌드 되는지 보고~
테스트 코드 잘 돌아가는지 보고~
도커 허브에 올리는 것으로 이해했다.
"배포" 라는 것은 새로운 코드(프로젝트)로 교체를 하는 작업인데,
사용자가 있을 경우에는 무중단 배포면 상관은 없겠지만,
만약 무중단 배포가 아닐 경우에는 이 순단시간을 최대한 줄여야한다.
즉, 배포하는 시간을 최대한 줄여야한다.
그래서 배포는 도커 이미지만 Pull 받아 기존 어플리케이션을 중단하고 곧 바로 기동만 하는 것으로 생각했다.
개발서버, 운영서버 어떻게 배포하면 좋을지 큰 흐름만 보여주려고한다.
물론! 정답은 없으며 나는 이렇게 설정 했다는 것이다.
배포 플로우
특징은 docker-compose.yml과 .env를 새로 만들어서 덮어 쓴다는 점이다.
초기에 프로젝트를 진행할 때는 수정되는 것이 많았다.
그래서 인제 항상 서버에 접속에서 .env 파일을 관리 하기보다는
GitHub Actions에 있는 Secret 에 값을 넣고 관리하는게 편했다.
그래서 배포 할 때마다
Tip) 지금은 관리하는 포인트가 GitHub Actions에서 env(환경변수) 를 계속 보내서 관리하고 있다.
운영 서버에서만 두고 관리하려면 이 동작을 굳이 할 필요는 없을 것 같다.

GitHub Action CD
# ==========================================
# 2. CD 작업 (NCP 개발 서버 배포 및 헬스 체크)
# ==========================================
cd-deploy:
needs: ci-build
if: github.event_name == 'push'
runs-on: [self-hosted, dev]
steps:
# 💡 체크아웃 단계 제거! (모든 파일을 동적으로 빚어내기 때문에 Git 코드를 다운받을 필요가 없습니다)
- name: Create Infrastructure Files (.env & docker-compose.yml)
run: |
IMAGE_TAG="${{ needs.ci-build.outputs.image_tag }}"
echo "🚀 배포될 이미지 태그: $IMAGE_TAG"
# 1. .env 파일 재생성
echo "IMAGE_TAG=$IMAGE_TAG" > .env
echo "DOCKER_IMAGE_NAME=${{ env.DOCKER_IMAGE_NAME }}" >> .env
# 2. 개발 환경용 애플리케이션 환경 변수 주입
echo "${{ secrets.DEV_APPLICATION_ENV }}" | base64 --decode >> .env
# 3. 💡 개발 서버 전용 docker-compose.yml 동적 생성!
echo "${{ secrets.DEV_DOCKER_COMPOSE }}" | base64 --decode > docker-compose.yml
- name: Login to GitHub Container Registry
run: echo "${{ secrets.GHCR_TOKEN }}" | docker login ghcr.io -u ${{ env.ACTOR }} --password-stdin
- name: Pull Latest Docker Image
run: docker-compose pull
- name: Service Replacement (Down -> Up)
run: |
docker-compose down
docker-compose up -d
- name: Cleanup System Resources
run: docker system prune -af --volumes
- name: Application Health Check
run: |
echo "🔄 애플리케이션 헬스 체크를 시작합니다..."
for i in {1..20}; do
RESPONSE=$(curl -s localhost:8080${{ secrets.HEALTH_CHECK_PATH }} || true)
if echo "$RESPONSE" | grep -q "true"; then
echo "✅ 애플리케이션 배포 및 실행 완료!"
exit 0
fi
echo "⏳ 서버 구동 중... 3초 후 재시도합니다. ($i/20)"
sleep 3
done
echo "❌ 애플리케이션 실행 실패 (타임아웃)"
exit 1

감사합니다.
- Total
- Today
- Yesterday
- Fetch
- 개발자
- 계단 오르기
- spring
- 시간 객체
- DBeaver
- Spring Security
- Front
- CI/CD
- java
- JavaScript
- 개발
- 트랜잭션
- 소셜로그인
- 디자인패턴
- 개발환경
- 멀티모듈
- 깃허브 액션
- Flutter
- 알고리즘
- 개발블로그
- JPA 페이징
- 그리디
- 네트워크
- BFS
- 코딩테스트
- aws
- 프로세스
- 카카오 로그인
- 데이터 베이스
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
