Blue/Green ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ ์‹œ ๊ตฌ๋ฒ„์ „์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์–ธ์ œ ์ข…๋ฃŒํ•ด์•ผ ํ• ๊นŒ? (graceful shutdown)

1. ๊ฐœ์š”

์ง„ํ–‰ ์ค‘์ธ Jikgong ์„œ๋น„์Šค์—์„œ Nginx๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Blue/Green  ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ ์ „๋žต์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐฐํฌ ์‹œ ๋‹ค์šด ํƒ€์ž„์„ ์ตœ์†Œํ™” ์‹œ์ผฐ๋‹ค. 

 

๊ทธ ๊ณผ์ •์—์„œ ์ž‘์„ฑํ•œ deploy.sh ํŒŒ์ผ์—” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ์ž‘์„ฑ๋˜์–ด์žˆ๋‹ค. 

# ๊ธฐ์กด์— ์‹คํ–‰ ์ค‘์ด์—ˆ๋˜ docker-compose๋Š” ์ข…๋ฃŒ์‹œ์ผœ์ค๋‹ˆ๋‹ค.
echo "jikgong-${TERMINATE_CONTAINER} down"
sudo docker-compose -f docker-compose.${TERMINATE_CONTAINER}.yml down --rmi all

 

์ด ๋ถ€๋ถ„์ด ๋ฌธ์ œ์˜€๋‹ค.

 

ํ˜„์žฌ ์‚ฌ์šฉ ์ค‘์ธ Blue/Green ๋ฐฉ์‹์˜ ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ฐ„๋žตํ•˜๊ฒŒ ์„ค๋ช…ํ•˜๋ฉด

 

1. ์ƒˆ๋กœ์šด ๋ฒ„์ „์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ตฌ๋™

2. ์ง€์†์ ์ธ ํ—ฌ์Šค์ฒดํฌ๋กœ ๊ตฌ๋™ ์™„๋ฃŒ ํ™•์ธ

3. ๊ตฌ๋™ ์™„๋ฃŒ ์‹œ ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ๋กœ ์‚ฌ์šฉ ์ค‘์ธ Nginx์˜ ํ”„๋ก์‹œ ์„ค์ •์„ ๋ณ€๊ฒฝํ•˜์—ฌ ์ƒˆ๋กœ์šด ๋ฒ„์ „์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐ€๋ฅดํ‚ค๋„๋ก ๋ณ€๊ฒฝ

4. ๊ธฐ์กด์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…Œ์ด๋„ˆ ์ข…๋ฃŒ

 

 

์œ„ ์ฝ”๋“œ๊ฐ€ 4๋ฒˆ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ฝ”๋“œ์ธ๋ฐ, ์—ฌ๊ธฐ์„œ ๋ฌธ์ œ์ ์€ ๊ตฌ๋ฒ„์ „์˜ ์ฒ˜๋ฆฌ ์ค‘์ธ ์ž‘์—…์„ ๋ฌด์‹œํ•˜๊ณ  ๊ฐ•์ œ ์ข…๋ฃŒ ์‹œํ‚จ๋‹ค๋Š” ์ ์ด๋‹ค. 

 

 

๊ทธ๋Ÿผ ์–ด๋–ป๊ฒŒ ์•ˆ์ „ํ•˜๊ฒŒ ์ข…๋ฃŒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์„๊นŒ?

 

๊ฒฐ๋ก  ๋ถ€ํ„ฐ ๋งํ•˜๋ฉด ๋‚˜๋Š” 2.2 / 2.3 ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์˜€๋‹ค. 

 

 

 

 

 

 

 

2.1 ํ”„๋กœ์„ธ์Šค๋ฅผ ์ง์ ‘ ์ข…๋ฃŒํ•  ๋•Œ 

๋‚˜๋Š” ๋ณดํ†ต ํ”„๋กœ์„ธ์Šค๋ฅผ ์ฃฝ์ด๊ณค ํ•  ๋•Œ kill -9 PID ๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ -9๋กœ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ข…๋ฃŒํ•˜๋ฉด ์‹œ์Šคํ…œ์— ์•…์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ๋‹ค. 

 

์˜ˆ๋ฅผ ๋“ค์–ด ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ๋ฐ›๊ณ  ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ 5์ดˆ๊ฐ€ ๊ฑธ๋ฆฌ๋Š” API๊ฐ€ ์žˆ๊ณ  ์ด๋ฅผ ์‹คํ–‰ ์ค‘์ผ ๋•Œ kill -9๋กœ ์ข…๋ฃŒํ•˜๊ฒŒ ๋˜๋ฉด ์ฆ‰์‹œ ์ข…๋ฃŒ๋˜์–ด ๋ฒ„๋ฆฌ๊ณ  ๋ฐ์ดํ„ฐ๊ฐ€์œ ์‹ค๋˜๊ฑฐ๋‚˜ ๋ฆฌ์†Œ์Šค๊ฐ€ ์ œ๋Œ€๋กœ ๋‹ซํžˆ์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

