12 minute read

๐Ÿš› ๋ฉ€ํ‹ฐ ๋ชจ๋“ˆ ๊ตฌ์กฐ๋กœ ์ด์‚ฌ๊ฐ€๊ธฐ!

1. ๊ธฐ์กด ๋ชจ๋†€๋ฆฌํ‹ฑ(Monolithic) ๊ตฌ์กฐ์—์„œ ๋Š๋‚€ ๋ฌธ์ œ์ 

์ž‘๋…„ 10์›”๋ถ€ํ„ฐ ์ง„ํ–‰ํ•œ ์‚ฌ์ด๋“œ ํ”„๋กœ์ ํŠธ์ธ ๋Œ€ํ”ผ๋กœ(Daepiro) ๋ฐฑ์—”๋“œ๋Š” ๋ชจ๋†€๋ฆฌํ‹ฑ ๊ตฌ์กฐ๋กœ ์ถœ๋ฐœํ–ˆ์Šต๋‹ˆ๋‹ค. 2๊ฐœ์›” ๋‚ด์— ๋ฐ๋ชจ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋น ๋ฅด๊ฒŒ ๊ฐœ๋ฐœํ•ด์•ผ ํ•˜๋Š” ์ด‰๋ฐ•ํ–ˆ๋˜ ์ผ์ •๊ณผ, ๋ฆฌ์†Œ์Šค๊ฐ€ ์ ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ„๋‹จํ•œ ๋ชจ๋†€๋ฆฌํ‹ฑ ๊ตฌ์กฐ๋ฅผ ์„ ํƒํ–ˆ์—ˆ์ฃ . ํ•˜์ง€๋งŒ ๊ฐœ๋ฐœ์ด ์ ์ฐจ ์ง„ํ–‰๋ ์ˆ˜๋ก ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฌธ์ œ์ ์ด ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

  1. ๊ธฐ๋Šฅ ๋ณ€๊ฒฝ/์ถ”๊ฐ€ ์‹œ ์˜ํ–ฅ๋„๋ฅผ ์˜ˆ์ธกํ•  ์ˆ˜ ์—†๋‹ค.
  2. ๊ด€๋ฆฌ ํฌ์ธํŠธ๊ฐ€ ๋Š˜์–ด๋‚œ๋‹ค.
    • ๋‹จ์ผ ๋ชจ๋“ˆ์—์„œ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ๊ด€๋ฆฌํ•˜๋ฏ€๋กœ ๋ณต์žก๋„๊ฐ€ ๋†’์•„์ง€๊ณ , ํ™•์žฅ์„ฑ์ด ํฌ๊ฒŒ ๋–จ์–ด์ง€๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
  3. ์ฝ”๋“œ ์ค‘๋ณต์ด ์ž์ฃผ ๋ฐœ์ƒํ•œ๋‹ค.

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

2. ๋ฉ€ํ‹ฐ๋ชจ๋“ˆ(Multi-module) ๊ตฌ์กฐ๋กœ์˜ ์ „ํ™˜๊ณผ ๊ธฐ๋Œ€ํšจ๊ณผ

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

  1. ์ฝ”๋“œ ์ค‘๋ณต ๊ฐ์†Œ ๋ฐ ๋„๋ฉ”์ธ ๋ถ„๋ฆฌ
    • ๋ชจ๋“ˆ ๋‹จ์œ„๋กœ ์‹œ์Šคํ…œ์„ ๋ถ„๋ฆฌ ํ•จ์— ๋”ฐ๋ผ ์ฝ”๋“œ ์ค‘๋ณต์„ ์ตœ์†Œํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ๋ชจ๋“ˆ์€ ์ฃผ์–ด์ง„ ์ฑ…์ž„์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๊ณ , ์—ฌ๋Ÿฌ ๋ชจ๋“ˆ์—์„œ ๊ณตํ†ต์ ์œผ๋กœ ํ•„์š”ํ•œ ์ฝ”๋“œ๋Š” ๋ณ„๋„์˜ ๋ชจ๋“ˆ์—์„œ ๊ด€๋ฆฌํ•˜๋Š” ๋“ฑ์˜ ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ์„ ํฌ๊ฒŒ ๋†’์ด๊ณ  ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
  2. ๊ด€๋ฆฌ ํฌ์ธํŠธ์˜ ๊ฐ์†Œ
    • ๊ฐ ๋ชจ๋“ˆ์˜ ์ฑ…์ž„๊ณผ ์˜์กด์„ฑ์ด ๋ช…ํ™•ํžˆ ์ •์˜๋˜์–ด์žˆ๋‹ค๋ฉด, ๊ฐœ๋ฐœ์ž๋Š” ์ž‘์—…์˜ ์˜ํ–ฅ ๋ฒ”์œ„๋ฅผ ์‰ฝ๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์–ด ๊ด€๋ฆฌ ํฌ์ธํŠธ๊ฐ€ ์ค„์–ด๋“ญ๋‹ˆ๋‹ค. ํŠน์ • ๊ธฐ๋Šฅ์„ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ๋ณ€๊ฒฝํ•  ๋•Œ, ์–ด๋Š ๋ชจ๋“ˆ์— ์˜ํ–ฅ์ด ์žˆ์„ ์ง€ ์‰ฝ๊ฒŒ ํŒŒ์•…ํ•˜๋ฉฐ ๊ณผ๊ฐํ•œ ์ž‘์—…์„ ์‹œ๋„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. ํ™•์žฅ์„ฑ, ์œ ์—ฐ์„ฑ ๊ฐœ์„ 
    • ํŠน์ • ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ํ™•์žฅ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—, ํ•ด๋‹น ๋ชจ๋“ˆ์— ๋Œ€ํ•ด์„œ๋งŒ ์ž‘์—…ํ•˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์‹œ์Šคํ…œ์˜ ์•ˆ์ •์„ฑ์„ ๊ฑฑ์ •ํ•˜์ง€ ์•Š๊ณ  ์ž‘์—…์ด ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.
  4. MSA ๋กœ์˜ ์ „ํ™˜ ๊ฐ€๋Šฅ์„ฑ
    • ๋ฉ€ํ‹ฐ๋ชจ๋“ˆ ๊ตฌ์กฐ๋Š” MSA ๋กœ์˜ ์ „ํ™˜์„ ์œ„ํ•œ ์ค‘๊ฐ„ ๊ณผ์ •์˜ ์—ญํ• ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠธ๋ž˜ํ”ฝ์— ๋”ฐ๋ผ ๊ฐ ๋ชจ๋“ˆ์„ ํ•„์š” ์‹œ ๋…๋ฆฝ์ ์ธ ์„œ๋น„์Šค๋กœ ํ™•์žฅํ•˜๊ธฐ์— ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋ชจ๋†€๋ฆฌํ‹ฑ ๊ตฌ์กฐ์—์„œ ํ•œ๋ฒˆ์— MSA ๋กœ์˜ ์ „ํ™˜์ด ํ•„์š”ํ•˜๋‹ค๋ฉด, ๊ต‰์žฅํžˆ ํฐ ๊ณต์ˆ˜๊ฐ€ ๋“ค ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ•ฉ๋‹ˆ๋‹ค๋งŒ, ๋ชจ๋“ˆ์ด ์ž˜ ๋ถ„๋ฆฌ๋œ ๋ฉ€ํ‹ฐ ๋ชจ๋“ˆ ๊ตฌ์กฐ์˜ ํ”„๋กœ์ ํŠธ๋ผ๋ฉด ํ›จ์”ฌ ์ ์€ ๋ฆฌ์Šคํฌ๋กœ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

