봄수의 연구실

GitHub Actions의 워크플로우를 활용한 프로젝트 관리 본문

DEV

GitHub Actions의 워크플로우를 활용한 프로젝트 관리

berom 2024. 2. 15. 10:32

Git Workflow 문제 해결 - 보일러 템플레이트

본 포스트에서는 GitHub Actions의 워크플로우를 활용해 프로젝트 관리를 어떻게 더 효율적으로 할 수 있는지에 대한 실제 경험을 공유하고자 합니다

GitHub Actions는 GitHub에서 직접 제공하는 CI/CD(Continuous Integration/Continuous Delivery) 도구로, 코드 통합부터 배포까지의 전 과정을 자동화할 수 있습니다. 복잡한 스크립트나 외부 서비스 없이도 GitHub 리포지토리 내에서 직접 이러한 프로세스들을 관리할 수 있는 것이 큰 장점입니다.

최근 프로젝트 리팩토링을 하며 GitHubvite-electron-builder 보일러플레이트를 도입했을 때, 그 중 GitHub Actions 설정이 인상적이었습니다. 프로젝트 빌드, 테스트 실행, 린트 및 코드 스타일 검사, 그리고 자동 릴리즈까지 한 번에 처리할 수 있는 워크플로우를 사용하니 개발자로써 고민해야 하는 영역이 줄어들어 편했습니다.

기본 개념

본격적인 Workflow 설명에 앞서 기본 개념을 짚고 넘어가겠습니다

Workflow (워크플로우)

  • 정의: 워크플로우는 개발 프로세스를 자동화하는 데 사용되는 설정입니다. 하나의 YAML 파일로 구성되어, 특정 이벤트에 의해 트리거되면 실행되는 일련의 작업을 정의합니다.
  • 구성: .github/workflows 디렉토리에 저장되며, 하나 이상의 작업(Job)으로 구성됩니다.

Event (이벤트)

  • 정의: 이벤트는 워크플로우를 실행시키는 트리거입니다. 코드 푸시, 풀 리퀘스트 생성, 정해진 시간에 따른 스케줄링(cron), 외부 시스템의 웹훅과 같은 다양한 이벤트가 워크플로우를 시작할 수 있습니다.

Job (작업)

  • 정의: 작업은 워크플로우 내에서 실행되는 일련의 단계(Steps)입니다. 각 작업은 별도의 가상 환경에서 실행되며, 서로 독립적으로 실행될 수도 있고, 다른 작업들에 의존하여 순차적으로 실행될 수도 있습니다.

Action (액션)

  • 정의: 액션은 재사용 가능한 작업 단위입니다. GitHub에서 제공하는 액션을 사용할 수도 있고, 커뮤니티에서 공유하는 액션을 활용하거나 직접 만들어 사용할 수도 있습니다.
  • 용도: 액션을 통해 커맨드 실행, 코드 체크아웃, 의존성 설치 등의 작업을 수행할 수 있습니다.

Step (단계)

  • 정의: 단계는 작업 내에서 실행되는 개별 명령어 또는 액션을 의미합니다. 각 단계는 순차적으로 실행되며, 액션 호출 또는 쉘 스크립트 실행을 통해 구체적인 작업을 수행합니다.

Workflow 설명

GitHub Actions의 워크플로우는 .yml 형식의 파일로 정의됩니다. 이 파일 내에서 개발자는 워크플로우가 어떠한 이벤트에 의해 트리거될지, 어떤 작업을 수행할지 등을 세밀하게 지정할 수 있습니다.

CI 프로세스 진입점 Workflow

on:
  workflow_dispatch: # 수동 트리거를 위해 GitHub UI에서 이 워크플로우를 시작할 수 있습니다.
  push:
    branches:
      - main # main 브랜치에 푸시될 때
      - 'renovate/**' # renovate로 시작하는 모든 브랜치명을 가진 브랜치에 푸시될 때

