Cilium Gateway API 외부 트래픽 설계

Cilium Gateway API 4편: LB 없이 노드별 클라우드 공인 IP로 받기

노드별 공인 IP를 VM에 연결하는 클라우드 SDN 환경에서 Cilium hostNetwork와 DNS로 외부 트래픽을 받습니다.

검증일 근거 자료

핵심 요약

  • 기준 구성은 각 control-plane 노드의 서로 다른 공인 IP를 공급자 SDN·가상 네트워크가 해당 VM vNIC와 fixed IP로 연결하고 DNS가 모든 공인 IP를 가리키는 방식
  • 공인 IP가 eth0에 없다는 사실만으로 NAT를 단정할 수 없으며 VM이 실제로 수신한 패킷의 목적지 주소를 먼저 확인해야 함
  • fixed IP가 목적지이면 0.0.0.0:80/443의 Cilium hostNetwork 리스너가 수신할 수 있지만 공인 /32가 유지되면 Linux local delivery 구성이 추가로 필요할 수 있음
  • 공급자 공인 IP는 클라우드 인프라 인벤토리와 DNS에서 관리하고 LB IPAM용 Gateway.spec.addresses에는 선언하지 않음
  • 다중 A/AAAA 레코드는 중앙 LB처럼 정상 노드를 선택하지 않으므로 요구 RTO에 따라 health-checked DNS 또는 외부 LB가 필요함

노드별 공인 IP의 전달 계층을 구분함

  • 공급자 공인 IP와 게스트 OS의 인터페이스 주소는 서로 다른 관리 계층에 존재할 수 있음
    • 공급자 제어 계층은 203.0.113.11cp-a의 vNIC와 연결함
    • 게스트 OS의 eth0에는 fixed IP인 10.0.0.11/24만 표시될 수 있음
    • 이 상태는 공인 IP 연결이 없다는 뜻이 아니며 NAT를 사용한다는 증거도 아님
Provider SDN steering per-node public IPs to Cilium host network listeners
  • 기본 데이터 경로는 공인 IP → 공급자 SDN·가상 스위치 → 대상 VM vNIC·fixed IP → Cilium hostNetwork Envoy 순서로 이해함
    • 공급자 구현에 따라 VM에서 보이는 최종 목적지가 fixed IP일 수도 있고 공인 /32일 수도 있음
    • 특정 공급자의 주소 변환, 라우팅, 가상 NIC 동작은 해당 공급자 문서로 확인해야 함

VM에서 목적지 주소를 먼저 판별함

  • 외부 요청을 보내는 동안 tcpdump -ni any로 패킷이 대상 VM까지 도착하는지 먼저 확인함
sudo tcpdump -ni any 'tcp port 80 or tcp port 443'
ip -br address
ip route show
kubectl get nodes -o wide
sudo ss -lntp '( sport = :80 or sport = :443 )'
  • 패킷 목적지가 노드 fixed IP이면 wildcard hostNetwork 리스너로 local delivery되는 경로를 점검함

    • 10.0.0.11:443과 같은 SYN이 보이면 로컬 방화벽과 0.0.0.0:443 리스너를 확인함
    • SYN은 도착하지만 응답이 없으면 리스너 상태, 로컬 방화벽, 반환 경로를 확인함
    • 패킷이 전혀 보이지 않으면 공인 IP 연결 대상, 공급자 방화벽, 보안 그룹, 네트워크 ACL을 확인함
  • 패킷 목적지가 공인 /32로 유지되면 Linux가 그 주소를 local address로 인식하는지 별도로 확인함

ip addr
ip route get local 203.0.113.11
ip route show table local
  • 공인 목적지가 local address로 인식되지 않으면 hostNetwork 활성화만으로 수신 문제를 해결할 수 없음
    • 공급자 문서에 따라 공인 /32, local route, 추가 가상 NIC 중 필요한 구성을 적용해야 함
    • 공급자가 관리하는 주소를 임의로 eth0에 추가하면 중복 주소나 잘못된 반환 경로가 생길 수 있음
    • net.ipv4.ip_nonlocal_bind=1은 로컬에 없는 주소로의 bind만 허용하며 패킷을 local delivery로 전환하지 않으므로 일반 해결책이 아님

hostNetwork를 선택 노드에만 활성화함

  • Cilium hostNetwork는 Gateway listener를 선택 노드의 모든 인터페이스에 0.0.0.0 또는 ::로 노출함
    • 기본 LoadBalancer Service 모드는 자동 비활성화되므로 두 모드는 상호 배타적임
    • 모든 Gateway의 listener port는 선택 노드에서 충돌하지 않도록 고유해야 함
    • TCPRouteUDPRoute는 host network 모드와 호환되지 않음
kubectl label node cp-a cp-b cp-c gateway.cilium.io/public=true
values-direct-public-ip.yaml
gatewayAPI:
  enabled: true
  hostNetwork:
    enabled: true
    nodes:
      matchLabels:
        gateway.cilium.io/public: "true"

envoy:
  enabled: true
  securityContext:
    capabilities:
      keepCapNetBindService: true
      envoy:
        - NET_BIND_SERVICE
  • 80/443처럼 1023 이하 포트에 바인딩하려면 NET_BIND_SERVICE capability가 필요함
    • 기존 capability 목록을 교체하지 않고 설치된 chart의 기본 목록에 NET_BIND_SERVICE를 추가해야 함
    • 위 예시는 standalone Envoy DaemonSet 모드의 핵심 값만 표시함
    • embedded Envoy 모드는 securityContext.capabilities.ciliumAgent에 추가해야 함