์ด ์™ธ์—๋„ ๊ฐ ํŒ€์›์ด ์ž‘์—…ํ•  ๋•Œ, ์š”๊ตฌ์‚ฌํ•ญ์„ ๋ณด๊ณ  ์–ด๋–ค ๋ชจ๋“ˆ์—์„œ ์ž‘์—…ํ•ด์•ผ ํ•  ์ง€ ์ž‘์—… ๊ณ„ํš/์ „๋žต์„ ์ˆ˜๋ฆฝํ•˜๊ธฐ ์‰ฌ์›Œ์ง€๋ฉฐ, ์ƒˆ๋กœ์šด ํŒ€์›์ด ํ•ฉ๋ฅ˜ ํ–ˆ์„ ๋•Œ ํ”„๋กœ์ ํŠธ์— ๋” ์‰ฝ๊ฒŒ ์ ์‘ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์œผ๋กœ ์ƒ๊ฐ๋ฉ๋‹ˆ๋‹ค! ์•„๋ฌด์ชผ๋ก, ๋ฉ€ํ‹ฐ ๋ชจ๋“ˆ๋กœ์˜ ์ „ํ™˜์€ ๋Œ€ํ”ผ๋กœ ๋ฐฑ์—”๋“œ์— ์žˆ์–ด์„œ ์•„์ฃผ ๊ธ์ •์ ์ธ ํšจ๊ณผ๊ฐ€ ์žˆ์„ ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค ใ…Žใ…Ž

3. ๋ชจ๋“ˆ ์„ค๊ณ„๋ฅผ ์œ„ํ•œ ๊ณ ๋ฏผ

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

์šฐ๋ฆฌ๊ฐ€ ์„ค๊ณ„ํ•œ ๊ตฌ์กฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

image

  • daepiro-core
    • ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฐ์ดํ„ฐ ์Šคํ† ๋ฆฌ์ง€์™€ ๊ด€๋ จ๋œ ๋ชจ๋“  ์ฑ…์ž„์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ •, ์—”ํ‹ฐํ‹ฐ ๊ด€๋ฆฌ, ์ฟผ๋ฆฌ ๋‹จ ์ฝ”๋“œ๋“ค์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.
  • daepiro-redis
    • ๋ ˆ๋””์Šค ์„ค์ •๊ณผ ๊ตฌํ˜„ ๋ถ€๋ถ„์„ ๋…๋ฆฝ์ ์œผ๋กœ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
  • daepiro-api
    • ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง์— ๋Œ€ํ•œ ์ฑ…์ž„์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ์ ์ ˆํ•œ ์„œ๋น„์Šค ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜์—ฌ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • daepiro-auth
    • ์ธ์ฆ, ์ธ๊ฐ€ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • daepiro-common
    • ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „๋ฐ˜์—์„œ ๊ณตํ†ต์ ์œผ๋กœ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ๊ธฐ๋Šฅ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ๋‹จ, common module ์ด ๋„ˆ๋ฌด ๋ฌด๊ฑฐ์›Œ์ง€์ง€ ์•Š๋„๋ก ๊ฒฝ๊ณ„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. common module ์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์€, ์ผ๋ฐ˜์ ์œผ๋กœ ์ „์ฒด module ์— ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  • daepiro-crawler
    • ๋Œ€ํ”ผ๋กœ ํ”„๋กœ์ ํŠธ๋Š” ์ผ์ • ์ฃผ๊ธฐ๋งˆ๋‹ค ์žฌ๋‚œ ์•Œ๋ฆผ ์‚ฌ์ดํŠธ๋ฅผ ํฌ๋กค๋งํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•ฉ๋‹ˆ๋‹ค. ํฌ๋กค๋Ÿฌ ๋ชจ๋“ˆ์—์„œ๋Š” ํฌ๋กค๋ง์„ ํ†ตํ•œ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ๋กœ์ง๋งŒ์„ ๋‹ด๋‹นํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • daepiro-app
    • ์—”ํŠธ๋ฆฌ ๋ชจ๋“ˆ์œผ๋กœ, ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ง„์ž…์ ์„ ์ œ๊ณตํ•˜๋Š” ์—ญํ• ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

