본문으로 바로가기 본문 바로가기
WIKI 기술 지식 베이스

BGP 없이 파드와 서비스의 L2 도달성 확보하기

원문 보기 위키 갱신

BGP를 구성하지 않고도 로컬 레이어 2(L2) 네트워크에서 파드와 LoadBalancer 서비스 VIP에 도달할 수 있게 만드는 방법을 다뤄요. Calico의 사용자 공간 ARP/NDP 응답기(responder)가 핵심 역할을 해요. 오버레이 캡슐화나 상위 라우터 설정 변경 없이 플랫 L2 환경에서 파드 IP와 서비스 VIP를 외부에 노출할 수 있어요.

출처: 문서

본문

전체 그림

BGP를 구성하지 않으면서, Calico가 해당 IP들에 대한 ARP와 NDP 요청에 직접 응답하게 해서 파드와 LoadBalancer 서비스 IP를 로컬 레이어 2(L2) 네트워크에서 도달 가능하게 만들어요.

가치

파드나 LoadBalancer VIP가 노드 물리 인터페이스와 같은 서브넷의 IP를 할당받으면, 그 L2 세그먼트의 외부 호스트는 ARP(IPv4)나 Neighbor Solicitation(IPv6)으로 도달을 시도해요. 기본값으로는 아무도 응답하지 않아요. 파드 IP는 veth 뒤 네트워크 네임스페이스에 존재하고, LoadBalancer VIP는 어떤 인터페이스에도 존재하지 않기 때문이죠.

전통적으로 이런 IP를 도달 가능하게 만드는 유일한 방법은 BGP로 광고하는 것이었고, 네트워크 인프라와 피어링이 필요했어요. 플랫 L2 환경 — 온프레미스, 엣지 배포, kind 클러스터, 상위 라우터를 설정할 수 없는 네트워크 — 에서는 BGP가 선택지가 아닐 수 있어요.

localSubnetL2Reachability를 활성화하면 Calico가 관련 호스트 인터페이스에서 사용자 공간 ARP/NDP 응답기를 실행하고, 호스트 서브넷 안에 속한 로컬 파드 IP와 선별된 LoadBalancer VIP에 응답해요. BGP도, 오버레이 캡슐화도, 네트워크 인프라 변경도 필요 없어요.

개념

사용자 공간 ARP/NDP 응답기

Calico는 서브넷 안에 최소 하나의 파드 IP나 LoadBalancer VIP를 갖는 각 호스트 물리 인터페이스에서 raw 소켓을 열고, 그 IP들에 대한 ARP/NDP 요청에 응답해요. 응답에는 노드의 MAC 주소가 담겨서, 외부 호스트는 워크로드 트래픽을 그 노드로 보내요. 이후 Calico의 평소 데이터 플레인이 나머지 경로를 전달해요 — 파드 IP라면 로컬 파드로 직접, LoadBalancer VIP라면 서비스 백엔드로 로드밸런싱해요. 응답기는 ARP/NDP에만 응답하고, 트래픽이 노드에 도착한 뒤 전달 방식은 바꾸지 않아요.

LoadBalancer VIP마다 한 노드가 응답

LoadBalancer VIP에 대해서는 정확히 하나의 노드가 ARP에 응답해요. Calico는 consistent-hash ring으로 클러스터 전체에서 일관되게 그 노드를 선출해서, 조율 없이도 모든 노드가 독립적으로 동일한 소유자에 동의해요. 소유 노드의 Node 오브젝트가 삭제되면 VIP는 다른 노드에 재할당되고, 그 노드가 gratuitous ARP / unsolicited Neighbor Advertisement로 외부 캐시를 능동적으로 갱신해요.

캡슐화 없는 IP 풀만 대상이 됨

Calico는 캡슐화가 없는 IP 풀(ipipMode와 vxlanMode가 모두 Never)에 속한 IP에만 응답해요. 캡슐화된 풀은 사설 파드 네트워크로 취급되며 호스트 L2 세그먼트에 광고되지 않아요.

시작하기 전에...

