GitHub Actions CI/CD ๊ตฌ์ถ ์๋ฒฝ ๊ฐ์ด๋: React ์ฑ AWS S3 + CloudFront ์๋ ๋ฐฐํฌ
"ํ์ฅ๋, ์ ๊ฐ ์๋์ผ๋ก ๋น๋ํด์ ์ฌ๋ฆฌ๋ค๊ฐ ์ค์๋ก ์๋ ์ฝ๋๋ฅผ ๋ฎ์ด์์ ์ต๋๋ค..."
์๋ ํ์ธ์! ํญ์ ๋ ํจ์จ์ ์ผ๋ก ์ผํ ๊ถ๋ฆฌ๋ฅผ ํ๋ ์์ด๋์ด ๋ฑ ํฌ, ํ๋ก ํธ์๋ ๊ฐ๋ฐ์์ ๋๋ค.
์ฒ์ ํ์ ํ๋ก์ ํธ๋ฅผ ๋งก์์ ๋์ ๊ธฐ์ต์ด ๋ฉ๋๋ค. ๊ธฐ๋ฅ ๊ฐ๋ฐ์ ๋ง์น๋ฉด ๋ก์ปฌ์์ npm run build๋ฅผ ์คํํ๊ณ , AWS S3 ์ฝ์์ ๋ก๊ทธ์ธํ ๋ค, ๊ธฐ์กด ํ์ผ์ ์ญ์ ํ๊ณ ์ ๋น๋ ํ์ผ์ ๋๋๊ทธ ์ค ๋๋กญ์ผ๋ก ์ฌ๋ฆฌ๋ ์ผ๋ จ์ ๊ณผ์ ์ ํ๋ฃจ์๋ ์๋ ๋ฒ์ฉ ๋ฐ๋ณตํ์ต๋๋ค. ๊ทธ๋ฌ๋ค ํ ๋ฒ์ ํ์ผ์ ๋ ์ฌ๋ฆฌ๊ฑฐ๋ ์ค์๋ก CloudFront ์บ์ ๋ฌดํจํ๋ฅผ ๋นผ๋จน์ด์ ์๋น์ค ํ๋ฉด์ด ๊นจ์ง๋ ๋ํ ์ฌ๊ณ ๋ฅผ ์น๊ธฐ๋ ํ์ฃ .
์ด๋ฐ ๋จ์ํ๊ณ ๋ฐ๋ณต์ ์ธ, ๊ทธ๋ฆฌ๊ณ ์ฌ๋์ ์ค์๊ฐ ๊ฐ์ ํ๊ธฐ ์ฌ์ด ๋ฐฐํฌ ๊ณผ์ ์ 100% ์๋ํํด ์ฃผ๋ ๋ง๋ฒ ๊ฐ์ ๊ธฐ์ ์ด ๋ฐ๋ก CI/CD(์ง์์ ํตํฉ/์ง์์ ๋ฐฐํฌ)์ ๋๋ค. ์ค๋์ ์ฃผ๋์ด ๊ฐ๋ฐ์๋ ์ฝ๊ฒ ๋ฐ๋ผ ํ ์ ์๋๋ก GitHub Actions๋ฅผ ํ์ฉํด React ์ฑ์ AWS S3์ CloudFront์ ์๋์ผ๋ก ๋ฐฐํฌํ๋ ํ์ดํ๋ผ์ธ ๊ตฌ์ถ๋ฒ์ ํ๋๋ถํฐ ์ด๊น์ง ์๊ธฐ ์ฝ๊ฒ ์ค๋ช ํด ๋๋ฆฌ๊ฒ ์ต๋๋ค.
1. CI/CD์ GitHub Actions: ์ง๊ด์ ์ธ ๋น์ ๋ก ์ดํดํ๊ธฐ
'ํ์ดํ๋ผ์ธ', '์ํฌํ๋ก์ฐ', 'CI/CD' ๊ฐ์ ๋ฏ์ ๊ธฐ์ ์ฉ์ด๋ค์ด ๋์ค๋ฉด ์ง๋ ๊ฒ๋จน๊ธฐ ์ฝ์ต๋๋ค. ์ด๋ฅผ ์ฐ๋ฆฌ๊ฐ ์ตํ ์๋ '์ ์๋ ๋ก๋ด ๊ณต์ฅ'์ ๋น์ ํด ๋ณด๊ฒ ์ต๋๋ค.
- ์๋ ๋ฐฐํฌ (๊ณผ๊ฑฐ์ ๋):
- ์์ ๋ฒ๊ฑฐ์ง ์ฌ์ฅ๋์ ๋๋ค. ์ฌ๋ฃ๋ฅผ ์ฐ๊ณ (์ฝ๋ฉ), ํจํฐ๋ฅผ ๊ตฝ๊ณ (๋น๋), ํฌ์ฅํด์(์์ถ), ์ง์ ์คํ ๋ฐ์ด๋ฅผ ํ๊ณ ์๋ ์ง(AWS S3)๊น์ง ๋ฐฐ๋ฌํฉ๋๋ค. ์๋์ด ๋ง์์ง๋ฉด ์ค์๊ฐ ์ฆ์์ง๊ณ ์ฌ์ฅ๋์ ์ฐ๋ฌ์ง๋๋ค.
- CI/CD ํ์ดํ๋ผ์ธ (GitHub Actions):
- ์ต์ฒจ๋จ ๋ก๋ด ์ปจ๋ฒ ์ด์ด ๋ฒจํธ์ ๋๋ค. ๊ฐ๋ฐ์๋ ๋ ์ํผ(์ฝ๋)๋ง ์์ฑํด์ main ๋ธ๋์น๋ผ๋ ์์์ ํญ ๋์ ธ ๋ฃ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค(git push). ๊ทธ๋ฌ๋ฉด ๋๊ธฐํ๊ณ ์๋ ๋ก๋ด(GitHub Actions)์ด ์์์ ์ฝ๋๋ฅผ ๊ฒ์ฌํ๊ณ , ๋น๋ํ๊ณ , ํฌ์ฅํด์ AWS ์๋ฒ๊น์ง 0.1์ด ๋ง์ ๋ก์ผ ๋ฐฐ์ก์ ์๋ฃํฉ๋๋ค.
๐ ์๋ ๋ฐฐํฌ vs GitHub Actions ์๋ ๋ฐฐํฌ ๋น๊ต
| ๋น๊ต ํญ๋ชฉ | ๊ธฐ์กด ์๋ ๋ฐฐํฌ ๋ฐฉ์ | GitHub Actions ์๋ ๋ฐฐํฌ (CI/CD) |
| ์์ ์๊ฐ | ๊ฐ๋ฐ์๊ฐ ์ง์ 5~10๋ถ๊ฐ ํ๋ฉด์ ๋ณด๋ฉฐ ๋๊ธฐํด์ผ ํจ | git push ํ ๋ฒ์ด๋ฉด ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์๋ ์คํ (0๋ถ ์์) |
| ์์ ์ฑ (ํด๋จผ ์๋ฌ) | ๋น๋ ๋ช ๋ น์ด ๋๋ฝ, ํด๋ ์ ๋ก๋ ์ค์ ๋ฑ ์ํ ์กด์ฌ | ์ฌ์ ์ ์์ฑ๋ ์คํฌ๋ฆฝํธ(YAML)๋๋ก๋ง 100% ๋์ผํ๊ฒ ๋์ |
| ๋น์ฉ ๋ฐ ์ ๊ทผ์ฑ | ๋ณ๋ ์ธํ๋ผ ์ง์์ด ํ์ | GitHub์์ ๋ฌด๋ฃ๋ก ์ ๊ณตํ๋ฉฐ ์ค์ ์ด ๋งค์ฐ ๊ฐํธํจ |
| ํ์ ํจ์จ | ํ ์ฌ๋์ด ๋ฐฐํฌ ๊ถํ์ ๋ ์ ํ์ฌ ๋ณ๋ชฉ ํ์ ๋ฐ์ | ํ์ ๋๊ตฌ๋ ๋ธ๋์น์ ๋ณํฉ(Merge)๋ง ํ๋ฉด ์๋์ผ๋ก ๋ฐฐํฌ๋จ |
2. CI/CD ํ์ดํ๋ผ์ธ ๊ตฌ์ถ 1๋จ๊ณ: AWS ๋ณด์ ์ค์ (๊ฐ์ฅ ์ค์!)
๋ก๋ด(GitHub Actions)์ด ๋ด AWS ์ฐฝ๊ณ (S3)์ ๋ฌผ๊ฑด์ ๋ฃ์ผ๋ ค๋ฉด ์ฐฝ๊ณ ์ด์ ๊ฐ ํ์ํฉ๋๋ค. ๋ณด์์ ๋ด ๋ง์คํฐ ๊ณ์ ์ ์ฃผ๋ฉด ์ ๋๊ณ , "์ค์ง S3 ์ ๋ก๋์ CloudFront ๊ฐฑ์ ๋ง ํ ์ ์๋ ์๋ฐ์์ฉ ์ด์ (IAM)"๋ฅผ ๋ฐ๋ก ๋ง๋ค์ด์ฃผ์ด์ผ ํฉ๋๋ค.
- AWS ์ฝ์์์ IAM (Identity and Access Management) ์๋น์ค๋ก ์ด๋ํฉ๋๋ค.
- ์๋ก์ด ์ฌ์ฉ์(User)๋ฅผ ์์ฑํ๊ณ ์ด๋ฆ์ github-actions-deploy-user ๋ฑ์ผ๋ก ์ง์ต๋๋ค.
- ๊ถํ ์ค์ ์์ ๋ค์ ๋ ๊ฐ์ง ์ ์ฑ
(Policy)์ ์ฐ๊ฒฐํฉ๋๋ค.
- AmazonS3FullAccess (๋๋ ํด๋น ๋ฒํท์๋ง ์ ๊ทผํ๋ ์ปค์คํ ๊ถํ)
- CloudFrontFullAccess (์บ์ ๋ฌดํจํ ์ฒ๋ฆฌ๋ฅผ ์ํด ํ์)
- ์ฌ์ฉ์ ์์ฑ ํ ๋ณด์ ์๊ฒฉ ์ฆ๋ช ํญ์์ ์ก์ธ์ค ํค(Access Key) ๋ง๋ค๊ธฐ๋ฅผ ํด๋ฆญํฉ๋๋ค.
- ๋ฐ๊ธ๋ Access Key ID์ Secret Access Key๋ฅผ ๋ณต์ฌํด ๋ก๋๋ค. (์ด ์ฐฝ์ ๋ซ์ผ๋ฉด ๋ค์ ๋ณผ ์ ์์ผ๋ ์ฃผ์ํ์ธ์!)
3. CI/CD ํ์ดํ๋ผ์ธ ๊ตฌ์ถ 2๋จ๊ณ: GitHub Secrets์ ์ด์ ์จ๊ธฐ๊ธฐ
๋ฐ๊ธ๋ฐ์ AWS ์ด์ ๋ฅผ ์์ค ์ฝ๋์ ๊ทธ๋๋ก ์ ์ด ์ฌ๋ฆฌ๋ฉด ์ ์ธ๊ณ ํด์ปค๋ค์ ๋จน์๊ฐ์ด ๋ฉ๋๋ค. ์์ ํ ๊ธ๊ณ ์ธ GitHub Secrets์ ์ด์ ๋ฅผ ๋ณด๊ดํด์ผ ํฉ๋๋ค.
- ๋ฐฐํฌํ React ํ๋ก์ ํธ์ GitHub ๋ ํฌ์งํ ๋ฆฌ๋ก ์ด๋ํฉ๋๋ค.
- Settings -> Secrets and variables -> Actions ๋ฉ๋ด๋ฅผ ํด๋ฆญํฉ๋๋ค.
- New repository secret ๋ฒํผ์ ๋๋ฌ ์๊น ๋ฉ๋ชจํด ๋ AWS ์ ๋ณด๋ค์ ๋ฑ๋กํฉ๋๋ค.
- AWS_ACCESS_KEY_ID: ๋ณต์ฌํ ์ก์ธ์ค ํค ID
- AWS_SECRET_ACCESS_KEY: ๋ณต์ฌํ ์ํฌ๋ฆฟ ํค
- AWS_REGION: ์์ธ ๋ฆฌ์ ์ธ ๊ฒฝ์ฐ ap-northeast-2
์ด๋ ๊ฒ ๊ธ๊ณ ์ ๋ฃ์ด๋๋ฉด, ๋์ค์ ์์ฑํ ์คํฌ๋ฆฝํธ์์ ${{ secrets.AWS_ACCESS_KEY_ID }}๋ผ๋ ์ด๋ฆํ๋ก ์์ ํ๊ฒ ๊บผ๋ด ์ธ ์ ์์ต๋๋ค.
4. ์ค์ ! GitHub Actions YAML ์คํฌ๋ฆฝํธ ์์ฑํ๊ธฐ
์ด์ ๋ก๋ด์๊ฒ ์ง์ํ ์์ ์ง์์(Workflow)๋ฅผ ์์ฑํ ์ฐจ๋ก์ ๋๋ค. React ํ๋ก์ ํธ ์ต์๋จ ๊ฒฝ๋ก์ .github/workflows/deploy.yml ํ์ผ์ ๋ง๋ค๊ณ ์๋ ์ฝ๋๋ฅผ ์์ฑํฉ๋๋ค. ์ฃผ์์ ๊ผผ๊ผผํ ์ฝ์ด๋ณด์ธ์!
# ์ํฌํ๋ก์ฐ์ ์ด๋ฆ์
๋๋ค. GitHub Actions ํญ์์ ์ด ์ด๋ฆ์ผ๋ก ํ์๋ฉ๋๋ค.
name: React App CI/CD to AWS S3 & CloudFront
# ์ด๋ค ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ ๋ ์ด ๊ณต์ฅ์ ๊ฐ๋ํ ์ง ๊ฒฐ์ ํฉ๋๋ค.
on:
push:
branches:
- main # main ๋ธ๋์น์ push๋ merge๊ฐ ์ผ์ด๋ ๋๋ง ์คํ๋ฉ๋๋ค.
# ๋ก๋ด์ด ์ํํ ์์
(jobs) ๋ชฉ๋ก์
๋๋ค.
jobs:
deploy:
runs-on: ubuntu-latest # ์ต์ ์ฐ๋ถํฌ ๋ฆฌ๋
์ค ํ๊ฒฝ์ ๊ฐ์ ์ปดํจํฐ๋ฅผ ํ ๋ ๋น๋ฆฝ๋๋ค.
steps:
# 1. ๋ด GitHub ์ ์ฅ์์ ์ฝ๋๋ฅผ ๊ฐ์ ์ปดํจํฐ๋ก ๊ฐ์ ธ์ต๋๋ค. (Checkout)
- name: 1. ์ ์ฅ์ ์ฝ๋ ๊ฐ์ ธ์ค๊ธฐ
uses: actions/checkout@v3
# 2. React๋ฅผ ๋น๋ํ๊ธฐ ์ํด Node.js ํ๊ฒฝ์ ์ธํ
ํฉ๋๋ค.
- name: 2. Node.js ํ๊ฒฝ ์ค์
uses: actions/setup-node@v3
with:
node-version: '18' # ํ๋ก์ ํธ์ ๋ง๋ ๋
ธ๋ ๋ฒ์ ์ฌ์ฉ
# 3. ํจํค์ง(๋ผ์ด๋ธ๋ฌ๋ฆฌ)๋ค์ ์ค์นํฉ๋๋ค.
- name: 3. ์์กด์ฑ ํจํค์ง ์ค์น (npm ci)
run: npm ci # npm install๋ณด๋ค ๋น ๋ฅด๊ณ ์ ํํ๊ฒ package-lock.json ๊ธฐ์ค์ผ๋ก ์ค์นํฉ๋๋ค.
# 4. React ์์ค์ฝ๋๋ฅผ ๋ธ๋ผ์ฐ์ ๊ฐ ์ฝ์ ์ ์๋ ์ ์ ํ์ผ(HTML/CSS/JS)๋ก ์์ถ(๋น๋)ํฉ๋๋ค.
- name: 4. ํ๋ก์ ํธ ๋น๋ํ๊ธฐ
run: npm run build
# 5. AWS ์ด์ (์ธ์ฆ ์ ๋ณด)๋ฅผ ๊ฐ์ ์ปดํจํฐ์ ์ธํ
ํฉ๋๋ค.
- name: 5. AWS ์๊ฒฉ ์ฆ๋ช
๊ตฌ์ฑ
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
# 6. ๋น๋๋ ๊ฒฐ๊ณผ๋ฌผ(build ํด๋)์ AWS S3 ๋ฒํท์ผ๋ก ์
๋ก๋(๋๊ธฐํ)ํฉ๋๋ค.
- name: 6. S3์ ๋น๋ ํ์ผ ์
๋ก๋ (Sync)
run: aws s3 sync ./build s3://my-react-bucket-name --delete
# --delete ์ต์
: S3์๋ ์์ง๋ง ์ ๋น๋ ํด๋์๋ ์๋ ์๋ ํ์ผ๋ค์ ์์์ ์ญ์ ํด ์ค๋๋ค.
# 7. CloudFront์ ์บ์๋ฅผ ์น ์ง์์ค๋๋ค. (์ด๊ฑธ ์ ํ๋ฉด ์ฌ์ฉ์๋ ์๋ ํ๋ฉด์ ๋ณด๊ฒ ๋ฉ๋๋ค!)
- name: 7. CloudFront ์บ์ ๋ฌดํจํ (Invalidation)
run: aws cloudfront create-invalidation --distribution-id E1A2B3C4D5E6F7 --paths "/*"
# E1A2B3C4D5E6F7 ๋ถ๋ถ์ ๋ณธ์ธ์ CloudFront ๋ฐฐํฌ ID๋ก ๋ณ๊ฒฝํด์ผ ํฉ๋๋ค.
์ด ํ์ผ์ ์ปค๋ฐํ๊ณ main ๋ธ๋์น์ ํธ์(Push)ํ๋ ์๊ฐ, GitHub ๋ ํฌ์งํ ๋ฆฌ์ Actions ํญ์์ ๋ ธ๋์ ๋ถ์ด ๋ค์ด์ค๋ฉฐ ํฑ๋๋ฐํด๊ฐ ์ ๋๊ฒ ๋์๊ฐ๋ ์ง๋ฆฟํ ๊ด๊ฒฝ์ ๋ชฉ๊ฒฉํ ์ ์์ต๋๋ค!
5. ์ค๋ฌด ๊ฒฝํ์์ ์ป์ ํธ๋ฌ๋ธ์ํ ๋ฐ ๊ฟํ (E-E-A-T)
ํ์ ์์ CI/CD๋ฅผ ๊ตฌ์ฑํ ๋ ์ฒ์์ ๋ฌด์กฐ๊ฑด ํ๋ ๋ฒ์ ์คํจ๋ฅผ ๊ฒช๊ฒ ๋ฉ๋๋ค. ์ ๊ฐ ์ง์ ๊ฒช์๋ ์ํ์ฐฉ์ค์ ํด๊ฒฐ ํ์ ๊ณต์ ํฉ๋๋ค.
- ์๋ฌ ์ฌ๋ก 1: CloudFront ์บ์ ๋ฌดํจํ(Invalidation)๋ฅผ ๋นผ๋จน์์ ๋
- ์ด๋ณด ์์ S3 ์ ๋ก๋๊น์ง๋ง ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํ๊ณ "๋ฐฐํฌ ๋!"์ ์ธ์ณค์ต๋๋ค. ๊ทธ๋ฐ๋ฐ ๊ธฐํ์๋ถ์ด "๋ฒํผ ์๊น์ด ๊ทธ๋๋ก์ธ๋ฐ์?"๋ผ๊ณ ํ์๋๊ตฐ์. CloudFront๋ ํ ๋ฒ ๊ฐ์ ธ๊ฐ S3 ํ์ผ์ ์ฃ์ง ๋ก์ผ์ด์ ์ ๊ฝ ์ฅ๊ณ (์บ์ฑ) ๋์์ฃผ์ง ์์ต๋๋ค. ์ํฌํ๋ก์ฐ 7๋จ๊ณ์ create-invalidation ๋ช ๋ น์ด๋ ์ ํ์ด ์๋ ํ์์ ๋๋ค. "/*" ๊ฒฝ๋ก๋ฅผ ์ง์ ํด ๋ชจ๋ ์บ์๋ฅผ ๊ฐ์ ๋ก ๋น์์ค์ผ ์ ์ฝ๋๊ฐ ์ฆ๊ฐ ๋ฐ์๋ฉ๋๋ค.
- ํ: npm install ๋์ npm ci๋ฅผ ์ฌ์ฉํ์ธ์
- ์ํฌํ๋ก์ฐ ์คํฌ๋ฆฝํธ๋ฅผ ๋ณด๋ฉด ํจํค์ง ์ค์น ์ npm install ๋์ npm ci๋ฅผ ์ ์ด๋์์ต๋๋ค. npm install์ ์คํํ ๋๋ง๋ค ํจํค์ง ๋ฒ์ ์ ๋ฏธ์ธํ๊ฒ ์ฌ๋ฆด ์ํ์ด ์์ง๋ง, npm ci๋ package-lock.json์ ๋ช ์๋ ๋ฒ์ ๊ณผ ํ ์จ ํ๋ ํ๋ฆฌ์ง ์๊ณ ๋๊ฐ์ด ์ค์นํฉ๋๋ค. ๋น๋ ์๋ฒ์ ๋ก์ปฌ ๊ฐ๋ฐ ํ๊ฒฝ์ ํ๊ฒฝ ๋ถ์ผ์น๋ฅผ ๋ง์์ฃผ๋ ์์ฃผ ํ๋ฅญํ ์์ ์ฅ์น์ ๋๋ค.
- ๋ณด์ ๊ณ ๋ํ (์๋์ด ๋ ๋ฒจ์ ํฌ์ธํธ): OIDC(OpenID Connect) ํ์ฉ
- ์ ๊ฐ์ด๋์์๋ ๊ฐ์ฅ ์ง๊ด์ ์ธ Access Key ๋ฐ๊ธ ๋ฐฉ์์ ์ค๋ช ํ์ต๋๋ค. ํ์ง๋ง ์ค๋ฌด(์ํฐํ๋ผ์ด์ฆ ํ๊ฒฝ)์์๋ Access Key๊ฐ ์ ์ถ๋ ์ํ ์์ฒด๋ฅผ ์์ ๊ธฐ ์ํด OIDC๋ผ๋ ๊ธฐ์ ์ ์๋๋ค. ํค๋ฅผ ๋ฐ๊ธ๋ฐ์ง ์๊ณ , AWS์ GitHub๊ฐ ์๋ก "๋ํฌ ๊ณต์ฅ์์ ์จ ๋ก๋ด์ด ๋ง๊ตฌ๋" ํ๊ณ ์ ๋ขฐ ๊ด๊ณ๋ฅผ ๋งบ์ด ์์ ๊ถํ์ ๋ถ์ฌํ๋ ๋ฐฉ์์ ๋๋ค. ์ถํ ๋ ๋ฒจ์ ์ ์ํ์ ๋ค๋ฉด 'GitHub Actions AWS OIDC ์ฐ๋' ํค์๋๋ก ํ์ตํด ๋ณด์๊ธธ ๊ฐ๋ ฅํ ์ถ์ฒํฉ๋๋ค.
์ด์ ์ฌ๋ฌ๋ถ์ git push ๋ช ๋ น์ด ๋จ ํ ์ค๋ก ์ฝ๋๋ฅผ ์ ์ธ๊ณ์ ๋ฐฐํฌํ ์ ์๋ CI/CD ํ์ดํ๋ผ์ธ์ ๊ฐ๊ฒ ๋์์ต๋๋ค. ์๋ ๋ฐฐํฌ๋ก ๋ฒ๋ ค์ง๋ ์๊ฐ๊ณผ ์๋์ง๋ฅผ ์๊ปด, ๋ ๋์ ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ฐ ์ง์คํ์๊ธธ ์์ํฉ๋๋ค!