GitHub Actions + Docker + NGINX๋ฅผ ํ™œ์šฉํ•œ Blue/Green ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ ํ™˜๊ฒฝ ๊ตฌ์ถ•

2025. 5. 4. 21:59ยทProject

๋“ค์–ด๊ฐ€๋ฉฐ

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

 

์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด, ๋ฐฐํฌ ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋‹ค์šดํƒ€์ž„์„ ์ตœ์†Œํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ๊ณ ๋ฏผํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ๋ฐฐํฌ ์ „๋žต์„ ์‚ดํŽด๋ณธ ๊ฒฐ๊ณผ, ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ ๋ฐฉ์‹์„ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ํšจ๊ณผ์ ์ด๋ผ ํŒ๋‹จํ•˜์˜€๊ณ , ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด GitHub Actions์™€ Blue-Green ๋ฐฐํฌ ๋ฐฉ์‹์„ ๋„์ž…ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

 

CI/CD ๋„๊ตฌ ์„ ์ • ์ด์œ 

๋ฐฐํฌ ์ž๋™ํ™”๋ฅผ ๊ตฌํ˜„ํ•˜๋Š”๋ฐ ์žˆ์–ด, Jenkins์™€ GitHub Actions ๋‘๊ฐ€์ง€ ์˜ต์…˜์„ ๋น„๊ตํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

Jenkins๋Š” ๋‹ค์–‘ํ•œ ์–ธ์–ด์™€ ์†Œ์Šค์ฝ”๋“œ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ๋Œ€ํ•ด ์ง€์†์ ์ธ ํ†ตํ•ฉ(CI)๊ณผ ๋ฐฐํฌ(CD)๋ฅผ ์ง€์›ํ•˜๋ฉฐ, ์ž์œ ๋„๊ฐ€ ๋†’๊ณ  ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์ด ์šฉ์ดํ•œ ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, Jenkins๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ณ„๋„์˜ ์„œ๋ฒ„๋ฅผ ๊ตฌ์ถ•ํ•˜๊ณ  ๊ด€๋ฆฌํ•ด์•ผ ํ•˜๊ณ , ์ดˆ๊ธฐ ์„ค์ •์ด ๋‹ค์†Œ ๋ฌด๊ฒ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋ฐ˜๋ฉด, GitHub Actions๋Š” GitHub์—์„œ ๊ณต์‹์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ์›Œํฌํ”Œ๋กœ์šฐ ์ž๋™ํ™” ๋„๊ตฌ๋กœ, ๋ณ„๋„์˜ ์ธํ”„๋ผ๋ฅผ ๊ตฌ์„ฑํ•  ํ•„์š”์—†์ด ์ฝ”๋“œ ํ‘ธ์‹œ, PR, Release ๋“ฑ GitHub๋‚ด์˜ ์ด๋ฒคํŠธ์— ๋ฐ˜์‘ํ•˜์—ฌ ์ž๋™์œผ๋กœ ๋นŒ๋“œ ๋ฐ ๋ฐฐํฌ ์ž‘์—…์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ๋Š” ๊ทœ๋ชจ๊ฐ€ ํฌ์ง€ ์•Š๊ณ , ๋ณต์žกํ•œ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์ด ํ•„์š”ํ•˜์ง€ ์•Š์€ ์ƒํ™ฉ์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋ณด๋‹ค ๊ฐ„๋‹จํ•˜๊ณ  ์„ค์ •์ด ๋น ๋ฅธ GitHub Actions๋ฅผ ์„ ํƒํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

 

๋ฐฐํฌ ์ „๋žต ์„ ์ • ์ด์œ 

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

 

Rolling ๋ฐฐํฌ

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

 

Canary ๋ฐฐํฌ

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

 

Blue-Green ๋ฐฐํฌ