๊ฐ ๋ชจ๋“ˆ์˜ ๊ฒฝ๊ณ„๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ถ„๋ฆฌํ•˜๊ณ , ์˜์กด์„ฑ์„ ์ตœ์†Œํ™” ํ•˜๋ฉด์„œ ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ณ ๋ฏผํ–ˆ๋Š”๋ฐ์š”! ๊ฐœ๋ฐœ์„ ๊ณ„์† ์ง„ํ–‰ํ•˜๋ฉด์„œ, ํ˜„์žฌ ๊ตฌ์กฐ๊ฐ€ ์ •๋ง ๊ดœ์ฐฎ์€ ์ง€ ํŒ€์›๋“ค๊ณผ ์ง€์†์ ์œผ๋กœ ๊ฒ€ํ† ํ•˜๋ฉฐ ๋‹ค์–‘ํ•œ ์‹œ๋„๋ฅผ ํ•ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ตœ์ ์˜ ๊ตฌ์กฐ๋Š” ์–ด๋–ค ๋ชจ์Šต์ผ๊นŒ์š”?

4. ๋ฉ€ํ‹ฐ๋ชจ๋“ˆ๋กœ์˜ ์ „ํ™˜

Grade ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ€ํ‹ฐ๋ชจ๋“ˆ ๊ตฌ์กฐ๋ฅผ ์‰ฝ๊ณ  ํšจ๊ณผ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

image

settings.gradle ์—์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๋ชจ๋“ˆ ์„ค์ •์„ ํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

rootProject.name = 'Backend'
include 'daepiro-app'
include 'daepiro-api'
include 'daepiro-core'
include 'daepiro-common'
include 'daepiro-redis'
include 'daepiro-auth'
include 'daepiro-crawler'

๋ฃจํŠธ ํ”„๋กœ์ ํŠธ์˜ build.gradle ์„ค์ •์ž…๋‹ˆ๋‹ค.

  • ์—ฌ๊ธฐ์„œ๋Š” ์ „์ฒด ํ”„๋กœ์ ํŠธ์— ๋Œ€ํ•œ ๊ณตํ†ต ์„ค์ •์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
plugins {
    // ํ•„์š”ํ•œ plugin ์„ค์ •์„ root ์—์„œ ํ•ด๋‘ก๋‹ˆ๋‹ค.
    id 'java'
    // spring boot ๋ฒ„์ „์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
    id 'org.springframework.boot' version '3.1.4'
    id 'io.spring.dependency-management' version '1.1.3'
    // ๋Œ€ํ”ผ๋กœ ๋ฐฑ์—”๋“œ์—์„œ๋Š” jib ๋ฅผ ์ด์šฉํ•˜์—ฌ ๋„์ปค ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.
    id 'com.google.cloud.tools.jib' version '3.4.0'
}

group = 'com.numberone.backend'
version = '0.0.2-SNAPSHOT'

allprojects {
    apply plugin: 'java'
    // ์ž๋ฐ” ๋ฒ„์ „์„ ๋ณ€๊ฒฝํ•˜๊ณ ์ž ํ•œ๋‹ค๋ฉด, ์•„๋ž˜ ์„ค์ •์„ ๋ณ€๊ฒฝํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
    sourceCompatibility = JavaVersion.VERSION_17

    repositories {
        mavenCentral()
        maven { url 'https://jitpack.io' }
    }
}

ext {
    set('springCloudVersion', "2021.0.1")
}

subprojects {
    apply plugin: 'io.spring.dependency-management'
    apply plugin: 'java-library'
    apply plugin: 'org.springframework.boot'
    apply plugin: 'jacoco'

    configurations {
        compileOnly {
            extendsFrom annotationProcessor
        }
    }

    repositories {
        mavenCentral()
    }

    dependencyManagement {
        imports {
            mavenBom("org.springframework.boot:spring-boot-dependencies:3.1.4")
            mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
        }
    }

    dependencies {
        // lombok
        compileOnly 'org.projectlombok:lombok'
        annotationProcessor 'org.projectlombok:lombok'
        annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'

        // spring
        implementation 'org.springframework.boot:spring-boot-starter-web'
        implementation 'org.springframework.boot:spring-boot-starter-validation'
        implementation 'org.springframework.boot:spring-boot-starter-security'
        implementation 'org.springframework.boot:spring-boot-starter-aop'

        // swagger
        implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'

    }

}
  • core module
dependencies {
 implementation project(':daepiro-common')

 // p6spy
 implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.5.8'

 // jpa
 api 'org.springframework.boot:spring-boot-starter-data-jpa'

 // mysql connector
 runtimeOnly 'com.mysql:mysql-connector-j'

 // === QueryDsl  ===
 implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
 annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
 annotationProcessor "jakarta.annotation:jakarta.annotation-api"
 annotationProcessor "jakarta.persistence:jakarta.persistence-api"
 // === QueryDsl  ===
}

// === Querydsl ๋นŒ๋“œ ์˜ต์…˜  ===
def querydslDir = "$buildDir/generated/querydsl"

sourceSets {
 main.java.srcDirs += [ querydslDir ]
}

tasks.withType(JavaCompile) {
 options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir)
}

clean.doLast {
 file(querydslDir).deleteDir()
}
// === Querydsl ๋นŒ๋“œ ์˜ต์…˜  ===