제한 사항

  • LoadBalancer VIP마다 노드 하나. active-active 고가용성이 없어요. 소유 노드가 내려가면, Node 오브젝트가 제거되고 새 소유자가 선출될 때까지 VIP에 도달할 수 없어요.
  • 클라우드 공급자 ARP 필터링. 일부 클라우드 네트워크(예: AWS VPC)는 하이퍼바이저에서 ARP를 필터링해요. VIP/파드 IP가 노드의 클라우드 인터페이스에 할당되지 않으면 응답기가 동작하지 않아요. Calico가 통제할 수 없는 인프라 제약이에요.
  • 서로 다른 서브넷 혼재. LoadBalancer VIP의 노드 선출은 노드가 VIP 서브넷에 인터페이스를 갖는지 확인하지 않고 모든 노드에 해시를 적용해요. 노드들이 같은 서브넷을 공유하지 않을 때만 문제가 돼요.
  • 같은 L2 세그먼트에 여러 호스트 인터페이스. Calico는 프록시 중인 IP가 서브넷에 속하는 모든 호스트 인터페이스에서 응답해요. 한 노드가 같은 L2 세그먼트(동일 브로드캐스트 도메인)에 인터페이스를 둘 이상 갖고, 프록시 중인 IP가 그 공유 서브넷에 속한다면, 인터페이스마다 자기 MAC 주소로 ARP/NDP에 응답해요. 그래서 외부 호스트는 그 IP가 여러 MAC으로 resolve되는 걸 보고, 이웃 캐시가 그 사이를 오가게 돼요.
  • LoadBalancer 서비스는 externalTrafficPolicy: Cluster만 지원. Calico는 서비스 백엔드가 어디에 있든 관계없이 모든 노드에 대한 consistent hash로 VIP 응답 노드를 선출해요. externalTrafficPolicy: Local에서는 선출된 노드에 로컬 백엔드가 없을 수 있어서, 도착한 트래픽이 드랍돼요. externalTrafficPolicy는 기본값 Cluster로 두세요.
  • IPVS 모드의 kube-proxy는 strictARP가 필요. IPVS 모드에서는 kube-proxy가 모든 노드에서 모든 LoadBalancer VIP를 kube-ipvs0 더미 인터페이스에 바인딩해요. 리눅스 커널은 기본적으로 호스트에 설정된 모든 IP에 대해 ARP에 응답하기 때문에, Calico가 선출한 노드가 아닌 모든 노드가 VIP에 응답하고, 외부 호스트는 여러 MAC 주소에서 상충하는 응답을 받게 돼요. kube-proxy를 iptables 또는 nftables 모드로 실행하거나, IPVS 모드를 유지하면서 kube-proxy 구성의 strictARP: true(kube-proxy ConfigMap 안에 YAML로 저장되는 KubeProxyConfiguration의 ipvs.strictARP 필드)를 설정해서, 커널이 더미 인터페이스 VIP에 대해 ARP 응답을 멈추고 Calico 응답기에 맡기도록 하세요.

참고: ARP/NDP 응답기는 Felix(calico-node) 안에서 실행돼요. 노드의 Felix가 재시작하는 동안에는 잠깐 ARP나 NDP에 응답하지 않아요. 상위 라우터와 스위치가 그 IP들에 대해 노드 MAC을 아직 캐시하고 있으므로 기존 연결은 계속 동작하지만, 상위에서 아직 캐시되지 않은 IP로의 새 연결은 Felix가 시작을 마치고 응답기 상태를 다시 구축할 때까지(보통 1분 미만) 실패할 수 있어요.

영향을 줄이려면 상위 라우터와 스위치의 ARP/이웃 캐시 타임아웃(aging time)을 늘려서, 캐시 항목이 Felix 재시작 동안 유지되도록 하세요.

방법

로컬 서브넷 L2 도달성 활성화

기본 FelixConfiguration에서 localSubnetL2Reachability를 PodsAndLoadBalancers로 설정해요:

kubectl patch felixconfiguration default --type='merge' \
   -p '{"spec": {"localSubnetL2Reachability": "PodsAndLoadBalancers"}}'

이 기능은 기본값이 Disabled예요. 이 설정은 데이터 플레인이 시작할 때 읽기 때문에, 값을 바꾸면 새 값을 적용하려고 Felix(calico-node)가 자동으로 재시작해요 — 수동 재시작은 필요 없어요. 기능은 Felix 재시작이 끝나면 효과가 생겨요. 전체 필드 참조는 Felix 구성 리소스를 보세요.

참고: 기능만 켠다고 아무 일도 일어나지 않아요. Calico는 호스트 서브넷과 겹치는 캡슐화 없는 IP 풀에 속한 IP에만 응답하므로, 그런 풀도 만들어야 해요.

선택 사항: 주기적 재광고 조정. 기능이 켜져 있는 동안 Felix는 프록시 중인 모든 IP를 주기적으로 gratuitous ARP / unsolicited Neighbor Advertisement로 재광고해요. 프록시 중인 IP 집합이 변하지 않아도 상위 이웃 캐시와 스위치 포워딩 테이블을 따뜻하게 유지하죠. localSubnetL2ReachabilityRefreshInterval이 주기를 제어해요(기본값 120s). 상위 장비가 ARP/NDP 항목을 빨리 만료시키면 값을 낮추거나, 0으로 설정해 주기적 재광고를 끄고 IP가 처음 프록시될 때의 일회성 광고만 유지할 수 있어요:

