TestForge Blog

GitHub Actions CI/CD 파이프라인 구축 — 빌드부터 배포까지

GitHub Actions로 테스트 → 빌드 → Docker 이미지 → Kubernetes 배포까지 완전한 CI/CD 파이프라인 구축 방법. 실전 워크플로우 예시 포함.

TestForge Team ·

CI/CD 파이프라인 전체 흐름

PR Open → 테스트 → 빌드
main merge → Docker Build → ECR Push → K8s Deploy

1. 기본 CI 워크플로우 (테스트)

# .github/workflows/ci.yml
name: CI

on:
  pull_request:
    branches: [main, develop]

jobs:
  test:
    runs-on: ubuntu-latest
    
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_DB: testdb
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
        ports: ["5432:5432"]
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
      
      redis:
        image: redis:7-alpine
        ports: ["6379:6379"]

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
          cache: gradle

      - name: Test
        run: ./gradlew test
        env:
          SPRING_DATASOURCE_URL: jdbc:postgresql://localhost:5432/testdb
          SPRING_DATASOURCE_USERNAME: test
          SPRING_DATASOURCE_PASSWORD: test
          SPRING_REDIS_HOST: localhost

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

2. Docker 빌드 + Registry Push

# .github/workflows/build.yml
name: Build & Push

on:
  push:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha,prefix=sha-
            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

3. Kubernetes 배포

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment: production

    steps:
      - uses: actions/checkout@v4

      - name: Configure kubectl (EKS)
        run: |
          aws eks update-kubeconfig \
            --region ap-northeast-2 \
            --name my-cluster
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Deploy
        run: |
          IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}"
          kubectl set image deployment/my-app \
            my-app=$IMAGE \
            --namespace=production
          kubectl rollout status deployment/my-app \
            --namespace=production \
            --timeout=5m

4. 환경별 배포 분기

on:
  push:
    branches:
      - main     → production 배포
      - develop  → staging 배포
      - 'release/**' → pre-production 배포

jobs:
  deploy:
    environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}

5. 보안 강화: OIDC로 AWS 인증 (Access Key 불필요)

permissions:
  id-token: write
  contents: read

- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456789:role/github-actions-role
    aws-region: ap-northeast-2

AWS IAM에서 GitHub OIDC Provider 신뢰 관계 설정 필요.

6. 슬랙 알림

- name: Notify Slack
  if: always()
  uses: slackapi/slack-github-action@v1
  with:
    payload: |
      {
        "text": "${{ job.status == 'success' && '✅' || '❌' }} Deploy ${{ job.status }}: ${{ github.repository }}@${{ github.sha }}",
        "blocks": [{
          "type": "section",
          "text": {
            "type": "mrkdwn",
            "text": "*${{ github.workflow }}* — <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>"
          }
        }]
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

비용 최적화 팁

# 캐시로 빌드 시간 단축
- uses: actions/cache@v4
  with:
    path: ~/.gradle/caches
    key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}

# 변경된 파일만 실행
on:
  push:
    paths:
      - 'src/**'
      - 'Dockerfile'
      - '.github/workflows/**'

# 동시 실행 제한 (같은 브랜치)
concurrency:
  group: deploy-${{ github.ref }}
  cancel-in-progress: true

전체 파이프라인 소요 시간 목표

단계목표
단위 테스트2분 이내
Docker 빌드 (캐시)1분 이내
배포 + 롤아웃3분 이내
전체5분 이내