CCA — 실리움 인증 어소시에이트 · Gateway API·사이드카 없는 메시·BGP · 실습
게이트웨이를 켰는데 GatewayClass 가 없다
목표
VM 안의 진짜 k3s + Cilium 1.20.1 에서 Gateway API 를 틀린 순서로 켜 보고 바로잡은 뒤, Gateway·HTTPRoute 로 경로·헤더·가중치 분할과
네임스페이스 경계(ReferenceGrant)를 실제 요청으로 확인합니다. 마지막에 요청을 실제로 받는 것이 노드의 cilium-envoy 라는 것을 서비스 목록에서 찾아냅니다.
왜 중요한가
Ingress 는 경로와 호스트 정도만 표준으로 정하고 나머지를 구현별 어노테이션에 맡겼습니다. Gateway API 는 역할을 나눕니다 — 인프라 담당은
GatewayClass·Gateway 를, 앱 팀은 HTTPRoute 를 소유하고, 헤더 매칭·가중치 분할 같은 기능이 스펙 안에 들어 있으며, 다른 네임스페이스를
참조하려면 받는 쪽이 ReferenceGrant 로 허락해야 합니다.
Cilium 은 사이드카 없이 이 API 를 구현합니다. L7 처리는 노드마다 하나씩 도는 envoy 가 맡고, eBPF 가 게이트웨이 주소로 온 트래픽을 그 envoy 로
넘깁니다. 이 구조를 모르면 "게이트웨이 파드가 어디 있지?" 를 찾다가 시간을 씁니다.
그리고 켜는 순서가 중요합니다. 공식 문서는 CRD 를 먼저 설치하라고 합니다. 이 실습에서 순서를 뒤집으면 무엇이 비는지, 재시작만으로 왜 채워지지
않는지를 직접 봅니다.
환경 준비에 약 5분이 걸립니다. Gateway API CRD 는 github raw 에서 받습니다. 세션이 끝나면 /root/cca-gateway 의 파일은 사라집니다.
단계
1. cilium CLI 로 Gateway API 기능을 켜세요(cilium upgrade --version 1.20.1 --reuse-values --set gatewayAPI.enabled=true). 그다음 공식 문서 순서대로 cilium-operator Deployment 와 cilium DaemonSet 을 재시작합니다. Gateway API CRD 는 아직 설치하지 않습니다. 그 순간을 /root/cca-gateway/no-crd.json 에 기록합니다 — enable_gateway_api(cilium-config 의 enable-gateway-api 문자열), gatewayclass_api(API 에 gatewayclasses 자원이 있으면 true, 없으면 false), operator_log(새 오퍼레이터가 CRD 가 없다고 남긴 로그 한 줄 그대로).
2. Gateway API v1.6.1 표준 CRD 7개(gatewayclasses gateways httproutes referencegrants grpcroutes backendtlspolicies tlsroutes)를 kubectl apply --server-side -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.6.1/config/crd/standard/gateway.networking.k8s.io_<자원>.yaml 로 설치하세요. 그 뒤 cilium-operator 를 다시 재시작하고, 새 오퍼레이터 파드가 뜬 뒤 20초 이상 기다렸다가 GatewayClass 개수를 셉니다. /root/cca-gateway/crd-late.json 에 crds(설치된 CRD 전체 이름 7개 목록), operator_started(재시작한 오퍼레이터 파드의 status.startTime), gatewayclasses(그때 개수), recorded_at(개수를 센 순간의 UTC 시각, date -u +%Y-%m-%dT%H:%M:%SZ) 를 기록합니다. Cilium 설정은 아직 다시 올리지 않습니다.
3. CRD 가 있는 상태에서 1단계의 cilium upgrade 명령을 한 번 더 실행해 GatewayClass cilium 이 생기고 Accepted=True 가 되게 하세요. /root/cca-gateway/gatewayclass.json 에 name, controller(spec.controllerName), accepted(Accepted 조건의 status), managed_by(라벨 app.kubernetes.io/managed-by 값) 를 기록합니다.
4. kubectl apply -f /opt/fixtures/cca-gateway/backends.json 으로 백엔드(cca-gw 의 store-v1·store-v2·admin, cca-shop 의 payments)를 띄우세요. cca-gw 네임스페이스에 Gateway shop-gw 를 만듭니다 — gatewayClassName cilium, 리스너 하나(name http, protocol HTTP, port 80). Programmed=True 가 되면 /root/cca-gateway/gateway.json 에 address(status.addresses 의 값), programmed, service(자동 생성된 서비스 이름), service_type, proxy_backend(에이전트 cilium-dbg service list 에서 그 주소:80 LoadBalancer 프런트엔드의 백엔드 ip:port), root_code(노드에서 curl http://<주소>/ 의 HTTP 코드, 숫자), server(그 응답의 server 헤더 값) 를 기록합니다.
5. cca-gw 에 HTTPRoute store 를 만드세요. parentRefs 는 shop-gw 하나, 규칙은 둘입니다 — (1) path PathPrefix /store 이면서 헤더 x-canary: yes 인 요청은 store-v2:8080, (2) path PathPrefix /store 인 나머지는 store-v1:8080. 노드에서 게이트웨이 주소로 /store/list, 헤더를 붙인 /store/list, /storefront 를 요청해 /root/cca-gateway/routes.json 에 store(첫 요청의 응답 JSON 의 app), store_canary(둘째의 app), storefront(셋째의 HTTP 코드, 숫자) 를 기록합니다.
6. cca-gw 에 HTTPRoute split 을 만드세요. parentRefs shop-gw, 규칙 하나 — path PathPrefix /checkout, backendRefs store-v1:8080 weight 80 과 store-v2:8080 weight 20. 라우트가 반영된 뒤 게이트웨이 주소의 /checkout 에 100번 요청해 응답의 app 을 세고 /root/cca-gateway/split.json 에 path, requests(보낸 수), counts({store-v1: n, store-v2: m}) 를 기록합니다. 두 버전 모두 관측돼야 합니다.
7. cca-gw 에 HTTPRoute pay 를 만드세요 — parentRefs shop-gw, path PathPrefix /pay, backendRefs 는 namespace cca-shop 의 payments:8080. 먼저 허락 없이 적용해 route 의 ResolvedRefs 조건(status·reason)과 /pay 의 HTTP 코드를 관측합니다. 그다음 cca-shop 에 ReferenceGrant 를 만들어 cca-gw 의 HTTPRoute 가 Service payments 만 참조하도록 허락하고, 다시 관측합니다. /root/cca-gateway/refgrant.json 에 before·after 각각 resolved_refs, reason, code(숫자) 를 기록합니다.
8. /root/cca-gateway/report.txt 에 키=값 일곱 줄을 씁니다 — gatewayclass_owner(GatewayClass cilium 의 managed-by 라벨), l7_proxy_daemonset(kube-system 에서 게이트웨이 요청을 실제로 처리하는 DaemonSet 이름), gateway_frontend(주소:80), gateway_proxy_backend(4단계와 같은 방법으로 지금 읽은 백엔드), envoy_config(cca-gw 에 자동 생성된 CiliumEnvoyConfig 이름), split_v2_count(split.json 의 store-v2 수), cross_namespace_without_grant(허락 없을 때의 HTTP 코드). 값은 실제 상태·기록과 일치해야 합니다.
참고
- Cilium Gateway API 지원(필수 CRD 목록과 설치 순서): https://docs.cilium.io/en/v1.20/network/servicemesh/gateway-api/gateway-api/
- 가중치 분할 예제: https://docs.cilium.io/en/v1.20/network/servicemesh/gateway-api/splitting/
- 헤더 기반 라우팅 예제: https://docs.cilium.io/en/v1.20/network/servicemesh/gateway-api/header/
- Envoy DaemonSet(cilium-envoy): https://docs.cilium.io/en/v1.20/security/network/proxy/envoy/
- Gateway API HTTP 라우팅: https://gateway-api.sigs.k8s.io/guides/http-routing/
- Gateway API 트래픽 분할: https://gateway-api.sigs.k8s.io/guides/traffic-splitting/
- ReferenceGrant: https://gateway-api.sigs.k8s.io/reference/api-types/referencegrant/
- 에이전트 명령은
kubectl -n kube-system exec ds/cilium -c cilium-agent -- cilium-dbg ...로 실행합니다. 게이트웨이 주소는 노드 IP 이므로 VM 셸에서 바로 curl 합니다.
단계 8개
- CRD 없이 게이트웨이 기능부터 켠 날
- CRD 를 뒤늦게 깔고 재시작했는데도 비어 있다
- GatewayClass 는 차트가 만든다
- 게이트웨이 주소 뒤에 서 있는 것은 envoy 다
- 경로가 같아도 헤더가 다르면 다른 버전으로
- 20% 만 새 버전으로 흘린다는 약속을 세어 본다
- 옆 네임스페이스의 결제 서비스는 허락을 받아야 붙는다
- 개통 보고서: 누가 무엇을 만들고 누가 요청을 받았나