CD - 100-hours-a-week/9-team-Devths-WIKI GitHub Wiki
3๋จ๊ณ: CD(์ง์์ ๋ฐฐํฌ) ํ์ดํ๋ผ์ธ ๊ตฌ์ถ
1. ๊ฐ์
1.1. CD์ ํ์์ฑ
- Continuous Deployment๋ ์ฝ๋๊ฐ ์ค๋น๋๋ ์ฆ์ ์ฌ๋์ ์น์ธ ์์ด ์๋์ผ๋ก ์ด์ ํ๊ฒฝ์ ๋ฐ์ํ์ฌ, ๋ฐฐํฌ ์๋๋ฅผ ์ต๋ํํ๊ณ ๋ฐฐํฌ ๊ณผ์ ์์ ๋ฐ์ํ๋ ์ง์ฐยท์๋ ์์ ์ ์ ๊ฑฐํ๊ธฐ ์ํด์ ์ฌ์ฉ.
- ๊ฐ๋ฐ ๊ณผ์ : ์๋ก์ด ๊ธฐ๋ฅ, ๋ฒ๊ทธ ์์ ์ ๊ฐ๋ฐ ์ ๋ฐ์ ๋ด์ญ์ ํ์ธํ๊ธฐ ์ํด ํ ์คํธ ์๋ฒ ๋ฑ์ ๋ฐฐํฌ ํ์
- ์ด์ ๊ณผ์ : ์๋ ๋ฐฐํฌ ์ ๋ฐ์ํ ์ ์๋ ์ค์ ์์ฒ ๋ฐฉ์ง, ์๋ชจ ์๊ฐ ๋ฐ ๋น์ฉ ๊ฐ์ ์ํด ํ์
1.2. ๊ธฐ๋ํจ๊ณผ
- ์๋ ๋ฐฐํฌ ๋๋น ์๊ฐ ๋ํญ ๊ฐ์ย (15๋ถ โ 3~5๋ถ)
- ์๋ ๋ฐฐํฌ ์ค์ ์์ฒ ๋ฐฉ์ง
- ๋กค๋ฐฑ ์๋ํ ๊ธฐ๋ฐ ํ๋ณด
- ์์ ๋จ์์ ์ฝ๋ ๋ณ๊ฒฝ์ ์์ฃผ ๋ฐฐํฌํ์ฌ ๋น ๋ฅธ ๊ธฐ๋ฅ ์ถ๊ฐ ๊ฐ๋ฅ
1.3. ์์ฝ
- Artifacts:ย AWS S3 (Versioning: Commit SHA)
- Agent:ย AWS CodeDeploy
- Secrets:ย AWS Parameter Store
- Notification:ย Discord Webhook
- Environment Strategy:
- Dev/Staging: Continuous Deployment (์ฆ์ ๋ฐฐํฌ)
- Prod: Continuous Delivery (์๋ ํธ๋ฆฌ๊ฑฐ)
- Deployment Strategy:ย Blue/Green
2. ์์ฌ ๊ฒฐ์
2.1. Artifact Repo
2.1.1. Artifact Repo ์๊ตฌ์ฌํญ
- CI ๋น๋ ์ฐ์ถ๋ฌผ(jar, zip, static files ๋ฑ)์ ์ง์นญ
- ํ์ฌ ์ปจํ ์ด๋(Docker)๋ฅผ ๋์ ํ์ง ์์ ๋จ๊ณ์ด๋ฏ๋ก Artifact Repo๋ ๋น๋ ์ฐ์ถ๋ฌผ ํ์ผ์ ์๋ณธ ๊ทธ๋๋ก ์ ์ฅํ ์ ์์ด์ผ ํจ.
- ๋ฒ์ ๊ด๋ฆฌ: ๋ฐฐํฌ ์คํจ ์ ์ฆ์ ๋กค๋ฐฑ ํ ์ ์๋๋ก ์ํฐํฉํธ์ ๋ฒ์ ์๋ณ์ด ๊ฐ๋ฅํด์ผ ํจ.
- ์ฐ์ํ ์ ๊ทผ์ฑ: CD ๋จ๊ณ์์ ๋ณด์ ์ธ์ฆ ๋ฐ ํต์ ์ด ์ํํ๊ณ ๋น ๋ฅด๊ฒ ์งํ๋์ด์ผ ํจ
- ์ ์ง๋ณด์ ์ต์ํ: ๋ณ๋์ ์๋ฒ๋ฅผ ๊ตฌ์ถํ์ง์๊ณ ๊ด๋ฆฌ๊ฐ ๊ฐ๋ฅํด์ผํจ.
2.1.2. ํ๋ณด ๊ธฐ์
- GitHub Actions (https://docs.github.com/ko/[email protected]/actions/tutorials/store-and-share-data)
- CI ์ํฌํ๋ก์ฐ ์คํ ์ค์ ์์ฑ๋๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ์ํฌํ๋ก์ฐ ๋ด์ ๋ค๋ฅธ Job๊ณผ ๊ณต์ ํ๊ฑฐ๋, ์คํ ์๋ฃ ํ ๋ค์ด๋ก๋ํ ์ ์์
- ์ฅ์
- ๋ณ๋์ ์คํ ๋ฆฌ์ง ๋ฐ ์๋น์ค ๊ตฌ์ถ ์์ด GitHub Actions ์คํฌ๋ฆฝํธ๋ก ๊ตฌ์ถ ๊ด๋ฆฌ ๊ฐ๋ฅ
- ๋ฌด๋ฃ ํ๋ ๋ด ์คํ ๋ฆฌ์ง ์ฌ์ฉ ๊ฐ๋ฅ
- ๋จ์
- ๋ณด์กด ๊ธฐ๊ฐ: ๋ณด๊ด ๊ธฐ๊ฐ 90์ผ๋ก ์ ํ
- ๋ฎ์ ์ ๊ทผ์ฑ: CD ๊ณผ์ ์์ EC2 ์ธ์คํด์ค์ ์ ์กํ๋ ค๋ฉด GitHub API ํ ํฐ ์ธ์ฆ์ ํตํ ์ค์ ํ์
- ์ฉ๋ ๋ถ์ผ์น: Build Job โ Test Job์ ๋ฐ์ดํฐ ์ ๋ฌ, ๋ก๊ทธ ์ ์ฅ์ ์ํด ์ค๊ณ๋ ๊ธฐ๋ฅ.
โ CD๋ฅผ ์ํ ์ ์ฅ์๋ก๋ ๋ถ์ ํฉ
- AWS S3
- AWS์ ๊ฐ์ฒด ์คํ ๋ฆฌ์ง ์๋น์ค
- ์ฅ์
- ๋์ ์ ๊ทผ์ฑ: AWS EC2์์ IAM Role ๊ธฐ๋ฐ์ผ๋ก ์ธ์ฆ ์์ด ์ ๊ทผ ๋ฐ ์ฐ๋ ๊ฐ๋ฅ. ์ถํ AWS Code Deploy ์ฐ๋ ์ฌ์ฉ์ ํธ์์ฑ
- ๋น์ฉ: AWS ์์ฐ ๋ด์์ ์ฌ์ฉ ๊ฐ๋ฅํ์ฌ ์ถ๊ฐ ๋น์ฉ ๋ฐ์ํ์ง ์์
- ์ ์ฐ์ฑ: Artifact ํ์ผ์ ์๋ณธ์ผ๋ก ์ ์ฅ ๊ฐ๋ฅ
- ๋ฒ์ ๊ด๋ฆฌ: S3 ๋ฒํท ๋ฒ์ ๊ด๋ฆฌ / ๊ณ์ธตํ ๊ตฌ์กฐ ์ฌ์ฉ์ผ๋ก ๋กค๋ฐฑ ์์ ๊ด๋ฆฌ ๊ฐ๋ฅ
- ๋จ์
- ์์กด์ฑ ๊ด๋ฆฌ ๋ฑ ์ถ๊ฐ ๊ธฐ๋ฅ ๋ถ์ฌ
โ ํ์ฌ ์ฌ์ฉ ๊ตฌ์กฐ ๋ฐ ์ถํ AWS ์ธํ๋ผ ๋ด์์์ ํ์ฅ์ ๊ณ ๋ คํ์ฌ ๊ฐ์ฅ ์ ์ ํ๋ค ํ๋จ.
- Jfrog Artifatory
- ๋ฒ์ฉ artifact Repository ์๋ฃจ์ (ํ์ผ, ํจํค์ง, ๋ผ์ด๋ธ๋ฌ๋ฆฌ, ์ปจํ ์ด๋ ์ด๋ฏธ์ง ๋ฑ ๊ด๋ฆฌ ๊ฐ๋ฅ)
- ์ฅ์
- ์ ์ฐ์ฑ: ๋ค์ํ ์ธ์ด์ ํจํค์ง ๋งค๋์ ๋ฅผ ์ง์, ๋ฉํ ๋ฐ์ดํฐ ๊ด๋ฆฌ ๊ธฐ๋ฅ
- ๋ณด์: Xray ๊ธฐ๋ฅ์ ํตํ ์ํฐํฉํธ ๋ณด์ ์ทจ์ฝ์ ์ค์บ ๊ฐ๋ฅ
- ๋จ์
- ๋น์ฉ: SaaS ์ฌ์ฉ ์ ์ถ๊ฐ ๋น์ฉ ๋ฐ์. ๋ฌด๋ฃ ๋ฒ์ ์ ์ด์์ ์ํ Self-host ์ธํ๋ผ ๊ตฌ์ถ ํ์
- Over engineering: CI/CD ์ ๋ฐ์ ๊ฑธ์ณ ์ํฐํฉํธ ๋ฟ๋ง ์๋ ๋ค์ํ ๋ด์ฉ์ ๋ํ ๊ด๋ฆฌ๋ฅผ ์ ๊ณต. ๋จ์ Artifact Repo ๋ก์ ๊ณผ๋ํ ๊ธฐ๋ฅ
โ ๊ท๋ชจ์ ๋ง์ง ์๋ ๊ณผ๋ํ ์๋น์ค
- Sonatype Nexus Repository Manager(https://help.sonatype.com/en/sonatype-nexus-repository.html)
- ์ํํธ์จ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ, ์์กด์ฑ, ์ํฐํฉํธ ์ ์ฅ ๊ด๋ฆฌ ๊ฐ๋ฅํ ์์คํ (Java ์ํ๊ณ ์ค์ฌ)
- ์ฅ์
- ๊ธฐ๋ฅ: ๋ค์ํ ๋ฐฐํฌ ์ ์ฑ ์ ์ง์(Release, Snapshot, Mixed)ํ์ฌ ๊ด๋ฆฌ ์ฉ์ด
- Proxy Repository: ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์บ์ฑํ์ฌ ๋น ๋ฅธ ๋น๋ ๊ฐ๋ฅ
- ๋จ์
- ๋น์ฉ: ์๋น์ค ์คํ์ ์ํ ์ธํ๋ผ๋ฅผ ์ง์ ๊ตฌ์ถํด์ผํจ. (์์ฉ Saas ์๋ฃจ์ ์ ์ ๋ฃ ํ๋)
โ ์ํฐํ๋ผ์ด์ฆ ์ค์ฌ ๊ธฐ๋ฅ, ๊ณผ๋ํ ์๋น์ค
2.2. Versioning
2.2.1. ๋ฒ์ ๋์ ํ์์ฑ
- ์ถ์ : ํ์ฌ ๋ฐฐํฌ๋์ด ํ ์คํธ/์ด์ ์ค์ธ ํ์ผ์ด ์ด๋ค ์ฝ๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ๊ฒ์ธ์ง ์๋ณ
- ์ ์ผ์ฑ: ๋ฒ์ ๊ฐ ์ด๋ฆ์ ๊ตฌ๋ถํ์ฌ ํผ๋ ๋ฐ ์๋ฌ ๋ฐฉ์ง
- ๋กค๋ฐฑ: ์ฅ์ ๋ฐ์ ์ ์ด๋ค ๋ฒ์ ์ผ๋ก ๋์๊ฐ ์ง ์๋ณํ๊ธฐ ์ํด์
2.2.2. ํ๋ณด๊ตฐ
- Commit SHA
- Git์ ์ปค๋ฐ ํด์๊ฐ์ ๊ทธ๋๋ก ๋ฒ์ ๋ช ์ผ๋ก ์ฌ์ฉ
- ์ฅ์
- ์๋ฒฝํ ์ถ์ ๊ฐ๋ฅ
- ์๋ํ ์ฉ์ด: ๋ฐฐํฌ ๊ณผ์ ์์ ๋ณ๋ ๋ก์ง ์์ด GitHub Actions ํ๊ฒฝ๋ณ์๋ก ๋ฐ๋ก ์ ๊ทผ ๊ฐ๋ฅ
- ๋จ์
- ์์ ๋ฐ ์์ ํ์ ๋ถ๊ฐ
- ๋ฎ์ ๊ฐ๋ ์ฑ
- Semantic Versioning(SemVer)
Major.Minor.Patchํ์์ ์ ํต์ ๋ฒ์ ๋ ๋ฐฉ๋ฒ(์: 0.1.2 ๋ฒ์ )- ์ฅ์
- ๋ช ํํ ์๋ฏธ: ์ ๋ฐ์ดํธ ์ค์๋์ ๋ฐ๋ฅธ ๋ฒ์ ์ฐจ์ด๋ฅผ ์ง๊ด์ ์ผ๋ก ๊ตฌ๋ถ ๊ฐ๋ฅ
- ๋จ์
- ์๋ํ ์ด๋ ค์: ๋งค ๋ฐฐํฌ๋ง๋ค Major, Minor,Patch ์ค ์ด๋ค ๋ฒ์ ์ ์ธ์ง์ ๋ฐ๋ผ ์๋์ผ๋ก ๊ตฌ๋ถ์ง์ด์ฃผ๊ฑฐ๋, ๋ณต์กํ ์คํฌ๋ฆฝํธ ์์ฑ ํ์
- Timestamp
- ๋น๋๋ ๋ ์ง์ ์๊ฐ์ ๋ฒ์ ๋ช ์ผ๋ก ์ฌ์ฉ
- ์ฅ์
- ์ ๋ ฌ ์ต์ ํ: ํ์ผ๋ช ์ผ๋ก ์ ๋ ฌ ๋ฐ ์ต๊ทผ ๋ฒ์ ํ์์ด ๊ฐ๋ฅ
- ์ง๊ด์ฑ: ์ธ์ ๋ฐฐํฌ๋ ํ์ผ์ธ์ง ํ์ ํ๊ธฐ ์ฌ์
- ๋จ์
- ์ฝ๋ ์ถ์ ์ด๋ ค์: ์ด๋ค ์ฝ๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋น๋ํ๋์ง ์์ ์์
- ์ต์ข ๊ฒฐ์ : Hybrid ์ ๋ต
{YYYYMMDD_HHmmss}-{SHA}.{ํ์ฅ์}๊ตฌ์กฐ๋ก ์์ฑ- ์ฅ์
- ํ์์คํฌํ๋ฅผ ์์ ๋์ด ์ ๋ ฌ์ ํตํด ์ต์ ๋ฐฐํฌ ํ์ผ์ ์ฆ์ ์๋ณ ๊ฐ๋ฅ
- ๋ฌธ์ ๋ฐ์ ์ SHA ๋ฅผ ํตํด ์ฝ๋ ์ถ์ ๊ฐ๋ฅ
- ์๋ํ: ์ถ๊ฐ ๋ก์ง ์์ด ๋จ์ ๋ช ๋ น์ด๋ก ์์ฑ ๊ฐ๋ฅ
- ๋กค๋ฐฑ: ํ์์คํฌํ, SHA ์ ๋ณด ๊ธฐ๋ฐ์ผ๋ก ์๊ฐ๊ธฐ์ค, ๊ธฐ๋ฅ ๋ฐฐํฌ ๊ธฐ์ค(์ฝ๋ ์์ )์ผ๋ก ๋ชจ๋ ๋กค๋ฐฑ ๊ฐ๋ฅ
2.3. ์๋ฒ ์ด์ ์ ๋ต
2.3.1. ์๋ฒ ์๊ตฌ์ฌํญ
- ๊ฐ๋ฐ ์๋ฒ: ๋์ ์ฑ๋ฅ ๋ถํ์(์ด์ ์๋ฒ์ ๊ฐ์ ํ์ ์์), ์์ ํ ์คํธ ๋ฐ ๋๋ฒ๊น ์ ์ํด ์์ ์ด์ ํ์
- ์ด์ ์๋ฒ: ๋์ ์ฑ๋ฅ ํ์. ์๋น์ค ์์ ์ด์ ํ์
- ์คํ ์ด์ง ์๋ฒ: ์ด์ ํ๊ฒฝ ๊ฒ์ฆ์ ์ํด ์ ์ฌํ๊ฑฐ๋ ๋์ผํ ์ธํ๋ผ ํ์. ๋น์ฉ ์ ๊ฐ์ ์ํ ๋ฐฉ์ ํ์
2.3.2. ์คํ ์ด์ง ์๋ฒ ์ด์ ์ ๋ต ๋น๊ต
๋น์ฉ ์ถ์ ์ t3.medium ์์ธ๋ฆฌ์ ๊ธฐ์ค
- ์ ํ์ ์ด์
- ์ด์ ๋ฐฐํฌ ์ง์ , QA ํ์์์๋ง ์๋์ผ๋ก ์ธ์คํด์ค๋ฅผ ์์ํ๊ณ , ๊ฒ์ฆ ํ ์๋์ผ๋ก ์ค์งํ๋ ๋ฐฉ์
- ์ฅ์
- ๋น์ฉ ์ต์ ํ: ํ ์คํธ ์ํ ์๊ฐ์๋ง ์ด์
- ๋จ์ํจ
- ๋จ์
- ์๋ํ ๋จ์ : CI/CD ํ์ดํ๋ผ์ธ์ ์๋ฒ๊ฐ ์ผ์ง ์ํฉ์ ์ ์ ๋ก ๋์. ์์ ์๋ํ ๋ถ๊ฐ๋ฅ
- Cold Start: ์๋ฒ ์์, ์๋น์ค ๊ตฌ๋๊น์ง ๋๊ธฐ ์๊ฐ ๋ฐ์
โ ๊ทน ์ด๊ธฐ ๋ฐฐํฌ์, ์คํ ์ด์ง ์๋ฒ์์ ํ ์คํธ๊ฐ ๊ฑฐ์ ์๋ ๊ฒฝ์ฐ ์ ํ ๊ฐ๋ฅ. ์ ๊ธฐ ๋ฐฐํฌ, ์์ ๋ฐฐํฌ ์ฌ์ดํด์ด ์์ฑ๋๋ฉด ์์ ์๋ํ๋ก ์ ํ ๊ฐ๋ฅ
- On-Demand(์์ ์ด์)
- ์ด์ ์๋ฒ์ ๋น์ทํ ๊ตฌ์กฐ๋ก ์ฆ์ ํ ์คํธ ๊ฐ๋ฅํ๋, ๋ง์ ๋น์ฉ ๋ฐ์
- ๋น์ฉ ์ถ์ : ์ฝ $37
- On-Demand + Scheduler(Time-based)
- ์ ๋ฌด์๊ฐ, ๋ฐฐํฌ ์ด์ ์๊ฐ์๋ง ์ผ๋๊ณ , ์ด ์ธ ์๊ฐ์ ์๋์ผ๋ก ๋๋ ๋ฐฉ์
- ์ฌ์ฉ ์๊ฐ๋งํผ ๊ณผ๊ธ์ผ๋ก ๋น์ฉ ์ ์ฝ ๊ฐ๋ฅ.
- ๋ค์ํ ๋ฐฐํฌ๊ฐ ์งํ๋๋ ์ด๊ธฐ ๊ฐ๋ฐ ๋จ๊ณ์ ๋์ํ๊ธฐ ์ด๋ ค์
- ๋ฐค, ์ฃผ๋ง ์๊ฐ ๋ฐฐํฌ ํ์ ์ ์๋ ์๋ ํ์
- ๋น์ฉ ์ถ์ : ์ฝ $11(์จ๋๋ฉ๋ ๋๋น ์ฝ 70% ์ ๊ฐ)
- Spot Instance
- AWS ์คํ ์ธ์คํด์ค๋ฅผ ์ฌ์ฉํ์ฌ ๋น์ทํ ์ธํ๋ผ๋ฅผ ์ ๊ฐ์ ์ผ๋๋ ๋ฐฉ์
- ์์ ์ด์์ผ๋ก ์ฆ์ ํ ์คํธ ๊ฐ๋ฅ
- AWS ํ์ ์ ๊บผ์ง ์ ์์. ์ํ ์ ์ง(EBS ์ค์ ) ํ์(Interruption Behavior ์์ , ๊ธฐ์กด
Terminate(์ญ์ )โStop(์ค์ง)๋ก ๋ณ๊ฒฝํ๋ฉด EBS ์ ์ง ์ํ๋ก ์ฌ์์ ๊ฐ๋ฅ) - ASG์ ์ฐ๋ํ์ฌ ์๋ ๋ณต๊ตฌ ๊ฐ๋ฅ(Spot Instance๋ก ๊ตฌ์ฑ๋ ASG ์์ฑ)
- ๋น์ฉ ์ถ์ : ์ฝ $11(์จ๋๋ฒค๋ ๋๋น ์ฝ 70% ์ ๊ฐ)
- ์์ ํ๊ฒฝ ๊ตฌ์ถ
DevโReleasePR ์์ฑ์ ํธ๋ฆฌ๊ฑฐ๋ก ์์ ์๋ฒ๋ฅผ ์์ฑ ํ ๋ฐฐํฌ.- ์๋ฒฝํ ๊ฒฉ๋ฆฌ ํ๊ฒฝ์์ ํ ์คํธ ๊ฐ๋ฅ ๋ฐ ๋น์ฉ ๊ทน๋จ์ ์ ์ฝ
- ๋ณต์กํ ๊ตฌ์ถ ๋ฐฉ์(IaC)ํ์.(์ค๋ฒ์์ง๋์ด๋ง)
์ต์ข ๊ฒฐ์ : Spot Instance ์ฌ์ฉํ์ฌ ์คํ ์ด์ง ์๋ฒ ๊ตฌ์ถ
| ํ๊ฒฝ | ์ด์ ๋ฐฉ์ | ์ฅ์ | ๋จ์ |
|---|---|---|---|
| ๊ฐ๋ฐ (Dev) | ์์ ๊ฐ๋, ์ฆ์ ๋ฐฐํฌ | - ๊ฐ๋ฐ ์์ฐ์ฑ ์ต์ ํ (๊ธฐ๋ค๋ฆผ ์์). - ์ธ์ ๋ ์ ์ํด ๋ก๊ทธ ํ์ธ ๊ฐ๋ฅ. | - ์์ ์ด์์ผ๋ก ์ธํ ๋น์ฉ ๋ฐ์. |
| ์คํ ์ด์ง (Staging) | AWS Spot Instance ์ฌ์ฉ | - ๋น์ฉ ์ ๊ฐ ํจ๊ณผ ํ์ค. - ๋น ๋ฅธ ์คํ ์ด์ง ํ ์คํธ ๊ฐ๋ฅ | ์ค๋จ ๊ฐ๋ฅ์ฑ ์์ |
| ์ด์ (Prod) | ์์ ๊ฐ๋, ์๋ ์น์ธ | - ์์ ์ฑ ํ๋ณด. - ์ฌ์ฉ์ ์๋น์ค ๋ณด์ฅ. |
2.4. Rollback ์ ๋ต
- ๊ฐ๋ฐ ์๋ฒ: ์๋ ๋กค๋ฐฑ
- ๋๋ฒ๊น ํ๊ฒฝ ๋ณด์กด์ ์ํด ์ํ๋ฅผ ์ ์ง ํ ํ์๊ฐ ์์
- ๊ฐ๋ฐ ๋จ๊ณ์์๋ ์ด์ ๋ฒ์ ์ผ๋ก ๋กค๋ฐฑ ํ ๋ค์ ํ์ธํ๋ ๊ฒ ๋ณด๋ค ๋ฒ๊ทธ ์์ ํ ์ปค๋ฐํ์ฌ ์ฌ ๋ฐฐํฌํ๊ณ ํ์ธํ๋๊ฒ์ด ํจ์จ์
- ์คํ ์ด์ง ์๋ฒ: Hybrid
- ๋ฐฐํฌ ์คํจ ์ ์๋ ๋กค๋ฐฑ
- ์ด์๊ณผ ๋์ผํ ๋ฐฐํฌ / ๋กค๋ฐฑ ์คํฌ๋ฆฝํธ์ ์๋์ ํ์ธํด์ผ ํจ
- ํ ์คํธ(QA)๋ฅผ ์ํ ์๋ฒ ์๋ ํ์
- ๊ธฐ๋ฅ ๊ฒฐํจ ์ ์๋ ๋กค๋ฐฑ
- ๋ฐฐํฌ์๋ ๋ฌธ์ ์์ผ๋, ๊ธฐ๋ฅ์ ๋ฌธ์ ๊ฐ ์๋ ๊ฒฝ์ฐ ํ ์คํธ ๋ฐ ๊ฐ๋ฐ์ ํ์ธ ํ ์๋ ๋กค๋ฐฑ ๋ฐ ํซํฝ์ค ๋ฐฐํฌ
- ์ด์์๋ฒ: ์๋ ๋กค๋ฐฑ(+ ์๋ ํธ๋ฆฌ๊ฑฐ)
- ๋ฌธ์ ๋ฐ์ ์ ์๋ฒ ๋ค์ดํ์ ์ต์ํ ์ํด ์ง์ ๋ฐฐํฌ๋ก ์๋ ๋กค๋ฐฑ
- ์จ์ ๋ก์ง ์๋ฌ ๋ฐ์์ ๋์์ ์ํ ์๋ ํธ๋ฆฌ๊ฑฐ๋ ํ์
2.5. ํ๊ฒฝ๋ณ CD ์ด์ ์ ๋ต
| ํ๊ฒฝ | ๋์ ๋ธ๋์น | ํธ๋ฆฌ๊ฑฐ(Trigger) ์กฐ๊ฑด | ๋ฐฐํฌ ์ ๋ต | ์ค๋ช |
|---|---|---|---|---|
| Dev (๊ฐ๋ฐ) | dev |
์๋ ํธ๋ฆฌ๊ฑฐ(PR Merge ์) | ๋จ์ ์ฌ์์ | - ์๋ ์ฐ์ - ๊ฐ๋ฐ์๊ฐ ์ฝ๋๋ฅผ ์ฌ๋ฆฌ์๋ง์ ์๋ฒ์ ๋ฐ์๋์ด ๊ธฐ๋ฅ ํ์ธ ๊ฐ๋ฅ - ๋ค์ดํ์ ํ์ฉ |
| Staging (๊ฒ์ฆ) | release |
์๋ ํธ๋ฆฌ๊ฑฐ(PR Merge ์) | Blue/Green (๋ฌด์ค๋จ ๋ฐฐํฌ) | - ๊ฒ์ฆ ์ฐ์ - ์ด์ ๋ฐฐํฌ ์ , ์ค์ ๋ฐ์ดํฐ์ ์ ์ฌํ ํ๊ฒฝ์์ QA ์งํ - ์ด์ํ๊ฒฝ๊ณผ ๋์ผํ ์ค์ ๊ฒ์ฆ |
| Prod (์ด์) | main |
์๋ ์น์ธ (Manual) | Blue/Green(๋ฌด์ค๋จ ๋ฐฐํฌ) | - ์์ ์ฑ ์ฐ์ - main์ ๋จธ์ง๋์ด๋ ์ฆ์ ๋ฐฐํฌํ์ง ์์ - ๊ด๋ฆฌ์๊ฐ GitHub Actions์์ ์น์ธํ์ฌ ์๋ ํธ๋ฆฌ๊ฑฐ - ์ฌ์ฉ์ ๊ฒฝํ์ ์ํด ๋ฌด์ค๋จ ๋ฐฐํฌ |
2.6. ๋ฐฐํฌ ์์ด์ ํธ ์ ํ
| ๋ฐฉ์ | ์ค๋ช | ์ฅ์ (Pros) | ๋จ์ (Cons) | ์ฐ๋ฆฌ ์๋น์ค ์ ํฉ์ฑ |
|---|---|---|---|---|
| SSH Script (via GitHub Actions) | GitHub Runner๊ฐ EC2์ SSH๋ก ์ ์ํ์ฌ ์ ์คํฌ๋ฆฝํธ ์คํ | - ๊ตฌ์ถ ์๋: appleboy/ssh-action ์ฌ์ฉํ์ฌ ๋น ๋ฅธ ์ค์ ๊ฐ๋ฅ - ์ ์ฐ์ฑ: ๋ฆฌ๋
์ค ๋ช
๋ น์ด ๊ทธ๋๋ก ์ฌ์ฉ ๊ฐ๋ฅ. |
- ๋ณด์: GitHub Secrets์ PEM ํค๋ฅผ ์ ์ฅํด์ผ ํจ, ssh ํฌํธ ์ ์ฒด ๊ฐ๋ฐฉ ํ์ - IP ์ ํ: ๋ณด์๊ทธ๋ฃน(SG)์์ GitHub IP ๋์ญ์ ์ด๊ฑฐ๋ Bastion์ ๊ฑฐ์ณ์ผ ํจ. - ํ์ฅ์ฑ: ์ถํ ์คํ ์ค์ผ์ผ๋ง ์ ์ฉ ์ ์๋ ๋ฐฐํฌ ๋ถ๊ฐ | - ๋จ์ผ ์ธ์คํด์ค ํ๊ฒฝ์์ ๊ฐ์ฅ ๋น ๋ฅด๊ณ ์ง๊ด์ - ์ถํ ASG ์ค์ ์ AWS CodeDeploy๋ก ๋ง์ด๊ทธ๋ ์ด์ ํ์.(๊ธฐ์ ๋ถ์ฑ ๋ฐ์) |
| AWS CodeDeploy | AWS์ ๊ณต์ ๋ฐฐํฌ ๊ด๋ฆฌ ์๋น์ค ์ฌ์ฉ (Agent ์ค์น ํ์) | - ์์ ์ฑ: ๋ฐฐํฌ ์คํจ ์ ์๋ ๋กค๋ฐฑ ๊ธฐ๋ฅ ๋ด์ฅ. - ํ์ฅ์ฑ: ์ถํ Auto Scaling Group ์ ์ฉ ์ ์๋ ๋ฐฐํฌ ์ง์. - ๋ฌด๋ฃ: EC2 ๋ฐฐํฌ ์ ์ถ๊ฐ ๋น์ฉ ์์. - ๋ณด์: Inbound ํฌํธ ์คํ ๋ถํ์ | - ๋ฌ๋ ์ปค๋ธ: IAM Role, AppSpec.yml ๋ฑ ์ด๊ธฐ ์ค์ ๋ณต์ก. - ์ข ์์ฑ: AWS ์ ์ฉ ์ค์ ํ์ผ ๊ด๋ฆฌ ํ์. | - ๋ค์ค ์ธ์คํด์ค(ASG) ๋จ๊ณ๋ก ๋์ด๊ฐ๋ฉด ๋์ ํ์ - ๊ธฐ์ ๋ถ์ฑ ๋ฐฉ์ง ๋ฐ ๋ณด์์ฑ, ์์ ์ฑ ์ฐ์. |
| Self-hosted Runner | EC2 ๋ด๋ถ์ GitHub Runner ์ค์นํ์ฌ ์คํ | - ๋ณด์ ์ต์: ์ธ๋ถ์์ ๋ค์ด์ค๋ SSH ํฌํธ(22)๋ฅผ ์ด ํ์ ์์ (Outbound ํต์ ). | - ๋ฆฌ์์ค ์๋ชจ: Runner ํ๋ก์ธ์ค๊ฐ ์๋ฒ CPU/RAM์ ์ ์ ํจ (๋จ์ผ ์ธ์คํด์ค์ ์น๋ช ์ ). - ๊ด๋ฆฌ ๋น์ฉ: Runner ์ ๋ฐ์ดํธ ๋ฐ ์ข๋น ํ๋ก์ธ์ค ๊ด๋ฆฌ ํ์. | ๋จ์ผ ์ธ์คํด์ค ํ๊ฒฝ์ธ V1์์๋ ๋ถ๊ฐ. ์๋ฒ ์์์ด ์ถ๊ฐ ํ์ํ์ฌ ๋ถ์ ์ ํ๋ค๊ณ ํ๋จ. |
2.7. ํ๊ฒฝ๋ณ์ ์ฃผ์
- GitHub Secret
- ๋ฐฐํฌ ์์ ์ GitHub๊ฐย
.envย ํ์ผ์ ๋ง๋ค์ด ์๋ฒ์ ์ ์ก - ์ฅ์ : ๊นํ๋ธ ๋ ํฌ์งํ ๋ฆฌ์์ ๋ฐ๋ก ์ค์ ํ ์ ์์
- ๋จ์ : ๋ณด์๋ฌธ์ (Secret ๊ฐ์ด ์๋ฒ ๋์คํฌ์ ๋จ์)
- ๋ฐฐํฌ ์์ ์ GitHub๊ฐย
- AWS Parameter Store
- ๋ฐฐํฌ ํ ์๋ฒ ๋ถํ ์์ ์ AWS API ํธ์ถํ์ฌ ๊ฐ ๊ฐ์ ธ์ค๊ธฐ
- ์ฅ์ : ๋ณด์(๊ฐ์ ๋ฉ๋ชจ๋ฆฌ์๋ง ์ ์ฅํ๊ฑฐ๋, ์ํธํ ๊ด๋ฆฌ ๊ฐ๋ฅ), ์ด๋ ฅ ๊ด๋ฆฌ ๋ฐ ๊ณ์ธตํ ๊ด๋ฆฌ ๊ฐ๋ฅ
- ๋จ์ : ๋ณต์กํ ์ค์ (SDK ์ฐ๋ ์ฝ๋ ์์ฑ ํ์)
2.8. ์๋ฆผ ์ฑ๋
- Discord(์ ์ )
- ํ์ฌ ํ ๋ด๋ถ์์ ์ฌ์ฉ์ค์ธ ๋ฉ์ ์ .
- ์ฅ์ : ๋น์ฉ ๋ฌด๋ฃ, ๋จ์ํ ์ฐ๋(Webhook)
- ๋จ์ : ๊ฐ๋ฐ ๋๊ตฌ์ ์ต์ ํ ๋์ง ์์ ํ๋ซํผ
- Slack
- ๋ฉ์ ์ ๊ธฐ๋ฐ ํ์ ํด
- ์ฅ์ : ๋ค์ํ ๊ฐ๋ฐ๋๊ตฌ์ ์ฐ๋์ ์ง์ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- ๋จ์ : ์ถ๊ฐ ๋น์ฉ ๋ฐ์(๋ฌด๋ฃ ํ๋ ์ฌ์ฉ์ 90์ผ ์ดํ ๋ฉ์์ง ์ญ์ โ ๋ฐฐํฌ ๋ฑ ์ด๋ ฅ ์ถ์ ๋ถ๊ฐ)
2.9. ๋ฐฐํฌ ๋ฒ์ ๋ฐ ๋ฐฉ์
-
Dev(๊ฐ๋ฐ), Staging ์๋ฒ
- ๊ฐ๋ฐ ๋ฐ ๊ฒ์ฆ ๊ณผ์ ์ ํ๋ฃจ์ ์ ํ ๋ฐ์
- ๋น ๋ฅธ MVP ๊ฐ๋ฐ๊ณผ์ ์ ๋ณด์กฐํ๊ธฐ ์ํด ๋น ๋ฅธ ๋ฐฐํฌ ํ์
- ๋ฒ๊ทธ๊ฐ ์์ด๋ ํ์ธ ํ ์์ ํ๋ฉด ๋๋ฏ๋ก ์น์ธ ์ ์ฐจ ๋ถ์
โ Continous Deployment(์์ ์๋ ๋ฐฐํฌ) ์ ์
-
Production(์ด์)
- ์ด์ ๋ฐฐํฌ๋ ๋ฎ์ ๋น๋๋ก ๋ฐ์
- ์ด์์ ์์ ์ฑ์ด ์ต์ฐ์
- ํด๋จผ์๋ฌ(์๋ชป๋ ์ฝ๋ ๋จธ์ง, ์์ ๋ฑ)๊ฐ ๋ฐ์ํด๋ ๋ฐฐํฌ ์ ํ๋ฒ ๋ ํ์ธ ํ ์ ์๋ ์์ ์ฅ์น ํ๋ณด ํ์
โContinuous Delivery(์น์ธ ํ ๋ฐฐํฌ) ์ ์
2.10. ๋ฐฐํฌ ์ ๋ต
- ์๋น์ค ํน์ฑ(์ทจ์ ์ผ์ ๊ด๋ฆฌ, AI ํผ๋๋ฐฑ)์ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ๊ณผ ์ฌ์ฉ์ ๊ฒฝํ(๋๊น ์์) ์ค์
- ์๋น์ค ์ค๋จ ์ ๋ฐ์ํ ์ ์๋ ๋ฌธ์
- ์ผ์ ๊ด๋ฆฌ ์๋น์ค์์ ์๋ฆผ ๋จนํต ๋ฐ์ ๊ฐ๋ฅ์ฑ
- ์ผ์ ์์ฑ, ์ปค๋ฎค๋ํฐ ๊ธ ์์ฑ ๋์ค ์ฐ๊ฒฐ ๋๊น์ผ๋ก ๋ฐ์ดํฐ ์ ์ค
- AI ์ด๋ ฅ์ ํผ๋๋ฐฑ ์๋ต ์ค (Long-polling) ์์ฒญ ๋๊น
- ๋ฐ๋ผ์ ๋ฌด์ค๋จ ๋ฐฐํฌ๋ฅผ ์๊ตฌ
๋ฌด์ค๋จ ๋ฐฐํฌ ์ ๋ต ๋น๊ต
-
๋กค๋ง ์ ๋ฐ์ดํธ
- ์ผ๋ถ ์๋ฒ๋ฅผ ๋๊ณ ์ ๋ฒ์ ์ ์คํํ์ฌ ์ ์ง์ ์ผ๋ก ๊ต์ฒดํ๋ ๋ฐฉ์. ๋จ์ผ ์ธ์คํด์ค์ ๊ฒฝ์ฐ ๋ค์ดํ์์ด ๋ฌด์กฐ๊ฑด ๋ฐ์.
- ์ฅ์ : ์ถ๊ฐ ๋ฆฌ์์ค ๋ถ์
- ๋จ์ : ์๋น์ค ์ค๋จ ๋ฐ์
โ ๋ฌด์ค๋จ ํ์ํ๋ฏ๋ก ๋ฐฐ์
-
๋ธ๋ฃจ/๊ทธ๋ฆฐ
- ๋์ผํ ์ธํ๋ผ๋ฅผ ํ๋ ๋ ๋์ฐ๋ ๋ฐฉ์
- ์ฅ์ : ๋ฌธ์ ๋ฐ์ ์ ์ฆ์ ๋กค๋ฐฑ ๊ฐ๋ฅ, ๋ฌด์ค๋จ ๋ฐฐํฌ ๊ฐ๋ฅ
- ๋จ์ : ์ผ์์ ์ผ๋ก ์ธํ๋ผ 2๋ฐฐ ํ์
โ ๊ฐ์ฅ ๊ฐ๋จํ๊ฒ ๋ฌด์ค๋จ์ ๊ตฌํํ ์ ์์
-
์นด๋๋ฆฌ
- ํธ๋ํฝ์ ์ผ๋ถ๋ง ์ ๋ฒ์ ์ผ๋ก ๋ณด๋ด, ์ ์ง์ ์ ํํ๋ ๋ฐฉ์
- ์ฅ์ : ์๋ฒ ๋ฌธ์ ๋ฐ์ ์ ๋ฆฌ์คํฌ ์ต์ํ ๊ฐ๋ฅ, ์ฑ๋ฅ ํ ์คํธ์ ์ ์ฉ
- ๋จ์ : ๋ผ์ฐํ ์ค์ ์ ๋ณต์ก์ฑ, ํธ๋ํฝ ๋ถ์ฐ ์ฅ๋น ํ์, ํธ๋ํฝ ์ ํ ๋์ ์ธํ๋ผ 2๋ฐฐ ํ์
โ ๋ง์ง ์์ ํธ๋ํฝ ์ค ์ผ๋ถ๋ฅผ ๋์์ผ๋ก ๋ถ์ํด์ผํ๋ฏ๋ก, ํธ๋ํฝ์ด ์ ์ ํ ์์ ์์๋ ํจ์จ์ฑ์ด ๋จ์ด์ ธ ๋ฐฐ์ (10%๋ฅผ ์ฐ์ ๋ณด๋ด๋ ์ํฉ ๊ฐ์ ์ V1 ํธ๋ํฝ ํผํฌ 100๋ช ๊ธฐ์ค 10๋ช ๋ง ์ ๋ฒ์ ์ผ๋ก ์ ํ)
-
์ ฐ๋์ฐ ๋ฐฐํฌ
- ํธ๋ํฝ ์ผ๋ถ๋ฅผ ์ ๋ฒ์ ์ ์ ๋ฌํ๋ฉฐ, ์๋ต์ ์ฌ์ฉ์์๊ฒ ๋ฐํํ์ง ์๋ ๋ฐฉ์
- ์นด๋๋ฆฌ ์ ๋ต๊ณผ์ ์ฐจ์ด์
- ์นด๋๋ฆฌ๋ ์ผ๋ถ ํธ๋ํฝ์ ๋์์ผ๋ก ํ ์คํธ๋ฅผ ์๋. ์ ฐ๋์ฐ๋ ์ ์ฒด ํธ๋ํฝ ๋์
- ์นด๋๋ฆฌ๋ ์ ๋ฒ์ ๋ฌธ์ ๋ฐ์ ์ ์ผ๋ถ ํธ๋ํฝ์ด ์ํฅ ๋ฐ์. ์ ฐ๋์ฐ๋ ์ํฅ ์์.
- ์ฅ์ : ์ค์ ํธ๋ํฝ์ ํตํ ๊ฒ์ฆ ๊ฐ๋ฅ, ์ฌ์ฉ์ ๊ฒฝํ ๋ฌด์์ค, ์ ๋ฒ์ ๋์ ์ค์๊ฐ ํผ๋๋ฐฑ ๊ฐ๋ฅ, ์ ๋ฒ์ ๋ฌธ์ ๋ฐ์์์๋ ์๋น์ค ์ํฅ ์์
- ๋จ์ : ํธ๋ํฝ ์ค์ ๋ณต์ก์ฑ, ์ธํ๋ผ ๋๋ฐฐ ํ์, Shadow ํธ๋ํฝ ์๋ต ๋ถ์์ ์ํ ์ถ๊ฐ ๋๊ตฌ ํ์, ํธ๋ํฝ ์ ํ ๋์ ์ธํ๋ผ 2๋ฐฐ ํ์
โ ๊ทน๋จ์ ์ผ๋ก ๋์ ์ ๋ขฐ์ฑ์ ์๊ตฌํ๋ ๊ฒฝ์ฐ ์ ์ ํ๊ฒ ์ผ๋, ์ฆ์ ๋กค๋ฐฑ ๊ฐ๋ฅํ๋ฏ๋ก ์ต์ฐ์ ๊ณ ๋ คํ์ง ์์.
โ ๋ธ๋ฃจ/๊ทธ๋ฆฐ ๋ฐฐํฌ ์ ์
3. ๋ฐฐํฌ ํ์ดํ๋ผ์ธ ์์ธ ์ค๊ณ
์ด์ ๋จ๊ณ์ ์์ฌ๊ฒฐ์ ์ ๋ฐํ์ผ๋ก CD ๊ณผ์ ์ ๊ตฌ์ฒด์ ์ผ๋ก ์ค๊ณ.
3.1. ์ ์ฒด ํ์ดํ๋ผ์ธ ํ๋ฆ๋
- CI ์๋ฃ(S3) โ ๋ธ๋์น ๋ถ๊ธฐ โ ํ๊ฒฝ๋ณ(Dev/Staging/Prod) ๋ฐฐํฌ ํ๋ก์ธ์ค ๋ค์ด์ด๊ทธ๋จ
3.2. ์๋ฒ ๋ด๋ถ ๋ฐฐํฌ/๋กค๋ฐฑ ํ๋ฆ๋ (Micro View)
- CodeDeploy Agent ๋์ โ ํฌํธ ํ์ธ โ Blue/Green ์ค์์นญ โ ํฌ์ค ์ฒดํฌ โ ๋กค๋ฐฑ/์ฑ๊ณต ํ์ ๋ฉ์ปค๋์ฆ
3.2.1. ๊ฐ๋ฐ์๋ฒ
๋ฐฐํฌ ๋ฐฉ์ ์์ฝ
- In Place ๋ฐฐํฌ(๋งํฌ)
-
Stop: ๋๊ณ ์๋, ๊ตฌ๋ฒ์ ํ๋ก์ธ์ค ๋ฐ ์๋ฒ๋ฅผ ์ข ๋ฃ(๋ค์ดํ์ ํ์ฉ)
-
Update: Artifact Repo์ธ S3์์ ์ต์ ํ์ผ์ ๋ฐ์์ ๋ฎ์ด์ฐ๊ธฐ
-
Start: ์ ๋ฒ์ ์ ๋ฐ์์ ์คํ
-
๊ฒ์ฆ: ์ ๋ฒ์ ์ ์ ์ ์คํ ์ฌ๋ถ๋ฅผ ๊ฒ์ฌ
4.1. ์ ์ ์คํ: ๋ฐฐํฌ ์๋ฃ ์๋ฆผ ๋ฐ ํ์ ์์
4.2. ์ค๋ฅ ๋ฐ์: ์คํจ ์๋ฆผ ๋ฐ ํ๊ฒฝ ๋ณด์กด
-
๋กค๋ฐฑ: ๋๋ฒ๊น ํ ์๋ ํธ๋ฆฌ๊ฑฐ ์ดํ ์ด์ ๋ฒ์ ์ผ๋ก ๋กค๋ฐฑ ๋ฐ ์ ๋ฒ์ ์ผ๋ก ์ ๊ท ๋ฐฐํฌ
ํ์ ์ค์ ์ฌํญ
-
EC2 ์ค์
-
์ธ์คํด์ค ์ค์ : t3.medium
-
์ค์ ๋ฉ๋ชจ๋ฆฌ ์ค์ : 4GB
-
์๋ฒ ์ด๊ธฐ ์ค์ ์คํฌ๋ฆฝํธ (๊ฐ์ข ์์กด์ฑ ๋ฒ์ ์ ์คํ์ ๋ง๊ฒ ํ์ ํ ์์ ์์ )
#!/bin/bash # 1. ์์คํ ์ ๋ฐ์ดํธ ๋ฐ ํ์ ํจํค์ง ์ค์น sudo apt-get update -y sudo apt-get install -y ruby wget curl git jq awscli # 2. Swap ๋ฉ๋ชจ๋ฆฌ 4GB ์ค์ sudo fallocate -l 4G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab # 3. CodeDeploy Agent ์ค์น cd /home/ubuntu wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install chmod +x ./install sudo ./install auto # 4. Runtimes ์ค์น # 4-1. Java 21 wget -O - https://packages.adoptium.net/artifactory/api/gpg/key/public | sudo apt-key add - echo "deb https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main" | sudo tee /etc/apt/sources.list.d/adoptium.list sudo apt-get update sudo apt-get install -y temurin-21-jdk # 4-2. Node.js 20 & PM2 curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt-get install -y nodejs sudo npm install -g pm2 # 4-3. Python 3.10 & venv sudo apt-get install -y python3.10 python3.10-venv python3-pip # 5. ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ ์์ฑ ๋ฐ ๊ถํ ์ค์ mkdir -p /home/ubuntu/apps/{backend,frontend,ai} sudo chown -R ubuntu:ubuntu /home/ubuntu/apps
-
-
AWS CodeDeploy ๋ฐฐํฌ ๊ทธ๋ฃน ์ค์
- ๋ฐฐํฌ ์ ํ: In-Place ๋ฐฐํฌ
- ๋ฐฐํฌ ๊ตฌ์ฑ:
CodeDeployDefault.AllAtOnceย (ํ ๋ฒ์ ๋ฐฐํฌ) - IAM Role ์ค์ :ย Parameter Store๋ฅผ ํตํ ํ๊ฒฝ๋ณ์ ์ฃผ์
์ ์ํด
AmazonSSMReadOnlyAccessย ๊ถํ ์ค์ - ๋ก๋๋ฐธ๋ฐ์: ์ค์ ํด์
- ๋กค๋ฐฑ ๊ตฌ์ฑ: ํด์
-
์๋น์ค ๋ณ ์ค์ ๋ฐ ์คํฌ๋ฆฝํธ
-
BE(Spring Boot)
-
๊ฒฝ๋ก: /home/ubuntu/apps/backend
-
ํฌํธ: 8080
-
appspec.ymlversion: 0.0 os: linux files: - source: / destination: /home/ubuntu/apps/backend overwrite: true permissions: - object: /home/ubuntu/apps/backend/scripts pattern: "**" owner: ubuntu group: ubuntu mode: 755 hooks: ApplicationStop: - location: scripts/stop.sh timeout: 60 runas: ubuntu ApplicationStart: - location: scripts/start.sh timeout: 60 runas: ubuntu ValidateService: - location: scripts/validate.sh timeout: 60 runas: ubuntu -
stop.sh#!/bin/bash # ์คํ ์ค์ธ JAR ํ๋ก์ธ์ค ์ฐพ๊ธฐ PID=$(pgrep -f 'backend.jar') if [ -z "$PID" ]; then echo ">>> ๊ตฌ๋ ์ค์ธ ๋ฐฑ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์์ต๋๋ค." else echo ">>> ํ๋ก์ธ์ค ์ข ๋ฃ ์๋ (PID: $PID)" kill -15 $PID # Graceful Shutdown sleep 5 fi -
start.sh#!/bin/bash PROJECT_ROOT="/home/ubuntu/apps/backend" DISCORD_SCRIPT="$PROJECT_ROOT/scripts/utils/discord.sh" cd $PROJECT_ROOT # [์๋ฌ ํธ๋ค๋ฌ] ์คํฌ๋ฆฝํธ ์คํ ์ค ์๋ฌ ๋ฐ์(ERR) ์ ์คํ๋จ error_handler() { echo ">>> [Error] ์คํฌ๋ฆฝํธ ์คํ ์คํจ" bash $DISCORD_SCRIPT "๋ฐฐํฌ ์คํจ (Backend)" "ApplicationStart ๋จ๊ณ์์ ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค.\n๋ก๊ทธ๋ฅผ ํ์ธํด์ฃผ์ธ์." "fail" } # ์๋ฌ(ERR) ์๊ทธ๋์ ๋ฐ์ผ๋ฉด error_handler ํจ์ ์คํ trap 'error_handler' ERR # -------------------------------------------------------- # ์์ ์๋ฆผ ์ ์ก echo ">>> [Start] ๋ฐฑ์๋ ๋ฐฐํฌ ์์" bash $DISCORD_SCRIPT "๋ฐฐํฌ ์์ (Backend)" "Spring Boot ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ๋์ ์์ํฉ๋๋ค." "info" # ์ฑ ์คํ nohup java -jar backend.jar > app.log 2>&1 & # nohup์ ์ฑ๊ณตํด๋ ๋ฐ๋ก ๋ฆฌํด. ํ๋ก์ธ์ค๊ฐ ์ง์ง ๋ด๋์ง 1์ด ๋ค ํ์ธ sleep 2 PID=$(pgrep -f 'backend.jar') if [ -z "$PID" ]; then echo ">>> ํ๋ก์ธ์ค๊ฐ ์คํ๋์ง ์์์ต๋๋ค." exit 1 # ์ฌ๊ธฐ์ ์๋ฌ๊ฐ ๋๋ฉด ์ trap์ด ๋ฐ๋ํด์ ๋์ค์ฝ๋ ์๋ฆผ fi echo ">>> [Start] ํ๋ก์ธ์ค ์คํ ํ์ธ๋จ (PID: $PID)" -
validate.sh#!/bin/bash PROJECT_ROOT="/home/ubuntu/apps/backend" DISCORD_SCRIPT="$PROJECT_ROOT/scripts/utils/discord.sh" # [์๋ฌ ํธ๋ค๋ฌ] error_handler() { bash $DISCORD_SCRIPT "ํฌ์ค ์ฒดํฌ ์คํจ (Backend)" "์๋ฒ๊ฐ ์ ์์ ์ผ๋ก ์๋ตํ์ง ์์ต๋๋ค (Timeout/500).\nํ์ธ์ด ํ์ํฉ๋๋ค." "fail" } trap 'error_handler' ERR # -------------------------------------------------------- echo ">>> [Validate] Health Check ์์" for i in {1..10}; do response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/health) if [ "$response" == "200" ]; then echo ">>> Health Check ์ฑ๊ณต (200 OK)" # ์ต์ข ์ฑ๊ณต ์๋ฆผ bash $DISCORD_SCRIPT "๋ฐฐํฌ ์๋ฃ (Backend)" "ํฌ์ค ์ฒดํฌ ํต๊ณผ. ์ ์ ์๋น์ค ์ค์ ๋๋ค." "success" exit 0 fi bash $DISCORD_SCRIPT "๊ตฌ๋ ๋๊ธฐ์ค... ($i/10)" "์๋ต ์ฝ๋: $response" "info" sleep 10 done # ๋ฐ๋ณต๋ฌธ์ด ๋๋ ๋๊น์ง 200์ด ์ ๋์ค๋ฉด ์๋ฌ ์ฒ๋ฆฌ echo ">>> Health Check ์ต์ข ์คํจ" exit 1 # -> trap ๋ฐ๋ -> ์คํจ ์๋ฆผ ์ ์ก
-
-
FE(Next.js)
-
๊ฒฝ๋ก: /home/ubuntu/apps/frontend
-
ํฌํธ: 3000
-
appspec.ymlversion: 0.0 os: linux files: - source: / destination: /home/ubuntu/apps/frontend overwrite: true permissions: - object: /home/ubuntu/apps/frontend/scripts pattern: "**" owner: ubuntu group: ubuntu mode: 755 hooks: ApplicationStop: - location: scripts/stop.sh timeout: 60 runas: ubuntu AfterInstall: - location: scripts/install_dependencies.sh timeout: 300 runas: ubuntu ApplicationStart: - location: scripts/start.sh timeout: 60 runas: ubuntu ValidateService: - location: scripts/validate.sh timeout: 60 runas: ubuntu -
stop.sh#!/bin/bash echo ">>> [Stop] Frontend ํ๋ก์ธ์ค ์ข ๋ฃ" pm2 delete frontend || true -
install_dependencies.sh๐ก
NEXT_PUBLIC_ํ๊ฒฝ๋ณ์๋ ์คํ ๋จ๊ณ์์ ์ฃผ์ ํ ์ ์์ผ๋ฉฐ, ๋ฐ์๋์ง ์์. ๋น๋๊ฐ ์งํ๋๋ CI์์ ์ฃผ์ ํ์!#!/bin/bash PROJECT_ROOT="/home/ubuntu/apps/frontend" DISCORD_SCRIPT="$PROJECT_ROOT/scripts/utils/discord.sh" cd $PROJECT_ROOT # [์๋ฌ ํธ๋ค๋ฌ] error_handler() { echo ">>> [Error] ์์กด์ฑ ์ค์น ์คํจ" bash $DISCORD_SCRIPT "๋ฐฐํฌ ์คํจ (Frontend)" "AfterInstall(npm ci) ๋จ๊ณ์์ ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค." "fail" } trap 'error_handler' ERR # -------------------------------------------------------- echo ">>> [Install] Production ์์กด์ฑ ์ค์น" # CI์์ node_modules๋ฅผ ๊ฐ์ ธ์ค์ง ์์์ผ๋ฏ๋ก ์ฌ๊ธฐ์ ์ค์น npm ci --only=production -
start.sh#!/bin/bash PROJECT_ROOT="/home/ubuntu/apps/frontend" DISCORD_SCRIPT="$PROJECT_ROOT/scripts/utils/discord.sh" cd $PROJECT_ROOT # [์๋ฌ ํธ๋ค๋ฌ] error_handler() { echo ">>> [Error] PM2 ์คํ ์คํจ" bash $DISCORD_SCRIPT "๋ฐฐํฌ ์คํจ (Frontend)" "ApplicationStart(PM2) ๋จ๊ณ์์ ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค." "fail" } trap 'error_handler' ERR # -------------------------------------------------------- # ์์ ์๋ฆผ bash $DISCORD_SCRIPT "๋ฐฐํฌ ์์ (Frontend)" "Next.js ์ ํ๋ฆฌ์ผ์ด์ (PM2) ๊ตฌ๋์ ์์ํฉ๋๋ค." "info" echo ">>> [Start] Frontend(PM2) ์คํ" # ๊ธฐ์กด ๋ก๊ทธ ๋น์ฐ๊ธฐ pm2 flush frontend || true # PM2 ์์ (์ด๋ฏธ ๋ ์์ผ๋ฉด ๋ฆฌ์คํํธ, ์์ผ๋ฉด ์์) # ํ๊ฒฝ๋ณ์๋ ๋น๋ ํ์ ์ฃผ์ or ๋ฐํ์ ์ฝ๋ ๋ ๋ฒจ ์ฒ๋ฆฌ ๊ฐ์ pm2 start npm --name "frontend" -- start -
validate.sh#!/bin/bash PROJECT_ROOT="/home/ubuntu/apps/frontend" DISCORD_SCRIPT="$PROJECT_ROOT/scripts/utils/discord.sh" # [์๋ฌ ํธ๋ค๋ฌ] error_handler() { bash $DISCORD_SCRIPT "ํฌ์ค ์ฒดํฌ ์คํจ (Frontend)" "์๋ฒ๊ฐ ์ ์์ ์ผ๋ก ์๋ตํ์ง ์์ต๋๋ค (Timeout/500)." "fail" } trap 'error_handler' ERR # -------------------------------------------------------- echo ">>> [Validate] Frontend ๊ตฌ๋ ํ์ธ" for i in {1..10}; do # Next.js ๋ฉ์ธ ํ์ด์ง(3000ํฌํธ) ํ์ธ response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000) if [ "$response" == "200" ]; then echo ">>> Frontend ์ ์ ๋์ ํ์ธ" # ์ต์ข ์ฑ๊ณต ์๋ฆผ bash $DISCORD_SCRIPT "๋ฐฐํฌ ์๋ฃ (Frontend)" "ํฌ์ค ์ฒดํฌ ํต๊ณผ! ์ ์ ์๋น์ค ์ค์ ๋๋ค." "success" exit 0 fi bash $DISCORD_SCRIPT "๊ตฌ๋ ๋๊ธฐ์ค... ($i/10)" "์๋ต ์ฝ๋: $response" "info" sleep 5 done echo ">>> Frontend ๊ตฌ๋ ์คํจ" exit 1 # -> trap ๋ฐ๋
-
-
AI(FastAPI)
-
๊ฒฝ๋ก: /home/ubuntu/apps/ai
-
ํฌํธ: 8000
-
appspec.ymlversion: 0.0 os: linux files: - source: / destination: /home/ubuntu/apps/ai overwrite: true permissions: - object: /home/ubuntu/apps/ai/scripts pattern: "**" owner: ubuntu group: ubuntu mode: 755 hooks: ApplicationStop: - location: scripts/stop.sh timeout: 60 runas: ubuntu AfterInstall: - location: scripts/setup_env.sh timeout: 60 runas: ubuntu ApplicationStart: - location: scripts/start.sh timeout: 180 # AI ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ก๋ฉ ์๊ฐ ๊ณ ๋ ค runas: ubuntu -
stop.sh#!/bin/bash PID=$(pgrep -f 'uvicorn main:app') if [ -z "$PID" ]; then echo ">>> ๊ตฌ๋ ์ค์ธ AI ์ ํ๋ฆฌ์ผ์ด์ ์ด ์์ต๋๋ค." else kill -15 $PID sleep 3 fi -
start.sh#!/bin/bash PROJECT_ROOT="/home/ubuntu/apps/ai" VENV_PATH="$PROJECT_ROOT/venv" DISCORD_SCRIPT="$PROJECT_ROOT/scripts/utils/discord.sh" cd $PROJECT_ROOT # [์๋ฌ ํธ๋ค๋ฌ] error_handler() { echo ">>> [Error] AI ์๋ฒ ์คํ ์คํจ" bash $DISCORD_SCRIPT "๋ฐฐํฌ ์คํจ (AI)" "ApplicationStart(Pip/Uvicorn) ๋จ๊ณ์์ ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค.\n๋ก๊ทธ๋ฅผ ํ์ธํด์ฃผ์ธ์." "fail" } trap 'error_handler' ERR # -------------------------------------------------------- # ์์ ์๋ฆผ bash $DISCORD_SCRIPT "๋ฐฐํฌ ์์ (AI)" "FastAPI ๊ฐ์ํ๊ฒฝ ์ค์ ๋ฐ ๊ตฌ๋์ ์์ํฉ๋๋ค." "info" echo ">>> [Start] ๊ฐ์ํ๊ฒฝ ์ค์ ๋ฐ ํจํค์ง ์ค์น" if [ ! -d "$VENV_PATH" ]; then echo ">>> ๊ฐ์ํ๊ฒฝ ์์ฑ ์ค..." python3 -m venv $VENV_PATH fi # ๊ฐ์ํ๊ฒฝ ํ์ฑํ source $VENV_PATH/bin/activate # ์์กด์ฑ ์ค์น pip install -r requirements.txt echo ">>> [Start] Uvicorn ์๋ฒ ์คํ" # ํ๊ฒฝ๋ณ์๋ Python ์ฝ๋(boto3) ๋ด๋ถ์์ ๋ก๋๋จ nohup uvicorn main:app --host 0.0.0.0 --port 8000 > app.log 2>&1 & # ํ๋ก์ธ์ค ์์ฑ ํ์ธ sleep 2 PID=$(pgrep -f 'uvicorn main:app') if [ -z "$PID" ]; then echo ">>> ํ๋ก์ธ์ค๊ฐ ์คํ๋์ง ์์์ต๋๋ค." exit 1 # trap ๋ฐ๋ fi echo ">>> PID: $PID ์คํ ํ์ธ๋จ" -
validate.sh#!/bin/bash PROJECT_ROOT="/home/ubuntu/apps/ai" DISCORD_SCRIPT="$PROJECT_ROOT/scripts/utils/discord.sh" # [์๋ฌ ํธ๋ค๋ฌ] error_handler() { bash $DISCORD_SCRIPT "ํฌ์ค ์ฒดํฌ ์คํจ (AI)" "AI ์๋ฒ๊ฐ ์๋ตํ์ง ์์ต๋๋ค. (/health ์๋ํฌ์ธํธ ํ์ธ ํ์)" "fail" } trap 'error_handler' ERR # -------------------------------------------------------- echo ">>> [Validate] AI Server Health Check" for i in {1..10}; do response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/health) if [ "$response" == "200" ]; then echo ">>> AI Server ์ ์ ๋์ ํ์ธ" # ์ต์ข ์ฑ๊ณต ์๋ฆผ bash $DISCORD_SCRIPT "๋ฐฐํฌ ์๋ฃ (AI)" "ํฌ์ค ์ฒดํฌ ํต๊ณผ! ๋ชจ๋ธ ์๋น ์ค๋น ์๋ฃ." "success" exit 0 fi bash $DISCORD_SCRIPT "AI ๊ตฌ๋ ๋๊ธฐ์ค... ($i/10)" "์๋ต ์ฝ๋: $response" "info" sleep 5 done echo ">>> AI Server ๊ตฌ๋ ์คํจ" exit 1 # -> trap ๋ฐ๋
-
-
3.2.2. ์คํ ์ด์ง ์๋ฒ
๋ฐฐํฌ๋ฐฉ์ ์์ฝ
- Blue/Green ๋ฐฐํฌ
- ๋จ์ผ ์ธ์คํด์ค ํ๊ฒฝ์์ Blue/Green ๊ตฌํํ๊ธฐ ์ํด Port ์ค์์นญ ์ฌ์ฉ
- Spot Instance ์ฌ์ฉ์ผ๋ก ๋น์ฉ ์ ๊ฐ
- AWS ASG๋ฅผ ์ฌ์ฉํ์ฌ Spot Instance ์ค๋จ ์ ์๋ ๋ณต๊ตฌ ์ค์
- ๋ฐฐํฌ ์ ์ธ์คํด์ค๊ฐ ๊บผ์ ธ์๋ ๊ฒฝ์ฐ, ASG๊ฐ ์ ์ธ์คํด์ค ์คํ ํ ๋ฐฐํฌ ์งํ
- Port Switching์ผ๋ก ์ธํ ์ผ์์ ๋ฆฌ์์ค 2๋ฐฐ ์๋ชจ์ ๋ํ ๋์
- ํ์ ํ ์ฌ๋ฌ ํด๊ฒฐ์ฑ
์ค, ์๋น์ค ๊ตฌํ์ ๋ฐ๋ผ ํ1 ํ์ฌ ๋์
- JVM ํ ๋ฉ๋ชจ๋ฆฌ ์ฉ๋ ์ ํ(์คํ ์
-Xmx์ต์ ์ ์ฉ) - Swap ๋ฉ๋ชจ๋ฆฌ ์ ๊ทน์ ์ค์ (๊ธฐ๋ณธ 4GB ์ค์ ์์ )
- Startup CPU ์ ์ด( BE ์์์ CPU ์ฌ์ฉ๋ฅ ์ฆ๊ฐ. t3 Burst ๊ธฐ๋ฅ ์ฌ์ฉ(๊ธฐ๋ณธ ๋์))
- OOM Killer ์ฐ์ ์์ ์กฐ์ (์๋ก ๋ฌ ํ๋ก์ธ์ค์ ๋์ ์ ์๋ฅผ ์ค ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ์ ์ ํ๋ก์ธ์ค ํฌ์)
- JVM ํ ๋ฉ๋ชจ๋ฆฌ ์ฉ๋ ์ ํ(์คํ ์
- ํ์ ํ ์ฌ๋ฌ ํด๊ฒฐ์ฑ
์ค, ์๋น์ค ๊ตฌํ์ ๋ฐ๋ผ ํ1 ํ์ฌ ๋์
ํ์ ์ค์ ์ฌํญ
-
EC2 ๋ฐ ASG
-
์ธ์คํด์ค ํ์ : t3.medium(Spot Instance)
-
์ค์๋ฉ๋ชจ๋ฆฌ: 4GB
-
์๋ฒ ์ด๊ธฐ ์ค์ ์คํฌ๋ฆฝํธ (ํฌํธ์ค์์นญ์ ์ํ Nginx ์ค์ ํฌํจ)
#!/bin/bash # 1. ์์คํ ์ ๋ฐ์ดํธ ๋ฐ ํ์ ํจํค์ง ์ค์น sudo apt-get update -y sudo apt-get install -y ruby wget curl git jq awscli # 2. Swap ๋ฉ๋ชจ๋ฆฌ 4GB ์ค์ (ํ์) sudo fallocate -l 4G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab # 3. CodeDeploy Agent ์ค์น cd /home/ubuntu wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install chmod +x ./install sudo ./install auto # 4. Runtimes ์ค์น # 4-1. Java 21 (Temurin) wget -O - https://packages.adoptium.net/artifactory/api/gpg/key/public | sudo apt-key add - echo "deb https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main" | sudo tee /etc/apt/sources.list.d/adoptium.list sudo apt-get update sudo apt-get install -y temurin-21-jdk # 5. ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ ์์ฑ ๋ฐ ๊ถํ ์ค์ # ๊ฐ ์๋น์ค(Backend, Frontend, AI)๊ฐ ์์นํ ํด๋๋ฅผ ๋ฏธ๋ฆฌ ์์ฑํฉ๋๋ค. mkdir -p /home/ubuntu/apps/{backend,frontend,ai} # ์์ฑ๋ ํด๋์ ์์ ๊ถ์ ubuntu ์ ์ ์๊ฒ ๋ถ์ฌํ์ฌ # CodeDeploy Agent(ubuntu ์ ์ ๋ก ์คํ๋จ)๊ฐ ์ฐ๊ธฐ ๊ถํ์ ๊ฐ๋๋ก ํฉ๋๋ค. sudo chown -R ubuntu:ubuntu /home/ubuntu/apps # 6. [Staging ์ ์ฉ] Nginx Blue/Green ์ด๊ธฐ ์ค์ (BE + AI) sudo apt-get install -y nginx # 6-1. Backend์ฉ ๋์ ํฌํธ ํ์ผ (๊ธฐ๋ณธ 8080) echo 'set $service_url http://127.0.0.1:8080;' | sudo tee /etc/nginx/conf.d/service-url.inc # 6-2. AI์ฉ ๋์ ํฌํธ ํ์ผ (๊ธฐ๋ณธ 8000) echo 'set $ai_service_url http://127.0.0.1:8000;' | sudo tee /etc/nginx/conf.d/ai-service-url.inc # 6-3. Nginx ๋ฉ์ธ ์ค์ (Reverse Proxy) cat <<EOF | sudo tee /etc/nginx/sites-available/default server { listen 80; server_name _; # Backend (Root) include /etc/nginx/conf.d/service-url.inc; location / { proxy_pass \$service_url; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; } # AI Server (Prefix: /api/ai ๋๋ ๋ด๋ถ ํธ์ถ์ฉ ํฌํธ ๋ถ๋ฆฌ ๊ฐ๋ฅ) # ์ฌ๊ธฐ์๋ /ai ๊ฒฝ๋ก๋ก ๋ค์ด์ค๋ฉด AI ์๋ฒ๋ก ๋ณด๋ธ๋ค๊ณ ๊ฐ์ include /etc/nginx/conf.d/ai-service-url.inc; location /ai/ { proxy_pass \$ai_service_url; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; # FastAPI ๋ฌธ์(Swagger) ๋ฑ์ ์ํด rewrite ํ์ํ ์ ์์ rewrite ^/ai/(.*)\$ /\$1 break; } } EOF sudo nginx -t sudo systemctl restart nginx
-
-
AWS CodeDeploy ๋ฐฐํฌ ๊ทธ๋ฃน ์ค์
- ๋ฐฐํฌ ์ ํ: In-Place ๋ฐฐํฌ(๋จ์ผ ์๋ฒ์ด๋ฏ๋ก AWS ์ ์ฅ์์๋ In-Place / ๋ด๋ถ ์คํฌ๋ฆฝํธ๋ก Blue/Green ๊ตฌํ)
- ํ๊ฒฝ ๊ตฌ์ฑ: Auto Scaling Group์ ํ
- ๋ฐฐํฌ ์ค์ :
CodeDeployDefault.OneAtATime - IAM Role:
AmazonSSMReadOnlyAccessย ํ์ - ๋ก๋๋ฐธ๋ฐ์:ย ์ค์ ํด์ (Nginx๊ฐ ๋ก๋๋ฐธ๋ฐ์ ์ญํ )
-
์๋น์ค ๋ณ ์ค์ ๋ฐ ์คํฌ๋ฆฝํธ
-
BE(Spring Boot)
-
์ ๋ต: Blue/Green ๋ฐฐํฌ 8080โ 8081 ํฌํธ์ค์์นญ
-
appspec.ymlversion: 0.0 os: linux files: - source: / destination: /home/ubuntu/apps/backend overwrite: true permissions: - object: /home/ubuntu/apps/backend/scripts pattern: "**" owner: ubuntu group: ubuntu mode: 755 hooks: # Stop ๋จ๊ณ๋ ์๋ต (์ ๋ฒ์ ๋์ฐ๊ณ ์ค์์นญ ํ ์ข ๋ฃํจ) ApplicationStart: - location: scripts/start.sh timeout: 120 runas: ubuntu ValidateService: - location: scripts/validate_and_switch.sh timeout: 180 runas: ubuntu -
start.shย (์ ํด ํฌํธ ์ฐพ๊ธฐ ๋ฐ ์คํ)#!/bin/bash PROJECT_ROOT="/home/ubuntu/apps/backend" DISCORD_SCRIPT="$PROJECT_ROOT/scripts/utils/discord.sh" cd $PROJECT_ROOT # ํ์ฌ ๊ตฌ๋ ์ค์ธ ํฌํธ ํ์ธ (service-url.inc ํ์ฑ) CURRENT_PORT=$(grep -oP '127.0.0.1:\K\d+' /etc/nginx/conf.d/service-url.inc) if [ "$CURRENT_PORT" == "8080" ]; then TARGET_PORT=8081 PROFILE="green" else TARGET_PORT=8080 PROFILE="blue" fi # ์์ ์๋ฆผ bash $DISCORD_SCRIPT "์คํ ์ด์ง ๋ฐฐํฌ ์์ (Backend)" "Target Port: $TARGET_PORT ($PROFILE)๋ก ๋ฐฐํฌ๋ฅผ ์์ํฉ๋๋ค." "info" # [3] ์ ํฌํธ์์ ์ฑ ์คํ (AWS SDK๊ฐ ํ๊ฒฝ๋ณ์ ๋ก๋) # -Dserver.port ์ต์ ์ผ๋ก ํฌํธ ๋์ ํ ๋น nohup java -jar -Dserver.port=$TARGET_PORT backend.jar > app.log 2>&1 & echo ">>> $TARGET_PORT ํฌํธ์์ ์ ํ๋ฆฌ์ผ์ด์ ์คํ ์ค..." -
validate_and_switch.shย (๊ฒ์ฆ ๋ฐ ์ค์์นญ ํ ๊ธฐ์กด ์๋น์ค ์ข ๋ฃ)#!/bin/bash PROJECT_ROOT="/home/ubuntu/apps/backend" DISCORD_SCRIPT="$PROJECT_ROOT/scripts/utils/discord.sh" # ํ์ฌ ์คํํ๋ ค๋ ํ๊ฒ ํฌํธ ์ฌํ์ธ CURRENT_PORT=$(grep -oP '127.0.0.1:\K\d+' /etc/nginx/conf.d/service-url.inc) if [ "$CURRENT_PORT" == "8080" ]; then TARGET_PORT=8081; else TARGET_PORT=8080; fi # Health Check echo ">>> Health Check ์์ (Port: $TARGET_PORT)" for i in {1..10}; do response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$TARGET_PORT/health) if [ "$response" == "200" ]; then break fi # ... (๋๊ธฐ ๋ก๊ทธ) ... # ์๋ ๋กค๋ฐฑ ๋ก์ง if [ $i -eq 10 ]; then echo ">>> [Auto Rollback] Health Check ์คํจ." bash $DISCORD_SCRIPT "๋ฐฐํฌ ์คํจ & ์๋ ๋กค๋ฐฑ (Backend)" "Nginx ์ ํ ์์ด ์ ๊ท ํ๋ก์ธ์ค๋ฅผ ์ข ๋ฃํฉ๋๋ค." "fail" # ์ ํ๋ก์ธ์ค ์ข ๋ฃ PID=$(lsof -t -i:$TARGET_PORT) if [ -n "$PID" ]; then kill -15 $PID; fi exit 1 # CodeDeploy Fail ์ฒ๋ฆฌ fi done # Nginx ์ค์์นญ (ํธ๋ํฝ ์ ํ) echo ">>> Nginx Port Switching ($CURRENT_PORT -> $TARGET_PORT)" echo "set \$service_url http://127.0.0.1:$TARGET_PORT;" | sudo tee /etc/nginx/conf.d/service-url.inc sudo nginx -s reload bash $DISCORD_SCRIPT "๋ฐฐํฌ ์๋ฃ (Staging)" "Nginx ํฌํธ ์ ํ ์๋ฃ. ($TARGET_PORT)" "success" # ๊ตฌ ๋ฒ์ ์ข ๋ฃ (Graceful Shutdown) echo ">>> ๊ตฌ ๋ฒ์ ํ๋ก์ธ์ค ์ข ๋ฃ (Port: $CURRENT_PORT)" OLD_PID=$(lsof -t -i:$CURRENT_PORT) if [ -n "$OLD_PID" ]; then kill -15 $OLD_PID; fi
-
-
FE(Next.js) + PM2
-
๋ฐฐํฌ ์ ๋ต: PM2์
reload๊ธฐ๋ฅ ํ์ฉํ์ฌ ๋ฌด์ค๋จ ์ฌ์์ ๊ตฌํ -
appspec.yml,install.sh,validate.sh๋ ๊ฐ๋ฐ์๋ฒ์ ๋์ผ -
start.sh#!/bin/bash PROJECT_ROOT="/home/ubuntu/apps/frontend" DISCORD_SCRIPT="$PROJECT_ROOT/scripts/utils/discord.sh" # ์์ ์๋ฆผ bash $DISCORD_SCRIPT "์คํ ์ด์ง ๋ฐฐํฌ ์์ (Frontend)" "PM2 Zero-downtime Reload๋ฅผ ์๋ํฉ๋๋ค." "info" cd $PROJECT_ROOT # PM2 Reload (ํ๋ก์ธ์ค๊ฐ ์์ผ๋ฉด Start, ์์ผ๋ฉด Reload) if pm2 list | grep -q "frontend"; then pm2 reload frontend --update-env echo ">>> PM2 Reload Complete" else pm2 start npm --name "frontend" -- start echo ">>> PM2 Start Complete" fi
-
-
AI(fastAPI)
-
๋ฐฐํฌ ์ ๋ต
-
appspec.ymlversion: 0.0 os: linux files: - source: / destination: /home/ubuntu/apps/ai overwrite: true permissions: - object: /home/ubuntu/apps/ai/scripts pattern: "**" owner: ubuntu group: ubuntu mode: 755 hooks: ApplicationStart: - location: scripts/start.sh timeout: 180 runas: ubuntu ValidateService: - location: scripts/validate_and_switch.sh timeout: 120 runas: ubuntu -
start.shย (ํฌํธ ๊ฒฐ์ ๋ฐ ์คํ)#!/bin/bash PROJECT_ROOT="/home/ubuntu/apps/ai" VENV_PATH="$PROJECT_ROOT/venv" DISCORD_SCRIPT="$PROJECT_ROOT/scripts/utils/discord.sh" cd $PROJECT_ROOT # ํ์ฌ AI ํฌํธ ํ์ธ (ai-service-url.inc ํ์ฑ) CURRENT_PORT=$(grep -oP '127.0.0.1:\K\d+' /etc/nginx/conf.d/ai-service-url.inc) if [ "$CURRENT_PORT" == "8000" ]; then TARGET_PORT=8001 PROFILE="green" else TARGET_PORT=8000 PROFILE="blue" fi bash $DISCORD_SCRIPT "์คํ ์ด์ง ๋ฐฐํฌ ์์ (AI)" "Target Port: $TARGET_PORT ($PROFILE)" "info" # ๊ฐ์ํ๊ฒฝ ๋ฐ ํจํค์ง ์ค์น if [ ! -d "$VENV_PATH" ]; then python3 -m venv $VENV_PATH; fi source $VENV_PATH/bin/activate pip install -r requirements.txt # ์ ํฌํธ์์ ์ฑ ์คํ # AWS SDK(boto3)๊ฐ ์ฝ๋ ๋ ๋ฒจ์์ ํ๊ฒฝ๋ณ์ ๋ก๋ํจ echo ">>> Uvicorn ์คํ (Port: $TARGET_PORT)" nohup uvicorn main:app --host 0.0.0.0 --port $TARGET_PORT > app.log 2>&1 & -
validate_and_switch.shย (๊ฒ์ฆ ๋ฐ ์๋ ๋กค๋ฐฑ ๋ก์ง)#!/bin/bash PROJECT_ROOT="/home/ubuntu/apps/ai" DISCORD_SCRIPT="$PROJECT_ROOT/scripts/utils/discord.sh" # ํ์ฌ ํฌํธ์ ํ๊ฒ ํฌํธ ๋ค์ ํ์ธ CURRENT_PORT=$(grep -oP '127.0.0.1:\K\d+' /etc/nginx/conf.d/ai-service-url.inc) if [ "$CURRENT_PORT" == "8000" ]; then TARGET_PORT=8001; else TARGET_PORT=8000; fi echo ">>> [Validate] AI Health Check (Port: $TARGET_PORT)" # Health Check (10ํ ์๋) for i in {1..10}; do response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$TARGET_PORT/health) if [ "$response" == "200" ]; then echo ">>> Health Check ์ฑ๊ณต!" break fi bash $DISCORD_SCRIPT "AI ๊ฒ์ฆ ๋๊ธฐ์ค... ($i/10)" "Port: $TARGET_PORT ์๋ต: $response" "info" sleep 5 # ์๋ ๋กค๋ฐฑ ๋ก์ง if [ $i -eq 10 ]; then echo ">>> [Auto Rollback] ๋ฐฐํฌ ์คํจ ๊ฐ์ง." bash $DISCORD_SCRIPT "๋ฐฐํฌ ์คํจ & ์๋ ๋กค๋ฐฑ (AI)" "Health Check ์คํจ. Nginx๋ฅผ ์ ํํ์ง ์๊ณ ์ ํ๋ก์ธ์ค๋ฅผ ์ข ๋ฃํฉ๋๋ค." "fail" # ์ ํ๋ก์ธ์ค ๊ฐ์ ์ข ๋ฃ (๋กค๋ฐฑ ์ํ) # fuser -k -n tcp $TARGET_PORT (๋๋ kill -9 PID) # Ubuntu์์๋ fuser๊ฐ ์์ ์ ์์ผ๋ฏ๋ก pgrep/kill ์ฌ์ฉ ๊ถ์ฅ PID=$(lsof -t -i:$TARGET_PORT) if [ -n "$PID" ]; then kill -9 $PID; fi # ์คํฌ๋ฆฝํธ ์คํจ ์ฒ๋ฆฌ -> CodeDeploy ์ํ 'Fail'๋ก ๊ธฐ๋ก๋จ exit 1 fi done # Nginx ์ค์์นญ (๋ฐฐํฌ ์ฑ๊ณต ์) echo ">>> [Switch] Nginx Port Switching ($CURRENT_PORT -> $TARGET_PORT)" echo "set \$ai_service_url http://127.0.0.1:$TARGET_PORT;" | sudo tee /etc/nginx/conf.d/ai-service-url.inc sudo nginx -s reload bash $DISCORD_SCRIPT "๋ฐฐํฌ ์๋ฃ (AI)" "Nginx ํฌํธ ์ ํ ์๋ฃ ($TARGET_PORT). ์ ์ ์๋น์ค ์ค์ ๋๋ค." "success" # ๊ตฌ ๋ฒ์ ์ข ๋ฃ (Graceful Shutdown) # ์ฌ์ฉ์์ AI API ์๋ต ๋๊ธฐ๋ฅผ ์ํด 30์ด ๋ถ์ฌ echo ">>> [Cleanup] ๊ตฌ ๋ฒ์ ์ข ๋ฃ (Port: $CURRENT_PORT)" OLD_PID=$(lsof -t -i:$CURRENT_PORT) if [ -n "$OLD_PID" ]; then kill -15 $OLD_PID; fi
-
-
3.2.3. ์ด์ ์๋ฒ
์ด์์๋ฒ ํน์ฑ
- ์์ ์ฑ ๋ฐ ๊ฐ์ฉ์ฑ์ด ์ต์ฐ์
- ์๋ ์น์ธ์ ํธ๋ฆฌ๊ฑฐ๋ก ๋ฐฐํฌ, ๋ณด์์ ์ธ ๋กค๋ฐฑ ๋ฐ ๋ชจ๋ํฐ๋ง ์ ๋ต ํ์
๋ฐฐํฌ ๋ฐฉ์ ์์ฝ
- Blue/Green ๋ฐฐํฌ(ํฌํธ ์ค์์นญ)
- ์๋ ์น์ธ
- ๋ณด์์ ๊ฒ์ฆ๊ณผ ๋กค๋ฐฑ
- ์คํ ์ด์ง๋ณด๋ค ๋ ๊ธด Health Check ๋ฐ ์ข ๋ฃ ๋๊ธฐ ์๊ฐ
- ๋ฐฐํฌ ์คํจ ์ ๋ฌด์กฐ๊ฑด ์๋ ๋กค๋ฐฑ ์คํ(ํธ๋ํฝ ์ ํ ์ฐจ๋จ)
- ๋ฐฐํฌ ์ฑ๊ณต ํ ๋ก์ง ์ ๋ฌธ์ ๋ฐ์ ์ CodeDeploy ์ฌ๋ฐฐํฌ ๊ธฐ๋ฅ์ ์ด์ฉํ ์๋ ๋กค๋ฐฑ ๊ฐ๋ฅ
ํ์ ์ค์ ์ฌํญ
-
์ธ์คํด์ค ํ์ : t3.large
-
์ค์๋ฉ๋ชจ๋ฆฌ: 4~8GB
-
์๋ฒ ์ด๊ธฐ ์ค์ ์คํฌ๋ฆฝํธ
- ์คํ ์ด์ง ์๋ฒ์ ์ค์๋ฉ๋ชจ๋ฆฌ ๋ฐ JVM ํ ๋ฉ๋ชจ๋ฆฌ ์ค์ ์ ์ธ ๋์ผ.
- Nginx ์ค์ ๋์ผ
-
AWS CodeDeploy ๋ฐฐํฌ ๊ทธ๋ฃน ์ค์
- ๋ฐฐํฌ ์ ํ: In-Place ๋ฐฐํฌ (๋จ์ผ ์ธ์คํด์ค ๋ด๋ถ ์คํฌ๋ฆฝํธ๋ก B/G ๊ตฌํ)
- ํ๊ฒฝ ๊ตฌ์ฑ:ย
CodeDeployDefault.OneAtATime - IAM Role:ย
AmazonSSMReadOnlyAccess - ๋ก๋๋ฐธ๋ฐ์: ์ค์ ํด์ (Nginx๊ฐ ์ญํ ๋์ฒด)
- ๋กค๋ฐฑ ๊ตฌ์ฑ: ๋ฐฐํฌ ์คํจ์ ์๋ ๋กค๋ฐฑ ํ์ฑํ
-
์๋น์ค ๋ณ ์ค์ ๋ฐ ์คํฌ๋ฆฝํธ
-
BE(Spring Boot)
-
appspec.yml: ์คํ ์ด์ง๊ณผ ๋์ผ -
start.sh(๋ฉ๋ชจ๋ฆฌ ์ค์ ์ฐจ์ด)#!/bin/bash PROJECT_ROOT="/home/ubuntu/apps/backend" DISCORD_SCRIPT="$PROJECT_ROOT/scripts/utils/discord.sh" cd $PROJECT_ROOT # ํ์ฌ ๊ตฌ๋ ์ค์ธ ํฌํธ ํ์ธ CURRENT_PORT=$(grep -oP '127.0.0.1:\K\d+' /etc/nginx/conf.d/service-url.inc) if [ "$CURRENT_PORT" == "8080" ]; then TARGET_PORT=8081 PROFILE="green" else TARGET_PORT=8080 PROFILE="blue" fi # ์์ ์๋ฆผ bash $DISCORD_SCRIPT "์ด์ ๋ฐฐํฌ ์์ (Backend)" "Target Port: $TARGET_PORT ($PROFILE)" "info" # ์ ํฌํธ์์ ์ฑ ์คํ (Prod ํ๊ฒฝ์ ๋ง์ถฐ ๋ฉ๋ชจ๋ฆฌ ์ํฅ) # t3.large(8GB) ๊ธฐ์ค: Heap 2GB~4GB ํ ๋น ์ค์ ํ์ nohup java -jar -Xms2048m -Xmx4096m -Dserver.port=$TARGET_PORT backend.jar > app.log 2>&1 & echo ">>> $TARGET_PORT ํฌํธ์์ ์ ํ๋ฆฌ์ผ์ด์ ์คํ ์ค..." -
validate_and_switch.shย (์ข ๋ฃ ๋๊ธฐ ์๊ฐ 60์ด ๋ฐ ํฌ์ค์ฒดํฌ ์ ์ฆ๊ฐ)#!/bin/bash PROJECT_ROOT="/home/ubuntu/apps/backend" DISCORD_SCRIPT="$PROJECT_ROOT/scripts/utils/discord.sh" CURRENT_PORT=$(grep -oP '127.0.0.1:\K\d+' /etc/nginx/conf.d/service-url.inc) if [ "$CURRENT_PORT" == "8080" ]; then TARGET_PORT=8081; else TARGET_PORT=8080; fi # Health Check (20ํ ์๋) echo ">>> Health Check ์์ (Port: $TARGET_PORT)" for i in {1..20}; do response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$TARGET_PORT/health) if [ "$response" == "200" ]; then break; fi bash $DISCORD_SCRIPT "๊ฒ์ฆ ๋๊ธฐ์ค... ($i/20)" "Port: $TARGET_PORT" "info" sleep 5 if [ $i -eq 20 ]; then bash $DISCORD_SCRIPT "โ ๋ฐฐํฌ ์คํจ & ์๋ ๋กค๋ฐฑ (Backend)" "Prod Health Check ์คํจ." "fail" PID=$(lsof -t -i:$TARGET_PORT) if [ -n "$PID" ]; then kill -15 $PID; fi exit 1 fi done # Nginx ์ค์์นญ echo "set \$service_url http://127.0.0.1:$TARGET_PORT;" | sudo tee /etc/nginx/conf.d/service-url.inc sudo nginx -s reload bash $DISCORD_SCRIPT "๋ฐฐํฌ ์๋ฃ (Production)" "Nginx ํฌํธ ์ ํ ์๋ฃ. ($TARGET_PORT)" "success" # ๊ตฌ ๋ฒ์ ์ข ๋ฃ (Grace Period 60์ด + graceful down 30์ด) echo ">>> ๊ธฐ์กด ์ฐ๊ฒฐ ์ข ๋ฃ ๋๊ธฐ (60์ด)..." sleep 60 OLD_PID=$(lsof -t -i:$CURRENT_PORT) if [ -n "$OLD_PID" ]; then kill -15 $OLD_PID; fi
-
-
FE(Next.js)
- ์คํ ์ด์ง ์๋ฒ์ ๋์ผํ ๋ฌด์ค๋จ ๋ฐฐํฌ ์ ๋ต
- ์คํ ์ด์ง ์๋ฒ์ ์คํฌ๋ฆฝํธ ์ ์ฒด ๋์ผ
-
AI(FastAPI)
-
start.sh: ์คํ ์ด์ง ์๋ฒ์ ๋์ผ -
validate_and_switch.sh: Grace Period 60์ดย ์ ์ฉ. ๊ธด ์ถ๋ก ์์ฒญ์ด ๋๊ธฐ์ง ์๋๋ก ๋ณดํธ.#!/bin/bash PROJECT_ROOT="/home/ubuntu/apps/ai" DISCORD_SCRIPT="$PROJECT_ROOT/scripts/utils/discord.sh" # ํ์ฌ ํฌํธ์ ํ๊ฒ ํฌํธ ๋ค์ ํ์ธ CURRENT_PORT=$(grep -oP '127.0.0.1:\K\d+' /etc/nginx/conf.d/ai-service-url.inc) if [ "$CURRENT_PORT" == "8000" ]; then TARGET_PORT=8001; else TARGET_PORT=8000; fi echo ">>> [Validate] AI Health Check (Port: $TARGET_PORT)" # Health Check (10ํ ์๋) for i in {1..10}; do response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$TARGET_PORT/health) if [ "$response" == "200" ]; then echo ">>> Health Check ์ฑ๊ณต!" break fi bash $DISCORD_SCRIPT "AI ๊ฒ์ฆ ๋๊ธฐ์ค... ($i/10)" "Port: $TARGET_PORT ์๋ต: $response" "info" sleep 5 # ์๋ ๋กค๋ฐฑ ๋ก์ง if [ $i -eq 10 ]; then echo ">>> [Auto Rollback] ๋ฐฐํฌ ์คํจ ๊ฐ์ง." bash $DISCORD_SCRIPT "๋ฐฐํฌ ์คํจ & ์๋ ๋กค๋ฐฑ (AI)" "Health Check ์คํจ. Nginx๋ฅผ ์ ํํ์ง ์๊ณ ์ ํ๋ก์ธ์ค๋ฅผ ์ข ๋ฃํฉ๋๋ค." "fail" # ์ ํ๋ก์ธ์ค ๊ฐ์ ์ข ๋ฃ (๋กค๋ฐฑ ์ํ) # fuser -k -n tcp $TARGET_PORT (๋๋ kill -9 PID) # Ubuntu์์๋ fuser๊ฐ ์์ ์ ์์ผ๋ฏ๋ก pgrep/kill ์ฌ์ฉ ๊ถ์ฅ PID=$(lsof -t -i:$TARGET_PORT) if [ -n "$PID" ]; then kill -9 $PID; fi # ์คํฌ๋ฆฝํธ ์คํจ ์ฒ๋ฆฌ -> CodeDeploy ์ํ 'Fail'๋ก ๊ธฐ๋ก๋จ exit 1 fi done # Nginx ์ค์์นญ (๋ฐฐํฌ ์ฑ๊ณต ์) echo ">>> [Switch] Nginx Port Switching ($CURRENT_PORT -> $TARGET_PORT)" echo "set \$ai_service_url http://127.0.0.1:$TARGET_PORT;" | sudo tee /etc/nginx/conf.d/ai-service-url.inc sudo nginx -s reload bash $DISCORD_SCRIPT "๋ฐฐํฌ ์๋ฃ (AI)" "Nginx ํฌํธ ์ ํ ์๋ฃ ($TARGET_PORT). ์ ์ ์๋น์ค ์ค์ ๋๋ค." "success" # ๊ตฌ ๋ฒ์ ์ข ๋ฃ (AI ์์ฒญ ๋ณดํธ๋ฅผ ์ํด 60์ด ๋๊ธฐ) echo ">>> ๊ธฐ์กด AI ์์ฒญ ์ข ๋ฃ ๋๊ธฐ (60์ด)..." sleep 60 OLD_PID=$(lsof -t -i:$CURRENT_PORT) if [ -n "$OLD_PID" ]; then kill -15 $OLD_PID; fi ``
-
-
4. ๊ธฐ์ ์ ๊ตฌํ ๋ช ์ธ
๋ฐฐํฌ ํ์ดํ๋ผ์ธ์ ์ค์ ์ธํ๋ผ ํ๊ฒฝ์์ ๊ตฌํํ๊ธฐ ์ํ ๊ธฐ์ ์ ์ธ๋ถ์ฌํญ ๋ฐ ์ค์ ๊ฐ ์ ์
4.1. ๋ฌด์ค๋จ ๋ฐฐํฌ ์ํคํ ์ฒ
- ๋์ ํฌํธ ๋ผ์ฐํ
(Dynamic Port Routing)
- Nginx๊ฐ ๊ณ ์ ๋ ํฌํธ๊ฐ ์๋, ๋ณ๋์ ์ค์ ํ์ผ์ ํตํด ๋์ ์ผ๋ก ํ๋ก์ ๋์์ ๋ณ๊ฒฝํ ์ ์๋๋ก ๊ตฌ์ฑ
- ๋งค์ปค๋์ฆ
- ๋ฐฐํฌ ๋ก์ง ์์ ์ Idle ํฌํธ๋ฅผ ์๋ณํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ๋
- Health Check ์ฑ๊ณต ์,
service-url.incํ์ผ์ ํฌํธ ์ ๋ณด ๋ฎ์ด์ฐ๊ธฐ nginx -s reload๋ช ๋ น์ด๋ฅผ ํตํด ๋ฌด์ค๋จ ์ค์ ๊ฐฑ์
- ํฌํธ ๊ฒฐ์ ๋ก์ง
- ํ์ฌ ์คํ์ค์ธ ํฌํธ๋ฅผ ํ์ธ ํ, ๋ฐฐํฌ ํ ํ์ผ ํฌํธ๋ฅผ ๊ฒฐ์ .
- ํฌํธ ๊ฐ์ง:
grep๋ช ๋ น์ด๋กservice-url.incํ์ผ ๋ด๋ถ์ ํฌํธ ๋ฒํธ ์ถ์ถ - ์ดํ ํฌํธ ํ ๊ธ ๋ก์ง์ ํตํด ๋ณ๊ฒฝ
4.2. ๋ฐํ์ ๋ฐ ๋ฆฌ์์ค ์ต์ ํ
๋จ์ผ ์ธ์คํด์ค ๋ด์์ ๋ค์์ ์๋น์ค(BE, FE, AI, Nginx)๊ฐ ๋์์ ๊ตฌ๋๋๋ฏ๋ก, ์๊ฒฉํ ๋ฆฌ์์ค ๊ด๋ฆฌ, ์ต์ ํ๊ฐ ์๊ตฌ๋จ.
- JVM ๋ฉ๋ชจ๋ฆฌ ์ ์ด
- ๋ฐฐํฌ ์ ์ผ์์ ์ผ๋ก ๋๊ฐ์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋์์ ์คํ๋๋ ๊ตฌ๊ฐ ๋ฐ์
- OOM(๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ)์ผ๋ก ์ธํ ์ข ๋ฃ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ํ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ ํ
- ๋ฐฐํฌ ์๊ฐ์ ๋ฉ๋ชจ๋ฆฌ ์คํ์ดํฌ ๋ฐฉ์ง๋ฅผ ์ํย
Xms,ยXmxย ํ ๋ฉ๋ชจ๋ฆฌ ์ ํ ์ค์ .
- Swap Memory ํ์ฉ
- ๋ฌผ๋ฆฌ ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ(OOM) ์ํฉ์ ๋ฐฉ์ดํ๊ธฐ ์ํด ์๋ฒ๋ณ๋ก 4~8GB Swap ๋ฉ๋ชจ๋ฆฌ ์ค์
fallocate๋ฅผ ์ฌ์ฉํ์ฌ ์ค์ ํ์ผ์ ์์ฑ,ย/etc/fstab์ ๋ฑ๋กํ์ฌ ์ฌ๋ถํ ํ์๋ ์ค์ ์ ์ง
- ์๋น์ค ๊ฒฉ๋ฆฌ
- ๊ฐ ์๋น์ค ๊ฐ ์ํธ ๊ฐ์ญ์ด ๋ฐ์ํ์ง ์๋๋ก ๋ ๋ฆฝ๋ ๋๋ ํ ๋ฆฌ๋ฅผ ๊ตฌ์ฑ
- ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ
- BE:
/home/ubuntu/apps/be - FE:
/home/ubuntu/apps/fe - AI:
/home/ubuntu/apps/ai
- BE:
4.3. AWS CodeDeploy ๊ตฌ์ฑ
AWS CodeDeploy์ Lifecycle Hooks ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐฐํฌ ๋จ๊ณ ์ธ๋ถํ ๋ฐ ๋จ๊ณ๋ณ ์คํฌ๋ฆฝํธ ๋ชจ๋ํ
4.3.1. Lifecycle Hooks
- ApplicationStop
- ๊ธฐ์กด ์คํ ์ค์ธ ์๋น์ค ํ๋ก์ธ์ค ํ์ธ ๋ฐ ์ข ๋ฃ
- B/G ๋ฐฐํฌ ์ ์๋ต (๋ฐฐํฌ ์คํจ ์ ๋ฆฌ์์ค ์ ๋ฆฌ์ ์ฌ์ฉ ๊ฐ๋ฅ)
- AfterInstall
- S3์์ ๋ค์ด๋ก๋ ํ Artifact์ ๊ถํ ์ค์ , ์์กด์ฑ ์ค์น ์งํ
- ApplicationStart
- ๊ฐ ์๋น์ค ๋ฐ ์ดํ๋ฆฌ์ผ์ด์ ์ ๋ฐฑ๊ทธ๋ผ์ด๋ ๋ชจ๋๋ก ์คํ(nohup, pm2)
- JVM ์ต์ , ํฌํธ ์ค์ ๋ฑ ์คํ์์ ์ค์ ๋ด์ฉ ์ ๋ฆฌ
- ValidateService
- ์คํ ๋ ์๋น์ค์ Health Check ์ํ
- ๊ฒ์ฆ ์ฑ๊ณต ์ Port Switch ๋ฐ ๊ตฌ ๋ฒ์ ์ดํ๋ฆฌ์ผ์ด์ ํ๋ก์ธ์ค ์ข ๋ฃ
4.3.2. ์คํฌ๋ฆฝํธ ๋ชจ๋ํ
์๋น์ค๋ณ, ๊ธฐ๋ฅ๋ณ๋ก ์คํฌ๋ฆฝํธ๋ฅผ ๋ถํ ํ์ฌ ์ ์ง๋ณด์์ฑ ํ๋ณด, ์ค๋ณต ๊ฐ์
scripts/start.sh: ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ๋ ๋ฐ ํฌํธ ํ ๋นscripts/stop.sh: ํ๋ก์ธ์ค ์๋ณ ๋ฐ ์ข ๋ฃ (Graceful Shutdown)scripts/validate_and_switch.sh: ํฌ์ค ์ฒดํฌ, Nginx Reload, ๊ตฌ ๋ฒ์ ์ ๋ฆฌ
4.4. ๋ณด์ ๊ฐํ ์ค์
- AWS ParameterStore์์ ํ๊ฒฝ๋ณ์ ์ฃผ์
.envํ์ผ ์์ฑ ๋ฐ ์ ์ก์ ํตํ ํ๊ฒฝ๋ณ์ ์ฃผ์ ์ ๋ณด์ ์ํ์ ์ผ๊ธฐ(ํ๋ฌธ ํค ๋ฐ PW ๋ ธ์ถ, ํด๋จผ์๋ฌ ๋ฑ)- ๋ชจ๋ ๋ฏผ๊ฐ์ ๋ณด๋ฅผ ์ํธํ ํ์ฌ ์ ์ฅ ๋ฐ ๊ด๋ฆฌ
- ์๋ฒ ๋ด ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ๋ ์ AWS SDK๋ฅผ ํตํด ๋ฉ๋ชจ๋ฆฌ์ ๊ฐ ๋ก๋
5. ๋ชจ๋ํฐ๋ง ๋ฐ ์ด์
๋ฐฐํฌ ํ์ดํ๋ผ์ธ์ ์ํ๋ฅผ ์ค์๊ฐ ์ถ์ , ์ด์ ์ฅ์ ๊ฐ์ง ๋ฐ ์ ํ, ๋ฐ์ดํฐ ๋ณดํธ๋ฅผ ์ํ ์ ๋ต ์๋ฆฝ
5.1. ์ค์๊ฐ ๋ฐฐํฌ ์๋ฆผ ์์คํ
CodeDeploy ์๋ฆผ์ ๋จ์ ์ฑ๊ณต/์คํจ ์ฌ๋ถ๋ง ํ์ธ ๊ฐ๋ฅ. ๊ฐ ๋จ๊ณ๋ณ(์์, ๊ฒ์ฆ, ์ข ๋ฃ ๋ฐ ๊ฒฐ๊ณผ)์ ์์ธํ ์์ธ์ ์ฆ๊ฐ ์ธ์งํ ์ ์๋๋ก ์๋ฆผ ์์คํ ์ ๊ตฌ์ถ
-
discord.sh- ๋์ค์ฝ๋ ์๋ฆผ์ ์ํ ์คํฌ๋ฆฝํธ
curl์ ์บก์ํํ์ฌ ์ฌ์ฌ์ฉ์ฑ์ ๋์ธ ์ ์คํฌ๋ฆฝํธ ๋ชจ๋ ๊ตฌํ.- Webhook URL์ ํ๋์ฝ๋ฉํ์ง ์๊ณ AWS Parameter Store์์ ์กฐํํ์ฌ ๋ณด์ ๊ฐํ
- ํ์ผ ๊ฒฝ๋ก:ย
/home/ubuntu/apps/{service}/scripts/utils/discord.sh(์๋น์ค๋ณ๋ก ๊ฐ๊ฐ ํฌํจ) - ๊ถํ:ย
755
#!/bin/bash # ------------------------------------------------------------------ # Usage: ./discord.sh "Title" "Description" "Status(success/fail/info)" # ------------------------------------------------------------------ TITLE=$1 MESSAGE=$2 STATUS=$3 # Webhook URL # AWS Parameter Store์์ Webhook URL ์กฐํ # ๋ฆฌ์ ์ ์์ธ(ap-northeast-2)๋ก ๊ณ ์ WEBHOOK_URL=$(aws ssm get-parameter \ --name "/global/discord_webhook" \ --with-decryption \ --query Parameter.Value \ --output text \ --region ap-northeast-2) # Status Color if [ "$STATUS" == "fail" ]; then COLOR=16711680 # Red (์ฅ์ /์คํจ) elif [ "$STATUS" == "success" ]; then COLOR=65280 # Green (์ฑ๊ณต/๋ณต๊ตฌ) else COLOR=3447003 # Blue (์งํ ์ค/์ ๋ณด) fi # Payload # JSON ํฌ๋งทํ . ์ค๋ฐ๊ฟ ๋ฌธ์ ๋ฑ์ ์ฒ๋ฆฌํ๊ธฐ ์ํด cat <<EOF PAYLOAD=$(cat <<EOF { "username": "Deploy Bot", "avatar_url": "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png", "embeds": [{ "title": "${TITLE}", "description": "${MESSAGE}", "color": ${COLOR}, "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)", "footer": { "text": "AWS CodeDeploy โข $(hostname)" } }] } EOF ) # ์๋ฆผ ๋ฐ์ก # curl์ ์ฌ์ฉํ์ฌ ๋น๋๊ธฐ ์ ์ก curl -H "Content-Type: application/json" -d "$PAYLOAD" "$WEBHOOK_URL" > /dev/null 2>&1 -
๋จ๊ณ๋ณ ์ํ ์ค๊ณ:ย ์์, ์ฑ๊ณต, ์คํจ
-
์๋ฌ ํธ๋ค๋ง ์๋ํ:ย ๋ฆฌ๋ ์คย
trapย ๋ช ๋ น์ด๋ฅผ ํ์ฉ, ์คํฌ๋ฆฝํธ ๋น์ ์ ์ข ๋ฃ ์ ์ฆ์ ์๋ฆผ ์ ์ก.(๊ฐ๋ณ ๋ฐฐํฌ ์คํฌ๋ฆฝํธ์ ํฌํจ)
5.2. ๋ฆฌ์์ค ๋ฐ ๋ก๊ทธ ๋ชจ๋ํฐ๋ง
๋จ์ผ ์ธ์คํด์ค ๋ด ๋ค์ค ์๋น์ค ๊ตฌ๋์ ๋ง๊ฒ ๊ฐ๋ณ ์๋น์ค ์ํ๋ฅผ ํ์ ํ๊ธฐ ์ํ ๋ชจ๋ํฐ๋ง ํ์
5.2.1. ์ธ์คํด์ค ๋ฆฌ์์ค ๋ชจ๋ํฐ๋ง(CloudWatch)
- ๋์: ์ธ์คํด์ค ๋ฆฌ์์ค(CPU ์ฌ์ฉ๋ฅ , ๋ฉ๋ชจ๋ฆฌ ์ ์ ์จ, ๋์คํฌ I/O, SWap ์ฌ์ฉ๋, ๋คํธ์ํฌ ๋ฑ)
- ์ค์ : AWS CloudWatch Agent๋ฅผ ์ค์นํ์ฌ ์ธ์คํด์ค ๋ด๋ถ ์งํ ์์ง
- ์๋ฆผ ์ค์
- CPU ์ฌ์ฉ๋ฅ 80% ์ด์ 5๋ถ ์ง์์ ์๋ฆผ
- ๋ฉ๋ชจ๋ฆฌ ์ฌ์ ๊ณต๊ฐ 500MB ์ดํ์ ์ฆ์ ์๋ฆผ(OOM ๊ฒฝ๊ณ )
5.2.2. ๋ก๊ทธ ํ์ผ ๊ด๋ฆฌ
๋์คํฌ ์ฉ๋ ๋ถ์กฑ์ ์ํ ๋ก๊ทธํ์ผ ๊ด๋ฆฌ ์ ์ฑ ์๋ฆฝ
๋ก๊ทธํ์ผ์ ์๋์ผ๋ก ์์ถ, ๋ฐฑ์ ์ญ์ , ๋กํ ์ด์ ์ ์ํํ๋ ์ ํธ๋ฆฌํฐ(logrotate) ์ฌ์ฉ
-
๋ก๊ทธ ๊ฒฝ๋ก
- Backend:ย
/home/ubuntu/apps/backend/app.log - Frontend:ย
~/.pm2/logs/ย (PM2์์ ๊ด๋ฆฌ) - AI:ย
/home/ubuntu/apps/ai/app.log - CodeDeploy:ย
/var/log/aws/codedeploy-agent/
- Backend:ย
-
Logrotate์ค์ : ๋ฆฌ๋ ์ค
Logrotate์ ํธ๋ฆฌํฐ ์ฌ์ฉํ์ฌ ์ผ๋ณ ์ํ, ์ต๋ 7์ผ ๋ณด๊ด, ์์ถ# ๋์ ๋ก๊ทธ ํ์ผ ๊ฒฝ๋ก /home/ubuntu/apps/be/*.log /home/ubuntu/apps/ai/*.log /home/ubuntu/apps/fe/*.log /var/log/aws/codedeploy-agent/codedeploy-agent.log { daily # ๋งค์ผ ๋กํ ์ดํธ ์คํ missingok # ๋ก๊ทธ ํ์ผ์ด ์์ด๋ ์๋ฌ ์์ด ๋์ด๊ฐ rotate30 # ์ต๊ทผ 30๊ฐ(30์ผ์น) ํ์ผ๋ง ๋ณด๊ดํ๊ณ ๋๋จธ์ง๋ ์ญ์ compress # ์ง๋ ๋ก๊ทธ ํ์ผ์ gzip์ผ๋ก ์์ถํ์ฌ ์ฉ๋ ์ ์ฝ (.gz) delaycompress # ์์ถ์ ๋ฐ๋ก ํ์ง ์๊ณ ํ๋ฃจ ๋ค์ ์ํ (ํ์ฌ ์ฐ๊ธฐ ์ค์ธ ํ์ผ ๋ณดํธ) notifempty # ๋ก๊ทธ ๋ด์ฉ์ด ๋น์ด์์ผ๋ฉด ๋กํ ์ดํธ ํ์ง ์์ copytruncate # ํต์ฌ: ํ์ฌ ๋ก๊ทธ ํ์ผ์ ๋ณต์ฌ(copy) ํ ์๋ณธ ๋ด์ฉ์ ๋น์(truncate). # ํ๋ก์ธ์ค ์ฌ์์ ์์ด ๋ก๊ทธ ํ์ผ์ ๋๋ ์ ์๋ ๊ฐ์ฅ ์์ ํ ๋ฐฉ๋ฒ. create 0644 ubuntu ubuntu # ์ ๋ก๊ทธ ํ์ผ ์์ฑ ์ ๊ถํ ๋ฐ ์์ ์ ์ง์ dateext # ๋ฐฑ์ ํ์ผ๋ช ์ ๋ ์ง ์ถ๊ฐ (app.log-20240101.gz) }
5.2.3. Artifact ๊ด๋ฆฌ
๋น๋ ๊ฒฐ๊ณผ๋ฌผ์ธ Artifact๊ฐ ๊ณผ๋ํ๊ฒ ์์ฌ ๋น์ฉ์ , ์ฑ๋ฅ์ ๋ฌธ์ ๋ฅผ ์ผ์ผํค๋ ๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํด Lifecycle ๊ด๋ฆฌ
- ์ ์ฉ ๋์: S3 ๋ด๋ถ์ ๋น๋ ์ฐ์ถ๋ฌผ ๊ฐ์ฒด ์ ์ฒด
- ์ ์ฑ
- Transition to IA(Infrequent Access)
- ์์ฃผ ์ ๊ทผํ์ง ์๋ ์ค๋๋ ๋น๋ํ์ผ์ ์ ๋ ดํ ์คํ ๋ฆฌ์งํด๋์ค(Standard-IA)๋ก ์ด๋ํ์ฌ ๋ณด๊ด ๋น์ฉ ์ฝ 40% ์ ์ฝ(์์ธ ๋ฆฌ์ ๊ธฐ์ค ์ผ๋ฐ S3 GB๋น USD 0.025 / AI GB๋น USD0.011๋ก 50% ์ด์ ๋น์ฉ ์ ๊ฐ
- Expiration
- ์์ฑ ํ 90์ผ(3๊ฐ์)์ด ์ง๋ ์ฝ๋๋ก ๋กค๋ฐฑํ๋ ๊ฒฝ์ฐ๋ ์์.
- ์๊ตฌ ์ญ์ ํ์ฌ ๋น์ฉ ๋์ ์ฐจ๋จ
- Transition to IA(Infrequent Access)
5.3. ๋ฐ์ดํฐ ๋ณดํธ ๋ฐ ๋ฐฑ์
๋ฐฐํฌ ์คํจ, ์ธ์คํด์ค ์ฅ์ ๋ฑ์ผ๋ก ์ธํ ๋ฐ์ดํฐ ์ ์ค์ ๋ฐฉ์งํ๊ธฐ ์ํ ์ ๋ต ์๋ฆฝ
5.3.1. ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฐฑ์
- ๋ก์ปฌ DB ๋ฐ์ดํฐ๋ฅผ ๋ณด์กดํ๊ธฐ ์ํด EBS ๋ณผ๋ฅจ ์ค๋
์ท์ ์ฃผ๊ธฐ์ ์ผ๋ก ์์ฑ
- ์ฌ์ฉ์๊ฐ ์ ์ด ์ฑ๋ฅ ์ํฅ์ด ์ ์ ์๋ฒฝ 2~5์ ์ฌ์ด์ ์ค๋ ์ท ์์ฑ ๋ฐ ๊ด๋ฆฌ
5.3.2. CodeDeploy ๋ฐฐํฌ ์ด๋ ฅ
- AWS CodeDeploy์ ๋ฐฐํฌ ์ด๋ ฅ ๋ณด์กด์ ํตํด ๊ธฐ๋ก ํ์ธ ๋ฐ ๋กค๋ฐฑ ๊ฐ๋ฅ
5.4. ์ฅ์ ์ ํ ๋ฐ ๋์
์๋ฒ ์ด์ ์ ๋ฐ์ํ ์ ์๋ ์ฅ์ ์๋๋ฆฌ์ค ๋ณ ๋์ ์ ์ฐจ
5.4.1. ๋ฐฐํฌ ์งํ Health Check ์คํจ
- ์๋ฒ ๋ด๋ถ ๋ฐฐํฌ ์คํฌ๋ฆฝํธ ์ค
validate_and_switch.sh๊ฐ ๊ฐ์งํ์ฌ exit1 ๋ฐํ - ์๋ ๋กค๋ฐฑ ์ค์ ์ด ๋ ๊ฒฝ์ฐ CodeDeploy๊ฐ ๋ฐฐํฌ ์ํ ํ์ธ ํ ์๋ ๋กค๋ฐฑ ํธ๋ฆฌ๊ฑฐ
- ํธ๋ํฝ ๋ณ๊ฒฝ์ด ์์ด ๊ตฌ ๋ฒ์ ์ ์ง โ ์ฌ์ฉ์ ์ํฅ ์์
5.4.2. ๋ฐฐํฌ ์ฑ๊ณต ํ ๋ก์ง ์ค๋ฅ ๋ฐ๊ฒฌ
- ๋๋ฒ๊น , QA, ๊ฐ์ข ์๋ฆผ ๋ฑ์ ํตํด ๊ฐ๋ฐ ๋ฐ ์ด์์ง์ด ์ค๋ฅ ์ธ์ง
- GitHub Actions์์ ์ด์ ๋ฒ์ ์ผ๋ก ๋กค๋ฐฑ ํธ๋ฆฌ๊ฑฐ ๋ฐ ์ฌ๋ฐฐํฌ ํ์ดํ๋ผ์ธ ์คํ
5.4.3. ์๋ฒ ๋ค์ด
- ์คํ ์ด์ง ์๋ฒ: ASG๊ฐ HealthCheck ์คํจ ๊ฐ์ง ํ ์ ์ธ์คํด์ค ์๋ ์คํ ๋ฐ ๋ฐฐํฌ ๋ก์ง ์คํ
- ์ด์ ์๋ฒ: ASG๋ฅผ ํตํ ์๋ ๋ณต๊ตฌ ๋ฐ CloudWatch๋ฅผ ํตํ ์๋ฆผ ์ ํ