Jenkins를 6년 쓰다가 GitHub Actions로 갈아탄 이유는 간단하다. Jenkins 서버가 금요일 저녁마다 죽었기 때문이다. 매주 금요일, 퇴근하려는 순간 슬랙에 '빌드 서버 다운'이라는 알림이 뜨면 느꼈던 그 절망감. 결국 GitHub Actions로 전환하고 나서 금요일 저녁이 자유로워졌다.

GitHub Actions를 선택한 이유

Jenkins, GitLab CI, CircleCI 등 다양한 CI/CD 도구가 있지만, GitHub Actions를 선택한 이유가 있었습니다:

기본 CI 워크플로우 구성

PR이 생성되면 자동으로 빌드와 테스트를 실행하는 워크플로우입니다.

name: CI Pipeline

on:
  pull_request:
    branches: [ main, develop ]
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Cache Gradle packages
        uses: actions/cache@v4
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Grant execute permission
        run: chmod +x gradlew

      - name: Build with Gradle
        run: ./gradlew build

      - name: Run tests
        run: ./gradlew test

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: build/reports/tests/

Gradle 캐싱의 효과

캐싱을 적용하면 의존성 다운로드 시간을 크게 줄일 수 있습니다. 실제 프로젝트에서 측정한 결과, 첫 빌드는 약 3분이 소요되지만 캐시가 적용된 이후에는 약 45초로 단축되었습니다.

테스트 커버리지 리포트

JaCoCo를 활용하여 테스트 커버리지를 측정하고 PR에 자동으로 리포트를 작성하는 설정입니다.

  - name: Generate JaCoCo report
    run: ./gradlew jacocoTestReport

  - name: Add coverage to PR
    uses: madrapps/jacoco-report@v1.6
    with:
      paths: build/reports/jacoco/test/jacocoTestReport.xml
      token: ${{ secrets.GITHUB_TOKEN }}
      min-coverage-overall: 60
      min-coverage-changed-files: 80

변경된 파일의 최소 커버리지를 80%로 설정하면, 새로 작성하는 코드에 대한 테스트 작성을 강제할 수 있습니다.

환경별 배포 워크플로우

개발, 스테이징, 운영 환경으로 단계적으로 배포하는 워크플로우입니다. GitHub Environments를 활용하여 각 환경에 대한 승인 프로세스를 추가할 수 있습니다.

name: Deploy Pipeline

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version.outputs.tag }}
    steps:
      - uses: actions/checkout@v4

      - name: Get version
        id: version
        run: echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT

      - name: Setup JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Build
        run: ./gradlew bootJar

      - name: Build Docker image
        run: |
          docker build -t myapp:${{ steps.version.outputs.tag }} .
          docker save myapp:${{ steps.version.outputs.tag }} | gzip > image.tar.gz

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: docker-image
          path: image.tar.gz

  deploy-staging:
    needs: build
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Download artifact
        uses: actions/download-artifact@v4
        with:
          name: docker-image

      - name: Deploy to staging
        run: |
          echo "Deploying ${{ needs.build.outputs.version }} to staging"
          # scp, ssh 등을 통한 배포 로직

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Download artifact
        uses: actions/download-artifact@v4
        with:
          name: docker-image

      - name: Deploy to production
        run: |
          echo "Deploying ${{ needs.build.outputs.version }} to production"
          # 운영 배포 로직

시크릿 관리

데이터베이스 비밀번호, API 키 등 민감한 정보는 GitHub Secrets에 저장합니다.

# GitHub Secrets 사용 예시
env:
  DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
  JWT_SECRET: ${{ secrets.JWT_SECRET }}
  SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

주의사항으로, Secrets 값은 로그에 자동으로 마스킹되지만 의도치 않게 노출되지 않도록 주의해야 합니다. 특히 echo로 환경변수를 출력하는 디버깅 코드를 배포 전에 반드시 제거해야 합니다.

Slack 알림 연동

배포 성공이나 실패 시 Slack으로 알림을 보내면 팀 전체가 배포 상황을 즉시 인지할 수 있습니다.

  - name: Notify Slack
    if: always()
    uses: 8398a7/action-slack@v3
    with:
      status: ${{ job.status }}
      fields: repo,message,commit,author,action,eventName,ref
    env:
      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
실무 경험 공유: SVN에서 Git으로 전환하면서 CI/CD를 처음 도입한 경험이 있습니다. 이전에는 로컬에서 빌드하고 FTP로 서버에 올리는 수동 배포 방식이었는데, CI/CD 파이프라인을 구축한 후에는 배포 실수가 확연히 줄었습니다. 자동화의 효과를 직접 체감한 경험이었고, 이후 프로젝트에서도 CI/CD를 우선적으로 세팅하게 되었습니다.

실무 팁

워크플로우 실행 시간 최적화

보안 점검 자동화

OWASP Dependency-Check를 CI에 포함하여 취약한 라이브러리를 자동으로 탐지합니다.

  - name: OWASP Dependency Check
    run: ./gradlew dependencyCheckAnalyze

  - name: Upload OWASP report
    uses: actions/upload-artifact@v4
    with:
      name: dependency-check-report
      path: build/reports/dependency-check-report.html

CI/CD 구축 후 팀에 생긴 변화

GitHub Actions를 활용한 CI/CD 파이프라인은 코드 품질 관리부터 자동 배포까지 개발 프로세스 전반을 개선합니다. 처음에는 간단한 빌드-테스트 워크플로우부터 시작하여 점진적으로 기능을 추가하는 것을 추천합니다.

Jaeseong
Jaeseong

10년차 풀스택 개발자. Spring Boot, Flutter, AI 등 실무 경험을 기록합니다.

GitHub →

💬 댓글