Docker Compose: ๊ฐœ๋ฐœ๋ถ€ํ„ฐ ์ƒ์‚ฐ๊นŒ์ง€

๊ฐœ๊ฐ•์„ ์˜ˆ์ƒํ•˜์—ฌ ์ค€๋น„ํ•œ ํŒŸ์บ์ŠคํŠธ ํ•„์‚ฌ๋ณธ ๋ฒˆ์—ญ "๋ฆฌ๋ˆ…์Šค ๊ด€๋ฆฌ์ž"

Docker Compose: ๊ฐœ๋ฐœ๋ถ€ํ„ฐ ์ƒ์‚ฐ๊นŒ์ง€

Docker Compose๋Š” ์ž‘์—…์„ ์ƒ์„ฑํ•˜๋Š” ๋†€๋ผ์šด ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.
์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์‚ฌ์šฉ๋˜๋Š” ์Šคํƒ ํ™˜๊ฒฝ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฐ ๊ตฌ์„ฑ ์š”์†Œ๋Š” ๋ช…ํ™•ํ•˜๊ณ  ๊ฐ„๋‹จํ•œ ๊ตฌ๋ฌธ์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. YAML-
ํŒŒ์ผ
.

์˜ ์ถœํ˜„์œผ๋กœ ๋„์ปค ์ž‘์„ฑ v3 ์ด๋Ÿฌํ•œ YAML ํŒŒ์ผ์€ ์ž‘์—…ํ•  ๋•Œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋ฌด๋ฆฌ ๋„์ปค ์Šค์›œ.

ํ•˜์ง€๋งŒ ์ด๋Š” ๋™์ผํ•œ docker-compose ํŒŒ์ผ์„ ๋‹ค์Œ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๊นŒ?
๊ฐœ๋ฐœ ํ”„๋กœ์„ธ์Šค์™€ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ? ๋˜๋Š” ๋™์ผํ•œ ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜์—ฌ
๊ฐ์ƒ‰? ์ผ๋ฐ˜์ ์œผ๋กœ ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด ๊ธฐ๋Šฅ์„ ์œ„ํ•ด์„œ๋Š” ๋‹ค์Œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

  • ๋ณ€์ˆ˜ ๋ณด๊ฐ„: ์ผ๋ถ€ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ
    ํ™˜๊ฒฝ๋งˆ๋‹ค ๋ณ€ํ™”ํ•˜๋Š” ๊ฐ€์น˜.
  • ๊ตฌ์„ฑ ์žฌ์ •์˜: ๋‘ ๋ฒˆ์งธ(๋˜๋Š” ์ž„์˜์˜
    ๋˜ ๋‹ค๋ฅธ ํ›„์†) ๊ด€๋ จ ๋‚ด์šฉ์„ ๋ณ€๊ฒฝํ•˜๋Š” docker-compose ํŒŒ์ผ
    ๋จผ์ € docker compose๊ฐ€ ๋‘ ํŒŒ์ผ์„ ๋ณ‘ํ•ฉํ•˜๋Š” ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ ํŒŒ์ผ๊ณผ ํ”„๋กœ๋•์…˜ ํŒŒ์ผ์˜ ์ฐจ์ด์ 

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

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

๋Œ€์‹ , ์šฐ๋ฆฌ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ํŠน์ • ๋ฒ„์ „์˜ ์ฝ”๋“œ๋กœ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.
์ ์ ˆํ•œ ํƒœ๊ทธ๋กœ ํ‘œ์‹œํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค(์˜๋ฏธ ์ฒด๊ณ„๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ).
๋ฒ„์ „ ๊ด€๋ฆฌ ๋˜๋Š” ๊ท€ํ•˜์˜ ์žฌ๋Ÿ‰์— ๋”ฐ๋ฅธ ๋‹ค๋ฅธ ์‹œ์Šคํ…œ).

๊ตฌ์„ฑ ์žฌ์ •์˜