bootJar {
 enabled = false
}
jar {
 enabled = true
}

  • redis module
dependencies {
    implementation project(':daepiro-common')

    // redis
    api 'org.springframework.boot:spring-boot-starter-data-redis'
}

bootJar {
    enabled = false
}
jar {
    enabled = true
}

  • common module
dependencies {
    // fcm
    implementation 'com.google.firebase:firebase-admin:9.1.1'

    //geocoding
    implementation 'com.google.code.geocoder-java:geocoder-java:0.16'
}

bootJar {
    enabled = false
}
jar {
    enabled = true
}

  • auth module
dependencies {
    implementation project(':daepiro-core')
    implementation project(':daepiro-redis')
    implementation project(':daepiro-common')

    // oauth
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'

    // jwt
    implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

}

bootJar {
    enabled = false
}
jar {
    enabled = true
}

  • api module
dependencies {
 implementation project(':daepiro-core')
 implementation project(':daepiro-common')
 implementation project(':daepiro-redis')
}

bootJar {
 enabled = false
}

jar {
 enabled = true
}

  • crawler module
dependencies {
    implementation project(':daepiro-common')
    implementation project(':daepiro-core')

    //crawling
    implementation 'com.squareup.okhttp3:okhttp:4.9.1'
    implementation 'org.jsoup:jsoup:1.14.2'
    implementation 'net.sourceforge.htmlunit:htmlunit:2.70.0'
}

bootJar {
    enabled = false
}
jar {
    enabled = true
}

๋ฉ€ํ‹ฐ๋ชจ๋“ˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๊ฐ ๋ชจ๋“ˆ์˜ ์ฑ…์ž„์— ๋”ฐ๋ผ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์Šคํ”„๋ง๋ถ€ํŠธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ํŒจํ‚ค์ง• ๋  ํ•„์š”๊ฐ€ ์žˆ๋Š”์ง€ ๊ณ ๋ คํ•ด์•ผ ํ•˜๋Š”๋ฐ์š”, gradle ์—์„œ๋Š” bootJar, jar task ์— ๋Œ€ํ•œ enabled flag ๋ฅผ ์„ค์ •ํ•˜์—ฌ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์Šคํ”„๋ง ๋ถ€ํŠธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์‹คํ–‰๋  ํ•„์š”๊ฐ€ ์—†๋Š” ๋ชจ๋“ˆ์˜ ๊ฒฝ์šฐ์—๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด bootJar task ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ณ  jar ๋กœ์˜ ํŒจํ‚ค์ง•๋งŒ ์ˆ˜ํ–‰๋˜์–ด ๋‹ค๋ฅธ ๋ชจ๋“ˆ์—์„œ ํ•„์š” ์‹œ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

bootJar {
    enabled = false
}
jar {
    enabled = true
}

์—”ํŠธ๋ฆฌ ๋ชจ๋“ˆ์—์„œ๋Š” bootJar ๋ฅผ true ๋กœ ์„ค์ •ํ•˜์—ฌ, ์‹คํ–‰๊ฐ€๋Šฅํ•œ ์Šคํ”„๋ง ๋ถ€ํŠธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ๊ฒ ์ฃ . ์ถ”ํ›„์— batch ๋ชจ๋“ˆ์„ ์ถ”๊ฐ€ํ•  ๊ณ„ํš์ด ์žˆ๋Š”๋ฐ์š”, batch application ์„ ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด batch ๋ชจ๋“ˆ์˜ gradle ์„ค์ • ๋ถ€๋ถ„์—์„œ๋Š” bootJar ๋ถ€๋ถ„์„ enable ํ•ด์ฃผ์–ด์•ผ ํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • app module
plugins {
    id 'com.google.cloud.tools.jib' version '3.4.0'
}

dependencies {
    implementation project(':daepiro-core')
    implementation project(':daepiro-common')
    implementation project(':daepiro-redis')
    implementation project(':daepiro-api')
    implementation project(':daepiro-crawler')
    implementation project(':daepiro-auth')
}

// system environment variables
def serverIP = System.getenv("EC2_PUBLIC_IP")
def jmxPort = System.getenv("JMX_PORT")
def dockerUserName = System.getenv("DOCKER_USERNAME")
def dockerImageName = System.getenv("DOCKER_IMAGE")

// jib
jib {
    from {
        image = "openjdk:17"
    }
    to {
        image = String.format("%s/%s", dockerUserName, dockerImageName)
        tags = ["latest"]
    }
    container {
        mainClass = 'com.numberone.backend.BackendApplication'
        creationTime = "USE_CURRENT_TIMESTAMP"
        jvmFlags = [
                "-Duser.timezone=Asia/Seoul",
                "-Xms128m", "-Xmx128m",
                "-Dcom.sun.management.jmxremote=true",
                "-Dcom.sun.management.jmxremote.local.only=false",
                "-Dcom.sun.management.jmxremote.port=" + jmxPort.toString(),
                "-Dcom.sun.management.jmxremote.ssl=false",
                "-Dcom.sun.management.jmxremote.authenticate=false",
                "-Djava.rmi.server.hostname=" + serverIP.toString(),
                "-Dcom.sun.management.jmxremote.rmi.port=" + jmxPort.toString(),
                "-XX:+HeapDumpOnOutOfMemoryError",
                "-XX:HeapDumpPath=/heap-dumps/heapdump.hprof"]
        ports = ['8080']
    }
}

