Cilium - Networking - 파드 간 통신 (2) Encapsulation, Service LB-IPAM, L2 Announcement

Gyullbb·2025년 8월 9일
0

K8S

목록 보기
17/22

Native Routing 환경에서 잘못된 라우팅 설정으로 인해 통신이 되지 않는 상황을 만들고, 이를 라우팅 설정 변경을 통해 해결하는 실습을 진행한다.

실습 환경

Kubernetes 클러스터 노드

  • k8s-ctr(IP: 192.168.10.100, podCIDR: 172.20.0.98)
  • k8s-w1(IP: 192.168.10.101, podCIDR: 172.20.1.60)
  • k8s-w0(IP: 192.168.20.100, podCIDR: 172.20.2.159)

Router 노드

  • router(IP: 192.168.10.200)

초기 상태

autoDirectNodeRoutes

Native Routing Mode 클러스터에서 autoDirectNodeRoutes=true로 설정하면, 같은 네트워크 대역에 있는 노드들은 서로의 podCIDR 정보를 자동으로 라우팅 테이블에 추가한다.
그러나 k8s-w0 노드는 다른 네트워크 대역에 속해 있기 때문에, 이 노드의 podCIDR은 라우팅 테이블에 자동으로 추가되지 않는다.

k8s-ctr
(|HomeLab:N/A) root@k8s-ctr:~# ip -c route
default via 10.0.2.2 dev eth0 proto dhcp src 10.0.2.15 metric 100
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
10.0.2.2 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
10.0.2.3 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
172.20.0.0/24 via 172.20.0.98 dev cilium_host proto kernel src 172.20.0.98
172.20.0.98 dev cilium_host proto kernel scope link
172.20.1.0/24 via 192.168.10.101 dev eth1 proto kernel <- autoDirectNodeRoutes로 인한 추가
192.168.10.0/24 dev eth1 proto kernel scope link src 192.168.10.100

k8s-w1
root@k8s-w1:~# ip -c route
default via 10.0.2.2 dev eth0 proto dhcp src 10.0.2.15 metric 100
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
10.0.2.2 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
10.0.2.3 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
172.20.0.0/24 via 192.168.10.100 dev eth1 proto kernel <- autoDirectNodeRoutes로 인한 추가
172.20.1.0/24 via 172.20.1.60 dev cilium_host proto kernel src 172.20.1.60
172.20.1.60 dev cilium_host proto kernel scope link
192.168.10.0/24 dev eth1 proto kernel scope link src 192.168.10.101

k8s-w0
root@k8s-w0:~# ip -c route
default via 10.0.2.2 dev eth0 proto dhcp src 10.0.2.15 metric 100
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
10.0.2.2 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
10.0.2.3 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
172.20.2.0/24 via 172.20.2.159 dev cilium_host proto kernel src 172.20.2.159
172.20.2.159 dev cilium_host proto kernel scope link
192.168.20.0/24 dev eth1 proto kernel scope link src 192.168.20.100
* k8s-ctr, k8s-w1 관련 route 없음

파드 통신 확인

k8s-w1과 k8s-w0에 각각 배포된 webpod를 호출하면, 일부만 응답이 오고 일부는 응답이 없다.
특히 k8s-w0에서 실행 중인 webpod IP를 직접 지정하여 ping을 시도해도 100% 패킷 손실이 발생한다.

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get ciliumnode -o json | jq '.items[].spec.addresses'
[
  {
    "ip": "192.168.10.100",
    "type": "InternalIP"
  },
  {
    "ip": "172.20.0.98",
    "type": "CiliumInternalIP"
  }
]
[
  {
    "ip": "192.168.20.100",
    "type": "InternalIP"
  },
  {
    "ip": "172.20.2.159",
    "type": "CiliumInternalIP"
  }
]
[
  {
    "ip": "192.168.10.101",
    "type": "InternalIP"
  },
  {
    "ip": "172.20.1.60",
    "type": "CiliumInternalIP"
  }
]

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get pods -o wide
NAME                      READY   STATUS    RESTARTS      AGE     IP             NODE      NOMINATED NODE   READINESS GATES
curl-pod                  1/1     Running   1 (20m ago)   6h14m   172.20.0.201   k8s-ctr   <none>           <none>
webpod-696774f575-5nvlc   1/1     Running   1 (18m ago)   6h13m   172.20.2.50    k8s-w0    <none>           <none>
webpod-696774f575-dsblb   1/1     Running   1 (20m ago)   6h12m   172.20.0.130   k8s-ctr   <none>           <none>
webpod-696774f575-wssxg   1/1     Running   1 (19m ago)   6h12m   172.20.1.184   k8s-w1    <none>           <none>

