-
Notifications
You must be signed in to change notification settings - Fork 5
๐ค Github Action๋ก CI CD ์๋ํํ๊ธฐ
์ ํฌ ๊ฐ๋ฐํ์ ์์ฑ๋ ์๋ ์๋น์ค๋ฅผ ์ฌ์ฉ์๋ค์๊ฒ ์ ๊ณตํ๊ธฐ ์ํด์ ์ด์ฌํ ๊ฐ๋ฐํ๊ณ ์์ต๋๋ค.
๊ทธ๋ ๋ค๋ฉด ์ด๋ค ๊ณผ์ ์ ํตํด์ ์ ํฌ ์๋น์ค๊ฐ ์ฌ์ฉ์๋ค์๊ฒ ๊ณต๊ฐ๋ ๊น์?
- ์ฝ๋ ํต์ผํ๊ธฐ
- ํต์ผ๋ ์ฝ๋ ์คํ์ผ์ ์ํด์ Eslint ๊ฒ์ฌ๋ฅผ ์ํํ๋ ๊ณผ์ ์ ๋๋ค.
- ๋ค ๋ช ์ ํ์์ด ํ ๋ช ์ด ๊ฐ๋ฐํ ๊ฒ์ฒ๋ผ ๋น์ทํ ์ฝ๋ ์คํ์ผ์ ๊ฐ์ง ์ ์๋๋ก ๊ฒ์ฌํ๊ณ ์์ต๋๋ค.
- ๋์ ํ์ธํ๊ธฐ
- ํ์๋ค์ด ๊ฐ๋ฐํ ์ฝ๋๊ฐ ์ ๋์ํ๋์ง Build ๊ฒ์ฌ๋ฅผ ์ํํ๋ ๊ณผ์ ์ ๋๋ค.
- ์ด์ฌํ ๊ฐ๋ฐํ ์ฝ๋๊ฐ ์๋น์ค๋ฅผ ์คํํ์ง ๋ชปํ๊ฒ ํ๋ฉด ์๋๊ฒ ์ฃ ? ์๋ก์ด ์ฝ๋๊ฐ ์๋น์ค์ ์ ์ค๋ฉฐ๋๋์ง ํ์ธํ๊ณ ์์ต๋๋ค.
- ์งํ ํ์ธํ๊ธฐ
- ์ ํฌ๊ฐ ์๋น์ค ์ฑ๋ฅ ์งํ๋ก ์ผ๊ณ ์๋ Lighthouse ๊ฒ์ฌ๋ฅผ ์ํํ๋ ๊ณผ์ ์ ๋๋ค.
- ๋ณด๊ณ ์์ ์๋ ์ฌ๋ฌ๊ฐ์ง ์ ์๋ฅผ ํตํด์ ์ผ์ ์์ค์ ์๋น์ค ํ์ง์ ์ ์งํ๊ณ ์์ต๋๋ค.
- ์๋น์ค ์ ๊ณตํ๊ธฐ
- ๊ฐ์ข ํ ์คํธ๋ฅผ ํต๊ณผํ ์ดํ ํด๋ผ์ฐ๋์ ์๋น์ค๋ฅผ ๋ฐฐํฌํ๋ ๊ณผ์ ์ ๋๋ค.
- ์ฌ์ฉ์๋ค์๊ฒ ์ง์์ ์ธ ์๋น์ค ๊ฒฝํ์ ์ ๊ณตํ๊ธฐ ์ํด์ ๋ฌด์ค๋จ ๋ฐฐํฌ๋ฅผ ํ๊ณ ์์ต๋๋ค.
์์ ๋ณธ ๊ฒ์ฒ๋ผ ๊ฐ๋ฐ๋ ์ฝ๋๊ฐ ์ฌ๋ฌ ๊ณผ์ ์ ํตํด์ ์ฌ์ฉ์๋ค์๊ฒ ์ ๊ณต๋ฉ๋๋ค.
ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ๋งค์ฃผ ๋ฐ๋ชจ ์์ฐ๊ณผ ์ฌ์ฉ์ ํผ๋๋ฐฑ์ ๋ฐ๊ธฐ ์ํด์ ์ผ์ฃผ์ผ์๋ ์ฌ๋ฌ ๋ฒ ๋ฐฐํฌํด์ผํ์ต๋๋ค.
ํ์ง๋ง ๋ฐฐํฌํ ๋๋ง๋ค ์์ ๊ณผ์ ์ ์๋์ผ๋ก ๋ฐ๋ณตํ๊ธฐ์๋ ๋๋ฌด ๋ถ๋ด๋๋ ์์ ์ด์์ต๋๋ค.
๊ทธ๋์ ๋ฐฐํฌ ๊ณผ์ ์ GitHub Action ์๋ํ์ ์์ํ๊ณ , ์ ํฌ๋ ๊ฐ๋ฐ์ ์ง์คํ ์ ์๋ ํ๊ฒฝ์ ๋ง๋ค๊ธฐ๋ก ํ์ต๋๋ค.
์ด์ ์ด๋ค์์ผ๋ก GitHub Action์ ํ์ฉํด์ CI/CD๋ฅผ ์๋ํํ๋์ง ์์๋ณด๊ฒ ์ต๋๋ค!
ํ๋ก ํธ์๋์ CI๋ main ๋ธ๋์น์ develop ๋ธ๋์น์ PR์ ์ฌ๋ฆฐ ์ํ์์ ์ํ๋ฉ๋๋ค.
name: Frontend CI
on:
pull_request:
branches: [main, develop]
paths:
- 'frontend/**'
์ดํ Eslint ๊ฒ์ฌ, Lighthouse ๊ฒ์ฌ๋ฅผ ์ํํฉ๋๋ค.
jobs:
lint:
...
lighthouse:
...
GitHub Action์์ job์ ๋ ๋ฆฝ์ ์ผ๋ก ์คํํ๋ ๋จ์์ ๋๋ค. job์ ๋ค์ด์๋ ์์ ๋ค์ ๋ณ๋ ฌ์ ์ผ๋ก ์คํ๋ฉ๋๋ค. ๋ ๊ณผ์ ์ ๋ณ๋ ฌ ์ฒ๋ฆฌํด์ CI ์๊ฐ์ ๋จ์ถํ๊ณ ์ํ์ต๋๋ค. ์ด์ ๊ณผ์ ์ ํ๋์ฉ ๋ณด๊ฒ ์ต๋๋ค.
lint:
name: Lint CI
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Cache Dependencies
uses: actions/cache@v3
id: frontend-cache
with:
path: frontend/node_modules
key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.OS }}-build-
${{ runner.OS }}-
- if: ${{ steps.frontend-cache.outputs.cache-hit != 'true' }}
name: Install Dependencies
run: npm install
working-directory: frontend
- name: Check Lint
run: npm run lint
env:
CI: true
working-directory: frontend
์ฐ์ ๋ฆฐํธ ๊ฒ์ฌ๋ฅผ ์ํํ๋ ๋ถ๋ถ์ ๋๋ค.
๊ฐ์ฅ ์ค์ํ ๋ถ๋ถ์ ๋ฆฐํธ ๊ฒ์ฌ๋ฅผ ์ํํ๋ Check Lint ์คํ ๊ฐ์๋ณด์ ๋๋ค.
๊ทธ๋ฐ๋ฐ ์์ ์คํ ๋ค์ ๋ฌด์์ผ๊น์?
Checkout ์คํ ์์๋ ์ฝ๋ ์ ์ฅ์์ ์๋ ์ฝ๋๋ฅผ CI ๊ณผ์ ์ ์ํํ๋ ์๋ฒ๋ก ๋ด๋ ค๋ฐ์ต๋๋ค.
์ดํ Install Dependencies ์คํ ์์ ํ๋ก ํธ์๋์์ ์ฌ์ฉํ๋ ๋ชจ๋์ ์ค์นํฉ๋๋ค. ์ด๋ ๋ชจ๋๋ค์ ์ค์นํ๋๋ฐ ์ค๋ ๊ฑธ๋ฆฌ๊ธฐ๋ ํ๊ณ , ์์ฃผ ๋ณ๊ฒฝ๋์ง ์๊ธฐ ๋๋ฌธ์ ์บ์ฑํด๋๊ธฐ ์ ์ ํด๋ณด์ ๋๋ค.
๊ทธ๋์ ์ค์น ๊ณผ์ ๋ฐ๋ก ์ง์ ์คํ ์ธ Cache Dependencies์์ ๋ชจ๋ ์บ์ฑ์ ์ํํฉ๋๋ค. ๋ชจ๋์ ์ค์นํ๋ฉด ๋ณ๊ฒฝ๋๋ package-lock.json ํ์ผ ๋ด์ฉ์ ํค๋ก ๋ง๋ค์ด์ ํ์ผ์ด ๋ณ๊ฒฝ๋์์ ๋๋ง ๋ชจ๋์ ์ค์นํ๊ณ , ๋ณ๊ฒฝ๋์ง ์์๋ค๋ฉด GitHub์ ์ ์ฅํด๋์ ๋ชจ๋ ์ค์น ํ์ผ์ ๊บผ๋ด๋ค ์๋๋ค.
lighthouse:
name: Lighthouse CI
runs-on: ubuntu-latest
steps:
... (์๋ต)
- name: Run Lighthouse CI
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
run: |
npm install -g @lhci/cli
lhci autorun
working-directory: frontend
- name: Format Lighthouse Score
id: format_lighthouse_score
uses: actions/github-script@v3
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const fs = require('fs');
const results = JSON.parse(fs.readFileSync('frontend/lighthouse_reports/manifest.json'));
const summaryTotal = {};
results.forEach((result) => {
const { summary } = result;
const formatResult = (res) => Math.round(res * 100);
Object.keys(summary).forEach((key) => {
if (key in summaryTotal) summaryTotal[key] += formatResult(summary[key]);
else summaryTotal[key] = formatResult(summary[key]);
});
});
Object.keys(summaryTotal).forEach((key) => {
summaryTotal[key] = Math.round(summaryTotal[key] / results.length);
});
const score = (res) => (res >= 90 ? '๐ข' : res >= 50 ? '๐ ' : '๐ด');
const comment = [
`โก๏ธ Lighthouse report!`,
`| Category | Score |`,
`| --- | --- |`,
`| ${score(summaryTotal.performance)} Performance | ${summaryTotal.performance} |`,
`| ${score(summaryTotal.accessibility)} Accessibility | ${summaryTotal.accessibility} |`,
`| ${score(summaryTotal['best-practices'])} Best Practices | ${summaryTotal['best-practices']} |`,
`| ${score(summaryTotal.seo)} SEO | ${summaryTotal.seo} |`,
`| ${score(summaryTotal.pwa)} PWA | ${summaryTotal.pwa} |`,
].join('\n');
core.setOutput('comment', comment)
- name: Comment Lighthouse Report
uses: unsplash/comment-on-pr@v1.3.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
msg: ${{ steps.format_lighthouse_score.outputs.comment}}
๋ค์์ Lighthouse ๊ฒ์ฌ๋ฅผ ์ํํ๋ ๊ณผ์ ์ ๋๋ค.
Run Lighthouse CI ์คํ ์์ Lighthouse ๊ฒ์ฌ๋ฅผ ์ํํฉ๋๋ค.
// .lighthouserc.js
module.exports = {
ci: {
collect: {
startServerCommand: 'npm run start',
url: ['http://localhost:3000'],
numberOfRuns: 3,
},
upload: {
target: 'filesystem',
outputDir: './lighthouse_reports',
reportFilenamePattern: '%%PATHNAME%%-%%DATETIME%%-report.%%EXTENSION%%',
},
},
};
์์ ๊ฐ์ด ์ฌ์ ์ ์ ์ํ๋ ๋ฐฉ๋ฒ๋๋ก ๊ฒ์ฌ๋ฅผ ์ํํฉ๋๋ค. ํ ๋ฒ๋ง ๊ฒ์ฌ๋ฅผ ์ํํ์ง ์๊ณ , numberOfRuns
์ ๋ฐ๋ผ ์ธ ๋ฒ์ ๊ฒ์ฌ๋ฅผ ์ํํฉ๋๋ค.
๊ฒ์ฌ๋ฅผ ์ํํ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ ์์ฝ ๋ณด๊ณ ์ ํ์ผ์ ํ์ธํ ์ ์์ต๋๋ค.
// lighthouse_reports/manifest.json
[
{
"url": "http://localhost:3000/",
"isRepresentativeRun": false,
"htmlPath": "/Users/dohyeon/Development/knoticle/frontend/lighthouse_reports/_-2022_11_26_12_40_53-report.html",
"jsonPath": "/Users/dohyeon/Development/knoticle/frontend/lighthouse_reports/_-2022_11_26_12_40_53-report.json",
"summary": {
"performance": 0.98,
"accessibility": 0.94,
"best-practices": 0.92,
"seo": 0.8,
"pwa": 0.3
}
},
...
]
์ด๋ฐ ๋ณด๊ณ ์๋ฅผ PR ์ฝ๋ฉํธ๋ก ํ์ธํ ์ ์๋ค๋ฉด, ์์ ๊ณผ์ ๋ง๋ค ์งํ๋ก ์ผ์ ์ ์๊ฒ ์ฃ ?
๋ค์ Format Lighthouse Score ์คํ ๊ณผ Comment Lighthouse Report ์คํ ์์๋ ๋ณด๊ณ ์๋ฅผ ํฌ๋งคํ ํด์ ์ฝ๋ฉํธ๋ก ๋จ๊ฒจ์ฃผ๋ ์์ ์ ์ํํฉ๋๋ค.
๋ฐฑ์๋์ CI๋ ํ๋ก ํธ์๋์ ๋ง์ฐฌ๊ฐ์ง๋ก main ๋ธ๋์น์ develop ๋ธ๋์น์ PR์ ์ฌ๋ฆฐ ์ํ์์ ์ํ๋ฉ๋๋ค.
name: Backend CI
on:
pull_request:
branches: [main, develop]
paths:
- 'backend/**'
jobs:
lint:
...
build:
...
๋์ผํ ๊ณผ์ ์ ์ํํ๊ธฐ ๋๋ฌธ์ ์ถ๊ฐ๋ก ์ค๋ช ๋๋ฆฌ์ง ์๊ฒ ์ต๋๋ค.
ํ๋ก ํธ์๋์ ๋ฐฑ์๋ ๋ชจ๋ CI ๊ณผ์ ์ ๊ฑฐ์น๊ณ ๋๋ฉด CD ๊ณผ์ ์ ์ํํฉ๋๋ค.
nCloud ์๋น์ค์ ์ ์ํด์ ๋น๋ํ๊ณ , PM2 reload๋ฅผ ํตํด์ ๋ฌด์ค๋จ ์คํํ๋ ๊ณผ์ ์ ๋๋ค.
ํ๋ก ํธ์๋์ ๋ฐฑ์๋๊ฐ ๋์ผํ CD ๊ณผ์ ์ ์ํํ๊ธฐ ๋๋ฌธ์ ํ๋ก ํธ์๋ ๊ธฐ์ค์ผ๋ก๋ง ์ค๋ช ๋๋ฆฌ๊ฒ ์ต๋๋ค.
name: Frontend CD
on:
push:
branches: [main]
paths:
- 'frontend/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Generate Environment Variables
run: |
echo "$FRONTEND_ENV" >> .env.production
env:
CI: false
FRONTEND_ENV: '${{ secrets.FRONTEND_ENV }}'
- name: Transfer Environment Variables with SCP
uses: appleboy/scp-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
password: ${{ secrets.SSH_PASSWORD }}
port: ${{ secrets.SSH_PORT }}
source: '.env.production'
target: 'knoticle/frontend'
- name: Deploy
uses: appleboy/ssh-action@v0.1.4
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
password: ${{ secrets.SSH_PASSWORD }}
port: ${{ secrets.SSH_PORT }}
script: ${{ secrets.SSH_FRONTEND_SCRIPT }}
Generate Environment Variables ์คํ ์์๋ GitHub Secrets์ ์ ์ฅ๋ ํ๊ฒฝ ๋ณ์๋ฅผ ์์ฑํ๋ ๊ณผ์ ์ ๋๋ค.
์ดํ Transfer Environment Variables with SCP ์คํ ์์ SCP(Secure Copy)๋ฅผ ํตํด nCloud ์๋ฒ์ ํ๊ฒฝ ๋ณ์๋ฅผ ๋ณด๋ด๊ฒ ๋ฉ๋๋ค. ํ๊ฒฝ ๋ณ์๋ ์๊ฒฉ ์ ์ฅ์์ ์ ์ฅ๋์ง ์๊ธฐ ๋๋ฌธ์ ์ด๋ฐ์์ผ๋ก ๊ด๋ฆฌํ๊ณ ์์ต๋๋ค.
๋ง์ง๋ง์ผ๋ก Deploy ์คํ ์์ nCloud์ ์ ์ํด์ ์ฝ๋๋ฅผ ๋น๋ํ๊ณ ๋ฌด์ค๋จ ์คํํ๋ ๊ณผ์ ์ ๊ฑฐ์นฉ๋๋ค.
- Lighthouse ์งํ ์ธ์ ๋ค๋ฅธ ์งํ๋ ๋ฐ์๋ณด๋ฉด ์ข์ ๊ฒ ๊ฐ์ต๋๋ค.
- ๊ตฌ๊ธ Page Speed Insight๋ ์น ํ์ด์ง์์ ์ฌ์ฉ์์ ํ๊ฒฝ ํ์ง์ ๋ํ ํ๊ฐ ์ ์๋ฅผ ์ ๊ณตํฉ๋๋ค. PR ์ฝ๋ฉํธ์ ํด๋น ๋ฆฌํฌํธ๋ก ํ์ธํ ์ ์๊ฒ ์ถ๊ฐํ ์์ ์ ๋๋ค.
- CI ๊ณผ์ ์์ ํ
์คํธ ์ฝ๋๋ฅผ ์คํํ๋ ๊ณผ์ ๋ ํ์ํด๋ณด์
๋๋ค.
- ํ ์คํธ ์ฝ๋๊ฐ ์์ฑ๋๋ฉด ์ด ๊ณผ์ ๋ ์ถ๊ฐํ ์์ ์ ๋๋ค.