kubectl patch felixconfiguration default --type='merge' \
   -p '{"spec": {"localSubnetL2ReachabilityRefreshInterval": "30s"}}'

호스트 서브넷에 전용 IP 풀 만들기

호스트 인터페이스 서브넷 안에 CIDR이 들어가는, 캡슐화 없는 IP 풀을 만드세요.

경고: 이 풀용으로 호스트 서브넷의 일부 범위를 예약하되, DHCP 범위, 정적 호스트 할당, 인프라 주소(라우터, 스위치, 관리 인터페이스)와는 겹치지 않게 하세요. 예를 들어 호스트 서브넷이 10.0.0.0/23이면, 호스트에는 10.0.0.2–10.0.0.255를 쓰고 풀에는 별도의 10.0.1.0/24를 사용하세요.

파드용:

apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
  name: host-subnet-pod-pool
spec:
  cidr: 10.0.1.0/24
  ipipMode: Never
  vxlanMode: Never
  natOutgoing: false
  disabled: false
  assignmentMode: Manual

assignmentMode: Manual은 풀을 옵트인(opt-in) 방식으로 유지해요. Calico가 이 풀에서 자동으로 할당하지 않으므로, 명시적으로 유도한 워크로드만 호스트 서브넷 IP를 받아요. 이 설정이 없으면 Calico가 임의 파드에 풀의 IP를 할당할 수 있어요.

LoadBalancer 서비스용으로는 allowedUses: [LoadBalancer]를 추가해요:

apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
  name: host-subnet-lb-pool
spec:
  cidr: 10.0.2.0/24
  ipipMode: Never
  vxlanMode: Never
  natOutgoing: false
  disabled: false
  assignmentMode: Manual
  allowedUses:
    - LoadBalancer

assignmentMode: Manual 덕분에 일반 LoadBalancer 서비스는 기존 자동 풀에서 계속 VIP를 가져가요. LoadBalancer kube-controller의 assignIPs 모드를 바꿀 필요가 없어요. 어노테이션을 붙인 서비스만 이 호스트 서브넷 풀에서 VIP를 가져가죠.

클러스터의 기본 워크로드 풀은 그대로고, 그 풀에서 할당된 워크로드의 동작도 예전과 완전히 같아요. LoadBalancer IP 풀에 대한 더 자세한 내용은 LoadBalancer IP 주소 관리 구성을 참고하세요.

파드나 서비스를 풀로 유도하기

파드와 서비스는 새 풀을 자동으로 쓰지 않아요 — 호스트 L2 세그먼트에서 도달 가능하기 원하는 특정 워크로드를 직접 유도하세요.

파드: 파드나 그 네임스페이스에 풀 이름으로 어노테이션을 붙여요:

cni.projectcalico.org/ipv4pools: '["host-subnet-pod-pool"]'

LoadBalancer 서비스: Service에 풀 이름으로 어노테이션을 붙여요:

projectcalico.org/ipv4pools: '["host-subnet-lb-pool"]'

IPv6는 projectcalico.org/ipv6pools를 사용해요. 이 어노테이션이 없는 서비스는 예전처럼 자동 LoadBalancer 풀에서 VIP를 받아요. 더 많은 옵션은 LoadBalancer IP 주소 관리 구성을 참고하세요.

도달성 확인

노드에서 ARP 요청이 도착하고 노드 MAC으로 응답이 나가는지 확인해요:

tcpdump -i <iface> arp

IPv6 NDP solicitation과 advertisement는:

tcpdump -i <iface> 'icmp6 and (icmp6.type == 135 or icmp6.type == 136)'

같은 L2 세그먼트의 외부 호스트에서 단계별 resolve를 확인해요:

arping -I <client-iface> <pod-or-vip>

응답이 없다면, IP가 호스트 인터페이스 서브넷과 캡슐화 없는 IP 풀 양쪽에 속하는지, 기능이 활성화됐는지, 네트워크가 ARP를 필터링하지 않는지(제한 사항 참고) 확인하세요.

추가 리소스

더 알아보기 (Learn more)

BGP 없이 서비스 IP를 노출하는 또 다른 경로인 BGP 기반 광고 문서도 함께 비교해 보면 선택 기준이 명확해져요. IP 풀 설계가 궁금하면 LoadBalancer IPAM 문서를 이어서 읽어보세요.