CI - 100-hours-a-week/9-team-Devths-WIKI GitHub Wiki

๋ชฉ์ฐจ (ํŽผ์น˜๊ธฐ/์ ‘๊ธฐ)

CI(์ง€์†์  ํ†ตํ•ฉ) ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์ถ• ์„ค๊ณ„

๐Ÿ’ก CI (Continuous Integration)์ด๋ž€? ์—ฌ๋Ÿฌ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋ณ€๊ฒฝํ•œ ์ฝ”๋“œ๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ํ†ตํ•ฉํ•˜๊ณ , ๋นŒ๋“œ ๋ฐ ํ…Œ์ŠคํŠธ ์ž๋™ํ™”๋ฅผ ํ†ตํ•ด ํ’ˆ์งˆ์„ ์œ ์ง€ํ•˜๋Š” ๊ฐœ๋ฐœ ๋ฐฉ๋ฒ•๋ก ์ž…๋‹ˆ๋‹ค.



โœจ 1. ๋„์ž… ๋ฐฐ๊ฒฝ

1. ์ฝ”๋“œ ๋ณ‘ํ•ฉ ๊ณผ์ •์—์„œ์˜ ํ•œ๊ณ„

  • ๊ฐ ํŒ€์€ ๋…๋ฆฝ ๋ ˆํฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋‚˜, ๊ณตํ†ต๋œ ๋ฆด๋ฆฌ์ฆˆ ์ผ์ •์„ ๋งž์ถ”๊ธฐ ์œ„ํ•ด ๊ฐ์ž์˜ develop ๋ธŒ๋žœ์น˜์—์„œ ๋นˆ๋ฒˆํžˆ ๋ณ‘ํ•ฉ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•จ.
  • PR ์‹œ์ ์—๋งŒ ์ˆ˜๋™ ๋ฆฌ๋ทฐ์™€ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋‹ค ๋ณด๋‹ˆ, ๋ฆฐํŠธ ์˜ค๋ฅ˜๋‚˜ ํ…Œ์ŠคํŠธ ์‹คํŒจ๊ฐ€ ๋’ค๋Šฆ๊ฒŒ ๋ฐœ๊ฒฌ๋˜์–ด ๋ฆฌ๋ทฐ ๋ณ‘๋ชฉ๊ณผ ์ผ์ • ์ง€์—ฐ์ด ๋ฐœ์ƒํ•˜๋Š” ์‚ฌ๋ก€ ์กด์žฌ.

2. ๋ฐฐํฌ ๋นˆ๋„์˜ ์ฆ๊ฐ€

  • ์„œ๋น„์Šค๋ฅผ ๋น ๋ฅด๊ฒŒ ๊ฐœ์„ ํ•˜๊ณ  ๊ธฐ๋Šฅ์„ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•ด ๋ฐฐํฌ ํšŸ์ˆ˜๊ฐ€ ์ฆ๊ฐ€ํ•จ.
  • ๋ฐ˜๋ณต์ ์ด๊ณ  ์ˆ˜๋™์ ์ธ ๋ฐฐํฌ ๊ณผ์ •์—์„œ๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ˆ„์ ๋˜๊ณ , ๋ฐฐํฌ ์†๋„๊ฐ€ ๋А๋ ค ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ ๋ฐ˜์˜์ด ๋Šฆ์–ด์ง.
  • CI ํŒŒ์ดํ”„๋ผ์ธ์„ ํ†ตํ•ด ์ž๋™ ๋นŒ๋“œ์™€ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฉด, ๋น ๋ฅธ ๋ฐฐํฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ณ  ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Œ.

3. ์ฝ”๋“œ ํ’ˆ์งˆ ๊ด€๋ฆฌ ์ฒด๊ณ„์˜ ๋ฏธํก

  • ๊ฐ ๋ ˆํฌ๋ณ„๋กœ ํ…Œ์ŠคํŠธ ์ •์ฑ…์ด๋‚˜ ๋ฆฐํŠธ ๊ทœ์น™์ด ์ •๋ฆฝ๋˜์ง€ ์•Š์•„, ์ผ๋ถ€ ๋ธŒ๋žœ์น˜์—์„œ๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ ๋ˆ„๋ฝ๋˜๊ฑฐ๋‚˜ ๋ฆฐํŠธ๊ฐ€ ๋ฌด์‹œ๋œ ์ฑ„ ๋ณ‘ํ•ฉ๋˜๋Š” ์‚ฌ๋ก€ ๋ฐœ์ƒ.
  • ํ˜„์žฌ main ๋ธŒ๋žœ์น˜ ๋ณ‘ํ•ฉ ์‹œ์ ์—๋„ ์ฝ”๋“œ ์ •์  ๋ถ„์„, ํ…Œ์ŠคํŠธ ์ž๋™ํ™”๊ฐ€ ์ถฉ๋ถ„ํžˆ ์ ์šฉ๋˜์ง€ ์•Š์•„, ์šด์˜ ๋ฐ˜์˜ ์ง์ „์— ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ๊ฒฌ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ์ƒํ•จ.
  • ํŒ€์› ๊ฐ„ ํ˜‘์—… ๊ฒฝํ—˜์ด ๋‹ค๋ฅด๋‹ค ๋ณด๋‹ˆ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ ๋ฐ ์‹คํ–‰ ์Šต๊ด€์ด ๋ถ€์กฑํ•˜์—ฌ ์ฝ”๋“œ ํ€„๋ฆฌํ‹ฐ์— ๋Œ€ํ•œ ์‹ ๋ขฐ์„ฑ ํ™•๋ณด๊ฐ€ ์–ด๋ ค์›€.

4. ์šด์˜ ํ™˜๊ฒฝ๊ณผ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์˜ ๋ถ„๋ฆฌ ๋ฏธํก

image
  • ๊ธฐ์กด์—๋Š” GitHub Flow ๋กœ์„œ, main๊ณผ feat ๋งŒ์œผ๋กœ ์šด์˜ํ•˜์—ฌ ํ›จ์”ฌ ๊ฐ„๋‹จํ•˜์ง€๋งŒ ๋น ๋ฅด๊ฒŒ ์ˆ˜์ • ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๋Š” ์ „๋žต์„ ์‚ฌ์šฉํ•จ.
  • ํ•˜์ง€๋งŒ ์šด์˜ ์„œ๋ฒ„์™€ ๊ฐœ๋ฐœ ์„œ๋ฒ„๊ฐ€ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์ง€ ์•Š์•„, ํ…Œ์ŠคํŠธ ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•œ ๋ฌธ์ œ๋‚˜ ๋ฒ„๊ทธ๊ฐ€ ์„œ๋น„์Šค ์ „์ฒด ๊ฐ€์šฉ์„ฑ์— ์ง์ ‘์ ์ธ ์˜ํ–ฅ์„ ๋ฏธ์น  ์œ„ํ—˜์ด ์žˆ๋‹ค๊ณ  ํŒ๋‹จํ•จ.


2. CI ํŒŒ์ดํ”„๋ผ์ธ ๋‹ค์ด์–ด๊ทธ๋žจ

2-1. Git Flow ๋ธŒ๋žœ์น˜ ์ „๋žต ๋‹ค์ด์–ด๊ทธ๋žจ ๋ฐ CI ํŒŒ์ดํ”„๋ผ์ธ ์ „์ฒด ํ๋ฆ„๋„

image
  • Git Flow ๊ธฐ๋ฐ˜์œผ๋กœ feature โ†’ develop โ†’ Release(Staging) โ†’ main ์ˆœ์œผ๋กœ PR๊ณผ ๋จธ์ง€๋ฅผ ์ง„ํ–‰
  • ๊ฐ ๋‹จ๊ณ„์—์„œ Lint โ†’ Static Analysis โ†’ Unit/Integration Test โ†’ Build โ†’ Artifact ์ €์žฅ โ†’ Discord ์•Œ๋ฆผ์œผ๋กœ CI๋ฅผ ์ง„ํ–‰


3. CI ํŒŒ์ดํ”„๋ผ์ธ ์„ค๊ณ„ ๋ฐ ๋ธŒ๋žœ์น˜ ์ „๋žต

3-1. CI ๊ฐœ์š”

  • ์‚ฌ์šฉํ•˜๋Š” ํ”Œ๋žซํผ : Github Actions
  • ํ™˜๊ฒฝ : AWS EC2 ์ธ์Šคํ„ด์Šค (๊ธฐ์กด 2๊ฐœ / ์ƒํ™ฉ์— ๋”ฐ๋ผ์„œ 3๊ฐœ)
  • ํ˜•์ƒ ๊ด€๋ฆฌ : GItHub
  • ๋ฐฐํฌ ๋Œ€์ƒ: ํ”„๋ก ํŠธ์—”๋“œ, ๋ฐฑ์—”๋“œ, AI ์„œ๋ฒ„, DB ์ „๋ถ€ ๋‹จ์ผ ์ธ์Šคํ„ด์Šค์—์„œ ์šด์˜
  • ๋ธŒ๋žœ์น˜ ์ „๋žต : ๊ฐœ๋ฐœ / ์Šคํ…Œ์ด์ง• / ์šด์˜ ์„œ๋ฒ„ ๊ตฌ์ถ•

3-2. CI ๋„๊ตฌ ๋น„๊ต ๋ฐ ํ•„์š”

๋„๊ตฌ ์„ ์ •์˜ ๊ธฐ์ค€์€ GitHub ํ†ตํ•ฉ์„ฑ, ์†๋„, ๋น„์šฉ, ๋ณด์•ˆ ๋ชจ๋ธ

image

CI/CD ๋„๊ตฌ ์ ์œ ์œจ [Jetbrain ์ฐธ๊ณ  ๋งํฌ] GitLab/Azure ํ™˜๊ฒฝ์„ ์‚ฌ์šฉํ•˜์ง€์•Š๊ธฐ์— ์ œ์™ธํ•˜์˜€์Šต๋‹ˆ๋‹ค.

ํ•ญ๋ชฉ GitHub Actions (์„ ํƒ) Jenkins Circle CI Travis CI
GitHub ํ†ตํ•ฉ์„ฑ ์™„์ „ ํ†ตํ•ฉ, ๋ณ„๋„ ์—ฐ๋™ ๋ถˆํ•„์š” Webhook ์—ฐ๋™ ๋ฐ ์„ค์ • ํ•„์š” OAuth ์—ฐ๋™ ํ•„์š” OAuth ์—ฐ๋™ ํ•„์š”
์„ค์ • ๋ฐฉ์‹ YAML ๊ธฐ๋ฐ˜ Groovy ์Šคํฌ๋ฆฝํŠธ ๋˜๋Š” UI ์„ค์ • YAML ๊ธฐ๋ฐ˜ YAML ๊ธฐ๋ฐ˜
์†๋„ ๋ฐ ์บ์‹ฑ ์šฐ์ˆ˜ (GitHub ์ธํ”„๋ผ ํ™œ์šฉ, ์บ์‹ฑ ์•ก์…˜ ์ง€์›) ์šด์˜ ์‚ฌ์–‘์— ๋”ฐ๋ผ ๋‹ค๋ฆ„ (์ž์ฒด ์„œ๋ฒ„ ์„ฑ๋Šฅ์— ์˜์กด) ์ตœ์ƒ (Docker ๋ ˆ์ด์–ด ์บ์‹ฑ ๋ฐ ๋ณ‘๋ ฌํ™” ํŠนํ™”) ๋ณดํ†ต (๋™์‹œ ์‹คํ–‰ ์ œํ•œ ์กด์žฌ)
๊ด€๋ฆฌ ๋ถ€๋‹ด ์ตœ์ € (์„œ๋ฒ„ ๊ด€๋ฆฌ ๋ถˆํ•„์š”) ์ตœ๊ณ  (๋งˆ์Šคํ„ฐ/์—์ด์ „ํŠธ ๊ด€๋ฆฌ) ๋‚ฎ์Œ (SaaS UI ์‚ฌ์šฉ) ๋‚ฎ์Œ (SaaS UI ์‚ฌ์šฉ)
๋น„์šฉ Public ์ด๊ธฐ์— ๋ฌด๋ฃŒ ์˜คํ”ˆ์†Œ์Šค๋กœ์„œ ์„œ๋ฒ„ ์ง์ ‘ ์šด์˜ ์ตœ๋Œ€ 6,000 ๋ถ„๊นŒ์ง€ ๋ฌด๋ฃŒ ์‚ฌ์šฉ ์›” ๋น„์šฉ $15 ๋ฐœ์ƒ

โ‡’ GitHub Actions๋Š” GitHub ๊ธฐ๋ฐ˜ ์›Œํฌํ”Œ๋กœ์šฐ์ด๊ธฐ์— ๋จธ์ง€ ๊ทœ์น™, ๊ถŒํ•œ/์‹œํฌ๋ฆฟ ๊ด€๋ฆฌ์™€ ๊ฐ™์€ ํ†ตํ•ฉ์„ฑ์ด ์ œ์ผ ์ข‹์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋ฒ„์ €๋‹ ๊ด€๋ฆฌ์—†์ด ์‰ฝ๊ฒŒ ์ž๋™ํ™”ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋น„์šฉ ๋ถ€๋‹ด ์—†์ด ๋น ๋ฅด๊ฒŒ ๊ตฌ์ถ• ๊ฐ€๋Šฅํ•˜๊ธฐ์— ์„ ์ •ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.