워크플로우의 첫 번째 단계는 이벤트에 의해 트리거되는 조건을 명시하는 것입니다. workflow_dispatch를 통해 수동으로 워크플로우를 시작할 수 있는 옵션을 제공함으로써, 개발자는 필요할 때 언제든지 CI 프로세스를 실행할 수 있습니다. push 이벤트는 main 브랜치와 renovate/** 브랜치에 코드가 푸시될 때 자동으로 워크플로우를 실행하도록 설정되어 있으며, 이를 통해 코드 변경 사항이 중요한 브랜치에 반영될 때마다 지속적인 통합 프로세스가 유지됩니다.

paths-ignore 설정은 특정 경로의 변경사항이 있을 때 워크플로우가 실행되지 않도록 함으로써, 불필요한 실행을 방지하고 리소스를 절약합니다. 예를 들어, Markdown 파일이나 개발 환경 설정 파일의 변경은 CI 프로세스에 영향을 미치지 않으므로, 이러한 파일의 변경을 무시함으로써 워크플로우의 실행을 최적화할 수 있습니다.

    paths-ignore:
      - '.github/**'
      - '**.md'

concurrency 설정은 동일한 레퍼런스에 대해 여러 워크플로우가 동시에 실행되는 것을 방지합니다. 이를 통해 자원의 낭비를 줄이고, 프로젝트의 상태가 항상 최신 상태를 반영하도록 합니다. cancel-in-progress 옵션은 새로운 실행이 시작될 때 이전 실행을 자동으로 취소하므로, 항상 최신 커밋에 대한 CI 프로세스만이 실행됩니다.

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

jobs 섹션에서는 워크플로우 내에서 수행할 구체적인 작업들을 정의합니다. typechecking 작업은 코드의 타입을 체크하고, draft_release 작업은 조건(dry-run)에 따라 실제 릴리즈를 준비합니다. 이러한 작업들은 프로젝트의 코드 품질을 유지하고, 배포 준비 과정을 자동화함으로써 개발자의 작업 부담을 줄여줍니다.

jobs:
  typechecking:
    uses: ./.github/workflows/typechecking.yml
  draft_release:
    permissions:
      contents: write

위 설정을 통해 CI 프로세스가 시작되면, 모든 타입 체크를 마친 후 다양한 운영체제 환경에서 릴리즈 준비를 진행합니다. 최종적으로 릴리즈가 준비되면, 릴리즈 드래프트로 남겨 개발자가 최종적으로 릴리즈를 진행할지 결정할 수 있습니다.

결론적으로 CI가 시작이 되면 먼저 모든 타입 체킹을 마치고, 윈도우,맥,우분투 환경에서 릴리즈를 진행합니다. 릴리즈가 끝나면 릴리즈 draft 로 남아 제가 릴리즈를 할지 안할지 선택 할 수 있습니다

Release Workflow

이 워크플로우는 타입 체킹이 완료된 후 시작되며, 배포 준비부터 실제 배포까지의 여러 단계를 포함합니다. 각 설정과 단계별로 어떤 역할을 하는지 자세히 살펴보겠습니다.

릴리즈 워크플로우는 workflow_call 이벤트에 의해 트리거되며, 다른 워크플로우에서 이 워크플로우를 호출할 수 있습니다.

  • dry-run 옵션은 실제로 변경사항을 적용하지 않고, 명령어나 프로세스를 실행해보는 방식을 의미합니다
  • workflow_call: 다른 워크플로우에서 이 워크플로우를 호출할 수 있게 해주며, inputs를 통해 dry-run 옵션을 받아들입니다. 이는 실제 배포 과정을 시뮬레이션하고자 할 때 유용합니다
name: Release
on:
  workflow_call:
    inputs:
      dry-run:
        description: '앱을 컴파일하지만, 배포 서버에 아티팩트를 업로드하지는 않습니다'
        default: false
        required: false
        type: boolean

모든 run 명령어가 실행될 때 사용될 기본 쉘 환경을 bash로 설정합니다. 이 설정은 일관된 실행 환경을 제공하여 스크립트의 호환성을 보장합니다.

defaults:
  run:
    shell: 'bash'

릴리즈 준비를 위한 작업은 다양한 운영체제 환경에서 실행됩니다. permissions 설정을 통해 이 작업이 릴리즈를 생성할 수 있는 권한을 가지며, strategy 설정은 작업의 실패 시 다른 작업의 실행을 즉시 중단(fail-fast)합니다. 이 설정은 프로젝트의 크로스 플랫폼 호환성을 검증하고, 릴리즈 준비 과정을 자동화합니다.

  • permissions: 릴리스를 생성할 수 있는 권한을 작업에 부여합니다.
  • strategy: 여러 운영체제(macos-latest, ubuntu-latest, windows-latest)에서 작업을 동시에 실행할 수 있게 합니다.
  • fail-fast : 여러 환경이나 버전에서 동시에 실행 되는 작업들 중 하날도 실패 할 경우 나머지 성공하지 않는 작업들을 즉시 중단 시킵니다
jobs:
  draft_release:
    permissions:
      contents: write
    strategy:
      fail-fast: true
      matrix:
        os: [ macos-latest, ubuntu-latest, windows-latest ]
    runs-on: ${{ matrix.os }}

코드 체크아웃부터 Node.js 설정, 종속성 설치, 프로젝트 빌드에 이르기까지 다양한 단계를 거칩니다. PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD 환경 변수를 통해 불필요한 브라우저 다운로드를 방지합니다.

yarn install 할 때 타임 아웃을 준 이유는 간헐적으로 릴리즈가 되지 않는 경우가 있었는데 타임 아웃 제한 때문이었습니다. 의존성 다운로드를 할 때 느린 경우가 간헐적으로 있었기 때문입니다

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: 'yarn'
      - name: Clear yarn cache and install dependencies
        run: |
          yarn cache clean
          yarn install --network-timeout 60000 --immutable --immutable-cache --check-cache
      - run: yarn build
  • 여러 단계를 통해 코드를 체크아웃하고, Node.js 환경을 설정하며, 종속성을 설치하고, 프로젝트를 빌드합니다. 특히 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD 환경 변수를 사용하여 불필요한 브라우저 다운로드를 건너뜁니다.

nick-fields/retry@v3 액션을 사용하여 아티팩트의 컴파일 및 업로드 과정을 관리합니다. dry-run 옵션에 따라 실제 GitHub 릴리즈에 아티팩트를 업로드할지 결정하며, 배포 서버와의 연결 문제가 발생할 경우 여러 번 재시도할 수 있습니다.

- name: Compile artifacts ${{ inputs.dry-run && '' || 'and upload them to github release' }}
  uses: nick-fields/retry@v3
  with:
	 timeout_minutes: 15
	 max_attempts: 6
	 retry_wait_seconds: 15
	 retry_on: error
	 command: ./node_modules/.bin/electron-builder --config electron-builder.yml --publish ${{ inputs.dry-run && 'never' || 'always' }}
  env:
	 GH_TOKEN: ${{ secrets.github_token }}

이 설정은 릴리즈 프로세스의 안정성을 보장하며, GitHub 토큰을 활용해 보안성 또한 강화합니다.

워크플로우 원본 파일들

워크플로우 진입점 (Workflow Entry Point)

# 이 워크플로우는 모든 CI (지속적 통합) 프로세스의 시작점입니다.
# 여기서부터 모든 다른 워크플로우가 시작됩니다.
on:
  workflow_dispatch: # 수동 트리거를 위해 GitHub UI에서 이 워크플로우를 시작할 수 있습니다.
  push:
    branches:
      - main # main 브랜치에 푸시될 때
      - 'renovate/**' # renovate로 시작하는 모든 브랜치명을 가진 브랜치에 푸시될 때
    paths-ignore:
      - '.github/**' # .github 디렉토리 내의 변경사항은 무시
      - '!.github/workflows/ci.yml' # 단, ci.yml 파일은 예외
      - '!.github/workflows/typechecking.yml' # typechecking.yml 파일도 예외
      - '!.github/workflows/tests.yml' # tests.yml 파일도 예외
      - '!.github/workflows/release.yml' # release.yml 파일도 예외
      - '**.md' # 모든 Markdown 파일 변경사항 무시
      - .editorconfig # editorconfig 파일 변경사항 무시
      - .gitignore # gitignore 파일 변경사항 무시
      - '.idea/**' # IDEA 설정 파일 변경사항 무시
      - '.vscode/**' # VSCode 설정 파일 변경사항 무시
  pull_request:
    paths-ignore:
      - '.github/**' # 위와 동일한 경로 무시 규칙
      - '!.github/workflows/ci.yml'
      - '!.github/workflows/typechecking.yml'
      # - '!.github/workflows/tests.yml' # 현재는 tests.yml 파일을 무시하지만, 주석 처리됨
      - '!.github/workflows/release.yml'
      - '**.md'
      - .editorconfig
      - .gitignore
      - '.idea/**'
      - '.vscode/**'

concurrency:
  group: ci-${{ github.ref }} # 동일한 참조(ref)에 대한 워크플로우 실행이 중복될 경우 이전 실행을 취소
  cancel-in-progress: true

jobs:
  typechecking:
    uses: ./.github/workflows/typechecking.yml # 타입 체크를 수행하는 작업
  # tests:
  #   uses: ./.github/workflows/tests.yml # 테스트를 수행하는 작업, 현재는 실행되지 않음
  draft_release:
    permissions:
      contents: write # 이 작업이 릴리스를 생성할 수 있도록 권한 부여
    with:
      dry-run: ${{ github.event_name != 'push' || github.ref_name != 'main' }} # 메인 브랜치에 push될 때만 실제 릴리스 생성
    needs: [ typechecking ] # 타입 체킹 작업이 성공한 후 실행
    uses: ./.github/workflows/release.yml # 릴리스 생성 작업

릴리스 생성 (Release Creation)

name: Release
on:
  workflow_call:
    inputs:
      dry-run:
        description: '앱을 컴파일하지만 배포 서버에 아티팩트를 업로드하지 않습니다'
        default: false
        required: false
        type: boolean

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

defaults:
  run:
    shell: 'bash'

jobs:
  draft_release:
    permissions:
      contents: write # 이 작업이 릴리스를 생성할 수 있도록 권한 부여
    strategy:

      fail-fast: true
      matrix:
        os: [ macos-latest, ubuntu-latest, windows-latest ]
    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
      - uses: actions/setup-node@v4
        with:
          cache: 'yarn'
      - name: Clear yarn cache and install dependencies
        run: |
          yarn cache clean
          yarn install --network-timeout 60000 --immutable --immutable-cache --check-cache
        env:
          PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
      - run: yarn build

      - name: Compile artifacts ${{ inputs.dry-run && '' || 'and upload them to github release' }}
        # 배포 서버와의 문제가 있을 경우 여러 번 재시도할 수 있는 이 액션을 사용합니다
        uses: nick-fields/retry@v3
        with:
          timeout_minutes: 15
          max_attempts: 6
          retry_wait_seconds: 15
          retry_on: error
          shell: 'bash'
          command: ./node_modules/.bin/electron-builder --config electron-builder.yml --publish ${{ inputs.dry-run && 'never' || 'always' }}
        env:
          GH_TOKEN: ${{ secrets.github_token }} # GitHub 토큰, 자동으로 제공됩니다 (이 비밀을 리포지토리 설정에서 정의할 필요 없음)

코드 스타일 및 정적 분석 (Linting and Static Analysis)

on:
  workflow_dispatch:
  push:
    paths:
      - '**.js'
      - '**.mjs'
      - '**.cjs'
      - '**.jsx'
      - '**.ts'
      - '**.mts'
      - '**.cts'
      - '**.tsx'
      - '**.vue'
  pull_request:
    paths:
      - '**.js'
      - '**.mjs'
      - '**.cjs'
      - '**.jsx'
      - '**.ts'
      - '**.mts'
      - '**.cts'
      - '**.tsx'
      - '**.vue'

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

defaults:
  run:
    shell: 'bash'

jobs:
  eslint:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
      - uses: actions/setup-node@v4
        with:
          cache: 'yarn'

      - run: yarn install --frozen-lockfile
        env:
          PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1

      - run: yarn lint

  # 템플릿 기여에서 코드 스타일을 검사하는 작업입니다.
  code-style:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
      - uses: actions/setup-node@v4
        with:
          cache: 'yarn'

      - run: yarn add prettier
      - run: yarn prettier --check "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue}"

타입 체킹 (Type Checking)

name: Typechecking
on: [ workflow_call ]

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

defaults:
  run:
    shell: 'bash'

jobs:
  typescript:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
      - uses: actions/setup-node@v4
        with:
          cache: 'yarn'
      - run: yarn install --frozen-lockfile
        env:
          PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
      - run: yarn run typecheck || echo "누락된 스크립트를 건너뜁니다"
728x90