들어오는 트래픽을 여러 서버로 나눠 보내는 장치.
L4와 L7
L4 (전송 계층)
- IP · 포트만 보고 분배한다. 내용은 안 본다
- 빠르고 부하가 적다. 프로토콜과 무관하다(TCP 면 무엇이든)
- 대신 URL 별 분기 · 헤더 기반 라우팅은 못 한다
L7 (응용 계층)
- HTTP 를 해석해 URL · 헤더 · 쿠키까지 보고 분배한다
- /api 는 A 그룹, /static 은 B 그룹 같은 라우팅이 가능하다
- TLS 종료 · 압축 · 캐싱 · 인증도 여기서 한다
- 대신 연결을 끊고 다시 맺으므로 비용이 크다
분배 알고리즘
- 라운드로빈 — 순서대로 돌아가며
- 가중 라운드로빈 — 서버 성능에 따라 비중을 다르게
- 최소 연결 — 지금 연결이 가장 적은 곳으로 (처리 시간 편차가 클 때 유리)
- IP 해시 — 같은 클라이언트를 같은 서버로 (세션 고정)
헬스체크가 핵심이다
죽은 서버로 계속 보내면 로드밸런서가 있으나 마나다
- 주기적으로 /health 를 찔러 살아 있는 서버만 풀에 남긴다
두 종류를 구분한다
liveness 프로세스가 살아 있나 → 실패하면 재시작
readiness 트래픽을 받을 준비가 됐나 → 실패하면 풀에서 뺀다
둘을 같게 두면 일시적 의존성 장애에 재시작 루프가 돈다
상태를 어떻게 다루나
-
세션을 서버 메모리에 두면 다른 서버로 가는 순간 로그인이 풀린다
-
① 세션 고정(스티키) — 같은 클라이언트를 같은 서버로
- 문제: 부하가 고르지 않고, 그 서버가 죽으면 세션이 사라진다
-
② 외부 세션 저장소 — 서버를 무상태로 만든다 (권장)
-
③ 토큰 기반 — 상태 자체를 클라이언트가 들고 다닌다
프록시 두 종류
-
리버스 프록시 — 서버 앞에 선다. 클라이언트는 진짜 서버를 모른다
- 로드밸런싱 · TLS 종료 · 캐싱 · 보안 필터
-
포워드 프록시 — 클라이언트 앞에 선다. 서버는 진짜 클라이언트를 모른다
- 사내 아웃바운드 통제 · 익명화 · 캐싱
원래 클라이언트 IP를 잃는다
프록시를 거치면 서버가 보는 출발지 IP 는 프록시의 IP 다
- X-Forwarded-For 헤더로 원래 IP 를 전달한다
- 이 헤더는 위조 가능하므로 '신뢰하는 프록시가 붙인 것만' 믿어야 한다
면접 함정
- ❌ "L7이 L4보다 항상 낫다" → 기능이 많지만 지연과 비용이 크다. 단순 분배면 L4가 낫다.
- ❌ "로드밸런서를 두면 고가용성" → 로드밸런서 자신이 단일 장애점이 된다. 이중화가 필요하다.
설정으로 보는 L7
upstream app {
least_conn; # 분배 알고리즘
server app1:8080 max_fails=3 fail_timeout=10s;
server app2:8080;
keepalive 32; # 업스트림 연결 재사용 (핸드셰이크를 아낀다)
}
server {
location /api { proxy_pass http://app; }
location /static { root /var/www; expires 1y; }
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_next_upstream error timeout http_502; # 실패하면 다음 서버로
}
keepalive를 안 잡으면 요청마다 업스트림과 새 연결을 맺어 TIME_WAIT이 쌓인다 — 트래픽이 많은 프록시에서 흔한 실수다.
무중단 배포와의 관계
-
① 새 인스턴스를 띄운다
-
② readiness 가 OK 가 되면 풀에 넣는다
-
③ 옛 인스턴스를 풀에서 뺀다 (여기서 바로 죽이면 안 된다)
-
④ 처리 중인 요청이 끝날 때까지 기다린다 (graceful shutdown · connection draining)
-
⑤ 종료한다
-
③ ~④ 를 건너뛰면 배포할 때마다 5xx 가 튄다