Blue-Green ๋ฐฐํฌ ๋ฐฉ์‹์€ ๊ธฐ์กด ์„œ๋น„์Šค(Blue)์™€ ์ƒˆ๋กœ์šด ๋ฒ„์ „(Green)์„ ๋™์‹œ์— ๊ตฌ๋™์‹œํ‚จ ๋’ค, ๊ธฐ๋Šฅ ๊ฒ€์ฆ์„ ๋งˆ์น˜๊ณ  ๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ๋ฅผ ํ†ตํ•ด ํŠธ๋ž˜ํ”ฝ์„ Green์œผ๋กœ ์ผ๊ด„ ์ „ํ™˜ํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋”๋ผ๋„ ๋‹ค์‹œ Blue๋กœ ๋น ๋ฅด๊ฒŒ ์ „ํ™˜์ด ๊ฐ€๋Šฅํ•ด ์•ˆ์ „์„ฑ๊ณผ ๋ณต๊ตฌ ๊ฐ€๋Šฅ์„ฑ์ด ๊ฐ€์žฅ ๋†’์€ ๋ฐฉ์‹์ด๋ผ๊ณ  ํŒ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๋ฌด์—‡๋ณด๋‹ค Blue-Green ๋ฐฐํฌ ์ „๋žต ์ž์ฒด๊ฐ€ ๊ฐ„๋‹จํ•˜๊ณ  ๋ช…ํ™•ํ•˜์—ฌ, ๊ธฐ์กด์˜ ์ž๋™ํ™” ํŒŒ์ดํ”„๋ผ์ธ๊ณผ ํ†ตํ•ฉํ•˜๊ธฐ ์‰ฌ์šด ๋ฐฉ๋ฒ•์ด๋ผ ํŒ๋‹จํ•ด ํ•ด๋‹น ๋ฐฉ์‹์„ ์ตœ์ข…์ ์œผ๋กœ ์„ ํƒํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

 

EC2 ์„œ๋ฒ„ ํŒŒ์ผ ๊ตฌ์กฐ

/wegotoo                       # ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๋””๋ ‰ํ† ๋ฆฌ
โ”‚
โ”œโ”€โ”€ /nginx                     # Nginx ๊ด€๋ จ ์„ค์ • ๋””๋ ‰ํ† ๋ฆฌ
โ”‚   โ”œโ”€โ”€ /conf                  # Nginx ์„ค์ • ํŒŒ์ผ ๋””๋ ‰ํ† ๋ฆฌ
โ”‚   โ”‚   โ””โ”€โ”€ wegotoo.conf       # Nginx ์„œ๋ฒ„ ์„ค์ • ํŒŒ์ผ
โ”‚   โ”‚
โ”‚   โ””โ”€โ”€ docker-compose.yml     # Nginx ๋ฐ ๊ธฐํƒ€ ์„œ๋น„์Šค ์ปจํ…Œ์ด๋„ˆ ์„ค์ • ํŒŒ์ผ
โ”‚
โ””โ”€โ”€ deploy.sh                  # ๋ฐฐํฌ ์ž๋™ํ™” ์Šคํฌ๋ฆฝํŠธ

 

NGINX

NGINX๋ฅผ ์‚ฌ์šฉํ•œ ์ด์œ ๋Š” ํŠธ๋ž˜ํ”ฝ ์ „ํ™˜์„ ์•ˆ์ „ํ•˜๊ณ  ๋น ๋ฅด๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

 

Blue-Green ๋ฐฐํฌ์˜ ํ•ต์‹ฌ์€ ํ˜„์žฌ ์šด์˜์ค‘์ธ ์ธ์Šคํ„ด์Šค์™€ ์ƒˆ ์ธ์Šคํ„ด์Šค๋ฅผ ๋™์‹œ์— ๋„์›Œ๋†“๊ณ , ํŠธ๋ž˜ํ”ฝ๋งŒ ์ „ํ™˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋•Œ ํŠธ๋ž˜ํ”ฝ์„ ์–ด๋–ป๊ฒŒ ์ „ํ™˜ํ•  ๊ฒƒ์ธ๊ฐ€๊ฐ€ ํ•ต์‹ฌ์ธ๋ฐ, ๊ทธ ์—ญํ• ์„ NGINX์˜ ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

