Cilium Gateway API 외부 트래픽 설계

Cilium Gateway API 2편: 클러스터 앞에 외부 LB가 있을 때

기존 외부 로드 밸런서에서 Cilium Gateway API로 트래픽을 전달하는 LoadBalancer, NodePort, hostNetwork 구성을 비교합니다.

검증일 근거 자료

핵심 요약

  • 외부 LB가 Kubernetes Service를 직접 관리할 수 있으면 기본 LoadBalancer 모드가 가장 단순한 구성
  • Kubernetes와 분리된 하드웨어·가상 LB라면 NodePort를 대상으로 등록하고 포트 변경을 자동화하는 구성이 범용적
  • 고정된 80/443 노드 포트가 필요하고 HTTP·HTTPS 중심이면 선택 노드에만 hostNetwork 리스너를 여는 구성이 적합할 수 있음
  • 외부 LB의 헬스 체크는 Kubernetes API 서버가 아니라 Gateway 데이터 경로를 통과하는 전용 엔드포인트를 검사해야 함
  • 클라이언트 IP는 L4 소스 보존 또는 신뢰 가능한 L7 헤더 중 하나를 선택하고 노드 접근 제어와 함께 구성해야 함

외부 LB와 Gateway의 책임을 분리함

  • 외부 LB는 정상 노드를 선택하고 Cilium Gateway는 TLS와 L7 라우팅을 담당하는 구성이 기본안
    • 외부 LB에서 TLS를 종료하지 않으면 인증서와 호스트 라우팅 계약을 Gateway에 집중할 수 있음
    • 외부 LB에서 TLS를 종료하면 LB와 Gateway 사이 재암호화 여부와 X-Forwarded-For 신뢰 규칙이 추가됨
External load balancer in front of Cilium Gateway API

LB 연동 능력에 따라 세 가지 패턴을 선택함

  • 패턴 A는 클라우드 컨트롤러가 생성한 LoadBalancer Service를 외부 LB와 동기화하는 방식

    • Cilium Gateway의 기본 동작이므로 별도 GatewayClass가 필요하지 않음
    • 클라우드별 Service annotation은 Gateway.spec.infrastructure.annotations에 두어 생성 Service로 전달할 수 있음
    • 외부 주소가 비어 있으면 Gateway 문제가 아니라 LoadBalancer 구현 또는 권한 문제일 가능성이 있음
  • 패턴 B는 외부 LB가 모든 인그레스 노드의 NodePort를 대상으로 사용하는 방식

    • CiliumGatewayClassConfig로 생성 Service 타입을 NodePort로 변경할 수 있음
    • 이 CRD는 v2alpha1이므로 버전 고정과 업그레이드 검증이 필요함
nodeport-gateway-class.yaml
apiVersion: cilium.io/v2alpha1
kind: CiliumGatewayClassConfig
metadata:
  name: external-lb
  namespace: edge
spec:
  service:
    type: NodePort
    externalTrafficPolicy: Cluster
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: cilium-external-lb
spec:
  controllerName: io.cilium/gateway-controller
  parametersRef:
    group: cilium.io
    kind: CiliumGatewayClassConfig
    name: external-lb
    namespace: edge
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: public
  namespace: edge
spec:
  gatewayClassName: cilium-external-lb
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-example-com
  • 생성된 nodePort는 외부 LB 구성의 입력값으로 자동 동기화해야 함
kubectl -n edge get svc cilium-gateway-public \
  -o jsonpath='{range .spec.ports[*]}{.name}{"="}{.nodePort}{"\n"}{end}'
  • 패턴 C는 hostNetwork로 선택 노드의 리스너 포트에 직접 바인딩하는 방식
    • 포트가 고정되어 외부 LB의 대상을 nodeIP:443으로 단순화할 수 있음
    • LoadBalancer Service 모드와 상호 배타적이며 TCPRoute·UDPRoute 제약이 있음
    • 구성 방법은 LB 없는 클라우드 공인 IP 구성에서 상세히 다룸

인그레스 노드만 LB 대상에 포함함

  • 외부 LB의 대상 집합은 역할이 명시된 노드로 제한해야 함
