๐ ๋ค์ด๊ฐ๊ธฐ ์
ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉฐ Nginx๋ฅผ ์ฌ์ฉํด ๋ฆฌ๋ฒ์ค ํ๋ก์ ์๋ฒ๋ฅผ ๊ตฌ์ถํด ๋ณธ ๊ฒฝํ์ด ์๋ค. Nginx๋ฅผ ์ฌ์ฉํด ๊ฐ๋จํ๊ฒ ๋ฆฌ๋ฒ์ค ํ๋ก์ ์ธํ ๊ณผ ๋ก๋ ๋ฐธ๋ฐ์ค ์๋ฒ๋ฅผ ๊ตฌ์ถํ ์ ์์์ง๋ง 2% ์์ฌ์ด ๋ถ๋ถ์ด ์กด์ฌํ๋ค. ์ด์ ๋ํ ๋์์ผ๋ก HAProxy๋ฅผ ์ฌ์ฉํ์ฌ ๋ก๋๋ฐธ๋ฐ์ฑ์ด ๊ฐ๋ฅํ ๊ณ ๊ฐ์ฉ์ฑ ๋ฆฌ๋ฒ์ค ํ๋ก์ ์๋ฒ๋ฅผ ๊ตฌ์ถํ๊ณ ๋ ๋์๊ฐ ๋ฌด์ค๋จ ๋ฐฐํฌํ๊ฒฝ๊น์ง ๋ง๋ค์ด๋ณด๊ณ ์ ํ๋ค.
โ HAProxy
HAProxy๋ ๊ณ ๊ฐ์ฉ์ฑ ๋ก๋๋ฐธ๋ฐ์์ ๋ฆฌ๋ฒ์ค ํ๋ก์ ์๋ฒ๋ฅผ ์ง์ํ๋ ์คํ์์ค ์ํํธ์จ์ด์ด๋ค. Nginx ์ผ๋ฐ ๋ฒ์ ๊ณผ ์ฐจ๋ณํ๋ HAProxy์ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ health check ๊ทธ๋ฆฌ๊ณ failover ๊ธฐ๋ฅ์ด๋ค. ๊ฐ๋จํ ์ค๋ช ํ๋ฉด, HAProxy ์๋ฒ๊ฐ ๋ฐฑ์๋ ์๋ฒ์ ์ํ๋ฅผ ์ง์์ ์ผ๋ก ํ์ธํ๊ณ ๋ฌธ์ ๊ฐ ์๋ ๋ ธ๋(์๋ฒ)๊ฐ ์๋ค๋ฉด ํด๋น ์๋ฒ๋ก ์์ฒญ์ ๋ณด๋ด์ง ์๊ณ ์ ์์ ์ธ ๋ ธ๋๋ก ํธ๋ํฝ์ ์ ๋ฌํ๋ ์ง์ ํ๋ค. ์ด ๊ฐ์ ๊ธฐ๋ฅ์ ํ์ฉํด ์ด์คํ ๋ฐฑ์๋ ์๋ฒ๋ฅผ ๋ก๋๋ฐธ๋ฐ์ฑ ํ๋ ๊ธฐ๋ฅ๊ณผ ๋ฌด์ค๋จ ๋ฐฐํฌํ๊ฒฝ์ ๊ตฌ์ฑํด๋ณด๊ณ ์ ํ๋ค.
๐บ ์ธํ๋ผ ๊ตฌ์ฑ
- ๋ชจ๋ ์ธํ๋ผ ๋ฐ ์ดํ๋ฆฌ์ผ์ด์ ์ ์ปจํ ์ด๋ ํํ๋ก ๋ง๋ ๋ค
- HAProxy๋ฅผ ํ์ฉํด 2๊ฐ์ Spring Boot ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก ๋ก๋๋ฐธ๋ฐ์ฑ์ด ๊ฐ๋ฅํ ํ๊ฒฝ ๊ตฌ์ถ
- Gitlab Runner๋ฅผ ํ์ฉํด Blue Green ๋ฌด์ค๋จ ๋ฐฐํฌ ํ๊ฒฝ ๊ตฌ์ถ
- ๋ฐฐํฌ ์ดํ ๋กค๋ฐฑ ์คํฌ๋ฆฝํธ ๊ตฌ์ถ
์ด๋ฒ ํฌ์คํ ์์๋ HAProxy๋ฅผ ์ฌ์ฉํด ๋ก๋๋ฐธ๋ฐ์ฑ์ ํ ์ ์๋ ์ธํ๋ผ๊น์ง ๋ง๋ค ๊ฒ์ด๋ค.
โ๏ธ Spring Boot ์ ํ๋ฆฌ์ผ์ด์ ์ด๋ฏธ์ง
1. ๋ค์๊ณผ ๊ฐ์ด ๊ฐ๋จํ๊ฒ GET /hello ์์ฒญ์ "hello"๋ก ์๋ตํ๋ API๋ฅผ ๋ง๋ ๋ค.
@RestController
@RequiredArgsConstructor
@Slf4j
public class HelloController {
@GetMapping("/hello")
public ResponseEntity<String> hello() {
log.info("hello controller requested!");
return ResponseEntity.ok().body("hello");
}
}
- ์์ฒญ ๋ก๊ทธ๋ฅผ ํ์ธํ๊ธฐ ์ํด hello controller requestd! ๋ฅผ ์ถ๋ ฅํ๋๋ก ๋ง๋ค์๋ค.
2. health check๋ฅผ ์ํด acturator์ ์์กด์ฑ์ ์ถ๊ฐํ๊ณ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํ๋ค.
์ฐธ๊ณ http://forward.nhnent.com/hands-on-labs/java.spring-boot-actuator/06-health.html
//build.gradle
dependencies {
// actuator
implementation 'org.springframework.boot:spring-boot-starter-actuator:3.0.4'
}
//application.yml
management:
endpoints:
web:
base-path: /application
endpoint:
health:
show-details: always
3. Spring boot ์ ํ๋ฆฌ์ผ์ด์ ์ ์ด๋ฏธ์ง ํํ๋ก ๋ฐฐํฌํด์ผ ๋๊ธฐ ๋๋ฌธ์ ๋ค์๊ณผ ๊ฐ์ด Dockerfile์ ์์ฑํ๊ณ ๋น๋ ํ ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๋ค.
FROM openjdk:17-jdk-slim-buster
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
docker build . -t backend:latest
4. ์ปจํ ์ด๋๋ก ๋ง๋ค์ด์ง ์๋น์ค๋ค์ด ํต์ ํ ์ ์๋ ๋คํธ์ํฌ๋ฅผ ๋ง๋ ๋ค.
docker network create haproxy-net
5. ๋ฐฑ์๋ ์๋ฒ ์ปจํ ์ด๋๋ฅผ ๋ ๊ฐ ๋์ด๋ค(Blue server group)
docker run -d --name blue-server-1 -p 8081:8080 --net haproxy-net backend:latest
docker run -d --name blue-server-2 -p 8082:8080 --net haproxy-net backend:latest
๐ง ํ ์คํธ
1. ์ปจํ ์ด๋ ํ์ธ
docker ps
2. api ํ ์คํธ
curl -X GET localhost:8081/hello
3. health check ํ ์คํธ
curl -X GET localhost:8081/application/health
โ๏ธ HAProxy ๋ฆฌ๋ฒ์ค ํ๋ก์ ์๋ฒ ๊ตฌ์ถ
1. ๋ฆฌ๋ฒ์ค ํ๋ก์ ์ธํ ์ ์ํด ๋ค์๊ณผ ๊ฐ์ด haproxy.cfg ํ์ผ์ ๋ง๋ค์๋ค.
#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
log 127.0.0.1 local1
maxconn 4000 # ํ๋ก์ธ์ค ๋น ์ต๋ ์ฐ๊ฒฐ ์์น
daemon # Background ์คํ
stats socket /var/lib/haproxy/stats mode 660 level admin expose-fd listeners # ํต๊ณ ๊ด๋ จ ์ ๋ณด์ ๋ํ ์์น๋ฅผ ์ง์
#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults # front, back, listen์ ๊ดํ ์ ์ญ ์น์
mode http # http ํ๋กํ ์ฝ ์ฌ์ฉํ๋ ๋ก๋๋ฐธ๋ฐ์ฑ ๋ชจ๋
log global # ๋ก๊ทธ๋ global ์ค์ ์ ๋ฐ๋ฆ
option httplog # ๋ก๊ทธ ๋ํ
์ผ์ ๋์
option dontlognull # ๋ก๊ทธ ๋น๋ํ ๋ฐฉ์ง๋ฅผ ์ํด probe(์ ์ฐฐ, ์ค์บ) ์ก๋คํ ๊ธฐ๋ก์ ๋จ๊ธฐ์ง ์์
retries 3 # ๋ฐฑ์๋ ์๋ฒ๊ฐ ๋ค์ด๋์๋ค๊ณ ํ๋จํ๋ ์์ฒญ ํ์
option forwardfor # X-Forwarded-For๋ฅผ ํค๋์ ์ถ๊ฐ
option http-server-close # ์ค์ ์๋ฒ์ ํด๋ผ์ด์ธํธ ๊ฐ ์ฐ๊ฒฐ์ด ์ข
๋ฃ ๋ ์ ์ ํด์ํ๋ก ๋๊ธฐํ์ง ์๊ณ ์๋ฒ์์ Handshake๋ฅผ ์ข
๋ฃํ์ฌ ๋ ๋น ๋ฅด๊ฒ ์๋ก์ด ์ธ์
์ ์ค๋นํ ์ ์๋๋ก ํ๋ ์ต์
timeout http-request 2m # ์์ฒญํ์์์ ์๊ฐ ์ค์
timeout queue 2m
timeout connect 2m
timeout client 2m
timeout server 2m
timeout http-keep-alive 2m
timeout check 2m
#---------------------------------------------------------------------
# FrontEnd Configuration
#---------------------------------------------------------------------
frontend http # ๋ค์ด์ค๋ ์์ฒญ์ ๋ํ ์ค์
bind *:8080 # 8080 ๋ฒ์ผ๋ก ๋ค์ด์ค๋ ์์ฒญ์ ์ฒ๋ฆฌ
http-request set-header X-Forwarded-Proto http
use_backend backend_blue
#---------------------------------------------------------------------
# BackEnd Platform Configuration
#---------------------------------------------------------------------
backend backend_blue
balance roundrobin # ๋ถํ๋ถ์ฐ ์๊ณ ๋ฆฌ์ฆ
# health check
option httpchk
http-check send meth GET uri /application/health
http-check expect status 200
# configure platform instances
server s1 blue-server-1:8080 check inter 1s fastinter 500ms # ํ์์์ 1์ด ๋จ์๋ก ์ฒดํฌ ํ๊ณ ์๋ฒ์ ์ํ๊ฐ ๋ณ๋์ด ์์ ๋ .5์ด ๋ง๋ค ์ฒดํฌ
server s2 blue-server-2:8080 check inter 1s fastinter 500ms
#---------------------------------------------------------------------
# HAProxy Monitoring Config
#---------------------------------------------------------------------
listen stats
bind *:9000
mode http
option dontlog-normal
stats enable
stats uri /haproxy
- 8080 ํฌํธ๋ก ๋ค์ด์ค๋ ์์ฒญ์ backend_blue๋ก ๋ณด๋ธ๋ค
- backend_blue์์๋ ๋ผ์ด๋ ๋ก๋น ๋ฐฉ์์ผ๋ก ์์์ ์ธํ ํ 2๊ฐ์ backend ์๋ฒ๋ก ๋ถ๋ฐฐํ๋ค
- /application/health๋ก ์๋ฒ์ ์ํ๋ฅผ health check ํ๋ค.
- 9000 ํฌํธ๋ก haproxy๋ก ์ฐ๊ฒฐ๋ ์๋ฒ๋ค์ ์ํ๋ฅผ ํ์ธํ ์ ์๋ค.
2. HAProxy ์ปจํ ์ด๋ ์คํ
docker run -d --name haproxy -p 8080:8080 -p 9000:9000 -v $(pwd)/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg --net haproxy-net haproxy:alpine
๐ง ํ ์คํธ
1. ์ปจํ ์ด๋ ๋ก๊ทธ ํ์ธ
docker logs ๋ช ๋ น์ด๋ก ์ปจํ ์ด๋ ๋ก๊ทธ๋ฅผ ํ์ธํ ์ ์๋ค.
docker logs -f haproxy
2. HAProxy ์ํ ๋ธ๋ผ์ฐ์ ํ์ธ
๋ก์ปฌ์์ HAProxy๋ฅผ ์คํํ ๊ฒฝ์ฐ ๋ธ๋ผ์ฐ์ ์์ ์๋ฒ์ ์ํ๋ฅผ ํ์ธํ ์ ์๋ค.
๐ง ๋ก๋ ๋ฐธ๋ฐ์ฑ ํ ์คํธ
์ปจํ ์ด๋ ๋ก๊ทธ๋ฅผ ํ์ธํ์ฌ ํด๋ผ์ด์ธํธ ์์ฒญ์ด ๋ฐฑ์๋ ์๋ฒ๋ก ๋ถ๋ฐฐ๋์ด ์ฒ๋ฆฌ๋๋์ง ํ์ธํ๋ค.
1. ๋ฐฑ์๋ ์๋ฒ ๋ก๊ทธ ํ์ธ
docker logs -f blue-server-1
docker logs -f blue-server-2
2. 100๋ฒ์ ์์ฒญ์ ๋ณด๋ด๋ ์คํฌ๋ฆฝํธ ์์ฑ
๋ค์ ์ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ๋ฉด 100๋ฒ์ ์์ฒญ์ haproxy ์๋ฒ๋ก ๋ณด๋ผ ์ ์๋ค
#!/bin/bash
#load_balancing.sh
url="http://localhost:8080/hello"
count=0
# Loop 100 times
for i in {1..100}
do
# Send an HTTP GET request and capture the response status code
status=$(curl -s -o /dev/null -w "%{http_code}" $url)
done
3. ๊ฒฐ๊ณผ ํ์ธ
๋ค์๊ณผ ๊ฐ์ด ๋ชจ๋ ์์ฒญ์ด ๋ผ์ด๋ ๋ก๋น ๋ฐฉ์์ผ๋ก ์ด์คํ๋ ์๋ฒ๋ก ๊ท ์ผํ๊ฒ ๋ก๋๋ฐธ๋ฐ์ฑ ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
- ์ข์ธก ํฐ๋ฏธ๋์ ์คํฌ๋ฆฝํธ๋ฅผ ์คํ(sh load_balancing.sh)
- ์ฐ์ธก์ ๋ฐฑ์๋ ์๋ฒ์ ๋ก๊ทธ๋ฅผ ํ์ธ(blue-server-1, blue-server-2)
๐ง failover ๊ธฐ๋ฅ ํ ์คํธ
1. ๋ฐฑ์๋ ์๋ฒ ๋์ปค ์ปจํ ์ด๋ ์ ์ง
docker stop blue-server-1
2. 100๋ฒ ์์ฒญ์ ๋ณด๋ด๋ ์คํฌ๋ฆฝํธ ์์ฑ(fail_over_test.sh)
- GET /hello๋ก 100๋ฒ์ ์์ฒญ์ ๋ณด๋ด๋ ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํ๋ค.
- ์ํ์ฝ๋๊ฐ 200๋ฒ์์ ํ์ธํ๊ณ count๋ฅผ ์ฌ๋ฆฐ๋ค.
- ๋ชจ๋ ์์ฒญ์ด ์ฑ๊ณต์ ์ด๋ฉด count ๊ฐ์ 100์ผ๋ก ์ถ๋ ฅํ๋ค.
#!/bin/bash
#fail_over_test.sh
url="http://localhost:8080/hello"
count=0
# Loop 100 times
for i in {1..100}
do
# Send an HTTP GET request and capture the response status code
status=$(curl -s -o /dev/null -w "%{http_code}" $url)
# If the status code is 200, increment the counter
if [ "$status" -eq "200" ]; then
((count++))
fi
done
# Output the count of successful requests
echo "Number of successful requests: $count"
3. ํ ์คํธ ๊ฒฐ๊ณผ ํ์ธ
์ด๋ฅผ ํตํด ํ๋์ ์๋ฒ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ ๊ฒฝ์ฐ HAProxy๊ฐ ๋ค๋ฅธ ์๋ฒ๋ก ๋ชจ๋ ์์ฒญ์ ๋ผ์ฐํ ํด์ฃผ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
๐ชข ๋ง์น๋ฉฐ
HAProxy๋ก ์ด์คํ๋ ์๋ฒ์ ๋ก๋๋ฐธ๋ฐ์ฑ ํ๊ฒฝ์ ๋ง๋ค๊ณ ํ ์คํธํด๋ณด์๋ค. ๊ธฐ์กด์ ์ฌ์ฉํ๋ Nginx์ ๋น๊ตํด ๋ดค์ ๋ ์ค์ ํ์ผ์ ์ต์ ๋ค์ด ์กฐ๊ธ ๋ ์ดํดํ๊ธฐ ์ฌ์ ๊ณ ๊ฐ๋จํ๊ฒ ์ปจํ ์ด๋ ํ๊ฒฝ์์ ๋ฆฌ๋ฒ์ค ํ๋ก์ ์๋ฒ๋ฅผ ๊ตฌ์ถํ ์ ์์๋ค. ๋ก๋๋ฐธ๋ฐ์ฑ ์ด์ธ์๋ failover๊ณผ ๊ฐ์ ๊ณ ๊ฐ์ฉ์ฑ ์๋ฒ ๊ตฌ์ฑ์ ์ํ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ ๊ฐ์ง๊ณ ์๋ค. HAProxy๊ฐ ์ฅ์ ๋ง ์๋ ๊ฒ์ ์๋๋ค. Nginx๋ ๊ทธ ์์ฒด๋ก ์น ์๋ฒ๋ก์ ๋์ํ ์ ์๊ณ ์ ์ ์ฝํ ์ธ ๋ฅผ ๋น ๋ฅด๊ฒ ํด๋ผ์ด์ธํธ์๊ฒ ์ ๊ณตํ๊ณ ๋ ๋ง์ connection์ ๊ฐ๋นํ ์ ์๋ค.
๋ค์ ํฌ์คํ ์์๋ HAProxy์ Gitlab-CI ํ์ดํ๋ผ์ธ์ ํ์ฉํด Blue-Green ๋ฌด์ค๋จ ๋ฐฐํฌ ํ๊ฒฝ์ ๊ตฌ์ถํด๋ณด๊ณ ์ ํ๋ค.
To Be Continue...