์ฐจ์ด์ ๊ณผ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ ์ข…์†์„ฑ์ด ๋‹ค๋ฅผ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์„ ๊ฐ์•ˆํ•  ๋•Œ
๊ฐœ๋ฐœ ๋ฐ ์ƒ์‚ฐ ๊ณผ์ •์—์„œ ์„œ๋กœ ๋‹ค๋ฅธ ๊ตฌ์„ฑ ํŒŒ์ผ์ด ํ•„์š”ํ•˜๋‹ค๋Š” ๊ฒƒ์€ ๋ถ„๋ช…ํ•ฉ๋‹ˆ๋‹ค.

Docker Compose๋Š” ๋‹ค์–‘ํ•œ Compose ํŒŒ์ผ์„ ๋ณ‘ํ•ฉํ•˜์—ฌ
์ตœ์ข… ๊ตฌ์„ฑ์„ ์–ป์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ์˜ˆ์ œ์—์„œ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

$ cat docker-compose.yml
version: "3.2"

services:
  whale:
    image: docker/whalesay
    command: ["cowsay", "hello!"]
$ docker-compose up
Creating network "composeconfigs_default" with the default driver
Starting composeconfigs_whale_1
Attaching to composeconfigs_whale_1
whale_1  |  ________
whale_1  | < hello! >
whale_1  |  --------
whale_1  |     
whale_1  |      
whale_1  |       
whale_1  |                     ##        .
whale_1  |               ## ## ##       ==
whale_1  |            ## ## ## ##      ===
whale_1  |        /""""""""""""""""___/ ===
whale_1  |   ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
whale_1  |        ______ o          __/
whale_1  |                     __/
whale_1  |           __________/
composeconfigs_whale_1 exited with code 0

๋งํ–ˆ๋“ฏ์ด docker compose๋Š” ์—ฌ๋Ÿฌ compose ๊ฒฐํ•ฉ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋‘ ๋ฒˆ์งธ ํŒŒ์ผ์˜ ๋‹ค์–‘ํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์žฌ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด:

$ cat docker-compose.second.yml
version: "3.2"
services:
  whale:
    command: ["cowsay", "bye!"]

$ docker-compose -f docker-compose.yml -f docker-compose.second.yml up
Creating composeconfigs_whale_1
Attaching to composeconfigs_whale_1
whale_1  |  ______
whale_1  | < bye! >
whale_1  |  ------
whale_1  |     
whale_1  |      
whale_1  |       
whale_1  |                     ##        .
whale_1  |               ## ## ##       ==
whale_1  |            ## ## ## ##      ===
whale_1  |        /""""""""""""""""___/ ===
whale_1  |   ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
whale_1  |        ______ o          __/
whale_1  |                     __/
whale_1  |           __________/
composeconfigs_whale_1 exited with code 0

์ด ๊ตฌ๋ฌธ์€ ๊ฐœ๋ฐœ ์ค‘์—๋Š” ๊ทธ๋‹ค์ง€ ํŽธ๋ฆฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
์—ฌ๋Ÿฌ ๋ฒˆ ํ•ด์•ผ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์šด ์ข‹๊ฒŒ๋„ docker compose๋Š” ์ž๋™์œผ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŠน์ˆ˜ ํŒŒ์ผ์„ ์ฐพ์Šต๋‹ˆ๋‹ค.
docker-compose.override.yml ๊ฐ’์„ ์žฌ์ •์˜ํ•˜๋ ค๋ฉด docker-compose.yml. ๋งŒ์•ฝ
๋‘ ๋ฒˆ์งธ ํŒŒ์ผ์˜ ์ด๋ฆ„์„ ๋ฐ”๊พธ๋ฉด ์›๋ž˜ ๋ช…๋ น๋งŒ ์‚ฌ์šฉํ•˜์—ฌ ๋™์ผํ•œ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

$ mv docker-compose.second.yml docker-compose.override.yml
$ docker-compose up
Starting composeconfigs_whale_1
Attaching to composeconfigs_whale_1
whale_1  |  ______
whale_1  | < bye! >
whale_1  |  ------
whale_1  |     
whale_1  |      
whale_1  |       
whale_1  |                     ##        .
whale_1  |               ## ## ##       ==
whale_1  |            ## ## ## ##      ===
whale_1  |        /""""""""""""""""___/ ===
whale_1  |   ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
whale_1  |        ______ o          __/
whale_1  |                     __/
whale_1  |           __________/
composeconfigs_whale_1 exited with code 0

์ข‹์•„์š”, ๊ทธ๊ฒŒ ๊ธฐ์–ตํ•˜๊ธฐ ๋” ์‰ฝ๋„ค์š”.

๋ณ€์ˆ˜ ๋ณด๊ฐ„

๊ตฌ์„ฑ ํŒŒ์ผ ์ง€์› ๋ณด๊ฐ„
๋ณ€์ˆ˜
๋ฐ ๊ธฐ๋ณธ๊ฐ’. ์ฆ‰, ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

services:
  my-service:
    build:
      context: .
    image: private.registry.mine/my-stack/my-service:${MY_SERVICE_VERSION:-latest}
...

๊ทธ๋ฆฌ๊ณ  ๋งŒ์•ฝ ๋‹น์‹ ์ด ๊ทธ๋ ‡๊ฒŒ ํ•œ๋‹ค๋ฉด docker-compose ๋นŒ๋“œ(๋˜๋Š” ํ‘ธ์‹œ) ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์—†์ด
$MY_SERVICE_VERSION, ๊ฐ’์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค ์ตœ๊ทผํ•˜์ง€๋งŒ ์„ค์ •ํ•˜๋ฉด
๋นŒ๋“œ ์ „ ํ™˜๊ฒฝ ๋ณ€์ˆ˜์˜ ๊ฐ’์€ ๋นŒ๋“œํ•˜๊ฑฐ๋‚˜ ํ‘ธ์‹œํ•  ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
๋“ฑ๋ก๋ถ€์— ๊ฐœ์ธ.๋ ˆ์ง€์ŠคํŠธ๋ฆฌ.๊ด‘์‚ฐ.

๋‚˜์˜ ์›์น™

๋‚˜์—๊ฒŒ ํšจ๊ณผ๊ฐ€ ์žˆ๋Š” ์ ‘๊ทผ ๋ฐฉ์‹์ด ๋‹น์‹ ์—๊ฒŒ๋„ ํšจ๊ณผ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ์ด๊ฒƒ์„ ๋”ฐ๋ฅธ๋‹ค
๊ฐ„๋‹จํ•œ ๊ทœ์น™:

  • ํ”„๋กœ๋•์…˜, ๊ฐœ๋ฐœ(๋˜๋Š” ๊ธฐํƒ€ ํ™˜๊ฒฝ)์„ ์œ„ํ•œ ๋ชจ๋“  ์Šคํƒ์€ ๋‹ค์Œ์„ ํ†ตํ•ด ์ •์˜๋ฉ๋‹ˆ๋‹ค.
    ๋„์ปค ์ž‘์„ฑ ํŒŒ์ผ
  • ๊ฐ€๋Šฅํ•œ ํ•œ ๋ชจ๋“  ํ™˜๊ฒฝ์„ ํฌ๊ด„ํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๊ตฌ์„ฑ ํŒŒ์ผ
    ์ค‘๋ณต์„ ํ”ผํ•˜์„ธ์š”.
  • ๊ฐ ํ™˜๊ฒฝ์—์„œ ์ž‘์—…ํ•˜๋ ค๋ฉด ํ•˜๋‚˜์˜ ๊ฐ„๋‹จํ•œ ๋ช…๋ น์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ธฐ๋ณธ ๊ตฌ์„ฑ์€ ํŒŒ์ผ์— ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. docker-compose.yml.
  • ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋Š” ์ด๋ฏธ์ง€ ํƒœ๊ทธ ๋˜๋Š” ๊ธฐํƒ€๋ฅผ ์ •์˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
    ํ™˜๊ฒฝ๋งˆ๋‹ค ๋‹ค๋ฅผ ์ˆ˜ ์žˆ๋Š” ๋ณ€์ˆ˜(์Šคํ…Œ์ด์ง•, ํ†ตํ•ฉ,
    ์ƒ์‚ฐ).
  • ์ƒ์‚ฐ ๋ณ€์ˆ˜์˜ ๊ฐ’์€ ๋‹ค์Œ์˜ ๊ฐ’์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
    ๊ธฐ๋ณธ์ ์œผ๋กœ ์ด๋Š” ์Šคํƒ์ด ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์‹œ์ž‘๋  ๋•Œ ์œ„ํ—˜์„ ์ตœ์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค.
    ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์„œ๋น„์Šค๋ฅผ ์‹œ์ž‘ํ•˜๋ ค๋ฉด ๋‹ค์Œ ๋ช…๋ น์„ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค. ๋„์ปค ์Šคํƒ ๋ฐฐํฌ - ์ž‘์„ฑ ํŒŒ์ผ docker-compose.yml -registry-auth my-stack-name.
  • ์ž‘์—… ํ™˜๊ฒฝ์€ ๋‹ค์Œ ๋ช…๋ น์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. ๋„์ปค - ์œ„๋กœ ๊ตฌ์„ฑ dd.

๊ฐ„๋‹จํ•œ ์˜ˆ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

# docker-compose.yml
...
services:
  my-service:
    build:
      context: .
    image: private.registry.mine/my-stack/my-service:${MY_SERVICE_VERSION:-latest}
    environment:
      API_ENDPOINT: ${API_ENDPOINT:-https://production.my-api.com}
...

ะ˜

# docker-compose.override.yml
...
services:
  my-service:
    ports: # This is needed for development!
      - 80:80
    environment:
      API_ENDPOINT: https://devel.my-api.com
    volumes:
      - ./:/project/src
...

๋‚˜๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค docker-compose (๋„์ปค-์ž‘์„ฑ)์Šคํƒ์„ ์‹คํ–‰ํ•˜๋ ค๋ฉด
์†Œ์Šค ์ฝ”๋“œ๊ฐ€ ๋งˆ์šดํŠธ๋œ ๊ฐœ๋ฐœ ๋ชจ๋“œ /ํ”„๋กœ์ ํŠธ/src.

ํ”„๋กœ๋•์…˜์—์„œ๋„ ๋™์ผํ•œ ํŒŒ์ผ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! ๊ทธ๋ฆฌ๊ณ  ๋‚˜๋Š” ํ™•์‹คํžˆ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
๊ฐ™์€ ํŒŒ์ผ docker-compose.yml ๋ฌด๋Œ€๋ฅผ ์œ„ํ•ด. ์ด๋ฅผ ํ™•์žฅํ•˜๋ ค๋ฉด
ํ”„๋กœ๋•์…˜์—์„œ๋Š” ์‚ฌ์ „ ์ •์˜๋œ ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•˜๊ณ  ์ „์†กํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
CI ๋‹จ๊ณ„์—์„œ:

export MY_SERVICE_VERSION=1.2.3
docker-compose -f docker-compose.yml build
docker-compose -f docker-compose.yml push

ํ”„๋กœ๋•์…˜์—์„œ๋Š” ๋‹ค์Œ ๋ช…๋ น์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

export MY_SERVICE_VERSION=1.2.3
docker stack deploy my-stack --compose-file docker-compose.yml --with-registry-auth

๋ฌด๋Œ€์—์„œ๋„ ๋™์ผํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋ ค๋ฉด ๋‹ค์Œ์„ ์ •์˜ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
์Šคํ…Œ์ด์ง• ํ™˜๊ฒฝ์—์„œ ์ž‘์—…ํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜:

export MY_SERVICE_VERSION=1.2.3
export API_ENDPOINT=http://staging.my-api.com
docker stack deploy my-stack --compose-file docker-compose.yml --with-registry-auth

๊ฒฐ๊ณผ์ ์œผ๋กœ ์šฐ๋ฆฌ๋Š” ๋‘ ๊ฐœ์˜ ์„œ๋กœ ๋‹ค๋ฅธ docker-compose ํŒŒ์ผ์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
๊ท€ํ•˜์˜ ๋ชจ๋“  ํ™˜๊ฒฝ์— ์ค‘๋ณต ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

๊ณผ์ •์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ "๋ฆฌ๋ˆ…์Šค ๊ด€๋ฆฌ์ž"

์ถœ์ฒ˜ : habr.com

์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ถ”๊ฐ€