docker-compose.yml

services: 
    nginx:
        image: nginx:latest
        container_name: nginx
        ports:
            - 80:80
        volumes:
            - ./conf:/etc/nginx/conf.d

์ด ์„ค์ • ํŒŒ์ผ์€ ๋กœ์ปฌ ๋””๋ ‰ํ† ๋ฆฌ ./conf์— ์žˆ๋Š” ํŒŒ์ผ๋“ค์„ NGINX ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์˜ /etc/nginx/conf.d ๊ฒฝ๋กœ์— ๋งˆ์šดํŠธํ•˜๋„๋ก ๊ตฌ์„ฑํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ฆ‰, NGINX๊ฐ€ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์„ค์ • ํŒŒ์ผ์„ ํ˜ธ์ŠคํŠธ ์‹œ์Šคํ…œ์—์„œ ๊ด€๋ฆฌํ•˜๊ณ  ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.

 

wegotoo.conf

server {
    listen 80;
    listen [::]:80;
    server_name api.wegotoo.net;

    location / {
        proxy_pass http://172.17.0.1:8081;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
    }
}

์ด ์„ค์ • ํŒŒ์ผ์€ api.wegotoo.net์œผ๋กœ ๋“ค์–ด์˜ค๋Š” ๋ชจ๋“  ์š”์ฒญ์„ Docker ํ˜ธ์ŠคํŠธ์˜ ํŠน์ • ํฌํŠธ๋กœ ์ „๋‹ฌํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. NGINX ์ปจํ…Œ์ด๋„ˆ๋Š” ์ด ์„ค์ • ํŒŒ์ผ์„ ์ฝ์–ด, ์š”์ฒญ์„ ์–ด๋””๋กœ ์ „๋‹ฌํ• ์ง€(๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ ๋Œ€์ƒ)๋ฅผ ๊ฒฐ์ •ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

deploy.sh

์ด ์ฝ”๋“œ๋Š” Docker์™€ NGINX๋ฅผ ํ™œ์šฉํ•œ Blue-Green ๋ฐฐํฌ ์ž๋™ํ™” ์Šคํฌ๋ฆฝํŠธ์ž…๋‹ˆ๋‹ค. ๊ธฐ์กด ์ปจํ…Œ์ด๋„ˆ์™€ ์ƒˆ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋ฒˆ๊ฐˆ์•„๊ฐ€๋ฉฐ ์‚ฌ์šฉํ•˜๋ฉฐ, ๋ฌด์ค‘๋‹จ์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ด๋ฆ„ ๋ฐ ํฌํŠธ ์„ค์ •
APP_NAME="wegotoo"
BLUE_PORT=8081
GREEN_PORT=8082

# ์ปจํ…Œ์ด๋„ˆ ์ด๋ฆ„ ์„ค์ •
BLUE_CONTAINER="wegotoo_blue"
GREEN_CONTAINER="wegotoo_green"

# ํ—ฌ์Šค ์ฒดํฌ๋ฅผ ์œ„ํ•œ URL ๊ธฐ๋ณธ ์ฃผ์†Œ
HEALTH_CHECK_URL="http://localhost"

echo "1๏ธโƒฃ ํ˜„์žฌ ์‹คํ–‰์ค‘์ธ ์ปจํ…Œ์ด๋„ˆ ํ™•์ธ ์ค‘..."
# ํ˜„์žฌ ์‹คํ–‰ ์ค‘์ธ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ BLUE์ธ์ง€ ํ™•์ธ
if docker ps --format '{{.Names}}' | grep -q "$BLUE_CONTAINER"; then
    ACTIVE=$BLUE_CONTAINER
    ACTIVE_PORT=$BLUE_PORT

    IDLE=$GREEN_CONTAINER
    IDLE_PORT=$GREEN_PORT