bootJar {
    enabled = true 
}
jar {
    enabled = true
}

  • app module ์€ entry module ์˜ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฉ€ํ‹ฐ๋ชจ๋“ˆ ํ”„๋กœ์ ํŠธ์—์„œ jib ๊ธฐ๋ฐ˜์œผ๋กœ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” mainClass ๋ฅผ ๋ช…์‹œํ•˜์ง€ ์•Š์œผ๋ฉด, jib ์—์„œ ๋ฉ”์ธ ํด๋ž˜์Šค๋ฅผ ์ฐพ์ง€ ๋ชปํ•˜์—ฌ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.
    • ์•„๋ž˜์™€ ๊ฐ™์ด mainClass path ๋ฅผ ๋ช…์‹œํ•ด์ฃผ๋„๋ก ํ•ฉ์‹œ๋‹ค.
      • mainClass = 'com.numberone.backend.BackendApplication'
      • ์™ธ์— jvmFlags ๋Š” ํ•„์š”์— ๋”ฐ๋ผ ์ถ”๊ฐ€ํ•˜๊ณ  ์ œ๊ฑฐํ•ด์ฃผ๋„๋ก ํ•ฉ์‹œ๋‹ค.

์„ธ๋ถ€ ์ฝ”๋“œ๋Š” ์•„๋ž˜ ๋ ˆํฌ์ง€ํ† ๋ฆฌ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • https://github.com/Team-NumberOne/Backend

5. ๋ฉ€ํ‹ฐ๋ชจ๋“ˆ๋กœ ์ „ํ™˜ํ•˜๋ฉฐ ์ƒ๊ธด ๊ณ ๋ฏผ๋“ค

์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ์—์„œ ์œ„ ๊ตฌ์กฐ๊ฐ€ ๊ณผ์—ฐ ์ตœ์ ์ผ๊นŒ์š”..? ๊ฐ ๋ชจ๋“ˆ ๊ฐ„ ์ฑ…์ž„๊ณผ ์˜์กด๊ด€๊ณ„๊ฐ€ ํ•ฉ๋ฆฌ์ ์ผ๊นŒ์š”..?

ํŒ€์›๊ณผ ํ•จ๊ป˜ ๋งŽ์ด ๊ณ ๋ฏผํ•˜๋ฉฐ ์„ค๊ณ„ํ•œ ๊ตฌ์กฐ์ด์ง€๋งŒ, ํ˜„์žฌ์˜ ๊ตฌ์กฐ๊ฐ€ ํ•ญ์ƒ ์ตœ์ ์ด ์•„๋‹ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์„ ๋ช…ํ™•ํžˆ ์ธ์ง€ํ•˜๋ฉฐ ์ž‘์—…ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์•ž์œผ๋กœ ๋ฐœ์ƒํ•  ์ถ”๊ฐ€์ ์ธ ์š”๊ตฌ์‚ฌํ•ญ๋“ค์„ ๊ฐœ๋ฐœํ•˜๊ฑฐ๋‚˜, ๋ฆฌํŒฉํ† ๋งํ•˜๋ฉด์„œ ๊ฐ๊ฐ์˜ ๋ชจ๋“ˆ์˜ ์ฑ…์ž„์„ ๋ชธ์†Œ ๊ฒฝํ—˜ํ•˜๋ฉฐ ์ตœ์ข…์ ์œผ๋กœ๋Š” ๋” ๋‚˜์€ ๊ตฌ์กฐ๋กœ ๋‚˜์•„๊ฐˆ ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ๊ฒ ์ฃ !

์ถ”๊ฐ€์ ์œผ๋กœ ๊ฐ ๋ชจ๋“ˆ์—์„œ ํ•„์š”ํ•œ ํ”„๋กœํผํ‹ฐ ํŒŒ์ผ๋“ค์„ ์–ด๋–ป๊ฒŒ ๊ด€๋ฆฌํ•  ์ง€ ๊ณ ๋ฏผ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ๋Š” ํ•˜๋‚˜์˜ application.yml ์— ๋ชจ๋“  ์„ค์ •์„ ๋ชฐ์•„๋‘์–ด ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ๋Š”๋ฐ์š”, ์—ฌ๋Ÿฌ ๋ชจ๋“ˆ์˜ ์„ค์ • ํŒŒ์ผ์ด ํ•˜๋‚˜์— ๋ชฐ๋ ค์žˆ๋‹ค๋Š” ๊ฒƒ์€ ๊ฒฐ์ฝ” ์ข‹์€ ๋ฐฉ์‹์ด ์•„๋‹ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ฒฐ๊ตญ, ๋ชจ๋“ˆ ๋ณ„๋กœ ๋…๋ฆฝ๋œ application.yml ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด๋‘๋ฉด ๋  ํ…๋ฐ์š”, ํ˜„์žฌ ๋ฐฐํฌ ๋ฐฉ์‹์—์„œ application.yml ์€ github secrets ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์™€ ๊ฐ™์ด directory ์— ์ง์ ‘ ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์ฃ .

- name: ๐Ÿง create application.yml
  run: |
    mkdir -p ./daepiro-app/src/main/resources
    cd ./daepiro-app/src/main/resources
    touch ./application.yml
    echo "$" | base64 --decode > ./application.yml
    ls -la
  shell: bash