helm upgrade cilium cilium/cilium \
  --namespace kube-system \
  --reuse-values \
  --values values-direct-public-ip.yaml

kubectl -n kube-system rollout status ds/cilium-envoy

Gateway 주소와 공급자 인벤토리를 분리함

  • host network 모드의 Gateway.status.addresses에는 Kubernetes Node가 보고한 InternalIP 또는 ExternalIP가 나타날 수 있음

    • Node가 fixed IP만 InternalIP로 보고하면 Gateway status에도 사설 주소만 표시될 수 있음
    • Cilium은 노드 주소를 정렬하며 Gateway status에는 최대 16개 주소를 게시함
    • 공급자 공인 IP가 status에 없더라도 Envoy 프로그래밍 실패를 의미하지 않음
  • 공급자 공인 IP를 LB IPAM용 Gateway.spec.addresses에 선언하지 않음

    • Cilium의 spec.addresses 지원은 LB IPAM과 함께 Service VIP를 지정하는 기능임
    • 이 필드가 공급자 SDN의 공인 IP 연결이나 VM 라우팅을 생성하지 않음
    • 노드별 공인 IP 연결은 클라우드 인프라 인벤토리에서 관리함
  • DNS는 각 노드에 연결된 서로 다른 공인 IP를 모두 가리키도록 구성함

app.example.com.  60  IN  A  203.0.113.11
app.example.com.  60  IN  A  203.0.113.12
app.example.com.  60  IN  A  203.0.113.13
  • 인증서는 공인 IP가 아니라 Gateway listener의 hostname을 기준으로 발급함
    • HTTP-01 challenge를 사용하면 모든 DNS 대상 노드에서 challenge Route가 도달해야 함
    • DNS-01 challenge는 노드별 HTTP 도달성에 덜 의존함

방화벽과 소스 IP를 관측으로 검증함

  • 클라우드 방화벽과 노드 방화벽에는 실제 listener 포트인 80/443만 필요한 출발지 범위에 허용함

    • Kubernetes API 6443은 관리자 VPN과 노드 CIDR로 제한함
    • etcd, kubelet, Cilium 관리 포트는 인터넷에 공개하지 않음
  • 공급자 네트워크가 원본 소스 IP를 보존하는지는 구현에 따라 달라질 수 있으므로 단정하지 않음

    • VM의 tcpdump에서 SYN의 출발지 주소를 확인함
    • Cilium Envoy access log에서 downstream remote address와 전달 헤더를 확인함
    • 관측 결과를 기준으로 네트워크 정책, rate limit, 감사 로그의 신뢰 경계를 정함
gateway-direct-public-ip.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: public
  namespace: edge
spec:
  gatewayClassName: cilium
  listeners:
    - name: https
      hostname: "*.example.com"
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-example-com

DNS 기반 분산의 한계를 수용함

  • 여러 A/AAAA 레코드는 클라이언트에 후보를 제공하지만 중앙 LB처럼 매 요청의 정상 노드를 선택하지 않음

    • resolver와 클라이언트가 TTL보다 오래 주소를 캐시할 수 있음
    • 기존 TCP 연결은 DNS 변경과 무관하게 실패 노드에 남을 수 있음
    • 재시도와 주소 순회 동작은 클라이언트마다 다름
  • 요구 RTO가 단순 DNS 제거 시간보다 짧으면 health-checked DNS 또는 외부 LB가 필요함

    • health-checked DNS는 장애 감지, TTL, 전파, 클라이언트 캐시 시간을 합산해 평가함
    • 수 초 단위의 일관된 failover와 연결 단위 health check가 필요하면 외부 LB가 더 적합함
  • control-plane 노드를 직접 공개하면 관리 트래픽과 사용자 트래픽이 같은 서버 자원을 공유함

    • 가능한 경우 공인 IP와 Gateway listener를 전용 infra 노드로 이동함
    • 불가피하면 Envoy connection limit, rate limit, resource limit와 API server 보호 지표를 설정함

각 공인 IP를 외부에서 독립 검증함

  • DNS round-robin이 단일 노드 장애를 숨기지 않도록 모든 공인 IP를 직접 고정해 검사함
kubectl get gateway -n edge public -o yaml
kubectl -n kube-system get pods -l k8s-app=cilium-envoy -o wide
sudo ss -lntp '( sport = :80 or sport = :443 )'
for ip in 203.0.113.11 203.0.113.12 203.0.113.13; do
  curl --fail-with-body \
    --connect-timeout 3 \
    --resolve app.example.com:443:$ip \
    https://app.example.com/healthz
done
  • 공인 IP별 검사에서 TLS, Route, 백엔드 경로를 한 요청으로 검증함
    • 한 노드를 중지하고 health-checked DNS가 실패 주소를 제거하는 시간을 측정함
    • 복구 노드가 listener와 최신 Envoy 설정을 받은 뒤 DNS 대상에 복귀하는지 확인함
    • 패킷 캡처와 Envoy access log를 함께 비교해 소스 IP 보존 여부를 확인함

실행 제안

  • 먼저 노드별 공인 IP 연결과 VM에서 보이는 목적지를 확인한 뒤 hostNetwork + 선택 노드 label + 공인 DNS를 적용함
  • 공인 /32가 local address로 인식되지 않으면 공급자 요구사항에 맞는 route·vNIC 구성을 먼저 해결함
  • 운영 RTO가 DNS 장애 제거 시간보다 짧으면 외부 LB 또는 health-checked DNS 계층을 추가함

참고 문서