IPVS L4 로드밸런싱
Linux IPVS(IP Virtual Server) 아키텍처, 스케줄링 알고리즘(rr/wrr/lc/wlc/lblc/sh/dh), 포워딩 모드(NAT/DR/TUN/FULLNAT), keepalived 고가용성, conntrack 연동, 성능 비교 종합 가이드. 커널 내부 데이터 경로, 핵심 자료구조/API, 운영 환경 튜닝 포인트와 장애 디버깅(Debugging) 절차까지 실무 관점으로 다룹니다.
핵심 요약
- VIP / Real Server — 클라이언트는 VIP(가상 IP)로 접속하고, IPVS가 실제 서버(Real Server)로 분산합니다.
- L4 커널 내 처리 — Netfilter LOCAL_IN/FORWARD 훅에서 패킷을 가로채 유저스페이스를 거치지 않고 전달합니다.
- 스케줄링 알고리즘 — rr(라운드로빈), wrr(가중치), lc(최소연결), wlc(가중최소연결) 등 10가지를 커널 모듈(Kernel Module)로 제공합니다.
- 포워딩 모드 — NAT(주소변환), DR(Direct Routing, MAC만 교체), TUN(IP 터널(Tunnel)), FULLNAT(src+dst 모두 변환) 4가지 모드를 지원합니다.
- conntrack 연동 — 세션 지속성(Persistence)과 상태 추적을 conntrack과 협력하여 처리합니다.
- keepalived HA — VRRP로 VIP 자동 페일오버, 헬스체크로 장애 Real Server 자동 제거를 수행합니다.
- Kubernetes kube-proxy — iptables 모드 대신 IPVS 모드를 선택하면 수천 개 서비스를 O(1)로 처리합니다.
- 성능 우위 — iptables DNAT 대비 10만 PPS 이상 고부하에서 IPVS가 선형 확장성을 유지합니다.
- 세션 동기화 HA — Active/Backup Director 간
ip_vs_sync멀티캐스트로 연결 상태를 실시간(Real-time) 동기화하여 페일오버 시 세션이 끊기지 않습니다. - XDP/Katran 가속 — NIC 드라이버 수준의 XDP 훅에서 패킷을 처리하면 Netfilter를 완전히 우회하여 IPVS 대비 2~5배 높은 처리량(Throughput)을 달성합니다.
- Kubernetes IPVS 모드 — kube-proxy IPVS 모드는 서비스 수 10,000개에서도 O(1) 해시(Hash) 룩업으로 균일한 지연(Latency)을 유지합니다. iptables 모드는 O(n) 선형 탐색입니다.
단계별 이해
- VIP와 Real Server 개념 잡기
클라이언트 → VIP(Director) → Real Server 흐름을 그림으로 익힙니다. Director가 패킷을 어떻게 전달하느냐가 포워딩 모드의 핵심입니다. - NAT 모드로 시작하기
가장 간단한 NAT 모드로 ipvsadm을 직접 실행해 패킷 흐름을 tcpdump로 확인합니다. dst IP가 Real Server로 바뀌는 과정을 눈으로 봅니다. - DR 모드 이해하기
Real Server가 VIP를 lo 인터페이스에 추가하고 ARP를 억제해야 하는 이유를 이해합니다. 응답 패킷이 Director를 거치지 않아 처리량이 2배 이상 늘어납니다. - 스케줄링 알고리즘 선택하기
균일 요청은 rr/wrr, 커넥션 유지 시간이 긴 서비스는 lc/wlc, 캐시(Cache) 서버는 dh/sh를 선택합니다. - keepalived로 HA 구성하기
Active/Standby Director 두 대에 keepalived를 설치하고 VRRP와 헬스체크 설정을 적용합니다. - 모니터링 및 진단하기
ipvsadm -Ln --stats, /proc/net/ip_vs, conntrack -L로 연결 상태를 실시간 확인합니다. - 세션 동기화로 무중단 HA 구성하기
Primary Director에ipvsadm --start-daemon master --syncid 1, Backup에--start-daemon backup을 설정합니다. keepalived notify 스크립트로 역할 전환 시 자동 활성화합니다. - Kubernetes IPVS 모드 활용하기
kube-proxy ConfigMap에서mode: "ipvs"로 설정하고,ipvsadm -Ln으로 ClusterIP/NodePort 서비스가 IPVS Virtual Service로 등록되는지 확인합니다.
개요: IPVS와 Linux Virtual Server
IPVS(IP Virtual Server)는 리눅스 커널 내에서 동작하는 L4 로드밸런서입니다.
1998년 Wensong Zhang이 개발한 LVS(Linux Virtual Server) 프로젝트의 핵심 컴포넌트로,
커널 2.6.10부터 메인라인에 포함되어 있습니다(net/netfilter/ipvs/).
Netfilter 훅을 통해 패킷을 가로채고, 유저스페이스를 전혀 거치지 않으므로
수십만 CPS(Connections Per Second) 규모에서도 안정적으로 동작합니다.
IPVS는 세 가지 주요 개념으로 구성됩니다.
- Virtual Service (가상 서비스): 클라이언트가 접속하는 VIP:Port:Protocol 조합.
ip_vs_service구조체(Struct)로 표현됩니다. - Real Server (실제 서버): 실제 요청을 처리하는 백엔드 서버.
ip_vs_dest구조체로 표현됩니다. - Connection (연결): 클라이언트 ↔ VIP 연결을 추적하는
ip_vs_conn구조체. 해시 테이블(Hash Table)로 관리됩니다.
IPVS 아키텍처와 Netfilter 통합
IPVS는 Netfilter 프레임워크의 두 훅 지점에 등록됩니다.
NF_INET_LOCAL_IN(우선순위(Priority) -1)에서 VIP로 향하는 패킷을 포착하고,
NF_INET_FORWARD(우선순위 -1)에서 기존 연결의 후속 패킷을 처리합니다.
패킷이 훅에 도달하면 IPVS는 ip_vs_in() 함수를 호출합니다.
핵심 자료구조
/* include/net/ip_vs.h */
/* Virtual Service: VIP:Port:Protocol */
struct ip_vs_service {
struct hlist_node s_list; /* 서비스 해시 테이블 노드 */
atomic_t refcnt;
u32 flags; /* IP_VS_SVC_F_* */
__u16 protocol; /* TCP/UDP/SCTP */
__be32 addr; /* VIP */
__be16 port; /* 포트 */
struct ip_vs_scheduler *scheduler; /* 스케줄러 포인터 */
struct list_head destinations; /* Real Server 목록 */
__u32 num_dests;
struct ip_vs_stats stats;
/* ... */
};
/* Real Server: 실제 백엔드 서버 */
struct ip_vs_dest {
struct list_head n_list; /* 서비스 내 RS 목록 */
__be32 addr; /* RS IP */
__be16 port; /* RS 포트 */
volatile unsigned flags; /* IP_VS_DEST_F_* */
atomic_t weight; /* 가중치 */
atomic_t activeconns; /* 활성 연결 수 */
atomic_t inactconns; /* 비활성 연결 수 */
struct ip_vs_stats stats;
/* ... */
};
/* Connection: 클라이언트-VIP 연결 상태 */
struct ip_vs_conn {
struct hlist_node c_list; /* 연결 해시 테이블 노드 */
/* 5-tuple */
__be32 caddr; /* 클라이언트 IP */
__be32 vaddr; /* VIP */
__be32 daddr; /* RS IP (Real Server) */
__be16 cport;
__be16 vport;
__be16 dport;
__u16 protocol;
/* 상태 */
volatile __u16 state; /* IP_VS_TCP_S_*, IP_VS_UDP_S_* */
unsigned long timeout;
atomic_t refcnt;
struct ip_vs_dest *dest; /* 배정된 RS 포인터 */
/* NAT용 포트/IP 변환 정보 */
union nf_inet_addr nat_addr;
__be16 nat_port;
/* ... */
};
Netfilter 훅 등록
/* net/netfilter/ipvs/ip_vs_core.c */
static const struct nf_hook_ops ip_vs_ops4[] = {
/* 인바운드: VIP로 들어오는 신규 연결 처리 */
{
.hook = ip_vs_in,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_NAT_DST + 1, /* conntrack DNAT 이후 */
},
/* 아웃바운드: Director → RS 포워딩 경로 */
{
.hook = ip_vs_out,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_FORWARD,
.priority = NF_IP_PRI_NAT_DST - 1,
},
/* 아웃바운드 LOCAL_OUT: DR/TUN 반환 경로 */
{
.hook = ip_vs_local_reply4,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_OUT,
.priority = NF_IP_PRI_NAT_DST + 1,
},
};
IPVS 패킷 처리 흐름은 다음 순서로 진행됩니다.
- NIC →
ip_rcv()→ routing → NF_INET_LOCAL_IN ip_vs_in()호출: 연결 해시 탐색 (ip_vs_conn_get())- 신규 연결이면 스케줄러(Scheduler) 호출 → RS 선택 →
ip_vs_conn_new() - 포워딩 모드에 따라 패킷 변환 후 전달
- RS 응답 →
ip_vs_out()에서 역방향 변환
스케줄링 알고리즘
IPVS는 스케줄링 알고리즘을 커널 모듈로 분리하여 동적 로드가 가능합니다.
struct ip_vs_scheduler의 schedule() 콜백(Callback)을 구현하면 됩니다.
현재 10가지 공식 알고리즘이 있습니다.
| 알고리즘 | 약어 | 모듈 | 설명 | 적합한 상황 |
|---|---|---|---|---|
| Round Robin | rr | ip_vs_rr | 순환 방식으로 RS를 선택. 가장 단순 | 서버 성능이 동일하고 요청 처리 시간이 균일할 때 |
| Weighted RR | wrr | ip_vs_wrr | 가중치 비율로 분배. 고성능 서버에 더 많이 할당 | 서버 성능이 불균일할 때 |
| Least Connection | lc | ip_vs_lc | 활성 연결 수가 가장 적은 RS 선택 | 요청 처리 시간이 불균일할 때 |
| Weighted LC | wlc | ip_vs_wlc | lc + 가중치 결합. (activeconns×256+inactconns)/weight 최솟값 선택 | 실무 범용 권장 알고리즘 |
| Locality-Based LC | lblc | ip_vs_lblc | 동일 클라이언트 IP → 동일 RS (캐시 친화). 과부하 시 재배분 | 웹 캐시 서버 |
| LBLC with Replication | lblcr | ip_vs_lblcr | lblc + RS 집합 복제. 핫스팟 방지 | 캐시 서버 클러스터 |
| Destination Hash | dh | ip_vs_dh | 목적지 IP 해시로 RS 고정 배정 | 정방향 프록시, 캐시 일관성(Cache Coherency) |
| Source Hash | sh | ip_vs_sh | 소스 IP 해시로 RS 고정 배정 (세션 지속성) | NAT 뒤 클라이언트 세션 유지 |
| Shortest Expected Delay | sed | ip_vs_sed | 예상 대기 시간(Latency) 최소 RS 선택. (activeconns+1)/weight | 응답 시간 편차가 큰 서비스 |
| Never Queue | nq | ip_vs_nq | 활성 연결 없는 RS 우선 배정, 없으면 sed로 폴백 | 유휴 서버를 즉시 활용해야 할 때 |
스케줄러 내부 구현 (wlc 예시)
각 스케줄러는 net/netfilter/ipvs/ 디렉터리 내 별도 파일로 구현됩니다.
struct ip_vs_scheduler의 schedule() 콜백을 구현하면 커스텀 스케줄러도 모듈로 등록할 수 있습니다.
/* net/netfilter/ipvs/ip_vs_wlc.c */
static struct ip_vs_dest *
ip_vs_wlc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
struct ip_vs_iphdr *iph)
{
struct ip_vs_dest *dest, *least = NULL;
unsigned long loh = ULONG_MAX;
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
if (dest->flags & IP_VS_DEST_F_OVERLOAD)
continue;
if (!atomic_read(&dest->weight))
continue;
/* overhead = (activeconns * 256 + inactconns) / weight */
unsigned long oh =
(atomic_read(&dest->activeconns) * 256UL
+ atomic_read(&dest->inactconns))
/ atomic_read(&dest->weight);
if (oh < loh) {
loh = oh;
least = dest;
}
}
return least;
}
LBLCR 스케줄러 — Locality-Based LC with Replication
ip_vs_lblcr 스케줄러는 웹 캐시 클러스터에 특화된 알고리즘입니다.
동일 목적지 IP는 동일 RS 집합(set)으로 보내 캐시 히트율을 높이되,
특정 RS가 과부하 상태이면 집합에 다른 RS를 추가하여 핫스팟을 방지합니다.
/* net/netfilter/ipvs/ip_vs_lblcr.c — 핵심 로직 */
/* 목적지 IP별 RS 집합 캐시 */
struct ip_vs_lblcr_entry {
struct hlist_node list;
__be32 addr; /* 목적지 IP */
struct ip_vs_dest_set set; /* RS 집합 */
unsigned long lastuse; /* 마지막 사용 시각 */
};
static struct ip_vs_dest *
ip_vs_lblcr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
struct ip_vs_iphdr *iph)
{
struct ip_vs_lblcr_table *tbl = svc->sched_data;
struct ip_vs_lblcr_entry *en;
struct ip_vs_dest *dest;
/* 1. 목적지 IP로 캐시 룩업 */
en = ip_vs_lblcr_get(tbl, iph->daddr);
if (!en) {
/* 2. 새 항목: wlc 알고리즘으로 RS 선택 후 집합 생성 */
dest = ip_vs_wlc_schedule(svc, skb, iph);
en = ip_vs_lblcr_new(iph->daddr, dest);
ip_vs_lblcr_add(tbl, en);
return dest;
}
/* 3. 캐시 히트: 집합 내 최소 연결 RS 선택 */
dest = ip_vs_dest_set_min(&en->set);
/* 4. 과부하 시 새 RS를 집합에 추가 (복제) */
if (is_overloaded(dest, svc)) {
struct ip_vs_dest *new_dest = ip_vs_wlc_schedule(svc, skb, iph);
ip_vs_dest_set_add(&en->set, new_dest);
dest = new_dest;
}
return dest;
}
SED와 NQ 알고리즘 상세
SED(Shortest Expected Delay)는 각 RS에 새 연결을 추가했을 때 예상되는 대기시간을 계산하여 최솟값을 선택합니다.
수식: (activeconns + 1) / weight. 기존 lc보다 가중치를 더 잘 활용하지만,
모든 RS가 바쁠 때 새 연결 1개짜리 RS를 무조건 선택하는 문제가 있습니다.
NQ(Never Queue)는 SED의 이 문제를 보완합니다. 활성 연결이 0인 RS(완전 유휴 서버)가 있으면 무조건 그 서버로 배정하고, 없을 때만 SED 알고리즘으로 폴백합니다. 유휴 서버를 즉시 활용해야 하는 오토스케일링 환경에 적합합니다.
/* net/netfilter/ipvs/ip_vs_nq.c */
static struct ip_vs_dest *
ip_vs_nq_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
struct ip_vs_iphdr *iph)
{
struct ip_vs_dest *dest, *least = NULL;
unsigned long loh = ULONG_MAX, doh;
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
if (!atomic_read(&dest->weight))
continue;
/* 활성 연결 0 = 완전 유휴 서버 → 즉시 반환 */
if (atomic_read(&dest->activeconns) == 0 &&
dest->flags & IP_VS_DEST_F_AVAILABLE)
return dest;
/* SED 수식: (activeconns + 1) * 256 / weight */
doh = (atomic_read(&dest->activeconns) + 1) * 256UL
/ atomic_read(&dest->weight);
if (doh < loh) {
loh = doh;
least = dest;
}
}
return least;
}
Maglev Hash (mh) 스케줄러 — 일관성 해시
커널 4.18에서 추가된 ip_vs_mh 스케줄러는 Google Maglev 논문에 기반한 일관성 해시 알고리즘을 구현합니다.
기존 sh(Source Hash)와 달리, RS 추가/제거 시 기존 세션의 95% 이상을 동일 RS로 유지하여
ECMP 환경에서 상태 동기화 없이도 안정적인 서비스를 제공합니다.
/* net/netfilter/ipvs/ip_vs_mh.c — 핵심 구조 */
struct ip_vs_mh_state {
struct rcu_head rcu_head;
struct ip_vs_mh_lookup *lookup; /* Maglev lookup table */
__u32 hash_seed; /* siphash 시드 */
unsigned int table_size; /* 기본 2503 (소수) */
unsigned int num_dests; /* RS 개수 */
};
struct ip_vs_mh_lookup {
struct ip_vs_dest __rcu *dest; /* 테이블 슬롯 → RS 매핑 */
};
/* Maglev 테이블 생성: RS별 preference list → lookup table 채움 */
static int ip_vs_mh_populate(struct ip_vs_mh_state *s,
struct ip_vs_service *svc)
{
/* 1. 각 RS에 대해 offset/skip 계산 (siphash 기반) */
/* 2. Round-robin 방식으로 빈 슬롯 채움 */
/* 3. 모든 슬롯이 채워질 때까지 반복 */
/* → RS 추가/제거 시 최소 슬롯만 재배치 */
}
/* 패킷 도착 시: 5-tuple 해시 → 테이블 인덱스 → RS 선택 */
static struct ip_vs_dest *
ip_vs_mh_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
struct ip_vs_iphdr *iph)
{
struct ip_vs_mh_state *s = svc->sched_data;
__u32 hash = ip_vs_mh_hashkey(iph) % s->table_size;
return rcu_dereference(s->lookup[hash].dest);
}
알고리즘별 상태 저장 오버헤드(Overhead) 비교
| 알고리즘 | 소스 파일 | 추가 상태 | 메모리 오버헤드 | Lock 경합(Contention) |
|---|---|---|---|---|
| rr | ip_vs_rr.c | 현재 RS 포인터 | 최소 (포인터 1개) | spinlock/RCU |
| wrr | ip_vs_wrr.c | 현재 RS + 현재 가중치 카운터 | 낮음 | spinlock |
| lc / wlc | ip_vs_lc.c / ip_vs_wlc.c | RS별 activeconns/inactconns (atomic) | 낮음 (RS당 2 atomic) | RCU (읽기 잠금(Lock) 없음) |
| lblc | ip_vs_lblc.c | 목적지 IP → RS 해시 캐시 | 중간 (캐시 크기 × 엔트리) | spinlock (캐시 갱신 시) |
| lblcr | ip_vs_lblcr.c | 목적지 IP → RS 집합 캐시 | 높음 (집합 크기 가변) | spinlock (집합 수정 시) |
| sh / dh | ip_vs_sh.c / ip_vs_dh.c | IP → RS 해시 테이블 (고정 256 버킷) | 중간 (256 × 포인터) | RCU |
| sed / nq | ip_vs_sed.c / ip_vs_nq.c | RS별 activeconns (atomic) | 낮음 | RCU |
포워딩 모드 (NAT / DR / TUN / FULLNAT)
IPVS는 패킷을 Real Server로 전달하는 네 가지 포워딩 모드를 지원합니다. 각 모드는 패킷 헤더를 어떻게 변환하느냐와 응답 경로가 어디를 통하느냐에서 차이가 납니다.
NAT 모드
NAT 모드는 Director가 목적지 IP를 VIP에서 RS IP로 변경합니다. 응답 패킷도 Director를 경유하므로 RS의 기본 게이트웨이를 Director로 설정해야 합니다.
# Director 설정
# 1. IP 포워딩 활성화
sysctl -w net.ipv4.ip_forward=1
# 2. 가상 서비스 추가 (VIP:80 TCP, wlc 알고리즘)
ipvsadm -A -t 10.0.0.1:80 -s wlc
# 3. Real Server 추가 (NAT 모드: -m)
ipvsadm -a -t 10.0.0.1:80 -r 192.168.100.11:80 -m -w 1
ipvsadm -a -t 10.0.0.1:80 -r 192.168.100.12:80 -m -w 1
# RS 서버에서: 기본 게이트웨이를 Director로 설정
ip route add default via 192.168.100.1 # Director 내부 IP
DR 모드 (Direct Routing)
DR 모드는 패킷의 MAC 주소만 변경하여 RS로 전달합니다. 응답 패킷은 RS에서 클라이언트로 직접 전송되므로 Director가 병목(Bottleneck)이 되지 않습니다. RS는 VIP를 lo 인터페이스에 추가하고 ARP 응답을 억제해야 합니다.
# Director 설정
ipvsadm -A -t 10.0.0.1:80 -s wlc
# DR 모드: -g (gateway/direct routing)
ipvsadm -a -t 10.0.0.1:80 -r 10.0.0.11:80 -g -w 1
ipvsadm -a -t 10.0.0.1:80 -r 10.0.0.12:80 -g -w 1
# ─────────────────────────────────────────
# 각 RS 서버에서 실행 (ARP 억제 + VIP 추가)
# ─────────────────────────────────────────
# ARP 요청을 lo 인터페이스에서 응답하지 않도록 설정
sysctl -w net.ipv4.conf.all.arp_ignore=1
sysctl -w net.ipv4.conf.all.arp_announce=2
# lo에 VIP 추가 (prefix /32: 브로드캐스트 없음)
ip addr add 10.0.0.1/32 dev lo
# /etc/sysctl.conf에 영구 적용
cat >> /etc/sysctl.conf << 'EOF'
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
EOF
TUN 모드
TUN 모드는 원본 패킷을 IP-in-IP로 캡슐화(Encapsulation)하여 RS에 전달합니다. RS가 물리적으로 다른 데이터센터에 있어도 됩니다. RS에서는 ipip 모듈을 로드하고 tunl0 인터페이스에 VIP를 할당합니다.
# RS 서버에서
modprobe ipip
ip addr add 10.0.0.1/32 dev tunl0
ip link set tunl0 up
sysctl -w net.ipv4.conf.tunl0.arp_ignore=1
sysctl -w net.ipv4.conf.tunl0.rp_filter=0
# Director
ipvsadm -A -t 10.0.0.1:80 -s rr
# TUN 모드: -i
ipvsadm -a -t 10.0.0.1:80 -r 203.0.113.11:80 -i -w 1
TUN 모드 캡슐화 옵션 (IPIP / GRE)
커널 4.9부터 ip_vs_tunnel_encap_type sysctl로 캡슐화 방식을 선택할 수 있습니다.
기본값은 IPIP(0)이며, GRE(1)를 선택하면 GRE 키를 통해 다중 VIP를 구분할 수 있습니다.
# TUN 캡슐화 방식 설정
# 0 = IPIP (기본), 1 = GRE, 2 = GUE (Generic UDP Encapsulation)
sysctl -w net.ipv4.vs.tunnel_encap_type=0
# GRE 모드 사용 시 RS에서 GRE 인터페이스 설정
ip tunnel add gre1 mode gre local RS_IP remote DIRECTOR_IP
ip addr add 10.0.0.1/32 dev gre1
ip link set gre1 up
# GUE(UDP 캡슐화) 모드: NAT 방화벽 통과 가능
sysctl -w net.ipv4.vs.tunnel_encap_type=2
sysctl -w net.ipv4.vs.tunnel_encap_dport=6080 # GUE 기본 포트
DR 모드 ARP 억제
DR 모드에서 RS는 VIP를 lo 인터페이스에 추가하는데, 이때 ARP 요청에 응답하지 않도록
arp_ignore와 arp_announce 두 파라미터를 반드시 설정해야 합니다.
이 설정 없이는 RS가 ARP 요청에 응답하여 VIP 트래픽이 Director가 아닌 RS로 직접 흘러들어 갑니다.
# ── arp_ignore 값의 의미 ──
# 0 (기본): 어떤 인터페이스에서도 로컬 IP로 ARP 응답 (위험)
# 1: ARP 요청이 들어온 인터페이스에 설정된 IP에 대해서만 응답
# 2: ARP 요청 수신 인터페이스 IP + 같은 서브넷에만 응답
# 8: ARP 요청에 절대 응답 안 함
# ── arp_announce 값의 의미 ──
# 0 (기본): 어떤 소스 IP도 ARP 광고에 사용
# 1: 목적지 서브넷에 없는 IP는 가급적 사용 안 함
# 2: 항상 해당 인터페이스의 최적 로컬 IP를 소스로 사용 (권장)
# DR 모드 RS 설정 완전 예시
# 1. 인터페이스별 + all 양쪽 설정 필수
sysctl -w net.ipv4.conf.all.arp_ignore=1
sysctl -w net.ipv4.conf.eth0.arp_ignore=1
sysctl -w net.ipv4.conf.all.arp_announce=2
sysctl -w net.ipv4.conf.eth0.arp_announce=2
# 2. VIP를 lo에 추가 (/32: 서브넷 광고 없음)
ip addr add 10.0.0.1/32 dev lo label lo:vip
# 3. rp_filter 비활성화 (소스 IP 검증 완화)
sysctl -w net.ipv4.conf.all.rp_filter=0
sysctl -w net.ipv4.conf.eth0.rp_filter=0
# 4. 확인
ip addr show dev lo
arping -I eth0 10.0.0.1 # RS에서 응답 없어야 정상
FULLNAT 모드
FULLNAT은 표준 커널에 포함되지 않은 비표준 패치(Patch)입니다.
Alibaba Cloud의 LVS 패치 또는 kube-nat 프로젝트에서 구현을 찾을 수 있습니다.
FULLNAT은 NAT 모드와 달리 소스 IP도 Director의 IP로 변경하므로
RS의 기본 게이트웨이를 Director로 설정할 필요가 없고,
RS가 서로 다른 서브넷 또는 VPC에 분산되어 있어도 동작합니다.
/* FULLNAT 패킷 흐름 */
/* 인바운드 (Client → Director → RS) */
원본: src=Client_IP:Cport dst=VIP:80
FULLNAT 변환: src=DIP:Eport dst=RS_IP:80
/* DIP = Director의 내부 IP, Eport = 임시 포트 */
/* RS 응답 (RS → Director) */
원본: src=RS_IP:80 dst=DIP:Eport
FULLNAT 역변환: src=VIP:80 dst=Client_IP:Cport
/* RS에서 원본 클라이언트 IP를 알아야 하는 경우: TOA (TCP Option Address) */
/* TOA는 TCP 옵션 필드(Kind=254)에 원본 클라이언트 IP를 삽입 */
/* RS 커널에 toa.ko 모듈 설치 후 getsockopt(SO_GET_REAL_IP)로 획득 */
toa.ko 커널 모듈을 설치하면 getsockopt(TCP_REAL_CLIENT_ADDR)로 원본 IP를 획득할 수 있습니다.
Nginx, HAProxy 등 대부분의 L7 프록시는 TOA 모듈 패치를 지원합니다.
IPVS + conntrack 연동
IPVS는 자체 연결 추적(ip_vs_conn)을 사용하지만,
NAT 모드에서는 Netfilter conntrack과도 협력합니다.
CONFIG_IP_VS_NFCT 옵션을 활성화하면 IPVS가 conntrack 항목을 생성/갱신합니다.
conntrack 바인딩
/* net/netfilter/ipvs/ip_vs_nfct.c */
/* IPVS 연결을 conntrack에 바인딩 */
void ip_vs_nfct_expect_related(struct sk_buff *skb, struct nf_conn *ct,
struct ip_vs_conn *cp, u_int8_t proto,
const __be16 dport, int from_rs)
{
struct nf_conntrack_expect *exp;
exp = nf_ct_expect_alloc(ct);
if (!exp)
return;
/* 기대 연결 등록 */
nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT,
nf_ct_l3num(ct), NULL, NULL, proto, NULL, &dport);
nf_ct_expect_related(exp, 0);
nf_ct_expect_put(exp);
}
/* 새 IPVS 연결 생성 시 conntrack 생성 */
static void ip_vs_nfct_bind_dest(struct ip_vs_conn *cp)
{
if (!(cp->flags & IP_VS_CONN_F_NFCT))
return;
/* cp → nf_conn 연결 */
}
세션 지속성 (Persistence)
IPVS는 동일 클라이언트의 연속 연결을 동일 RS로 유도하는 세션 지속성을 지원합니다.
-p 옵션으로 지속 시간(초)을 설정합니다.
# 세션 지속성: 300초 동안 같은 클라이언트는 같은 RS로
ipvsadm -A -t 10.0.0.1:80 -s lc -p 300
# 지속성 확인
ipvsadm -Lcn | grep PERSIST
# conntrack 상태 확인
conntrack -L | grep "dport=80"
# IPVS 연결 테이블 확인
cat /proc/net/ip_vs_conn
conntrack 관련 sysctl
# IPVS + conntrack 연동 활성화
sysctl -w net.ipv4.vs.conntrack=1
# IPVS 연결 해시 테이블 크기 (2의 지수)
sysctl -w net.ipv4.vs.conn_reuse_mode=1
# 연결 타임아웃 조정
sysctl -w net.ipv4.vs.tcp_timeout_established=900
sysctl -w net.ipv4.vs.tcp_timeout_fin_wait=30
sysctl -w net.ipv4.vs.udp_timeout=300
# 대규모 환경: 해시 테이블 버킷 수 (기본 4096)
# 커널 파라미터: ip_vs_conn_tab_bits=20 (2^20 = 1M 버킷)
ip_vs_nfct_expect_related() 전체 흐름
FTP, SIP 같은 ALG(Application Layer Gateway) 프로토콜은 데이터 연결을 동적으로 생성합니다.
IPVS는 ip_vs_nfct_expect_related()를 통해 conntrack의 expect 메커니즘을 활용하여
이러한 연관 연결을 자동으로 추적합니다.
/* net/netfilter/ipvs/ip_vs_nfct.c */
/*
* 흐름: FTP 제어 연결(21) → 데이터 연결(20) 추적
*
* 1. FTP 클라이언트가 PORT 명령으로 데이터 포트를 알림
* 2. ip_vs_ftp.c의 ALG가 포트 번호를 파싱
* 3. ip_vs_nfct_expect_related() 호출 → conntrack expect 등록
* 4. 데이터 연결이 도착하면 conntrack이 IPVS에 통보
* 5. IPVS가 같은 RS로 데이터 연결을 유도
*/
void ip_vs_nfct_expect_related(struct sk_buff *skb, struct nf_conn *ct,
struct ip_vs_conn *cp, u_int8_t proto,
const __be16 dport, int from_rs)
{
struct nf_conntrack_expect *exp;
/* 1. expect 구조체 할당 */
exp = nf_ct_expect_alloc(ct);
if (!exp)
return;
/* 2. expect 초기화: 프로토콜 + 목적지 포트 지정 */
nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT,
nf_ct_l3num(ct),
from_rs ? &cp->daddr : NULL, /* RS IP */
from_rs ? NULL : &cp->vaddr, /* VIP */
proto, NULL, &dport);
/* 3. NAT 헬퍼: expect 연결의 DNAT 처리를 IPVS에 위임 */
exp->expectfn = ip_vs_nfct_expect_callback;
exp->flags |= NF_CT_EXPECT_PERMANENT;
/* 4. conntrack에 expect 등록 */
nf_ct_expect_related(exp, 0);
nf_ct_expect_put(exp);
}
/* FTP ALG 모듈 로드 */
// modprobe ip_vs_ftp
// modprobe nf_conntrack_ftp
conntrack 없이 IPVS 운용
대규모 환경에서는 conntrack의 오버헤드를 제거하기 위해 비활성화할 수 있습니다.
단, 이 경우 IPVS 자체 연결 테이블(ip_vs_conn)만 사용하므로
FTP/SIP ALG, SNAT 자동 처리 등 일부 기능이 동작하지 않습니다.
# conntrack 없이 IPVS 운용 (DR 모드에서 주로 사용)
sysctl -w net.ipv4.vs.conntrack=0
# conntrack 모듈 언로드 (DR/TUN 모드 전용 환경)
modprobe -r nf_conntrack_ipv4
modprobe -r nf_conntrack
# IPVS 자체 연결 해시 테이블 크기 조정
# 부팅 파라미터: ip_vs.conn_tab_bits=20 → 2^20 = 1,048,576 버킷
# /etc/modprobe.d/ipvs.conf
echo "options ip_vs conn_tab_bits=20" > /etc/modprobe.d/ipvs.conf
# 현재 연결 테이블 크기 확인
cat /proc/sys/net/ipv4/vs/conn_tab_size
NAT 모드: conntrack SNAT/DNAT 협력
IPVS NAT 모드는 Netfilter conntrack의 DNAT/SNAT와 협력합니다. 패킷이 NF_INET_LOCAL_IN 훅에 도달하면 먼저 conntrack이 DNAT(VIP→RS)를 처리하고, 이어서 IPVS가 연결 테이블을 갱신합니다. 응답 패킷은 conntrack이 역방향 SNAT(RS→VIP)를 수행합니다.
/* IPVS NAT + conntrack 협력 패킷 흐름 */
/* ── 인바운드 SYN (Client → Director) ── */
// 1. NF_INET_PRE_ROUTING: conntrack이 NEW 상태로 등록
// 2. 라우팅 결정: VIP가 로컬 → NF_INET_LOCAL_IN
// 3. conntrack DNAT (ip_tables: DNAT 규칙이 있는 경우)
// 4. IPVS ip_vs_in(): 스케줄러 → RS 선택 → ip_vs_conn_new()
// 5. ip_vs_nat_xmit(): dst IP를 VIP→RS IP로 변환
// 6. NF_INET_LOCAL_OUT → NF_INET_POST_ROUTING → RS로 전송
/* ── 아웃바운드 SYN-ACK (RS → Director) ── */
// 1. NF_INET_PRE_ROUTING (Director에서 수신)
// 2. IPVS ip_vs_out(): src IP를 RS IP→VIP로 변환
// 3. conntrack이 ESTABLISHED로 상태 갱신
// 4. NF_INET_POST_ROUTING → Client로 전송
keepalived와 고가용성 (HA)
keepalived는 IPVS Director의 고가용성을 위한 사실상의 표준 도구입니다. 두 가지 핵심 기능을 제공합니다.
- VRRP (Virtual Router Redundancy Protocol): Active/Standby Director 간 VIP 페일오버
- 헬스체크 (Health Check): RS 장애 감지 후 자동 제거/복구
keepalived는 내부적으로 libipvs를 통해 ipvsadm 규칙을 직접 관리하므로, keepalived 설정만으로 IPVS 서비스 전체를 선언적으로 관리할 수 있습니다.
keepalived.conf 전체 예시
# /etc/keepalived/keepalived.conf
global_defs {
router_id LVS_DIRECTOR_1
script_user root
enable_script_security
}
# ── VRRP 인스턴스: VIP 페일오버 ──
vrrp_instance VI_1 {
state MASTER # Standby는 BACKUP
interface eth0
virtual_router_id 51 # 0-255, 같은 그룹끼리 동일
priority 100 # Standby는 90 (낮을수록 우선순위 낮음)
advert_int 1 # VRRP 광고 주기 (초)
authentication {
auth_type PASS
auth_pass secretkey
}
virtual_ipaddress {
10.0.0.1/24 dev eth0 # VIP
}
}
# ── Virtual Server 선언 ──
virtual_server 10.0.0.1 80 {
delay_loop 6 # 헬스체크 주기 (초)
lb_algo wlc # 스케줄링 알고리즘
lb_kind NAT # 포워딩 모드: NAT | DR | TUN
persistence_timeout 300 # 세션 지속성 (초, 0=비활성)
protocol TCP
# ── Real Server 1 ──
real_server 192.168.100.11 80 {
weight 1
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
connect_port 80
}
}
# ── Real Server 2 ──
real_server 192.168.100.12 80 {
weight 1
HTTP_GET {
url {
path /healthz
status_code 200
}
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
# ── Real Server 3 (고가중치) ──
real_server 192.168.100.13 80 {
weight 3
HTTP_GET {
url {
path /healthz
digest 9b3a0c85a887a0e3cf30b19abe2eadca
}
connect_timeout 3
}
}
}
# ── UDP 서비스 예시 ──
virtual_server 10.0.0.1 53 {
delay_loop 10
lb_algo rr
lb_kind DR
protocol UDP
real_server 192.168.100.11 53 {
weight 1
DNS_CHECK {
type A
name example.com
}
}
}
HA 페일오버 동작
# keepalived 서비스 관리
systemctl enable --now keepalived
# VRRP 상태 확인
journalctl -u keepalived -f
# 현재 IPVS 규칙 확인 (keepalived가 자동 적용)
ipvsadm -Ln
# Active Director 장애 시 시뮬레이션
systemctl stop keepalived # Standby가 MASTER로 전환, VIP 인계
# 헬스체크 실패 시 RS 자동 제거 확인
watch -n1 ipvsadm -Ln
ipvsadm 설정 실전
ipvsadm은 IPVS 커널 모듈과 netlink 소켓(Socket)으로 통신하는 CLI 도구입니다.
규칙은 /etc/ipvsadm.rules 또는 ipvsadm-save로 영구 저장합니다.
기본 명령어
# 커널 모듈 로드
modprobe ip_vs
modprobe ip_vs_rr ip_vs_wrr ip_vs_lc ip_vs_wlc
# ── 서비스 관리 ──
# TCP 서비스 추가
ipvsadm -A -t VIP:PORT -s SCHEDULER [-p TIMEOUT]
# UDP 서비스 추가
ipvsadm -A -u VIP:PORT -s SCHEDULER
# 서비스 수정
ipvsadm -E -t VIP:PORT -s NEW_SCHEDULER
# 서비스 삭제
ipvsadm -D -t VIP:PORT
# ── Real Server 관리 ──
# RS 추가 (-m NAT, -g DR, -i TUN)
ipvsadm -a -t VIP:PORT -r RS_IP:RS_PORT -m -w WEIGHT
# RS 수정 (가중치 변경)
ipvsadm -e -t VIP:PORT -r RS_IP:RS_PORT -m -w NEW_WEIGHT
# RS 삭제
ipvsadm -d -t VIP:PORT -r RS_IP:RS_PORT
# ── 조회 ──
# 전체 규칙 (숫자 포트)
ipvsadm -Ln
# 연결 통계 포함
ipvsadm -Ln --stats
# 연결 속도 포함
ipvsadm -Ln --rate
# 현재 연결 목록
ipvsadm -Lcn
# ── 저장/복원 ──
ipvsadm-save -n > /etc/ipvsadm.rules
ipvsadm-restore < /etc/ipvsadm.rules
# ── 전체 초기화 ──
ipvsadm -C
고급 설정 예시
# HTTPS 서비스 + 세션 지속성 300초
ipvsadm -A -t 10.0.0.1:443 -s wlc -p 300
ipvsadm -a -t 10.0.0.1:443 -r 192.168.100.11:443 -m -w 2
ipvsadm -a -t 10.0.0.1:443 -r 192.168.100.12:443 -m -w 1
# 퍼시스턴스 넷마스크: /24 서브넷 단위 세션 유지
ipvsadm -A -t 10.0.0.1:80 -s sh -p 300 -M 255.255.255.0
# 방화벽 마크 기반 가상 서비스 (포트 무관 로드밸런싱)
# iptables로 마크 설정
iptables -t mangle -A PREROUTING -d 10.0.0.1 -j MARK --set-mark 1
# IPVS에서 마크로 서비스 정의
ipvsadm -A -f 1 -s wlc
ipvsadm -a -f 1 -r 192.168.100.11:0 -m -w 1
# IPv6 지원
ipvsadm -A -t [2001:db8::1]:80 -s rr
ipvsadm -a -t [2001:db8::1]:80 -r [2001:db8::11]:80 -m
성능 튜닝 파라미터
# /etc/sysctl.d/99-ipvs.conf
# IP 포워딩 (NAT 모드 필수)
net.ipv4.ip_forward = 1
# 연결 추적 테이블 크기 (대규모 환경)
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_buckets = 262144
# IPVS 연결 재사용 모드
# 0: 비활성, 1: TIME_WAIT 재사용, 2: 완전 재사용
net.ipv4.vs.conn_reuse_mode = 1
# TIME_WAIT 상태 연결 제어
net.ipv4.vs.expire_nodest_conn = 1
net.ipv4.vs.expire_quiescent_template = 1
# TCP 타임아웃 (기본값 900초는 길다)
net.ipv4.vs.tcp_timeout_established = 300
net.ipv4.vs.tcp_timeout_time_wait = 30
# 동기화 데몬 (백업 Director와 연결 상태 동기화)
# ipvsadm --start-daemon master --mcast-interface eth0
net.ipv4.vs.sync_threshold = "3 50"
# SYN 쿠키 (SYN 플러드 방어)
net.ipv4.tcp_syncookies = 1
# 로컬 포트 범위 확장
net.ipv4.ip_local_port_range = 1024 65535
IPVS vs nftables nat vs HAProxy 비교
L4 로드밸런싱을 구현하는 방법은 여러 가지가 있습니다. 각 방식의 동작 레이어, 성능 특성, 적합한 사용 사례를 비교합니다.
| 항목 | IPVS (DR) | IPVS (NAT) | nftables DNAT | HAProxy (L4) | nginx upstream |
|---|---|---|---|---|---|
| 동작 레이어 | L4 커널 | L4 커널 | L4 커널 | L4 유저스페이스 | L7 유저스페이스 |
| 컨텍스트 스위치 | 없음 | 없음 | 없음 | 있음 | 있음 |
| 룩업 복잡도 | O(1) 해시 | O(1) 해시 | O(n) 규칙 | O(1) 해시 | O(n) 업스트림 |
| TLS 오프로딩(Offloading) | 불가 | 불가 | 불가 | 가능 | 가능 |
| L7 라우팅 | 불가 | 불가 | 불가 | 제한적 | 가능 |
| 헬스체크 | keepalived 별도 | keepalived 별도 | 없음 | 내장 | 내장 |
| 10만 서비스 | O(1) 유지 | O(1) 유지 | 성능 저하 | O(1) 유지 | 메모리 증가 |
| Kubernetes 지원 | kube-proxy IPVS | kube-proxy IPVS | kube-proxy iptables | 별도 Ingress | Ingress Controller |
| 설정 복잡도 | 중간 | 낮음 | 낮음 | 낮음 | 낮음 |
| 권장 용도 | 고성능 LB | 범용 LB | 소규모 | L4 프록시 | L7 프록시 |
세션 지속성(Persistence)
세션 지속성(Persistence)은 동일 클라이언트의 여러 연결을 항상 동일 RS로 유도하는 기능입니다. HTTP/1.1 Keep-Alive, 데이터베이스 연결 풀, SIP 통화처럼 여러 TCP 연결이 동일 백엔드를 사용해야 하는 경우에 필수입니다. IPVS는 지속성 엔진(Persistence Engine)을 플러그인 형태로 지원합니다.
기본 지속성 엔진 (ip_vs_pe_default)
기본 지속성 엔진은 클라이언트 IP + 넷마스크를 키로 사용하여
지속성 테이블(ip_vs_pe_data)에 RS 매핑(Mapping)을 저장합니다.
키 계산은 ip_vs_conn_hashkey_conn() 함수가 담당합니다.
/* include/net/ip_vs.h — 지속성 엔진 인터페이스 */
struct ip_vs_pe {
struct list_head n_list;
char *name; /* 엔진 이름 (예: "sip") */
struct module *module;
/* 지속성 키 계산: skb에서 키를 추출하여 param에 저장 */
int (*fill_param)(struct ip_vs_conn_param *p, struct sk_buff *skb);
/* 지속성 체크: 기존 연결과 현재 패킷의 키가 일치하는지 확인 */
bool (*ct_match)(const struct ip_vs_conn_param *p,
struct ip_vs_conn *ct);
/* 해시값 계산 */
u32 (*hashkey_raw)(const struct ip_vs_conn_param *p, u32 initval,
bool inverse);
int (*show_pe_data)(const struct ip_vs_conn *cp, char *buf);
};
/* 기본 엔진 등록 */
static struct ip_vs_pe ip_vs_pe_default = {
.name = "",
.fill_param = ip_vs_fill_iph_skb,
.hashkey_raw = ip_vs_conn_hashkey_param,
/* ct_match 없음: IP만 비교 */
};
SIP Call-ID 기반 지속성 (ip_vs_pe_sip)
SIP 프로토콜은 동일 통화(Call)의 여러 UDP 패킷이 서로 다른 소스 포트를 사용할 수 있습니다.
클라이언트 IP만으로는 지속성을 보장할 수 없으므로, SIP Call-ID 헤더를 키로 사용합니다.
ip_vs_pe_sip.ko 모듈이 SIP 페이로드(Payload)를 파싱하여 Call-ID를 추출합니다.
# SIP 지속성 설정
modprobe ip_vs_pe_sip
# SIP UDP 443 서비스에 SIP 지속성 엔진 + 360초 타임아웃
ipvsadm -A -u 10.0.0.1:5060 -s rr -p 360 --pe sip
ipvsadm -a -u 10.0.0.1:5060 -r 192.168.100.11:5060 -m -w 1
ipvsadm -a -u 10.0.0.1:5060 -r 192.168.100.12:5060 -m -w 1
# 확인: SIP Call-ID별 지속성 엔트리
ipvsadm -Lcn | grep PERSIST
FWM(Firewall Mark) 기반 지속성
FWM 기반 지속성은 iptables/nftables로 패킷에 마크를 붙여 포트 무관하게 동일 RS로 유도합니다. HTTPS(443)와 HTTP(80)를 동시에 처리하는 서버에서 두 포트의 클라이언트를 같은 RS로 보내야 할 때 유용합니다.
# ── FWM 기반 지속성 설정 완전 예시 ──
# 1. iptables로 HTTP + HTTPS 패킷에 마크 1 설정
iptables -t mangle -A PREROUTING -d 10.0.0.1 -p tcp --dport 80 -j MARK --set-mark 1
iptables -t mangle -A PREROUTING -d 10.0.0.1 -p tcp --dport 443 -j MARK --set-mark 1
# 2. IPVS 마크 기반 가상 서비스 정의 (포트 0 = 모든 포트)
ipvsadm -A -f 1 -s wlc -p 360
# 3. Real Server 추가
ipvsadm -a -f 1 -r 192.168.100.11:0 -m -w 1
ipvsadm -a -f 1 -r 192.168.100.12:0 -m -w 1
# 4. 확인: 마크 기반 서비스 목록
ipvsadm -Ln
# FWM 1 wlc persistent 360
# -> 192.168.100.11:0 Masq 1 0 0
# -> 192.168.100.12:0 Masq 1 0 0
지속성 + DR/TUN 모드 주의점
DR/TUN 모드에서 세션 지속성을 사용할 때는 다음 사항에 주의해야 합니다.
| 상황 | 문제 | 해결책 |
|---|---|---|
| DR + Persistence + NAT 방화벽(Firewall) | 클라이언트 IP가 NAT IP로 마스킹 → 지속성 키 충돌 | 넷마스크 축소(-M 255.255.255.255) 또는 FWM 사용 |
| TUN + IPv6 지속성 | IPIP 터널이 IPv6를 캡슐화하지 못하는 경우 | GUE 캡슐화 타입으로 전환 |
| 지속성 타임아웃 만료 중 RS 제거 | 활성 지속성 엔트리가 남아 있어 새 연결이 삭제된 RS로 향할 수 있음 | expire_quiescent_template=1 sysctl 활성화 |
| sh 스케줄러 + Persistence 중복 | sh 알고리즘 자체가 IP 해시 기반 → -p 옵션과 기능 중복 | sh는 -p 없이 사용, 지속성 필요 시 lc/wlc + -p 조합 선호 |
HTTPS 서비스 지속성 완전 예시
# HTTPS 로드밸런서: 300초 세션 지속성
# 같은 클라이언트의 새 HTTPS 연결은 항상 같은 RS로
ipvsadm -A -t 10.0.0.1:443 -s wlc -p 300
ipvsadm -a -t 10.0.0.1:443 -r 192.168.100.11:443 -m -w 2
ipvsadm -a -t 10.0.0.1:443 -r 192.168.100.12:443 -m -w 1
ipvsadm -a -t 10.0.0.1:443 -r 192.168.100.13:443 -m -w 1
# 지속성 엔트리 확인
ipvsadm -Lcn
# PERSIST 10.0.0.1:443 192.168.100.11:443 300 CIP.CIP.CIP.CIP
# 지속성 + 서브넷 단위 (/24 서브넷의 모든 클라이언트를 같은 RS로)
ipvsadm -A -t 10.0.0.1:443 -s wlc -p 300 -M 255.255.255.0
# ip_vs_persist_conn_out() — 지속성 엔트리 생성 경로
# ip_vs_in() → 신규 연결 → ip_vs_conn_new() → ip_vs_persist_conn_out() 호출
# → persist_template 생성 → 해시 테이블에 (CIP, VIP) 키로 저장
세션 동기화 프로토콜
IPVS 세션 동기화는 Active(Master) Director의 연결 상태를
Backup Director에 실시간으로 전송하여,
Active 장애 발생 시 기존 세션이 끊기지 않도록 합니다.
ip_vs_sync.c에 구현되어 있으며, 멀티캐스트 또는 유니캐스트로 동기화 패킷을 전송합니다.
동기화 프로토콜 v1 vs v2
v2 프로토콜(커널 3.14+)은 IPv6 지원과 확장 속성(PE 데이터, 타임아웃 등)을 추가했습니다.
/* net/netfilter/ipvs/ip_vs_sync.c */
/* v1 동기화 메시지 구조 (IPv4 전용) */
struct ip_vs_sync_conn_v0 {
__u8 reserved;
__u8 protocol; /* TCP/UDP */
__be16 cport; /* 클라이언트 포트 */
__be16 vport; /* VIP 포트 */
__be16 dport; /* RS 포트 */
__be32 fwmark; /* FW 마크 */
__be32 timeout; /* 타임아웃 (초) */
__be32 caddr; /* 클라이언트 IPv4 */
__be32 vaddr; /* VIP IPv4 */
__be32 daddr; /* RS IPv4 */
__be16 flags; /* IP_VS_CONN_F_* */
__be16 state; /* 연결 상태 */
};
/* v2 동기화 메시지 헤더 */
struct ip_vs_sync_mesg {
__u8 reserved;
__u8 syncid; /* 동기화 그룹 ID (0-255) */
__be16 size; /* 전체 메시지 크기 */
__u8 nr_conns; /* 포함된 연결 수 (최대 255) */
__u8 version; /* 프로토콜 버전 (1 or 2) */
__u16 spare;
/* 뒤에 ip_vs_sync_v4/v6 구조체가 nr_conns개 이어짐 */
};
/* v2: IPv6 + 확장 속성 지원 */
struct ip_vs_sync_v6 {
__u8 type; /* STYPE_F_INET6 */
__u8 protocol;
__be16 ver_size; /* 버전 + 크기 */
__be32 flags;
__be16 state;
__be16 cport, vport, dport;
__be32 fwmark;
__be32 timeout;
struct in6_addr caddr, vaddr, daddr; /* IPv6 주소 */
/* 확장 속성: PE 데이터, 세션 지속성 정보 등 */
};
동기화 데몬 설정
# ── Master Director 설정 ──
# syncid=1, eth1 인터페이스로 멀티캐스트 전송
ipvsadm --start-daemon master --syncid 1 --mcast-interface eth1
# ── Backup Director 설정 ──
ipvsadm --start-daemon backup --syncid 1 --mcast-interface eth1
# 기본 멀티캐스트 그룹: 224.0.0.81 (IPv4), ff02::12 (IPv6)
# 기본 포트: UDP 8848
# 유니캐스트 동기화 (방화벽 환경, 커널 4.5+)
ipvsadm --start-daemon master --syncid 1 \
--mcast-interface eth1 \
--sync-maxlen 1500 \
--mcast-port 8848 \
--mcast-ttl 1
# 동기화 상태 확인
ipvsadm --list-daemon
# 동기화 통계
cat /proc/net/ip_vs_stats | grep -i sync
동기화 데몬 내부 스레드(Thread)
/* net/netfilter/ipvs/ip_vs_sync.c — 스레드 구조 */
/*
* Master: sync_thread_master()
* - ipvs->sync_queue에서 동기화할 연결 꺼내기
* - ip_vs_sync_conn() 호출: 연결 → 동기화 패킷 변환
* - 멀티캐스트 소켓으로 전송
* - 배치 처리: sync_send_mesg_maxlen(기본 1460 bytes)까지 묶어 전송
*
* Backup: sync_thread_backup()
* - 멀티캐스트 소켓에서 동기화 패킷 수신
* - ip_vs_process_message() 호출
* - 각 ip_vs_sync_conn_v0 / v2 파싱
* - ip_vs_conn_new() 또는 ip_vs_conn_update() 호출
*/
/* 동기화 전송 임계값 튜닝 */
// 연결 상태가 변할 때마다 즉시 전송하지 않고 임계값 초과 시 전송
// net.ipv4.vs.sync_threshold = "sync_threshold sync_period"
sysctl -w net.ipv4.vs.sync_threshold="3 50"
// → activeconns가 3 이상이거나, 50개 이상 연결이 대기 중이면 전송
// 동기화 패킷 최대 크기 조정 (기본 1460 bytes = MTU - IP/UDP 헤더)
// 크게 설정하면 묶음 전송으로 효율 향상
sysctl -w net.ipv4.vs.sync_sock_size=65536
세션 동기화 없이 HA 구성: ECMP + Maglev Hash
세션 동기화는 멀티캐스트 트래픽과 CPU 오버헤드를 수반합니다. 초고성능 환경에서는 ECMP(Equal-Cost Multi-Path) + Maglev 일관성 해시를 조합하여 상태 동기화 없이도 동일 클라이언트를 항상 동일 Director로 유도합니다. Director 장애 시 해당 Director의 연결만 끊기고 나머지는 유지됩니다.
# ECMP + Maglev Hash (BGP ECMP 환경)
# 1. 각 Director가 동일 VIP를 BGP로 광고
# 2. 업스트림 라우터가 ECMP로 분산 (Maglev hash 선택 시 동일 5-tuple → 동일 Director)
# 3. Director별로 독립적 IPVS 운영 (세션 동기화 불필요)
# IPVS Maglev Hash 스케줄러 (커널 4.18+, ip_vs_mh 모듈)
modprobe ip_vs_mh
ipvsadm -A -t 10.0.0.1:80 -s mh
ipvsadm -a -t 10.0.0.1:80 -r 192.168.100.11:80 -g -w 1
ipvsadm -a -t 10.0.0.1:80 -r 192.168.100.12:80 -g -w 1
# mh 알고리즘: RS 추가/제거 시 기존 연결의 95% 이상 유지
# (일관성 해시: 버킷 테이블 크기 기본 2503)
Primary + Backup IPVS HA 클러스터 완전 예시
# ── Primary Director (10.0.0.2): keepalived.conf ──
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
virtual_ipaddress {
10.0.0.1/24 dev eth0
}
notify_master "/usr/local/bin/ipvs-start-master.sh"
notify_backup "/usr/local/bin/ipvs-start-backup.sh"
}
virtual_server 10.0.0.1 80 {
delay_loop 2
lb_algo wlc
lb_kind DR
persistence_timeout 300
protocol TCP
real_server 192.168.100.11 80 {
weight 1
TCP_CHECK { connect_timeout 3 connect_port 80 }
}
real_server 192.168.100.12 80 {
weight 1
TCP_CHECK { connect_timeout 3 connect_port 80 }
}
}
# ── /usr/local/bin/ipvs-start-master.sh ──
#!/bin/bash
ipvsadm --start-daemon master --syncid 1 --mcast-interface eth1
# ── /usr/local/bin/ipvs-start-backup.sh ──
#!/bin/bash
ipvsadm --stop-daemon master 2>/dev/null
ipvsadm --start-daemon backup --syncid 1 --mcast-interface eth1
XDP/eBPF 기반 IPVS 가속
전통적인 IPVS는 Netfilter 훅에서 동작하여 커널 네트워크 스택을 부분적으로 거칩니다. XDP(eXpress Data Path)를 활용하면 NIC 드라이버 수준에서 패킷을 처리하여 IPVS 대비 2~5배 높은 처리량을 달성할 수 있습니다. Facebook의 Katran이 대표적인 XDP 기반 L4 로드밸런서 구현입니다.
Facebook Katran 아키텍처
Katran은 Facebook이 공개한 오픈소스 XDP L4 로드밸런서입니다. IPVS와 달리 Netfilter를 전혀 거치지 않고 NIC 드라이버 레벨의 XDP 훅에서 패킷을 처리합니다. 세션 상태는 BPF LRU 해시 맵에 저장하며, 백엔드 선택은 Maglev 일관성 해시를 사용합니다.
/* Katran XDP BPF 프로그램 핵심 로직 (C pseudocode) */
/* BPF 맵 정의 */
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, struct vip_definition); /* VIP + 포트 + 프로토콜 */
__type(value, struct vip_meta); /* 백엔드 정보 + Maglev 테이블 인덱스 */
__uint(max_entries, MAX_VIPS);
} vip_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__type(key, struct flow_key); /* 5-tuple */
__type(value, struct real_pos_lru); /* 선택된 백엔드 인덱스 */
__uint(max_entries, DEFAULT_LRU_SIZE); /* 기본 8M 엔트리 */
} lru_map SEC(".maps");
SEC("xdp")
int xdp_katran_lb(struct xdp_md *ctx) {
struct flow_key fkey = {};
struct real_def *real;
/* 1. 패킷 파싱: IP 헤더 + 5-tuple 추출 */
parse_packet(ctx, &fkey);
/* 2. VIP 맵 룩업 */
struct vip_meta *vip = bpf_map_lookup_elem(&vip_map, &fkey.vip);
if (!vip)
return XDP_PASS; /* 비-VIP 패킷은 커널 스택으로 */
/* 3. LRU 세션 테이블 룩업 (기존 세션 재사용) */
struct real_pos_lru *pos = bpf_map_lookup_elem(&lru_map, &fkey);
if (!pos) {
/* 4. 신규 세션: Maglev hash로 백엔드 선택 */
__u32 real_pos = maglev_hash(&fkey, vip->reals_cnt);
/* 5. LRU에 저장 */
bpf_map_update_elem(&lru_map, &fkey, &real_pos, BPF_ANY);
pos = &real_pos;
}
/* 6. 백엔드 정보 조회 */
real = get_real_by_pos(*pos);
/* 7. IPIP 캡슐화: 원본 패킷을 백엔드 IP로 캡슐화 */
encapsulate_ipip(ctx, real->dst);
/* 8. XDP_TX: NIC에서 직접 전송 (커널 스택 우회) */
return XDP_TX;
}
Google Maglev 일관성 해시
Maglev 해시는 Google이 개발한 일관성 해시 알고리즘으로, 백엔드 추가/제거 시 기존 세션의 95% 이상을 동일 백엔드로 유지합니다. ECMP 환경에서 여러 Director가 동일한 선택을 하도록 결정론적(deterministic) 해시를 사용합니다.
/* Maglev 해시 테이블 구성 (백엔드 3개, 테이블 크기 7) */
/* 각 백엔드는 순환 시퀀스로 테이블 슬롯을 채움 */
백엔드 B0: preference = [3, 0, 4, 1, 5, 2, 6]
백엔드 B1: preference = [0, 2, 4, 6, 1, 3, 5]
백엔드 B2: preference = [3, 4, 5, 6, 0, 1, 2]
/* 순서대로 빈 슬롯 채우기 */
슬롯 3 → B0 (B0 첫 번째 선택)
슬롯 0 → B1 (B1 첫 번째 선택)
슬롯 4 → B2 (B2 첫 번째 선택)
슬롯 1 → B0 (슬롯 3 점유, 다음 선택 0 → B1 점유, 4 → B2 점유, 1 → 빈 슬롯)
슬롯 2 → B1
슬롯 5 → B2
슬롯 6 → B0
/* 최종 테이블: [B1, B0, B1, B0, B2, B2, B0] */
/* 패킷 해시 % 7 → 테이블 인덱스 → 백엔드 선택 */
/* ip_vs_mh 커널 구현 (net/netfilter/ipvs/ip_vs_mh.c) */
// 테이블 크기: 기본 2503 (소수), 변경: ipvsadm --mh-port 명령
ipvsadm -A -t 10.0.0.1:80 -s mh --mh-port 2503
ipvsadm -a -t 10.0.0.1:80 -r 192.168.100.11:80 -g -w 1
ipvsadm -a -t 10.0.0.1:80 -r 192.168.100.12:80 -g -w 1
ipvsadm -a -t 10.0.0.1:80 -r 192.168.100.13:80 -g -w 1
XDP L4 LB 한계
| 항목 | IPVS | XDP/Katran |
|---|---|---|
| conntrack 지원 | 지원 (CONFIG_IP_VS_NFCT) | 미지원 (LRU 맵으로 대체) |
| ALG (FTP/SIP) | 모듈로 지원 | BPF 파서 직접 구현 필요 |
| 완전한 NAT | 지원 | 제한적 (주로 IPIP/DSR) |
| NIC 호환성 | 모든 NIC | XDP Native 지원 NIC 필요 |
| 디버깅 도구 | ipvsadm, procfs, conntrack | bpftool, bpftrace, XDP 통계 |
| 운영 복잡도 | 낮음 | 높음 (BPF 프로그램 관리 필요) |
UDP/QUIC 로드밸런싱
IPVS는 TCP와 달리 UDP 로드밸런싱에서 추가적인 고려사항이 있습니다. UDP는 비연결 지향 프로토콜이므로 IPVS는 타임아웃 기반으로만 세션을 관리합니다.
UDP 처리 특수성
/* net/netfilter/ipvs/ip_vs_proto_udp.c */
/* UDP 연결 상태 머신 (매우 단순) */
static const struct ip_vs_timeout_table udp_timeouts[] = {
[IP_VS_UDP_S_NORMAL] = { "UDP", 300 }, /* 기본 300초 */
[IP_VS_UDP_S_LAST] = { "UDP_LAST", 2 },
};
/* UDP 타임아웃은 TCP보다 훨씬 짧아야 함 */
// 기본값 300초는 DNS 쿼리(수십ms)에 과도하게 길다
# UDP 타임아웃 조정
sysctl -w net.ipv4.vs.udp_timeout=30 # 30초로 단축
sysctl -w net.ipv4.vs.udp_timeout_established=30
# UDP 세션 없이 로드밸런싱하는 경우 (상태 추적 불필요)
# --persistence 옵션 없으면 UDP 패킷마다 스케줄러 재호출
ipvsadm -A -u 10.0.0.1:53 -s rr
ipvsadm -a -u 10.0.0.1:53 -r 192.168.100.11:53 -m -w 1
ipvsadm -a -u 10.0.0.1:53 -r 192.168.100.12:53 -m -w 1
QUIC(UDP 443) 로드밸런싱 과제
QUIC 프로토콜은 UDP 443 포트를 사용하며, 클라이언트가 IP를 변경해도 Connection ID를 통해 세션을 유지하는 특성이 있습니다. 클라이언트 IP 기반 세션 지속성만으로는 IP 변경 시나리오(모바일 핸드오버, NAT 변경)를 처리할 수 없습니다.
/* QUIC Connection ID 기반 로드밸런싱 BPF 프로그램 패턴 */
/* QUIC 헤더 파싱: Long Header에서 Connection ID 추출 */
struct quic_long_header {
__u8 first_byte; /* 비트 7: 1 = Long Header */
__be32 version;
__u8 dcil; /* Destination Connection ID 길이 */
/* dcil 바이트의 Destination Connection ID */
/* ... */
};
/* XDP BPF: QUIC Connection ID → 백엔드 선택 */
SEC("xdp")
int xdp_quic_lb(struct xdp_md *ctx) {
__u8 *conn_id = extract_quic_conn_id(ctx);
if (!conn_id)
return XDP_PASS;
/* Connection ID의 특정 바이트에 서버 ID 인코딩 (QUIC LB 초안) */
/* RFC 9000 + draft-ietf-quic-load-balancers 참고 */
__u8 server_id = conn_id[1]; /* 서버 ID 바이트 */
return forward_to_backend(ctx, server_id);
}
# 현실적 대안: IPVS persistence + 충분히 긴 타임아웃
ipvsadm -A -u 10.0.0.1:443 -s sh -p 3600
# sh(Source Hash) = 클라이언트 IP 해시 → 같은 클라이언트는 항상 같은 서버로
DNS UDP 로드밸런싱
# DNS UDP(53) + TCP(53) 동시 로드밸런싱
# UDP DNS: 짧은 타임아웃 + rr 알고리즘
ipvsadm -A -u 10.0.0.1:53 -s rr
ipvsadm -a -u 10.0.0.1:53 -r 192.168.100.11:53 -m -w 1
ipvsadm -a -u 10.0.0.1:53 -r 192.168.100.12:53 -m -w 1
# TCP DNS: 지속성 필요 없음 (보통 1회성 쿼리)
ipvsadm -A -t 10.0.0.1:53 -s rr
ipvsadm -a -t 10.0.0.1:53 -r 192.168.100.11:53 -m -w 1
ipvsadm -a -t 10.0.0.1:53 -r 192.168.100.12:53 -m -w 1
# DNS 헬스체크 (keepalived)
real_server 192.168.100.11 53 {
weight 1
DNS_CHECK {
type A
name healthcheck.example.com
# 응답이 있으면 RS 정상으로 판단
}
}
SIP UDP 로드밸런싱
# SIP(5060/UDP) 로드밸런싱: ip_vs_pe_sip 모듈 사용
modprobe ip_vs_pe_sip
# SIP Call-ID 기반 지속성 (같은 통화는 항상 같은 SIP 서버로)
ipvsadm -A -u 10.0.0.1:5060 -s rr -p 3600 --pe sip
ipvsadm -a -u 10.0.0.1:5060 -r 192.168.100.11:5060 -m -w 1
ipvsadm -a -u 10.0.0.1:5060 -r 192.168.100.12:5060 -m -w 1
# RTCP(5061)도 같은 SIP 서버로: FWM 활용
iptables -t mangle -A PREROUTING -d 10.0.0.1 -p udp --dport 5060 -j MARK --set-mark 10
iptables -t mangle -A PREROUTING -d 10.0.0.1 -p udp --dport 5061 -j MARK --set-mark 10
ipvsadm -A -f 10 -s rr -p 3600 --pe sip
ipvsadm -a -f 10 -r 192.168.100.11:0 -m -w 1
ipvsadm -a -f 10 -r 192.168.100.12:0 -m -w 1
게임 서버 UDP 로드밸런싱
# 게임 서버: 짧은 지연시간 + 세션 지속성 필수
# 클라이언트가 방에 입장하면 같은 게임 서버 유지
# 전략 1: sh(Source Hash) 알고리즘 — IP 기반 고정 배정
ipvsadm -A -u 10.0.0.1:7777 -s sh
ipvsadm -a -u 10.0.0.1:7777 -r 192.168.100.11:7777 -m -w 1
ipvsadm -a -u 10.0.0.1:7777 -r 192.168.100.12:7777 -m -w 1
# 전략 2: DR 모드 + persistence (응답 지연 최소화)
# 게임 서버가 같은 L2 서브넷에 있는 경우
ipvsadm -A -u 10.0.0.1:7777 -s wlc -p 1800
ipvsadm -a -u 10.0.0.1:7777 -r 10.0.0.11:7777 -g -w 1
ipvsadm -a -u 10.0.0.1:7777 -r 10.0.0.12:7777 -g -w 1
# DR 모드 RS 설정 (UDP도 동일)
# lo에 VIP 추가 + arp_ignore/arp_announce 설정 필수
Kubernetes/컨테이너(Container) 환경 IPVS
Kubernetes kube-proxy는 서비스 트래픽을 처리하는 두 가지 주요 모드를 제공합니다: iptables 모드와 IPVS 모드. 서비스 수가 많을수록 IPVS 모드의 성능 우위가 뚜렷해집니다.
kube-proxy IPVS 모드 활성화
# kube-proxy ConfigMap 설정 (kubeadm 클러스터)
kubectl edit configmap kube-proxy -n kube-system
# 다음 설정 추가/변경:
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
ipvs:
scheduler: "wlc" # 스케줄링 알고리즘
syncPeriod: 30s # IPVS 규칙 동기화 주기
minSyncPeriod: 5s # 최소 동기화 주기
strictARP: false # true: kube-ipvs0 ARP 응답 억제
# kube-proxy DaemonSet 재시작
kubectl rollout restart daemonset kube-proxy -n kube-system
# IPVS 모드 확인
kubectl logs -n kube-system -l k8s-app=kube-proxy | grep "Using ipvs"
# 노드에서 IPVS 규칙 확인
ipvsadm -Ln
# TCP 10.96.0.1:443 wlc persistent 10800
# -> 10.244.0.2:8443 Masq 1 0 0
# -> 10.244.1.3:8443 Masq 1 0 0
kube-proxy가 생성하는 IPVS 규칙 패턴
# ClusterIP 서비스 → IPVS Virtual Service
# kubectl get svc my-service → ClusterIP: 10.96.100.1, Port: 80
ipvsadm -Ln | grep -A3 "10.96.100.1"
# TCP 10.96.100.1:80 rr
# -> 10.244.0.5:80 Masq 1 0 0
# -> 10.244.1.6:80 Masq 1 0 0
# NodePort 서비스 → 모든 노드 IP + NodePort
ipvsadm -Ln | grep -A3 "32080"
# TCP 192.168.1.10:32080 rr ← 노드 IP
# TCP 127.0.0.1:32080 rr ← loopback
# TCP 10.244.0.1:32080 rr ← Pod CIDR 게이트웨이
# sessionAffinity: ClientIP → IPVS persistence (-p 10800초)
# kubectl patch svc my-svc -p '{"spec":{"sessionAffinity":"ClientIP"}}'
ipvsadm -Ln | grep "10.96.100.1"
# TCP 10.96.100.1:80 rr persistent 10800
kube-proxy IPVS vs Cilium kube-proxy replacement 비교
| 항목 | kube-proxy IPVS | Cilium kube-proxy replacement |
|---|---|---|
| 구현 | IPVS + iptables (MASQ) | eBPF (XDP + TC hook) |
| 연결 추적 | conntrack 사용 | eBPF 맵 (conntrack 없이) |
| NodePort 처리 | IPVS + SNAT | XDP 레벨 (NIC에서 직접) |
| 서비스 룩업 | O(1) (IPVS 해시) | O(1) (BPF 해시 맵) |
| 10,000 서비스 지연 | ~50 μs | ~10 μs |
| 네트워크 정책 | 별도 도구 필요 | eBPF 네트워크 정책 내장 |
| 설치 복잡도 | 낮음 (기본 포함) | 높음 (CNI 교체 필요) |
| 관찰 가능성 | ipvsadm, conntrack | Hubble (L7까지 가시화) |
컨테이너 오버레이(Overlay) 네트워크에서 FULLNAT 필요성
Kubernetes에서 오버레이 네트워크(VXLAN, GENEVE)를 사용할 때, kube-proxy IPVS NAT 모드는 Pod IP로 DNAT 후 오버레이로 전송합니다. 이 경우 RS(Pod)는 패킷의 소스 IP가 클라이언트 IP 대신 노드 IP로 보이는 문제가 발생합니다. FULLNAT 또는 SNAT를 통해 일관된 소스 IP를 보장해야 합니다.
# K8s IPVS NAT 모드에서 SNAT 동작 확인
# 외부 클라이언트(203.0.113.1) → NodePort(192.168.1.10:32080) → Pod(10.244.1.6:80)
# 노드에서 iptables POSTROUTING 규칙 확인
iptables -t nat -L POSTROUTING -n | grep KUBE-POSTROUTING
# KUBE-POSTROUTING ... 0x4000/0x4000 MASQUERADE
# IPVS + iptables 협력 흐름:
# 1. 패킷 수신: src=203.0.113.1, dst=192.168.1.10:32080
# 2. IPVS: dst → 10.244.1.6:80 (DNAT)
# 3. iptables KUBE-MARK-MASQ: 마크 0x4000 설정
# 4. POSTROUTING: 마크 있으면 MASQUERADE → src=10.244.X.1 (노드 Pod IP)
# 5. Pod에서 수신: 클라이언트 IP가 노드 IP로 마스킹됨
# X-Forwarded-For 또는 PROXY Protocol로 원본 IP 전달 필요
커널 소스 구조
IPVS 소스는 net/netfilter/ipvs/ 디렉터리에 집중되어 있습니다.
총 약 50개 파일, 6만 줄 규모입니다.
| 파일 | 역할 |
|---|---|
ip_vs_core.c |
핵심 패킷 처리, Netfilter 훅 등록, ip_vs_in() / ip_vs_out() |
ip_vs_conn.c |
연결 해시 테이블, ip_vs_conn_new() / ip_vs_conn_get() / ip_vs_conn_put() |
ip_vs_ctl.c |
netlink 제어 인터페이스, ipvsadm 통신, sysctl 등록 |
ip_vs_sched.c |
스케줄러 등록/해제 API, ip_vs_scheduler_bind() |
ip_vs_rr.c |
Round Robin 스케줄러 |
ip_vs_wlc.c |
Weighted Least Connection 스케줄러 |
ip_vs_lblc.c |
Locality-Based Least Connection 스케줄러 (캐시 테이블 포함) |
ip_vs_nat.c |
NAT 모드 패킷 변환, ip_vs_nat_xmit() |
ip_vs_tunnel.c |
TUN 모드 IP-in-IP 캡슐화, ip_vs_tunnel_xmit() |
ip_vs_xmit.c |
DR 모드 MAC 변환 및 전송, ip_vs_dr_xmit() |
ip_vs_nfct.c |
conntrack 연동, ip_vs_nfct_bind_dest() |
ip_vs_sync.c |
멀티캐스트 연결 상태 동기화 (Active/Standby Director) |
ip_vs_proto_tcp.c |
TCP 특화 처리, 상태 기계, 포트 변환 |
ip_vs_proto_udp.c |
UDP 특화 처리, 타임아웃 관리 |
ip_vs_est.c |
통계 추정기 (연결 속도, 바이트 레이트) |
include/net/ip_vs.h |
전체 자료구조 정의 (ip_vs_conn, ip_vs_service, ip_vs_dest 등) |
커널 빌드 설정
# .config 관련 옵션
CONFIG_IP_VS=m # IPVS 코어
CONFIG_IP_VS_IPV6=y # IPv6 지원
CONFIG_IP_VS_DEBUG=n # 디버그 (성능 저하)
CONFIG_IP_VS_NFCT=y # conntrack 연동
CONFIG_IP_VS_PROTO_TCP=y
CONFIG_IP_VS_PROTO_UDP=y
CONFIG_IP_VS_PROTO_SCTP=y
# 스케줄러
CONFIG_IP_VS_RR=m
CONFIG_IP_VS_WRR=m
CONFIG_IP_VS_LC=m
CONFIG_IP_VS_WLC=m
CONFIG_IP_VS_LBLC=m
CONFIG_IP_VS_LBLCR=m
CONFIG_IP_VS_DH=m
CONFIG_IP_VS_SH=m
CONFIG_IP_VS_SED=m
CONFIG_IP_VS_NQ=m
진단 및 모니터링
IPVS 문제 진단은 /proc 인터페이스, ipvsadm 통계, conntrack, 그리고 eBPF/ftrace를 활용합니다.
procfs 인터페이스
# ── /proc 인터페이스 ──
# 현재 IPVS 서비스 및 RS 목록
cat /proc/net/ip_vs
# 현재 활성 연결 (5-tuple 전체)
cat /proc/net/ip_vs_conn
# IPVS 통계 (패킷/바이트/연결 수)
cat /proc/net/ip_vs_stats
# 상세 통계 (CPU별)
cat /proc/net/ip_vs_stats_percpu
ipvsadm 모니터링
# 실시간 통계 (1초 갱신)
watch -n1 'ipvsadm -Ln --stats'
# 연결 속도 (CPS, 초당 새 연결)
ipvsadm -Ln --rate
# 특정 서비스만
ipvsadm -Ln -t 10.0.0.1:80 --stats
# 현재 연결 수 (활성 + 비활성)
ipvsadm -Lcn | wc -l
# RS별 연결 분포 확인
ipvsadm -Ln | awk '/->/{print $2, $5}'
conntrack 진단
# IPVS 관련 conntrack 항목 확인
conntrack -L -p tcp --dport 80 | head -20
# 연결 수 집계
conntrack -C
# 연결 테이블 포화 확인 (drops > 0 이면 문제)
conntrack -S | grep drop
# 실시간 이벤트 추적
conntrack -E -p tcp --dport 80
eBPF/ftrace 진단
# bpftrace: ip_vs_in 함수 진입 추적
bpftrace -e '
kprobe:ip_vs_in {
@calls = count();
}
interval:s:1 {
print(@calls);
clear(@calls);
}'
# ftrace: IPVS 함수 추적
echo 'ip_vs_*' > /sys/kernel/debug/tracing/set_ftrace_filter
echo function > /sys/kernel/debug/tracing/current_tracer
cat /sys/kernel/debug/tracing/trace_pipe
# perf: IPVS 관련 심볼 샘플링
perf top -p $(pgrep -x keepalived) --kallsyms=/proc/kallsyms
# 연결 해시 충돌 확인 (해시 테이블 부족 신호)
bpftrace -e '
kprobe:ip_vs_conn_get {
@gets = count();
}
kretprobe:ip_vs_conn_get {
if (retval == 0) @misses = count();
}
interval:s:5 {
print(@gets); print(@misses);
clear(@gets); clear(@misses);
}'
자주 발생하는 문제와 해결책
| 증상 | 원인 | 해결책 |
|---|---|---|
| RS로 연결이 안 됨 (NAT) | RS의 기본 게이트웨이가 Director가 아님 | ip route add default via DIRECTOR_IP |
| DR 모드에서 응답 없음 | RS의 ARP 억제 미설정 또는 VIP 미설정 | arp_ignore=1, arp_announce=2, lo에 VIP 추가 |
| 연결이 갑자기 끊김 | conntrack 테이블 포화 (nf_conntrack: table full) | nf_conntrack_max 증가, 타임아웃 단축 |
| 특정 RS에만 연결 집중 | conn_reuse_mode=0으로 TIME_WAIT 연결 재사용 | sysctl net.ipv4.vs.conn_reuse_mode=1 |
| keepalived 재시작(Reboot) 후 연결 끊김 | IPVS 연결 상태 동기화 미설정 | ipvsadm --start-daemon master/backup 설정 |
| SYN 패킷이 RS에 도달하지 않음 | rp_filter가 소스 IP를 차단 | sysctl net.ipv4.conf.all.rp_filter=0 |
IPVS 커널 내부 구조
IPVS의 핵심 성능은 효율적인 해시 테이블 설계에서 비롯됩니다.
ip_vs_conn 해시 테이블, 서비스/목적지 룩업, 타이머(Timer) 관리의 내부 동작을 상세히 살펴봅니다.
ip_vs_conn 해시 테이블 구조
IPVS 연결 해시 테이블은 ip_vs_conn_tab[] 배열로 구현됩니다.
해시 함수는 5-tuple(프로토콜, 소스 IP, 소스 포트, 목적지 IP, 목적지 포트)을 입력받아
Jenkins hash를 기반으로 버킷 인덱스를 계산합니다.
/* net/netfilter/ipvs/ip_vs_conn.c — 해시 테이블 핵심 */
/* 전역 연결 해시 테이블 */
static struct hlist_head *ip_vs_conn_tab;
static unsigned int ip_vs_conn_tab_size; /* 2^conn_tab_bits */
static unsigned int ip_vs_conn_tab_mask; /* size - 1 */
/* 해시 함수: 5-tuple → 버킷 인덱스 */
static inline unsigned int
ip_vs_conn_hashkey(struct netns_ipvs *ipvs, int af,
unsigned int proto,
const union nf_inet_addr *addr,
__be16 port)
{
return jhash_3words(
(__force u32)addr->ip, (__force u32)port, proto,
ipvs->conn_rnd /* 랜덤 시드: 해시 충돌 공격 방지 */
) & ip_vs_conn_tab_mask;
}
/* 연결 탐색: RCU read lock으로 락 없이 수행 */
struct ip_vs_conn *
ip_vs_conn_get(int af, int protocol,
const union nf_inet_addr *s_addr, __be16 s_port,
const union nf_inet_addr *d_addr, __be16 d_port)
{
unsigned int hash;
struct ip_vs_conn *cp;
hash = ip_vs_conn_hashkey(ipvs, af, protocol, s_addr, s_port);
rcu_read_lock();
hlist_for_each_entry_rcu(cp, &ip_vs_conn_tab[hash], c_list) {
if (cp->af == af &&
ip_vs_addr_equal(af, &cp->caddr, s_addr) &&
ip_vs_addr_equal(af, &cp->vaddr, d_addr) &&
cp->cport == s_port &&
cp->vport == d_port &&
cp->protocol == protocol) {
if (!__ip_vs_conn_get(cp))
continue;
rcu_read_unlock();
return cp;
}
}
rcu_read_unlock();
return NULL;
}
/* 연결 생성: atomic + spinlock으로 동시 삽입 방지 */
struct ip_vs_conn *
ip_vs_conn_new(const struct ip_vs_conn_param *p, int dest_af,
const union nf_inet_addr *daddr, __be16 dport,
unsigned int flags, struct ip_vs_dest *dest, __u32 fwmark)
{
struct ip_vs_conn *cp;
cp = kmem_cache_alloc(ip_vs_conn_cachep, GFP_ATOMIC);
/* 5-tuple, dest, flags 초기화 */
/* 타이머 설정: setup_timer(&cp->timer, ip_vs_conn_expire, cp) */
/* 해시 테이블 삽입: hlist_add_head_rcu() */
ip_vs_bind_dest(cp, dest);
return cp;
}
연결 타이머 관리
각 ip_vs_conn은 프로토콜별 상태 머신에 따른 타이머를 가집니다.
TCP의 경우 SYN_RECV, ESTABLISHED, FIN_WAIT, CLOSE_WAIT 등 11개 상태를 추적하며,
각 상태에 독립적인 타임아웃을 설정할 수 있습니다.
/* net/netfilter/ipvs/ip_vs_proto_tcp.c — TCP 상태 타임아웃 */
static const int tcp_timeouts[IP_VS_TCP_S_LAST+1] = {
[IP_VS_TCP_S_NONE] = 2, /* 초기: 2초 */
[IP_VS_TCP_S_ESTABLISHED] = 900, /* 연결됨: 15분 (기본) */
[IP_VS_TCP_S_SYN_SENT] = 120, /* SYN 전송: 2분 */
[IP_VS_TCP_S_SYN_RECV] = 60, /* SYN 수신: 1분 */
[IP_VS_TCP_S_FIN_WAIT] = 120,
[IP_VS_TCP_S_TIME_WAIT] = 120,
[IP_VS_TCP_S_CLOSE] = 10,
[IP_VS_TCP_S_CLOSE_WAIT] = 60,
[IP_VS_TCP_S_LAST_ACK] = 30,
[IP_VS_TCP_S_LISTEN] = 120,
[IP_VS_TCP_S_SYNACK] = 30,
[IP_VS_TCP_S_LAST] = 2,
};
/* 타이머 만료 콜백 */
static void ip_vs_conn_expire(struct timer_list *t)
{
struct ip_vs_conn *cp = from_timer(cp, t, timer);
/* 1. 연결을 해시 테이블에서 제거 */
ip_vs_conn_unhash(cp);
/* 2. 바인딩된 dest의 activeconns/inactconns 감소 */
ip_vs_unbind_dest(cp);
/* 3. conntrack 연결 해제 (CONFIG_IP_VS_NFCT) */
ip_vs_nfct_release(cp);
/* 4. 동기화 백업에 만료 통보 */
ip_vs_sync_conn(ipvs, cp, sysctl_sync_threshold(ipvs));
/* 5. 캐시 해제 */
kmem_cache_free(ip_vs_conn_cachep, cp);
}
서비스 룩업 경로 (ip_vs_service_find)
ip_vs_in()이 호출되면 다음 순서로 처리합니다.
ip_vs_in() 패킷 처리 흐름:
1. ip_vs_fill_iph_skb() — IP 헤더 파싱
2. ip_vs_conn_get() — 기존 연결 해시 O(1) 룩업
→ 히트: 즉시 포워딩 (packet_xmit)
→ 미스: 3번으로
3. ip_vs_service_find() — VIP:Port:Protocol 서비스 탐색
→ 미스: NF_ACCEPT (비-IPVS 패킷)
4. svc->scheduler->schedule() — 스케줄러 호출, RS 선택
5. ip_vs_conn_new() — 새 연결 생성 (kmem_cache_alloc)
6. cp->packet_xmit() — 포워딩 모드별 전송
NAT → ip_vs_nat_xmit()
DR → ip_vs_dr_xmit()
TUN → ip_vs_tunnel_xmit()
IPVS 통계는 서비스/목적지별로 ip_vs_stats 구조체를 유지하며,
per-CPU 카운터(ip_vs_kstats __percpu *cpustats)를 사용하여
패킷 처리 경로에서 락 없이 카운터를 업데이트합니다. 읽기 시에만 모든 CPU의 값을 합산합니다.
FULLNAT: TOA와 원본 IP 전달
FULLNAT 모드에서 가장 큰 과제는 RS가 원본 클라이언트 IP를 알 수 없는 점입니다. 이를 해결하는 세 가지 접근 방식이 있습니다.
TOA (TCP Option Address) 메커니즘 상세
TOA TCP 옵션 포맷 (IPv4):
+------+------+------+------+------+------+------+------+
| Kind | Len | Client IPv4 Address | Port |
| 0xFE | 0x08 | 4 bytes | 2 bytes|
+------+------+------+------+------+------+------+------+
Director: SYN 패킷 FULLNAT 변환 시 TCP Options에 TOA 삽입 (ip_vs_fullnat_insert_toa)
RS: toa.ko 모듈 로드 → getpeername()/getsockopt() 시 원본 IP 반환
Proxy Protocol v2 대안
TOA는 TCP 전용이고 커널 패치가 필요합니다. UDP 환경이나 커널 수정이 불가한 경우
Proxy Protocol v2를 사용할 수 있습니다. Director가 페이로드 앞에 16바이트 헤더(원본 IP:port 포함)를
삽입하고, RS의 애플리케이션(Nginx listen 80 proxy_protocol, HAProxy bind *:80 accept-proxy)이 파싱합니다.
FULLNAT 포트 고갈 문제
FULLNAT은 모든 연결의 소스 IP를 DIP(Director Internal IP)로 변환하므로, 단일 DIP의 임시 포트(기본 1024~65535)가 부족해질 수 있습니다. 대규모 환경에서는 여러 DIP를 풀로 관리합니다.
# FULLNAT 포트 고갈 방지 전략
# 1. DIP 풀: 여러 내부 IP를 Director에 할당
# Alibaba LVS-FULLNAT 패치에서 local_address 풀 지원
# ip addr add 172.16.0.1/32 dev lo
# ip addr add 172.16.0.2/32 dev lo
# ipvsadm --set --local-address 172.16.0.1-172.16.0.10
# 2. 포트 범위 확장
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
# → 64,511 포트 × DIP 10개 = 645,110 동시 연결
# 3. TIME_WAIT 빠른 재사용
sysctl -w net.ipv4.tcp_tw_reuse=1
# 4. 모니터링: DIP별 사용 포트 수
ss -tn state established | awk '{print $4}' | cut -d: -f1 | sort | uniq -c | sort -rn
Kubernetes kube-proxy IPVS 모드
kube-proxy IPVS 모드는 단순히 iptables 대신 IPVS를 사용하는 것 이상으로, ipset, dummy 인터페이스, iptables 마스커레이드 등 여러 컴포넌트를 조합하여 동작합니다.
ipset 연동 상세
kube-proxy IPVS 모드는 여전히 일부 iptables 규칙을 사용하지만, ipset으로 매칭하여 규칙 수를 고정으로 유지합니다. 서비스가 10,000개여도 iptables 규칙 수는 약 10개로 동일합니다.
# kube-proxy가 생성하는 ipset 목록
ipset list -n
# KUBE-CLUSTER-IP — 모든 ClusterIP:Port (hash:ip,port)
# KUBE-NODE-PORT-TCP/UDP — NodePort 포트 (bitmap:port)
# KUBE-LOOP-BACK — hairpin MASQUERADE 대상 (hash:ip,port,ip)
# KUBE-EXTERNAL-IP — externalIPs (hash:ip,port)
# KUBE-LOAD-BALANCER — LB Ingress IP (hash:ip,port)
# KUBE-*-LOCAL-* — externalTrafficPolicy=Local 전용
# iptables는 ipset 매칭으로 고정 ~10개 규칙 유지
iptables -t nat -L KUBE-SERVICES -n
# -m set --match-set KUBE-CLUSTER-IP dst,dst -j KUBE-MARK-MASQ
Graceful Termination 동작
Kubernetes 1.22+에서 EndpointSlice의 Terminating 상태를 활용하여
Pod 종료 시 기존 연결을 보호합니다. kube-proxy는 Terminating 상태의 Pod에 대해
IPVS weight를 0으로 설정하여 신규 연결만 차단하고, 기존 연결은 유지합니다.
# Graceful Termination 확인
ipvsadm -Ln | grep 10.244.0.5
# -> 10.244.0.5:80 Masq 0 5 0 ← weight=0(새 연결 차단), activeconns=5(기존 유지)
# 권장 sysctl
sysctl -w net.ipv4.vs.expire_nodest_conn=1 # Pod IP 사라지면 연결 즉시 만료
sysctl -w net.ipv4.vs.expire_quiescent_template=1 # weight=0 시 persist template 만료
서비스 타입별 IPVS 매핑
| 서비스 타입 | IPVS Virtual Service 생성 | ipset |
|---|---|---|
| ClusterIP | ClusterIP:Port VS (kube-ipvs0에 IP 바인딩) |
KUBE-CLUSTER-IP |
| NodePort | ClusterIP VS + NodeIP:NodePort VS (모든 노드 IP) |
KUBE-NODE-PORT-* |
| LoadBalancer | ClusterIP + NodePort + LB_IP:Port VS |
KUBE-LOAD-BALANCER |
| ExternalIP | ClusterIP + ExternalIP:Port VS |
KUBE-EXTERNAL-IP |
Maglev 일관성 해싱
Maglev는 Google이 2016년 NSDI 논문에서 공개한 일관성 해싱 알고리즘입니다. 기존 일관성 해시(Karger et al.)와 달리 모든 Director가 동일한 lookup 테이블을 구성하므로 ECMP 환경에서 상태 동기화 없이도 동일한 백엔드 선택을 보장합니다.
백엔드 추가/제거 시 리밸런싱
Maglev 해시의 핵심 장점은 백엔드 변경 시 최소한의 세션만 재배치(Relocation)되는 점입니다. N개 백엔드에서 1개를 제거하면 이론적으로 M/N개 슬롯(전체의 1/N)만 변경됩니다. 실제로는 테이블 크기 M이 소수이고 충분히 클 때(기본 2503) 95% 이상의 세션이 유지됩니다.
백엔드 3개(B0,B1,B2) → B2 제거 시:
원본: [B1, B0, B1, B0, B2, B2, B0]
변경: [B1, B0, B1, B0, B0, B1, B0] ← 슬롯 4,5만 변경 (2/7 = 28.6%)
ip_vs_mh.c 테이블 크기: 기본 2503(소수), 대규모 65537
재구성 비용: O(M × N) → 2503 × 100 = ~250K 연산 (~1ms)
가중치: ip_vs_mh_gcd_weight()로 weight 비율만큼 슬롯 배분
mh vs sh 비교
| 항목 | sh (Source Hash) | mh (Maglev Hash) |
|---|---|---|
| RS 변경 시 세션 유지율 | ~50% (단순 modulo) | ~95% (일관성 해시) |
| ECMP 호환 | 불가 (Director별 다른 결과) | 가능 (결정론적 테이블) |
| 가중치/커널 | 미지원 / 2.6.10+ | GCD 기반 지원 / 4.18+ |
keepalived Health Check
keepalived는 다양한 헬스체크 방식을 제공하여 RS의 가용성을 감시합니다. 헬스체크 실패 시 자동으로 RS의 weight를 0으로 변경하거나 IPVS에서 제거합니다.
헬스체크 종류 상세
| 체크 종류 | 동작 | 적합한 서비스 | 설정 예시 |
|---|---|---|---|
TCP_CHECK |
TCP 3-way handshake 성공 여부만 확인 | TCP 포트 열림만 확인하면 되는 경우 | connect_timeout 3 |
HTTP_GET |
HTTP GET 요청 후 상태 코드 또는 digest 검증 | 웹 서버, API 서버 | url { path /healthz status_code 200 } |
SSL_GET |
HTTPS GET 요청 + TLS handshake 검증 | HTTPS 서비스 | url { path /healthz status_code 200 } |
MISC_CHECK |
외부 스크립트 실행 (종료 코드 0=정상) | 커스텀 헬스체크 (DB, 큐 등) | misc_path "/usr/local/bin/check_db.sh" |
DNS_CHECK |
DNS 쿼리 성공 여부 확인 | DNS 서버 | type A name health.example.com |
SMTP_CHECK |
SMTP HELO 응답 확인 | 메일 서버 | host { connect_ip RS_IP } |
헬스체크 타이머 상세 설정
# keepalived.conf — 헬스체크 상세 설정
virtual_server 10.0.0.1 80 {
delay_loop 6 # 헬스체크 주기 (초)
lb_algo wlc
lb_kind NAT
sorry_server 192.168.100.99 80 # 모든 RS 실패 시 대체 서버
real_server 192.168.100.11 80 {
weight 2
inhibit_on_failure # 실패 시 weight=0 (삭제 대신)
TCP_CHECK {
connect_timeout 3 # 연결 타임아웃
nb_get_retry 3 # 재시도 횟수
delay_before_retry 3 # 재시도 간격
}
}
real_server 192.168.100.12 80 {
weight 1
HTTP_GET {
url { path /healthz status_code 200 }
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
real_server 192.168.100.13 80 {
weight 1
MISC_CHECK {
misc_path "/usr/local/bin/check_backend.sh 192.168.100.13"
misc_timeout 10
misc_dynamic # 종료코드 0=정상, 1=실패, 2~255=weight
}
}
}
notify 스크립트와 inhibit_on_failure
RS 상태 변경 시 notify_up/notify_down 스크립트를 호출하여
Slack, PagerDuty 등에 알림을 보낼 수 있습니다.
inhibit_on_failure는 RS 장애 시 IPVS에서 삭제하지 않고 weight를 0으로 변경하여
기존 연결을 보호합니다. RS 복구 시 자동으로 원래 weight로 복원됩니다.
# inhibit_on_failure 동작 흐름
정상: weight=2 → 새 연결 수신
실패: weight=0 → 새 연결 차단, 기존 연결 유지
복구: weight=2 → 새 연결 재개
# 장애 감지 최대 시간 계산
delay_loop + (nb_get_retry × (connect_timeout + delay_before_retry))
= 6 + (3 × (3 + 3)) = 24초
대규모 환경 운영 (10만+ 동시 세션)
IPVS를 수십만 동시 연결 규모로 운영할 때는 커널 파라미터, 연결 테이블, CPU 친화성, 메모리 레이아웃 등을 세밀하게 튜닝해야 합니다.
연결 해시 테이블 및 conntrack 튜닝
# ── conn_tab_bits: 해시 버킷 수 (부팅 시 설정, 런타임 변경 불가) ──
# 기본 12(4K 버킷) → 10만 연결 시 버킷당 24개 체인 → 성능 저하
# 권장: 100만 연결 → bits=20 (1M 버킷, 메모리 ~8MB)
echo "options ip_vs conn_tab_bits=20" > /etc/modprobe.d/ipvs.conf
# ── conntrack 분리 (DR/TUN 전용 환경) ──
sysctl -w net.ipv4.vs.conntrack=0
iptables -t raw -A PREROUTING -j NOTRACK
iptables -t raw -A OUTPUT -j NOTRACK
# ── NAT 모드: conntrack 테이블 확장 ──
sysctl -w net.netfilter.nf_conntrack_max=2097152
echo 524288 > /sys/module/nf_conntrack/parameters/hashsize
nf_conntrack_max를 충분히 높이세요.
CPU Affinity 및 RPS/RFS 설정
# IRQ 핀닝: irqbalance 비활성화 후 NIC 큐별 CPU 고정
systemctl stop irqbalance
echo 1 > /proc/irq/48/smp_affinity # eth0-TxRx-0 → CPU 0
echo 2 > /proc/irq/49/smp_affinity # eth0-TxRx-1 → CPU 1
# RPS: 소프트웨어 큐 분산 (16 CPU 예시)
echo ffff > /sys/class/net/eth0/queues/rx-0/rps_cpus
# RFS: 연결별 CPU 고정
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
echo 4096 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
10만+ 세션 튜닝 체크리스트
# IPVS 핵심 파라미터
echo "options ip_vs conn_tab_bits=20" > /etc/modprobe.d/ipvs.conf # 1M 버킷
sysctl -w net.ipv4.vs.timeout_established=180 # 기본 900→180
sysctl -w net.ipv4.vs.expire_nodest_conn=1 # RS 제거 시 즉시 만료
sysctl -w net.ipv4.vs.expire_quiescent_template=1
# 네트워크 스택
sysctl -w net.netfilter.nf_conntrack_max=2097152
sysctl -w net.core.somaxconn=65535
sysctl -w net.core.netdev_max_backlog=100000
sysctl -w net.ipv4.ip_local_port_range="1024 65535" # FULLNAT 포트 확장
참고 자료
- 커널 공식 문서: IPVS sysctl 파라미터 — IPVS 커널 모듈의 sysctl 튜닝 파라미터 공식 문서입니다
- Linux Virtual Server 프로젝트 — IPVS의 원래 프로젝트 사이트로 아키텍처 및 스케줄링 알고리즘 상세 문서를 제공합니다
- ipvsadm(8) 매뉴얼 — IPVS 관리 도구의 명령 옵션과 사용법을 설명합니다
- keepalived 매뉴얼 — IPVS 헬스체크 및 VRRP 고가용성 데몬 설정 문서입니다
- LWN: Load balancing with IPVS — 커널 기반 L4 로드밸런싱의 아키텍처를 분석합니다
- Kubernetes: Virtual IPs and Service Proxies — kube-proxy IPVS 모드의 공식 설명입니다
- kube-proxy IPVS 구현 소스 — Kubernetes에서 IPVS를 활용하는 구현 코드입니다
- Meta: Katran — XDP 기반 L4 로드밸런서 — IPVS 대안으로 XDP를 활용한 고성능 로드밸런서 사례입니다
- 커널 소스: net/netfilter/ipvs/ — IPVS 커널 모듈의 소스 코드 디렉터리입니다
관련 문서
IPVS와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.