else 
    ACTIVE=$GREEN_CONTAINER
    ACTIVE_PORT=$GREEN_PORT

    IDLE=$BLUE_CONTAINER
    IDLE_PORT=$BLUE_PORT
fi

echo "2๏ธโƒฃ $IDLE ์ปจํ…Œ์ด๋„ˆ์— ์ตœ์‹  ์ด๋ฏธ์ง€๋กœ ๋ฐฐํฌ ์‹œ์ž‘..."
# ์ด๋ฏธ์ง€ ์ด๋ฆ„ ์„ค์ •
IMAGE="$APP_NAME/prod:latest"

# ๊ธฐ์กด IDLE ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์‹คํ–‰์ค‘์ด๋ฉด ์ข…๋ฃŒ
docker rm -f $IDLE || true
# ์ƒˆ ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰
docker run -d --name $IDLE -p $IDLE_PORT:8080 $IMAGE

echo "3๏ธโƒฃ ํ—ฌ์Šค ์ฒดํฌ ์‹œ์ž‘..."
# ์ƒˆ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ •์ƒ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธ
for i in {1..10}; do
    sleep 5
    STATUS=$(curl -s "$HEALTH_CHECK_URL:$IDLE_PORT/health-check" | jq -r '.status')
    if [ "$STATUS" == "OK" ]; then
        echo "โœ… ํ—ฌ์Šค ์ฒดํฌ ์„ฑ๊ณต!"
        break
    fi
    if [ $i -eq 10 ]; then
        echo "โŒ ํ—ฌ์Šค ์ฒดํฌ ์‹คํŒจ! ๋กค๋ฐฑํ•ฉ๋‹ˆ๋‹ค."
        docker rm -f $IDLE || true
        exit 1
    fi
done

echo "4๏ธโƒฃ NGINX ์„ค์ • ํŒŒ์ผ ํฌํŠธ ์Šค์œ„์นญ ์ค‘..."
NGINX_CONTAINER_NAME="nginx"
# NGINX ์„ค์ • ํŒŒ์ผ์—์„œ ํฌํŠธ๋ฅผ ACTIVE → IDLE๋กœ ๋ณ€๊ฒฝ
docker exec $NGINX_CONTAINER_NAME sed -i "s/$ACTIVE_PORT/$IDLE_PORT/g" /etc/nginx/conf.d/wegotoo.conf
# NGINX ์žฌ์‹œ์ž‘ (๋ฆฌ๋กœ๋“œ)
docker exec $NGINX_CONTAINER_NAME nginx -s reload

echo "5๏ธโƒฃ ์ด์ „ ACTIVE ์ปจํ…Œ์ด๋„ˆ ์ข…๋ฃŒ..."
# ์ด์ „ ACTIVE ์ปจํ…Œ์ด๋„ˆ ์ข…๋ฃŒ
docker rm -f $ACTIVE || true