๊ฐ ๋ชจ๋“ˆ ๋ณ„๋กœ application.yml ์ด ๋ถ„๋ฆฌ๋œ๋‹ค๋ฉด, ๊ฐ ๋ชจ๋“ˆ ๋ณ„ PROPERTY ๋ฅผ GitHub secrets ์— ๋ณ„๋„๋กœ ์ €์žฅํ•ด๋‘์–ด์•ผ ํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ๊ฐœ๋ฐœํ•˜๋ฉฐ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์ƒ๊ธฐ๋ฉด, ๊ทธ๋•Œ๋งˆ๋‹ค ์ˆ˜๋™์œผ๋กœ github secrets ์„ ๋ณ€๊ฒฝํ•œ ๋’ค ํŒ€์›์—๊ฒŒ ์ˆ˜์ •์‚ฌํ•ญ์„ ์„ค๋ช…ํ•ด์•ผ ํ•˜๋Š” ๊ณผ์ •์ด ํ•„์—ฐ์ ์ผํ…๋ฐ์š”.. ์ƒ์‚ฐ์„ฑ์ด ๋งŽ์ด ๋–จ์–ด์งˆ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋”๋ถˆ์–ด, GitHub secrets ์€ ๋ณ€๊ฒฝ history ๊ฐ€ ๋‚จ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ž˜๋ชป ๋ณ€๊ฒฝํ–ˆ์„๋•Œ ์„œ๋น„์Šค ์žฅ์• ๋กœ ์ด์–ด์ง€๋Š” ํฐ ๋ฆฌ์Šคํฌ๊ฐ€ ์กด์žฌํ•˜์ฃ . ์‚ฌ๋žŒ์˜ ์‹ค์ˆ˜๊ฐ€ ์„œ๋น„์Šค ์žฅ์• ๋กœ ์˜จ์ „ํžˆ ์ด๋ฃจ์–ด์ง€๋Š” ์ด ๊ตฌ์กฐ๋Š” ์ •๋ง ์ข‹์ง€ ์•Š์€ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”? ์—ฌ๋Ÿฌ ๋ฐฉ๋ฉด์œผ๋กœ ๊ณ ๋ฏผ ์ค‘์ธ๋ฐ์š”, ์–ผ๋ฅธ ํ•ด๊ฒฐํ•ด์„œ ํฌ์ŠคํŒ…ํ•˜๊ณ ์‹ถ๋„ค์š”!

6. ๊ธฐ์กด์˜ ๋ฐฐํฌ ๋ฐฉ์‹์˜ ๋ฌธ์ œ์ , ๊ทธ๋ฆฌ๊ณ  blue - green ๋ฐฐํฌ๋กœ์˜ ์ „ํ™˜

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

์„ ํƒํ•œ ๋ฐฉ๋ฒ•์€ blue-green ๋ฐฐํฌ์ธ๋ฐ์š”, ํ˜„์žฌ ์„œ๋น„์Šค ์ค‘์ธ ํ™˜๊ฒฝ(blue) ์™ธ์— ์ƒˆ๋กœ์šด ๋ฒ„์ „์„ ๋ฐฐํฌํ•  ํ™˜๊ฒฝ(green)์„ ๋ชจ๋‘ ๊ตฌ์„ฑํ•œ ๋’ค, nginx ๋ฅผ ์ด์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด ๋ฒ„์ „์ด ์™„์ „ํžˆ ๋ฐฐํฌ๋˜๊ณ  health-check ๊ฐ€ ๋œ๋‹ค๋ฉด ํŠธ๋ž˜ํ”ฝ์„ blue ์—์„œ green ์œผ๋กœ ํ•œ๋ฒˆ์— ์ „ํ™˜ํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

image

์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๋Š” ๋‹ค์šดํƒ€์ž„์„ ๊ฑฐ์˜ ์ฒด๊ฐํ•˜์ง€ ๋ชปํ•˜๊ณ (nginx ๋ฅผ reload ํ•˜๋Š” ์ •๋„์˜ ๋‹ค์šดํƒ€์ž„์ด ์กด์žฌํ•˜์ง€๋งŒ ๋งค์šฐ ์งง๊ธฐ ๋•Œ๋ฌธ์— ์ผ๋ฐ˜์ ์œผ๋กœ ์ธ์ง€ํ•˜๊ธฐ ์–ด๋ ค์šธ ๊ฒƒ) ์„œ๋น„์Šค๋ฅผ ์ง€์†์ ์œผ๋กœ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ๋กค๋ฐฑ์ด ํ•„์š”ํ•  ๊ฒฝ์šฐ์— ์ด์ „ ๋ฒ„์ „์˜ ์ปจํ…Œ์ด๋„ˆ๋กœ ๊ณง๋ฐ”๋กœ ํŠธ๋ž˜ํ”ฝ์„ ์ „ํ™˜ํ•˜๋ฉด์„œ ๋น ๋ฅด๊ฒŒ ๋กค๋ฐฑํ•  ์ˆ˜ ์žˆ์ฃ .

( ์—ฌ๋‹ด ) ๊ฐ€๋‚œํ•œ.. ๋Œ€ํ”ผ๋กœ ๋ฐฑ์—”๋“œ๋Š” aws ec2 t2.micro ํ™˜๊ฒฝ์—์„œ ๋Œ์•„๊ฐ€๊ณ  ์žˆ๋Š”๋ฐ์š”,, ๋Œ€ํ”ผ๋กœ ์Šคํ”„๋ง ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋™์‹œ์— 2๊ฐœ ์ด์ƒ ๋„์šฐ๋Š” ๊ฒƒ์€ ์ฃผ์–ด์ง„ ์„œ๋ฒ„ ๋ฆฌ์†Œ์Šค ์ƒ ์–ด๋ ค์šธ ๊ฒƒ์œผ๋กœ ํŒ๋‹จํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์•„์ง ์ˆ˜์ต์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์•„ ์„œ๋ฒ„ ์‚ฌ์–‘์„ ๋†’์ผ ์ˆ˜๊ฐ€ ์—†์–ด, t2.micro ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ํ˜„์žฌ, ์ž„์‹œ์ ์œผ๋กœ ํ•˜๋‚˜์˜ ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ๋งŒ ์‚ด์•„์žˆ๋„๋ก ์กฐ์น˜ํ•ด๋‘๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€๋ น, blue ์— ์ƒˆ๋กœ์šด ๋ฒ„์ „์„ ๋ฐฐํฌํ•˜๊ฒŒ ๋œ ๊ฒฝ์šฐ๋ผ๋ฉด blue ๋ฅผ ์ƒˆ๋กœ ๋„์šฐ๊ณ  green ์€ ๋กค๋ฐฑ ์‹œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์‚ด๋ ค๋‘์–ด์•ผ ํ•˜์ง€๋งŒ, ์ง€๊ธˆ์€ green ์„ kill ํ•˜๊ณ  ์žˆ์ฃ ..

