2025-07-25 00:47
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 깃 젠킨스