echo "โœ… ๋ฐฐํฌ ์™„๋ฃŒ! ํ˜„์žฌ ์„œ๋น„์Šค ํฌํŠธ๋Š” $IDLE_PORT ์ž…๋‹ˆ๋‹ค."
  1. ํ˜„์žฌ ์‹คํ–‰์ค‘์ธ ์ปจํ…Œ์ด๋„ˆ ํŒŒ์•…
    • ํ˜„์žฌ ์‹คํ–‰์ค‘์ธ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ BLUE์ธ์ง€ GREEN์ธ์ง€ ํŒŒ์•…ํ•ฉ๋‹ˆ๋‹ค.
    • ๊ทธ์— ๋”ฐ๋ผ ACTIVE(ํ˜„์žฌ ์„œ๋น„์Šค ์ค‘), IDLE(๋Œ€๊ธฐ ์ค‘)๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  2. IDLE ์ปจํ…Œ์ด๋„ˆ์— ์ตœ์‹  ์ด๋ฏธ์ง€๋กœ ๋ฐฐํฌ ์‹œ์ž‘
    • ์ด์ „ IDLE ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์‹คํ–‰์ค‘์ผ ์ˆ˜๋„ ์žˆ๋Š” ์ƒํ™ฉ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ œ๊ฑฐํ•˜๊ณ  ์ตœ์‹  ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•ด ์ƒˆ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  3. ํ—ฌ์Šค ์ฒดํฌ
    • ์ง€์ •ํ•œ ํ—ฌ์Šค ์ฒดํฌ ์—”๋“œ ํฌ์ธํŠธ๋ฅผ ํ†ตํ•ด ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ์ตœ๋Œ€ 10ํšŒ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
    • ์„ฑ๊ณต ์‹œ ๋ฃจํ”„๋ฅผ ๋น ์ ธ ๋‚˜์˜ค๊ณ , ์‹คํŒจ ์‹œ ๋ฐฐํฌ๋ฅผ ์ค‘๋‹จํ•ฉ๋‹ˆ๋‹ค.
  4. NGINX ์„ค์ • ์Šค์œ„์นญ
    • ํ˜„์žฌ ์„œ๋น„์Šค ์ค‘์ธ ํฌํŠธ๋ฅผ ์ƒˆ ์ปจํ…Œ์ด๋„ˆ์˜ ํฌํŠธ๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
    • ์ด ์„ค์ •์€ NGINX ๋‚ด๋ถ€์˜ wegotoo.conf ํŒŒ์ผ์„ ์ˆ˜์ •ํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.
    • ์ดํ›„ NGINX๋ฅผ reloadํ•˜์—ฌ ์„ค์ •์„ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.
  5. ์ด์ „ ์ปจํ…Œ์ด๋„ˆ ์ •๋ฆฌ
    • ์ด์ „ ์ปจํ…Œ์ด๋„ˆ(ACTIVE)๋ฅผ ์ข…๋ฃŒ ๋ฐ ์‚ญ์ œํ•˜์—ฌ ๋ฆฌ์†Œ์Šค๋ฅผ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

 

GitHub Actions

GitHub Actions workflows ํ๋ฆ„

  1. ์ฝ”๋“œ๊ฐ€ main ๋ธŒ๋žœ์น˜์— push ๋˜๋ฉด, GitHub Actions๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ ๋ฉ๋‹ˆ๋‹ค.
  2. Gradle์„ ํ†ตํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋นŒ๋“œํ•˜๊ณ , Docker ์ด๋ฏธ์ง€๋ฅผ Docker Hub์— push ํ•ฉ๋‹ˆ๋‹ค.
  3. EC2 ์„œ๋ฒ„๋กœ SSH ์ ‘์† ํ›„, deploy.sh ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•˜์—ฌ Blue-Green ๋ฐฐํฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  4. ๋ฐฐํฌ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด NGINX ์„ค์ •์„ ๋ณ€๊ฒฝํ•˜์—ฌ ํŠธ๋ž˜ํ”ฝ์„ ์ƒˆ๋กœ์šด ์ปจํ…Œ์ด๋„ˆ๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

wegotoo_prod.yml

name: wegotoo_prod  

on:  
  push:  
    branches: [ "main" ]  
  pull_request:  
    branches: [ "main" ]  