kubectl label node worker-a worker-b gateway.cilium.io/expose=true
kubectl get nodes -l gateway.cilium.io/expose=true -o wide
  • NodePort는 기본적으로 여러 노드에서 수신될 수 있으므로 외부 LB와 방화벽이 대상 범위를 제한해야 함

    • 인터넷에서는 외부 LB의 VIP만 공개함
    • 노드의 NodePort는 LB 소스 CIDR에서만 허용함
    • 컨트롤 플레인 노드가 대상이 아니면 node.kubernetes.io/exclude-from-external-load-balancers label 또는 LB 대상 필터로 제외함
  • Cilium Gateway 트래픽에서는 externalTrafficPolicy: Cluster를 기본값으로 유지해도 Envoy가 관찰한 원격 주소를 HTTP 헤더로 전달할 수 있음

    • 일반 Service의 소스 IP 동작과 Cilium의 Envoy TPROXY 경로를 혼동하면 안 됨
    • Node IPAM LB를 함께 사용할 때는 Cilium Gateway의 dummy Endpoint 제약 때문에 Local을 피해야 함

헬스 체크가 전체 데이터 경로를 검증해야 함

  • TCP 연결 확인은 포트 생존만 검증하고 Route·TLS·백엔드 장애를 발견하지 못함

    • 빠른 노드 제거가 우선이면 TCP 체크를 사용함
    • 사용자 요청 가능 여부가 우선이면 전용 hostname과 /healthz Route를 사용함
  • 전용 헬스 체크는 외부 LB와 동일한 포트·SNI·Host 헤더로 요청해야 함

gateway-health-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: gateway-health
  namespace: edge
spec:
  parentRefs:
    - name: public
  hostnames: ["gateway-health.example.com"]
  rules:
    - matches:
        - path:
            type: Exact
            value: /healthz
      backendRefs:
        - name: gateway-health
          port: 8080
  • Kubernetes API 서버의 /readyz를 Gateway 헬스 체크로 사용하면 안 됨
    • API 서버가 정상이어도 Envoy·Route·백엔드 경로가 실패할 수 있음
    • API 서버가 일시적으로 실패해도 이미 프로그램된 Envoy 데이터 경로는 계속 요청을 처리할 수 있음

클라이언트 IP 신뢰 규칙을 한 번만 적용함

  • L4 패스스루 LB가 소스 IP를 보존하면 Cilium의 기본 gatewayAPI.useRemoteAddress=true를 유지함

    • LB가 SNAT하면 Envoy에는 LB 주소만 보이므로 LB의 DSR 또는 소스 보존 기능을 검토함
  • L7 LB가 신뢰 가능한 X-Forwarded-For를 생성하면 trusted hop 설정과 접근 제어를 함께 적용함

values-trusted-l7-lb.yaml
gatewayAPI:
  enabled: true
  useRemoteAddress: false
  xffNumTrustedHops: 1
  • trusted hop 수는 LB 체인 수와 정확히 일치해야 함
    • CDN과 외부 LB가 연속되면 각 프록시의 헤더 append 규칙을 먼저 확인함
    • 노드 포트로 직접 접근하는 경로를 차단하지 않으면 클라이언트가 신뢰 헤더를 위조할 수 있음

장애와 변경 시나리오를 검증함

  • 정상 상태에서는 두 노드가 모두 LB 대상이고 Gateway와 Route condition이 True여야 함
kubectl get gateway -n edge public -o wide
kubectl get httproute -A
kubectl get svc -n edge cilium-gateway-public -o yaml
  • 장애 훈련에서는 한 인그레스 노드를 drain하거나 Envoy를 중지해 제거 시간을 측정해야 함

    • LB 헬스 체크 간격과 실패 임계값을 기록함
    • 기존 keep-alive 연결과 신규 연결의 실패 양상을 분리함
    • 복구 후 대상 재등록과 연결 분산을 확인함
  • 변경 훈련에서는 Gateway 재생성 시 NodePort가 바뀌어도 LB 자동화가 따라가는지 확인해야 함

    • 정적 수동 등록만 가능한 장비라면 hostNetwork 또는 예약 가능한 별도 Service 설계를 검토함

실행 제안

  • Kubernetes 연동 LB는 기본 LoadBalancer, 독립 외부 LB는 NodePort, 고정 노드 포트가 핵심이면 hostNetwork 순서로 검토
  • 운영 반영 전 LB → 노드 → Envoy → Route → 백엔드 전체를 통과하는 헬스 체크와 장애 훈련을 완료해야 함

참고 문서