LabHub
배우기 러닝패스 코스

CCA — 실리움 인증 어소시에이트 · BGP 사고 조사: 경로를 배웠는데 왜 못 갈까 · 이론

초록색 BGP 연결과 실제 HTTP 사이의 네 단계

LabHub 에서 이어서 보기

한 줄 요약

BGP 피어의 Established는 경로 교환 연결이 맺어졌다는 증거이지, 사용자의 HTTP 요청이 성공했다는 증거가 아닙니다. 이 모듈은 같은 서버 앞에 다섯 라우터를 놓고 피어 연결, 수신 경로인 RIB, 커널 전달 경로인 FIB, 실제 응답을 따로 비교합니다. 서로 다른 실패를 같은 timeout으로 뭉뚱그리지 않고, 어떤 관측이 어느 주장까지 뒷받침하는지 설명하는 것이 목표입니다.

왜 이게 필요했나

온콜 화면에는 피어 다섯 개 중 네 개가 초록색입니다. 그런데 서비스 담당자는 여전히 접속할 수 없다고 합니다. 여기서 모든 에이전트를 재시작하면 경로가 잠깐 바뀌거나 과거 로그가 사라져 원인을 더 찾기 어려워집니다. 먼저 운영자가 확인한 성공과 사용자가 기대한 성공이 같은지 물어야 합니다. TCP 연결을 맺은 것, 프리픽스를 배운 것, 커널에 경로를 넣은 것, 서버의 응답을 받은 것은 서로 다른 사건입니다.

실습에는 AS 번호를 잘못 적은 피어, 연결만 된 피어, 경로를 배우지만 커널에 설치하지 않는 라우터, 정상 전달 라우터가 있습니다. 하나를 고칠 때 다른 비교가 없어지지 않도록 각 라우터를 Linux 네트워크 네임스페이스에 따로 배치했습니다. 네임스페이스마다 인터페이스와 라우팅 테이블이 분리되지만, 모두 개인 VM 안에 있으므로 집의 라우터나 플랫폼의 운영 BGP를 건드리지 않습니다. 아래 주소는 실습 내부용이며 실제 조직의 라우팅 설정으로 그대로 옮기지 않습니다.

어떻게 동작하나

주소를 먼저 읽고 연결 방향을 맞춘다

각 라우터는 veth로 VM과 직접 연결됩니다. VM 쪽 Cilium의 AS는 65001, 라우터 쪽 FRR의 AS는 65002입니다. Cilium의 peerASN에는 상대 FRR의 65002가 들어가야 합니다. 반대로 FRR의 remote-as에는 Cilium의 65001이 들어갑니다. local과 remote는 문서에 영원히 고정된 방향이 아니라, 지금 어느 장비의 설정을 읽는가에 따라 바뀌는 이름입니다.

| 비교 라우터 | VM 인터페이스와 IP | 라우터 IP | 역할 |
| --- | --- | --- | --- |
| empty | bgp-empty, 192.0.2.1 | 192.0.2.2 | 잘못된 AS를 고친 뒤 광고 없는 연결 유지 |
| rib | bgp-rib, 192.0.2.5 | 192.0.2.6 | PodCIDR을 배우되 커널에는 설치하지 않음 |
| pod | bgp-pod, 192.0.2.9 | 192.0.2.10 | PodCIDR을 배워 실제 Pod로 전달 |
| vip | bgp-vip, 192.0.2.13 | 192.0.2.14 | 선택한 서비스 VIP에만 접근 |
| withdraw | bgp-withdraw, 192.0.2.17 | 192.0.2.18 | 초기 서비스의 광고만 철회 |

