iBetter Books
수정

Ch 03. GitHub Actions로 CI·CD 구성

코드를 푸시할 때마다 수동으로 테스트하고, 빌드하고, 배포하는 것은 번거롭고 실수가 생기기 쉽습니다. GitHub Actions로 이 과정을 자동화합니다. 테스트 실패 시 배포가 차단되고, 성공 시 자동으로 배포까지 이어집니다.

CI 워크플로우 — 테스트 자동화

main 브랜치에 PR을 올리거나 푸시할 때마다 테스트가 자동 실행됩니다.

# 새 파일: .github/workflows/ci.ymlname: CIon:  push:    branches: [main]  pull_request:    branches: [main]jobs:  test:    name: Test    runs-on: ubuntu-latest    steps:      - name: 코드 체크아웃        uses: actions/checkout@v4      - name: Dart SDK 설정        uses: dart-lang/setup-dart@v1        with:          sdk: stable      - name: 의존성 설치        run: dart pub get      - name: 정적 분석        run: dart analyze      - name: 코드 포맷 확인        run: dart format --output=none --set-exit-if-changed .      - name: 테스트 실행 (커버리지 포함)        run: dart test --coverage=coverage      - name: 커버리지 변환        run: |          dart pub global activate coverage          dart pub global run coverage:format_coverage \            --lcov \            --in=coverage \            --out=coverage/lcov.info \            --packages=.dart_tool/package_config.json \            --report-on=lib      - name: Codecov 업로드        uses: codecov/codecov-action@v3        with:          file: coverage/lcov.info

dart format --set-exit-if-changed는 포맷이 맞지 않으면 빌드를 실패시킵니다. 팀 전체가 동일한 코드 스타일을 유지하도록 강제합니다.

멀티 패키지 CI

이 교재의 프로젝트는 여러 패키지로 나뉩니다. 각 패키지를 별도 job으로 테스트합니다.

# 새 파일: .github/workflows/ci-all.ymlname: CI All Packageson:  push:    branches: [main]  pull_request:    branches: [main]jobs:  test-dart-validator:    name: dart_validator 테스트    runs-on: ubuntu-latest    defaults:      run:        working-directory: dart_validator    steps:      - uses: actions/checkout@v4      - uses: dart-lang/setup-dart@v1        with:          sdk: stable      - run: dart pub get      - run: dart analyze      - run: dart test --coverage=coverage  test-todo-server:    name: todo_server 테스트    runs-on: ubuntu-latest    defaults:      run:        working-directory: todo_server    steps:      - uses: actions/checkout@v4      - uses: dart-lang/setup-dart@v1        with:          sdk: stable      - run: dart pub get      - run: dart analyze      - run: dart test

defaults.run.working-directory로 각 job의 기본 작업 디렉토리를 지정합니다.

CD 워크플로우 — Docker 이미지 빌드 및 배포

main 브랜치에 머지될 때 Docker 이미지를 빌드하고 GitHub Container Registry에 푸시합니다.

# 새 파일: .github/workflows/deploy.ymlname: Deployon:  push:    branches: [main]    paths:      - 'todo_server/**'jobs:  build-and-push:    name: Docker 빌드 및 푸시    runs-on: ubuntu-latest    permissions:      contents: read      packages: write    steps:      - name: 코드 체크아웃        uses: actions/checkout@v4      - name: GitHub Container Registry 로그인        uses: docker/login-action@v3        with:          registry: ghcr.io          username: ${{ github.actor }}          password: ${{ secrets.GITHUB_TOKEN }}      - name: Docker 메타데이터 설정        id: meta        uses: docker/metadata-action@v5        with:          images: ghcr.io/${{ github.repository }}/todo-server          tags: |            type=sha,prefix=sha-            type=raw,value=latest      - name: Docker Buildx 설정        uses: docker/setup-buildx-action@v3      - name: 이미지 빌드 및 푸시        uses: docker/build-push-action@v5        with:          context: ./todo_server          push: true          tags: ${{ steps.meta.outputs.tags }}          labels: ${{ steps.meta.outputs.labels }}          cache-from: type=gha          cache-to: type=gha,mode=max  deploy:    name: 서버 배포    needs: build-and-push    runs-on: ubuntu-latest    environment: production    steps:      - name: SSH로 서버 배포        uses: appleboy/ssh-action@v1        with:          host: ${{ secrets.SERVER_HOST }}          username: ${{ secrets.SERVER_USER }}          key: ${{ secrets.SERVER_SSH_KEY }}          script: |            docker pull ghcr.io/${{ github.repository }}/todo-server:latest            docker-compose -f /opt/todo-app/docker-compose.yml up -d todo-server            docker image prune -f

