GitHub Actions CI/CD 파이프라인 설계

GitHub Actions CI/CD란 무엇인가

GitHub Actions는 GitHub 저장소에 내장된 CI/CD 플랫폼입니다. 코드 푸시, PR 생성, 태그 발행 등의 이벤트를 트리거로 빌드, 테스트, 배포 파이프라인을 자동 실행합니다. 별도 CI 서버 없이 YAML 파일 하나로 전체 파이프라인을 정의할 수 있다는 점이 Jenkins, GitLab CI 대비 가장 큰 장점입니다.

이 글에서는 워크플로우 구조부터 실전 파이프라인 설계, 캐시·시크릿·매트릭스 전략, 그리고 비용을 줄이는 최적화 기법까지 실무에서 바로 적용할 수 있는 내용을 다룹니다.

워크플로우 기본 구조

GitHub Actions 워크플로우는 .github/workflows/ 디렉토리에 YAML 파일로 정의합니다. 핵심 구성 요소는 다음과 같습니다.

구성 요소 역할 예시
Workflow 자동화 프로세스 전체 ci.yml
Event (on) 워크플로우 실행 트리거 push, pull_request, schedule
Job 독립 실행 단위 (Runner 위에서) build, test, deploy
Step Job 내 순차 실행 단위 checkout, install, test
Action 재사용 가능한 단위 작업 actions/checkout@v4

최소 워크플로우 예시

name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm test

실전 파이프라인 설계: Node.js 프로젝트

프로덕션 수준의 파이프라인은 lint, test, build, deploy를 분리하고 Job 간 의존성을 설정합니다.

name: Production Pipeline
on:
  push:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm run lint

  test:
    runs-on: ubuntu-latest
    needs: lint
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: test
        ports:
          - 5432:5432
        options: --health-cmd pg_isready --health-interval 10s
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm test
        env:
          DATABASE_URL: postgresql://postgres:test@localhost:5432/test

  build:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  deploy:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main'
    environment: production
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dist
      - run: echo "Deploy to production"

Docker 이미지 빌드 + 레지스트리 푸시

컨테이너 기반 배포를 사용한다면 Docker 이미지 빌드와 레지스트리 푸시를 파이프라인에 포함해야 합니다. Docker Multi-Stage Build 최적화와 함께 적용하면 이미지 크기와 빌드 시간을 동시에 줄일 수 있습니다.

  docker:
    runs-on: ubuntu-latest
    needs: test
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

매트릭스 전략: 다중 환경 테스트

여러 Node.js 버전, OS, 데이터베이스 조합을 한 번에 테스트하려면 매트릭스 전략을 사용합니다.

  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
        node-version: [18, 20, 22]
      fail-fast: false
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

fail-fast: false로 설정하면 하나의 조합이 실패해도 나머지 조합의 테스트가 계속 실행됩니다. 어떤 환경에서 문제가 발생하는지 한 번에 파악할 수 있습니다.

캐시 전략: 빌드 시간 단축

의존성 설치는 파이프라인에서 가장 시간이 오래 걸리는 단계입니다. 캐시를 적극 활용해야 합니다.

방법 설정 효과
setup-node cache cache: npm npm ci 시간 50% 단축
actions/cache 커스텀 키 + 경로 지정 유연한 캐시 제어
Docker layer cache cache-from: type=gha Docker 빌드 70% 단축
Turborepo remote cache 모노레포 빌드 캐시 변경된 패키지만 빌드

커스텀 캐시 예시

      - uses: actions/cache@v4
        with:
          path: ~/.npm
          key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-npm-

시크릿 관리 모범 사례

API 키, 데이터베이스 비밀번호 등 민감 정보는 반드시 GitHub Secrets에 저장하고, 워크플로우에서 ${{ secrets.SECRET_NAME }}으로 참조합니다.

      - name: Deploy
        run: |
          aws s3 sync dist/ s3://${{ secrets.S3_BUCKET }}
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

시크릿 관리 체크리스트

  1. Repository Secrets: 저장소 단위 시크릿
  2. Environment Secrets: production/staging 환경별 분리
  3. Organization Secrets: 여러 저장소에서 공유
  4. GITHUB_TOKEN: 자동 생성, 최소 권한 원칙 적용 (permissions 블록)