(|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
Hostname: webpod-696774f575-wssxg
---
---
Hostname: webpod-696774f575-dsblb
---
---
Hostname: webpod-696774f575-dsblb
---
---
Hostname: webpod-696774f575-dsblb
---
---
---

(|HomeLab:N/A) root@k8s-ctr:~# export WEBPOD=$(kubectl get pod -l app=webpod --field-selector spec.nodeName=k8s-w0 -o jsonpath='{.items[0].status.podIP}')
(|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it curl-pod -- ping -c 2 -w 1 -W 1 $WEBPOD
PING 172.20.2.50 (172.20.2.50) 56(84) bytes of data.

--- 172.20.2.50 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

패킷 흐름을 router 노드에서 tcpdump로 확인해보면, 요청이 eth1(내부망)으로 들어오지만, 다시 eth0(외부망)으로 나가 버린다.

root@router:~# tcpdump -i any icmp -nn
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
18:11:47.232880 eth1  In  IP 172.20.0.163 > 172.20.2.54: ICMP echo request, id 7, seq 1, length 64
18:11:47.232892 eth0  Out IP 172.20.0.163 > 172.20.2.54: ICMP echo request, id 7, seq 1, length 64

router의 ip route를 확인한 결과, 클러스터 Pod CIDR 대역으로 가는 경로가 설정되어 있지 않다.
즉, k8s-w0의 Pod로 향해야 할 트래픽이 올바른 내부 경로를 찾지 못하고 외부로 빠져나가 통신이 실패한 것이다.

root@router:~# ip route
default via 10.0.2.2 dev eth0 proto dhcp src 10.0.2.15 metric 100
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
10.0.2.2 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
10.0.2.3 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
10.10.1.0/24 dev loop1 proto kernel scope link src 10.10.1.200
10.10.2.0/24 dev loop2 proto kernel scope link src 10.10.2.200
192.168.10.0/24 dev eth1 proto kernel scope link src 192.168.10.200
192.168.20.0/24 dev eth2 proto kernel scope link src 192.168.20.200

파드 통신 개선

각 노드의 Pod CIDR 대역으로 향하는 트래픽이 해당 노드로 전달되도록 라우팅 규칙을 수동으로 추가한다.

root@router:~# ip route add 172.20.1.0/24 via 192.168.10.101
root@router:~# ip route add 172.20.0.0/24 via 192.168.10.100
root@router:~# ip route add 172.20.2.0/24 via 192.168.20.100

root@router:~# ip -c route
default via 10.0.2.2 dev eth0 proto dhcp src 10.0.2.15 metric 100
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
10.0.2.2 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
10.0.2.3 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
10.10.1.0/24 dev loop1 proto kernel scope link src 10.10.1.200
10.10.2.0/24 dev loop2 proto kernel scope link src 10.10.2.200
172.20.0.0/24 via 192.168.10.100 dev eth1
172.20.1.0/24 via 192.168.10.101 dev eth1
172.20.2.0/24 via 192.168.20.100 dev eth2
192.168.10.0/24 dev eth1 proto kernel scope link src 192.168.10.200
192.168.20.0/24 dev eth2 proto kernel scope link src 192.168.20.200

라우팅 규칙을 추가한 후 다시 k8s-w0의 pod로의 통신을 확인해보면 정상 통신되는 것을 확인할 수 있다.

(|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it curl-pod -- ping -c 2 -w 1 -W 1 $WEBPOD
PING 172.20.2.54 (172.20.2.54) 56(84) bytes of data.
64 bytes from 172.20.2.54: icmp_seq=1 ttl=61 time=0.958 ms

--- 172.20.2.54 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.958/0.958/0.958/0.000 ms
command terminated with exit code 1

root@router:~# tcpdump -i any icmp -nn
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
18:20:48.457587 eth1  In  IP 172.20.0.163 > 172.20.2.54: ICMP echo request, id 19, seq 1, length 64
18:20:48.457605 eth2  Out IP 172.20.0.163 > 172.20.2.54: ICMP echo request, id 19, seq 1, length 64
18:20:48.458117 eth2  In  IP 172.20.2.54 > 172.20.0.163: ICMP echo reply, id 19, seq 1, length 64
18:20:48.458118 eth1  Out IP 172.20.2.54 > 172.20.0.163: ICMP echo reply, id 19, seq 1, length 64
^C
4 packets captured
4 packets received by filter
0 packets dropped by kernel

해당 실습은 노드 개수가 적기 때문에 수동으로 라우트를 추가해도 문제가 되지 않는다.
그러나 노드가 100대 이상으로 늘어나고, 각 노드의 Pod CIDR이 변경되는 상황에서는 모든 노드에 대해 수동으로 라우팅을 설정하는 것은 사실상 불가능하다.

이러한 문제를 해결하기 위해 BGP를 통한 동적 라우팅이나 Overlay 네트워크를 활용한다.
이번 글에서는 Overlay 네트워크를 이용해 통신이 이루어지는 방식을 살펴본다.

Overlay 네트워크

Cilium은 기본적으로 Encapsulation(캡슐화) 기반 네트워킹 방식을 제공한다. 각 노드 간에 UDP 기반 터널(VXLAN 또는 Geneve)을 자동으로 구성해 파드의 트래픽을 캡슐화하여 전달하기 때문에, 물리 노드 간 IP 통신만 가능하면 각 노드의 Pod CIDR에 대해 별도의 라우트를 수동 설정할 필요가 없다.

VXLAN


VXLAN은 고정된 8바이트 헤더 구조를 가진 L2 over L3 오버레이 프로토콜이다.
내부 L2 프레임을 외부 UDP 패킷으로 캡슐화하여 전송하며, 데이터센터와 클라우드 환경에서 널리 사용된다.

VXLAN은 VTEP(VXLAN Tunnel Endpoint)라는 네트워크 장비를 통해서 동작하는데, VTEP는 오버레이 네트워크와 물리 네트워크 사이를 연결하는 역할을 수행한다.

1) Ingress VTEP (송신 쪽)
패킷이 오버레이 네트워크에 들어오면, Ingress VTEP이 다음 작업을 수행한다.

  • 원본 L2 프레임 수신
  • 원본 L2 프레임을 UDP 패킷의 페이로드에 넣고 헤더 추가
  • VXLAN 헤더 (8바이트): VNI(네트워크 ID)로 목적지 설정
  • Outer UDP/IP 헤더: Egress VTEP의 IP 주소로 목적지 설정
  • Outer Ethernet 헤더: 물리망에서의 다음 홉 MAC 주소로 목적지 설정
  • 물리망으로 전송

이렇게 캡슐화된 패킷은 일반 L3 네트워크를 통해 라우팅된다.

2) Egress VTEP (수신 쪽)
패킷이 터널 반대쪽에 도착하면, Egress VTEP가 다음 작업을 수행한다.

  • Outer Ethernet, Outer IP, Outer UDP, 그리고 VXLAN 헤더를 순서대로 벗겨냄.
  • 원본 L2 프레임 복원
  • 목적지에 전달

GENEVE

Geneve는 VXLAN에서 확장성과 유연성을 개선한 최신 캡슐화 프로토콜(IETF 표준)이다.

(참고문서 : https://tetrate.io/blog/using-geneve-tunnels-to-implement-istio-ambient-mesh-traffic-interception)

VXLAN과 GENEVE의 가장 큰 차이점은 사용 포트(VXLAN(4789/UDP), GENEVE(6081/UDP))와 헤더 구조이다.

VXLAN의 경우 헤더가 고정되어있기 때문에 캡슐화 정보 외의 추가 메타데이터(정책, QoS, 보안 태그, 트래픽 분석 등)를 담을 수 없는데,
GENEVE는 이 VXLAN의 단점을 TLV 기반 확장성으로 개선하였다.

TLV(Type-Length-Value)

TLV는 Geneve 옵션 필드를 이루는 기본 단위이며, 여러 개를 연속해서 붙일 수 있다.
이를 통해 새로운 네트워크 기능을 블록처럼 조립하듯 필요에 따라 유연하게 추가할 수 있다.

필드설명
Type옵션의 종류 식별 (예: 보안 태그, QoS, 정책 ID 등)
Length데이터 길이(4바이트 단위)
Value실제 메타데이터 값

TLV 구조의 장점

  • 유연성: 필요할 때만 옵션 추가 → 불필요한 오버헤드 감소
  • 확장성: 새로운 기능이 필요해도 프로토콜 개정 없이 Type만 새로 정의해서 추가 가능
  • 호환성: TLV를 이해 못하는 장비도 기본 패킷은 처리 가능 : 호환성 확장
  • 메타데이터 다양성: 정책, 보안, 트래픽 분석, 장애 추적 등 다양한 목적의 데이터 삽입 가능

TLV 옵션 사용 예시 : 서비스 체이닝 (Service Chaining)

TLV 옵션 사용 예시로 서비스 체이닝이 있다.
서비스 체이닝이란 패킷이 네트워크를 통과하는 동안 방화벽, IDS, 로드밸런서, WAN 최적화 장비 등 여러 네트워크 기능을 정해진 순서대로 거치게 하는 기술이다.
VXLAN은 오직 VNI만 있어 어떤 순서로 서비스 거칠지 알 수 없지만, Geneve의 TLV 옵션을 쓰면 패킷 안에 경로 정보(SPID)와 현재 단계(SI)를 넣어 서비스 체이닝이 가능해진다.

예시

Option Class: 0x0101 (Service Chaining)
Type: Path + Index
Length: 8 bytes
Option Data:
    SPID = 0x0012
    SI   = 0x04

SPID (Service Path ID)

  • 어떤 서비스 경로인지를 나타내는 번호
  • 예: 0x0012 → 방화벽 → IDS → WAN → LB

SI (Service Index)

  • 현재 서비스 경로에서 어디까지 왔는지를 나타내는 카운터
  • 시작할 때는 전체 길이, 서비스 거칠 때마다 1씩 감소

동작 원리

  • SPID 확인 → 미리 정의된 경로(서비스 체인)를 찾음
  • SI 값을 보고 지금 어느 단계인지 파악
  • 서비스 기능을 수행한 뒤 SI를 1 감소
  • SI가 0이 되면 체인 종료, 최종 목적지로 전달

MTU 1450

물리적 네트워크에서 1500바이트의 표준 이더넷 MTU를 사용하는 경우, VXLAN과 Geneve는 MTU를 1450으로 설정하는 것이 일반적이다.
위의 VXLAN과 GENEVE 패킷 이미지를 보면 VXLAN은 Original L2 Frame을 제외한 고정 헤더크기가 50 Byte이며, GENEVE도 마찬가지로 최소 헤더 크기가 50Byte이다.
캡슐화 시 추가되는 외부 IP/UDP/프로토콜 헤더 크기를 고려해, 패킷이 조각(Fragmentation)나지 않도록 MTU를 1450으로 설정하는 것이다.
GENEVE의 경우 TLV 옵션에 따라 헤더 크기가 증가할 수 있기 때문에, 옵션을 고려하여 MTU 사이즈를 결정해야 한다.

이전 글에서 VXLAN에서의 통신 실습을 해보았기 때문에 이번에는 GENEVE로 통신 확인을 진행한다.

GENEVE 통신 확인

GENEVE 설정

# 모듈 확인
(|HomeLab:N/A) root@k8s-ctr:~# grep -E 'CONFIG_VXLAN=y|CONFIG_VXLAN=m|CONFIG_GENEVE=y|CONFIG_GENEVE=m|CONFIG_FIB_RULES=y' /boot/config-$(uname -r)
CONFIG_FIB_RULES=y
CONFIG_VXLAN=m
CONFIG_GENEVE=m

(|HomeLab:N/A) root@k8s-ctr:~# lsmod | grep -E 'vxlan|geneve'
(|HomeLab:N/A) root@k8s-ctr:~# modprobe geneve
(|HomeLab:N/A) root@k8s-ctr:~# lsmod | grep -E 'vxlan|geneve'
geneve                 45056  0
ip6_udp_tunnel         16384  1 geneve
udp_tunnel             36864  1 geneve

(|HomeLab:N/A) root@k8s-ctr:~# helm upgrade cilium cilium/cilium --namespace kube-system --version 1.18.0 --reuse-values --set routingMode=tunnel --set tunnelProtocol=geneve --set autoDirectNodeRoutes=false --set installNoConntrackIptablesRules=false

# cilium 설정 확인
(|HomeLab:N/A) root@k8s-ctr:~# cilium features status | grep datapath_network
Yes      cilium_feature_datapath_network                                         mode=overlay-geneve                               1        1       1

(|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it -n kube-system ds/cilium -- cilium status | grep ^Routing
Routing:                 Network: Tunnel [geneve]   Host: BPF

(|HomeLab:N/A) root@k8s-ctr:~# cilium config view | grep tunnel
routing-mode                                      tunnel
tunnel-protocol                                   geneve
tunnel-source-port-range                          0-0

(|HomeLab:N/A) root@k8s-ctr:~# ip -c addr show cilium_geneve
26: cilium_geneve: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default
    link/ether 0e:73:b0:50:47:a6 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::c73:b0ff:fe50:47a6/64 scope link
       valid_lft forever preferred_lft forever

GENEVE 설정 확인

클러스터의 노드가 서로 다른 네트워크 대역에 있지만, 모든 노드에 각 Pod의 네트워크 대역 정보가 route에 등록되어 있다.

(|HomeLab:N/A) root@k8s-ctr:~# ip -c route | grep cilium_host
172.20.0.0/24 via 172.20.0.74 dev cilium_host proto kernel src 172.20.0.74 <- pod 대역
172.20.0.74 dev cilium_host proto kernel scope link
172.20.1.0/24 via 172.20.0.74 dev cilium_host proto kernel src 172.20.0.74 mtu 1450 <- pod 대역
172.20.2.0/24 via 172.20.0.74 dev cilium_host proto kernel src 172.20.0.74 mtu 1450 <- pod 대역

root@k8s-w1:~# ip -c route | grep cilium_host
172.20.0.0/24 via 172.20.1.41 dev cilium_host proto kernel src 172.20.1.41 mtu 1450 <- pod 대역
172.20.1.0/24 via 172.20.1.41 dev cilium_host proto kernel src 172.20.1.41 <- pod 대역
172.20.1.41 dev cilium_host proto kernel scope link
172.20.2.0/24 via 172.20.1.41 dev cilium_host proto kernel src 172.20.1.41 mtu 1450 <- pod 대역

root@k8s-w0:~# ip -c route
default via 10.0.2.2 dev eth0 proto dhcp src 10.0.2.15 metric 100
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
10.0.2.2 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
10.0.2.3 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
10.10.0.0/16 via 192.168.20.200 dev eth1 proto static
172.20.0.0/24 via 172.20.2.204 dev cilium_host proto kernel src 172.20.2.204 mtu 1450 <- pod 대역
172.20.0.0/16 via 192.168.20.200 dev eth1 proto static
172.20.1.0/24 via 172.20.2.204 dev cilium_host proto kernel src 172.20.2.204 mtu 1450 <- pod 대역
172.20.2.0/24 via 172.20.2.204 dev cilium_host proto kernel src 172.20.2.204 <- pod 대역
172.20.2.204 dev cilium_host proto kernel scope link
192.168.10.0/24 via 192.168.20.200 dev eth1 proto static
192.168.20.0/24 dev eth1 proto kernel scope link src 192.168.20.100

(|HomeLab:N/A) root@k8s-ctr:~# ip route get 172.20.1.10
172.20.1.10 dev cilium_host src 172.20.0.74 uid 0
    cache mtu 1450

(|HomeLab:N/A) root@k8s-ctr:~# ip route get 172.20.2.10
172.20.2.10 dev cilium_host src 172.20.0.74 uid 0
    cache mtu 1450

route 규칙을 보면 src에 172.20.0.74, 172.20.1.41, 172.20.2.204와 같은 IP가 지정되어 있는데, 이는 각 노드의 cilium-agent에서 router역할을 하는 IP이다.

(|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it $CILIUMPOD0 -n kube-system -c cilium-agent -- cilium status --all-addresses | grep router
  172.20.0.74 (router)
(|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it $CILIUMPOD1 -n kube-system -c cilium-agent -- cilium status --all-addresses | grep router
  172.20.1.41 (router)
(|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it $CILIUMPOD2 -n kube-system -c cilium-agent -- cilium status --all-addresses | grep router
  172.20.2.204 (router)

Cilium은 각 노드에 eBPF 기반 데이터패스와 가상 인터페이스(cilium_host)를 만드는데, 이 cilium_host 인터페이스가 해당 노드에서 다른 노드/외부로 나가는 트래픽의 기본 라우터 역할을 한다.
Cilium Router IP는 이 cilium_host 인터페이스에 할당된 IP이다.
cilium status로 본 router ip와 cilium_host의 ip가 동일한 것을 볼 수 있다.

(|HomeLab:N/A) root@k8s-ctr:~# ip addr show cilium_host
5: cilium_host@cilium_net: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether aa:71:74:33:f7:d6 brd ff:ff:ff:ff:ff:ff
    inet 172.20.0.74/32 scope global cilium_host
       valid_lft forever preferred_lft forever
    inet6 fe80::a871:74ff:fe33:f7d6/64 scope link
       valid_lft forever preferred_lft forever

root@k8s-w1:~# ip addr show cilium_host
5: cilium_host@cilium_net: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 16:39:18:15:d2:cd brd ff:ff:ff:ff:ff:ff
    inet 172.20.1.41/32 scope global cilium_host
       valid_lft forever preferred_lft forever
    inet6 fe80::1439:18ff:fe15:d2cd/64 scope link
       valid_lft forever preferred_lft forever

root@k8s-w0:~# ip addr show cilium_host
5: cilium_host@cilium_net: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether fa:b0:9f:c2:1b:84 brd ff:ff:ff:ff:ff:ff
    inet 172.20.2.204/32 scope global cilium_host
       valid_lft forever preferred_lft forever
    inet6 fe80::f8b0:9fff:fec2:1b84/64 scope link
       valid_lft forever preferred_lft forever

Cilium Router IP는 각 노드에 하나씩 존재하며, 같은 노드의 모든 Pod는 이 Router IP를 게이트웨이로 사용한다.

bpf ipcache를 보면 Cilium이 다른 노드에 뜬 pod의 경우 hastunnel이라는 flag를 부여하는 것을 볼 수 있는데, 이 flag가 있을 경우 캡슐화 대상으로 인지하여 캡슐화를 진행하게 된다.

# 다른 노드 내 pod
(|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -n kube-system $CILIUMPOD0 -- cilium-dbg bpf ipcache list | grep hastunnel
172.20.1.41/32      identity=6 encryptkey=0 tunnelendpoint=192.168.10.101 flags=hastunnel
172.20.1.73/32      identity=11761 encryptkey=0 tunnelendpoint=192.168.10.101 flags=hastunnel
172.20.2.54/32      identity=11761 encryptkey=0 tunnelendpoint=192.168.20.100 flags=hastunnel
172.20.2.204/32     identity=6 encryptkey=0 tunnelendpoint=192.168.20.100 flags=hastunnel
172.20.2.0/24       identity=2 encryptkey=0 tunnelendpoint=192.168.20.100 flags=hastunnel
172.20.1.0/24       identity=2 encryptkey=0 tunnelendpoint=192.168.10.101 flags=hastunnel

# 동일 노드 내 pod 
(|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -n kube-system $CILIUMPOD0 -- cilium-dbg bpf ipcache list | grep -v hastunnel
IP PREFIX/ADDRESS   IDENTITY
172.20.0.63/32      identity=11761 encryptkey=0 tunnelendpoint=0.0.0.0 flags=<none>
172.20.0.152/32     identity=22419 encryptkey=0 tunnelendpoint=0.0.0.0 flags=<none>
172.20.0.163/32     identity=29101 encryptkey=0 tunnelendpoint=0.0.0.0 flags=<none>
172.20.0.210/32     identity=4046 encryptkey=0 tunnelendpoint=0.0.0.0 flags=<none>
172.20.0.224/32     identity=57021 encryptkey=0 tunnelendpoint=0.0.0.0 flags=<none>
192.168.10.101/32   identity=6 encryptkey=0 tunnelendpoint=0.0.0.0 flags=<none>
192.168.20.100/32   identity=6 encryptkey=0 tunnelendpoint=0.0.0.0 flags=<none>
0.0.0.0/0           identity=2 encryptkey=0 tunnelendpoint=0.0.0.0 flags=<none>
10.0.2.15/32        identity=1 encryptkey=0 tunnelendpoint=0.0.0.0 flags=<none>
172.20.0.81/32      identity=35319 encryptkey=0 tunnelendpoint=0.0.0.0 flags=<none>
172.20.0.242/32     identity=57021 encryptkey=0 tunnelendpoint=0.0.0.0 flags=<none>
172.20.0.74/32      identity=1 encryptkey=0 tunnelendpoint=0.0.0.0 flags=<none>
172.20.0.77/32      identity=31483 encryptkey=0 tunnelendpoint=0.0.0.0 flags=<none>
172.20.0.25/32      identity=14322 encryptkey=0 tunnelendpoint=0.0.0.0 flags=<none>
172.20.0.186/32     identity=16348 encryptkey=0 tunnelendpoint=0.0.0.0 flags=<none>
192.168.10.100/32   identity=1 encryptkey=0 tunnelendpoint=0.0.0.0 flags=<none>

GENEVE 통신 확인

서로 다른 노드 간 Pod통신을 통해 GENEVE 통신을 확인해본다.
GENEVE는 기본 포트로 6081/UDP를 사용한다.

(|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it curl-pod -- ping -c 2 -w 1 -W 1 $WEBPOD
PING 172.20.2.54 (172.20.2.54) 56(84) bytes of data.
64 bytes from 172.20.2.54: icmp_seq=1 ttl=63 time=0.896 ms
--- 172.20.2.54 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.896/0.896/0.896/0.000 ms
command terminated with exit code 1

root@router:~# tcpdump -i any udp port 6081 -nn
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
23:59:32.251108 eth1  In  IP 192.168.10.100.31322 > 192.168.20.100.6081: Geneve, Flags [none], vni 0x71ad: IP 172.20.0.163 > 172.20.2.54: ICMP echo request, id 37, seq 1, length 64
23:59:32.251125 eth2  Out IP 192.168.10.100.31322 > 192.168.20.100.6081: Geneve, Flags [none], vni 0x71ad: IP 172.20.0.163 > 172.20.2.54: ICMP echo request, id 37, seq 1, length 64
23:59:32.251597 eth2  In  IP 192.168.20.100.29257 > 192.168.10.100.6081: Geneve, Flags [none], vni 0x2df1: IP 172.20.2.54 > 172.20.0.163: ICMP echo reply, id 37, seq 1, length 64
23:59:32.251607 eth1  Out IP 192.168.20.100.29257 > 192.168.10.100.6081: Geneve, Flags [none], vni 0x2df1: IP 172.20.2.54 > 172.20.0.163: ICMP echo reply, id 37, seq 1, length 64

pod 대역이 캡슐화되어 노드 간 통신을 통해 패킷이 전송되는 것을 확인할 수 있다.

이전 글에서 확인한 VXLAN과 마찬가지로 hubble로 모니터링을 할 때, 패킷이 캡슐화 된다는 의미의 to-overlay 메시지를 확인할 수 있다.

(|HomeLab:N/A) root@k8s-ctr:~# hubble observe -f --protocol tcp --pod curl-pod
(|HomeLab:N/A) root@k8s-ctr:~# hubble observe -f --protocol tcp --pod curl-pod
Aug  8 15:05:43.118: default/curl-pod:36548 (ID:29101) -> default/webpod-697b545f57-9c7vb:80 (ID:11761) to-endpoint FORWARDED (TCP Flags: SYN)
Aug  8 15:05:43.118: default/curl-pod:36548 (ID:29101) <- default/webpod-697b545f57-9c7vb:80 (ID:11761) to-overlay FORWARDED (TCP Flags: SYN, ACK)
Aug  8 15:05:43.119: default/curl-pod:36548 (ID:29101) -> default/webpod-697b545f57-9c7vb:80 (ID:11761) to-endpoint FORWARDED (TCP Flags: ACK)
Aug  8 15:05:43.119: default/curl-pod:36548 (ID:29101) -> default/webpod-697b545f57-9c7vb:80 (ID:11761) to-endpoint FORWARDED (TCP Flags: ACK, PSH)
Aug  8 15:05:43.120: default/curl-pod:36548 (ID:29101) <> default/webpod-697b545f57-9c7vb (ID:11761) pre-xlate-rev TRACED (TCP)
Aug  8 15:05:43.120: default/curl-pod:36548 (ID:29101) <> default/webpod-697b545f57-9c7vb (ID:11761) pre-xlate-rev TRACED (TCP)
Aug  8 15:05:43.121: default/curl-pod:36548 (ID:29101) <> default/webpod-697b545f57-9c7vb (ID:11761) pre-xlate-rev TRACED (TCP)
Aug  8 15:05:43.121: default/curl-pod (ID:29101) <> 10.96.107.144:80 (world) pre-xlate-fwd TRACED (TCP)
...

Service LB-IPAM

LB IPAM이란 Cilium이 IP 주소를 LoadBalancer 유형의 서비스에 할당할 수 있게 해주는 기능이다.
LB IPAM은 Cilium BGP Control Plane 및 L2 Announcements / L2 Aware LB(베타)와 같은 기능과 함께 작동하는데, Cilium BGP Control Plane을 사용하여 LB IPAM이 할당한 IP 주소를 BGP를 통해 광고하고 L2 Announcements / L2 Aware LB(베타)를 통해 로컬로 광고하게 된다.

Cilium에서 LoadBalancer IP Pool을 생성한 후 해당 Pool 내에서 LB에 IP를 할당하는 설정을 해본다.

(|HomeLab:N/A) root@k8s-ctr:~# cat << EOF | kubectl apply -f -
apiVersion: "cilium.io/v2"  # v1.17 : cilium.io/v2alpha1
kind: CiliumLoadBalancerIPPool
metadata:
  name: "cilium-lb-ippool"
spec:
  blocks:
  - start: "192.168.10.211"
    stop:  "192.168.10.215"
EOF
ciliumloadbalancerippool.cilium.io/cilium-lb-ippool created

(|HomeLab:N/A) root@k8s-ctr:~# kubectl api-resources | grep -i CiliumLoadBalancerIPPool
ciliumloadbalancerippools           ippools,ippool,lbippool,lbippools   cilium.io/v2                      false        CiliumLoadBalancerIPPool

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get ippools
NAME               DISABLED   CONFLICTING   IPS AVAILABLE   AGE
cilium-lb-ippool   false      False         5               74s

기존에 배포한 webpod의 Service를 Loadbalancer Service로 변경하면 LB IPAM에 의해 자동으로 ExternalIP가 할당된다.
kubectl get ippools명령어를 통해 사용가능한 ip의 개수를 확인할 수 있다.

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get svc webpod
NAME     TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
webpod   ClusterIP   10.96.107.144   <none>        80/TCP    26h

(|HomeLab:N/A) root@k8s-ctr:~# kubectl patch svc webpod -p '{"spec":{"type":"LoadBalancer"}}'
service/webpod patched

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get svc webpod
NAME     TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)        AGE
webpod   LoadBalancer   10.96.107.144   192.168.10.211   80:31968/TCP   26h

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get ippools
NAME               DISABLED   CONFLICTING   IPS AVAILABLE   AGE
cilium-lb-ippool   false      False         4               3m11s

Kubernetes 노드 내 통신

Kubernetes 노드에서 생성한 LoadBalancer의 ExternalIP를 통해 Pod와 정상적으로 통신되는 것을 확인할 수 있다.

(|HomeLab:N/A) root@k8s-ctr:~# LBIP=$(kubectl get svc webpod -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

(|HomeLab:N/A) root@k8s-ctr:~# curl -s $LBIP
Hostname: webpod-697b545f57-9c7vb
IP: 127.0.0.1
IP: ::1
IP: 172.20.2.54
IP: fe80::a8cc:5fff:fee9:7bf7
RemoteAddr: 172.20.0.74:52054
GET / HTTP/1.1
Host: 192.168.10.211
User-Agent: curl/8.5.0
Accept: */*

(|HomeLab:N/A) root@k8s-ctr:~# for i in {1..100};  do kubectl exec -it curl-pod -- curl -s $LBIP | grep Hostname; done | sort | uniq -c | sort -nr

     36 Hostname: webpod-697b545f57-9r7tp
     33 Hostname: webpod-697b545f57-9c7vb
     31 Hostname: webpod-697b545f57-wls7t

외부 통신

실습 환경 상에서 클러스터 외부에 있는 Router 노드에서 LoadBalancer의 ExternalIP로 통신을 시도해보면 정상 통신이 되지 않는다.

root@router:~# LBIP=192.168.10.211

root@router:~# curl --connect-timeout 1 $LBIP
curl: (28) Failed to connect to 192.168.10.211 port 80 after 1001 ms: Timeout was reached

root@router:~# arping -i eth1 $LBIP -c 1
ARPING 192.168.10.211
Timeout

--- 192.168.10.211 statistics ---
1 packets transmitted, 0 packets received, 100% unanswered (0 extra)

LoadBalancer 타입 서비스의 ExternalIP는 클러스터 내부 노드들이 해당 IP를 직접 소유하거나 응답할 수 있는 IP가 아니다.
외부에서 ExternalIP에 ARP ping을 보냈지만, ARP 응답이 없는데, 이는 ExternalIP가 클러스터 노드 네트워크 내에 실제로 할당된 IP가 아니기 때문에 클러스터 내부 노드에서 ARP 응답을 하지 않아 외부 라우터가 MAC 주소를 알 수 없기 때문이다.

ExternalIP를 인지하기 위해서는 외부 네트워크에 클러스터 내 특정 노드가 LoadBalancer IP를 소유하고 있음을 알릴 수 있는 수단이 필요한데, 대표적으로는 L2 Announcement가 있다.

Cilium L2 Announcement

Cilium L2 Announcement는 클러스터 내 특정 노드가 LoadBalancer IP를 자신이 소유한 IP인 것처럼 외부 네트워크에 알리는 기능이다.
특정 노드가 LoadBalancer IP에 대한 ARP 요청을 받으면 직접 ARP 응답을 보내도록 처리하기 때문에, 외부 라우터는 LoadBalancer IP와 연결된 MAC 주소를 학습할 수 있게 된다.
결과적으로, 외부 네트워크에서 LoadBalancer IP로 보내는 패킷이 올바른 노드로 전달되어 통신이 가능해집진다.

Cilium에서 L2 Announcement 설정을 활성화 한 후 상태 변화를 확인해보자.

# router
root@router:~# arping -i eth1 $LBIP -c 100000
ARPING 192.168.10.211
Timeout
Timeout
Timeout
Timeout
Timeout

(|HomeLab:N/A) root@k8s-ctr:~# helm upgrade cilium cilium/cilium --namespace kube-system --version 1.18.0 --reuse-values \
   --set l2announcements.enabled=true --set l2NeighDiscovery.enabled=true && watch -d kubectl get pod -A

(|HomeLab:N/A) root@k8s-ctr:~# kubectl rollout restart -n kube-system ds/cilium
daemonset.apps/cilium restarted

(|HomeLab:N/A) root@k8s-ctr:~# kubectl -n kube-system exec ds/cilium -c cilium-agent -- cilium-dbg config --all | grep EnableL2Announcements
EnableL2Announcements             : true

(|HomeLab:N/A) root@k8s-ctr:~# cilium config view | grep enable-l2
enable-l2-announcements                           true
enable-l2-neigh-discovery                         true

(|HomeLab:N/A) root@k8s-ctr:~# ip neigh show | grep -i reachable
192.168.10.101 dev eth1 lladdr 08:00:27:af:e6:df managed extern_learn REACHABLE
10.0.2.2 dev eth0 lladdr 52:55:0a:00:02:02 managed extern_learn REACHABLE
192.168.10.200 dev eth1 lladdr 08:00:27:0f:bd:dd managed extern_learn REACHABLE

L2 ARP 모드 정책 설정

CiliumL2AnnouncementPolicy를 통해 ARP를 광고할 대상 Service와 Node를 지정한다.
이 때, LoadBalancer IP Pool은 반드시 같은 네트워크 대역에 속한 노드에서만 유효하다는 제약사항이 있다.
이 때문에 실습 환경에서 다른 네트워크 대역에 존재하는 노드인 k8s-w0를 제외하고 정책을 설정해야 한다.
k8s-w0를 리더 노드로 지정하게 되면 리더 노드 선정 과정에서 동작 실패가 발생하게 된다.

cat << EOF | kubectl apply -f -
apiVersion: "cilium.io/v2alpha1"  # not v2
kind: CiliumL2AnnouncementPolicy
metadata:
  name: policy1
spec:
  serviceSelector:
    matchLabels:
      app: webpod
  nodeSelector:
    matchExpressions:
      - key: kubernetes.io/hostname
        operator: NotIn
        values:
          - k8s-w0
  interfaces:
  - ^eth[1-9]+
  externalIPs: true
  loadBalancerIPs: true
EOF

리더 노드 선정 확인 및 상태 확인

정책 적용 직후, Cilium은 Lease 리소스를 생성하여 ARP 광고를 수행할 리더 노드를 선출한다.
해당 실습에서는 k8s-w1 노드가 리더로 선정되었다.

(|HomeLab:N/A) root@k8s-ctr:~# kubectl -n kube-system get lease | grep "cilium-l2announce"
cilium-l2announce-default-webpod       k8s-w1                                                                      4s

(|HomeLab:N/A) root@k8s-ctr:~# kubectl -n kube-system get lease/cilium-l2announce-default-webpod -o yaml | yq
{
  "apiVersion": "coordination.k8s.io/v1",
  "kind": "Lease",
  "metadata": {
    "creationTimestamp": "2025-08-09T12:35:25Z",
    "name": "cilium-l2announce-default-webpod",
    "namespace": "kube-system",
    "resourceVersion": "76902",
    "uid": "98cf5f36-f611-47b4-aad6-de3f83312fc9"
  },
  "spec": {
    "acquireTime": "2025-08-09T12:35:25.085954Z",
    "holderIdentity": "k8s-w1",
    "leaseDurationSeconds": 15,
    "leaseTransitions": 0,
    "renewTime": "2025-08-09T12:35:45.144393Z"
  }
}

(|HomeLab:N/A) root@k8s-ctr:~# export CILIUMPOD1=$(kubectl get -l k8s-app=cilium pods -n kube-system --field-selector spec.nodeName=k8s-w1  -o jsonpath='{.items[0].metadata.name}')

리더 노드의 Cilium 에이전트에서 현재 ARP 광고가 수행되는 인터페이스와 IP를 조회한다.

(|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -n kube-system $CILIUMPOD1 -- cilium-dbg shell -- db/show l2-announce
IP               NetworkInterface
192.168.10.211   eth1

리더 노드에서 External LoadBalancer IP을 대상으로 ARP 요청을 보내면 ARP 패킷이 인터페이스를 통해 정상 전송된다.
또한 HTTP 요청도 정상 전송된다.

(|HomeLab:N/A) root@k8s-ctr:~# arping -i eth1 $LBIP -c 1000
ARPING 192.168.10.211
60 bytes from 08:00:27:af:e6:df (192.168.10.211): index=0 time=362.306 usec
60 bytes from 08:00:27:af:e6:df (192.168.10.211): index=1 time=330.916 usec
60 bytes from 08:00:27:af:e6:df (192.168.10.211): index=2 time=260.588 usec
60 bytes from 08:00:27:af:e6:df (192.168.10.211): index=3 time=255.961 usec
^C
--- 192.168.10.211 statistics ---
4 packets transmitted, 4 packets received,   0% unanswered (0 extra)
rtt min/avg/max/std-dev = 0.256/0.302/0.362/0.046 ms

(|HomeLab:N/A) root@k8s-ctr:~# curl --connect-timeout 1 $LBIP
Hostname: webpod-697b545f57-wls7t
IP: 127.0.0.1
IP: ::1
IP: 172.20.1.73
IP: fe80::a076:2dff:fe73:24ac
RemoteAddr: 172.20.0.74:46438
GET / HTTP/1.1
Host: 192.168.10.211
User-Agent: curl/8.5.0
Accept: */*

ARP 테이블을 조회해보면, LoadBalancer IP에 대응하는 MAC 주소가 현재 리더 노드인 k8s-w1의 eth1 MAC 주소와 일치한다.

Router 노드에서 ARP 테이블을 조회해보면 LoadBalancer IP(192.168.10.211)가 리더노드 MAC 주소와 매핑되어 있다.

(|HomeLab:N/A) root@k8s-ctr:~# arp -a | grep '08:00:27:af:e6:df'
k8s-w1 (192.168.10.101) at 08:00:27:af:e6:df [ether] on eth1

root@router:~# arp -a
? (192.168.10.101) at 08:00:27:af:e6:df [ether] on eth1
? (192.168.20.100) at 08:00:27:3a:be:fa [ether] on eth2
? (192.168.10.100) at 08:00:27:04:d1:9c [ether] on eth1
? (192.168.10.211) at 08:00:27:af:e6:df [ether] on eth1
? (10.0.2.3) at 52:55:0a:00:02:03 [ether] on eth0
_gateway (10.0.2.2) at 52:55:0a:00:02:02 [ether] on eth0

LoadBalancer IP에 다수 요청을 보내 서비스가 모든 Pod에 분산되는지를 확인해보면, 모든 Pod에 정상 분산되고 있다.

(|HomeLab:N/A) root@k8s-ctr:~# for i in {1..100};  do kubectl exec -it curl-pod -- curl -s $LBIP | grep Hostname; done | sort | uniq -c | sort -nr
     43 Hostname: webpod-697b545f57-wls7t
     29 Hostname: webpod-697b545f57-9r7tp
     28 Hostname: webpod-697b545f57-9c7vb

Failover

리더 노드의 Cilium Agent는 주기적으로 lease를 갱신하며 리더임을 선언하는데, 리더 노드에 네트워크 장애 등 특정 이유로 정상 동작을 하지 못하는 상황이면 Lease 갱신이 실패 된다.
Kubernetes API 서버는 leaseDurationSeconds 이후 Lease를 만료로 인식하게된다.

Lease가 만료되면, 다른 노드들의 Cilium Agent들이 API 서버에 Lease 획득 요청을 보낸다.
Kubernetes API 서버는 Lease를 신규 획득하려는 노드들 중 첫번째 요청을 승인하고, 승인받은 노드는 새로운 리더가 된다.

리더가 된 노드의 Cilium Agent는 즉시, 자신이 소유한 LoadBalancer IP에 대해 ARP 응답을 송출한다.

이를 실습으로 확인해본다.

# Failover 전 arp 상태 : 리더 노드 = k8s-w1
root@router:~# arp -a
? (192.168.10.101) at 08:00:27:af:e6:df [ether] on eth1
? (192.168.20.100) at 08:00:27:3a:be:fa [ether] on eth2
? (192.168.10.100) at 08:00:27:04:d1:9c [ether] on eth1
? (192.168.10.211) at 08:00:27:af:e6:df [ether] on eth1
? (10.0.2.3) at 52:55:0a:00:02:03 [ether] on eth0
_gateway (10.0.2.2) at 52:55:0a:00:02:02 [ether] on eth0

(|HomeLab:N/A) root@k8s-ctr:~# kubectl -n kube-system get lease | grep "cilium-l2announce"
cilium-l2announce-default-webpod       k8s-w1                                                                      11m

# k8s-w1 reboot를 통한 장애 발생
k8s-w1 reboot

# 새로운 리더 노드 선출 확인
(|HomeLab:N/A) root@k8s-ctr:~# kubectl -n kube-system get lease | grep "cilium-l2announce"
cilium-l2announce-default-webpod                                                                                   11m
(|HomeLab:N/A) root@k8s-ctr:~# kubectl -n kube-system get lease | grep "cilium-l2announce"
cilium-l2announce-default-webpod       k8s-ctr

# 새로운 리더 선출 이후 arp 송신 확인
root@router:~# arping -i eth1 $LBIP -c 100000
ARPING 192.168.10.211
60 bytes from 08:00:27:04:d1:9c (192.168.10.211): index=0 time=297.774 usec
60 bytes from 08:00:27:04:d1:9c (192.168.10.211): index=1 time=262.590 usec
60 bytes from 08:00:27:04:d1:9c (192.168.10.211): index=2 time=233.700 usec
60 bytes from 08:00:27:04:d1:9c (192.168.10.211): index=3 time=276.347 usec

root@router:~# arp -a
? (192.168.10.101) at 08:00:27:af:e6:df [ether] on eth1
? (192.168.20.100) at 08:00:27:3a:be:fa [ether] on eth2
? (192.168.10.100) at 08:00:27:04:d1:9c [ether] on eth1
? (192.168.10.211) at 08:00:27:04:d1:9c [ether] on eth1
? (10.0.2.3) at 52:55:0a:00:02:03 [ether] on eth0
_gateway (10.0.2.2) at 52:55:0a:00:02:02 [ether] on eth0

(|HomeLab:N/A) root@k8s-ctr:~# ip addr show dev eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:04:d1:9c brd ff:ff:ff:ff:ff:ff
    altname enp0s9
    inet 192.168.10.100/24 brd 192.168.10.255 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe04:d19c/64 scope link
       valid_lft forever preferred_lft forever

0개의 댓글