jobs:  
  develop:  
    # ์‹คํ–‰ ํ™˜๊ฒฝ  
    runs-on: ubuntu-22.04  
    steps:  
      - name: Checkout  
        uses: actions/checkout@v3  

      # JDK 17  
      - name: Set up JDK 17  
        uses: actions/setup-java@v3  
        with:  
          java-version: '17'  
          distribution: 'temurin'  

      # Gradle Caching  
      - name: Gradle Caching  
        uses: actions/cache@v3  
        with:  
          path: |  
            ~/.gradle/caches  
            ~/.gradle/wrapper  
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}  
          restore-keys: |  
            ${{ runner.os }}-gradle-  

      # application-prod.yml  
      - name: Copy prod Secret  
        env:  
          DEV_SECRET: ${{ secrets.APPLICATION_PROD_YML }}  
          DEV_SECRET_DIR: src/main/resources  
          DEV_SECRET_DIR_FILE_NAME: application-prod.yml  
        run: echo $DEV_SECRET | base64 --decode >> $DEV_SECRET_DIR/$DEV_SECRET_DIR_FILE_NAME  

      # application-jwt.yml  
      - name: Copy jwt Secret  
        env:  
          DEV_SECRET: ${{ secrets.APPLICATION_JWT_YML }}  
          DEV_SECRET_DIR: src/main/resources  
          DEV_SECRET_DIR_FILE_NAME: application-jwt.yml  
        run: echo $DEV_SECRET | base64 --decode >> $DEV_SECRET_DIR/$DEV_SECRET_DIR_FILE_NAME  

      # application-oauth.yml  
      - name: Copy oauth Secret  
        env:  
          DEV_SECRET: ${{ secrets.APPLICATION_OAUTH_YML }}  
          DEV_SECRET_DIR: src/main/resources  
          DEV_SECRET_DIR_FILE_NAME: application-oauth.yml  
        run: echo $DEV_SECRET | base64 --decode >> $DEV_SECRET_DIR/$DEV_SECRET_DIR_FILE_NAME  

      # ./gradlew ๊ถŒํ•œ ์„ค์ •  
      - name: ./gradlew ๊ถŒํ•œ ์„ค์ •  
        run: chmod +x ./gradlew  

      # Gradle Build  
      - name: Build with Gradle  
        run: |  
          ./gradlew clean  
          ./gradlew compileJava  
          ./gradlew build  

      # Docker Buildํ•˜๊ณ  DockerHub์— Push      - name: Docker Build & Push to DockerHub  
        run: |  
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}  
          docker build -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_PROD_REPO }}:latest .  
          docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_PROD_REPO }}:latest  

      # GitHub IP๋ฅผ ์š”์ฒญ  
      - name: Get GitHub IP  
        id: ip  
        uses: haythem/public-ip@v1.2  

      # AWS ์„ธํŒ…  
      - name: AWS Setting  
        uses: aws-actions/configure-aws-credentials@v1  
        with:  
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}  
          aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }}  
          aws-region: ap-northeast-2  

      # GitHub IP๋ฅผ AWS์— ์ถ”๊ฐ€  
      - name: Add GitHub IP to AWS  
        run: |  
          aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32  

      # SSH Key๋กœ ์„œ๋ฒ„์— ์ ‘์†ํ•˜๊ณ  deploy.sh ์‹คํ–‰
      - name: Run deploy.sh on EC2
        uses: appleboy/ssh-action@v0.1.6  
        with:  
          host: ${{ secrets.EC2_PROD_HOST }}  
          username: ${{ secrets.EC2_USERNAME }}  
          key: ${{ secrets.KEY }}  
          port: 22  
          script: |  
            cd wegotoo  
            chmod +x deploy.sh
            ./deploy.sh

      # Security Group์—์„œ Github IP๋ฅผ ์‚ญ์ œ  
      - name: Remove Github IP From Security Group  
        run: |  
          aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32

 

๊ฒฐ๊ณผ

์‹คํ–‰ ์ „

์ปจํ…Œ์ด๋„ˆ ์ƒํƒœ

์‚ฌ์šฉ์ค‘์ธ ํฌํŠธ: 8081 / ์ปจํ…Œ์ด๋„ˆ ์ด๋ฆ„: wegotoo_blue

NGINX ์„ค์ • ํŒŒ์ผ

:8081๋กœ ํŠธ๋ž˜ํ”ฝ ์ „๋‹ฌ

GitHub Actions๋ฅผ ํ†ตํ•ด ์œ„ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „์—๋Š” wegotoo_blue ์ปจํ…Œ์ด๋„ˆ๊ฐ€ :8081๋ฒˆ ํฌํŠธ์—์„œ ์„œ๋น„์Šค ์ค‘์ด๊ณ , NGINX ์„ค์ • ํŒŒ์ผ ์—ญ์‹œ proxy_pass http://172.17.0.1:8081;๋กœ ์ง€์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. 

 

