PARA/03_Resources/R001_개발_레퍼런스(참고문서)/개발 환경과 도구/GitHub Actions로 CI CD 파이프라인 구축하기.md

GitHub Actions로 CI CD 파이프라인 구축하기

개요

개발 프로젝트를 진행하다 보면 CI/CD 파이프라인을 구축하는 일이 필수적이다. 최근 모노레포 구조의 프로젝트에서 GitHub Actions를 활용해 자동 배포 워크플로우를 설정하는 과정에서 몇 가지 트러블슈팅 경험이 있었다. npm에 패키지를 배포할 의도는 없었는데 GitHub Actions 워크플로우에서 자꾸 npm 배포 관련 오류가 발생했던 상황을 해결한 경험을 공유하고자 한다.

1. 문제 상황 파악

처음 맞닥뜨린 오류는 이랬다:

🦋 error an error occurred while publishing @common-ui/ui: ENEEDAUTH This command requires you to be logged in to https://registry.npmjs.org/
🦋 error You need to authorize this machine using `npm adduser`

Turborepo와 Changesets를 활용한 모노레포 구조에서 pnpm run release 명령이 자동으로 npm에 패키지를 배포하려고 시도하는 상황이었다. 근데 난 npm에 배포할 생각이 전혀 없었다. 그냥 코드 빌드하고 GitHub에 릴리스 태그만 만들고 싶었을 뿐인데...

2. NPM_TOKEN 설정 시도

처음에는 공식 문서대로 NPM_TOKEN을 발급받아 GitHub 시크릿에 등록했다. 그런데 이번엔 또 다른 오류가 튀어나왔다:

error an error occurred while publishing @common-ui/ui: E404 Not Found - PUT https://registry.npmjs.org/@common-ui%2fui - Not found

npm 스코프 접근 권한 문제였다. @common-ui라는 스코프를 사용하려면 해당 이름의 npm 사용자나 조직이 필요한데, 이런 걸 만들 생각도 없었다. 솔직히 나는 그냥 코드 빌드만 자동화하고 싶었을 뿐이었다.

3. 불필요한 npm 배포 단계 제거하기

문제의 근본적인 원인은 Turborepo 템플릿에 기본으로 포함된 changesets 배포 설정이 내 상황에는 필요 없었다는 점이다. 그래서 워크플로우에서 npm 배포 관련 설정을 모두 제거하고 필요한 단계만 남기기로 했다.

이전에는 다음과 같은 changesets 배포 설정이 있었다:

- name: Create Release PR or Publish
  id: changesets
  uses: changesets/action@v1
  with:
    publish: pnpm run release
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

이를 단순히 빌드만 실행하도록 변경했다:

- name: Build project
  run: pnpm run build
  shell: bash

4. GitHub 릴리스 태그 생성 및 권한 문제 해결

npm 배포 대신 GitHub 릴리스 태그를 자동으로 생성하는 단계를 추가했지만, 새로운 문제가 발생했다:

Error: Resource not accessible by integration

이는 GitHub Actions 워크플로우가 릴리스를 생성할 권한이 없다는 의미였다. 이를 해결하기 위해 워크플로우 파일에 권한 설정을 추가했다:

permissions:
  contents: write

5. 최종 워크플로우 구성

불필요한 부분을 제거하고 필요한 기능만 추가한 최종 워크플로우는 다음과 같다:

name: Release
on:
  push:
    branches:
      - main            # 운영환경
      - develop         # 개발환경
      - 'release/**'    # 릴리스 환경
  workflow_dispatch:    # 수동 실행
    inputs:
      branch:
        description: '릴리스 대상 브랜치'
        required: false
        default: 'main'
 
# 동시 실행 방지 및 이전 실행 취소
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
# 저장소 내용 쓰기 권한 부여
permissions:
  contents: write
 
jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      # 1. 저장소 체크아웃
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # 전체 커밋 이력 가져오기
 
      # 2. pnpm 설정
      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 8.15.6
 
      # 3. Node.js 설정 (캐싱 포함)
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
 
      # 4. 의존성 설치
      - name: Install dependencies
        run: pnpm install --frozen-lockfile
        shell: bash
 
      # 5. 프로젝트 빌드
      - name: Build project
        run: pnpm run build
        shell: bash
 
      # 6. feature 브랜치에서 develop으로 자동 PR 생성
      - name: Create PR to develop from feature branch
        if: startsWith(github.ref, 'refs/heads/feature/')
        uses: peter-evans/create-pull-request@v5
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          commit-message: 'chore: Auto PR from ${{ github.ref_name }}'
          branch: 'auto/pr/${{ github.ref_name }}'
          title: 'Auto PR: ${{ github.ref_name }} → develop'
          body: '이 PR은 GitHub Actions에 의해 자동 생성되었습니다.'
          base: 'develop'
 
      # 7. main 브랜치일 경우 릴리스 브랜치 생성
      - name: Create release branch from main
        if: github.ref == 'refs/heads/main'
        uses: peter-evans/create-branch@v2
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          branch: release/${{ github.run_number }}
          source: main
 
      # 8. GitHub 릴리스 생성
      - name: Create GitHub Release
        if: |
          github.event_name == 'workflow_dispatch' ||
          github.ref == 'refs/heads/main' ||
          startsWith(github.ref, 'refs/heads/release/')
        uses: softprops/action-gh-release@v1
        with:
          tag_name: v${{ github.run_number }}
          name: ${{ github.ref == 'refs/heads/main' && 'Release' || 'Pre-release' }} ${{ github.run_number }}
          draft: false
          prerelease: ${{ github.ref != 'refs/heads/main' }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

이 워크플로우는 다음 기능을 수행한다:

  • 코드 체크아웃과 빌드
  • feature 브랜치에서 develop으로 자동 PR 생성
  • main 브랜치에서 릴리스 브랜치 자동 생성
  • GitHub 릴리스 및 태그 자동 생성

결론 및 교훈

이 경험을 통해 몇 가지 중요한 점을 깨달았다:

  1. 실제 필요한 것만 설정하자. npm에 배포할 계획이 없다면 해당 기능을 제거하고 필요한 부분만 구성하는 것이 좋다.

  2. GitHub Actions의 권한 시스템을 이해하는 것이 중요하다. 워크플로우에서 릴리스나 브랜치를 생성하려면 명시적으로 contents: write 권한이 필요하다.

  3. 템플릿을 그대로 사용하지 말고 프로젝트에 맞게 조정하자. 보통 템플릿이나 공식 예제는 모든 상황을 포괄하기 위해 불필요하게 복잡한 경우가 많다.

  4. 빌드 파이프라인은 점진적으로 개선하자. 처음부터 완벽한 워크플로우를 만들기보다 기본 기능부터 시작해 필요에 따라 기능을 추가하는 것이 효율적이다.

결국, CI/CD 파이프라인은 프로젝트에 맞게 최적화되어야 한다. 내 프로젝트에 필요한 기능만 포함하고 불필요한 복잡성은 제거함으로써 유지보수하기 쉽고 효율적인 워크플로우를 구축할 수 있다.

이 트러블슈팅 경험이 비슷한 상황에 처한 다른 개발자들에게 도움이 되었으면 한다.
이 CI/CD 파이프라인이 적용된 모노레포 구조의 도입기는 [[KnowledgeBase/Blog/모노레포를 실무에 적용한 뒤 한달. 후기]]에서 확인할 수 있다.

댓글

첫 번째 댓글을 남겨보세요.