์ ์šฉ์€ ๋งค์šฐ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. ๋Œ€ํ”ผ๋กœ ๋ฐฑ์—”๋“œ์—์„œ๋Š” (์ž๊ธˆ ์ ˆ์•ฝ ์ด์Šˆ๋กœ) blue, green ์ปจํ…Œ์ด๋„ˆ๋ฅผ ํ•œ ์„œ๋ฒ„์—์„œ ๋„์›Œ์•ผ ํ–ˆ์œผ๋ฏ€๋กœ port ๋ฅผ ๋‹ค๋ฅด๊ฒŒ ํ•˜์—ฌ ๋‘ ํ™˜๊ฒฝ์„ ๊ตฌ๋ถ„ํ–ˆ์Šต๋‹ˆ๋‹ค.

docker-compose ์—์„œ blue, green ์— ๋Œ€ํ•œ ext - in port ๋ฅผ ๊ฐ๊ฐ 8081:8080, 8082:8080 ์œผ๋กœ ์„ค์ •ํ•ด๋‘์—ˆ์–ด์š”.

version: '3'
services:
  blue:
    image: your-docker-image
    container_name: blue
    restart: always
    ports:
      - 8081:8080

  green:
    image: your-docker-image
    container_name: green
    restart: always
    ports:
      - 8082:8080
...

๋˜ํ•œ blue, green ์šฉ๋„๋กœ ์‚ฌ์šฉํ•  nginx.conf ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •ํ•ด๋‘์—ˆ์Šต๋‹ˆ๋‹ค.

  • blue
server {
        listen 80 default_server;
        listen [::]:80 default_server;

        # SSL configuration
        #
        # listen 443 ssl default_server;
        # listen [::]:443 ssl default_server;
        #
        # Note: You should disable gzip for SSL traffic.
        # See: https://bugs.debian.org/773332
        #
        # Read up on ssl_ciphers to ensure a secure configuration.
        # See: https://bugs.debian.org/765782
        #
        # Self signed certs generated by the ssl-cert package
        # Don't use them in a production server!
        #
        # include snippets/snakeoil.conf;

        root /var/www/html;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;

        server_name localhost;

        location / {
                proxy_pass http://localhost:8081;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
        }

        # pass PHP scripts to FastCGI server
        #
        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
        #
        #       # With php-fpm (or other unix sockets):
                fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        #       # With php-cgi (or other tcp sockets):
        #       fastcgi_pass 127.0.0.1:9000;
        }

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #       deny all;
        #}
}

  • green
server {
        listen 80 default_server;
        listen [::]:80 default_server;

        # SSL configuration
        #
        # listen 443 ssl default_server;
        # listen [::]:443 ssl default_server;
        #
        # Note: You should disable gzip for SSL traffic.
        # See: https://bugs.debian.org/773332
        #
        # Read up on ssl_ciphers to ensure a secure configuration.
        # See: https://bugs.debian.org/765782
        #
        # Self signed certs generated by the ssl-cert package
        # Don't use them in a production server!
        #
        # include snippets/snakeoil.conf;

        root /var/www/html;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;

        server_name localhost;

        location / {
                proxy_pass http://localhost:8082;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
        }

        # pass PHP scripts to FastCGI server
        #
        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
        #
        #       # With php-fpm (or other unix sockets):
                fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        #       # With php-cgi (or other tcp sockets):
        #       fastcgi_pass 127.0.0.1:9000;
        }

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #       deny all;
        #}
}

๋ฐฐํฌ ์‰˜ ์Šคํฌ๋ฆฝํŠธ๋Š” ์•„๋ž˜์™€ ๊ฐ™์•„์š”.

#!/bin/bash

IS_GREEN_EXIST=$(docker ps | grep green)
DEFAULT_CONF=" /etc/nginx/nginx.conf"

