GitHub Actions
GitHub Actions¶
1. GitHub Actionsλ?¶
GitHub Actionsλ CI/CD μλν νλ«νΌμ λλ€. μ½λ νΈμ, PR μμ± λ±μ μ΄λ²€νΈμ λ°λΌ μλμΌλ‘ μν¬νλ‘μ°λ₯Ό μ€νν©λλ€.
μ£Όμ μ©λ¶
| μ©λ | μμ |
|---|---|
| CI (Continuous Integration) | ν μ€νΈ μλ μ€ν, λ¦°νΈ κ²μ¬ |
| CD (Continuous Deployment) | μλ λ°°ν¬, Docker μ΄λ―Έμ§ λΉλ |
| μλν | μ΄μ λΌλ²¨λ§, λ¦΄λ¦¬μ€ λ ΈνΈ μμ± |
ν΅μ¬ κ°λ ¶
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Workflow β
β (.github/workflows/ci.yml) β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
β β Job: build β β
β β βββββββββββ βββββββββββ βββββββββββ β β
β β β Step 1 βββ Step 2 βββ Step 3 β β β
β β βCheckout β β Install β β Test β β β
β β βββββββββββ βββββββββββ βββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
β β Job: deploy β β
β β βββββββββββ βββββββββββ β β
β β β Build βββ Deploy β β β
β β βββββββββββ βββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| κ°λ | μ€λͺ |
|---|---|
| Workflow | μλν νλ‘μΈμ€ μ 체 (YAML νμΌ) |
| Event | μν¬νλ‘μ°λ₯Ό νΈλ¦¬κ±°νλ μ΄λ²€νΈ |
| Job | κ°μ λ¬λμμ μ€νλλ λ¨κ³ λ¬Άμ |
| Step | κ°λ³ μμ λ¨μ |
| Action | μ¬μ¬μ© κ°λ₯ν μμ λ¨μ |
| Runner | μν¬νλ‘μ°λ₯Ό μ€ννλ μλ² |
2. μν¬νλ‘μ° νμΌ κ΅¬μ‘°¶
μν¬νλ‘μ°λ .github/workflows/ λλ ν 리μ YAML νμΌλ‘ μ μ₯ν©λλ€.
κΈ°λ³Έ ꡬ쑰¶
# .github/workflows/ci.yml
name: CI Pipeline # μν¬νλ‘μ° μ΄λ¦
on: # νΈλ¦¬κ±° μ΄λ²€νΈ
push:
branches: [main]
pull_request:
branches: [main]
jobs: # μμ
μ μ
build: # Job μ΄λ¦
runs-on: ubuntu-latest # μ€ν νκ²½
steps: # λ¨κ³λ€
- name: Checkout # Step μ΄λ¦
uses: actions/checkout@v4
- name: Run tests
run: npm test
3. νΈλ¦¬κ±° μ΄λ²€νΈ (on)¶
push / pull_request¶
on:
push:
branches:
- main
- 'release/**'
paths:
- 'src/**' # src ν΄λ λ³κ²½ μλ§
paths-ignore:
- '**.md' # md νμΌ λ³κ²½ 무μ
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
μλ μ€ν (workflow_dispatch)¶
on:
workflow_dispatch:
inputs:
environment:
description: 'λ°°ν¬ νκ²½'
required: true
default: 'staging'
type: choice
options:
- staging
- production
μ€μΌμ€ (cron)¶
on:
schedule:
- cron: '0 9 * * 1-5' # νμΌ μ€μ 9μ (UTC)
# cron νμ: λΆ μ μΌ μ μμΌ
# 0 9 * * 1-5 = λ§€μ£Ό μ-κΈ 09:00
λ€λ₯Έ μν¬νλ‘μ° μλ£ μ¶
on:
workflow_run:
workflows: ["Build"]
types: [completed]
4. Jobs μ€μ ¶
κΈ°λ³Έ Job¶
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm test
μ€ν νκ²½ (runs-on)¶
jobs:
build:
runs-on: ubuntu-latest # Ubuntu μ΅μ
# runs-on: ubuntu-22.04 # νΉμ λ²μ
# runs-on: macos-latest # macOS
# runs-on: windows-latest # Windows
Job μμ‘΄μ± (needs)¶
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: npm run build
test:
needs: build # build μλ£ ν μ€ν
runs-on: ubuntu-latest
steps:
- run: npm test
deploy:
needs: [build, test] # λ λ€ μλ£ ν μ€ν
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh
λ³λ ¬ μ€ν¶
jobs:
test-node-16:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v4
with:
node-version: '16'
- run: npm test
test-node-18: # λ³λ ¬ μ€ν
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v4
with:
node-version: '18'
- run: npm test
λ§€νΈλ¦μ€ μ λ΅(Matrix Strategy)¶
λ§€νΈλ¦μ€ λΉλλ₯Ό μ¬μ©νλ©΄ μ¬λ¬ ꡬμ±μμ μλμΌλ‘ μμ μ μ€νν μ μμ΅λλ€.
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
node-version: [18, 20, 22]
os: [ubuntu-latest, macos-latest, windows-latest]
# 9κ°μ μμ
μμ± (3κ° λ²μ Γ 3κ° OS)
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm test
κ³ κΈ λ§€νΈλ¦μ€ μ΅μ ¶
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
node-version: [18, 20]
include:
# νΉμ μ‘°ν© μΆκ°
- os: windows-latest
node-version: 20
exclude:
# νΉμ μ‘°ν© μ μΈ
- os: macos-latest
node-version: 18
fail-fast: false # νλκ° μ€ν¨ν΄λ λ€λ₯Έ μμ
κ³μ μ€ν
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm test
Python λ§€νΈλ¦μ€ μμ ¶
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: |
pip install -r requirements.txt
pytest
μ‘°κ±΄λΆ μ€ν (if)¶
jobs:
deploy:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh
notify:
if: failure() # μ€ν¨ μμλ§
runs-on: ubuntu-latest
steps:
- run: echo "Build failed!"
5. Steps μ€μ ¶
Action μ¬μ© (uses)¶
steps:
# 곡μ Action
- uses: actions/checkout@v4
# νΉμ λ²μ
- uses: actions/setup-node@v4
with:
node-version: '18'
# λ§μΌνλ μ΄μ€ Action
- uses: docker/build-push-action@v5
λͺ λ Ήμ΄ μ€ν (run)¶
steps:
# λ¨μΌ λͺ
λ Ή
- run: npm install
# μ¬λ¬ λͺ
λ Ή
- run: |
npm install
npm run build
npm test
# μμ
λλ ν 리 μ§μ
- run: npm install
working-directory: ./frontend
# μ μ§μ
- run: echo "Hello"
shell: bash
νκ²½ λ³μ¶
steps:
- run: echo $MY_VAR
env:
MY_VAR: "Hello"
- run: echo ${{ env.MY_VAR }}
Secrets μ¬μ©¶
steps:
- run: echo ${{ secrets.API_KEY }}
env:
API_KEY: ${{ secrets.API_KEY }}
Secrets μ€μ : Repository β Settings β Secrets and variables β Actions
6. κ³ κΈ κΈ°λ₯¶
μμ‘΄μ± μΊμ±(Dependency Caching)¶
μΊμ±μ ν΅ν΄ μ€ν κ° μμ‘΄μ±μ μ μ₯νμ¬ μν¬νλ‘μ° μλλ₯Ό λμ λλ€.
actions/cache μ¬μ©¶
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# npm μΊμ
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npm ci
- run: npm test
Python pip μΊμ¶
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip' # λ΄μ₯ μΊμ± μ§μ
- run: pip install -r requirements.txt
Go λͺ¨λ μΊμ¶
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.21'
cache: true # Go λͺ¨λ μλ μΊμ±
- run: go build
μ¬λ¬ μΊμ κ²½λ‘¶
- uses: actions/cache@v4
with:
path: |
~/.npm
~/.cache
node_modules
key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-deps-
μ¬μ¬μ© κ°λ₯ν μν¬νλ‘(Reusable Workflows)¶
λ€λ₯Έ μν¬νλ‘μμ νΈμΆν μ μλ μν¬νλ‘λ₯Ό μμ±ν©λλ€.
νΈμΆ κ°λ₯ν μν¬νλ‘¶
# .github/workflows/reusable-deploy.yml
name: Reusable Deploy
on:
workflow_call:
inputs:
environment:
required: true
type: string
version:
required: false
type: string
default: 'latest'
secrets:
deploy-token:
required: true
outputs:
deployment-url:
description: "Deployed application URL"
value: ${{ jobs.deploy.outputs.url }}
jobs:
deploy:
runs-on: ubuntu-latest
outputs:
url: ${{ steps.deploy.outputs.url }}
steps:
- uses: actions/checkout@v4
- name: Deploy to ${{ inputs.environment }}
id: deploy
run: |
echo "Deploying version ${{ inputs.version }} to ${{ inputs.environment }}"
# Deployment logic here
echo "url=https://${{ inputs.environment }}.example.com" >> $GITHUB_OUTPUT
env:
DEPLOY_TOKEN: ${{ secrets.deploy-token }}
μ¬μ¬μ© κ°λ₯ν μν¬νλ‘ νΈμΆ¶
# .github/workflows/main.yml
name: Main Pipeline
on: push
jobs:
deploy-staging:
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: staging
version: ${{ github.sha }}
secrets:
deploy-token: ${{ secrets.DEPLOY_TOKEN }}
deploy-production:
needs: deploy-staging
if: github.ref == 'refs/heads/main'
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: production
version: ${{ github.sha }}
secrets:
deploy-token: ${{ secrets.DEPLOY_TOKEN }}
notify:
needs: deploy-production
runs-on: ubuntu-latest
steps:
- run: echo "Deployed to ${{ needs.deploy-production.outputs.deployment-url }}"
λ³΅ν© μ‘μ (Composite Actions)¶
μ¬λ¬ λ¨κ³λ‘ ꡬμ±λ μ¬μ©μ μ μ μ‘μ μ μμ±ν©λλ€.
# .github/actions/setup-app/action.yml
name: 'Setup Application'
description: 'Install and configure application'
inputs:
node-version:
description: 'Node.js version'
required: false
default: '20'
install-deps:
description: 'Install dependencies'
required: false
default: 'true'
outputs:
cache-hit:
description: 'Whether cache was restored'
value: ${{ steps.cache.outputs.cache-hit }}
runs:
using: 'composite'
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Cache dependencies
id: cache
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
if: inputs.install-deps == 'true' && steps.cache.outputs.cache-hit != 'true'
shell: bash
run: npm ci
- name: Display info
shell: bash
run: |
echo "Node version: $(node --version)"
echo "npm version: $(npm --version)"
λ³΅ν© μ‘μ μ¬μ©¶
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup application
uses: ./.github/actions/setup-app
with:
node-version: '20'
install-deps: 'true'
- run: npm test
- run: npm run build
ν΄λΌμ°λ μ 곡μ OIDC μΈμ¦¶
OpenID Connect(OIDC)λ₯Ό μ¬μ©νλ©΄ μ격 μ¦λͺ μ μ μ₯νμ§ μκ³ λ ν΄λΌμ°λ μ 곡μμ ν€ μμ΄ μΈμ¦ν μ μμ΅λλ€.
AWS OIDC¶
jobs:
deploy-to-aws:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
aws-region: us-east-1
- name: Deploy to S3
run: |
aws s3 sync ./build s3://my-bucket
GCP OIDC¶
jobs:
deploy-to-gcp:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/github/providers/github-provider'
service_account: 'github-actions@my-project.iam.gserviceaccount.com'
- name: Deploy to Cloud Run
run: |
gcloud run deploy my-service --image gcr.io/my-project/my-image
Azure OIDC¶
jobs:
deploy-to-azure:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Deploy to Azure
run: |
az webapp deploy --resource-group myRG --name myApp
λμμ± μ μ΄(Concurrency Control)¶
μ¬λ¬ μν¬νλ‘ μ€νμ΄ μλ‘ κ°μνλ κ²μ λ°©μ§ν©λλ€.
name: Deploy
on:
push:
branches: [main]
# ν λ²μ νλμ λ°°ν¬λ§
concurrency:
group: production-deploy
cancel-in-progress: false # νμ¬ λ°°ν¬κ° μλ£λ λκΉμ§ λκΈ°
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./deploy.sh
λΈλμΉλ³ λμμ±¶
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true # μ νΈμκ° λμ°©νλ©΄ μ΄μ μ€ν μ·¨μ
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./deploy.sh
PRλ³ λμμ±¶
name: CI
on:
pull_request:
concurrency:
group: ci-${{ github.event.pull_request.number }}
cancel-in-progress: true # μ€λλ PR λΉλ μ·¨μ
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
7. μ€μ΅ μμ ¶
μμ 1: Node.js ν μ€νΈ μλν (μ΅μ λ²μ )¶
# .github/workflows/node-ci.yml
name: Node.js CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test
- name: Build
run: npm run build
μμ 2: Docker μ΄λ―Έμ§ λΉλ & νΈμ (μ΅μ λ²μ )¶
# .github/workflows/docker.yml
name: Docker Build & Push
on:
push:
branches: [main]
tags: ['v*']
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
μμ 3: PR μλ λΌλ²¨λ§¶
# .github/workflows/labeler.yml
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 }}
# .github/labeler.yml (λΌλ²¨ κ·μΉ)
frontend:
- 'src/frontend/**'
- '*.css'
- '*.html'
backend:
- 'src/backend/**'
- 'api/**'
documentation:
- '**/*.md'
- 'docs/**'
μμ 4: μλ λ°°ν¬ (Vercel)¶
# .github/workflows/deploy.yml
name: Deploy to Vercel
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install Vercel CLI
run: npm install -g vercel
- name: Deploy to Vercel
run: vercel --prod --token=${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
μμ 5: λ¦΄λ¦¬μ€ μλν¶
# .github/workflows/release.yml
name: Release
on:
push:
tags: ['v*']
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate changelog
id: changelog
uses: orhun/git-cliff-action@v3
with:
args: --latest --strip header
- name: Create Release
uses: softprops/action-gh-release@v1
with:
body: ${{ steps.changelog.outputs.content }}
draft: false
prerelease: ${{ contains(github.ref, 'beta') }}
8. μ μ©ν Actions (μ΅μ λ²μ )¶
| Action | μ©λ | μ΅μ λ²μ |
|---|---|---|
actions/checkout@v4 |
μ½λ 체ν¬μμ | v4 |
actions/setup-node@v4 |
Node.js μ€μ | v4 |
actions/setup-python@v5 |
Python μ€μ | v5 |
actions/setup-go@v5 |
Go μ€μ | v5 |
actions/cache@v4 |
μμ‘΄μ± μΊμ± | v4 |
docker/setup-buildx-action@v3 |
Docker Buildx μ€μ | v3 |
docker/build-push-action@v6 |
Docker λΉλ/νΈμ | v6 |
docker/login-action@v3 |
Docker λ μ§μ€νΈλ¦¬ λ‘κ·ΈμΈ | v3 |
aws-actions/configure-aws-credentials@v4 |
AWS μΈμ¦ (OIDC) | v4 |
google-github-actions/auth@v2 |
GCP μΈμ¦ (OIDC) | v2 |
azure/login@v2 |
Azure μΈμ¦ (OIDC) | v2 |
μΊμ±μΌλ‘ μλ ν₯μ¶
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # npm μΊμ μλ μ²λ¦¬
- run: npm ci
9. λλ²κΉ ¶
λ‘κ·Έ νμΈ¶
- Actions νμμ μν¬νλ‘μ° μ€ν κΈ°λ‘ νμΈ
- κ° Stepμ λ‘κ·Έ νΌμ³λ³΄κΈ°
λλ²κ·Έ λͺ¨λ¶
steps:
- run: echo "Debug info"
env:
ACTIONS_RUNNER_DEBUG: true
λ‘컬 ν μ€νΈ (act)¶
# act μ€μΉ (macOS)
brew install act
# μν¬νλ‘μ° μ€ν
act push
# νΉμ Jobλ§ μ€ν
act -j build
λͺ λ Ήμ΄/λ¬Έλ² μμ½¶
| ν€μλ | μ€λͺ |
|---|---|
name |
μν¬νλ‘μ°/μ€ν μ΄λ¦ |
on |
νΈλ¦¬κ±° μ΄λ²€νΈ |
jobs |
μμ μ μ |
runs-on |
μ€ν νκ²½ |
steps |
λ¨κ³ μ μ |
uses |
Action μ¬μ© |
run |
λͺ λ Ήμ΄ μ€ν |
with |
Action νλΌλ―Έν° |
env |
νκ²½ λ³μ |
if |
μ‘°κ±΄λΆ μ€ν |
needs |
Job μμ‘΄μ± |
strategy.matrix |
λ§€νΈλ¦μ€ λΉλ |
λ€μ λ¨κ³¶
Kubernetesλ₯Ό λ°°μμ 컨ν μ΄λ μ€μΌμ€νΈλ μ΄μ μ μ΄ν΄ν΄λ΄ μλ€! β Docker/06_Kubernetes_μ λ¬Έ.md