티스토리 뷰

 

CI/CD 흐름은 아는가?

 

수동으로 배포했을 때는 로컬에서 아래와 같은 흐름을 가졌다.

 

  1. 로컬에서 빌드
  2. 내 운영 서버로 scp 명령어로 jar 이동
  3. jar 직접 기동

그런데  처음 한두번은 "뭐 이정도면 할만하지.." 라며 재밌게 수동 배포를 하였다.

 

하지만, 날이 갈 수록 되게 불편해지기 시작했다.

 

그래서 선택한 것은 GitHub Actions 이였다.

Google에 검색을 해보면 AWS Lamda, S3를 배포를 이용하는데,

나는 가난 버전인 Self hosted를 이용하기로 했다.

 

그럼 자동으로 배포했을 때 나는 아래와 같은 흐름을 가졌다.

 

  1. main(master) 브랜치에 Merge
  2. GitHub Actions에서 코드 체크 아웃
  3. GitHub Actions에서 JDK 세팅 하고 빌드
  4. 서버에 만들어 둔 배포 쉘 스크립트 실행
  5. 배포 쉘 스크립트에서 기존 기동되어 있는 jar 중지
  6. 배포 쉘 스크립트에서 jar 백업
  7. 배포 쉘 스크립트에서 코드 체크해서 가져와서 빌드 해둔 jar 기동

 

이렇게 해도 정상적인 배포가 되었기 때문에 나는 CI/CD를 구축한 사람이 되었다.

 

 

 

 

Docker를 이용한 CI/CD는?

 

지속적인 통합(CI) 부터 보자

도커를 이용한 배포를 할 때 CI의 주요 작업은 아래와 같았다.

 

  • 코드를 통합하기 전에 오류 없는지 빌드 및 테스트 코드 실행
  • Merge가 되면 도커 파일을 이용해 도커 이미지 생성
  • 도커 이미지를 도커 허브에 올리기

[그림1] CI

 

[그림2] AI로 생성한 4컷만화 CI

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(환경변수) 를 계속 보내서 관리하고 있다.

운영 서버에서만 두고 관리하려면 이 동작을 굳이 할 필요는 없을 것 같다.

 

[그림2] CD

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
링크
«   2026/04   »
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
글 보관함