# blue๊ฐ€ ์‹คํ–‰ ์ค‘์ด๋ฉด green์„ upํ•ฉ๋‹ˆ๋‹ค.
if [ -z $IS_GREEN_EXIST ];then
  echo "### BLUE => GREEN ####"
  echo ">>> green image๋ฅผ pullํ•ฉ๋‹ˆ๋‹ค."
  docker-compose pull green
  echo ">>> green container๋ฅผ upํ•ฉ๋‹ˆ๋‹ค."
  docker-compose up -d green
  while [ 1 = 1 ]; do
  echo ">>> green health check ์ค‘..."
  sleep 3
  REQUEST=$(curl http://127.0.0.1:8082/hello)
    if [ -n "$REQUEST" ]; then
      echo ">>> ๐Ÿƒ health check success !"
      break;
    fi
  done;
  sleep 3
  echo ">>> nginx๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ ํ•ฉ๋‹ˆ๋‹ค."
  sudo ln -s -f /etc/nginx/sites-available/green /etc/nginx/sites-enabled/default
  sudo nginx -s reload
  echo ">>> blue container๋ฅผ downํ•ฉ๋‹ˆ๋‹ค."
  docker-compose stop blue

# green์ด ์‹คํ–‰ ์ค‘์ด๋ฉด blue๋ฅผ upํ•ฉ๋‹ˆ๋‹ค.
else
  echo "### GREEN => BLUE ###"
  echo ">>> blue image๋ฅผ pullํ•ฉ๋‹ˆ๋‹ค."
  docker-compose pull blue
  echo ">>> blue container upํ•ฉ๏ฟฝ๏ฟฝ๋‹ค."
  docker-compose up -d blue
  while [ 1 = 1 ]; do
    echo ">>> blue health check ์ค‘..."
    sleep 3
    REQUEST=$(curl http://127.0.0.1:8081/hello)
    if [ -n "$REQUEST" ]; then
      echo ">>> ๐Ÿƒ health check success !"
      break;
    fi
  done;
  sleep 3
  echo ">>> nginx๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ ํ•ฉ๋‹ˆ๋‹ค."
  sudo ln -s -f /etc/nginx/sites-available/blue /etc/nginx/sites-enabled/default
  sudo nginx -s reload
  echo ">>> green container๋ฅผ downํ•ฉ๋‹ˆ๋‹ค."
  docker-compose stop green
fi

7. ์•ž์œผ๋กœ์˜ ๊ณผ์ œ๋Š”?

์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ์‚ฌ์ด๋“œ ํ”„๋กœ์ ํŠธ์ธ ๋Œ€ํ”ผ๋กœ์˜ ์•„ํ‚คํ…์ณ๋ฅผ ๋ฉ€ํ‹ฐ๋ชจ๋“ˆ ๊ตฌ์กฐ๋กœ ์ „ํ™˜ํ•œ ๊ฒƒ๊ณผ, blue-green ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ๋ฅผ ์ ์šฉํ•œ ๊ฒฝํ—˜์— ๋Œ€ํ•ด์„œ ์ž‘์„ฑํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค! ์ด์ œ ๋Œ€ํ”ผ๋กœ ๋ฐฑ์—”๋“œ๋ฅผ ์กฐ๊ธˆ ๋” ๋ฐœ์ „์‹œํ‚ค๊ธฐ ์œ„ํ•ด ๋‹ค๋ฅธ ๊ณผ์ œ๋ฅผ ํ•ด๋ณด๋ ค๊ณ  ํ•ด์š”! ์ง€๊ธˆ ์‚ดํŽด๋ณด๊ณ  ์žˆ๋Š” ๊ฒƒ์€.. ํ˜„์žฌ์˜ ๊ตฌ์กฐ์—์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ์šฉ์ดํ•˜๋„๋ก ํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ•  ์ง€์— ๋Œ€ํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค. ์ง€๊ธˆ ๋Œ€ํ”ผ๋กœ ๋ฐฑ์—”๋“œ์— ์กด์žฌํ•˜๋Š” ๊ฐ€์žฅ ํฐ ๋ฌธ์ œ๋Š” ์ผ๋ถ€ api ๋“ค์ด mysql ์— ์™„์ „ํžˆ ์˜์กด์ ์ธ ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋Š” ๊ฒƒ์ธ๋ฐ์š”, ์ผ๋ฐ˜์ ์œผ๋กœ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ๋Š” h2 database ๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์€๋ฐ ํ˜„์žฌ ๋Œ€ํ”ผ๋กœ ๋ฐฑ์—”๋“œ์—์„œ๋Š” ๋ถˆ๊ฐ€๋Šฅํ•˜์ฃ . ์ด ๋ถ€๋ถ„์„ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”? ์‚ฌ์‹ค ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ๊ฐ€ ํŠน์ • db ๊ตฌํ˜„์ฒด์— ์˜์กดํ•˜๊ณ  ์žˆ๋Š” ์ƒํ™ฉ์€ ์ข‹์ง€ ์•Š๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. mysql ์ด๋ผ๋Š” ๊ตฌํ˜„์ฒด์— ์˜์กดํ•˜๊ณ  ์žˆ๋Š” ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ๋ฅผ ๋ฆฌํŒฉํ† ๋งํ•˜์—ฌ, ํ…Œ์ŠคํŠธ๊ฐ€ ์šฉ์ดํ•˜๋„๋ก ํ•ด๋ณผ ๊ณ„ํš์ด์—์š”!! ํ•ด๋‹น ์ž‘์—…์ด ์™„๋ฃŒ๋˜๋ฉด ์กฐ๋งŒ๊ฐ„ ๋‹ค์‹œ ํฌ์ŠคํŒ…ํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค! ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค :)


(24.03.07 ์ถ”๊ฐ€)
์ผ๋ฐ˜์ ์ธ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ h2 ๋ฅผ ์ด์šฉํ•˜๋Š” ์ด์œ ๋Š” ํŽธ๋ฆฌ์„ฑ ๋•Œ๋ฌธ์ผํ…๋ฐ์š”, ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ์˜ ํ•ต์‹ฌ ๋กœ์ง์„ ํ…Œ์ŠคํŠธ ํ•  ์ˆ˜ ์—†์Œ์—๋„ h2 ๋ฅผ ๊ณ ์ง‘ํ•ด์•ผ ํ• ๊นŒ์š”!? ์ด ๋ถ€๋ถ„์€ ๐Ÿšƒ ๋” ๋น ๋ฅด๊ฒŒ ๋‹ฌ๋ฆฌ๊ธฐ ์œ„ํ•ด ์ž ๊น ๋ฉˆ์ถ”๊ธฐ ํฌ์ŠคํŒ…์—์„œ ๋‹ค๋ค„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค!

References

  1. What is Blue Green Deployment by Shashank Srivastava Apr 9, 2021
  2. Blue Green Deployment
  3. Creating a Gradle multi-module project

Leave a comment