GitHub Actions와 CI/CD - Git & GitHub 협업 마스터 5편
GitHub Actions and CI/CD - Master Git & GitHub Collaboration Part 5
들어가며: 왜 CI/CD가 필요할까?
프로젝트를 진행하다 보면 코드를 수정할 때마다 테스트를 돌리고, 빌드하고, 배포하는 과정을 반복해야 합니다. 혼자 개발할 때는 그나마 괜찮지만, 팀으로 일하게 되면 이 과정이 상당히 번거로워집니다. "내 컴퓨터에서는 잘 되는데?"라는 말을 한 번쯤 해보셨다면, 아마 CI/CD의 필요성을 느끼셨을 겁니다.
오늘은 Git & GitHub 협업 마스터 시리즈의 5편으로, GitHub Actions를 활용한 CI/CD에 대해 깊이 있게 다뤄보려고 합니다. 처음 접하시는 분들도 이해할 수 있도록 기초부터 시작해서, 실제로 프로젝트에 바로 적용할 수 있는 실전 워크플로우까지 함께 만들어보겠습니다.
1. CI/CD란 무엇인가
1.1 CI (Continuous Integration) - 지속적 통합
CI는 개발자들이 코드 변경사항을 자주, 그리고 정기적으로 메인 브랜치에 통합하는 개발 방식입니다. 단순히 코드를 합치는 게 아니라, 매번 통합할 때마다 자동으로 빌드하고 테스트를 실행해서 문제가 없는지 확인합니다.
예를 들어볼까요? 세 명의 개발자가 각자 다른 기능을 개발하고 있다고 가정해봅시다. CI가 없다면 각자 몇 주간 따로 개발한 후 한꺼번에 합치려고 할 때 엄청난 충돌이 발생할 수 있습니다. 하지만 CI를 적용하면 매일, 아니 하루에도 여러 번 코드를 통합하고 자동으로 검증하기 때문에 문제를 조기에 발견하고 해결할 수 있습니다.
1.2 CD (Continuous Delivery/Deployment) - 지속적 전달/배포
CD는 두 가지 의미로 사용됩니다. Continuous Delivery는 코드 변경사항이 자동으로 테스트를 거쳐 배포 가능한 상태로 준비되는 것을 말하고, Continuous Deployment는 여기서 한 발 더 나아가 프로덕션 환경까지 자동으로 배포되는 것을 의미합니다.
정리하면 이렇습니다:
- Continuous Delivery: 코드가 언제든 배포될 수 있는 상태로 자동 준비됨 (배포 버튼은 사람이 누름)
- Continuous Deployment: 모든 테스트를 통과하면 자동으로 프로덕션에 배포됨
1.3 CI/CD의 실제 이점
제가 실제로 CI/CD를 도입했을 때 느꼈던 가장 큰 변화는 "배포에 대한 두려움이 사라졌다"는 것입니다. 예전에는 금요일 오후에 배포하는 것을 꺼렸습니다. 문제가 생기면 주말에 고생해야 하니까요. 하지만 자동화된 테스트와 배포 파이프라인이 갖춰지니, 언제든 자신감 있게 배포할 수 있게 되었습니다.
2. GitHub Actions 소개
2.1 GitHub Actions란?
GitHub Actions는 GitHub에서 직접 제공하는 CI/CD 플랫폼입니다. 2019년에 정식 출시된 이후로 빠르게 성장해서, 지금은 가장 인기 있는 CI/CD 도구 중 하나가 되었습니다. GitHub 저장소와 완벽하게 통합되어 있어서 별도의 외부 서비스 설정 없이 바로 사용할 수 있다는 게 가장 큰 장점입니다.
GitHub Actions의 핵심 특징을 정리하면:
- GitHub 네이티브: 별도의 서비스 연동 없이 저장소에서 바로 사용
- YAML 기반 설정: 이해하기 쉬운 선언적 문법
- 마켓플레이스: 수천 개의 미리 만들어진 액션을 재사용 가능
- 무료 사용량: 퍼블릭 저장소는 무제한, 프라이빗도 월 2,000분 무료
- 다양한 러너: Linux, Windows, macOS 환경 모두 지원
2.2 다른 CI/CD 도구와의 비교
Jenkins, CircleCI, Travis CI 같은 다른 CI/CD 도구들도 있습니다. 각각 장단점이 있지만, GitHub를 주로 사용한다면 GitHub Actions를 선택하는 게 대부분의 경우 좋은 선택입니다. 설정이 간단하고, 추가 인프라 구축이 필요 없으며, GitHub의 이벤트(Push, PR 등)와 자연스럽게 연동되기 때문입니다.
3. Workflow 파일 구조 이해하기
3.1 워크플로우 파일 위치
GitHub Actions의 워크플로우 파일은 저장소의 .github/workflows/ 디렉토리에 위치합니다. 이 디렉토리에 YAML 파일을 생성하면 GitHub가 자동으로 인식하고 실행합니다.
my-project/
├── .github/
│ └── workflows/
│ ├── ci.yml
│ ├── deploy.yml
│ └── test.yml
├── src/
├── tests/
└── package.json
3.2 워크플로우의 구성 요소
워크플로우는 다음과 같은 계층 구조를 가집니다:
- Workflow (워크플로우): 자동화된 전체 프로세스, 하나의 YAML 파일로 정의
- Event (이벤트): 워크플로우를 시작시키는 트리거
- Job (작업): 같은 러너에서 실행되는 스텝들의 모음
- Step (단계): 개별 작업 단위, 쉘 명령어나 액션을 실행
- Action (액션): 재사용 가능한 작업 단위
4. 기본 문법 마스터하기
4.1 name - 워크플로우 이름
워크플로우의 이름을 지정합니다. GitHub Actions 탭에서 표시되는 이름이므로 알아보기 쉽게 작성하세요.
name: CI Pipeline
4.2 on - 이벤트 트리거
워크플로우가 언제 실행될지 정의합니다. 가장 많이 사용하는 이벤트들을 살펴보겠습니다.
# Push 이벤트
on:
push:
branches: [ main, develop ]
paths:
- 'src/**'
- '!src/**/*.md'
# Pull Request 이벤트
on:
pull_request:
branches: [ main ]
types: [ opened, synchronize, reopened ]
# 스케줄 (크론 문법)
on:
schedule:
- cron: '0 9 * * 1' # 매주 월요일 오전 9시 (UTC)
# 수동 실행
on:
workflow_dispatch:
inputs:
environment:
description: '배포 환경 선택'
required: true
default: 'staging'
type: choice
options:
- staging
- production
4.3 jobs - 작업 정의
실제로 수행할 작업들을 정의합니다. 여러 개의 job을 정의할 수 있고, 기본적으로는 병렬로 실행됩니다.
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 코드 체크아웃
uses: actions/checkout@v4
- name: Node.js 설정
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 의존성 설치
run: npm ci
- name: 빌드
run: npm run build
4.4 runs-on - 러너 환경 지정
작업이 실행될 가상 환경을 지정합니다. GitHub에서 호스팅하는 러너를 사용하거나, 자체 호스팅 러너를 사용할 수 있습니다.
# GitHub 호스팅 러너
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
runs-on: windows-latest
runs-on: macos-latest
# 자체 호스팅 러너
runs-on: self-hosted
4.5 uses와 run - 스텝 실행
uses는 미리 만들어진 액션을 사용할 때, run은 쉘 명령어를 직접 실행할 때 사용합니다.
steps:
# 액션 사용
- name: 코드 체크아웃
uses: actions/checkout@v4
# 쉘 명령어 실행
- name: 테스트 실행
run: npm test
# 여러 줄 명령어
- name: 빌드 및 배포
run: |
npm run build
npm run deploy
5. 자주 쓰는 액션들
5.1 actions/checkout
가장 기본적인 액션으로, 저장소의 코드를 워크플로우 환경으로 체크아웃합니다. 거의 모든 워크플로우에서 첫 번째 스텝으로 사용됩니다.
- uses: actions/checkout@v4
with:
fetch-depth: 0 # 전체 히스토리 가져오기 (태그, 브랜치 정보 필요할 때)
ref: ${{ github.head_ref }} # 특정 브랜치 체크아웃
5.2 actions/setup-node
Node.js 환경을 설정합니다. 버전 지정과 캐시 설정을 함께 할 수 있습니다.
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # 자동으로 node_modules 캐싱
registry-url: 'https://registry.npmjs.org' # npm 배포 시 필요
5.3 actions/cache
의존성이나 빌드 결과물을 캐싱해서 워크플로우 실행 시간을 단축합니다.
- name: 캐시 복원
uses: actions/cache@v4
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
5.4 기타 유용한 액션들
# Python 설정
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
# Java 설정
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
# Docker 빌드 및 푸시
- uses: docker/build-push-action@v5
with:
context: .
push: true
tags: user/app:latest
6. 테스트 자동화 구현
6.1 기본 테스트 워크플로우
PR이 생성되거나 업데이트될 때 자동으로 테스트를 실행하는 워크플로우입니다.
name: Test
on:
pull_request:
branches: [ main, develop ]
push:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Node.js 설정
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: 의존성 설치
run: npm ci
- name: 린트 검사
run: npm run lint
- name: 타입 검사
run: npm run type-check
- name: 단위 테스트
run: npm test -- --coverage
- name: 테스트 커버리지 리포트
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
6.2 테스트 결과 리포팅
테스트 결과를 PR에 코멘트로 남기면 리뷰어가 빠르게 확인할 수 있습니다.
- name: 테스트 결과 리포트
uses: dorny/test-reporter@v1
if: success() || failure()
with:
name: Jest Tests
path: junit.xml
reporter: jest-junit
7. 배포 자동화
7.1 GitHub Pages 배포
정적 사이트를 GitHub Pages로 자동 배포하는 워크플로우입니다.
name: Deploy to GitHub Pages
on:
push:
branches: [ main ]
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Node.js 설정
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: 빌드
run: |
npm ci
npm run build
- name: Pages 아티팩트 업로드
uses: actions/upload-pages-artifact@v3
with:
path: ./dist
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: GitHub Pages 배포
id: deployment
uses: actions/deploy-pages@v4
7.2 Vercel 배포
Vercel로 자동 배포하는 설정입니다. Preview와 Production 환경을 구분해서 배포할 수 있습니다.
name: Deploy to Vercel
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Vercel CLI 설치
run: npm install -g vercel@latest
- name: Vercel 프로젝트 정보 가져오기
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
- name: 빌드
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
- name: 프로덕션 배포
if: github.ref == 'refs/heads/main'
run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
7.3 AWS S3 + CloudFront 배포
name: Deploy to AWS
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Node.js 설정
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: 빌드
run: |
npm ci
npm run build
- name: AWS 자격 증명 설정
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2
- name: S3에 업로드
run: aws s3 sync ./dist s3://${{ secrets.S3_BUCKET }} --delete
- name: CloudFront 캐시 무효화
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
--paths "/*"
8. Secrets 관리
8.1 Secrets 설정 방법
API 키, 토큰 같은 민감한 정보는 절대 코드에 직접 작성하면 안 됩니다. GitHub Secrets를 사용해서 안전하게 관리하세요.
설정 방법:
- 저장소 Settings 이동
- 왼쪽 메뉴에서 "Secrets and variables" -> "Actions" 선택
- "New repository secret" 클릭
- 이름과 값 입력 후 저장
8.2 Secrets 사용 예시
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: 배포
env:
API_KEY: ${{ secrets.API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: |
echo "API 키가 설정되었습니다"
npm run deploy
8.3 환경별 Secrets 관리
Environments 기능을 활용하면 staging, production 등 환경별로 다른 secrets를 관리할 수 있습니다.
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging
steps:
- name: 스테이징 배포
run: deploy --env staging
env:
API_URL: ${{ secrets.API_URL }} # staging 환경의 API_URL
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production
steps:
- name: 프로덕션 배포
run: deploy --env production
env:
API_URL: ${{ secrets.API_URL }} # production 환경의 API_URL
9. Matrix 빌드
9.1 Matrix 전략이란?
Matrix 빌드를 사용하면 여러 환경(OS, 언어 버전 등)에서 동시에 테스트를 실행할 수 있습니다. 예를 들어 Node.js 18, 20, 22 버전에서 모두 테스트하고 싶다면 각각 별도의 job을 만들 필요 없이 matrix로 정의하면 됩니다.
name: Cross-version Test
on: [push, pull_request]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18, 20, 22]
exclude:
- os: windows-latest
node-version: 18
fail-fast: false # 하나가 실패해도 나머지는 계속 실행
steps:
- uses: actions/checkout@v4
- name: Node.js ${{ matrix.node-version }} 설정
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
9.2 동적 Matrix
JSON을 활용해서 동적으로 matrix를 구성할 수도 있습니다.
jobs:
setup:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- id: set-matrix
run: echo "matrix={\"include\":[{\"project\":\"api\"},{\"project\":\"web\"}]}" >> $GITHUB_OUTPUT
build:
needs: setup
runs-on: ubuntu-latest
strategy:
matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}
steps:
- run: echo "Building ${{ matrix.project }}"
10. 실전 워크플로우 예시
10.1 완전한 CI 파이프라인
name: CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run lint
type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run type-check
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm test -- --coverage
- uses: codecov/codecov-action@v3
build:
needs: [lint, type-check, test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build
path: dist/
10.2 자동 릴리즈 워크플로우
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: 릴리즈 노트 생성
uses: softprops/action-gh-release@v1
with:
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
10.3 PR 자동 라벨링
name: PR Labeler
on:
pull_request:
types: [opened, synchronize]
jobs:
label:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/labeler@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
10.4 스케줄 기반 보안 점검
name: Security Check
on:
schedule:
- cron: '0 9 * * 1' # 매주 월요일 오전 9시 (UTC)
workflow_dispatch:
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: 의존성 보안 점검
run: npm audit --audit-level=high
- name: 의존성 업데이트 확인
run: npm outdated || true
- name: Snyk 보안 스캔
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
마치며: CI/CD로 개발 생산성 높이기
이번 글에서는 GitHub Actions를 활용한 CI/CD 파이프라인 구축에 대해 살펴보았습니다. 처음에는 설정 파일 작성이 복잡하게 느껴질 수 있지만, 한번 구축해두면 이후로는 자동화의 혜택을 계속 누릴 수 있습니다.
중요한 것은 처음부터 완벽한 파이프라인을 만들려고 하지 않는 것입니다. 간단한 테스트 자동화부터 시작해서 점점 확장해 나가세요. 저도 처음에는 단순히 npm test만 실행하는 워크플로우로 시작했다가, 필요에 따라 린트, 타입 체크, 배포 자동화 등을 하나씩 추가해 나갔습니다.
다음 6편에서는 오픈소스 기여하기에 대해 다루겠습니다. Fork, PR 워크플로우, 컨트리뷰션 가이드라인 등 실제로 오픈소스 프로젝트에 기여하는 방법을 상세히 알아보겠습니다.