3-3. Git Flow ๋ธŒ๋žœ์น˜ ์ „๋žต


๋ธŒ๋žœ์น˜ ํ๋ฆ„๋„ ์š”์•ฝ

(feat/*) โ†’ develop โ†’ release โ†’ main
			                    โ†‘
		                     hotfix/*

์šด์˜ ์‹œ ์œ ์˜์‚ฌํ•ญ

๋ธŒ๋žœ์น˜ ์ด๋ฆ„ ์šฉ๋„ ๋ณ‘ํ•ฉ ๋ฐฉ์‹
main ์šด์˜ ๊ธฐ์ค€ ์•ˆ์ •ํ™” ์ฝ”๋“œ PR ๋ฆฌ๋ทฐ ํ›„ ๋ณ‘ํ•ฉ
release ๋ฐฐํฌ ์ „ ์šด์˜ ํ™˜๊ฒฝ ๊ฒ€์ฆ, QA ๊ฒ€์ฆ dev โ†’ release โ†’ main
develop ๊ฐœ๋ฐœ ํ†ตํ•ฉ ๋ธŒ๋žœ์น˜ PR ๋ฆฌ๋ทฐ ํ›„ ๋ณ‘ํ•ฉ
feat/* ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ Push
hotfix/* ๊ธด๊ธ‰ ํŒจ์น˜ ๋ฐ ์šด์˜ ๋ฒ„๊ทธ ์ˆ˜์ • main์—์„œ ๋ถ„๊ธฐ โ†’ main ๋กœ ๋ณ‘ํ•ฉ

3-3-1. ๋ณ„๋„์˜ Release(Staging) ๋ธŒ๋žœ์น˜ ๋ฅผ ๋งŒ๋“  ์ด์œ 

๋ณ„๋„์˜ ์ƒ์‹œ Release ์„œ๋ฒ„๋ฅผ ์šด์˜ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์„ ํƒ์ ์œผ๋กœ Release ํ™˜๊ฒฝ์„ ๊ตฌ์„ฑํ•˜๋Š” ์ „๋žต์„ ์ฑ„ํƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.

1. ๋ฐฐํฌ ์•ˆ์ •์„ฑ ํ™•๋ณด

  • Release ํ™˜๊ฒฝ์„ ํ•„์š” ์‹œ์—๋งŒ ๊ตฌ์„ฑํ•˜๋ฉด,
    • ์šด์˜ ํŠธ๋ž˜ํ”ฝ๊ณผ ๋ถ„๋ฆฌ๋œ ๊ณต๊ฐ„์—์„œ ๋ฐฐํฌ ์ „ ๊ฒ€์ฆ(QA/๋ถ€ํ•˜)์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๊ณ 
    • ๋ฌธ์ œ ๋ฐœ์ƒ ์‹œ์—๋„ Dev/Main์— ๋ฏธ์น˜๋Š” ์˜ํ–ฅ์„ ์ตœ์†Œํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋˜ํ•œ ๋ฆด๋ฆฌ์ฆˆ ์ง์ „ ์šด์˜๊ณผ ๋™์ผํ•œ ์กฐ๊ฑด์—์„œ ๊ฒ€์ฆ ํ›„ ๋ฐฐํฌํ•จ์œผ๋กœ์จ, ๋ฐฐํฌ ์•ˆ์ •์„ฑ์„ ๋†’์ผ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2. ๋ฆฌ์†Œ์Šค ๋ฐ ๋น„์šฉ

  • ๋ถ€ํ•˜ํ…Œ์ŠคํŠธ ๋ฐ QA๋ฅผ ์ง„ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์šด์˜ ์„œ๋ฒ„์™€ ๋™์ผํ•˜๊ฒŒ ์ง„ํ–‰ํ•ด์•ผํ•˜๊ธฐ์—, ๋™์ผํ•œ ์ŠคํŽ™(High-spec)์˜ ํ™˜๊ฒฝ์ด ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.
  • Release ํ™˜๊ฒฝ ์—†์ด Dev ์„œ๋ฒ„๋กœ๋งŒ ๊ฐ€๋™ํ•  ๊ฒฝ์šฐ, ์‹ค์ œ ํŠธ๋ž˜ํ”ฝ ์‚ฌ์šฉ๋ฅ ์ด ๋‚ฎ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ๋†’์€ ๋น„์šฉ์„ ์œ ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • ์šด์˜๊ณผ ๋™์ผํ•œ $75.92 ์˜ ๋น„์šฉ์ด ์˜ˆ์ƒ๋˜๋ฉฐ, ์šด์˜/๊ฐœ๋ฐœ์— ์•ฝ$152 ๋‹ฌ๋Ÿฌ ์ฒญ๊ตฌ ์˜ˆ์ƒ
  • ์„ ํƒ์ ์œผ๋กœ Release ํ™˜๊ฒฝ์„ ๊ตฌ์„ฑํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด, ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” ๋‚ฎ์€ ์ŠคํŽ™์œผ๋กœ ์ง„ํ–‰ํ•ด๋„ ๋œ๋‹ค๊ณ  ํŒ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.
    • Idleํ•œ ์ƒํƒœ์—์„œ ๊ธฐ๋ณธ ์„ค์น˜ ์‹œ, ํ•„์š”ํ•œ ๋ฉ”๋ชจ๋ฆฌ 1.2GB
    • ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” ์ง€์—ฐ ์‹œ๊ฐ„์ด ํ—ˆ์šฉ๋œ๋‹ค๊ณ  ํŒ๋‹จํ•˜์—ฌ, ์Šค์™‘ ๋ฉ”๋ชจ๋ฆฌ ํ†ตํ•œ ์ถ”๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ํ™•๋ณด
      • ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋งŽ์€ ๋‹จ์ผ ์ธ์Šคํ„ด์Šค ํŠน์„ฑ ์ƒ, Context Switching ์ด ์ ๋„๋ก CPU๊ฐ€ ๋งŽ์€ ์ธ์Šคํ„ด์Šค๊ฐ€ ํ•„์š”ํ•˜๋‹ค๊ณ  ํŒ๋‹จ
      • t2์— ๋น„ํ•ด์„œ CPU๊ฐ€ 1์ฝ”์–ด ๋” ๋งŽ๊ณ  ์ €๋ ดํ•œ t3๋ฅผ ์„ ์ •
    • ๊ฐœ๋ฐœํ™˜๊ฒฝ์—์„œ๋Š” t3.small 1๋Œ€ ์„ค์ •ํ•˜์—ฌ, ํ•œ ๋‹ฌ(744์‹œ๊ฐ„)์— ์•ฝ$22 ์ฒญ๊ตฌ ์˜ˆ์ƒ

โ‡’ 35.5% ๋” ์ €๋ ดํ•˜๊ฒŒ ์šด์˜๊ฐ€๋Šฅ

3. ์šด์˜์˜ ๋ณต์žก์„ฑ ๊ฐ€๋Šฅ์„ฑ ์กด์žฌ

  • ํด๋ผ์šฐ๋“œ ํ™˜๊ฒฝ์„ ์ตœ๋Œ€ 3๊ฐœ๊นŒ์ง€ ๊ตฌ์ถ•ํ•ด์•ผ๋œ๋‹ค๋Š” ๋‹จ์ ์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.
  • ํ•˜์ง€๋งŒ, Artifact์˜ ์œ ์ง€ ๋ฐ CD์œผ๋กœ์˜ ํ™•์žฅ์„ฑ์„ ๊ณ ๋ คํ•œ๋‹ค๋ฉด ์ˆ˜๋™ ๋ฐฐํฌ์˜ ํ™˜๊ฒฝ๋ณด๋‹ค๋Š” ์šด์˜์˜ ๋‚œ์ด๋„๊ฐ€ ๋‚ฎ์•„์ง„๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๊ณ , ๋น„์šฉ์  ์ธก๋ฉด๊ณผ ์•ˆ์ •์„ฑ์ด ๋Š˜์–ด๋‚œ๋‹ค๋Š” ์žฅ์ ์„ ํƒํ•˜๊ธฐ๋กœ ํŒ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.

3-4. ๋‹จ๊ณ„ ์ •์˜

๋ธŒ๋žœ์น˜ ์ด๋ฆ„ ํŒŒ์ดํ”„๋ผ์ธ ์„ค๋ช…
main lint โ†’ ์ •์  ๋ถ„์„ โ†’ ๋‹จ์œ„/ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ โ†’ build โ†’ artifact
release lint โ†’ ์ •์  ๋ถ„์„ โ†’ ๋‹จ์œ„/ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ โ†’ build โ†’ artifact
develop lint โ†’ ์ •์  ๋ถ„์„ โ†’ ๋‹จ์œ„/ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ โ†’ build โ†’ artifact
hotfix/* lint โ†’ ์ •์  ๋ถ„์„ โ†’ ๋‹จ์œ„/ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ โ†’ build โ†’ artifact
feat/* -
  • ๊ฐ ๋‹จ๊ณ„๋ฅผ ์‹คํ–‰ํ•  ๋•Œ, ํ•˜๋‚˜์˜ ํŒŒ์ดํ”„๋ผ์ธ์ด๋ผ๋„ ์‹คํŒจ ์‹œ, merge ๋ถˆ๊ฐ€๋Šฅ

3-4-1. Lint

์†Œ์Šค ์ฝ”๋“œ์˜ ์Šคํƒ€์ผ ๋ถˆ์ผ์น˜, ๊ตฌ๋ฌธ ์˜ค๋ฅ˜ ๋ฐ ์ฝ”๋”ฉ ๊ทœ์น™ ์ค€์ˆ˜ ์—ฌ๋ถ€๋ฅผ ๋ถ„์„

  • ๋ชฉ์ : ์ฝ”๋“œ ์Šคํƒ€์ผ/ํฌ๋งท/๊ธฐ์ดˆ ๊ทœ์น™์„ ๋น ๋ฅด๊ฒŒ ํ†ต๊ณผ์‹œํ‚ค๊ณ  PR ํ’ˆ์งˆ์„ ๊ท ์ผํ™”
  • ๋Œ€์ƒ: ๊ณตํ†ต
  • ์‚ฌ์šฉ ๋„๊ตฌ
    • Front: eslint, prettier
    • Python: ruff, eslint, prettier
    • Java: eslint, prettier
  • Lint ์‹คํŒจ ์‹œ, PR ๋จธ์ง€ ์‹คํŒจ

3-4-2. ์ •์  ๋ถ„์„

์†Œ์Šค ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜์ง€ ์•Š๊ณ  ์ทจ์•ฝ์  ์‹๋ณ„, ๋ฒ„๊ทธ, ์„ฑ๋Šฅ ๋ฌธ์ œ, ์ฝ”๋”ฉ ํ‘œ์ค€ ๋ฏธ์ค€์ˆ˜ ๋“ฑ์— ๋Œ€ํ•œ ์ž ์žฌ์ ์ธ ๋ฌธ์ œ์— ๋Œ€ํ•ด ๋ถ„์„

  • ์šฐ๋ฆฌ ์„œ๋น„์Šค, ์ •์  ๋ถ„์„ ๋„๊ตฌ์˜ ์„ ํƒ ๊ธฐ์ค€์€ ๋น„์šฉ๊ณผ ์ทจ์•ฝ์  ์‹๋ณ„ ๊ณผ ๋ฒ„๊ทธ ์‹๋ณ„์ด๋‹ค.์ •์  ๋ถ„์„ ์ด์ „์— Lint์˜ ์ž‘์—…์ด ์žˆ๊ธฐ์— ์ฝ”๋”ฉ ํ‘œ์ค€ ๋ฏธ์ค€์ˆ˜๋Š” ๊ฐ€์ค‘์น˜๋ฅผ ๋‚ฎ๊ฒŒ ๋‘๊ณ  ์ง„ํ–‰ํ•จ
SonarQube CodeQL(์„ ํƒ) Code Climate Codacy Semgrep
์ฝ”๋”ฉ ํ‘œ์ค€ ๋ฏธ์ค€์ˆ˜ ๊ฐ€๋Šฅ (Maintainability/Code Smell ๊ทœ์น™ + Quality Profile/Quality Gate๋กœ ๊ฐ•์ œ) ๊ฐ€๋Šฅ(์ฃผ๋กœ โ€œ์ฝ”๋”ฉ ์˜ค๋ฅ˜/์ทจ์•ฝ์ โ€ ์ค‘์‹ฌ. ํ•„์š”ํ•˜๋ฉด ์ปค์Šคํ…€ ์ฟผ๋ฆฌ๋กœ ๊ทœ์น™ํ™” ๊ฐ€๋Šฅ) ๊ฐ€๋Šฅ(์—”์ง„/๋ฃฐ ๊ธฐ๋ฐ˜์œผ๋กœ ์Šคํƒ€์ผยท์ค‘๋ณตยท๋ณต์žก๋„ ๋“ฑ โ€œ์œ ์ง€๋ณด์ˆ˜์„ฑโ€ ์œ„๋ฐ˜์„ ์žก๋Š” ์šฉ๋„์— ๊ฐ•ํ•จ) ๊ฐ€๋Šฅ(IDE/PR์—์„œ โ€œcoding standardsโ€๋ฅผ ๊ณต์œ ยท๊ฐ•์ œ + ๋ฃฐ ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆ) ๊ฐ€๋Šฅ(์ปค์Šคํ…€ ๋ฃฐ๋กœ ์ฝ”๋”ฉ ํ‘œ์ค€/๊ฐ€๋“œ๋ ˆ์ผ ๊ฐ•์ œ์— ์ตœ์ )
๋ฒ„๊ทธ ์‹๋ณ„ ๊ฐ€๋Šฅ ๊ฐ€๋Šฅ ์ผ๋ถ€ ๊ฐ€๋Šฅ ๊ฐ€๋Šฅ ๊ฐ€๋Šฅ
์ทจ์•ฝ์  ์‹๋ณ„ ๊ฐ€๋Šฅ(Security = vulnerability & hotspot) ๊ฐ€๋Šฅ(โ€œvulnerabilitiesโ€ ์‹๋ณ„์„ ๋ช…์‹œ) ๊ธฐ๋ณธ์€ ์ฝ”๋“œ ํ’ˆ์งˆ ์ค‘์‹ฌ. ๋ณด์•ˆ์€ ๋ณ„๋„ ์—”์ง„/๋„๊ตฌ ์—ฐ๋™ ์„ฑ๊ฒฉ์ด ๊ฐ•ํ•จ ๊ฐ€๋Šฅ(SAST ์ทจ์•ฝ์  + ํ•˜๋“œ์ฝ”๋”ฉ ์‹œํฌ๋ฆฟ + ์ทจ์•ฝ ์˜์กด์„ฑ ๋“ฑ) ๊ฐ€๋Šฅ(Semgrep Code๋Š” SAST๋กœ ์ทจ์•ฝ์  ํƒ์ง€)
๋น„์šฉ - (Server) ์ง์ ‘ ์šด์˜ ์‹œ ์ธํ”„๋ผ/DB ์šด์˜๋น„ ๋ฐœ์ƒ- (Cloud) Free: private 50k LOC / Team $32/mo~ - Public repo๋Š” ์ผ๋ถ€ ๋ณด์•ˆ ๊ธฐ๋Šฅ์„ ๋ฌด๋ฃŒ๋กœ ์ œ๊ณต- Private repo์—์„œ Code Scanning ๋“ฑ ์“ฐ๋ ค๋ฉด GitHub Code Security/Advanced Security ๋ผ์ด์„ ์Šค ํ•„์š”(ํ™œ์„ฑ ์ปค๋ฏธํ„ฐ ๊ธฐ์ค€ ๊ณผ๊ธˆ/๋ผ์ด์„ ์‹ฑ) - Open Source $0- Startup $0(์ตœ๋Œ€ 4 seats)- Team $16.67/seatยทmo(์—ฐ๊ฐ„) ๋˜๋Š” $20/seatยทmo(์›”๊ฐ„) ์›” **$18** - Community Edition Free- Teams $40/์›”/์ปจํŠธ๋ฆฌ๋ทฐํ„ฐ (์ตœ๋Œ€ 10๋ช…๊นŒ์ง€ ๋ฌด๋ฃŒ ๊ตฌ๊ฐ„ ํ‘œ๊ธฐ)
ํŠน์ง• - ๊ทœ์น™์ด Bug / Code Smell / Vulnerability / Security Hotspot๋กœ ์ฒด๊ณ„ํ™” (Sonar Documentation)- ๊ธฐ๋ณธ ๋‚ด์žฅ DB(H2)๋Š” ํ…Œ์ŠคํŠธ์šฉ์ด๊ณ , ์šด์˜์€ ์™ธ๋ถ€ DB(์˜ˆ: PostgreSQL ๋“ฑ) ๊ถŒ์žฅ - GitHub โ€œcode scanning alertsโ€๋กœ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐ”๋กœ PR/๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ๋ถ™์Œ (GitHub Docs)- Public repo๋Š” ๋ฐ”๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ, ์กฐ์ง/Private๋Š” Code Security ํ™œ์„ฑํ™” ํ•„์š” - ์ขŒ์„(Seat) ๊ธฐ๋ฐ˜ ๊ณผ๊ธˆ + GitHub PR ์—ฐ๋™ ์ œ๊ณต (Code Climate)- ์—ฌ๋Ÿฌ ์˜คํ”ˆ์†Œ์Šค ๋ถ„์„ ์—”์ง„์„ โ€œ์—”์ง„/ํ”Œ๋Ÿฌ๊ทธ์ธโ€ ํ˜•ํƒœ๋กœ ๋ถ™์ด๋Š” ์ƒํƒœ๊ณ„ - โ€œUnlimited LOCโ€๋ฅผ ๊ฐ•์กฐํ•˜๋Š” ์ขŒ์„ ๊ธฐ๋ฐ˜(ํ”Œ๋žœ์— ๋”ฐ๋ผ PR ์Šค์บ”/๋ฆฌํฌ ์ˆ˜ ๋“ฑ ์ฐจ๋“ฑ) (codacy.com)- SAST/์‹œํฌ๋ฆฟ/์˜์กด์„ฑ + ํ’ˆ์งˆ(์ค‘๋ณต/๋ณต์žก๋„/์„ฑ๋Šฅ ์ด์Šˆ)๊นŒ์ง€ ํ•œ ํ™”๋ฉด์—์„œ ๊ด€๋ฆฌ - ์˜คํ”ˆ์†Œ์Šค CLI + ๋ฃฐ(ํŒจํ„ด) ๊ธฐ๋ฐ˜์ด๋ผ ์ปค์Šคํ…€ ๋ฃฐ ์ž‘์„ฑ/์ ์šฉ์ด ๊ฐ•์  (GitHub)- Teams๋Š” ์ œํ’ˆ๋ณ„(์ฝ”๋“œ/SCA/์‹œํฌ๋ฆฟ)๋กœ ๋ถ„๋ฆฌ ๊ณผ๊ธˆ ๊ตฌ์กฐ

โ‡’ CodeQL์€ GitHub ์ €์žฅ์†Œ/PR ํ๋ฆ„์— โ€œ๋‚ด์žฅํ˜•โ€์œผ๋กœ ๋ถ™๊ณ , ์ทจ์•ฝ์  ๋ฐ ์˜ค๋ฅ˜(๋ฒ„๊ทธ์„ฑ) ํƒ์ง€๊ฐ€ ๊ณต์‹์ ์œผ๋กœ ๋ชฉ์ ์— ํฌํ•จ ๋˜์–ด ์žˆ์–ด, ์ดˆ๊ธฐ ์šด์˜ ๋น„์šฉ๊ณผ ๋„์ž…/์šด์˜ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ตœ์†Œํ™”ํ•˜๋ฉด์„œ ํ•„์š”ํ•œ ์ˆ˜์ค€์˜ ๋ณด์•ˆยท๋ฒ„๊ทธ ํƒ์ง€๋ฅผ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ๊ธฐ์— ์„ ์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.


3-4-3. ํ…Œ์ŠคํŠธ

  • ๋ชฉ์ : โ€œ์ฝ”๋“œ๊ฐ€ ์˜๋„๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€โ€๋ฅผ ์‹คํ–‰์œผ๋กœ ๊ฒ€์ฆ
  • ๋ฒ”์œ„
    • ๋‹จ์œ„ ํ…Œ์ŠคํŠธ(Unit Test): ํ•จ์ˆ˜/๋ชจ๋“ˆ ๋ ˆ๋ฒจ ๋กœ์ง ๊ฒ€์ฆ (๊ฐ€์žฅ ๋น ๋ฅด๊ณ  ๋งŽ์ด)
    • ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ(Integration Test): DB/์™ธ๋ถ€ API ์—ฐ๋™์—์„œ ํ…Œ์ŠคํŠธ ๊ฒ€์ฆ
      • ์šฐ๋ฆฌ์„œ๋น„์Šค๋Š” ์™ธ๋ถ€ API๊ฐ€ 3๊ฐœ๋กœ ๋งŽ๊ธฐ์—, ์‹ค์ œ ํ˜ธ์ถœ ๋Œ€์‹  Mock/Stubbing์œผ๋กœ ํ•ญ์ƒ ๊ฐ™์€ ์‘๋‹ต์„ ๋ฐ›๊ฒŒ ๋งŒ๋“ค์–ด์„œ ํ…Œ์ŠคํŠธ ์‹ ๋ขฐ๋„ ๋†’์ด๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.
  • ์‚ฐ์ถœ๋ฌผ
    • ํ…Œ์ŠคํŠธ ๋ฆฌํฌํŠธ ์—…๋กœ๋“œ
    • ์ปค๋ฒ„๋ฆฌ์ง€ ๋ฆฌํฌํŠธ : โ€œ์œ ์ง€๋ณด์ˆ˜ ๊ฐ€๋Šฅํ•œ ์ˆ˜์ค€์˜ ์ตœ์†Œ ๊ธฐ์ค€โ€๋งŒ ์„ค์ •

3-4-4. ๋นŒ๋“œ

  • ๋ชฉ์ : ๋ฐฐํฌ ๊ฐ€๋Šฅํ•œ ์‚ฐ์ถœ๋ฌผ (์‹คํ–‰ํŒŒ์ผ/์ •์ ํŒŒ์ผ/ํŒจํ‚ค์ง€) ์„ ์ƒ์„ฑ

  • ์ˆ˜ํ–‰ ์ž‘์—…

    • ์ข…์†์„ฑ ์„ค์น˜ ๋ฐ ๋นŒ๋“œ
      • Front: npm run build (์ •์  ์‚ฐ์ถœ๋ฌผ ์ƒ์„ฑ)
      • Backend(Java): gradle build (jar ์ƒ์„ฑ)
      • Backend(Python): ํŒจํ‚ค์ง•/์˜์กด์„ฑ ๊ณ ์ • ํ›„ ์‹คํ–‰ ๋‹จ์œ„ ๊ตฌ์„ฑ
    • ๋นŒ๋“œ ์บ์‹œ ํ™œ์šฉ : ์˜์กด์„ฑ ์บ์‹œ๋กœ CI ์†๋„ ์•ˆ์ •ํ™”
  • ํ’ˆ์งˆ ๊ธฐ์ค€

    • ๋ถˆํ•„์š”ํ•œ ๋นŒ๋“œ ๋‚ญ๋น„ ๋ฐฉ์ง€์œ„ํ•ด์„œ, ๋นŒ๋“œ๋Š” โ€œํ…Œ์ŠคํŠธ ํ†ต๊ณผ ํ›„โ€ ์—๋งŒ ์ˆ˜ํ–‰
    • ๋นŒ๋“œ ๊ฒฐ๊ณผ๋ฌผ์— ๋ฒ„์ „ ์‹๋ณ„์ž(commit hash / tag / build number)๋ฅผ ๋ฐ˜๋“œ์‹œ ํฌํ•จ

3-4-5. Artifact ์—…๋กœ๋“œ๋ฅผ ํ•ด์•ผํ•˜๋Š” ์ด์œ 

  • artifact๋Š” ๋นŒ๋“œ๋œ ์‹คํ–‰ ํŒŒ์ผ ๋˜๋Š” ์ •์  ํŒŒ์ผ์„ ์˜๋ฏธํ•˜๋ฉฐ, ์ปค๋ฐ‹ ํ•ด์‹œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ €์žฅ๋˜์–ด ๋กค๋ฐฑ ์‹œ ์ •ํ™•ํ•œ ์‹œ์  ๋ณต์›์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
  • ๋‹ค์ค‘ ์ธ์Šคํ„ด์Šค์˜ ํ™•์žฅ์„ฑ์„ ๊ณ ๋ คํ•ด์„œ ์‚ฐ์ถœ๋ฌผ์„ ๊ณตํ†ต์˜ ์ €์žฅ์†Œ์— ์ €์žฅํ•˜๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•˜๋‹ค.


4. ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์„ฑ ๋ช…์„ธ

4-1. GitHub Actions ์„ค๊ณ„ ๋ฌธ์„œ

4-1-1. ์†๋„๋ฅผ ๋น ๋ฅด๊ฒŒ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ์บ์‹ฑ

์‹ค์ œ ์ด๋ฏธ์ง€ ํ™•์ธ image image
  • ๋งค๋ฒˆ ๋นŒ๋“œ๋ฅผ ์ง„ํ–‰ํ• ๋•Œ๋งˆ๋‹ค ์†๋„๊ฐ€ ๋งค์šฐ ๋А๋ฆฌ๊ธฐ์— ์–ด๋–ค ๊ฒƒ์ด ๋ฌธ์ œ์ธ๊ฐ€ ํ™•์ธ์„ ํ•ด๋ณด์•˜๋”๋‹ˆ, Gradle๊ณผ ๊ฐ™์€ ์˜์กด์„ฑ ์„ค์น˜์—์„œ 4๋ถ„ 40์ดˆ๊ฐ€ ๊ฑธ๋ฆฌ๋Š” ๋ชจ์Šต์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Œ
  • Gradle ์บ์‹ฑ์œผ๋กœ 98.9% ๊ฐ์†Œ

4-1-2. ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ํ™˜๊ฒฝ ์„ค์ • ๊ตฌ์„ฑ

ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ํ™˜๊ฒฝ ์„ค์ • ๊ธฐ์ค€ ๋ฉฑ๋“ฑ์„ฑ, ๊ฒฉ๋ฆฌ์„ฑ, ์œ ์ง€๋ณด์ˆ˜ ์œผ๋กœ ์žก์•˜์Šต๋‹ˆ๋‹ค.

์ข…๋ฅ˜ ์ง์ ‘ ์„ค์น˜ํ•ด์„œ ํ”„๋กœ์„ธ์Šค๋กœ ๋„์šฐ๊ธฐ ๊นƒํ—ˆ๋ธŒ ์ž์ฒด ์ปจํ…Œ์ด๋„ˆ ์‚ฌ์šฉ [๊ทผ๊ฑฐ] self-hosted Runner
์†๋„ ๋งค๋ฒˆ ํŒจํ‚ค์ง€ ๋‹ค์šด๋กœ๋“œ ๋ฐ ๋นŒ๋“œ/์„ค์น˜ ์‹œ๊ฐ„ ๋ฐœ์ƒ ์ด๋ฏธ์ง€ Pull ๋ฐ ์ปจํ…Œ์ด๋„ˆ ๊ธฐ๋™ ์‹œ๊ฐ„ ํ•„์š” Runner์— ์˜์กด์„ฑ/์บ์‹œ๊ฐ€ ์ด๋ฏธ ์žˆ์œผ๋ฉด ๋งค์šฐ ๋น ๋ฆ„.
ํŠน์ง• apt install์˜ ์Šคํฌ๋ฆฝํŠธ์„ ์›Œํฌํ”Œ๋กœ์šฐ์— ์ง์ ‘ ๋ช…์‹œ jobs.<id>.services ์„ค์ •์„ ํ†ตํ•ด ์„ ์–ธ์ ์œผ๋กœ ๊ตฌ์„ฑ ์‹ค์ œ EC2์— ํ…Œ์ŠคํŠธ์šฉ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์ „ ๊ตฌ์„ฑ
์žฅ์  Docker ์ง€์‹ ์—†์ด๋„ ๋ฆฌ๋ˆ…์Šค ๋ช…๋ น์–ด๋งŒ์œผ๋กœ ์ฆ‰์‹œ ํ™˜๊ฒฝ ๊ตฌ์„ฑ ๊ฐ€๋Šฅ. ๋งค๋ฒˆ ๊นจ๋—ํ•œ ์ƒํƒœ์—์„œ ํ…Œ์ŠคํŠธ ์ง„ํ–‰ ๊ฐ€๋Šฅ VPC ๋‚ด๋ถ€์— ๋‘์–ด ๋ณด์•ˆ ํ†ต๊ณผ ์—†์ด ๋‚ด๋ถ€ DB/API์— ๊ณ ์† ์ ‘๊ทผ ๊ฐ€๋Šฅ
๋ฌธ์ œ์  Runner์—์„œ ํŒจํ‚ค์ง€ ์„ค์น˜/ํ™˜๊ฒฝ ์ฐจ์ด๋กœ ๋ถˆ์•ˆ์ •ํ•ด์งˆ ์ˆ˜ ์žˆ์Œ ๊นƒํ—ˆ๋ธŒ ๊ณต์šฉ ์„œ๋ฒ„์˜ CPU/RAM ํ•œ๊ณ„๋กœ ์„ฑ๋Šฅ ๋ณ‘๋ชฉ ๊ฐ€๋Šฅ์„ฑ ์กด์žฌ ๋ฐ์ดํ„ฐ ํ™˜๊ฒฝ ์˜ค์—ผ ๋ฌธ์ œ ๋ฐ EC2์˜ ๋ฆฌ์†Œ์Šค ๋ถ€์กฑ ๋ฌธ์ œ ๊ฐ€๋Šฅ์„ฑ

โ‡’ GitHub Service Containers ๋ฐฉ์‹์„ ์ฑ„ํƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.

1. ๋ฉฑ๋“ฑ์„ฑ

  • Self-hosted Runner๋Š” ์ด์ „ ํ…Œ์ŠคํŠธ๊ฐ€ ๋‚จ๊ธด ๋ฐ์ดํ„ฐ๋‚˜ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋‹ค์Œ ํ…Œ์ŠคํŠธ์— ์˜ํ–ฅ์„ ์ฃผ๋Š” 'ํ™˜๊ฒฝ ์˜ค์—ผ' ๋ฌธ์ œ ๊ฐ€๋Šฅ์„ฑ์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.
  • Service Containers๋Š” Job ์‹œ์ž‘ ์‹œ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ข…๋ฃŒ ์‹œ ์ฆ‰์‹œ ํŒŒ๊ธฐํ•ฉ๋‹ˆ๋‹ค. ์–ด๋–ค ์‹œ์ ์— ์‹คํ–‰ํ•˜๋”๋ผ๋„ ํ•ญ์ƒ ์™„๋ฒฝํ•˜๊ฒŒ ๊นจ๋—ํ•œ ์ƒํƒœ(Fresh State)์—์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹œ์ž‘๋˜๋ฏ€๋กœ, ์ฝ”๋“œ ๋ณ€๊ฒฝ ์™ธ์˜ ์™ธ๋ถ€ ์š”์ธ์œผ๋กœ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ฌ๋ผ์ง€์ง€ ์•Š๋Š” ๋ฉฑ๋“ฑ์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

2. ๊ฒฉ๋ฆฌ์„ฑ : ์ถฉ๋Œ ์—†๋Š” ๋ณ‘๋ ฌ ํ…Œ์ŠคํŠธ

  • Self-hosted Runner๋Š” ์—ฌ๋Ÿฌ ๋นŒ๋“œ๊ฐ€ ๋™์‹œ์— ๋Œ์•„๊ฐˆ ๊ฒฝ์šฐ ํฌํŠธ ์ถฉ๋Œ์ด๋‚˜ ๋ฆฌ์†Œ์Šค ๊ฐ„์„ญ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฐ ํ…Œ์ŠคํŠธ๋Š” ๋…๋ฆฝ๋œ Docker ์ปจํ…Œ์ด๋„ˆ ๋„คํŠธ์›Œํฌ ๋‚ด์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ๋™์ผํ•œ ํฌํŠธ(์˜ˆ: MySQL 3306)๋ฅผ ์‚ฌ์šฉํ•˜๋”๋ผ๋„ ์ปจํ…Œ์ด๋„ˆ ๋‹จ์œ„๋กœ ์™„๋ฒฝํžˆ ๊ฒฉ๋ฆฌ๋˜์–ด ์žˆ์–ด, ๋ณ‘๋ ฌ ๋นŒ๋“œ ์‹œ์—๋„ ์•ˆ์ •์ ์ธ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

3. ์œ ์ง€๋ณด์ˆ˜์„ฑ : ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์˜ ๊ด€๋ฆฌ

  • ์ง์ ‘ ์„ค์น˜๋Š” OS ์—…๋ฐ์ดํŠธ์— ๋”ฐ๋ฅธ ์Šคํฌ๋ฆฝํŠธ ์ˆ˜์ •์ด ํ•„์š”ํ•˜๊ณ , Self-hosted Runner๋Š” EC2์˜ ๋ณด์•ˆ ํŒจ์น˜, ๋””์Šคํฌ ์šฉ๋Ÿ‰, ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ด€๋ฆฌ๋ฅผ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์„œ๋น„์Šค ์ปจํ…Œ์ด๋„ˆ๋Š” YAML ํŒŒ์ผ ๋‚ด์— ์‚ฌ์šฉํ•  ์ด๋ฏธ์ง€์™€ ๋ฒ„์ „๋งŒ ๋ช…์‹œํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. GitHub๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” ๊ด€๋ฆฌํ˜• ์„œ๋น„์Šค๋ฅผ ํ™œ์šฉํ•จ์œผ๋กœ์จ ์ธํ”„๋ผ ์šด์˜ ๋ถ€๋‹ด์„ ์ตœ์†Œํ™”ํ•˜๊ณ  ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ฒ€์ฆ์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4-1-3. GitHub Secrets ํ™˜๊ฒฝ ์„ค์ • ๊ตฌ์„ฑ

๊ณตํ†ต
- `DISCORD_WEBHOOK_URL` : ์ „๋‹ฌํ•  ์ „์ฒด ๋””์Šค์ฝ”๋“œ ์ฑ„ํŒ…๋ฐฉ
- `AWS_REGION` : ์ง€์—ญ
- `AWS_ACCESS_KEY` : AWS AccessKey
- `AWS_SECRET_KEY`  : AWS SecretKey
- `BUCKET_NAME` : S3๋ฒ„ํ‚ท ์ด๋ฆ„

BE

- `DISCORD_WEBHOOK_URL_BE` : ์ „๋‹ฌํ•  ์ง๊ตฐ ๋””์Šค์ฝ”๋“œ ์ฑ„ํŒ…๋ฐฉ
- `application-test.yml` : ํ…Œ์ŠคํŠธ์šฉ YAML

FE

- `DISCORD_WEBHOOK_URL_FE` : ์ „๋‹ฌํ•  ์ง๊ตฐ ๋””์Šค์ฝ”๋“œ ์ฑ„ํŒ…๋ฐฉ
- `env` : ENV

AI

- `DISCORD_WEBHOOK_URL_AI` : ์ „๋‹ฌํ•  ์ง๊ตฐ ๋””์Šค์ฝ”๋“œ ์ฑ„ํŒ…๋ฐฉ
- `env` : ENV

4-1-4. ๋ธŒ๋žœ์น˜ ๋ณดํ˜ธ

  • develop, release, main ์—์„œ โ€œRequire status checks to passโ€ ํ™œ์„ฑํ™”

4-2. ํŒŒ์ดํ”„๋ผ์ธ YAML ์ž‘๋™

4-2-1. BE ๊ด€๋ จ ํŒŒ์ดํ”„๋ผ์ธ ์„ค๊ณ„

  • BE ํŒŒ์ดํ”„๋ผ์ธ PR ์„ค๊ณ„ (ํŽผ์น˜๊ธฐ)
    name: Devths-BE PR ํŒŒ์ดํ”„๋ผ์ธ
    
    on:
      pull_request:
        branches:
          - develop
          - release
          - main
    
    env:
      JAVA_VERSION: "21"
     
    jobs:
      lint:
        name: lint
        runs-on: ubuntu-22.04
        steps:
          - name: ์ฒดํฌ์•„์›ƒ
          - uses: actions/checkout@v4
    			
          - name: ์ž๋ฐ” ์„ค์น˜
            uses: actions/setup-java@v4
            with:
              distribution: temurin
              java-version: ${{ env.JAVA_VERSION }}
    
    			- name: Gradle ์บ์‹ฑ
            uses: actions/cache@v3
            with:
              path: |
                ~/.gradle/caches
                ~/.gradle/wrapper
              key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
              restore-keys: |
                gradle-${{ runner.os }}
    
          - name: Lint ์ˆ˜ํ–‰
            run: ๋ฆฐํŠธ ์ˆ˜ํ–‰
    
      static_analysis:
        name: static-analysis
        runs-on: ubuntu-22.04
        needs: [lint]
        steps:
          - name: ์ฒดํฌ์•„์›ƒ
          - uses: actions/checkout@v4
    
          - name: ์ž๋ฐ” ์„ค์น˜
            uses: actions/setup-java@v4
            with:
              distribution: temurin
              java-version: ${{ env.JAVA_VERSION }}
    
          - name: ์ •์  ๋ถ„์„ ์ง„ํ–‰
            run : ์ •์  ๋ถ„์„ ๋„๊ตฌ ์„ ์ • ํ›„ ์ง„ํ–‰
    
      test:
        name: test
        runs-on: ubuntu-22.04
        needs: [static_analysis]
    
        services:
          mysql:
            image: mysql:8.0
            env:
            # ํ…Œ์ŠคํŠธ์šฉ ์„œ๋น„์Šค์ด๊ธฐ์— ๋…ธ์ถœ๋˜์–ด๋„ ๋ฌธ์ œ ์—†์Œ
              MYSQL_ROOT_PASSWORD: root
              MYSQL_DATABASE: app
              MYSQL_USER: app
              MYSQL_PASSWORD: app
            ports:
              - 3306:3306
            options: >-
              --health-cmd="mysqladmin ping -h 127.0.0.1 -u root -proot"
              --health-interval=10s
              --health-timeout=5s
              --health-retries=10
    
        steps:
          - name: ๋ ˆํฌ์ง€ํ† ๋ฆฌ ์ฒดํฌ์•„์›ƒ
          - uses: actions/checkout@v4
    
          - name: ์ž๋ฐ” ์„ค์ •
            uses: actions/setup-java@v4
            with:
              distribution: temurin
              java-version: ${{ env.JAVA_VERSION }}
    			
    			- name: Application-test ์ฃผ์ž…
    				run: |
              mkdir -p src/test/resources
              echo "${{secrets.APPLICATION_TEST_YML}}" > ./src/test/resources/application-test.yml
    						
          - name: ํ…Œ์ŠคํŠธ ์‹คํ–‰
            run: ./gradlew test
    
          - name: ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ํ™•์ธ
            if: always()
            uses: actions/upload-artifact@v4
            with:
              name: be-test-reports-${{ github.sha }}
              path: |
                build/reports/tests
                build/test-results
              if-no-files-found: ignore
    
          - name: Jacoco ์ปค๋ฒ„๋ฆฌ์ง€ ํ™•์ธํ•˜๊ธฐ
            if: always()
            uses: actions/upload-artifact@v4
            with:
              name: be-coverage-${{ github.sha }}
              path: |
                build/reports/jacoco
              if-no-files-found: ignore
    
      build:
        name: build
        runs-on: ubuntu-22.04
        needs: [test] # ํ…Œ์ŠคํŠธ ํ†ต๊ณผ ํ›„ ๋นŒ๋“œ
        steps:
          - name: ์ฒดํฌ์•„์›ƒ
          - uses: actions/checkout@v4
    
          - name: ์ž๋ฐ” ์„ค์น˜
            uses: actions/setup-java@v4
            with:
              distribution: temurin
              java-version: ${{ env.JAVA_VERSION }}
          			
          - name: Bootjar ๊ธฐ๋ฐ˜ ๋นŒ๋“œ(ํ…Œ์ŠคํŠธ ํ†ต๊ณผ)
            run: ./gradlew bootJar -x test --no-daemon
    
      notify-discord:
    	  name: notify
        runs-on: ubuntu-22.04
        needs: build
        if: always()
        steps:
          - name: Discord ๋นŒ๋“œ ์•Œ๋ฆผ
            uses: sarisia/actions-status-discord@v1
            with:
              webhook: ${{ github.event.pull_request.base.ref == 'develop' && secrets.DISCORD_WEBHOOK_URL_BE || secrets.DISCORD_WEBHOOK_URL }}
              title: "${{ needs.build.result == 'success' && 'โœ…' || 'โŒ' }} ์ƒˆ๋กœ์šด PR์ด ์˜ฌ๋ผ์™”์Šต๋‹ˆ๋‹ค!"
              description: |
                **์ œ๋ชฉ**: ${{ github.event.pull_request.title }}
                **์ž‘์„ฑ์ž**: ${{ github.event.pull_request.user.login }}
                **๋ธŒ๋žœ์น˜**: `${{ github.event.pull_request.base.ref }}`โ†`${{ github.event.pull_request.head.ref }}`
    
                **๐Ÿ“Š ๋นŒ๋“œ ๊ฒฐ๊ณผ**:
                - ์ƒํƒœ: ${{ needs.build.result == 'success' && '์„ฑ๊ณต' || '์‹คํŒจ' }}
    
                **๐Ÿ”— ๋งํฌ**:
                - [PR ํ™•์ธํ•˜๊ธฐ](${{ github.event.pull_request.html_url }})
    
              color: "${{ needs.build.result == 'success' && 65280 || 16711680 }}"
              username: GitHub PR Bot
              avatar_url: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
  • BE ํŒŒ์ดํ”„๋ผ์ธ Merge ์„ค๊ณ„ (ํŽผ์น˜๊ธฐ)
    name: Devths-BE
    
    on:
    
      push:
        branches:
          - develop
          - release
          - main
    
    env:
      JAVA_VERSION: "21"
      AWS_REGION: ${{ secrets.AWS_REGION }}
      S3_BUCKET: ${{ secrets.S3_BUCKET }}
      S3_PREFIX: ci/${{ github.repository }}/${{ github.ref_name }}/${{ timestamp }}_${{ github.sha }} #์ปค๋ฐ‹ ๋ฐ ์‹œ๊ฐ„ ๊ธฐ์ค€ ํ•ด์‹œ
    
    jobs:
      lint:
        name: lint
        runs-on: ubuntu-22.04
        steps:
          - name: ์ฒดํฌ์•„์›ƒ
          - uses: actions/checkout@v4
    			
          - name: ์ž๋ฐ” ์„ค์น˜
            uses: actions/setup-java@v4
            with:
              distribution: temurin
              java-version: ${{ env.JAVA_VERSION }}
    
    			- name: Gradle ์บ์‹ฑ
            uses: actions/cache@v3
            with:
              path: |
                ~/.gradle/caches
                ~/.gradle/wrapper
              key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
              restore-keys: |
                gradle-${{ runner.os }}
    
          - name: Lint ์ˆ˜ํ–‰
            run: ๋ฆฐํŠธ ์ˆ˜ํ–‰
    
      static_analysis:
        name: static-analysis
        runs-on: ubuntu-22.04
        needs: [lint]
        steps:
          - name: ์ฒดํฌ์•„์›ƒ
          - uses: actions/checkout@v4
    
          - name: ์ž๋ฐ” ์„ค์น˜
            uses: actions/setup-java@v4
            with:
              distribution: temurin
              java-version: ${{ env.JAVA_VERSION }}
    
          - name: ์ •์  ๋ถ„์„ ์ง„ํ–‰
            run : ์ •์  ๋ถ„์„ ๋„๊ตฌ ์„ ์ • ํ›„ ์ง„ํ–‰
    
      test:
        name: test
        runs-on: ubuntu-22.04
        needs: [static_analysis]
    
        # ํ†ตํ•ฉํ…Œ์ŠคํŠธ์šฉ ์„œ๋น„์Šค ์ปจํ…Œ์ด๋„ˆ ์„ค์ •
        services:
          mysql:
            image: mysql:8.0
            env:
            # ํ…Œ์ŠคํŠธ์šฉ ์„œ๋น„์Šค์ด๊ธฐ์— ๋…ธ์ถœ๋˜์–ด๋„ ๋ฌธ์ œ ์—†์Œ
              MYSQL_ROOT_PASSWORD: root
              MYSQL_DATABASE: app
              MYSQL_USER: app
              MYSQL_PASSWORD: app
            ports:
              - 3306:3306
            options: >-
              --health-cmd="mysqladmin ping -h 127.0.0.1 -u root -proot"
              --health-interval=10s
              --health-timeout=5s
              --health-retries=10
    
        steps:
          - name: ๋ ˆํฌ์ง€ํ† ๋ฆฌ ์ฒดํฌ์•„์›ƒ
          - uses: actions/checkout@v4
    
          - name: ์ž๋ฐ” ์„ค์ •
            uses: actions/setup-java@v4
            with:
              distribution: temurin
              java-version: ${{ env.JAVA_VERSION }}
    			
    			- name: Application-test ์ฃผ์ž…
    				run: |
              mkdir -p src/test/resources
              echo "${{secrets.APPLICATION_TEST_YML}}" > ./src/test/resources/application-test.yml
    						
          - name: ํ…Œ์ŠคํŠธ ์‹คํ–‰
            run: ./gradlew test
    
          - name: ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ํ™•์ธ
            if: always()
            uses: actions/upload-artifact@v4
            with:
              name: be-test-reports-${{ github.sha }}
              path: |
                build/reports/tests
                build/test-results
              if-no-files-found: ignore
    
          - name: Jacoco ์ปค๋ฒ„๋ฆฌ์ง€ ํ™•์ธํ•˜๊ธฐ
            if: always()
            uses: actions/upload-artifact@v4
            with:
              name: be-coverage-${{ github.sha }}
              path: |
                build/reports/jacoco
              if-no-files-found: ignore
    
      build:
        name: build
        runs-on: ubuntu-22.04
        needs: [test] # ํ…Œ์ŠคํŠธ ํ†ต๊ณผ ํ›„ ๋นŒ๋“œ
        steps:
          - name: ์ฒดํฌ์•„์›ƒ
          - uses: actions/checkout@v4
    
          - name: ์ž๋ฐ” ์„ค์น˜
            uses: actions/setup-java@v4
            with:
              distribution: temurin
              java-version: ${{ env.JAVA_VERSION }}
          			
          - name: Bootjar ๊ธฐ๋ฐ˜ ๋นŒ๋“œ(ํ…Œ์ŠคํŠธ ํ†ต๊ณผ)
            run: ./gradlew bootJar -x test --no-daemon
     
          - name: AWS ์ž๊ฒฉ ์ฆ๋ช… ์„ค์ •
            uses: aws-actions/configure-aws-credentials@v4
            with:
              aws-access-key-id: ${{secrets.AWS_ACCESS_KEY}}
              aws-secret-access-key: ${{secrets.AWS_SECRET_KEY}}
              aws-region: ${{secrets.AWS_REGION}}
    
          - name: JAR์„ S3 ์—…๋กœ๋“œ
            run: |
              set -euo pipefail
              DEST="s3://${S3_BUCKET}/${S3_PREFIX}/artifacts/jar"
              mkdir -p /tmp/jars
    
              # build/libs/*.jar ์—…๋กœ๋“œ (์—ฌ๋Ÿฌ ๊ฐœ์ผ ์ˆ˜ ์žˆ์–ด for ์ฒ˜๋ฆฌ)
              shopt -s nullglob
              files=(build/libs/*.jar)
              if [ ${#files[@]} -eq 0 ]; then
                echo "No jar found in build/libs"
                exit 1
              fi
    
              for f in "${files[@]}"; do
                aws s3 cp "$f" "${DEST}/$(basename "$f")" --only-show-errors
              done
    
      notify-discord:
        name: notify
        runs-on: ubuntu-22.04
        needs: build
        if: always()
        steps:
          - name: Discord ๋นŒ๋“œ ์•Œ๋ฆผ
            uses: sarisia/actions-status-discord@v1
            with:
              webhook: ${{ secrets.DISCORD_WEBHOOK_BACK_URL }}
              title: "${{ needs.build.result == 'success' && 'โœ…' || 'โŒ' }} ์ƒˆ๋กœ์šด PR์ด ์˜ฌ๋ผ์™”์Šต๋‹ˆ๋‹ค!"
              description: |
                **์ œ๋ชฉ**: ${{ github.event.pull_request.title }}
                **์ž‘์„ฑ์ž**: ${{ github.event.pull_request.user.login }}
                **๋ธŒ๋žœ์น˜**: `${{ github.event.pull_request.base.ref }}`โ†`${{ github.event.pull_request.head.ref }}`
    
                **๐Ÿ“Š ๋นŒ๋“œ ๊ฒฐ๊ณผ**:
                - ์ƒํƒœ: ${{ needs.build.result == 'success' && '์„ฑ๊ณต' || '์‹คํŒจ' }}
    
                **๐Ÿ”— ๋งํฌ**:
                - [PR ํ™•์ธํ•˜๊ธฐ](${{ github.event.pull_request.html_url }})
    
              color: "${{ needs.build.result == 'success' && 65280 || 16711680 }}"
              username: GitHub PR Bot
              avatar_url: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png

4-2-2. FE ๊ด€๋ จ ํŒŒ์ดํ”„๋ผ์ธ ์„ค๊ณ„

  • FE ํŒŒ์ดํ”„๋ผ์ธ PR ์„ค๊ณ„ (ํŽผ์น˜๊ธฐ)
    name: Devths-FE PR ํŒŒ์ดํ”„๋ผ์ธ
    
    on:
      pull_request:
        branches:
          - develop
          - release
          - main
    
    env:
      NODE_VERSION: "20.x"
    
    jobs:
      # 1. Lint ๋‹จ๊ณ„
      lint:
        name: lint
        runs-on: ubuntu-22.04
        steps:
          - name: ์ฒดํฌ์•„์›ƒ
            uses: actions/checkout@v3
          
          - name: ๋…ธ๋“œ JS ์„ค์น˜
            uses: actions/setup-node@v3
            with:
              node-version: ${{ env.NODE_VERSION }}
              cache: 'npm'
          
          - name: ์˜์กด์„ฑ ์„ค์น˜
            run: npm ci
            
          - name: Lint ๋ถ„์„
            run: npm run lint
    
      # 2. ์ •์  ๋ถ„์„ ๋‹จ๊ณ„ (Lint ์„ฑ๊ณต ์‹œ ์‹คํ–‰)
      static-analysis:
        name: static-analysis (CodeQL)
        needs: [lint]
        runs-on: ubuntu-22.04
        permissions:
          actions: read
          contents: read
          security-events: write # ๊ฒฐ๊ณผ๋ฅผ Security ํƒญ์— ๊ธฐ๋กํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”
    
        steps:
          - name: ์ฒดํฌ์•„์›ƒ
            uses: actions/checkout@v3
    
          - name: CodeQL ์ดˆ๊ธฐํ™”
            uses: github/codeql-action/init@v2
            with:
              languages: javascript # TS๋ฅผ ํฌํ•จํ•œ JS ํ™˜๊ฒฝ ๋ถ„์„
    
          - name: ๋…ธ๋“œ JS ์„ค์น˜
            uses: actions/setup-node@v3
            with:
              node-version: ${{ env.NODE_VERSION }}
              cache: 'npm'
          
          - name: ์˜์กด์„ฑ ์„ค์น˜
            run: npm ci
    
          - name: CodeQL ๋ถ„์„ ์ˆ˜ํ–‰
            uses: github/codeql-action/analyze@v2
    
      # 3. ํ…Œ์ŠคํŠธ ๋‹จ๊ณ„ (์ •์  ๋ถ„์„ ์„ฑ๊ณต ์‹œ ์‹คํ–‰)
      test:
        name: test
        needs: [static-analysis]
        runs-on: ubuntu-22.04
        steps:
          - name: ์ฒดํฌ์•„์›ƒ
            uses: actions/checkout@v3
    
          - name: ๋…ธ๋“œ JS ์„ค์น˜
            uses: actions/setup-node@v3
            with:
              node-version: ${{ env.NODE_VERSION }}
              cache: 'npm'
    			
          - name: ์˜์กด์„ฑ ์„ค์น˜
            run: npm ci
    				
          - name: ํ…Œ์ŠคํŠธ ์‹คํ–‰ (JUnit ๋ฆฌํฌํŠธ ์ƒ์„ฑ)
            run: npm test -- --reporters=default --reporters=jest-junit
            env:
              JEST_JUNIT_OUTPUT_DIR: "./test-results"
              JEST_JUNIT_OUTPUT_NAME: "test-results.xml"
    
    			- name: Test ๊ฒฐ๊ณผ ์—…๋กœ๋“œ
            uses: EnricoMi/publish-unit-test-result-action@v2
            if: always()
            with:
              files: 'test-results/test-results.xml'
    							  
      # 4. ๋นŒ๋“œ ๋‹จ๊ณ„ (ํ…Œ์ŠคํŠธ ์„ฑ๊ณต ์‹œ ์‹คํ–‰)
      build:
        name: build
        needs: [test]
        runs-on: ubuntu-22.04
        steps:
          - name: ์ฒดํฌ์•„์›ƒ
            uses: actions/checkout@v3
          
          - name: ๋…ธ๋“œ JS ์„ค์น˜
            uses: actions/setup-node@v3
            with:
              node-version: ${{ env.NODE_VERSION }}
              cache: 'npm'
          
          - name: ์˜์กด์„ฑ ์„ค์น˜
            run: npm ci
          
          - name: ํ”„๋กœ์ ํŠธ ๋นŒ๋“œ
            run: npm run build
    		
      # 5. ๋””์Šค์ฝ”๋“œ ์•Œ๋ฆผ ๋‹จ๊ณ„ (ํ•ญ์ƒ ์‹คํ–‰๋˜์ง€๋งŒ ๋นŒ๋“œ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํฌํŠธํ•จ)
      notify-discord:
        name: notify
        runs-on: ubuntu-22.04
        needs: [build] # ๋นŒ๋“œ ๋‹จ๊ณ„์˜ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋‹ค๋ฆผ
        if: always()
        steps:
          - name: Discord ๋นŒ๋“œ ์•Œ๋ฆผ
            uses: sarisia/actions-status-discord@v1
            with:
              webhook: ${{ github.event.pull_request.base.ref == 'develop' && secrets.DISCORD_WEBHOOK_URL_FE || secrets.DISCORD_WEBHOOK_URL }}
              title: "${{ needs.build.result == 'success' && 'โœ…' || 'โŒ' }} ํ”„๋ŸฐํŠธ ์ƒˆ๋กœ์šด PR์ด ์˜ฌ๋ผ์™”์Šต๋‹ˆ๋‹ค!"
              description: |
                **์ œ๋ชฉ**: ${{ github.event.pull_request.title }}
                **์ž‘์„ฑ์ž**: ${{ github.event.pull_request.user.login }}
                **๋ธŒ๋žœ์น˜**: `${{ github.event.pull_request.base.ref }}` โ† `${{ github.event.pull_request.head.ref }}`
                
                **๐Ÿ“Š ์ตœ์ข… ๋นŒ๋“œ ๊ฒฐ๊ณผ**:
                - ์ƒํƒœ: ${{ needs.build.result == 'success' && '์„ฑ๊ณต' || '์‹คํŒจ' }}
                
                **๐Ÿ”— ๋งํฌ**:
                - [PR ํ™•์ธํ•˜๊ธฐ](${{ github.event.pull_request.html_url }})
    
              color: "${{ needs.build.result == 'success' && 65280 || 16711680 }}"
              username: GitHub PR Bot
              avatar_url: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
  • FE ํŒŒ์ดํ”„๋ผ์ธ Merge ์„ค๊ณ„ (ํŽผ์น˜๊ธฐ)
    name: DevthsFE ํŒŒ์ดํ”„๋ผ์ธ
    
    on:
      push:
        branches:
          - develop
          - release
          - main
    env:
      NODE_VERSION: "20.x"
      AWS_REGION: ${{ secrets.AWS_REGION }}
      S3_BUCKET: ${{ secrets.S3_BUCKET }}
      S3_PREFIX: ci/${{ github.repository }}/${{ github.ref_name }}/${{ timestamp }}_${{ github.sha }} #์ปค๋ฐ‹ ๋ฐ ์‹œ๊ฐ„ ๊ธฐ์ค€ ํ•ด์‹œ
    
    jobs:
      # 1. Lint ๋‹จ๊ณ„
      lint:
        name: lint
        runs-on: ubuntu-22.04
        steps:
          - name: ์ฒดํฌ์•„์›ƒ
            uses: actions/checkout@v3
          
          - name: ๋…ธ๋“œ JS ์„ค์น˜
            uses: actions/setup-node@v3
            with:
              node-version: ${{ env.NODE_VERSION }}
              cache: 'npm'
          
          - name: ์˜์กด์„ฑ ์„ค์น˜
            run: npm ci
            
          - name: Lint ๋ถ„์„
            run: npm run lint
    
      # 2. ์ •์  ๋ถ„์„ ๋‹จ๊ณ„ (Lint ์„ฑ๊ณต ์‹œ ์‹คํ–‰)
      static-analysis:
        name: static-analysis (CodeQL)
        needs: [lint]
        runs-on: ubuntu-22.04
        permissions:
          actions: read
          contents: read
          security-events: write # ๊ฒฐ๊ณผ๋ฅผ Security ํƒญ์— ๊ธฐ๋กํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”
    
        steps:
          - name: ์ฒดํฌ์•„์›ƒ
            uses: actions/checkout@v3
    
          - name: CodeQL ์ดˆ๊ธฐํ™”
            uses: github/codeql-action/init@v2
            with:
              languages: javascript # TS๋ฅผ ํฌํ•จํ•œ JS ํ™˜๊ฒฝ ๋ถ„์„
    
          - name: ๋…ธ๋“œ JS ์„ค์น˜
            uses: actions/setup-node@v3
            with:
              node-version: ${{ env.NODE_VERSION }}
              cache: 'npm'
          
          - name: ์˜์กด์„ฑ ์„ค์น˜
            run: npm ci
    
          - name: CodeQL ๋ถ„์„ ์ˆ˜ํ–‰
            uses: github/codeql-action/analyze@v2
    
      # 3. ํ…Œ์ŠคํŠธ ๋‹จ๊ณ„ (์ •์  ๋ถ„์„ ์„ฑ๊ณต ์‹œ ์‹คํ–‰)
      test:
        name: test
        needs: [static-analysis]
        runs-on: ubuntu-22.04
        steps:
          - name: ์ฒดํฌ์•„์›ƒ
            uses: actions/checkout@v3
    
          - name: ๋…ธ๋“œ JS ์„ค์น˜
            uses: actions/setup-node@v3
            with:
              node-version: ${{ env.NODE_VERSION }}
              cache: 'npm'
    			
          - name: ์˜์กด์„ฑ ์„ค์น˜
            run: npm ci
    				
          - name: ํ…Œ์ŠคํŠธ ์‹คํ–‰ (JUnit ๋ฆฌํฌํŠธ ์ƒ์„ฑ)
            run: npm test -- --reporters=default --reporters=jest-junit
            env:
              JEST_JUNIT_OUTPUT_DIR: "./test-results"
              JEST_JUNIT_OUTPUT_NAME: "test-results.xml"
    
    			- name: Test ๊ฒฐ๊ณผ ์—…๋กœ๋“œ
            uses: EnricoMi/publish-unit-test-result-action@v2
            if: always()
            with:
              files: 'test-results/test-results.xml'
    							  
      # 4. ๋นŒ๋“œ ๋‹จ๊ณ„ (ํ…Œ์ŠคํŠธ ์„ฑ๊ณต ์‹œ ์‹คํ–‰)
      build:
        name: build
        needs: [test]
        runs-on: ubuntu-22.04
        steps:
          - name: ์ฒดํฌ์•„์›ƒ
            uses: actions/checkout@v3
          
          - name: ๋…ธ๋“œ JS ์„ค์น˜
            uses: actions/setup-node@v3
            with:
              node-version: ${{ env.NODE_VERSION }}
              cache: 'npm'
          
          - name: ์˜์กด์„ฑ ์„ค์น˜
            run: npm ci
          
          - name: ํ”„๋กœ์ ํŠธ ๋นŒ๋“œ
            run: npm run build
    			
    			- name: AWS ์ž๊ฒฉ ์ฆ๋ช… ์„ค์ •
            uses: aws-actions/configure-aws-credentials@v4
            with:
              aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
              aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }}
              aws-region: ${{ env.AWS_REGION }}
    
          - name: S3 ๊ณ ์œ  ๊ฒฝ๋กœ๋กœ ์—…๋กœ๋“œ
            run: |
              # ์‚ฌ์šฉ์ž๊ฐ€ ์ •์˜ํ•œ prefix ๊ทœ์น™ ์ ์šฉ
              S3_PATH="s3://${{ env.S3_BUCKET }}/ci/${{ github.repository }}/${{ github.ref_name }}/${{ env.TIMESTAMP }}_${{ github.sha }}"
              aws s3 sync ./dist $S3_PATH
              echo "DEPLOY_URL=$S3_PATH" >> $GITHUB_ENV
    			
      # 5. ๋””์Šค์ฝ”๋“œ ์•Œ๋ฆผ ๋‹จ๊ณ„ (ํ•ญ์ƒ ์‹คํ–‰๋˜์ง€๋งŒ ๋นŒ๋“œ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํฌํŠธํ•จ)
      notify-discord:
        name: notify
        runs-on: ubuntu-22.04
        needs: [build] # ๋นŒ๋“œ ๋‹จ๊ณ„์˜ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋‹ค๋ฆผ
        if: always()
        steps:
          - name: Discord ๋นŒ๋“œ ์•Œ๋ฆผ
            uses: sarisia/actions-status-discord@v1
            with:
              webhook: ${{ github.event.pull_request.base.ref == 'develop' && secrets.DISCORD_WEBHOOK_URL_FE || secrets.DISCORD_WEBHOOK_URL }}
              title: "${{ needs.build.result == 'success' && 'โœ…' || 'โŒ' }} ํ”„๋ŸฐํŠธ ์ƒˆ๋กœ์šด PR์ด ์˜ฌ๋ผ์™”์Šต๋‹ˆ๋‹ค!"
              description: |
                **์ œ๋ชฉ**: ${{ github.event.pull_request.title }}
                **์ž‘์„ฑ์ž**: ${{ github.event.pull_request.user.login }}
                **๋ธŒ๋žœ์น˜**: `${{ github.event.pull_request.base.ref }}` โ† `${{ github.event.pull_request.head.ref }}`
                
                **๐Ÿ“Š ์ตœ์ข… ๋นŒ๋“œ ๊ฒฐ๊ณผ**:
                - ์ƒํƒœ: ${{ needs.build.result == 'success' && '์„ฑ๊ณต' || '์‹คํŒจ' }}
                
                **๐Ÿ”— ๋งํฌ**:
                - [PR ํ™•์ธํ•˜๊ธฐ](${{ github.event.pull_request.html_url }})
    
              color: "${{ needs.build.result == 'success' && 65280 || 16711680 }}"
              username: GitHub PR Bot
              avatar_url: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png

4-2-3. AI ๊ด€๋ จ ํŒŒ์ดํ”„๋ผ์ธ ์„ค๊ณ„

  • AI ํŒŒ์ดํ”„๋ผ์ธ PR ์„ค๊ณ„ (ํŽผ์น˜๊ธฐ)
    name: Devths-AI PR CI ํŒŒ์ดํ”„๋ผ์ธ
    
    on:
      pull_request:
        branches: 
        - develop
        - release
        - main
    
    env:
      PYTHON_VERSION: "3.10.19"
    
    jobs:
      # 1. Lint ๋‹จ๊ณ„ (Ruff)
      lint:
        name: lint (Ruff)
        runs-on: ubuntu-22.04
        steps:
          - name : ์ฒดํฌ์•„์›ƒ
            uses: actions/checkout@v4
          
          - name: ํŒŒ์ด์ฌ ์„ค์ •
            uses: actions/setup-python@v4
            with:
              python-version: ${{ env.PYTHON_VERSION }}
              cache: 'pip'
          
          - name: Ruff ์„ค์น˜ ๋ฐ ์‹คํ–‰
            run: |
              pip install ruff
              ruff check .
    
      # 2. ์ •์  ๋ถ„์„ ๋‹จ๊ณ„ (CodeQL)
      static-analysis:
        name: static-analysis (CodeQL)
        needs: [lint]
        runs-on: ubuntu-22.04
        permissions:
          security-events: write
        steps:
          - name : ์ฒดํฌ์•„์›ƒ
            uses: actions/checkout@v4
      
          - name: CodeQL ์ดˆ๊ธฐํ™”
            uses: github/codeql-action/init@v2
            with:
              languages: python
      
          - name: CodeQL ๋ถ„์„ ์ˆ˜ํ–‰
            uses: github/codeql-action/analyze@v2
    
      # 3. ํ…Œ์ŠคํŠธ ๋‹จ๊ณ„ (Pytest)
      test:
        name: test (Pytest)
        needs: [static-analysis]
        runs-on: ubuntu-22.04
        permissions:
          checks: write
          pull-requests: write
        steps:
          - name : ์ฒดํฌ์•„์›ƒ
            uses: actions/checkout@v4
          
          - name: ํŒŒ์ด์ฌ ์„ค์ •
            uses: actions/setup-python@v4
            with:
              python-version: ${{ env.PYTHON_VERSION }}
              cache: 'pip'
          
          - name: ์˜์กด์„ฑ ์„ค์น˜
            run: |
              python -m pip install --upgrade pip
              pip install -r requirements.txt
              pip install pytest pytest-xml-report
          
          - name: ํ…Œ์ŠคํŠธ ์‹คํ–‰
            run: pytest --junitxml=test-results.xml
          
          - name: Test ๊ฒฐ๊ณผ ์—…๋กœ๋“œ
            uses: EnricoMi/publish-unit-test-result-action@v2
            if: always()
            with:
              files: 'test-results.xml'
    
      # 4. ๋นŒ๋“œ ๋‹จ๊ณ„
      build:
        name: build
        needs: [test]
        runs-on: ubuntu-22.04
        steps:
          - name : ์ฒดํฌ์•„์›ƒ
            uses: actions/checkout@v4
            
          - name: ํŒŒ์ด์ฌ ์„ค์ •
            uses: actions/setup-python@v4
            with:
              python-version: ${{ env.PYTHON_VERSION }}
              cache: 'pip'
          
          - name: ์˜์กด์„ฑ ์„ค์น˜ ๋ฐ ๋นŒ๋“œ ๊ฒ€์ฆ
            run: |
              python -m pip install --upgrade pip
              pip install -r requirements.txt
    
      # 5. ๋””์Šค์ฝ”๋“œ ์•Œ๋ฆผ
      notify-discord:
        name: notify
        runs-on: ubuntu-22.04
        needs: [build]
        if: always()
        steps:
          - name: Discord ๋นŒ๋“œ ์•Œ๋ฆผ
            uses: sarisia/actions-status-discord@v1
            with:
              webhook: ${{ github.event.pull_request.base.ref == 'develop' && secrets.DISCORD_WEBHOOK_URL_AI || secrets.DISCORD_WEBHOOK_URL }}
              title: "${{ needs.build.result == 'success' && 'โœ…' || 'โŒ' }} AI PR ํŒŒ์ดํ”„๋ผ์ธ ๊ฒฐ๊ณผ"
              description: |
                **์ œ๋ชฉ**: ${{ github.event.pull_request.title }}
                **์ž‘์„ฑ์ž**: ${{ github.event.pull_request.user.login }}
                **์ƒํƒœ**: ${{ needs.build.result == 'success' && '์„ฑ๊ณต' || '์‹คํŒจ' }}
    
                **๐Ÿ“Š ๋ถ„์„ ์š”์•ฝ**:
                - Lint: Ruff ๊ฒ€์‚ฌ ์™„๋ฃŒ
                - Security: CodeQL ์ •์  ๋ถ„์„ ์™„๋ฃŒ
                - Test: Pytest ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์™„๋ฃŒ
                - Build: Pip ์˜์กด์„ฑ ๊ฒ€์ฆ ์™„๋ฃŒ
                
                **๐Ÿ”— ๋งํฌ**:
                - [PR ํ™•์ธํ•˜๊ธฐ](${{ github.event.pull_request.html_url }})
    
              color: "${{ needs.build.result == 'success' && 65280 || 16711680 }}"
              username: GitHub AI Bot
              avatar_url: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
  • AI ํŒŒ์ดํ”„๋ผ์ธ Merge ์„ค๊ณ„ (ํŽผ์น˜๊ธฐ)
    name: Devths-AI ๋ฐฐํฌ ํŒŒ์ดํ”„๋ผ์ธ
    
    on:
      push:
        branches:
          - develop
          - release
          - main
    
    env:
      PYTHON_VERSION: "3.10.19"
      AWS_REGION: ${{ secrets.AWS_REGION }}
      S3_BUCKET: ${{ secrets.S3_BUCKET_AI }}
    
    jobs:
      # 1. Lint ๋‹จ๊ณ„
      lint:
        name: lint (Ruff)
        runs-on: ubuntu-22.04
        steps:
          - uses: actions/checkout@v4
          - name: ํŒŒ์ด์ฌ ์„ค์ •
            uses: actions/setup-python@v4
            with:
              python-version: ${{ env.PYTHON_VERSION }}
              cache: 'pip'
          - run: pip install ruff
          - run: ruff check .
    
      # 2. ์ •์  ๋ถ„์„ (CodeQL)
      static-analysis:
        name: static-analysis (CodeQL)
        needs: [lint]
        runs-on: ubuntu-22.04
        permissions:
          security-events: write
        steps:
          - uses: actions/checkout@v4
          - name: CodeQL ์ดˆ๊ธฐํ™”
            uses: github/codeql-action/init@v2
            with:
              languages: python
          - name: CodeQL ๋ถ„์„ ์ˆ˜ํ–‰
            uses: github/codeql-action/analyze@v2
    
      # 3. ํ…Œ์ŠคํŠธ ๋‹จ๊ณ„
      test:
        name: test (Pytest)
        needs: [static-analysis]
        runs-on: ubuntu-22.04
        permissions:
          checks: write
          pull-requests: write
        steps:
          - uses: actions/checkout@v4
          - name: ํŒŒ์ด์ฌ ์„ค์ •
            uses: actions/setup-python@v4
            with:
              python-version: ${{ env.PYTHON_VERSION }}
              cache: 'pip'
          - run: |
              pip install -r requirements.txt
              pip install pytest pytest-xml-report
          - name: ํ…Œ์ŠคํŠธ ์‹คํ–‰
            run: pytest --junitxml=test-results.xml
          - name: Test ๊ฒฐ๊ณผ ์—…๋กœ๋“œ
            uses: EnricoMi/publish-unit-test-result-action@v2
            if: always()
            with:
              files: 'test-results.xml'
    
      # 4. ๋นŒ๋“œ ๋ฐ S3 ์—…๋กœ๋“œ
      build_and_deploy:
        name: build and S3 upload
        needs: [test]
        runs-on: ubuntu-22.04
        steps:
          - uses: actions/checkout@v4
          - name: ํŒŒ์ด์ฌ ์„ค์ •
            uses: actions/setup-python@v4
            with:
              python-version: ${{ env.PYTHON_VERSION }}
              cache: 'pip'
          
          # ํƒ€์ž„์Šคํƒฌํ”„ ์ƒ์„ฑ
          - name: ํƒ€์ž„์Šคํƒฌํ”„ ์ƒ์„ฑ
            run: echo "TIMESTAMP=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV
    
          - name: AWS ์ž๊ฒฉ ์ฆ๋ช… ์„ค์ •
            uses: aws-actions/configure-aws-credentials@v4
            with:
              aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
              aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }}
              aws-region: ${{ env.AWS_REGION }}
    
          - name: S3๋กœ ์†Œ์Šค ํŒจํ‚ค์ง€ ์—…๋กœ๋“œ
            run: |
              # 1. ์—…๋กœ๋“œํ•  ๊ฒฝ๋กœ ์„ค์ • (prefix)
              S3_PATH="s3://${{ env.S3_BUCKET }}/ci/${{ github.repository }}/${{ github.ref_name }}/${{ env.TIMESTAMP }}_${{ github.sha }}"
              
              # 2. ๋ถˆํ•„์š”ํ•œ ํŒŒ์ผ(.git, __pycache__ ๋“ฑ) ์ œ์™ธํ•˜๊ณ  ๋™๊ธฐํ™”
              aws s3 sync . $S3_PATH \
                --exclude ".git/*" \
                --exclude ".github/*" \
                --exclude "__pycache__/*" \
                --exclude "*.pyc" \
                --exclude ".pytest_cache/*"
                
              echo "DEPLOY_PATH=$S3_PATH" >> $GITHUB_ENV
    
      # 5. ๋””์Šค์ฝ”๋“œ ์•Œ๋ฆผ
      notify-discord:
        name: notify
        runs-on: ubuntu-22.04
        needs: [build_and_deploy]
        if: always()
        steps:
          - name: Discord ๋ฐฐํฌ ์•Œ๋ฆผ
            uses: sarisia/actions-status-discord@v1
            with:
              webhook: ${{ github.ref_name == 'develop' && secrets.DISCORD_WEBHOOK_URL_AI || secrets.DISCORD_WEBHOOK_URL }}
              title: "${{ needs.build_and_deploy.result == 'success' && 'โœ…' || 'โŒ' }} AI ๋ฐฐํฌ ํŒŒ์ดํ”„๋ผ์ธ ๊ฒฐ๊ณผ"
              description: |
                **์ด๋ฒคํŠธ**: PUSH (`${{ github.ref_name }}`)
                **์ž‘์„ฑ์ž**: ${{ github.actor }}
                **์ƒํƒœ**: ${{ needs.build_and_deploy.result == 'success' && '์„ฑ๊ณต' || '์‹คํŒจ' }}
                
                **๐Ÿ“Š ๋ฐฐํฌ ์ •๋ณด**:
                - S3 ์ €์žฅ ๊ฒฝ๋กœ: `ci/${{ github.repository }}/${{ github.ref_name }}/...`
                
                **๐Ÿ”— ๋งํฌ**:
                - [GitHub Actions ์‹คํ–‰ ํ™•์ธ](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
              color: "${{ needs.build_and_deploy.result == 'success' && 65280 || 16711680 }}"
              username: GitHub AI Bot


5. CI ๊ฒฐ๊ณผ ์•Œ๋ฆผ ๋ฐ ํ”ผ๋“œ๋ฐฑ ์‹œ์Šคํ…œ


5-1. ์•Œ๋ฆผ ์‹œ์Šคํ…œ ์ข…๋ฅ˜ ๋น„๊ต

  • ์šฐ๋ฆฌ ์„œ๋น„์Šค์˜ ์•Œ๋ฆผ ์‹œ์Šคํ…œ ์„ ํƒ ๊ธฐ์ค€์€ ์ ‘๊ทผ์„ฑ, ํ™•์žฅ์„ฑ, ๋น„์šฉ์ž…๋‹ˆ๋‹ค.
  • ์•Œ๋ฆผ ์ž์ฒด๊ฐ€ ๊ฐœ๋ฐœ์ž๋“ค์—๊ฒŒ ๋ช…ํ™•ํ•˜๊ณ  ์‰ฝ๊ฒŒ ์ „๋‹ฌ๋  ์ˆ˜ ์žˆ์–ด์•ผํ•ฉ๋‹ˆ๋‹ค.
  • CI์˜ ์•Œ๋ฆผ ๋ฟ ์•„๋‹ˆ๋ผ, ๋ชจ๋‹ˆํ„ฐ๋ง์—์„œ๊นŒ์ง€ ์•Œ๋ฆผ์ด ์‚ฌ์šฉ๋˜์–ด์•ผ ํ•˜๊ธฐ์— ํ™•์žฅ์„ฑ์ด ์ข‹์€ ๊ฒƒ์„ ์„ ํƒํ•˜๊ธฐ๋กœ ํŒ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.

์•Œ๋ฆผ ์„œ๋น„์Šค ๋น„๊ต

image

์š”์ฆ˜IT(์›”๊ฐ„ PV 100๋งŒ, MAU 46๋งŒ, ๋‰ด์Šค๋ ˆํ„ฐ 8๋งŒ5์ฒœ.) ๋งค๊ฑฐ์ง„ ๋‚ด ๋ฐœ์ทŒ (2025.09.18 ๊ธฐ์ค€) ์˜ˆ์™ธ์ ์œผ๋กœ ํ˜„์žฌ ์‚ฌ์šฉ์ค‘์ธ ๋””์Šค์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ, ํ˜‘์—…์šฉ ๋ฉ”์‹ ์ €ํˆด ๋น„๊ต

์ข…๋ฅ˜ Discord (์„ ํƒ) Slack Teams
์ ‘๊ทผ์„ฑ - ๊ธฐ์กด์— ํŒ€์ด ์‚ฌ์šฉ ์ค‘์ธ ํ”Œ๋žซํผ์ด๊ธฐ์— ์ฆ‰์‹œ ๋„์ž… ๊ฐ€๋Šฅ - ํŒ€ ์ „์›์ด ์‹ ๊ทœ ํˆด ์ ์‘ ํ•„์š” - ํŒ€ ์ „์›์ด ์‹ ๊ทœ ํˆด ์ ์‘ ํ•„์š”
GitHub ํ™•์žฅ์„ฑ - Webhook์„ ํ†ตํ•œ ์ง์ ‘ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ๊ฐ€๋Šฅ - Webhook์„ ํ†ตํ•œ ์ง์ ‘ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ๊ฐ€๋Šฅ - Webhook์„ ํ†ตํ•œ ์ง์ ‘ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ๊ฐ€๋Šฅ
AWS ํ™•์žฅ์„ฑ - ์ง์ ‘ ๊ตฌ์„ฑ ํ•„์š” (SNS/EventBridge โ†’ Lambda โ†’ Discord Webhook) - Slack ๊ธฐ๋ฐ˜ AWS Support ์•ฑ ์กด์žฌ
โ†’ ์†์‰ฝ๊ฒŒ ์„ค์ • ๊ฐ€๋Šฅ
- Webhook์„ ํ†ตํ•œ ์ง์ ‘ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ๊ฐ€๋Šฅ
PLG ์Šคํƒ ํ™•์žฅ์„ฑ - Webhook์„ ํ†ตํ•œ ์ง์ ‘ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ๊ฐ€๋Šฅ - Slack API ๋˜๋Š” Webhook์„ ํ†ตํ•œ ์—ฐ๊ฒฐ ๊ฐ€๋Šฅ - Webhook์„ ํ†ตํ•œ ์ง์ ‘ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ๊ฐ€๋Šฅ
ELK ์Šคํƒ ํ™•์žฅ์„ฑ - Webhook์„ ํ†ตํ•œ ์ง์ ‘ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ๊ฐ€๋Šฅ - Webhook์„ ํ†ตํ•œ ์ง์ ‘ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ๊ฐ€๋Šฅ - Webhook์„ ํ†ตํ•œ ์ง์ ‘ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ๊ฐ€๋Šฅ
๋น„์šฉ - ๋ฌด๋ฃŒ๋กœ ์‹œ์ž‘ ๊ฐ€๋Šฅ
- AWS Lambda๋ฅผ ์“ฐ๊ฒŒ ๋œ๋‹ค๋ฉด, 100๋งŒ ๊ฑด๋‹น $0.20 ๋ฐœ์ƒ
- ๊ธฐ๋ณธ์€ ๋ฌด๋ฃŒ๋กœ ์‹œ์ž‘ ๊ฐ€๋Šฅ
- ๋ฌด๋ฃŒ ๋ฒ„์ „์€ 90์ผ ์ดํ›„ ํžˆ์Šคํ† ๋ฆฌ ์ œํ•œ
- PRO ๋ฒ„์ „ ์œ ๋ฃŒ, ์ธ๋‹น $4.4 ๋ฐœ์ƒ
- (์กฐ์ง ๋ผ์ด์„ ์Šค ์ •์ฑ…์— ๋”ฐ๋ผ ์ƒ์ด)

โ‡’ Slack์€ ์ž์ฒด API๊ฐ€ ์กด์žฌํ•˜์—ฌ ์•Œ๋ฆผ ์—ฐ๋™์ด ๋” ์‰ฝ๋‹ค๋Š” ์žฅ์ ์ด ์กด์žฌํ•˜์ง€๋งŒ, ๋‹ค๋ฅธ ์„œ๋น„์Šค ๋˜ํ•œ webhook์„ ํ†ตํ•ด์„œ ์•Œ๋ฆผ ์ „๋‹ฌ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ์—, ์šฐ๋ฆฌ ์„œ๋น„์Šค์—์„œ๋Š” ์ ์‘์„ฑ/๋น„์šฉ์— ๋” ๊ฐ€์‚ฐ์ ์„ ๋‘์–ด์„œ, ๊ธฐ์กด์— ์“ฐ๋Š” Discord๋ฅผ ์•Œ๋ฆผ ๋ฉ”์‹ ์ €๋กœ ์„ ์ • ํ•˜์˜€์Šต๋‹ˆ๋‹ค.


5-2. Discord Webhook ์•Œ๋ฆผ

ํ•ญ๋ชฉ ์„ค๋ช…
ํˆด Discord Webhook
์•Œ๋ฆผ ์‹œ์  ์›Œํฌํ”Œ๋กœ์šฐ ์ข…๋ฃŒ ์‹œ (always())
ํฌํ•จ ๋‚ด์šฉ ๋นŒ๋“œ ์ƒํƒœ, ์‹คํ–‰ํ•œ ๋ธŒ๋žœ์น˜, ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€, ์›Œํฌํ”Œ๋กœ์šฐ ๋งํฌ
์‹คํŒจ ์‹œ ์กฐ์น˜ -

5-2-1. ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ ํฌ๋งท ๋ฐ ์˜ˆ์‹œ

Success: โœ… [๋ธŒ๋žœ์น˜๋ช…] ๋ธŒ๋žœ์น˜ ๋ฐฐํฌ ๊ฒฐ๊ณผ
์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€: [์ปค๋ฐ‹ ์ œ๋ชฉ]
์ž‘์„ฑ์ž: [์ž‘์„ฑ์ž]
๋ธŒ๋žœ์น˜: [๋ธŒ๋žœ์น˜๋ช…]

๐Ÿ“Š ๋นŒ๋“œ/๋ฐฐํฌ ๊ฒฐ๊ณผ:
์ƒํƒœ: [์„ฑ๊ณต/์‹คํŒจ]

Event - [push/pr]

Triggered by [๊ฐœ๋ฐœ์ž๋ช…]

2025. 12. 18. ์˜คํ›„ 7:47

5-2-2. CI ํŒŒ์ดํ”„๋ผ์ธ ์•Œ๋ฆผ ๊ตฌ์„ฑ

  • ๋นŒ๋“œ/ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ Discord ์•Œ๋ฆผ์„ ์ž๋™ ์ƒ์„ฑ
  • ๊ฐœ๋ฐœ์ž๊ฐ€ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ–ˆ๋Š”์ง€์™€ ๊ด€๋ จ ์ปค๋ฐ‹์œผ๋กœ ๋ฐ”๋กœ ์ด๋™ํ•˜์—ฌ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑ
  • ๋„ˆ๋ฌด ๋งŽ์€ ์•Œ๋žŒ์ด ์กด์žฌํ•œ๋‹ค๋ฉด, ์•Œ๋žŒ ์ „๋‹ฌ์— ๋Œ€ํ•œ ๋ฐฉํ•ด
    • ๋ธŒ๋žœ์น˜ ๋ฐ ์ƒํ™ฉ์— ๋”ฐ๋ผ์„œ ์ „๋‹ฌ๋˜๋Š” ์ฑ„ํŒ…๋ฐฉ ์ง€์ •

      ์ผ€์ด์Šค ์†Œ์Šค ๋ธŒ๋žœ์น˜ ํƒ€๊นƒ ๋ธŒ๋žœ์น˜ ์ƒํ™ฉ ์•Œ๋ฆผ ์ฑ„ํŒ…๋ฐฉ
      1 Feat/* Dev PR ๊ฒฐ๊ณผ ํ•ด๋‹น ์ง๊ตฐ ์ฑ„ํŒ…๋ฐฉ(FE/BE/AI)
      2 Feat/* Dev Merge ๊ฒฐ๊ณผ ์ „์ฒด ์ฑ„ํŒ…๋ฐฉ
      3 Dev Release PR/Merge ์ „์ฒด ์ฑ„ํŒ…๋ฐฉ
      4 Release Main PR/Merge ๊ฒฐ๊ณผ ์ „์ฒด ์ฑ„ํŒ…๋ฐฉ
      5 Hotfix Main PR/Merge ๊ฒฐ๊ณผ ์ „์ฒด ์ฑ„ํŒ…๋ฐฉ
# ๋นŒ๋“œ/ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰
result = runBuildPipeline()  # lint โ†’ test โ†’ build

# ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ๋ฉ”์‹œ์ง€ ๊ตฌ์„ฑ
if result == SUCCESS:
    status = "โœ… ์„ฑ๊ณต"
    color = GREEN
else:
    status = "โŒ ์‹คํŒจ"
    color = RED

# Discord ์•Œ๋ฆผ ์ „์†ก
sendDiscordWebhook(
    title = f"{status} - ๋ธŒ๋žœ์น˜ ๋ฐฐํฌ ๊ฒฐ๊ณผ",
    description = f"์ปค๋ฐ‹: <์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€>\n์ž‘์„ฑ์ž: <์ž‘์„ฑ์ž>\n๋ธŒ๋žœ์น˜: <๋ธŒ๋žœ์น˜ ์ด๋ฆ„>\n์ƒํƒœ: {status}",
    color = color
)


6. CI ๋„์ž… ์„ฑ๊ณผ ์š”์•ฝ

6-1. ์†Œ์š” ์‹œ๊ฐ„ ๋ฐ CI ๋„์ž… ์ „ํ›„์˜ ๋ณ€ํ™”

ํ•ญ๋ชฉ CI ๋„์ž… ์ „ CI ๋„์ž… ํ›„ ๋น„๊ณ 
ํ…Œ์ŠคํŠธ ์‹œ๊ฐ„ ์ˆ˜๋™, ํ‰๊ท  10๋ถ„ ์ž๋™ํ™”, ํ‰๊ท  #๋ถ„ ์‹œ๊ฐ„ ์ ˆ๊ฐ ๊ฐ€๋Šฅ
ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ๋น„์ •๊ธฐ์  ์ˆ˜๋™ ์ธก์ • JaCoCo๋กœ ์ž๋™ ์ธก์ • ์ปค๋ฒ„๋ฆฌ์ง€ ๋ฆฌํฌํŠธ๋ฅผ ์‹œ๊ฐํ™”ํ•˜์—ฌ ๊ณต์œ  ๊ฐ€๋Šฅ
ํ”ผ๋“œ๋ฐฑ ์‹œ์  QA ๋‹จ๊ณ„ ์ปค๋ฐ‹ ์งํ›„ ๊ฐœ๋ฐœ ๋‹จ๊ณ„์—์„œ ๋ฒ„๊ทธ ์กฐ๊ธฐ ๋ฐœ๊ฒฌ ๊ฐ€๋Šฅ
์ฝ”๋“œ ํ’ˆ์งˆ ์ ๊ฒ€ ์—†์Œ Lint ๋„๊ตฌ๋กœ ์ž๋™ ๊ฒ€์‚ฌ ์ผ๊ด€๋œ ์ฝ”๋“œ ์Šคํƒ€์ผ ์œ ์ง€ ๊ฐ€๋Šฅ
์ฝ”๋“œ ๊ฒ€์‚ฌ ์ž์ฒด ์ฝ”๋“œ ๋ฆฌ๋ทฐ๋ฅผ ํ†ตํ•œ ์ž์ฒด์ ์ธ ๋ถ„์„ ์ •์  ๋ถ„์„ ๋„๊ตฌ๋กœ ์ž๋™ ๊ฒ€์‚ฌ ์‹ค์ˆ˜๋กœ ์ธํ•ด ํ™•์ธํ•˜์ง€ ๋ชปํ•  ๋‚ด์šฉ์„ ๋ฐœ๊ฒฌ ๊ฐ€๋Šฅ
์•Œ๋ฆผ ๋ฐ ๋Œ€์‘ ์†๋„ ๋นŒ๋“œ ์‹คํŒจ ์‹œ ์ˆ˜๋™ ํ™•์ธ Discord๋กœ ์ฆ‰์‹œ ์•Œ๋ฆผ ์‹คํŒจ ์ฆ‰์‹œ ํŒ€์›์—๊ฒŒ ์ž๋™ ํ†ต๋ณด ๊ฐ€๋Šฅ
โš ๏ธ **GitHub.com Fallback** โš ๏ธ