${{ secrets.GITHUB_TOKEN }}은 GitHub Actions가 자동으로 제공합니다. SERVER_HOST, SERVER_USER, SERVER_SSH_KEY는 GitHub 저장소의 Settings → Secrets에서 설정합니다.

cache-from/cache-to: type=gha는 GitHub Actions 캐시를 Docker 레이어 캐시로 활용합니다. 의존성이 바뀌지 않으면 dart pub get 레이어가 캐시되어 빌드 시간이 크게 줄어듭니다.

pub.dev 자동 퍼블리시

dart_validator 패키지의 버전 태그를 푸시하면 자동으로 pub.dev에 배포합니다.

# 새 파일: dart_validator/.github/workflows/publish.ymlname: Publish to pub.devon:  push:    tags:      - 'v[0-9]+.[0-9]+.[0-9]+'jobs:  publish:    name: pub.dev 배포    runs-on: ubuntu-latest    permissions:      id-token: write  # OIDC 토큰 필요    steps:      - uses: actions/checkout@v4      - uses: dart-lang/setup-dart@v1        with:          sdk: stable      - name: 의존성 설치        working-directory: dart_validator        run: dart pub get      - name: 테스트 실행        working-directory: dart_validator        run: dart test      - name: pub.dev 배포        working-directory: dart_validator        run: dart pub publish --force

permissions.id-token: write는 GitHub OIDC 토큰을 이용하여 pub.dev 인증을 처리합니다. 비밀번호나 API 키 없이 안전하게 배포할 수 있습니다. pub.dev에서 해당 저장소를 "trusted publisher"로 등록해야 합니다.

브랜치 보호 규칙 설정

CI가 통과되지 않으면 main 브랜치에 머지할 수 없도록 설정합니다.

GitHub 저장소 → Settings → Branches → Branch protection rules에서 설정합니다.

  • Branch name pattern: main
  • Require status checks to pass before merging: 체크
  • Status checks: Test (CI 워크플로우의 job 이름)
  • Require branches to be up to date before merging: 체크

이 설정이 있으면 테스트를 통과하지 못한 코드는 main에 들어올 수 없습니다.

Actions 실행 시간 절약 팁

GitHub Actions 무료 사용량(월 2,000분)을 효율적으로 사용합니다.

첫째, paths 필터로 변경된 패키지만 테스트합니다.

on:  push:    paths:      - 'dart_validator/**'

둘째, 의존성 캐시를 사용합니다.

- name: pub 캐시  uses: actions/cache@v3  with:    path: ~/.pub-cache    key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}

셋째, 빠른 작업부터 실행합니다. dart analyzedart test보다 빠릅니다. 분석에서 실패하면 테스트까지 실행하지 않아도 됩니다.

이번 챕터 정리

  • CI 워크플로우로 PR마다 분석, 포맷 확인, 테스트를 자동 실행했습니다.
  • CD 워크플로우로 main 머지 시 Docker 이미지 빌드 → GHCR 푸시 → 서버 배포를 자동화했습니다.
  • GitHub OIDC로 API 키 없이 pub.dev 자동 배포를 구성했습니다.
  • 브랜치 보호 규칙으로 테스트 통과를 머지 조건으로 강제했습니다.

다음 챕터에서는 Dart DevTools로 성능을 분석하고 최적화합니다.