각 연결망은 /30이며 라우터의 인터페이스 이름은 router입니다. FRR은 수동 연결 대기 상태이고 Cilium이 연결을 시작합니다. empty 피어만 Cilium의 peerASN이 65003으로 잘못 들어 있습니다. 첫 복구는 전체 네트워크를 다시 만드는 것이 아니라 그 값만 65002로 맞추는 일입니다. sourceInterface는 피어별 VM veth를 지정하므로, 다른 연결망의 주소로 접속하려는 실수도 구분할 수 있습니다. [Cilium BGP 리소스와 피어 설정](https://docs.cilium.io/en/stable/network/bgp-control-plane/bgp-control-plane-configuration/)에서 설정의 각 방향을 확인하세요.

PodCIDR 광고와 커널 경로는 한 단계가 아니다

CiliumBGPAdvertisement의 PodCIDR은 노드가 할당받은 파드 네트워크를 광고합니다. 이 실습의 전체 풀은 10.42.0.0/16이지만 실제 노드에 할당된 범위는 CiliumNode에서 읽어야 합니다. 관측 도구는 실제 Pod IP가 그 범위 안에 있는지도 확인합니다. 전체 /16을 외워 적거나 관측되지 않은 주소로 표를 채우면 경로가 어느 노드의 것인지 구분할 수 없습니다.

rib 라우터에는 의도적으로 FRR의 bgp no-rib가 설정돼 있습니다. 이 상태에서도 BGP RIB에는 수신한 최선 경로가 보일 수 있지만 커널에는 그 경로가 설치되지 않습니다. 여기서 학생이 해야 할 일은 모든 라우터를 동일하게 복구하는 것이 아니라, 이 비교 상태를 보존하며 왜 HTTP가 실패하는지 설명하는 것입니다. pod 라우터에서는 같은 PodCIDR을 광고하되 FRR이 커널에 경로를 설치하게 두어 HTTP 200과 비교합니다. 두 라우터가 서로 다른 결과를 주는 것이 정상입니다. [FRR 8.4의 no-kernel·no-rib 설명](https://docs.frrouting.org/en/stable-8.4/bgp.html)을 읽을 때도 RIB라는 단어가 어느 계층의 테이블을 가리키는지 확인하세요.

FIB에서는 목적지 프리픽스만 보지 않습니다. protocol이 bgp인지, 다음 홉이 해당 연결망의 VM 주소인지, 출력 장치가 router인지도 대조합니다. 기본 경로나 정적 /32를 넣어 성공시킨 답은 BGP 전달 성공을 증명하지 못합니다. iproute2 JSON은 /32 목적지를 접미사 없는 IP로 보여 줄 수 있으므로 문자열 모양이 아니라 정규화한 네트워크를 비교합니다.

IP 할당과 서비스 광고도 분리한다

LoadBalancer IPAM은 서비스에 쓸 주소를 할당합니다. 주소가 생겼다는 사실만으로 외부 라우터가 그 주소를 아는 것은 아닙니다. 5단계에서는 학생용 풀 203.0.113.10~11을 만들고 selected와 excluded 서비스가 서로 다른 주소를 받는 것까지만 확인합니다. 둘의 backend selector는 같고 같은 HTTP Pod를 가리킵니다. 이 상태에서 주소만 보고 광고까지 됐다고 결론 내리지 않습니다. [LB IPAM](https://docs.cilium.io/en/stable/network/lb-ipam/)의 주소 할당과 BGP의 광고를 별도 책임으로 읽어 보세요.

6단계에서는 CiliumBGPAdvertisement의 서비스 선택자로 publish=selected만 고릅니다. vip 라우터에는 selected의 정확한 /32가 있어야 하고, excluded의 경로나 PodCIDR을 추가하지 않습니다. 같은 backend가 살아 있어도 excluded에는 도달하지 못해야 합니다. 정상 응답 하나만 확인하면 실수로 두 서비스를 모두 공개한 구성을 놓치므로, 성공할 대상과 실패할 대상을 함께 시험합니다. 선택자는 PeerConfig가 광고 리소스를 고르는 층과 광고 리소스가 Service를 고르는 층에 각각 존재한다는 것도 구분해야 합니다.

철회는 서버를 끄는 일이 아니다

철회 비교용 서비스 cca-retire는 학생용 풀과 별개로 203.0.113.20을 미리 할당받습니다. 초기화는 실제 광고 UID와 서비스 UID, RIB/FIB, HTTP 200을 확인한 후 baseline.json을 남깁니다. 학생은 그 증거를 읽고 cca-withdraw 광고만 삭제합니다. HTTP 서버와 서비스는 남아 있고 피어도 Established이지만, withdraw 라우터의 목적지 경로가 사라져 연결이 실패하는 상태를 만들어야 합니다.

서비스나 피어를 삭제해 실패를 만드는 것은 다른 실험입니다. 목적지 서버 자체가 없어지면 광고 철회 효과를 분리할 수 없습니다. 그래서 채점은 초기 Pod·노드·서비스 UID가 유지되는지 확인하고, 현재 피어·경로와 새 HTTP 요청까지 대조합니다. Cilium의 광고 변화는 비동기로 전달되므로 삭제 API가 끝나자마자 경로가 사라졌다고 가정하지 말고 관측 명령이 수렴을 확인한 뒤 채점하세요. [BGP 운영 안내](https://docs.cilium.io/en/stable/network/bgp-control-plane/bgp-control-plane-operation/)는 제어면 변화와 전달의 연속성을 함께 다룹니다.

같은 연결 실패도 경로 표와 묶어 해석한다

curl 종료 코드 7 하나로 경로 부재를 단정할 수는 없습니다. 경로가 있어도 대상 포트가 연결을 거부하면 같은 코드가 나올 수 있습니다. 이 실습에서는 같은 서버가 pod 라우터에서 정상 응답한다는 비교, 실패한 라우터의 RIB/FIB에 목적지 경로가 없다는 관측을 함께 사용합니다. 반대로 rib 비교는 RIB가 있고 FIB가 없으므로, empty와 HTTP 결과가 같아도 멈춘 계층이 다릅니다. 숫자가 같은 결과를 한 원인으로 합치지 않는 것이 사고 보고의 핵심입니다.

관측 도구가 실패 이유를 요약해 주더라도 원문을 직접 읽어 보세요. 먼저 kubectl get ciliumbgpclusterconfig cca-bgp -o yaml로 의도한 AS·피어와 상태 조건을 보고, cilium bgp peers로 연결 상태를 봅니다. 이어 각 라우터의 show bgp ipv4 unicast jsonip -n cca-pod -j route show를 대조합니다. 설정을 저장했다는 증거만 있고 라우터의 수신 경로가 없다면 아직 광고가 수렴하지 않았거나 선택자가 맞지 않을 수 있습니다. 이때 서비스 배포부터 고치면 다른 계층을 건드리는 셈입니다.

상태 조건에서 NoMatchingNode가 보이면 노드 선택자를, MissingPeerConfigs가 보이면 참조한 PeerConfig의 존재를 먼저 확인합니다. 이 조건들은 원인 범위를 좁혀 주지만, 조건이 사라졌다는 사실만으로 HTTP 성공이 증명되지는 않습니다. 이번 실습처럼 초기 피어와 서버를 보존하는 과제에서는 잘못된 실험을 되돌릴 때도 무엇을 변경했는지 기록해야 합니다. 새 객체를 같은 이름으로 만들면 UID가 달라지고, 이전 기록은 같은 환경의 증거가 아니게 됩니다.

현장에서 만나는 모습

LabHub의 조리법 탐침에서는 단일 VM의 라우터 네임스페이스가 실제 외부 머신과 완전히 같지 않다는 점이 드러났습니다. socket-LB가 connect 시점에 VIP를 Pod IP로 바꾸면, 외부 라우터의 VIP 경로를 시험한다고 생각하면서 실제로는 Pod 경로를 시험할 수 있었습니다. 이 환경은 최초 설치부터 socketLB.hostNamespaceOnly를 켜고 veth의 패킷 처리로 비교합니다. 여러 veth 중 직접 라우팅 장치도 명시합니다. 이것은 모든 운영 장애에 적용할 만능 설정이 아니라 측정 환경의 조건입니다. [socket-LB 우회와 장치 선택](https://docs.cilium.io/en/stable/network/kubernetes/kubeproxy-free/)의 전제를 함께 읽으세요.

실패의 이름도 정확히 써야 합니다. curl이 7로 끝나고 출력이 000이면 이 실습에서는 연결 실패로 기록합니다. 000은 서버가 보낸 HTTP 코드가 아닙니다. 이를 애플리케이션의 HTTP 500이나 NetworkPolicy의 timeout과 같은 것으로 적지 않습니다. 반대로 HTTP 200도 다른 서버의 응답이면 부족하므로 실제 서버는 이번 요청 식별자를 응답에 되돌려 줍니다. 보고서는 현재 리소스 UID와 설정 해시, 각 라우터의 경로, 서로 다른 요청의 응답을 함께 묶습니다.

다음 실습에서 할 것

초기 환경의 신원을 기록하고 잘못된 피어 AS 하나를 수정합니다. 이어 RIB만 가진 비교와 실제 Pod 전달을 만들고, 주소 할당과 선택 광고를 분리합니다. 마지막으로 초기 서비스의 광고를 철회한 뒤 다섯 상태가 동시에 남은 보고서를 작성합니다. 설정 변경·파드 재생성 뒤에는 낡은 보고서를 재사용하지 않습니다. 이 실습은 개인 VM의 IPv4·단일 노드 비교이며, 여러 물리 노드의 ECMP 분산이나 장애 조치까지 검증했다고 확대해서 말하지 않습니다.