Ver4.0 ๐ GitHub Actions๋ก CI/CD ๊ตฌ์ถํ๊ธฐ โ ๋ฐฐํฌ ์๋ํ ์์ ์ ๋ณต ๊ฐ์ด๋

๐ GitHub Actions๋ก CI/CD ๊ตฌ์ถํ๊ธฐ โ ๋ฐฐํฌ ์๋ํ ์์ ์ ๋ณต ๊ฐ์ด๋
"git push ํ๋๋ ์์์ ํ ์คํธํ๊ณ ์์์ ๋ฐฐํฌ๋จ" ๊ทธ ๋ง๋ฒ์ ์ ์ฒด
๐งช ํ ์คํธ ยท ๐ณ ๋์ปค ๋น๋ ยท โ๏ธ ํด๋ผ์ฐ๋ ๋ฐฐํฌ๊น์ง ํ ๋ฒ์
๐ต ํ๋กค๋ก๊ทธ: "์ ๋ก์ปฌ์์ ์ ๋๋๋ฐ์?"
์น๊ตฌ์ผ, ์ฐ๋ฆฌ ์์งํด์ง์.
๊ฐ๋ฐํ๋ค ๋ณด๋ฉด ์ด๋ฐ ๋์ฌ ํ ๋ฒ์ฏค ํด๋ดค์ง?
"์ดโฆ ์ ์ปดํจํฐ์์๋ ์ ๋์๊ฐ๋๋ฐ์?"
"์ ๊ทธ ํ์ผ ์ปค๋ฐ ์ ํ๋ค์ ใ ใ "
"๋ฐฐํฌ๋ ์ ๊ฐ ๋ฐค์ ์ง์ ์๋ฒ ๋ค์ด๊ฐ์ ํ ๊ฒ์โฆ"
์ด ์ธ ๋ฌธ์ฅ์ ์ฌ์ค ๊ฐ์ ๋ณ์ ์ฆ์์ด์ผ. ๋ณ๋ช
์ ์๋ ๋ฐฐํฌ ์ฆํ๊ตฐ.
์ฌ๋ ์์ด ๋ค์ด๊ฐ๋ ๊ณณ์ ๋ฐ๋์ ์ค์๊ฐ ๋ผ๊ณ , ์ค์๋ ๊ผญ ๊ธ์์ผ ์ ๋
์ ํฐ์ง๋ค.
๊ทธ๋์ ๋์จ ๊ฒ CI/CD๊ณ , ๊ทธ์ค์์๋ GitHub Actions๋
"GitHub ์ฐ๊ณ ์์ผ๋ฉด ๊ทธ๋ฅ ๋ฐ๋ก ์์ ๊ฐ๋ฅ"์ด๋ผ๋ ์ฌ๊ธฐ๊ธ ์ ๊ทผ์ฑ์ ๊ฐ์ง ๋๊ตฌ์ผ.
์ด ๊ธ์์๋ ๊ฐ๋
โ ๋ฌธ๋ฒ โ ์ค์ ์ํฌํ๋ก โ ์บ์ฑ/์ํฌ๋ฆฟ โ ๋ฐฐํฌ โ ๋น์ฉ/๋ณด์๊น์ง
์ง์ง ์ค๋ฌด์์ ์ฐ๋ ์์๋๋ก ์ญ ํ์ด์ค๊ฒ. ์ปคํผ ํ ์ ์ฑ๊ฒจ์. โ
๐ง 1์ฅ. CI/CD, ์ฉ์ด๋ถํฐ ์ ๋๋ก ์ ๋ฆฌํ์
CI (Continuous Integration, ์ง์์ ํตํฉ)
์ฝ๋๋ฅผ ์์ฃผ, ์๊ฒ ํตํฉํ๊ณ , ํตํฉํ ๋๋ง๋ค ์๋์ผ๋ก ๋น๋+ํ
์คํธํ๋ ์ต๊ด์ด์ผ.
๋ชฉ์ ์ ๋จ์ํด. "๊นจ์ง ์ฝ๋๋ฅผ ์ต๋ํ ๋นจ๋ฆฌ ๋ฐ๊ฒฌํ๋ค."
3๊ฐ์ ๋์ ๋ธ๋์น ํผ์ ํ๊ณ ๋ค๋ค๊ฐ ๋จธ์งํ๋ฉด ๊ทธ๊ฑด ํตํฉ์ด ์๋๋ผ ์ถฉ๋ ๋์์น์ง.
CI๋ "ํ๋ฃจ์ ํ ๋ฒ ์ด์ main๊ณผ ๋ง๋๋ผ"๋ ๋ฌธํ + ๊ทธ๊ฑธ ๋ฐ์ณ์ฃผ๋ ์๋ํ ๋๊ตฌ์ ์กฐํฉ์ด์ผ.
CD, ์ฌ์ค ๋ ๊ฐ์ง ๋ป์ด ์๋ค
| ์ฉ์ด | ์๋ฏธ | ์ด์ ๋ฐ์ ๋ฐฉ์ |
|---|---|---|
| Continuous Delivery (์ง์์ ์ ๊ณต) | ์ธ์ ๋ ๋ฐฐํฌ ๊ฐ๋ฅํ ์ํ๋ก ํญ์ ์ค๋น | ๋ง์ง๋ง ๋ฒํผ์ ์ฌ๋์ด ๋๋ฆ |
| Continuous Deployment (์ง์์ ๋ฐฐํฌ) | ํ ์คํธ ํต๊ณผํ๋ฉด ์ด์๊น์ง ์๋ | ์ฌ๋ ๊ฐ์ ์์ |
๐ก ํ์ค ํ
์คํํธ์
์ด๊ธฐ์ Delivery๋ก ์์ํ๋ ๊ฒ ์์ ํด.
staging๊น์ง๋ ์์ ์๋, production์ ์๋ ์น์ธ(Environment approval).
ํ
์คํธ ์ปค๋ฒ๋ฆฌ์ง์ ๋ชจ๋ํฐ๋ง์ด ๊ฐ์ถฐ์ง ๋ค์์ Deployment๋ก ๋์ด๊ฐ๋ฉด ๋ผ.
์ GitHub Actions์ธ๊ฐ?
1) ๋ณ๋ ์๋ฒ๊ฐ ํ์ ์๋ค
Jenkins์ฒ๋ผ ๋ง์คํฐ ์๋ฒ๋ฅผ ์ธ์ฐ๊ณ ํ๋ฌ๊ทธ์ธ ์ง์ฅ์ ๊ฒช์ ํ์๊ฐ ์์ด. GitHub๊ฐ ๋ฌ๋๋ฅผ ๋น๋ ค์ค.
2) ์ฝ๋์ ๊ฐ์ ๋ ํฌ์ ์ฐ๋ค.github/workflows/*.yml. ์ค์ ์ด ์ฝ๋ ๋ฆฌ๋ทฐ ๋์์ด ๋๊ณ , PR๋ก ๋ณ๊ฒฝ ์ด๋ ฅ์ด ๋จ์. ์ด๊ฒ ์ง์ง ํฌ๋ค.
3) ๋ง์ผํ๋ ์ด์ค ์ํ๊ณactions/checkout, actions/setup-node, docker/build-push-actionโฆ ๋จ์ด ๋ง๋ ๋ธ๋ก์ ์กฐ๋ฆฝํ๋ฉด ๋.
4) ์ด๋ฒคํธ๊ฐ GitHub ๊ทธ ์์ฒด
PR ์ด๋ฆผ, ์ด์ ๋ผ๋ฒจ ๋ถ์, ๋ฆด๋ฆฌ์ฆ ์์ฑ, ์ฝ๋ฉํธ ์์ฑโฆ GitHub์์ ์ผ์ด๋๋ ๊ฑฐ์ ๋ชจ๋ ์ผ์ด ํธ๋ฆฌ๊ฑฐ๊ฐ ๋ ์ ์์ด.
5) ํผ๋ธ๋ฆญ ๋ ํฌ๋ ๋ฌด๋ฃ
์คํ์์ค๋ ํ์ค ๋ฌ๋ ์ฌ์ฉ ์ ๋น์ฉ์ด ๋ค์ง ์์. ํ๋ผ์ด๋น์ ๋ฌด๋ฃ ๋ถ(minutes) ํ๋ ํ ๊ณผ๊ธ.
๐งฉ 2์ฅ. ๊ตฌ์กฐ ํด๋ถ โ Workflow, Job, Step, Action
GitHub Actions๋ ๋ฌ์์ ์ธํ(๋งํธ๋ฃ์์นด) ๊ตฌ์กฐ์ผ. ์ด ๊ณ์ธต๋ง ์ดํดํ๋ฉด YAML์ด ๊ฐ์๊ธฐ ์ฝํ.
์ฉ์ด ์ ๋ฆฌํ
| ์์ | ์ค๋ช | ๊ธฐ์ต ํฌ์ธํธ |
|---|---|---|
| Workflow | YAML ํ์ผ 1๊ฐ = ์๋ํ ํ๋ก์ธ์ค 1๊ฐ | .github/workflows/ ์์ ์์น |
| Event(on) | ์ํฌํ๋ก๋ฅผ ๊นจ์ฐ๋ ๋ฐฉ์์ | push, pull_request, schedule, workflow_dispatchโฆ |
| Job | ๊ฐ์ ๋ฌ๋(๊ฐ์ ๋จธ์ )์์ ์คํ๋๋ ๋ฌถ์ | ๊ธฐ๋ณธ ๋ณ๋ ฌ ์คํ |
| Step | Job ์์ ํ ์ค ๋ช ๋ น ๋๋ Action ํธ์ถ | run ๋๋ uses |
| Action | ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์คํ ๋จ์(ํจํค์ง) | ๋ง์ผํ๋ ์ด์ค์์ ๊ฐ์ ธ์ด |
| Runner | ์ค์ ๋ก ์ผํ๋ ๋จธ์ | GitHub-hosted / Self-hosted |
โ ๏ธ ์ด๋ณด ์ต๋ ํจ์
Job์ ์๋ก ๋ค๋ฅธ ๋จธ์ ์์ ๋๋ค. ๊ทธ๋์ job A์์ ๋ง๋ dist/ ํด๋๊ฐ job B์ ์ ๋ ๊ทธ๋ฅ ์ ๋์ด๊ฐ.
๋๊ธฐ๊ณ ์ถ์ผ๋ฉด actions/upload-artifact + actions/download-artifact๋ฅผ ์จ์ผ ํด.
๋ฐ๋๋ก ๊ฐ์ Job ์์ Step๋ผ๋ฆฌ๋ ํ์ผ ์์คํ
์ ๊ณต์ ํ๋ค.
โ๏ธ 3์ฅ. ์ฒซ ์ํฌํ๋ก โ Hello, Automation
๋ ํฌ ๋ฃจํธ์ .github/workflows/ci.yml ๋ง๋ค๊ณ ์ด๊ฑฐ ๋ถ์ฌ๋ฃ์ด ๋ด.
name: CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
hello:
runs-on: ubuntu-latest
steps:
- name: ์ฝ๋ ๋ด๋ ค๋ฐ๊ธฐ
uses: actions/checkout@v4
- name: ์ธ์ฌํ๊ธฐ
run: echo "์๋
$GITHUB_ACTOR! ์ปค๋ฐ $GITHUB_SHA ํ์ธํ๋ค."
- name: ํ์ผ ๊ตฌ๊ฒฝ
run: ls -al
push ํ๊ณ ๋ ํฌ์ Actions ํญ ์ด์ด๋ด. ์ด๋ก ์ฒดํฌ ๋จ๋ฉด ์ฑ๊ณต. ๐
์ด๊ฒ CI/CD์ ์ฒซ ์ฌ์ฅ ๋ฐ๋์ด์ผ.
๊ฐ ์ค์ด ํ๋ ์ผ
name: โ Actions ํญ์ ๋ณด์ด๋ ์ํฌํ๋ก ์ด๋ฆ. ์ ์ฐ๋ฉด ํ์ผ๋ช
์ด ๋ค์ด๊ฐ.
on: โ ํธ๋ฆฌ๊ฑฐ. branches ํํฐ๋ก ๋ถํ์ํ ์คํ(=๋น์ฉ)์ ์ค์ผ ์ ์์ด.
runs-on: โ ๋ฌ๋ OS. ubuntu-latest๊ฐ ๊ฐ์ฅ ๋น ๋ฅด๊ณ ์ ๋ ดํด. windows-latest๋ 2๋ฐฐ, macos-latest๋ 10๋ฐฐ ๊ณผ๊ธ(ํ๋ผ์ด๋น ๊ธฐ์ค).
actions/checkout@v4 โ ๊ฑฐ์ ๋ชจ๋ ์ํฌํ๋ก์ 1๋ฒ ์คํ
. ์ด๊ฑฐ ์์ผ๋ฉด ๋ฌ๋์ ์ฝ๋๊ฐ ์๋ค. ์ง์ง ์๋ค.
๐งท ๋ฒ์ ํ ๊ณ ์ ์ต๊ด
@v4์ฒ๋ผ ๋ฉ์ด์ ํ๊ทธ๋ฅผ ์ฐ๋ ๊ฒ ๊ธฐ๋ณธ์ด๊ณ , ๋ณด์์ด ์ค์ํ ์กฐ์ง์ @a1b2c3... ์ปค๋ฐ SHA๋ก ๊ณ ์ ํด. ํ๊ทธ๋ ์ฌ์ง์ ๋ ์ ์์ด์ ๊ณต๊ธ๋ง ๊ณต๊ฒฉ ํ๋ฉด์ด ๋๋๊น.
๐ฏ 4์ฅ. ํธ๋ฆฌ๊ฑฐ(on) ์ด์ ๋ฆฌ โ ์ธ์ ๋๋ฆด ๊ฒ์ธ๊ฐ
์์ฃผ ์ฐ๋ ์ด๋ฒคํธ
on:
# 1) ํธ์ โ ๊ฒฝ๋ก ํํฐ๋ก ๋ชจ๋
ธ๋ ํฌ ์ต์ ํ
push:
branches: [ main ]
paths:
- 'apps/web/**'
- '!**/*.md' # ๋ฌธ์๋ง ๋ฐ๋๋ฉด ์คํ ์ ํจ
# 2) PR โ ์ด๋ค ์ก์
์ ๋ฐ์ํ ์ง ์ง์
pull_request:
types: [ opened, synchronize, reopened ]
# 3) ํ๊ทธ ํธ์ โ ๋ฆด๋ฆฌ์ฆ ๋ฐฐํฌ์ฉ
push:
tags: [ 'v*.*.*' ]
# 4) ์ค์ผ์ค โ ํฌ๋ก (UTC ๊ธฐ์ค!)
schedule:
- cron: '0 18 * * *' # ๋งค์ผ ํ๊ตญ์๊ฐ 03:00
# 5) ์๋ ์คํ + ์
๋ ฅ๊ฐ
workflow_dispatch:
inputs:
environment:
description: '๋ฐฐํฌ ๋์'
type: choice
options: [ staging, production ]
default: staging
# 6) ๋ค๋ฅธ ์ํฌํ๋ก๊ฐ ๋๋ ๋ค
workflow_run:
workflows: [ "CI" ]
types: [ completed ]
๐ cron์ UTC๋ค
ํ๊ตญ์๊ฐ(KST)์ UTC+9. "๋งค์ผ ์ค์ 9์"๋ฅผ ์ํ๋ฉด 0 0 * * *.
๊ทธ๋ฆฌ๊ณ GitHub ํฌ๋ก ์ ์ ํํ ์๊ฐ ๋ณด์ฅ์ด ์์ด. ๋ถํ๊ฐ ํฌ๋ฉด ์ ๋ถ~์์ญ ๋ถ ์ง์ฐ๋๊ฑฐ๋ ์คํต๋ ์ ์์ผ๋ ๊ฒฐ์ ๋ง๊ฐ์ฒ๋ผ ์ค์ํ ์์
์ ๋ค๋ฅธ ์ค์ผ์ค๋ฌ๋ฅผ ์จ.
pull_request vs pull_request_target
์ธ๋ถ ํฌํฌ์์ ์จ PR์ ์ํฌ๋ฆฟ์ ์ ๊ทผํ ์ ์์ด(์๋๋ ๋ณด์ ์ค๊ณ).
pull_request_target์ ๋ฒ ์ด์ค ๋ธ๋์น ๊ถํ์ผ๋ก ์คํ๋ผ์ ์ํฌ๋ฆฟ์ ์ธ ์ ์๋๋ฐ,
์ฌ๊ธฐ์ PR ์ฝ๋๋ฅผ ์ฒดํฌ์์ํด ์คํํ๋ฉด ์ํฌ๋ฆฟ ์ ์ถ ์ทจ์ฝ์ ์ด ๋ผ.
๐จ ์ ๋ ๊ธ์ง ํจํด
pull_request_target + ref: ${{ github.event.pull_request.head.sha }} ์ฒดํฌ์์ + npm install(์คํฌ๋ฆฝํธ ์คํ) ์กฐํฉ.
๋จ์ ์ฝ๋๋ฅผ ๋ด ์ํฌ๋ฆฟ ๊ถํ์ผ๋ก ์คํ์์ผ์ฃผ๋ ๊ฑฐ์ผ. ์ค์ ๋ก ์ ๋ช
์คํ์์ค๋ค์ด ์ด๊ฑธ๋ก ํธ๋ ธ์ด.
๐ ๏ธ 5์ฅ. ์ค์ ์ํฌํ๋ก โ Node.js ์ฑ CI
์ด์ ์ง์ง ์ธ ๋งํ ๊ฑธ ๋ง๋ค์ด๋ณด์. ๋ฆฐํธ โ ํ์ ์ฒดํฌ โ ํ ์คํธ โ ๋น๋.
name: Node CI
on:
push:
branches: [ main ]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
quality:
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
node: [ 18, 20, 22 ]
steps:
- uses: actions/checkout@v4
- name: Node ${{ matrix.node }} ์ธํ
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: 'npm' # ๋ด๋ถ์ ์ผ๋ก ์บ์ ์ฒ๋ฆฌ
- name: ์์กด์ฑ ์ค์น
run: npm ci # install ์๋! ci ์
- name: ๋ฆฐํธ
run: npm run lint
- name: ํ์
์ฒดํฌ
run: npx tsc --noEmit
- name: ํ
์คํธ + ์ปค๋ฒ๋ฆฌ์ง
run: npm test -- --coverage
- name: ๋น๋
run: npm run build
- name: ๋น๋ ๊ฒฐ๊ณผ ์ ์ฅ
if: matrix.node == 20
uses: actions/upload-artifact@v4
with:
name: web-dist
path: dist/
retention-days: 7
์ฌ๊ธฐ์ ๊ผญ ์ฑ๊ธธ 3๊ฐ์ง
โ concurrency โ ๋๊ณผ ์๊ฐ ์ ์ฝ์ ์
๊ฐ์ ๋ธ๋์น์ ์ฐ์ ํธ์ํ๋ฉด ์ด์ ์คํ์ ์๋ ์ทจ์ํด. ์ฝ๋ ๋ฆฌ๋ทฐ ์ค 3๋ฒ ํธ์ํ๋ค๊ณ 3๋ฒ ๋ค ๋๋ฆด ์ด์ ์์ง.
โก npm ci vs npm install
ci๋ package-lock.json์ ๊ทธ๋๋ก ์ค์นํ๊ณ node_modules๋ฅผ ์ง์ด ๋ค ๊น์. ๊ฒฐ๊ณผ๊ฐ ํญ์ ๋์ผ = ์ฌํ ๊ฐ๋ฅํ ๋น๋. CI์์ ๋ฌด์กฐ๊ฑด ci.
โข strategy.matrix โ ์กฐํฉ ํญ๋ฐ ์ฃผ์
Node 3๊ฐ ร OS 3๊ฐ = 9๋ฒ ์คํ = ๋น์ฉ 9๋ฐฐ. ํ์ํ ์กฐํฉ๋ง ๊ณจ๋ผ ์ฐ๊ณ , include/exclude๋ก ๋ค๋ฌ์ด.
โฑ๏ธ timeout-minutes๋ ํ์ ์ต๊ด
๊ธฐ๋ณธ ํ์์์์ด 360๋ถ(6์๊ฐ)์ด์ผ. ๋ฌดํ ๋๊ธฐ ๊ฑธ๋ฆฐ ํ ์คํธ ํ๋๊ฐ 6์๊ฐ ๊ณผ๊ธ๋ ์ ์์ด. ํญ์ 10~30๋ถ์ผ๋ก ๊ฑธ์ด๋์.
โก 6์ฅ. ์บ์ฑ โ ํ์ดํ๋ผ์ธ ์๋๋ฅผ 2๋ฐฐ๋ก
CI๊ฐ ๋๋ฆฌ๋ฉด ์ฌ๋๋ค์ CI๋ฅผ ์ ๋ณธ๋ค. ๊ทธ๋ฌ๋ฉด CI๋ ์ฃฝ๋๋ค.
๊ทธ๋์ ์บ์ฑ์ ๊ธฐ๋ฅ์ด ์๋๋ผ ๋ฌธํ ์ ์ง ์ฅ์น์ผ.
- name: ์์กด์ฑ ์บ์
uses: actions/cache@v4
with:
path: |
~/.npm
.next/cache
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
์บ์ ํค ์ค๊ณ ์์น
| ๊ตฌ์ฑ | ์ญํ |
|---|---|
runner.os | OS๋ณ ๋ฐ์ด๋๋ฆฌ ์ถฉ๋ ๋ฐฉ์ง |
hashFiles(lockํ์ผ) | ์์กด์ฑ์ด ๋ฐ๋๋ฉด ํค๊ฐ ๋ฐ๋ = ์ ์บ์ |
restore-keys | ์ ํํ ์ผ์น ์ ํ๋ฉด ๋ถ๋ถ ์ผ์น๋ก๋ผ๋ ๋ณต์ |
๐ฆ ์ธ์ด๋ณ ์ถ์ฒ
โข Node: setup-node์ cache: npm|yarn|pnpm
โข Python: setup-python์ cache: pip
โข Java: setup-java์ cache: gradle|maven
โข Go: setup-go๋ ๊ธฐ๋ณธ ์บ์ ๋ด์ฅ
โ ์ ์ฉ setup ์ก์
์ ์บ์ ์ต์
์ด ๊ฐ์ฅ ๊ฐ๋จํ๊ณ ์์ ํ๋ค.
โ ๏ธ ์บ์ ํ๋
๋ ํฌ๋น ์บ์ ์ด๋์ ์ํ(์ผ๋ฐ์ ์ผ๋ก 10GB)์ด ์๊ณ , ๋์ผ๋ฉด ์ค๋๋ ๊ฒ๋ถํฐ ์๋ ์ญ์ ๋ผ. 7์ผ๊ฐ ๋ฏธ์ฌ์ฉ ์บ์๋ ์ ๊ฑฐ๋ผ.
๊ทธ๋ฆฌ๊ณ ์บ์๋ ์ ๋ขฐ ์ ์ฅ์๊ฐ ์๋๋ค โ ๋น๋ฐ๊ฐ์ด๋ ๋ฐ๋์ ํ์ํ ์ฐ์ถ๋ฌผ์ ์บ์์ ๋์ง ๋ง. ์ํฐํฉํธ๋ ๋ ์ง์คํธ๋ฆฌ๋ฅผ ์จ.
์บ์ vs ์ํฐํฉํธ, ํท๊ฐ๋ฆฌ์ง ๋ง์
| Cache | Artifact | |
|---|---|---|
| ๋ชฉ์ | ์ฌ์ฌ์ฉ์ผ๋ก ์๋ ํฅ์ | ๊ฒฐ๊ณผ๋ฌผ ๋ณด๊ด/์ ๋ฌ |
| ๋ฒ์ | ๋ธ๋์น ๊ฐ ๊ณต์ (์ ํ์ ) | ๊ฐ์ ์ํฌํ๋ก ์คํ ๋ด Job ๊ฐ ์ ๋ฌ + ๋ค์ด๋ก๋ |
| ์ฌ๋ผ์ง | ์๋ ์ ๊ฑฐ ๊ฐ๋ฅ | ๋ณด์กด ๊ธฐ๊ฐ๊น์ง ์ ์ง |
| ์์ผ๋ฉด? | ๋ค์ ๋ง๋ค๋ฉด ๋จ (๋ฌดํด) | ํ์ดํ๋ผ์ธ ์คํจ (์น๋ช ์ ) |
๐ 7์ฅ. ์ํฌ๋ฆฟ๊ณผ ํ๊ฒฝ โ ๋น๋ฐ์ ์ฝ๋์ ๋์ง ์๋๋ค
๋ฐฐํฌ์ ํ์ฐ์ ์ผ๋ก ๋น๋ฐ์ด ๋ถ์ด. API ํค, SSH ํค, ๋ ์ง์คํธ๋ฆฌ ํ ํฐโฆ
์ด๊ฑธ YAML์ ํ๋์ฝ๋ฉํ๋ ์๊ฐ ๊น ํ์คํ ๋ฆฌ์ ์์ํ ๋ฐ์ ๋ผ.
์ํฌ๋ฆฟ 3๊ณ์ธต
| ๋ ๋ฒจ | ๋ฒ์ | ์ถ์ฒ ์ฉ๋ |
|---|---|---|
| Repository secret | ํด๋น ๋ ํฌ | ์ผ๋ฐ ํ ํฐ |
| Environment secret | ํน์ ํ๊ฒฝ(production ๋ฑ) | ์ด์ ์๊ฒฉ์ฆ๋ช + ์น์ธ ๊ท์น |
| Organization secret | ์กฐ์ง ์ ์ฒด | ๊ณตํต ๋ ์ง์คํธ๋ฆฌ ๊ณ์ |
jobs:
deploy-prod:
runs-on: ubuntu-latest
environment:
name: production # ์น์ธ/๋ณดํธ ๊ท์น ์ ์ฉ
url: https://example.com
steps:
- uses: actions/checkout@v4
- name: ๋ฐฐํฌ
env:
API_KEY: ${{ secrets.PROD_API_KEY }}
DB_URL: ${{ secrets.PROD_DB_URL }}
run: ./scripts/deploy.sh
โ Environment์ ์ง์ง ๊ฐ์
โข Required reviewers: ํน์ ์ธ์์ด ์น์ธํด์ผ Job ์งํ
โข Wait timer: N๋ถ ๋๊ธฐ(์ทจ์ ๊ธฐํ ํ๋ณด)
โข Deployment branches: main ๋ธ๋์น์์๋ง ์ด์ ๋ฐฐํฌ ํ์ฉ
โ "์ค์๋ก feature ๋ธ๋์น์์ ์ด์ ๋ฐฐํฌ" ์ฌ๊ณ ๋ฅผ ๊ตฌ์กฐ์ ์ผ๋ก ์ฐจ๋จํด.
GITHUB_TOKEN๊ณผ ๊ถํ ์ต์ํ
๋ชจ๋ ์คํ๋ง๋ค ์๋์ผ๋ก GITHUB_TOKEN์ด ๋ฐ๊ธ๋ผ. ํธํ๋ฐ, ๊ธฐ๋ณธ ๊ถํ์ด ๋์ผ๋ฉด ์ํํด.
permissions:
contents: read # ๊ธฐ๋ณธ์ ์ฝ๊ธฐ๋ง
# ํ์ํ Job์์๋ง ์๋์ฒ๋ผ ์น๊ฒฉ
jobs:
release:
permissions:
contents: write
packages: write
id-token: write # OIDC์ฉ
๐ ์ฅ๊ธฐ ํค๋ฅผ ์์ ๋ ๋ฒ: OIDC
AWS/GCP/Azure์ ๋ฐฐํฌํ ๋ Access Key๋ฅผ ์ํฌ๋ฆฟ์ ์ ์ฅํ๋ ์๋๋ ๋๋ฌ์ด.
GitHub์ด ๋ฐ๊ธํ ์งง์ ์๋ช
์ ID ํ ํฐ์ผ๋ก ํด๋ผ์ฐ๋ ์ญํ ์ ์ง์ ๋งก๋(assume) ๋ฐฉ์์ด ํ์ค์ด์ผ.
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/gha-deploy
aws-region: ap-northeast-2
- run: aws s3 sync ./dist s3://my-bucket --delete
๐ OIDC๊ฐ ์ข์ ์ด์
์๊ตฌ ํค๊ฐ ์กด์ฌํ์ง ์์ผ๋ ์ ์ถ๋ ํค ์์ฒด๊ฐ ์๋ค. ๊ฒ๋ค๊ฐ IAM ์ ๋ขฐ ์ ์ฑ ์์ "์ด ๋ ํฌ, ์ด ๋ธ๋์น, ์ด ํ๊ฒฝ๋ง"์ผ๋ก ์กฐ๊ฑด์ ์ขํ ์ ์์ด.
๐ ๋ง์คํน์ ๋ง๋ฅ์ด ์๋๋ค
๋ก๊ทธ์ ์ํฌ๋ฆฟ์ด ๊ทธ๋๋ก ์ฐํ๋ฉด GitHub์ด ***๋ก ๊ฐ๋ ค์ฃผ์ง๋ง, base64 ์ธ์ฝ๋ฉยท๋ถ๋ถ ์ถ๋ ฅยทJSON ์ฌ์กฐ๋ฆฝ ๊ฐ์ ๋ณํ์ ๋ชป ์ก์. echo $SECRET ๋๋ฒ๊น
์ต๊ด์ ๋ฒ๋ฆฌ์.
๐ณ 8์ฅ. ์ค์ ์ํฌํ๋ก โก Docker ๋น๋ & GHCR ํธ์
์ปจํ ์ด๋ ์๋์ CD๋ ๋์ฒด๋ก "์ด๋ฏธ์ง ๋ง๋ค์ด ๋ ์ง์คํธ๋ฆฌ์ ์ฌ๋ฆฌ๊ณ , ์๋ฒ๊ฐ ๊ทธ๊ฑธ ๋น๊ฒจ๊ฐ๋ค"๋ก ์ ๋ฆฌ๋ผ.
name: Build & Push Image
on:
push:
branches: [ main ]
tags: [ 'v*.*.*' ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
docker:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Buildx ์
์
uses: docker/setup-buildx-action@v3
- name: GHCR ๋ก๊ทธ์ธ
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: ํ๊ทธ/๋ผ๋ฒจ ์๋ ์์ฑ
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,format=short
- name: ๋น๋ & ํธ์
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
ํฌ์ธํธ ํด์ค
cache-from/to: type=gha โ Docker ๋ ์ด์ด ์บ์๋ฅผ GitHub ์บ์์ ์ ์ฅ. ๋ ๋ฒ์งธ ๋น๋๋ถํฐ ์ฒด๊ฐ ์๋๊ฐ ํ ๋ฌ๋ผ์ ธ.
metadata-action โ ๋ธ๋์น๋ฉด :main, ํ๊ทธ v1.2.3์ด๋ฉด :1.2.3, ํญ์ :sha-abc1234. ํ๊ทธ ๊ท์น์ ์์ผ๋ก ์ง์ง ๋ง.
platforms ๋ฉํฐ์์น โ Apple Silicon ๊ฐ๋ฐ์ + ARM ์๋ฒ(Graviton) ๋์. ๋จ, ์๋ฎฌ๋ ์ด์
์ ๋๋ ค์ ๋น๋ ์๊ฐ์ด ๋์ด๋๋ ํ์ํ ๋๋ง.
latest ํ๊ทธ๋ง ์ฐ์ง ๋ง๊ธฐ โ ๋กค๋ฐฑ์ด ๋ถ๊ฐ๋ฅํด์ ธ. ๋ฐ๋์ ๋ถ๋ณ ํ๊ทธ(sha ๋๋ semver)๋ฅผ ๋ฐฐํฌ์ ์ฌ์ฉํด.
๐งช ๋ณด๋์ค: ์ด๋ฏธ์ง ์ทจ์ฝ์ ์ค์บ
ํธ์ ์ ์ Trivy ๊ฐ์ ์ค์บ๋๋ฅผ ๋ฃ์ผ๋ฉด CVE๊ฐ ์ฌ๊ฐ๋ ๊ธฐ์ค์ผ๋ก ๊ฑธ๋ฌ์ ธ.
severity: CRITICAL,HIGH + exit-code: 1 ์กฐํฉ์ด๋ฉด ์ํ ์ด๋ฏธ์ง๋ ๋ฐฐํฌ ์์ฒด๊ฐ ์ฐจ๋จ๋ผ.
๐ข 9์ฅ. ์ค์ ์ํฌํ๋ก โข ๋ฐฐํฌ ํจํด 4์ข
โ ์ ์ ์ฌ์ดํธ โ GitHub Pages
name: Deploy Pages
on:
push:
branches: [ main ]
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci && npm run build
- 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:
- id: deployment
uses: actions/deploy-pages@v4
โก ๋ฆฌ๋ ์ค ์๋ฒ โ SSH ๋ฐฐํฌ
- name: SSH๋ก ๋ฐฐํฌ
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
set -e
cd /srv/app
docker compose pull
docker compose up -d --remove-orphans
docker image prune -f
๐ก set -e ๊ผญ ๋ฃ์
์์ผ๋ฉด ์ค๊ฐ ๋ช ๋ น์ด ์คํจํด๋ ์คํฌ๋ฆฝํธ๊ฐ ๊ณ์ ์งํ๋ผ์ "๋ฐฐํฌ ์ฑ๊ณต"์ผ๋ก ํ์๋ ์คํจ ๋ฐฐํฌ๊ฐ ์๊ธด๋ค. ์ด๊ฒ ์ ์ผ ๋ฌด์์ด ์ ํ์ ๋ฒ๊ทธ์ผ.
โข Kubernetes โ ์ด๋ฏธ์ง ํ๊ทธ ๊ต์ฒด
- name: kubeconfig ์ค๋น
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
- name: ๋กค๋ง ์
๋ฐ์ดํธ
run: |
kubectl set image deployment/web \
web=ghcr.io/${{ github.repository }}:sha-${GITHUB_SHA::7} \
-n production
kubectl rollout status deployment/web -n production --timeout=180s
- name: ์คํจ ์ ๋กค๋ฐฑ
if: failure()
run: kubectl rollout undo deployment/web -n production
โฃ ์๋ฒ๋ฆฌ์ค / ๋งค๋์ง๋ ํ๋ซํผ
Vercel, Netlify, Cloud Run, Lambda ๋ฑ์ ๋๋ถ๋ถ ๊ณต์ Action์ด๋ CLI๊ฐ ์์ด์
npx vercel deploy --prod, gcloud run deploy, serverless deploy ํ ์ค๋ก ๋๋.
์ด ๊ฒฝ์ฐ ํต์ฌ์ ์ธ์ฆ์ OIDC๋ก ์ฒ๋ฆฌํ๋๊ฐ์ผ.
๐งฌ 10์ฅ. ์ฌ์ฌ์ฉ ์ํฌํ๋ก & Composite Action
์๋น์ค๊ฐ 10๊ฐ๋ฉด ์ํฌํ๋ก๋ 10๊ฐ. ๊ทธ๋ฐ๋ฐ ๋ด์ฉ์ ๊ฑฐ์ ๋ณต๋ถ์ด์ง.
์ด๋ ๋ณต๋ถ์ ๋ชจ๋ํํ๋ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์์ด.
Reusable Workflow (Job ๋จ์ ์ฌ์ฌ์ฉ)
# .github/workflows/reusable-node-ci.yml
name: Reusable Node CI
on:
workflow_call:
inputs:
node-version:
type: string
default: '20'
working-directory:
type: string
default: '.'
secrets:
NPM_TOKEN:
required: false
jobs:
ci:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: npm
- run: npm ci
- run: npm test
# ํธ์ถํ๋ ์ชฝ
jobs:
web:
uses: ./.github/workflows/reusable-node-ci.yml
with:
node-version: '20'
working-directory: apps/web
secrets: inherit
Composite Action (Step ๋จ์ ์ฌ์ฌ์ฉ)
# .github/actions/setup-project/action.yml
name: 'Setup Project'
description: '๋
ธ๋ ์ธํ
+ ์์กด์ฑ ์ค์น'
inputs:
node-version:
default: '20'
runs:
using: 'composite'
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: npm
- run: npm ci
shell: bash # composite์์๋ shell ํ์!
| ๊ตฌ๋ถ | Reusable Workflow | Composite Action |
|---|---|---|
| ์ฌ์ฌ์ฉ ๋จ์ | Job(๋ค) ์ ์ฒด | Step ๋ฌถ์ |
runs-on ์ง์ | ๊ฐ๋ฅ | ๋ถ๊ฐ(ํธ์ถ Job ๋ฐ๋ผ๊ฐ) |
| ์ํฌ๋ฆฟ ์ ๋ฌ | secrets: ๋ช
์/inherit | ์ ๋ ฅ๊ฐ์ผ๋ก ์ ๋ฌ |
| ์ ํฉํ ์ํฉ | ํ์ค CI ํ์ดํ๋ผ์ธ ํต์ผ | ๊ณตํต ์ค๋น ์คํ ์ถ์ฝ |
๐๏ธ ์กฐ์ง ํ์ค ๋ง๋ค๊ธฐ
org/.github-workflows ๊ฐ์ ๊ณต์ฉ ๋ ํฌ์ ์ฌ์ฌ์ฉ ์ํฌํ๋ก๋ฅผ ๋ชจ์๋๊ณ , ๊ฐ ์๋น์ค๋ uses: org/repo/.github/workflows/ci.yml@v1๋ก ํธ์ถ.
๋ณด์ ์ ์ฑ
ยทํ
์คํธ ๊ธฐ์ค์ ํ ๊ณณ์์ ์
๋ฐ์ดํธํ ์ ์์ด์ ํ๋ซํผ ํ์ด ์ ๋ง ์ข์ํ๋ ํจํด์ด์ผ.
๐๏ธ 11์ฅ. ์กฐ๊ฑด๋ถ ์คํ, ํํ์, ์ถ๋ ฅ๊ฐ
if ํํ์
- name: main์ผ ๋๋ง
if: github.ref == 'refs/heads/main'
run: echo "์ด์ ๋ธ๋์น"
- name: PR์ ํน์ ๋ผ๋ฒจ ์์ ๋๋ง
if: contains(github.event.pull_request.labels.*.name, 'deploy-preview')
run: ./preview.sh
- name: ์ด์ ์คํ
์คํจํด๋ ์คํ
if: always()
run: ./cleanup.sh
- name: ์คํจํ์ ๋๋ง ์๋ฆผ
if: failure()
run: ./notify-slack.sh
- name: ์ปค๋ฐ ๋ฉ์์ง์ skip ์์ ๋
if: "!contains(github.event.head_commit.message, '[skip deploy]')"
run: ./deploy.sh
Step/Job ์ถ๋ ฅ๊ฐ ์ฃผ๊ณ ๋ฐ๊ธฐ
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.ver.outputs.value }}
steps:
- uses: actions/checkout@v4
- id: ver
run: echo "value=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
release:
needs: prepare
runs-on: ubuntu-latest
steps:
- run: echo "๋ฐฐํฌ ๋ฒ์ ${{ needs.prepare.outputs.version }}"
โ ๏ธ ์คํฌ๋ฆฝํธ ์ธ์ ์ ์ฃผ์
run: echo "${{ github.event.issue.title }}" ์ฒ๋ผ ์ฌ์ฉ์ ์
๋ ฅ์ run์ ์ง์ ๋ณด๊ฐํ๋ฉด, ์ ๋ชฉ์ ์
ธ ๋ช
๋ น์ ์ฌ์ด ์คํ์ํฌ ์ ์์ด.
๋ฐ๋์ env๋ก ํ ๋ฒ ๋ฐ์์ ์ฐ์:
env:
TITLE: ${{ github.event.issue.title }}
run: echo "$TITLE"
์์๋๋ฉด ํธํ ์ปจํ ์คํธ
| ํํ์ | ์๋ฏธ |
|---|---|
github.sha | ์ปค๋ฐ ํด์(์ด๋ฏธ์ง ํ๊ทธ๋ก ์ต์ ) |
github.ref_name | ๋ธ๋์น/ํ๊ทธ ์ด๋ฆ |
github.event_name | ํธ๋ฆฌ๊ฑฐ ์ด๋ฒคํธ ์ข ๋ฅ |
github.actor | ์คํ์ ์ ๋ฐํ ์ฌ์ฉ์ |
runner.os | Linux / Windows / macOS |
job.status | success / failure / cancelled |
๐ฅ๏ธ 12์ฅ. Self-hosted Runner โ ์ธ์ ํ์ํ๊ฐ
GitHub ๋ฌ๋๋ ๋งค๋ฒ ๊นจ๋ํ ์ VM์ด๋ผ ์์ ํ์ง๋ง, ์ด๋ฐ ์ํฉ์ ํ๊ณ๊ฐ ์์ด.
๋ด๋ถ๋ง ์ ๊ทผ โ ์ฌ๋ด DB, VPN ๋ค์ ์คํ ์ด์ง ์๋ฒ์ ๋ถ์ด์ผ ํ ๋.
๊ณ ์ฌ์/ํน์ ํ๋์จ์ด โ GPU ํ ์คํธ, ๋์ฉ๋ ๋น๋, ๊ธด ์ปดํ์ผ.
๋๋ ์ฌ์ฉ์ผ๋ก ๋น์ฉ ์ญ์ โ ์ ์๋ง ๋ถ ์ฐ๋ฉด ์์ฒด ๋ฌ๋๊ฐ ์ ๋ ดํ ์ ์์ด.
์บ์ ๋ก์ปฌํ โ ๊ฑฐ๋ํ ์์กด์ฑ ์บ์๋ฅผ ๋์คํฌ์ ์์ฃผ์์ผ ์๋ ํ๋ณด.
๐ซ ํผ๋ธ๋ฆญ ๋ ํฌ์ self-hosted runner ์ฐ์ง ๋ง
GitHub ๊ณต์ ๊ถ๊ณ ์ฌํญ์ด์ผ. ๋๊ตฌ๋ PR๋ก ์์ ์ฝ๋๋ฅผ ๋ณด๋ผ ์ ์๊ณ , self-hosted๋ ์ํ๊ฐ ์ ์ง๋๋ฏ๋ก ๋ฌ๋ ์์ฒด๊ฐ ์นจํด๋ ์ ์์ด.
์จ์ผ ํ๋ค๋ฉด ephemeral(1ํ์ฉ) ๋ฌ๋ + ์ปจํ
์ด๋ ๊ฒฉ๋ฆฌ + ๋คํธ์ํฌ ์ต์ ๊ถํ์ด ๊ธฐ๋ณธ์ด๋ค.
โ๏ธ ํ์ค์ ๊ตฌ์ฑ
์ฟ ๋ฒ๋คํฐ์ค ํ๊ฒฝ์ด๋ฉด ARC(Actions Runner Controller)๋ก Job๋ง๋ค Pod๋ฅผ ๋์ฐ๊ณ ๋๋๋ฉด ์ญ์ ํ๋ ๋ฐฉ์์ด ๊ฐ์ฅ ๊น๋ํด. ์คํ ์ค์ผ์ผ + ๊ฒฉ๋ฆฌ๋ฅผ ๋์์ ์ป๋๋ค.
๐ธ 13์ฅ. ๋น์ฉ๊ณผ ์๋ ์ต์ ํ ์ฒดํฌ๋ฆฌ์คํธ
ํ๋ผ์ด๋น ๋ ํฌ๋ ๋ถ(minutes) ๋จ์ ๊ณผ๊ธ์ด๊ณ , OS๋ณ ๋ฐฐ์๊ฐ ์์ด.
| ๋ฌ๋ | ๋ถ๋น ๊ณผ๊ธ ๋ฐฐ์ | ๋ฉ๋ชจ |
|---|---|---|
| Linux | ร1 | ๊ธฐ๋ณธ ์ ํ์ง |
| Windows | ร2 | ๊ผญ ํ์ํ ๋๋ง |
| macOS | ร10 | iOS ๋น๋ ์ ์ฉ |
์ต์ ํ 12๊ณ๋ช
1. paths / paths-ignore๋ก ๋ฌธ์ยท์ค์ ๋ณ๊ฒฝ ์ CI ์คํต
2. concurrency: cancel-in-progress๋ก ์ค๋ณต ์คํ ์ ๊ฑฐ
3. ์บ์ ์ ๊ทน ํ์ฉ (์์กด์ฑ + Docker ๋ ์ด์ด)
4. Job ๋ณ๋ ฌํ โ ๋ฆฐํธ/ํ ์คํธ/๋น๋๋ฅผ ๋๋ ๋์ ์คํ
5. ๋ฌด๊ฑฐ์ด E2E๋ PR๋ง๋ค ๋ง๊ณ main ๋ณํฉ ํ / ์ผ๊ฐ์
6. fetch-depth: 1 (checkout ๊ธฐ๋ณธ๊ฐ)๋ก ์์ ํด๋ก ์ ์ง
7. timeout-minutes ํญ์ ์ค์
8. matrix ์กฐํฉ ์ต์ํ, fail-fast ์ ๋ต ์์์ ์ผ๋ก ์ ํ
9. ์ํฐํฉํธ retention-days ์งง๊ฒ (์คํ ๋ฆฌ์ง๋ ๊ณผ๊ธ)
10. ๋ฉํฐ์คํ ์ด์ง Dockerfile๋ก ์ด๋ฏธ์งยท๋น๋ ์๊ฐ ์ถ์
11. ํ ์คํธ ์ค๋ฉ(๋ถํ )์ผ๋ก ๊ธด ํ ์คํธ๋ฅผ ๋ณ๋ ฌ ์ฒ๋ฆฌ
12. ์กฐ์ง ๋จ์ Usage ๋ฆฌํฌํธ๋ฅผ ์ 1ํ ํ์ธ โ ๋ฒ์ธ์ ๋ ํ๋์ ์ํฌํ๋ก๋ค
๐ฏ ๋ชฉํ ์์น
PR CI๋ 10๋ถ ์ด๋ด๊ฐ ์ฌ๋ฆฌ์ ๋ง์ง๋
ธ์ ์ด์ผ.
15๋ถ ๋์ด๊ฐ๋ฉด ๊ฐ๋ฐ์๋ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ๋ค๋ฅธ ์ผ์ ์์ํ๊ณ , ์ปจํ
์คํธ ์ค์์นญ ๋น์ฉ์ด ํญ๋ฐํด. ๊ฒฐ๊ตญ "CI ๋ฌด์ ๋ฌธํ"๊ฐ ์๊ธด๋ค.
๐งฏ 14์ฅ. ๋๋ฒ๊น ์์กด ๊ฐ์ด๋
์์ฃผ ๋๋ ์๋ฌ์ ํด๊ฒฐ
| ์ฆ์ | ์์ธ | ํด๊ฒฐ |
|---|---|---|
| ํ์ผ์ด ์๋ค๊ณ ํจ | checkout ๋๋ฝ | 1๋ฒ ์คํ ์ ์ถ๊ฐ |
| ๊ถํ ๊ฑฐ๋ถ(403) | permissions ๋ถ์กฑ | ํด๋น Job์ write ๊ถํ ๋ถ์ฌ |
| ์ํฌ๋ฆฟ์ด ๋น ๊ฐ | ํฌํฌ PR / ์ด๋ฆ ์คํ / Environment ๋ฏธ์ง์ | ์ค์ฝํ ํ์ธ |
| ๋ก์ปฌ์ ๋๋๋ฐ CI๋ง ์คํจ | ๋์๋ฌธ์ ๊ตฌ๋ถ(Linux) ยท ์๊ฐ๋ ยท ๋ก์ผ์ผ | ๊ฒฝ๋ก ๋์๋ฌธ์, TZ ๊ณ ์ |
| YAML ํ์ฑ ์๋ฌ | ํญ ๋ฌธ์, ๋ค์ฌ์ฐ๊ธฐ ๋ถ์ผ์น | ์คํ์ด์ค 2์นธ ํต์ผ |
| ์ํฌํ๋ก๊ฐ ์์ ์ ๋์๊ฐ | ๊ฒฝ๋ก ์ค๋ฅ / ๊ธฐ๋ณธ ๋ธ๋์น ์๋ / ๋ฌธ๋ฒ ์ค๋ฅ | .github/workflows/ ํ์ธ |
๋๋ฒ๊ทธ ๋ฌด๊ธฐ 3์ข
โ ๋๋ฒ๊ทธ ๋ก๊ทธ ์ผ๊ธฐ โ ๋ ํฌ ์ํฌ๋ฆฟ/๋ณ์์ ACTIONS_STEP_DEBUG: true ์ถ๊ฐํ๋ฉด ์์ธ ๋ก๊ทธ๊ฐ ๋์.
โก ํ๊ฒฝ ๋คํ
- run: |
echo "=== env ==="; env | sort
echo "=== pwd ==="; pwd; ls -al
echo "=== versions ==="; node -v; npm -v
โข ๋ก์ปฌ ์ฌํ โ act ๊ฐ์ ๋๊ตฌ๋ก ์ํฌํ๋ก๋ฅผ ๋ก์ปฌ ๋์ปค์์ ๋๋ ค๋ณผ ์ ์์ด. ์๋ฒฝํ ๋์ผํ์ง ์์ง๋ง YAML ๋ฌธ๋ฒ/ํ๋ฆ ๊ฒ์ฆ์ ์ถฉ๋ถํด.
๐ Job Summary ํ์ฉ
$GITHUB_STEP_SUMMARY์ ๋งํฌ๋ค์ด์ ์ฐ๋ฉด ์คํ ๊ฒฐ๊ณผ ํ์ด์ง์ ์์๊ฒ ํ์๋ผ.
ํ
์คํธ ์ปค๋ฒ๋ฆฌ์ง, ๋ฐฐํฌ๋ ๋ฒ์ , ๋ณ๊ฒฝ ํ์ผ ๋ชฉ๋ก ๊ฐ์ ๊ฑธ ๋ฃ์ด๋๋ฉด ํ์๋ค์ด ๋ก๊ทธ๋ฅผ ํํค์น์ง ์์๋ ๋ผ.
- run: |
echo "### โ
๋ฐฐํฌ ์๋ฃ" >> $GITHUB_STEP_SUMMARY
echo "- ๋ฒ์ : \`${GITHUB_SHA::7}\`" >> $GITHUB_STEP_SUMMARY
๐ฑ 15์ฅ. ์ฑ์ํ ํ์ดํ๋ผ์ธ์ผ๋ก ๊ฐ๋ ๋ก๋๋งต
๋จ๊ณ๋ณ ์คํ ๊ณผ์
Lv.1 โ Lv.2
PR์ npm test ํ๋๋ง ๋ถ์ฌ. ๊ทธ๋ฆฌ๊ณ Branch protection์์ "status check ํ์"๋ก ๊ฑธ์ด. ์ด ์๊ฐ๋ถํฐ ๊นจ์ง ์ฝ๋๊ฐ main์ ๋ชป ๋ค์ด์จ๋ค.
Lv.2 โ Lv.3
์ด๋ฏธ์ง ๋น๋ + ๋ ์ง์คํธ๋ฆฌ ํธ์ + staging ์๋ ๋ฐฐํฌ. ์ฌ๊ธฐ์ ๋ถ๋ณ ํ๊ทธ ์ต๊ด์ ๋ค์ฌ๋๋ ๊ฒ ํต์ฌ์ด์ผ.
Lv.3 โ Lv.4
production Environment + Required reviewers. ๋กค๋ฐฑ ์คํฌ๋ฆฝํธ๋ฅผ ๋ฏธ๋ฆฌ ๋ง๋ค๊ณ ์ค์ ๋ก ํ ๋ฒ ๋๋ ค๋ด. ์ ์จ๋ณธ ๋กค๋ฐฑ์ ์๋ ๋กค๋ฐฑ์ด๋ค.
Lv.4 โ Lv.5
์๋ฌ์จยท์ง์ฐ์๊ฐ ์งํ๋ฅผ ๋ณด๊ณ ํธ๋ํฝ์ ์ ์ง ํ๋. ์๊ณ์น ์ด๊ณผ ์ ์๋ ๋กค๋ฐฑ. ์ฌ๊ธฐ๊น์ง ์ค๋ฉด ๋ฐฐํฌ๊ฐ ์ด๋ฒคํธ๊ฐ ์๋๋ผ ์ผ์์ด ๋ผ.
"๋ฐฐํฌ๊ฐ ๋ฌด์์ด ํ์ ๋ฐฐํฌ๋ฅผ ๋๋ฌผ๊ฒ ํ๊ณ ,
๋ฐฐํฌ๋ฅผ ๋๋ฌผ๊ฒ ํ๋ ํ์ ๋ฐฐํฌ๊ฐ ๋ ๋ฌด์์์ง๋ค."
์ด ์ ์ํ์ ๋๋ ์ ์ผํ ๋๊ตฌ๊ฐ ์๋ํ์ผ.
๐งพ 16์ฅ. ์ค๋ฌด ํตํฉ ์์ โ ํ๋๋ก ํฉ์ณ๋ณด๊ธฐ
name: CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
# 1) ํ์ง ๊ฒ์ฆ (๋ชจ๋ PR)
test:
runs-on: ubuntu-latest
timeout-minutes: 15
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-retries 5
ports: [ '5432:5432' ]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npm run lint
- run: npm test
env:
DATABASE_URL: postgres://postgres:test@localhost:5432/postgres
- name: ์์ฝ ๊ธฐ๋ก
if: always()
run: echo "### ํ
์คํธ ๊ฒฐ๊ณผ: ${{ job.status }}" >> $GITHUB_STEP_SUMMARY
# 2) ์ด๋ฏธ์ง ๋น๋ (main๋ง)
build:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
tag: ${{ steps.t.outputs.tag }}
steps:
- uses: actions/checkout@v4
- id: t
run: echo "tag=sha-${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ steps.t.outputs.tag }}
cache-from: type=gha
cache-to: type=gha,mode=max
# 3) staging ์๋ ๋ฐฐํฌ
deploy-staging:
needs: build
runs-on: ubuntu-latest
environment: staging
steps:
- run: echo "staging ๋ฐฐํฌ ${{ needs.build.outputs.tag }}"
# 4) production ์น์ธ ํ ๋ฐฐํฌ
deploy-prod:
needs: [ build, deploy-staging ]
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- run: echo "production ๋ฐฐํฌ ${{ needs.build.outputs.tag }}"
- name: ์คํจ ์๋ฆผ
if: failure()
run: echo "๋กค๋ฐฑ ์ ์ฐจ ์์"
๐งฉ services: ๋ธ๋ก
ํ
์คํธ์ DB/Redis๊ฐ ํ์ํ ๋ Docker ์ปจํ
์ด๋๋ฅผ ์ฌ์ด๋์นด๋ก ๋์์ค.
--health-cmd๋ก ์ค๋น ์๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฌ๊ฒ ํ๋ ๊ฒ ํต์ฌ์ด์ผ. ์ ํ๋ฉด "connection refused" ํ๋ ์ดํค ํ
์คํธ์ ์๋ฌ๋ฆฐ๋ค.
๐ 17์ฅ. ๋ณด์ ํ๋๋ ์ต์ข ์ ๊ฒ
1. ์ต์ ๊ถํ โ ์ํฌํ๋ก ์ต์๋จ permissions: contents: read ๊ธฐ๋ณธ๊ฐ, ํ์ํ Job๋ง ์น๊ฒฉ.
2. ์ก์ ๋ฒ์ ๊ณ ์ โ ์ ๋ขฐ ์กฐ์ง ์ธ์ ์ก์ ์ ์ปค๋ฐ SHA๋ก ํ.
3. ํ์ฉ ๋ชฉ๋ก โ ์กฐ์ง ์ค์ ์์ ์ฌ์ฉ ๊ฐ๋ฅํ Action์ ํ์ดํธ๋ฆฌ์คํธ๋ก ์ ํ.
4. OIDC ์ ํ โ ์ฅ๊ธฐ ํด๋ผ์ฐ๋ ํค ์ ๊ฑฐ.
5. ์ธ์ ์
์ฐจ๋จ โ ์ฌ์ฉ์ ์
๋ ฅ์ ํญ์ env ๊ฒฝ์ .
6. Environment ๋ณดํธ โ ์ด์ ๋ฐฐํฌ๋ ์น์ธ + ๋ธ๋์น ์ ํ.
7. ์ํฌ๋ฆฟ ์ค์บ๋ + Dependabot โ ์ ์ถ ํ์ง์ ์์กด์ฑ ํจ์น ์๋ํ.
8. ๋ก๊ทธ ์์ โ ๋๋ฒ๊ทธ์ฉ ์ํฌ๋ฆฟ ์ถ๋ ฅ ๊ธ์ง, ์คํ ๋ก๊ทธ ์ ๊ทผ ๊ถํ ๊ด๋ฆฌ.
9. ํฌํฌ PR ์ ์ฑ โ ์ฒซ ๊ธฐ์ฌ์ ์ํฌํ๋ก๋ ์น์ธ ํ ์คํ์ผ๋ก ์ค์ .
10. ์ํฐํฉํธ ์ ๊ฒ โ .env, ํค ํ์ผ์ด ์ํฐํฉํธ์ ์์ด์ง ์๋์ง ํ์ธ.
๐งจ ์ค์ ๋ก ์์ฃผ ํฐ์ง๋ ์ฌ๊ณ
โ ๋ก์ปฌ .env๋ฅผ ๊ทธ๋๋ก ์ปค๋ฐ โ ํ์คํ ๋ฆฌ์ ์๊ตฌ ๊ธฐ๋ก
โก ์ ๋ขฐ ์ ๋๋ ์ก์
์ด ์
๋ฐ์ดํธ๋๋ฉฐ ์ํฌ๋ฆฟ ํ์ทจ(๊ณต๊ธ๋ง ๊ณต๊ฒฉ)
โข ํผ๋ธ๋ฆญ ๋ ํฌ self-hosted ๋ฌ๋๊ฐ ํฌ๋ฆฝํ ๋ง์ด๋์ ์
์ฉ
์ธ ๊ฐ์ง ๋ชจ๋ ์ค์ ํ ์ค๋ก ๋ง์ ์ ์๋ ๊ฒ๋ค์ด์ผ.
๐ ์ํ๋ก๊ทธ: ์๋ํ๋ ๊ฒฐ๊ตญ 'ํ์ ํ๋'
์ฌ๊ธฐ๊น์ง ์์ผ๋ฉด ์ด์ ์ ๊ฑฐ์ผ.
GitHub Actions๋ YAML ๋ฌธ๋ฒ ์ธ์์ด ์๋๋ผ ํ์ ์ฝ์์ ์ฝ๋๋ก ๊ตณํ๋ ์์
์ด๋ผ๋ ๊ฑธ.
"ํ
์คํธ๋ ํต๊ณผํด์ผ ๋จธ์งํ๋ค"
"์ด์ ๋ฐฐํฌ๋ ๋ ๋ช
์ด ์น์ธํ๋ค"
"๋กค๋ฐฑ์ 3๋ถ ์์ ๊ฐ๋ฅํด์ผ ํ๋ค"
์ด๋ฐ ๊ท์น์ ์ฌ๋ ๊ธฐ์ต์ ๋งก๊ธฐ๋ฉด ๋ฌด๋์ง๊ณ , ํ์ดํ๋ผ์ธ์ ์๊ธฐ๋ฉด ์ ์ง๋๋ค.
๊ทธ๊ฒ DevOps์ ๋ณธ์ง์ด์ผ. ๋๊ตฌ๊ฐ ๋ฌธํ๋ฅผ ์ง์ผ์ฃผ๋ ๊ตฌ์กฐ๋ฅผ ๋ง๋๋ ๊ฑฐ.
๐ณ ์ค๋ ๋น์ฅ ํ ์ ์๋ ๊ฒ
โ .github/workflows/ci.yml ๋ง๋ค๊ณ ํ
์คํธ ํ ์ค ๋๋ฆฌ๊ธฐ
โก Branch protection์ status check ํ์๋ก ๊ฑธ๊ธฐ
โข concurrency์ timeout-minutes ์ถ๊ฐํ๊ธฐ
์ด ์ธ ๊ฐ๋ง ํด๋ ์ด์ ๋ณด๋ค ํจ์ฌ ๋จ๋จํ ๋ ํฌ๊ฐ ๋ผ. ์ง์ง์ผ.
ํด๋ผ์ฐ๋/DevOps ์์ญ์ ํผ์ ํ๋ฉด ์ฝ์ง ์๊ฐ์ด ์ ๋
๊ธธ์ด.
์ด๋ฏธ ๊ฒช์ด๋ณธ ์ฌ๋์ 30๋ถ ์๋ด์ด ํผ์ 3์ผ ํค๋งค๋ ๊ฑธ ๋์ฒดํ๋ ๋ถ์ผ๊ฑฐ๋ .
๊ทธ๋์ ์ฌ๋ฅ๋ท ๊ฐ์ ์ฌ๋ฅ ๊ณต์ ํ๋ซํผ์์ ์ธํ๋ผยทCI/CD ์ค๋ฌด์์๊ฒ ํ์ดํ๋ผ์ธ ๋ฆฌ๋ทฐ๋ฅผ ๋ฐ์๋ณด๋ ๊ฑด
๊ฝค ํ์ค์ ์ธ ์ ํ์ด์ผ. ๋จ์ ์ํฌํ๋ก ํ๋ ์ฝ๋ ๊ฒ๋ง์ผ๋ก๋ ๋ฐฐ์ฐ๋ ๊ฒ ๋ง์ผ๋๊น.
๊ทธ๋ฆฌ๊ณ ์ง๊ธ ์ฝ๊ณ ์๋ ์ฌ๋ฅ๋ท '์ง์์ธ์ ์ฒ'์ฒ๋ผ
์ ๋ฆฌ๋ ์ง์์ ๋จผ์ ํ๊ณ ์์ ๋๋ฉด, ์ฝ์ง์ ์ ๋๋์ด ํ์คํ ์ค์ด๋ ๋ค.
๊ธ์์ผ ์ ๋
์ ๋ง์ ํธํ ํด๊ทผํ๋ ๊ฐ๋ฐ์์
์๋ฒฝ์ ์๋ฒ ๋ค์ด๊ฐ๋ ๊ฐ๋ฐ์์ ์ฐจ์ด๋
์ค๋ ฅ์ด ์๋๋ผ ํ์ดํ๋ผ์ธ์ด์๋ค.
์, ์ด์ ํฐ๋ฏธ๋ ์ด๊ณ mkdir -p .github/workflows ๋ถํฐ ๊ฐ๋ณด์. ๐
์ฒซ ์ด๋ก ์ฒดํฌ์ ๊ทธ ์พ๊ฐ, ์ง์ ๋๊ปด๋ด์ผ ํด.
๐ Happy Automating! ๐
๊ด๋ จ ํค์๋
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.

๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.