์‹คํ–‰ ํ›„

์ปจํ…Œ์ด๋„ˆ ์ƒํƒœ

์‚ฌ์šฉ์ค‘์ธ ํฌํŠธ: 8082 / ์ปจํ…Œ์ด๋„ˆ ์ด๋ฆ„: wegotoo_green

NGINX ์„ค์ • ํŒŒ์ผ

:8082๋กœ ๋ณ€๊ฒฝ๋จ

์‹คํ–‰๋œ ํ›„์—๋Š” ์ƒˆ๋กญ๊ฒŒ ๊ธฐ๋™๋œ wegotoo_green ์ปจํ…Œ์ด๋„ˆ๊ฐ€ :8082๋ฒˆ ํฌํŠธ์—์„œ ์ •์ƒ์ ์œผ๋กœ ๊ตฌ๋™๋œ ์ดํ›„, NGINX ์„ค์ •์ด ์ž๋™์œผ๋กœ proxy_pass http://172.17.0.1:8082;๋กœ ์ „ํ™˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ๊ฒฐ๊ณผ ํŠธ๋ž˜ํ”ฝ์ด ๊ธฐ์กด์˜ wegotoo_blue ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์•„๋‹Œ wegotoo_green ์ปจํ…Œ์ด๋„ˆ๋กœ ๋ฌธ์ œ ์—†์ด ์ด๋™ํ•จ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. 

'Project' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

์ฑ„ํŒ… ๋ฉ”์„ธ์ง€ ์ฝ์Œ ์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ ๊ตฌ์กฐ ๊ฐœ์„ ๊ธฐ  (0) 2024.12.18
Embedded Mongo/Redis ์ ์šฉํ•˜๊ธฐ  (0) 2024.11.26
๋ณ„์  ์ค‘๋ณต ์ƒ์„ฑ ๋™์‹œ์„ฑ ์ด์Šˆ ํ•ด๊ฒฐ  (0) 2024.10.27
CompletableFuture๋ฅผ ํ™œ์šฉํ•œ ์„ฑ๋Šฅ ๊ฐœ์„ ๊ธฐ  (0) 2024.08.01
'Project' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • ์ฑ„ํŒ… ๋ฉ”์„ธ์ง€ ์ฝ์Œ ์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ ๊ตฌ์กฐ ๊ฐœ์„ ๊ธฐ
  • Embedded Mongo/Redis ์ ์šฉํ•˜๊ธฐ
  • ๋ณ„์  ์ค‘๋ณต ์ƒ์„ฑ ๋™์‹œ์„ฑ ์ด์Šˆ ํ•ด๊ฒฐ
  • CompletableFuture๋ฅผ ํ™œ์šฉํ•œ ์„ฑ๋Šฅ ๊ฐœ์„ ๊ธฐ
jwooo๐ŸŒฑ
jwooo๐ŸŒฑ
jwooo's log ์ž…๋‹ˆ๋‹ค.
  • jwooo๐ŸŒฑ
    jwooo's log
    jwooo๐ŸŒฑ
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (11)
      • Java (4)
      • Project (5)
      • Computer Science (2)
        • Network (1)
        • Security (1)
  • ๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

    • ํ™ˆ
    • ๋ฐฉ๋ช…๋ก
  • ๋งํฌ

  • ๊ณต์ง€์‚ฌํ•ญ

  • ์ธ๊ธฐ ๊ธ€

  • ์ตœ๊ทผ ๋Œ“๊ธ€

  • ์ตœ๊ทผ ๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.3
jwooo๐ŸŒฑ
GitHub Actions + Docker + NGINX๋ฅผ ํ™œ์šฉํ•œ Blue/Green ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ ํ™˜๊ฒฝ ๊ตฌ์ถ•
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”