Cilium 기반 Ingress와 Gateway API
Cilium-Ingress
kubernetes의 Ingress에 대한 설명은 이전 블로그 게시글을 참고해주세요.
Cilium-Ingress 이란?
Cilium Ingress는 eBPF 기반 네트워킹과 Envoy L7 Proxy를 결합하여 기존의 Ingress Controller와 다른 방식으로 동작합니다.
특히, Source IP의 가시성을 유지하고, 네트워크 정책(CiliumNetworkPolicy)의 통합, 유연한 로드밸런서 모드, Host Network 모드 지원 등에서 강점이 있습니다. 또한, CiliumNetworkPolicy을 통해 Ingress 트래픽을 세세하게 제어할 수 있습니다.
설정 방법
Cilium은 kubernetes Ingress 리소스를 ingressClassName: cilium 으로 사용할 수 있습니다. 또한, 기본 방식과의 호환성을 위해 kubernetes.io/ingress.class: cilium 주석으로도 가능합니다.
주요 기능
주요 기능에는 Path 기반의 라우팅과 TLS Termination이 있습니다.
Loadbalancer Mode
mode 변경 시 새로운 LB IP가 할당되어 기존의 연결은 종료될 수 있습니다.
Dedicated : ingress 마다 별도의 LB를 생성하여 경로 충돌을 방지하는데 유리한 방식입니다.
Shared : 모든 Ingress가 하나의 LB를 공유하여 사용하는 방식으로, 리소스를 절약할 수 있습니다.
Cilium Ingress와 다른 ingress controller의 차이
다른 controller들은 일반적으로 K8s Cluster에 Deployment나 DaemonSet으로 설치되며, Service의 LoadBalancer Type으로 노출됩니다. 하지만 Cilium Ingress나 Gateway API는 Loadbalancer나 NodePort Type로 노출되고, Host Network에서도 노출 될 수 있습니다. 이때, 트래픽이 Service의 Port에 도착하면 eBPF가 TPROXY 커널 기능을 통해 해당 트래픽을 가로채 Envoy에게 전달합니다.
또한, Cilium이 다른 Ingress Controller와 달리 externalTrafficPolicy에 상관없이 소스 IP를 유지합니다.
트래픽 흐름
client
-> Ingress Service Port
-> Cilium eBPF Service LB
-> TPROXY
-> Envoy Proxy
-> L7 Routing Policy
-> eBPF
-> Backend Pod
즉, eBPF와 TPROXY를 이용하여 Envoy로 트래픽을 전달하며 CiliumNetworkPolicy을 Ingress 트래픽에 적용할 수 있습니다.
CiliumNetworkPolicy 구조
트래픽은 node별로 띄어져 있는 Envoy Proxy를 반드시 통과해야 합니다. 이때, Envoy는 Cilium의 eBPF Policy Engine과 직접 통신하여 L3~L7까지 정책을 검사합니다.
보통 Ciliym Ingress에서는 2개의 단계에서 유입되는 트래픽이 있습니다.
1. 외부 -> ingress
2. ingress -> 내부
따라서 CiliumNetworkPolicy을 설정할 때, 위의 두 경로 모두 정책을 설정해야 합니다.
Cilium-GatewayAPI
kubernetes의 Gateway API에 대한 설명은 이전 블로그 게시글을 참고해주세요.
Cilium-GatewayAPI란?
Kubernetes Ingress는 단순한 HTTP 기반 라우팅에는 충분하지만, 고급 라우팅 기능이 부족하고, TCP/UDP등 다양한 프로토콜에 대한 지원이 제한적이었으며, 세밀한 권한 분리가 불가능했습니다.
이러한 한계를 보완하기 위해 Kubernetes는 Gateway API를 도입했고, Cilium은 이를 eBPF 기반 네트워킹 및 Envoy L7 Proxy와 통합하여 지원합니다.
지원 범위
Cilium은 Gateway API v1.2.0을 지원하지만, v1.3의 일부 기능은 아직 지원하지 않습니다. (v1.3 구현 현황)
지원되는 리소스 목록
- GatewayClass
- Gateway
- HTTPRoute
- GRPCRoute
- TLSRoute (experimental)
- ReferenceGrant
추가적으로, Cilium은 CiliumGatewayClassConfig라는 CRD를 제공하며, 이는 GatewayClass.parametersRef에서 참조할 수 있습니다.
동작 방식
Cilium Gateway API는 Ingress와 마찬가지로 Envoy Proxy를 통해 eBPF 기반의 Service LB 및 TPROXY 기능을 통해 트래픽을 Envoy로 전달합니다. 이후 Envoy는 Gateway API 리소스에 정의된 정책(HTTPRoute, GRPCRoute, TLSRoute 등)에 따라 트래픽을 처리합니다.
트래픽 흐름
Client
→ Gateway Service Port
→ (Cilium eBPF LB)
→ (TPROXY)
→ Envoy Proxy (Gateway Listener)
→ L7 Routing (HTTP/GRPC/TLS)
→ (eBPF)
→ Backend Pod
설정 방법
1. GatewayClass 생성 : Cilium에서 제공하는 CiliumGatewayClassConfig를 참조하는 GatewayClass를 정의합니다.
실습 - cilium ingress
Cilium-Ingress 구성
Cilium K8S Ingress Support 관련 정보 확인
이때 주의 해야 할 점은 Cilium Ingress 와 Cilium GatewayAPI는 동시에 활성화가 불가능합니다.( 다른 Ingress와는 가능합니다.)
...
--set ingressController.enabled=true \
--set ingressController.loadbalancerMode=shared \
--set loadBalancer.l7.backend=envoy \
...
cilium 설치 시 위와 같이 옵션을 지정하여 배포하면, cilium Ingress를 설정할 수 있습니다.
cilium config view | grep -E '^loadbalancer|l7'
cilium config view 명령어를 통해 현재 설정된 cilium 옵션 중 loadbalancer 부분을 확인해보겠습니다.
enable-l7-proxy true 은 Cilium이 L7(Layer 7, 애플리케이션 계층) 프록시 기능을 활성화했음을 의미합니다.
즉, HTTP, gRPC 등 애플리케이션 레벨의 요청/응답을 인식하고 정책 적용이나 로드밸런싱을 수행할 수 있습니다.
loadbalancer-l7 envoy 은 L7 프록시로 Envoy를 사용하도록 설정되었다는 것을 의미합니다.
loadbalancer-l7-algorithm round_robin 은 L7 로드밸런싱 알고리즘이 round robin 방식으로 설정되었다는 것을 의미합니다.
loadbalancer-l7-ports L7 loadbalancer를 적용할 port인데 현재는 할당되어있지 않습니다.
ingress 에 예약된 내부 IP 확인
kubectl exec -it -n kube-system ds/cilium -- cilium ip list | grep ingress
현재 Cilium이 관리하는 IP 목록 중 ingress 트래픽과 연관된 IP 주소를 확인해보겠습니다.
node(cilium-envoy) 별로 존재하기에 node의 IP가 출력된 것을 확인할 수 있습니다.
cilium-envoy 확인
kubectl get pod -n kube-system -l k8s-app=cilium-envoy -owide
kubectl get svc,ep -n kube-system cilium-envoy
envoy가 host network를 쓰기 때문에 192.169.10.100, 101 대역을 사용하는 것을 확인할 수 있습니다.
cilium-ingress 확인
kubectl get svc,ep -n kube-system cilium-ingress
cilium-ingress라고 하는 논리적인 endpoint 역할을 하는 loadbalancer 타입의 service가 생성되었습니다.
하지만 endpoint 192.192.192.192:999인데, cilium-ingress를 사용하면 모든 node에서 통신할 수 있게 되기에 내부에서만 사용하는 논리 IP endpoint가 할당되게 됩니다.
즉, 192.192.192.192:999는 실제로 통신해야 하는 주소가 아니라, 내부에서만 쓰이는 논리 IP endpoint이고, 외부/내부 클라이언트는 cilium-ingress 서비스를 통해 들어와서 각 노드의 Envoy로 트래픽이 전달됩니다.
LB-IPAM 설정 후 확인
BGP모드를 사용하지 않게 때문에 한쪽의 node가 리더가 되는 L2 방식 LoadBalancer IP 할당하고 광고하도록 설정합니다.
cilium config view | grep l2
현재 L2 Announcement 활성화 상태를 확인해보겠습니다.
enable-l2-announcements가 true이기 때문에 Cilium 에이전트가 클러스터 노드에서 ARP(IPv4)/NDP(IPv6) 응답을 통해 LB IP를 직접 광고할 수 있습니다.
하지만 L2 광고 기능을 쓰려면, Cilium이 사용할 External IP 풀을 미리 정의해야 서비스에 External IP를 자동 할당할 수 있기에 CiliumLoadBalancerIPPool와 CiliumL2AnnouncementPolicy을 설정하도록 하겠습니다.
CiliumLoadBalancerIPPool 생성
apiVersion: "cilium.io/v2"
kind: CiliumLoadBalancerIPPool
metadata:
name: "cilium-lb-ippool"
spec:
blocks:
- start: "192.168.10.211"
stop: "192.168.10.215"
LB 서비스에 할당할 External IP 범위를 CRD로 선언하는데, Cilium이 LoadBalancer 타입 서비스 생성 할 때에 block 범위에서 자동으로 IP를 하나 선택해 할당합니다. 이때, pool block에 등록된 IP는 네트워크에서 비어 있어야 합니다.
IPPool 상태 확인
kubectl get ippool
Service 확인
kubectl get svc -A
cilium-ingress에 EXTERNAL-IP가 할당된 것을 확인할 수 있습니다.
CiliumL2AnnouncementPolicy 설정
apiVersion: "cilium.io/v2alpha1"
kind: CiliumL2AnnouncementPolicy
metadata:
name: policy1
spec:
interfaces:
- eth1
externalIPs: true
loadBalancerIPs: true
외부에서 접근이 가능하도록 어떤 인터페이스를 통해 L2 광고를 내보낼지 CRD로 선언하는데, eth1에서 externalIPs와 LoadBalancer를 광고하도록 설정합니다.
현재 리더 역할 노드 확인
kubectl -n kube-system get lease | grep "cilium-l2announce"
어떤 노드가 특정 LoadBalancer IP를 L2 광고하는 “리더” 역할을 맡고 있는지 확인해보겠습니다.
cilium-ingress 서비스에 할당된 LB IP는 지금 k8s-w1 노드에서만 광고하고 있는 것을 확인할 수 있습니다.
k8s 외부 노드에서 LB External-IP로 호출 확인
# router node
sudo arping -i eth1 192.168.10.211 -c 2
k8s 외부 node인 router에 접근해서 LB의 IP로 2회 요청을 보내어 통신해보겠습니다.
위 결과로 Cilium L2 Announcement 정책에 따라 192.168.10.211이 리더 노드에서 실제로 네트워크에 광고되고 있음을 알 수 있습니다.
Cilium-Ingress HTTP 통신 테스트
Ingress 실습을 진행하기 위해 공식문서를 참고하여 Istio 프로젝트의 bookinfo Demo Application으로 진행하도록 하겠습니다.
Bookinfo Demo App 배포
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.26/samples/bookinfo/platform/kube/bookinfo.yaml
배포 확인
kubectl get pod,svc,ep
istio 와 다르게 sidecar 컨테이너가 없고, NodePort와 LoadBalancer 서비스 없는 것을 확인할 수 있습니다.
ingress-controller 확인
kubectl get ingressclasses.networking.k8s.io
현재 배포되어 있는 ingress-controller를 확인해보겠습니다.
cilium ingress-controller가 배포되어있는 것을 확인할 수 있습니다.
ingress 배포
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: basic-ingress
namespace: default
spec:
ingressClassName: cilium
rules:
- http:
paths:
- backend:
service:
name: details
port:
number: 9080
path: /details
pathType: Prefix
- backend:
service:
name: productpage
port:
number: 9080
path: /
pathType: Prefix
ingress 확인
kubectl get ingress
ingress의 External-IP가 cilium-ingress LoadBalancer 의 External-IP인 것을 알 수 있습니다.
kc describe ingress
라우팅 경로를 확인해보기 위해 ingress를 자세히 살펴보겠습니다.
현재 / 와 /details에 각각 productpage, details가 매핑되어 있는 것을 확인할 수 있습니다.
호출 테스트
curl -so /dev/null -w "%{http_code}\n" http://192.168.10.211/
curl -so /dev/null -w "%{http_code}\n" http://192.168.10.211/details/1
각각 라우팅이 잡혀있어 200이 출력되는 것을 알 수 있습니다.
curl -so /dev/null -w "%{http_code}\n" http://192.168.10.211/ratings
하지만 위와 같이 경로가 잡혀있지 않은 /ratings를 호출하면 어떻게 될까요?
당연하게도 404 에러가 출력되는 것을 확인할 수 있습니다.
[router] 외부에서 호출 테스트
curl -so /dev/null -w "%{http_code}\n" http://192.168.10.211/
curl -so /dev/null -w "%{http_code}\n" http://192.168.10.211/details/1
외부에서 통신이 정상적으로 이루어지는지 확인하기 위해 router에 접근해서 LB의 External-IP로 통신을 해보겠습니다.
통신이 성공하여 200이 출력되는 것을 알 수 있습니다.
실습 - cilium Gateway API
Cilium-Ingress 구성
CRD 설치
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/standard/gateway.networking.k8s.io_gatewayclasses.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/standard/gateway.networking.k8s.io_gateways.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/standard/gateway.networking.k8s.io_httproutes.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/standard/gateway.networking.k8s.io_referencegrants.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/standard/gateway.networking.k8s.io_grpcroutes.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml
CRD 확인
kubectl get crd | grep gateway.networking.k8s.io
Cilium Gateway API 설정
helm upgrade cilium cilium/cilium --version 1.18.1 --namespace kube-system --reuse-values \
--set ingressController.enabled=false --set gatewayAPI.enabled=true
앞선 실습에서 설정했었던 ingressController.enabled를 false로 바꾸고, gatewayAPI.enabled를 true로 설정하여 helm upgrade를 진행합니다.
kubectl -n kube-system rollout restart deployment/cilium-operator
kubectl -n kube-system rollout restart ds/cilium
그 후 cilium agent를 재시작합니다.
Cilium Gateway API 배포 확인
cilium config view | grep gateway-api
Gateway 배포
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: my-gateway
spec:
gatewayClassName: cilium
listeners:
- protocol: HTTP
port: 80
name: web-gw
allowedRoutes:
namespaces:
from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: http-app-1
spec:
parentRefs:
- name: my-gateway
namespace: default
rules:
- matches:
- path:
type: PathPrefix
value: /details
backendRefs:
- name: details
port: 9080
- matches:
- headers:
- type: Exact
name: magic
value: foo
queryParams:
- type: Exact
name: great
value: example
path:
type: PathPrefix
value: /
method: GET
backendRefs:
- name: productpage
port: 9080
실습을 진행할 gateway 리소스를 배포합니다.
path가 /details로 시작하는 요청은 Service/details:9080 으로 라우팅되고, 경로가 /으로 시작하는 요청은 header가 magic=foo이고, 쿼리 파라미터가 great=example일때만 Service/productpage:9080으로 라우팅 되도록 HTTPRoute을 설정했습니다.
gateway 확인
kubectl get svc,ep cilium-gateway-my-gateway
ingress와 마찬가지로 External-IP가 Cilium LoadBalancer의 External IP인 것을 확인할 수 있습니다.
Cilium-GatewayAPI HTTP 통신 테스트
공식 문서를 참고하여 URL 경로를 기반으로 한 트래픽이 Gateway API에 의해 전달되는지 확인해 보겠습니다.
curl -so /dev/null -w "%{http_code}\n" http://192.168.10.211/details/1
위에서 확인했었던 요청을 한번 확인해보겠습니다.
경로가 /details로 시작하기 때문에 이 트래픽은 HTTPRoute의 첫 번째 규칙과 일치하여, 포트 9080을 통해 Service로 통신이 이루어진 것을 알 수 있습니다.
curl -so /dev/null -w "%{http_code}\n" http://192.168.10.211/
하지만 / 로 통신하면 404가 호출되고 있습니다.
curl -H 'magic: foo' -so /dev/null -w "%{http_code}\n" http://192.168.10.211?great\=example
마찬가지로 위에서 HTTPRoute를 설정한 것 처럼 header에 magic: foo와 쿼리 파라미터 greate=example로 호출해보겠습니다.
이번엔 200으로 성공한 것을 알 수 있습니다.
이 내용은 CloudNet@에서 진행하는 Cilium 스터디를 참여하면서 공부하는 내용을 기록하며, CloudNet@에서 제공해주는 자료들을 바탕으로 작성되었습니다.