kill์„ ํ•  ๋• signal์„ ์ค„ ์ˆ˜ ์žˆ๋Š”๋ฐ ๋ชฉ๋ก์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

$ kill -l

1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR

 

 

ํ•˜์ง€๋งŒ 15 - SIGTERM์„ ์‚ฌ์šฉํ•˜๋ฉด ํ”„๋กœ์„ธ์Šค๋ฅผ ์ •์ƒ ์ข…๋ฃŒ ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ, ํ”„๋กœ์„ธ์Šค๊ฐ€ ํ˜„์žฌ ์‚ฌ์šฉ์ค‘์ธ ๋ฆฌ์†Œ์Šค๋ฅผ ํ•ด์ œํ•˜๊ณ , ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ๋“ฑ ์•ˆ์ „ํ•˜๊ฒŒ ๋ชจ๋“  ์ž‘์—…์„ ์™„๋ฃŒํ•  ๋•Œ ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์ค€๋‹ค. 

 

 

๋‚˜์˜ ๊ฒฝ์šฐ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ง์ ‘ ์ฃฝ์ด์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— PASS~

 

 

 

 

 

 

 

2.2 Spring Boot ์ข…๋ฃŒ ์‹œ Graceful Shutdown

Graceful Shutdown์€ Spring Boot 2.3.0 Release ๋ถ€ํ„ฐ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค. 

 

์ด๋ฅผ ํ™œ์„ฑํ™” ํ•˜๋ฉด ์Šคํ”„๋ง ๋ถ€ํŠธ๋Š” ์ข…๋ฃŒ ์š”์ฒญ์ด ๋“ค์–ด์˜จ ์ดํ›„๋ก  ๋” ์ด์ƒ ์ƒˆ๋กœ์šด ์š”์ฒญ์„ ๋ฐ›์ง€ ์•Š์œผ๋ฉด์„œ, ์ฒ˜๋ฆฌ ์ค‘์ธ ์š”์ฒญ์€ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•œ ๋’ค ํ”„๋กœ์„ธ์Šค๋ฅผ ์ข…๋ฃŒํ•˜๊ฒŒ ๋œ๋‹ค. 

server:
  shutdown: graceful

 

 

๊ธฐ๋ณธ์ ์œผ๋กœ 30s ๋™์•ˆ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์ด๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค. 

spring:
  lifecycle:
    timeout-per-shutdown-phase: 100s

 

 

 

 

 

 

 

 

2.3 Docker ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ข…๋ฃŒํ•  ๋•Œ

๋‚˜๊ฐ™์€ ๊ฒฝ์šฐ์—” docker-compose๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์Šคํ”„๋ง ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋„์šฐ๊ณ  ์ข…๋ฃŒํ•˜๊ณ ์žˆ๋‹ค. 

 

2.2 ๋ฐฉ๋ฒ• ์ฒ˜๋Ÿผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‹จ์—์„œ graceful shutdown ์„ค์ •์„ ํ•ด๋’€์ง€๋งŒ ์ปจํ…Œ์ด๋„ˆ ์ข…๋ฃŒ ์‹œ์—” ์„ค์ •์ด ๋จนํžˆ์งˆ ์•Š์•˜๋‹ค. ์œ„์—์„œ ์–ธ๊ธ‰์€ ํ•˜์ง€ ์•Š์•˜์ง€๋งŒ graceful shudown์ด ์„ค์ •๋˜์–ด ์žˆ๋”๋ผ๋„ kill -9๋กœ ์ข…๋ฃŒ ์‹œ ์ฆ‰์‹œ ์ข…๋ฃŒ ๋œ๋‹ค. 

 

 

 

ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด 1๋ถ„ ๊ฐ„ Thread.sleep์„ ํ•˜๋Š” ํ…Œ์ŠคํŠธ api๋ฅผ ์ž‘์„ฑํ•ด๋’€๋‹ค. 

@RestController
@Slf4j
public class TestController {

    @GetMapping("/test")
    public String test() throws InterruptedException {
        log.info("์‹œ์ž‘");
        Thread.sleep(60000);
        log.info("์ข…๋ฃŒ");
        return "๋ฐฐํฌ ์™„๋ฃŒ";
    }
}

 

 

 

 

application.yml ์„ค์ •์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 100s

 

 

 

 

docker compose ๋กœ ์Šคํ”„๋ง ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ๋™ํ•˜๊ณ , curl๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋ณด์•˜๋‹ค. ์ดํ›„ ๋ฐ”๋กœ docker compose down์œผ๋กœ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋‚ด๋ ธ๊ณ  ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค. 

$ docker compose up 

$ curl http://IP:8080/test  # ์š”์ฒญ ์‹œ์ž‘

$ docker compose down

 

 

 

graceful shutdown ๋กœ๊ทธ๊ฐ€ ์ฐํžˆ๋ฉฐ ๊ธฐ๋‹ค๋ฆฌ๋Š”๊ฐ€ ์‹ถ๋”๋‹ˆ ์ข…๋ฃŒ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋ฐ”๋กœ ์ข…๋ฃŒ๋˜์—ˆ๋‹ค. 

 

 

 

 

 

docker compose down ๋ช…๋ น์–ด๊ฐ€ ์‹คํ–‰๋˜๋ฉด ๋‚ด๋ถ€์ ์œผ๋กœ ์ปจํ…Œ์ด๋„ˆ STOP ํ›„ REMOVE ์ž‘์—…์„ ์ง„ํ–‰ํ•œ๋‹ค. 

์ด๋•Œ docker stop ์€ graceful ํ•œ ์ข…๋ฃŒ๋ฅผ ์ง€์›ํ•ด์ฃผ๋Š”๋ฐ ์•„๋ž˜์™€ ๊ฐ™์ด -t ์˜ต์…˜์„ ์‚ฌ์šฉํ•ด ๋ช‡ ์ดˆ๊ฐ„ ๋Œ€๊ธฐํ•  ์ง€ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹จ์œ„๋Š” s์ด๋‹ค. 

docker stop -t 100 {contianer_id}

 

 

 

 

์š”์ฒญ ์ฒ˜๋ฆฌ ์ค‘ ๋ช…๋ น์–ด๋ฅผ ๋‚ ๋ฆฌ๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ๋Œ€๊ธฐํ•˜๋‹ค๊ฐ€ 1๋ถ„ ํ›„ ๋ชจ๋“  ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋๋‚˜๋ฉด ์ •์ƒ ์ข…๋ฃŒํ•˜๋Š” ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

 

 

 

 

๊ทธ๋ฆฌ๊ณ  shutdown ๋„์ค‘์— ๋‹ค๋ฅธ ์—”๋“œํฌ์ธํŠธ๋กœ ์š”์ฒญ์„ ๋ณด๋ƒˆ์„ ๋• ์—ฐ๊ฒฐ ์‹คํŒจํ•˜์˜€๊ณ , graceful ์ข…๋ฃŒ ๋Œ€๊ธฐ ์ค‘์—” ๋‹ค๋ฅธ ์š”์ฒญ์„ ๋ฐ›์ง€ ์•Š๋Š” ๋‹ค๋Š” ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. 

    @GetMapping("/test2")
    public String test2() throws InterruptedException {
        log.info("test2 ์ง„์ž…");
        return "์ฒ˜๋ฆฌ ์™„๋ฃŒ";
    }

 

 

 

 

 

 

 

์œ„ ์ฒ˜๋Ÿผ  docker stop ๋ช…๋ น์–ด์—  -t ์˜ต์…˜์„ ๋„ฃ์„ ์ˆ˜๋„ ์žˆ์ง€๋งŒ docker compose ํŒŒ์ผ์—์„œ ๋ฐ”๋กœ ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. 

version: "3.8"
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ./log:/log
    ports:
      - "8080:8080"
    restart: always
    stop_grace_period: 100s  # ๋Œ€๊ธฐ ์‹œ๊ฐ„ ์„ค์ •

 

docker-compose ์—์„œ ํ•˜๋Š” graceful ์‹œ๊ฐ„์„ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ -t๋กœ ์ข…๋ฃŒํ•˜๋ ค๋ฉด deploy.sh ๊นŒ์ง€ ์ˆ˜์ •ํ•ด์ค˜์•ผ ํ–ˆ๋‹ค.

 

docker compose down๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ stop -> remove ์ด๊ธด ํ•˜์ง€๋งŒ stop ์‹œ์— ์˜ต์…˜์„ ๋ถ™์—ฌ ์ค„ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ํ•˜๋ ค๋ฉด docker compose stop, docker compose remove ๋กœ ๋‚˜๋ˆ  ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. 

 

 

๊ท€์ฐฎ๊ธฐ ๋•Œ๋ฌธ์— ๋‚œ compose์—์„œ ์„ค์ •ํ•ด๋’€๋‹ค.. ใ…Žใ…Ž 

 

 

 

 

 

 

 

3. ๊ฒฐ๊ณผ

Blue/Green ๋ฐฉ์‹์—์„œ ๊ธฐ์กด ๊ตฌ๋ฒ„์ „์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ข…๋ฃŒ์‹œํ‚ฌ ๋•Œ๋„ ๊ณ ๋ คํ•  ์‚ฌํ•ญ์ด ์žˆ์—ˆ๋‹ค. ์ด๊ฒƒ ๋ง๊ณ ๋„ ๋‚ด๊ฐ€ ๋†“์น˜๊ณ  ์žˆ๋Š” ๋˜ ๋‹ค๋ฅธ ๋ฌด์–ธ๊ฐ€๊ฐ€ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์ง€๋งŒ ์ฐจ๊ทผ์ฐจ๊ทผ ์•Œ์•„๊ฐˆ ์ƒ๊ฐ์ด๋‹ค.

 

์ด๋ฐ–์—๋„ Nginx์—์„œ ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ๋ฅผ ๋ณ€๊ฒฝ ํ›„ ์žฌ๋กœ๋”ฉ ํ•˜๋Š” ๋ฐ์—๋„ ์ •๋ง ์งง์ง€๋งŒ ์‹œ๊ฐ„์ด ์†Œ์š”๋˜๋Š” ๊ฒƒ์œผ๋กœ ์•Œ๊ณ  ์žˆ๋‹ค. 

์ด ๋ถ€๋ถ„๋„ ๋ฌธ์ œ๊ฐ€ ๋œ๋‹ค๋ฉด ๋ฌธ์ œ๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค. 

 

์œ„ ๋ฐฉ์‹์€ ์‚ฌ์‹ค ์ข…๋ฃŒ๋  ๋•Œ ๊นŒ์ง€ ์„ค์ •ํ•œ ์‹œ๊ฐ„ ๋งŒํผ ๊ธฐ๋‹ค๋ ค ์ฃผ๋Š” ๊ฒƒ ๋ฟ, ์š”์ฒญ ์ฒ˜๋ฆฌ์— ๊ทธ ์ด์ƒ์˜ ์‹œ๊ฐ„์ด ์†Œ์š”๋œ๋‹ค๋ฉด ์ด๊ฑด ๋ณด์žฅํ•  ์ˆ˜ ์—†๋‹ค. ํ•˜์ง€๋งŒ ๊ทธ ์ด์ƒ์ด ์†Œ์š”๋˜๋Š” ๊ฒƒ ๋ถ€ํ„ฐ ๋ฌธ์ œ๊ฐ€ ์•„๋งˆ ์žˆ์ง€ ์•Š์„๊นŒ..? 

 

๋ฐฐ์น˜ ๋กœ์ง์— ๋Œ€ํ•ด์„œ๋„ ๊ณ ๋ฏผํ•œ ์ ์ด ์žˆ๋‹ค. ์Šค์ผ€์ค„๋ง์„ ํ†ตํ•ด ๋ฐฐ์น˜ ๋กœ์ง์„ ์‹คํ–‰ ์‹œํ‚ค๊ณ  ์žˆ์—ˆ๋‹ค๋ฉด 100์ดˆ? ๋ณด๋‹ค ๋” ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์ง€ ์•Š์„๊นŒ? ํ–ˆ๋Š”๋ฐ, ์• ์ดˆ์— ๊ทธ๋Ÿฐ ๋ฐฐ์น˜ ๋กœ์ง์ด BLUE/GREEN ๋ฐฐํฌ์— ๋ฌถ์—ฌ ์žˆ์œผ๋ฉด ์•ˆ ๋œ๋‹ค๊ณ  ํ•œ๋‹ค. 

 

 

 

์•„๋ฌดํŠผ, ์ž‘์—… ์ค‘์ธ ํ”„๋กœ์„ธ์Šค ์ฒ˜๋ฆฌ๋ฅผ ๊ธฐ๋‹ค๋ฆฐ ํ›„ ์ข…๋ฃŒํ•˜๊ธฐ ์œ„ํ•ด ๋ณ€๊ฒฝ ์‹œํ‚จ ๋ถ€๋ถ„์„ ์ •๋ฆฌํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค. 

 

1. application.yml์— graceful ์„ค์ •

spring:
  # ์ข…๋ฃŒ ์ „ ์ฒ˜๋ฆฌ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค 100์ดˆ๊ฐ„ ๋Œ€๊ธฐ
  lifecycle:
    timeout-per-shutdown-phase: 100s

# ์ž‘์—… ์ค‘์ธ ํ”„๋กœ์„ธ์Šค ์ฒ˜๋ฆฌ ํ›„ ์ข…๋ฃŒ
server:
  shutdown: graceful

 

 

2. docker-compose ํŒŒ์ผ์—์„œ graceful ์„ค์ •

version: "3.8"
services:
  app:
    stop_grace_period: 100s # garceful ์ข…๋ฃŒ