Environment와 수동 승인

프로덕션 배포 전 수동 승인을 강제하려면 Environment 보호 규칙을 사용합니다.

  deploy:
    runs-on: ubuntu-latest
    needs: build
    environment:
      name: production
      url: https://myapp.com
    steps:
      - run: echo "Deploying to production"

GitHub 저장소 Settings → Environments → production에서 Required reviewers를 설정하면, 이 Job은 지정된 팀원이 승인해야 실행됩니다.

Reusable Workflow: 중복 제거

여러 저장소에서 동일한 파이프라인을 사용한다면 Reusable Workflow로 공통 로직을 추출합니다.

# .github/workflows/reusable-test.yml
on:
  workflow_call:
    inputs:
      node-version:
        required: true
        type: string

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
      - run: npm ci
      - run: npm test
# 호출하는 워크플로우
jobs:
  call-test:
    uses: my-org/.github/.github/workflows/reusable-test.yml@main
    with:
      node-version: '20'

비용 최적화 전략

GitHub Actions는 public 저장소는 무료이지만, private 저장소는 분 단위로 과금됩니다. 비용을 줄이는 핵심 전략은 다음과 같습니다.

전략 설명 절감 효과
경로 필터링 paths:로 변경된 파일이 있을 때만 실행 불필요한 실행 제거
동시성 제한 concurrency:로 중복 실행 취소 연속 푸시 시 이전 빌드 취소
timeout-minutes Job 타임아웃 설정 무한 실행 방지
Self-hosted Runner 자체 서버에서 실행 과금 시간 0분
on:
  push:
    branches: [main]
    paths:
      - 'src/**'
      - 'package.json'

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

흔한 실수와 해결법

실수 1: 캐시 키가 너무 넓거나 좁음

캐시 키에 lock 파일 해시를 포함하지 않으면 오래된 의존성이 사용됩니다. 반대로 너무 구체적이면 캐시 히트율이 낮아집니다. hashFiles('**/package-lock.json')restore-keys를 함께 사용하세요.

실수 2: GITHUB_TOKEN 권한 과다

기본적으로 GITHUB_TOKEN은 넓은 권한을 가집니다. permissions 블록으로 필요한 권한만 명시하세요.

permissions:
  contents: read
  packages: write

실수 3: 시크릿을 로그에 노출

echo ${{ secrets.API_KEY }}는 자동 마스킹되지만, Base64 인코딩 등 변환 후 출력하면 노출됩니다. 시크릿 값을 절대 가공해서 출력하지 마세요.

실수 4: needs 체인 없이 배포 Job 실행

테스트 Job이 실패해도 배포 Job이 실행될 수 있습니다. 반드시 needs:로 의존성을 명시하세요.

운영 체크리스트

  1. 모든 워크플로우에 timeout-minutes 설정 (권장: 15~30분)
  2. concurrency 그룹으로 중복 실행 방지
  3. 프로덕션 배포에 Environment 보호 규칙 적용
  4. permissions 블록으로 최소 권한 원칙 적용
  5. 의존성 캐시 활용 여부 확인 (npm, pip, gradle)
  6. Self-hosted Runner 도입 시 보안 격리 검토
  7. 워크플로우 실행 이력을 주 1회 점검하여 불필요한 실행 제거

마무리

GitHub Actions는 설정 진입 장벽이 낮지만, 프로덕션 수준의 파이프라인을 만들려면 캐시, 시크릿, 환경 분리, 비용 최적화까지 고려해야 합니다. 이 글의 패턴을 기반으로 프로젝트에 맞게 커스터마이즈하면, 안정적이고 빠른 CI/CD 파이프라인을 구축할 수 있습니다.

Spring Boot 프로젝트의 환경별 설정 분리가 필요하다면 Spring Boot Profiles 심화 가이드를 함께 읽어보세요.

지금 프로젝트의 CI/CD 파이프라인에 캐시와 concurrency 설정을 추가해 보세요. 빌드 시간 변화를 댓글로 공유해 주시면, 추가 최적화 팁을 드리겠습니다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux