2025-07-25 00:47

Status:

Tags:데브옵스

깃허브 액션

기존 문제점

  • 수동 배포 번거로움
  • 반복적 테스트 작업
  • 기존 CI/CD 도구 복잡함과 진입 장벽 데브옵스 엔지니어 반필수 솔직히 깃허브 쓰면 그냥 깃허브 액션 쓰는게 훨씬 쉽고 정신건강에 좋다.

핵심 구조

이벤트 워크 플로우 작업 액션 러너

Workflow (워크플로우)Job (작업)Step (단계)Action (액션)

0. 특정 이벤트 발생

이벤트와 트리거 발생한 이벤트가 워크플로우

  • push
  • pull_request
  • schedule
  • workflow_dispatch

1. Workflow (워크플로우)

자동화된 프로세스의 정의, 가장 상위 개념

  • .github/workflows 디렉토리에 yaml 파일로 저장
  • 특정 이벤트에 의해 트리거
  • 하나의 저장소에 여러 워크플로우 가능
  • 각각 다른 작업 수행(빌드, 테스트, 배포 등)
name: 워크플로우 이름
on: [이벤트]
jobs:
  작업명:
    runs-on: 실행환경
    steps:
      - 단계들...

2. Job (작업)

동일한 러너에서 실행되는 단계들의 집합

  • 병렬 실행
  • needs 키워드 쓰면 순차 실행 가능
  • 각각 독립된 가상머신에서 실행
  • runs-on 으로 실행 환경 지정

3. Step (단계)

작업 내에서 순차적으로 실행되는 개별 명령

  • run : 셀 명령어 실행
  • uses: 액션 사용

4. Action(액션)

재사용 가능한 코드 단위, 복잡한 작업 캡슐화 직접 만들기도 하고 이미 만들어진걸 가져다가 쓸수도 있음 깃허브 마켓 플레이스엔 2만개 이상의 액션 등록되어 있음 CD 파이프라인

    steps:
    - name: 체크아웃
      uses: actions/checkout@v4
      
    - name: Node.js 설정  
      uses: actions/setup-node@v4

5. 러너 환경

깃허브에서 제공하는 가상 머신으로 유지보수 자동으로 처리 각 작업마다 가상머신 제공하고, 보통은 우분투로 쓴다. 월 2000분 무료, 퍼블릭은 무제한 원하면 직접 self-hosted 러너스 생성해서 만들수도 있음

개념설명
이벤트특정 상황 발생 시 트리거, 예: 푸시, PR
워크플로우이벤트에 따른 작업 집합 정의
잡(Job)병렬 또는 순차적 한 작업 단위
스텝(Step)하나의 명령 또는 액션 수행
액션(Action)재사용 가능한 자동화 모듈
러너(Runner)작업 실행 환경 (VM/컨테이너)

yaml 통합 예시 코드

# ==============================================================================
# GitHub Actions 완전 문법 참조 스크립트 (Next.js 프로젝트용)
# 모든 주요 문법과 고급 기능을 포함한 학습용 종합 예제
# ==============================================================================
 
# ------------------------------------------------------------------------------
# 1. 워크플로우 메타데이터 (Workflow Metadata)
# ------------------------------------------------------------------------------
 
# 워크플로우 이름 - Actions 탭에서 표시되는 이름
# 생략하면 파일 경로가 표시됨
name: "Complete GitHub Actions Syntax Reference (Next.js)"
 
# 워크플로우 실행 시 표시되는 동적 이름
# 표현식과 컨텍스트 -name: "${{ github.actor }}이(가) ${{ github.ref_name }} 브랜치에서 배포 실행 🚀"
 
# ------------------------------------------------------------------------------
# 2. 이벤트 트리거 (Event Triggers)
# ------------------------------------------------------------------------------
 
# 워크플로우를 실행시키는 이벤트들 정의
on:
  # 2.1 Push 이벤트 - 코드가 푸시될 때 실행
  push:
    # 특정 브랜치에서만 실행
    branches: 
      - main
      - develop
      - "release/*"  # 와일드카드 패턴 사용 가능
    # 특정 브랜치 제외
    branches-ignore:
      - "feature/*"
    # 특정 파일 경로에서만 실행
    paths:
      - "src/**"
      - "pages/**"
      - "components/**"
      - "package.json"
      - "package-lock.json"
    # 특정 파일 경로 제외
    paths-ignore:
      - "docs/**"
      - "README.md"
      - "*.md"
    # 특정 태그에서 실행
    tags:
      - "v*.*.*"
 
  # 2.2 Pull Request 이벤트
  pull_request:
    # PR 이벤트 타입 지정
    types: 
      - opened      # PR 생성
      - synchronize # PR 업데이트
      - reopened    # PR 재오픈
      - closed      # PR 닫힘
    branches: [main]
 
  # 2.3 스케줄 이벤트 - cron 표현식 사용
  schedule:
    # 매일 오전 2시 (UTC) 실행
    - cron: '0 2 * * *'
    # 매주 월요일 오전 9시 실행
    - cron: '0 9 * * MON'
 
  # 2.4 수동 트리거 - Actions 탭에서 수동 실행 가능
  workflow_dispatch:
    # 입력 매개변수 정의
    inputs:
      environment:
        description: '배포 환경 선택'
        required: true
        type: choice
        options: 
          - development
          - staging  
          - production
        default: 'staging'
      
      skip_tests:
        description: '테스트 건너뛸지 여부'
        required: false
        type: boolean
        default: false
      
      custom_message:
        description: '사용자 정의 메시지'
        required: false
        type: string
        default: 'Manual deployment'
 
  # 2.5 다른 워크플로우에서 호출 가능하게 설정
  workflow_call:
    inputs:
      node_version:
        description: 'Node.js 버전'
        required: false
        type: string
        default: '20'
    secrets:
      NPM_TOKEN:
        description: 'NPM 레지스트리 토큰'
        required: true
    outputs:
      build_version:
        description: '빌드된 버전'
        value: ${{ jobs.build.outputs.version }}
 
# ------------------------------------------------------------------------------
# 3. 전역 환경 변수 (Global Environment Variables)
# ------------------------------------------------------------------------------
 
# 모든 작업에서 사용 가능한 환경 변수
env:
  # 정적 환경 변수
  NODE_ENV: production
  NEXT_TELEMETRY_DISABLED: 1
  CI: true
  
  # 표현식을 사용한 동적 환경 변수
  BRANCH_NAME: ${{ github.ref_name }}
  COMMIT_SHA: ${{ github.sha }}
  BUILD_TIME: ${{ github.run_number }}
  
  # 조건부 환경 변수 (삼항 연산자 사용)
  DEPLOY_URL: ${{ github.ref == 'refs/heads/main' && 'https://prod.example.com' || 'https://staging.example.com' }}
 
# ------------------------------------------------------------------------------
# 4. 권한 설정 (Permissions)
# ------------------------------------------------------------------------------
 
# GITHUB_TOKEN의 권한 설정
permissions:
  contents: read          # 저장소 내용 읽기
  pages: write           # GitHub Pages 쓰기
  id-token: write        # OIDC 토큰 쓰기
  actions: read          # Actions 읽기
  checks: write          # 체크 쓰기
  deployments: write     # 배포 쓰기
  issues: write          # 이슈 쓰기
  pull-requests: write   # PR 쓰기
  security-events: write # 보안 이벤트 쓰기
 
# ------------------------------------------------------------------------------
# 5. 동시성 제어 (Concurrency)
# ------------------------------------------------------------------------------
 
# 동시 실행 제어 - 같은 그룹의 워크플로우는 하나씩만 실행
concurrency:
  # 동시성 그룹 이름 (브랜치별로 구분)
  group: ${{ github.workflow }}-${{ github.ref }}
  # 진행 중인 작업을 취소할지 여부
  cancel-in-progress: true
 
# ------------------------------------------------------------------------------
# 6. 기본값 설정 (Defaults)
# ------------------------------------------------------------------------------
 
# 모든 작업의 기본 설정
defaults:
  run:
    # 기본 셸 설정
    shell: bash
    # 기본 작업 디렉토리
    working-directory: ./
 
# ------------------------------------------------------------------------------
# 7. 작업 정의 (Jobs)
# ------------------------------------------------------------------------------
 
jobs:
  # ===========================================================================
  # 7.1 코드 품질 검사 작업
  # ===========================================================================
  
  code-quality:
    # 작업 이름 (선택사항)
    name: "코드 품질 및 린트 검사"
    
    # 실행 환경 지정
    runs-on: ubuntu-latest  # ubuntu-latest, windows-latest, macos-latest
    
    # 작업 실행 조건 (표현식 사용)
    if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }}
    
    # 작업 타임아웃 (분 단위)
    timeout-minutes: 15
    
    # 작업 레벨 환경 변수
    env:
      NODE_VERSION: "20"
      
    # 작업 단계들
    steps:
      # 7.1.1 저장소 체크아웃
      - name: "📥 소스코드 체크아웃"
        uses: actions/checkout@v4
        with:
          # 전체 히스토리 가져오기 (기본값: 1)
          fetch-depth: 0
          # 특정 토큰 사용 (기본값: GITHUB_TOKEN)
          token: ${{ secrets.GITHUB_TOKEN }}
          # LFS 파일 체크아웃
          lfs: false
      
      # 7.1.2 Node.js 환경 설정
      - name: "⚙️ Node.js 환경 설정"
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          # 패키지 매니저 캐시 활성화
          cache: 'npm'
          # 레지스트리 URL 설정
          registry-url: 'https://registry.npmjs.org'
          # .nvmrc 파일에서 Node 버전 읽기
          node-version-file: '.nvmrc'
      
      # 7.1.3 의존성 캐싱
      - name: "📦 의존성 캐시 설정"
        uses: actions/cache@v3
        with:
          path: |
            ~/.npm
            node_modules
          # 캐시 키 생성 (package-lock.json 해시 기반)
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          # 캐시 복원 키 (부분 매치 허용)
          restore-keys: |
            ${{ runner.os }}-node-
      
      # 7.1.4 의존성 설치
      - name: "📦 의존성 설치"
        run: |
          # npm ci는 package-lock.json을 기반으로 정확한 버전 설치
          npm ci --prefer-offline --no-audit
        env:
          # 단계별 환경 변수
          NPM_CONFIG_LOGLEVEL: warn
      
      # 7.1.5 TypeScript 타입 검사
      - name: "🔍 TypeScript 타입 검사"
        run: npx tsc --noEmit
        # 계속 실행 (오류가 있어도 다음 단계 실행)
        continue-on-error: false
      
      # 7.1.6 ESLint 검사
      - name: "🧹 ESLint 코드 린트 검사"
        run: |
          # ESLint 실행 및 결과를 파일로 저장
          npm run lint -- --format=json --output-file=eslint-report.json
          # 경고 포함 개수 확인
          npm run lint -- --max-warnings 0
        # 실패해도 계속 진행
        continue-on-error: true
      
      # 7.1.7 Prettier 포맷 검사
      - name: "💅 Prettier 포맷 검사"
        run: npm run format:check
      
      # 7.1.8 보안 취약점 검사
      - name: "🔒 보안 취약점 검사"
        run: |
          # npm audit 실행
          npm audit --audit-level=high
          # 결과를 JSON 파일로 저장
          npm audit --json > audit-results.json
        continue-on-error: true
      
      # 7.1.9 아티팩트 업로드 (린트 결과)
      - name: "📄 린트 결과 업로드"
        uses: actions/upload-artifact@v3
        if: always()  # 항상 실행 (성공/실패 관계없이)
        with:
          name: lint-results
          path: |
            eslint-report.json
            audit-results.json
          # 보존 기간 (일 단위)
          retention-days: 7
 
  # ===========================================================================
  # 7.2 매트릭스 전략을 사용한 테스트 작업  
  # ===========================================================================
  
  test:
    name: "테스트 실행 (Node ${{ matrix.node }}, ${{ matrix.os }})"
    
    # 이전 작업에 의존
    needs: [code-quality]
    
    # 매트릭스 전략 설정
    strategy:
      # 하나의 매트릭스가 실패해도 다른 매트릭스는 계속 실행
      fail-fast: false
      # 최대 병렬 실행 수
      max-parallel: 4
      # 매트릭스 변수 정의
      matrix:
        # 운영체제 매트릭스
        os: [ubuntu-latest, windows-latest, macos-latest]
        # Node.js 버전 매트릭스  
        node: ['18', '20', '21']
        # 매트릭스 조합 추가
        include:
          # 실험적 설정 추가
          - os: ubuntu-latest
            node: '22'
            experimental: true
        # 매트릭스 조합 제외
        exclude:
          # Windows에서 Node 21 제외
          - os: windows-latest
            node: '21'
    
    # 매트릭스 OS에서 실행
    runs-on: ${{ matrix.os }}
    
    # 실험적 빌드는 실패해도 전체 실패로 처리하지 않음
    continue-on-error: ${{ matrix.experimental == true }}
    
    steps:
      - name: "📥 체크아웃"
        uses: actions/checkout@v4
      
      - name: "⚙️ Node.js ${{ matrix.node }} 설정"
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: 'npm'
      
      - name: "📦 의존성 설치"
        run: npm ci
      
      # 조건부 단계 실행
      - name: "🧪 단위 테스트 실행"
        if: ${{ !inputs.skip_tests }}
        run: |
          # 테스트 실행 및 커버리지 생성
          npm test -- --coverage --watchAll=false
        env:
          # 테스트 환경 변수
          NODE_ENV: test
          CI: true
      
      - name: "📊 테스트 커버리지 업로드"
        uses: codecov/codecov-action@v3
        if: matrix.os == 'ubuntu-latest' && matrix.node == '20'
        with:
          files: ./coverage/lcov.info
          flags: unittests
          name: codecov-umbrella
 
  # ===========================================================================
  # 7.3 빌드 작업 (여러 출력과 아티팩트 처리)
  # ===========================================================================
  
  build:
    name: "빌드 및 아티팩트 생성"
    needs: [test]
    runs-on: ubuntu-latest
    
    # 작업 출력 정의 (다른 작업에서 사용 가능)
    outputs:
      # 출력 변수 정의
      build-version: ${{ steps.version.outputs.version }}
      build-time: ${{ steps.build-info.outputs.time }}
      artifact-url: ${{ steps.upload.outputs.artifact-url }}
    
    steps:
      - uses: actions/checkout@v4
      
      - name: "⚙️ Node.js 설정"
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - run: npm ci
      
      # 버전 정보 생성
      - name: "📋 버전 정보 생성"
        id: version
        run: |
          # 버전 문자열 생성
          VERSION="1.0.${{ github.run_number }}"
          echo "version=${VERSION}" >> $GITHUB_OUTPUT
          echo "Generated version: ${VERSION}"
      
      # 빌드 정보 설정
      - name: "ℹ️ 빌드 정보 설정"
        id: build-info
        run: |
          # 빌드 시간 설정
          BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
          echo "time=${BUILD_TIME}" >> $GITHUB_OUTPUT
          
          # 환경변수 파일에 추가
          echo "BUILD_VERSION=${{ steps.version.outputs.version }}" >> $GITHUB_ENV
          echo "BUILD_TIME=${BUILD_TIME}" >> $GITHUB_ENV
      
      # Next.js 빌드
      - name: "🏗️ Next.js 애플리케이션 빌드"
        run: |
          # 빌드 환경 설정
          echo "Building Next.js application..."
          echo "Version: $BUILD_VERSION"
          echo "Time: $BUILD_TIME"
          
          # Next.js 빌드 실행
          npm run build
        env:
          # 빌드 환경 변수
          NODE_ENV: production
          NEXT_TELEMETRY_DISABLED: 1
          # Secrets 사용
          NEXT_PUBLIC_API_URL: ${{ secrets.API_URL }}
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
      
      # 빌드 결과 검증
      - name: "✅ 빌드 결과 검증"
        run: |
          # 빌드 폴더 존재 확인
          if [ ! -d ".next" ]; then
            echo "❌ Build failed: .next directory not found"
            exit 1
          fi
          
          # 빌드 파일 크기 확인
          BUILD_SIZE=$(du -sh .next | cut -f1)
          echo "📊 Build size: $BUILD_SIZE"
          
          # 빌드 정보를 파일로 저장
          cat > build-info.json << EOF
          {
            "version": "$BUILD_VERSION",
            "buildTime": "$BUILD_TIME",
            "size": "$BUILD_SIZE",
            "commit": "${{ github.sha }}",
            "branch": "${{ github.ref_name }}"
          }
          EOF
      
      # 빌드 아티팩트 업로드
      - name: "📦 빌드 아티팩트 업로드"
        id: upload
        uses: actions/upload-artifact@v3
        with:
          name: next-build-${{ steps.version.outputs.version }}
          path: |
            .next/
            public/
            build-info.json
            package.json
          retention-days: 30
      
      # GitHub Pages용 정적 파일 생성 (조건부)
      - name: "📄 GitHub Pages 아티팩트 생성"
        if: github.ref == 'refs/heads/main'
        uses: actions/upload-pages-artifact@v2
        with:
          path: ./out
 
  # ===========================================================================
  # 7.4 보안 검사 작업
  # ===========================================================================
  
  security:
    name: "보안 검사"
    runs-on: ubuntu-latest
    # 병렬 실행 (다른 작업에 의존하지 않음)
    
    # 권한 설정 (작업별)
    permissions:
      security-events: write
      actions: read
      contents: read
    
    steps:
      - uses: actions/checkout@v4
      
      # CodeQL 분석 초기화
      - name: "🔍 CodeQL 분석 초기화"
        uses: github/codeql-action/init@v2
        with:
          languages: javascript
          # 추가 쿼리 사용
          queries: security-extended
      
      # 종속성 보안 검사
      - name: "🔒 종속성 보안 검사"
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high --json-file-output=snyk-results.json
        continue-on-error: true
      
      # CodeQL 분석 수행
      - name: "📊 CodeQL 분석 수행"
        uses: github/codeql-action/analyze@v2
 
  # ===========================================================================
  # 7.5 조건부 배포 작업 (환경별)
  # ===========================================================================
  
  deploy:
    name: "배포 (${{ github.event.inputs.environment || 'staging' }})"
    needs: [build, security]
    runs-on: ubuntu-latest
    
    # 배포 환경 설정
    environment:
      name: ${{ github.event.inputs.environment || 'staging' }}
      # 배포 URL (동적 설정)
      url: ${{ steps.deploy.outputs.url }}
    
    # 메인 브랜치 또는 수동 실행에서만 배포
    if: ${{ github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' }}
    
    steps:
      - uses: actions/checkout@v4
      
      # 환경별 설정 로드
      - name: "⚙️ 환경 설정"
        id: config
        run: |
          ENV="${{ github.event.inputs.environment || 'staging' }}"
          
          case $ENV in
            "production")
              echo "domain=app.example.com" >> $GITHUB_OUTPUT
              echo "api_url=https://api.example.com" >> $GITHUB_OUTPUT
              echo "deploy_path=/var/www/production" >> $GITHUB_OUTPUT
              ;;
            "staging")
              echo "domain=staging.example.com" >> $GITHUB_OUTPUT
              echo "api_url=https://staging-api.example.com" >> $GITHUB_OUTPUT
              echo "deploy_path=/var/www/staging" >> $GITHUB_OUTPUT
              ;;
            *)
              echo "domain=dev.example.com" >> $GITHUB_OUTPUT
              echo "api_url=https://dev-api.example.com" >> $GITHUB_OUTPUT
              echo "deploy_path=/var/www/development" >> $GITHUB_OUTPUT
              ;;
          esac
      
      # 빌드 아티팩트 다운로드
      - name: "📦 빌드 아티팩트 다운로드"
        uses: actions/download-artifact@v3
        with:
          name: next-build-${{ needs.build.outputs.build-version }}
          path: ./build
      
      # 배포 실행
      - name: "🚀 애플리케이션 배포"
        id: deploy
        run: |
          ENV="${{ github.event.inputs.environment || 'staging' }}"
          DOMAIN="${{ steps.config.outputs.domain }}"
          
          echo "🚀 Deploying to $ENV environment..."
          echo "🌐 Domain: $DOMAIN"
          
          # 배포 로직 (예: rsync, scp, API 호출 등)
          # 실제 배포 명령어들...
          
          # 배포 URL 출력
          echo "url=https://$DOMAIN" >> $GITHUB_OUTPUT
        env:
          # 배포에 필요한 시크릿들
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
          SERVER_HOST: ${{ secrets.SERVER_HOST }}
          SERVER_USER: ${{ secrets.SERVER_USER }}
      
      # 배포 상태 알림
      - name: "📢 배포 완료 알림"
        if: always()
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          fields: repo,message,commit,author,action,eventName,ref,workflow
          text: |
            배포 ${{ job.status }}!
            🌍 환경: ${{ github.event.inputs.environment || 'staging' }}
            🔗 URL: ${{ steps.deploy.outputs.url }}
            💬 메시지: ${{ github.event.inputs.custom_message || github.event.head_commit.message }}
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
 
  # ===========================================================================
  # 7.6 정리 작업 (Cleanup Job)
  # ===========================================================================
  
  cleanup:
    name: "정리 작업"
    runs-on: ubuntu-latest
    # 모든 작업이 완료된 후 실행 (성공/실패 무관)
    needs: [deploy]
    if: always()
    
    steps:
      # 이전 아티팩트 정리
      - name: "🧹 이전 아티팩트 정리"
        uses: actions/github-script@v6
        with:
          script: |
            // 30일 이상된 아티팩트 삭제
            const artifacts = await github.rest.actions.listArtifactsForRepo({
              owner: context.repo.owner,
              repo: context.repo.repo,
            });
            
            const thirtyDaysAgo = new Date();
            thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
            
            for (const artifact of artifacts.data.artifacts) {
              if (new Date(artifact.created_at) < thirtyDaysAgo) {
                await github.rest.actions.deleteArtifact({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  artifact_id: artifact.id,
                });
                console.log(`Deleted artifact: ${artifact.name}`);
              }
            }
 
# ==============================================================================
# 추가 고급 문법 예제들
# ==============================================================================
 
# 다른 워크플로우 파일에서 이 워크플로우를 재사용하는 방법:
# 
# jobs:
#   call-reusable-workflow:
#     uses: ./.github/workflows/this-file.yml
#     with:
#       node_version: '18'
#     secrets:
#       NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
 
# 표현식에서 사용 가능한 함수들:
# - contains(search, item): 포함 여부 확인
# - startsWith(searchString, searchValue): 시작 문자열 확인  
# - endsWith(searchString, searchValue): 끝 문자열 확인
# - format(string, replaceValue0, replaceValue1, ...): 문자열 포맷팅
# - join(array, separator): 배열 조인
# - toJSON(value): JSON 문자열로 변환
# - fromJSON(value): JSON 파싱
# - hashFiles(path): 파일 해시 생성
# - success(): 이전 단계들이 모두 성공
# - always(): 항상 실행
# - cancelled(): 취소됨
# - failure(): 실패함
 
# 컨텍스트 변수들:
# - github.*: GitHub 이벤트 및 저장소 정보
# - env.*: 환경 변수
# - job.*: 현재 작업 정보  
# - steps.*: 이전 단계 출력
# - runner.*: 러너 정보
# - secrets.*: 저장소 시크릿
# - vars.*: 저장소 변수
# - inputs.*: 워크플로우 입력
# - matrix.*: 매트릭스 값
 

References

GitHub Actions 완전 핸드북_ 초보자부터 중급자까지 GitHub Actions 문법과 Next.js 실제 사용 사례 핸드북 CI CD 젠킨스