GitHub Actions CI/CD 심화

GitHub Actions란?

GitHub Actions는 GitHub 저장소에 내장된 CI/CD 플랫폼입니다. Push, PR, 스케줄 등의 이벤트에 반응해 빌드, 테스트, 배포를 자동화합니다. 이 글에서는 기본 워크플로우를 넘어 매트릭스 빌드, 재사용 가능한 워크플로우, 캐싱 전략, 시크릿 관리 등 실전 패턴을 심화합니다.

워크플로우 기본 구조

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

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

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true  # 같은 브랜치의 이전 실행 취소

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - run: pnpm install --frozen-lockfile
      - run: pnpm lint

  test:
    needs: lint  # lint 통과 후 실행
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - run: pnpm install --frozen-lockfile
      - run: pnpm test -- --coverage
      - uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: coverage/

concurrency는 같은 브랜치에서 여러 커밋을 빠르게 Push할 때 이전 실행을 취소해 리소스를 절약합니다. needs로 Job 간 의존 관계를 명시합니다.

매트릭스 빌드

여러 환경(Node 버전, OS)에서 동시에 테스트하는 패턴입니다:

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
        node: [18, 20, 22]
        exclude:
          - os: macos-latest
            node: 18  # macOS + Node 18 조합 제외
        include:
          - os: ubuntu-latest
            node: 20
            coverage: true  # 이 조합에만 커버리지 추가
      fail-fast: false  # 하나 실패해도 나머지 계속 실행

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: npm ci
      - run: npm test
      - if: matrix.coverage
        run: npm run test:coverage

fail-fast: false는 하나의 매트릭스 조합이 실패해도 나머지를 계속 실행합니다. 어떤 환경에서 실패하는지 전체 그림을 파악할 수 있습니다.

캐싱 전략

의존성 설치 시간을 대폭 줄이는 캐싱 패턴입니다:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # pnpm 캐시 (setup-node 내장)
      - uses: pnpm/action-setup@v2
        with:
          version: 9
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'

      # Docker 레이어 캐시
      - uses: docker/setup-buildx-action@v3
      - uses: docker/build-push-action@v5
        with:
          context: .
          push: false
          cache-from: type=gha
          cache-to: type=gha,mode=max

      # 커스텀 캐시 (빌드 결과물 등)
      - uses: actions/cache@v4
        with:
          path: |
            .next/cache
            dist/
          key: build-${{ runner.os }}-${{ hashFiles('src/**') }}
          restore-keys: |
            build-${{ runner.os }}-

type=gha는 GitHub Actions 전용 Docker 캐시 백엔드로, 별도 설정 없이 레이어 캐싱을 활용할 수 있습니다. restore-keys는 정확한 키가 없을 때 접두사 매칭으로 가장 가까운 캐시를 복원합니다.

재사용 가능한 워크플로우

여러 저장소에서 공통 CI/CD 로직을 공유하는 패턴입니다:

# .github/workflows/reusable-deploy.yml (공통 저장소)
name: Reusable Deploy

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      image-tag:
        required: true
        type: string
    secrets:
      KUBE_CONFIG:
        required: true
      SLACK_WEBHOOK:
        required: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v4

      - name: Set up kubectl
        uses: azure/setup-kubectl@v3

      - name: Deploy to K8s
        env:
          KUBECONFIG_DATA: ${{ secrets.KUBE_CONFIG }}
        run: |
          echo "$KUBECONFIG_DATA" | base64 -d > /tmp/kubeconfig
          export KUBECONFIG=/tmp/kubeconfig
          kubectl set image deployment/api 
            api=${{ inputs.image-tag }}
          kubectl rollout status deployment/api --timeout=300s

      - name: Notify Slack
        if: always() && secrets.SLACK_WEBHOOK
        run: |
          STATUS=${{ job.status }}
          curl -X POST ${{ secrets.SLACK_WEBHOOK }} 
            -d "{"text":"Deploy $STATUS: ${{ inputs.environment }}"}"
# 호출하는 워크플로우
name: Production Deploy

on:
  push:
    tags: ['v*']

jobs:
  deploy:
    uses: org/shared-workflows/.github/workflows/reusable-deploy.yml@main
    with:
      environment: production
      image-tag: ghcr.io/org/api:${{ github.ref_name }}
    secrets:
      KUBE_CONFIG: ${{ secrets.PROD_KUBE_CONFIG }}
      SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

workflow_call로 정의한 워크플로우는 다른 저장소에서 uses로 호출할 수 있습니다. 이 패턴은 K8s ArgoCD GitOps 배포와 조합하면 완전한 GitOps 파이프라인을 구축할 수 있습니다.

Docker 빌드 + 레지스트리 Push

jobs:
  build-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write  # GHCR Push 권한

    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/metadata-action@v5
        id: meta
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=sha,prefix=
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}

      - uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          platforms: linux/amd64,linux/arm64

metadata-action은 Git 컨텍스트(브랜치, 태그, SHA)에서 자동으로 Docker 이미지 태그를 생성합니다. platforms로 멀티 아키텍처 빌드도 가능합니다.

Environment와 보호 규칙

프로덕션 배포에 승인 프로세스를 추가합니다:

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: staging  # 자동 배포
    steps:
      - run: echo "Deploying to staging..."

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment:
      name: production    # 승인 필요 (GitHub 설정)
      url: https://api.example.com
    steps:
      - run: echo "Deploying to production..."

GitHub 저장소 Settings → Environments에서 production 환경에 Required reviewers를 설정하면, 해당 Job이 실행되기 전 지정된 사람의 승인이 필요합니다.

Composite Action: 재사용 가능한 Step

# .github/actions/setup-project/action.yml
name: 'Setup Project'
description: 'Install dependencies and setup environment'

inputs:
  node-version:
    default: '20'

runs:
  using: 'composite'
  steps:
    - uses: pnpm/action-setup@v2
      with:
        version: 9
    - uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
        cache: 'pnpm'
    - run: pnpm install --frozen-lockfile
      shell: bash
    - run: pnpm prisma generate
      shell: bash

# 사용
jobs:
  test:
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-project
        with:
          node-version: '20'
      - run: pnpm test

반복되는 Setup 단계를 Composite Action으로 추출하면 워크플로우가 깔끔해집니다.

시크릿 관리 베스트 프랙티스

  • Environment secrets: 환경별(staging/production) 분리
  • OIDC: AWS/GCP에 시크릿 없이 인증 (aws-actions/configure-aws-credentials)
  • GITHUB_TOKEN: 자동 생성, 권한은 permissions로 최소화
  • 마스킹: ::add-mask::값으로 로그에서 민감 정보 숨기기
# OIDC로 AWS 인증 (시크릿 키 불필요)
- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456:role/deploy
    aws-region: ap-northeast-2

# permissions 최소 권한 원칙
permissions:
  contents: read
  packages: write
  id-token: write  # OIDC에 필요

OIDC를 사용하면 장기 시크릿(AWS Access Key)을 저장하지 않아도 됩니다. K8s 시크릿 암호화와 조합하면 전체 시크릿 체인을 안전하게 관리할 수 있습니다.

마무리

GitHub Actions는 단순 CI를 넘어 매트릭스 빌드로 다환경 테스트, 재사용 워크플로우로 조직 표준화, GHA 캐시로 빌드 최적화, Environment 보호 규칙으로 안전한 배포, OIDC로 시크릿리스 인증까지 지원하는 완전한 CI/CD 플랫폼입니다. Composite Action과 Reusable Workflow를 적극 활용해 DRY한 파이프라인을 구축하세요.

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