L2 Announcements / L2 Aware LB

L2 Announcements / L2 Aware LB (Beta)

L2 Announcements는 서비스를 로컬 네트워크에서 보이고 접근 가능하게 만드는 기능이에요. 주로 BGP 기반 라우팅이 없는 네트워크(예: 사무실 또는 캠퍼스 네트워크)의 온프레미스 배포를 위한 것이에요. ExternalIP 및/또는 LoadBalancer IP에 대한 ARP/NDP 쿼리에 응답해 north/south 로드 밸런서 역할을 해요.

출처: L2 Announcements / L2 Aware LB (Beta)

본문

Note 이는 베타 기능이에요. 문제가 발생하면 피드백을 주고 GitHub issue를 제출해 주세요.

L2 Announcements는 서비스를 로컬 네트워크에서 보이고 접근 가능하게 만드는 기능이에요. 이 기능은 주로 BGP 기반 라우팅이 없는 네트워크(예: 사무실 또는 캠퍼스 네트워크) 내 온프레미스 배포를 위한 것이에요.

사용할 때 이 기능은 ExternalIP 및/또는 LoadBalancer IP에 대한 ARP/NDP 쿼리에 응답해요. 이 IP들은 여러 노드의 가상 IP(네트워크 디바이스에 설치되지 않은)이므로, 각 서비스에 대해 한 번에 한 노드만 ARP/NDP 쿼리에 응답하고 그 MAC 주소로 응답해요. 이 노드는 서비스 로드 밸런싱 기능으로 로드 밸런싱을 수행해 north/south 로드 밸런서 역할을 해요.

NodePort 서비스보다 이 기능의 장점은 각 서비스가 고유한 IP를 사용할 수 있어 여러 서비스가 같은 포트 번호를 사용할 수 있다는 것이에요. NodePort를 사용할 때는 트래픽을 어느 호스트로 보낼지 클라이언트가 결정해야 하고, 노드가 다운되면 해당 IP+Port 조합은 사용할 수 없게 돼요. L2 announcements를 사용하면 서비스 VIP가 단순히 다른 노드로 마이그레이션되어 계속 동작해요.

구성 (Configuration)

L2 Announcements 기능과 모든 요구 사항은 다음과 같이 활성화할 수 있어요:

Helm ConfigMap Helm Repository OCI Registry

helm upgrade cilium cilium/cilium --version 1.20.2 \
   --namespace kube-system \
   --reuse-values \
   --set l2announcements.enabled=true \
   --set k8sClientRateLimit.qps={QPS} \
   --set k8sClientRateLimit.burst={BURST} \
   --set kubeProxyReplacement=true \
   --set k8sServiceHost=${API_SERVER_IP} \
   --set k8sServicePort=${API_SERVER_PORT}
helm upgrade cilium oci://quay.io/cilium/charts/cilium 1.20.2 \
   --namespace kube-system \
   --reuse-values \
   --set l2announcements.enabled=true \
   --set k8sClientRateLimit.qps={QPS} \
   --set k8sClientRateLimit.burst={BURST} \
   --set kubeProxyReplacement=true \
   --set k8sServiceHost=${API_SERVER_IP} \
   --set k8sServicePort=${API_SERVER_PORT}
enable-l2-announcements: true
kube-proxy-replacement: true
k8s-client-qps: {QPS}
k8s-client-burst: {BURST}

Warning 이 기능을 사용할 때는 API 사용량 증가 때문에 클라이언트 rate limit(k8sClientRateLimit.qps 및 k8sClientRateLimit.burst) 크기 조정이 중요해요. 크기 조정 지침은 Sizing client rate limit을 참고하세요.

사전 요구 사항 (Prerequisites)

  • Kube Proxy replacement 모드가 활성화되어 있어야 해요. 자세한 내용은 Kubernetes Without kube-proxy를 참고하세요.
  • L2 Aware LB가 광고될 모든 디바이스는 --devices 플래그나 devices Helm 옵션에 명시적으로 설정된 경우 활성화되고 포함되어야 해요. NodePort Devices, Port and Bind settings 참고.

제한 사항 (Limitations)

  • L3->L2 변환 프로토콜의 방식 때문에 한 노드가 특정 IP에 대한 모든 ARP/NDP 요청을 받으므로, 트래픽이 클러스터에 도달하기 전에는 로드 밸런싱이 일어날 수 없어요.
  • 이 기능은 현재 트래픽 밸런싱 메커니즘이 없어서 같은 정책 안의 노드들이 비대칭적으로 부하를 받을 수 있어요. 자세한 내용은 Leader Election을 참고하세요.
  • 이 기능은 서비스의 externalTrafficPolicy: Local과 호환되지 않아요. 파드가 없는 노드에 서비스 IP가 광고되어 트래픽이 드롭될 수 있기 때문이에요.

정책 (Policies)

정책은 어떤 서비스를 어디서 어떻게 광고할지 세밀하게 제어해요. 다음은 모든 선택적 필드를 사용한 예제 정책이에요:

apiVersion: "cilium.io/v2alpha1"
kind: CiliumL2AnnouncementPolicy
metadata:
  name: policy1
spec:
  serviceSelector:
    matchLabels:
      color: blue
  nodeSelector:
    matchExpressions:
      - key: node-role.kubernetes.io/control-plane
        operator: DoesNotExist
  interfaces:
  - ^eth[0-9]+
  externalIPs: true
  loadBalancerIPs: true

서비스 셀렉터 (Service Selector)

서비스 셀렉터는 이 정책이 어떤 서비스를 선택하는지 결정하는 label selector예요. 서비스 셀렉터를 제공하지 않으면 정책이 모든 서비스를 선택해요. 서비스는 광고를 위해 정책이 선택하려면 loadBalancerClass가 지정되지 않았거나 io.cilium/l2-announcer로 설정되어 있어야 해요.

라벨이 아니라 .meta.name이나 .meta.namespace 같은 다른 메타데이터와 매칭하는 몇 가지 특수 목적 셀렉터 필드가 있어요.

Selector Field
io.kubernetes.service.namespace .meta.namespace
io.kubernetes.service.name .meta.name

노드 셀렉터 (Node Selector)

노드 셀렉터 필드는 어떤 노드가 서비스를 광고할 후보인지 결정하는 label selector예요.

선택된 노드( Leader Election 참고)가 특정 서비스의 모든 트래픽에 대한 north/south 로드 밸런서 역할을 하므로, 클러스터에서 노드의 하위 집합을 선택하는 것이 바람직할 수 있어요.

인터페이스 (Interfaces)

interfaces 필드는 선택된 서비스가 어떤 네트워크 인터페이스 위에서 광고될지 결정하는 정규 표현식(golang syntax) 목록이에요. 이 필드는 선택사항이며, 지정하지 않으면 모든 인터페이스가 사용돼요.

표현식은 OR로 결합되므로, 어떤 표현식과도 매칭되는 모든 네트워크 디바이스가 매칭돼요.

L2 announcements는 선택된 디바이스가 devices Helm 옵션에 지정된 디바이스 집합의 일부일 때만 동작해요. NodePort Devices, Port and Bind settings 참고.

Note 이 셀렉터는 보안 기능이 아니에요. 광고되지 않았더라도(예: ARP/NDP 항목을 하드코딩해) 인터페이스를 통해 서비스는 여전히 사용 가능해요.

IP 유형 (IP Types)

externalIPs와 loadBalancerIPs 필드는 어떤 종류의 IP가 광고되는지 결정해요. 둘 다 기본값은 false이므로, 기능적인 정책은 항상 둘 중 하나 또는 둘 다 true로 설정해야 해요.

externalIPs가 true이면 .spec.externalIPs 필드의 모든 IP가 광고돼요. 이 IP들은 서비스 작성자가 관리해요.

loadBalancerIPs가 true이면 서비스의 .status.loadbalancer.ingress 필드의 모든 IP가 광고돼요. 이 IP들은 클러스터 관리자가 어떤 IP를 할당할 수 있는지 더 잘 제어하기 위해 구성할 수 있는 LoadBalancer IP Address Management (LB IPAM)이 할당할 수 있어요.

Note 사용자가 externalIPs를 사용하려면, 외부 IP에 대한 서비스 로드 밸런싱을 활성화하기 위해 externalIPs.enable=true Helm 옵션을 설정해야 해요.

상태 (Status)

정책이 어떤 이유로든 유효하지 않으면 정책의 status가 이를 반영해요. 예를 들어 유효하지 않은 match expression이 제공된 경우:

$ kubectl describe l2announcement
Name:         policy1
Namespace:
Labels:       <none>
Annotations:  <none>
API Version:  cilium.io/v2alpha1
Kind:         CiliumL2AnnouncementPolicy
Metadata:
  #[...]
Spec:
  #[...]
  Service Selector:
    Match Expressions:
      Key:       something
      Operator:  NotIn
      Values:
Status:
  Conditions:
    Last Transition Time:  2023-05-12T15:39:01Z
    Message:               values: Invalid value: []string(nil): for 'in', 'notin' operators, values set can't be empty
    Observed Generation:   1
    Reason:                error
    Status:                True
    Type:                  io.cilium/bad-service-selector

사용자가 오류를 해결하도록 정책을 업데이트하면 이 오류 조건들의 status는 False로 바뀌어요.

리더 선출 (Leader Election)

ARP/NDP의 동작 방식 때문에 호스트는 IP당 하나의 MAC 주소(가장 최근에 본 응답)만 저장해요. 이는 주어진 IP에 대한 요청에 응답할 수 있는 노드는 클러스터에서 하나뿐임을 의미해요.

이 동작을 구현하기 위해 모든 Cilium agent는 자신의 노드에 대해 어떤 서비스가 선택됐는지 결정하고, 모든 서비스에 대한 리더 선출에 참여하기 시작해요. 이를 위해 Kubernetes lease mechanism을 사용해요. 각 서비스는 lease로 변환되며, lease 보유자는 선택된 인터페이스에서 요청에 응답하기 시작해요.

lease 메커니즘은 선착순 선택 순서예요. 따라서 lease를 먼저 주장하는 노드가 그것을 얻어요. 이는 비대칭 트래픽 분포를 일으킬 수 있어요.

Lease

lease는 Cilium이 배포된 네임스페이스(보통 kube-system)에 생성돼요. 다음 명령으로 lease를 검사할 수 있어요:

$ kubectl -n kube-system get lease
NAME                                  HOLDER                                                    AGE
cilium-l2announce-default-deathstar   worker-node                                               2d20h
cilium-operator-resource-lock         worker-node2-tPDVulKoRK                                   2d20h
kube-controller-manager               control-plane-node_9bd97f6c-cd0c-4565-8486-e718deb310e4   2d21h
kube-scheduler                        control-plane-node_2c490643-dd95-4f73-8862-139afe771ffd   2d21h

cilium-l2announce-로 시작하는 lease는 이 기능이 사용하는 lease예요. 이름의 마지막 부분은 네임스페이스와 서비스 이름이에요. holder는 현재 lease를 보유하고 있어 해당 서비스의 IP를 광고한 노드의 이름을 나타내요.

lease를 검사하려면:

$ kubectl -n kube-system get lease/cilium-l2announce-default-deathstar -o yaml
apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
  creationTimestamp: "2023-05-09T15:13:32Z"
  name: cilium-l2announce-default-deathstar
  namespace: kube-system
  resourceVersion: "449966"
  uid: e3c9c020-6e24-4c5c-9df9-d0c50f6c4cec
spec:
  acquireTime: "2023-05-09T15:14:20.108431Z"
  holderIdentity: worker-node
  leaseDurationSeconds: 3
  leaseTransitions: 1
  renewTime: "2023-05-12T12:15:26.773020Z"

acquireTime은 현재 리더가 lease를 획득한 시각이에요. holderIdentity는 현재 holder/리더 노드의 이름이에요. 리더가 leaseDurationSeconds초 동안 lease를 갱신하지 않으면 새 리더가 선택돼요. leaseTransitions는 lease가 몇 번 주인을 바꿨는지, renewTime은 리더가 lease를 마지막으로 갱신한 시각을 나타내요.

lease와 관련해 조정할 수 있는 세 가지 Helm 옵션이 있어요:

  • l2announcements.leaseDuration은 생성된 lease의 leaseDurationSeconds 값을 결정하며, 결과적으로 failover가 발생하기 전에 리더가 얼마나 오래 "다운" 상태여야 하는지를 결정해요. 기본값은 15s이고, 항상 1s보다 크고 leaseRenewDeadline보다 커야 해요.
  • l2announcements.leaseRenewDeadline은 리더가 lease를 갱신해야 하는 간격이에요. 기본값은 5s이고 leaseRetryPeriod보다 최소 20% 커야 하며 1ns 아래가 될 수 없어요.
  • l2announcements.leaseRetryPeriod는 lease 갱신이 실패하면 agent가 다시 시도하기 전에 얼마나 기다려야 하는지 결정해요. 기본값은 2s이고 leaseRenewDeadline보다 최소 20% 작고 1ns보다 커야 해요.

Note 이론적으로 실패와 failover 사이의 최단 시간은 leaseDuration - leaseRenewDeadline이고 최장은 leaseDuration + leaseRenewDeadline이에요. 따라서 기본값으로 failover는 10s에서 20s 사이에 발생해요. 아래 예시에서는 이 시간들이 2s에서 4s 사이예요.

Helm ConfigMap Helm Repository OCI Registry

helm upgrade cilium cilium/cilium --version 1.20.2 \
   --namespace kube-system \
   --reuse-values \
   --set l2announcements.enabled=true \
   --set kubeProxyReplacement=true \
   --set k8sServiceHost=${API_SERVER_IP} \
   --set k8sServicePort=${API_SERVER_PORT} \
   --set k8sClientRateLimit.qps={QPS} \
   --set k8sClientRateLimit.burst={BURST} \
   --set l2announcements.leaseDuration=3s \
   --set l2announcements.leaseRenewDeadline=1s \
   --set l2announcements.leaseRetryPeriod=200ms
helm upgrade cilium oci://quay.io/cilium/charts/cilium 1.20.2 \
   --namespace kube-system \
   --reuse-values \
   --set l2announcements.enabled=true \
   --set kubeProxyReplacement=true \
   --set k8sServiceHost=${API_SERVER_IP} \
   --set k8sServicePort=${API_SERVER_PORT} \
   --set k8sClientRateLimit.qps={QPS} \
   --set k8sClientRateLimit.burst={BURST} \
   --set l2announcements.leaseDuration=3s \
   --set l2announcements.leaseRenewDeadline=1s \
   --set l2announcements.leaseRetryPeriod=200ms
enable-l2-announcements: true
kube-proxy-replacement: true
l2-announcements-lease-duration: 3s
l2-announcements-renew-deadline: 1s
l2-announcements-retry-period: 200ms
k8s-client-qps: {QPS}
k8s-client-burst: {BURST}

빠른 실패 감지와 CPU + 네트워크 사용량 사이에는 트레이드오프가 있어요. 각 서비스는 CPU와 네트워크 오버헤드를 발생시키므로, 서비스 수가 적은 클러스터는 더 빠른 failover 시간을 더 쉽게 감당할 수 있어요. 더 큰 클러스터는 오버헤드가 너무 높으면 파라미터를 늘려야 할 수 있어요.

클라이언트 rate limit 크기 조정 (Sizing client rate limit)

리더 선출 프로세스는 지속적으로 API 트래픽을 생성하며, 정확한 양은 구성된 lease duration, 구성된 renew deadline, 기능을 사용하는 서비스 수에 따라 달라요.

기본 클라이언트 rate limit은 5 QPS이며 최대 10 QPS까지의 burst가 허용돼요. 이 기본 한도는 L2 announcements를 사용할 때 빠르게 도달하므로, 사용자는 클라이언트 rate limit을 그에 맞게 조정해야 해요.

최악의 시나리오에서 서비스는 불균등하게 분포되므로, renew deadline을 기준으로 피크 부하를 가정할게요. 서로소 노드 집합에 걸친 여러 정책이 있는 복잡한 시나리오에서는 노드당 최대 QPS가 더 낮아요.

QPS = #services * (1 / leaseRenewDeadline)

// example
#services = 65
leaseRenewDeadline = 2s
QPS = 65 * (1 / 2s) = 32.5 QPS

베이스 QPS를 계산된 값 근처로 설정하면 충분해야 해요. multi-node 시나리오에서는 lease가 노드에 분산되고 선출에 참여하는 비-holder의 QPS는 더 낮기 때문이에요.

burst QPS는 API 서버를 사용하는 다른 기능들이 일으키는 트래픽 burst를 허용하기 위해 약간 더 높아야 해요.

Failover

리더 선출에 참여하는 노드들이 lease holder가 leaseDurationSeconds초 동안 lease를 갱신하지 않은 것을 감지하면, API 서버에 자신을 새 holder로 만들어 달라고 요청해요. 처리되는 첫 번째 요청만 통과하고 나머지는 거부돼요.

노드가 리더/holder가 되면 구성된 모든 인터페이스에 걸쳐 gratuitous ARP 응답을 보내요. 이를 수락하는 클라이언트는 ARP 테이블을 즉시 업데이트해 새 리더/holder로 트래픽을 보내게 돼요. 모든 클라이언트가 gratuitous ARP 응답을 수락하지는 않아요. ARP 스푸핑에 사용될 수 있기 때문이에요. 이런 클라이언트는 내부 테이블의 TTL에 도달했을 때만 ARP로 재쿼리하므로 lease에 구성된 것보다 더 긴 다운타임을 겪을 수 있어요.

트러블슈팅 (Troubleshooting)

이 섹션은 L2 Announcements를 트러블슈팅하는 단계별 가이드로, 문제를 해결하거나 특정 영역으로 좁히는 데 도움이 될 거예요.

먼저 해야 할 일은 기능이 활성화됐는지, kube proxy replacement가 활성화됐는지, 선택적으로 external IP가 활성화됐는지 확인하는 것이에요.

$ kubectl -n kube-system exec ds/cilium -- cilium-dbg config --all | grep EnableL2Announcements
EnableL2Announcements             : true

$ kubectl -n kube-system exec ds/cilium -- cilium-dbg config --all | grep KubeProxyReplacement
KubeProxyReplacement              : true

$ kubectl -n kube-system exec ds/cilium -- cilium-dbg config --all | grep EnableExternalIPs
EnableExternalIPs                 : true

EnableL2Announcements 또는 KubeProxyReplacement가 false를 나타내면 올바른 설정을 활성화하고 Configuration helm 차트를 배포하세요. EnableExternalIPs는 외부 IP를 사용하려면 true로 설정해야 해요.

다음으로 정책이 하나 이상 구성됐는지 확인하세요. L2 announcements는 정책 없이는 동작하지 않아요.

$ kubectl get CiliumL2AnnouncementPolicy
NAME      AGE
policy1   6m16s

L2 announcements는 이제 정책이 매칭하는 모든 서비스에 대한 lease를 생성해야 해요. lease를 다음과 같이 확인할 수 있어요:

$ kubectl -n kube-system get lease | grep "cilium-l2announce"
cilium-l2announce-default-service-red   kind-worker                       34s

출력이 비어 있으면 정책이 올바르게 구성되지 않았거나 agent가 올바르게 실행되지 않는 것이에요. 에러 메시지에 대해 agent의 로그를 확인하세요:

$ kubectl -n kube-system logs ds/cilium | grep "l2"

일반적인 오류는 agent가 lease를 생성하지 못하는 것이에요.

$ kubectl -n kube-system logs ds/cilium | grep "error"
time="2024-06-25T12:01:43Z" level=error msg="error retrieving resource lock kube-system/cilium-l2announce-default-service-red: leases.coordination.k8s.io \"cilium-l2announce-default-service-red\" is forbidden: User \"system:serviceaccount:kube-system:cilium\" cannot get resource \"leases\" in API group \"coordination.k8s.io\" in the namespace \"kube-system\"" subsys=klog

이것은 agent의 클러스터 역할이 올바르지 않으면 발생할 수 있어요. 이는 helm 차트를 사용하지 않고 L2 announcements를 활성화할 때 주로 발생해요. helm 차트를 재배포하거나 kubectl edit clusterrole cilium을 실행하고 rules에 다음 블록을 추가해 클러스터 역할을 수동으로 업데이트하세요:

- apiGroups:
  - coordination.k8s.io
  resources:
  - leases
  verbs:
  - create
  - get
  - update
  - list
  - delete

또 다른 일반적인 오류는 구성된 클라이언트 rate limit이 너무 낮은 것이에요. 이것도 로그에서 볼 수 있어요:

$ kubectl -n kube-system logs ds/cilium | grep "l2"
2023-07-04T14:59:51.959400310Z level=info msg="Waited for 1.395439596s due to client-side throttling, not priority and fairness, request: GET:https://127.0.0.1:6443/apis/coordination.k8s.io/v1/namespaces/kube-system/leases/cilium-l2announce-default-example" subsys=klog
2023-07-04T15:00:12.159409007Z level=info msg="Waited for 1.398748976s due to client-side throttling, not priority and fairness, request: PUT:https://127.0.0.1:6443/apis/coordination.k8s.io/v1/namespaces/kube-system/leases/cilium-l2announce-default-example" subsys=klog

이 로그들은 lease 갱신의 간헐적 실패, 연결 문제 및/또는 빈번한 리더 변경과 연관돼요. 클라이언트 rate limit을 조정하는 방법은 Sizing client rate limit을 참고하세요.

다른 L2 관련 오류를 찾으면 에러 메시지와 그곳에 도달하기 위해 취한 단계를 포함해 GitHub issue를 열어 주세요.

lease가 생성됐다고 가정하면 다음 단계는 agent 내부 상태를 확인하는 것이에요. 동작하지 않는 서비스를 하나 골라 lease를 검사하세요. holder 이름을 가져와 holder 노드의 cilium agent 파드를 찾으세요. 마지막으로 cilium agent 파드의 이름을 가져와 l2-announce 상태를 검사하세요:

$ kubectl -n kube-system get lease cilium-l2announce-default-service-red
NAME                                    HOLDER        AGE
cilium-l2announce-default-service-red   <node-name>   20m

$ kubectl -n kube-system get pod -l 'app.kubernetes.io/name=cilium-agent' -o wide | grep <node-name>
<agent-pod>   1/1     Running   0          35m   172.19.0.3   kind-worker          <none>           <none>

$ kubectl -n kube-system exec pod/<agent-pod> -- cilium-dbg shell -- db/show l2-announce
# IP        NetworkInterface
10.0.10.0   eth0

l2 announce 상태는 서비스의 IP와 그것이 광고되는 네트워크 인터페이스를 포함해야 해요. lease는 있지만 그 IP가 l2-announce 상태에 없거나 주어진 네트워크 디바이스에 대한 항목이 없다면, 정책의 디바이스 셀렉터가 원하는 네트워크 디바이스와 매칭되는지 다시 확인하세요 (값은 정규 표현식이에요). 필터가 올바른 것 같거나 지정되지 않았다면 알려진 디바이스를 검사하세요:

$ kubectl -n kube-system exec ds/cilium -- cilium-dbg shell -- db/show devices
Name              Index   Selected   Type     MTU     HWAddr              Flags                    Addresses
lxc5d23398605f6   10      false      veth     1500    b6:ed:d8:d2:dd:ec   up|broadcast|multicast   fe80::b4ed:d8ff:fed2:ddec
lxc3bf03c00d6e3   12      false      veth     1500    8a:d1:0c:91:8a:d3   up|broadcast|multicast   fe80::88d1:cff:fe91:8ad3
eth0              50      true       veth     1500    02:42:ac:13:00:03   up|broadcast|multicast   172.19.0.3, fc00:c111::3, fe80::42:acff:fe13:3
lo                1       false      device   65536                       up|loopback              127.0.0.1, ::1
cilium_net        2       false      veth     1500    1a:a9:2f:4d:d3:3d   up|broadcast|multicast   fe80::18a9:2fff:fe4d:d33d
cilium_vxlan      4       false      vxlan    1500    2a:05:26:8d:79:9c   up|broadcast|multicast   fe80::2805:26ff:fe8d:799c
lxc611291f1ecbb   8       false      veth     1500    7a:fb:ec:54:e2:5c   up|broadcast|multicast   fe80::78fb:ecff:fe54:e25c
lxc_health        16      false      veth     1500    0a:94:bf:49:d5:50   up|broadcast|multicast   fe80::894:bfff:fe49:d550
cilium_host       3       false      veth     1500    22:32:e2:80:21:34   up|broadcast|multicast   10.244.1.239, fd00:10:244:1::f58a

Selected가 true로 설정된 디바이스만 L2 announcements에 사용할 수 있어요. 일반적으로 IP가 할당된 모든 물리적 디바이스가 선택된 것으로 간주돼요. --devices 플래그나 devices Helm 옵션으로 디바이스를 필터링할 수 있어요. 원하는 디바이스가 목록에 있지만 선택되지 않았다면 devices 플래그/옵션이 그것을 필터링하는지 확인하세요.

원하는 디바이스가 목록에 없거나 선택된 것으로 봐야 하는데 선택되지 않았다면 GitHub issue를 열어 주세요.

L2 상태에 IP와 디바이스 조합이 포함되어 있는데도 여전히 연결 문제가 있다면, 클러스터 내에서 ARP를 테스트할 차례예요. 같은 L2 네트워크에서 lease holder가 아닌 cilium agent 파드를 하나 고르세요. 그런 다음 다음 명령으로 서비스 IP에 ARP 요청을 보내세요:

$ kubectl -n kube-system exec pod/cilium-z4ef7 -- sh -c 'apt update && apt install -y arping && arping -i <netdev-on-l2> <service-ip>'
[omitting apt output...]
ARPING 10.0.10.0
58 bytes from 02:42:ac:13:00:03 (10.0.10.0): index=0 time=11.772 usec
58 bytes from 02:42:ac:13:00:03 (10.0.10.0): index=1 time=9.234 usec
58 bytes from 02:42:ac:13:00:03 (10.0.10.0): index=2 time=10.568 usec

출력이 위와 같은데도 서비스가 여전히 같은 L2 네트워크의 클라이언트에서 도달할 수 없다면, 문제는 클라이언트 관련일 수 있어요. 서비스가 L2 네트워크 외부에서 도달 가능하길 기대했는데 그렇지 않다면, 게이트웨이 디바이스의 ARP와 라우팅 테이블을 확인하세요.

ARP 요청이 실패하면(출력에 Timeout이 표시) lease가 있는 cilium-agent의 BPF 맵을 확인하세요:

$ kubectl -n kube-system exec pod/cilium-vxz67 -- bpftool map dump pinned /sys/fs/bpf/tc/globals/cilium_l2_responder_v4
[{
        "key": {
            "ip4": 655370,
            "ifindex": 50
        },
        "value": {
            "responses_sent": 20
        }
    }
]

responses_sent 필드는 데이터 경로가 ARP 요청에 응답할 때마다 증가해요. 필드가 0이면 ARP 요청이 노드에 도달하지 않은 것이에요. 필드가 0보다 크면 문제는 반환 경로에 있는 것이에요. 두 경우 모두 네트워크와 클라이언트를 검사하세요.

ARP 요청에 응답하는데도 서비스가 여전히 도달할 수 없을 수 있어요. 이는 여러 이유로 발생할 수 있으며, 보통 L2 announcements보다는 다른 Cilium 기능과 관련돼요.

그러나 흔한 문제 하나는 서비스에 .Spec.ExternalTrafficPolicy: Local을 사용하는 것이에요. 이 설정은 보통 로드 밸런서에게 두 번째 홉을 피하기 위해 준비된 파드가 최소 1개 있는 노드로만 트래픽을 전달하라고 지시해요. 안타깝게도 L2 announcements는 현재 이 설정을 인지하지 못하고 정책과 매칭되는 모든 노드에 서비스 IP를 광고해요. 파드가 없는 노드가 트래픽을 받으면 이를 드롭해요. 이를 고치려면 정책을 .Spec.ExternalTrafficPolicy: Cluster로 설정하세요.

위 단계 중 어느 것도 문제 해결에 도움이 되지 않으면 GitHub issue를 열어 주세요.

L2 Pod Announcements

L2 Pod Announcements는 Gratuitous ARP 응답 / Neighbor Discovery Advertisements를 사용해 Pod IP 주소를 L2 네트워크에 광고해요. 활성화되면 노드는 로컬로 생성된 모든 파드에 대해 구성된 네트워크 인터페이스(들)에서 Gratuitous ARP 응답 / NDP Advertisements를 전송해요. 이 기능은 위의 L2 announcements 기능과 별도로 활성화돼요.

L2 Pod Announcements를 활성화하려면 다음을 설정하세요:

Helm ConfigMap Helm Repository OCI Registry

helm upgrade cilium cilium/cilium --version 1.20.2 \
   --namespace kube-system \
   --reuse-values \
   --set l2podAnnouncements.enabled=true \
   --set l2podAnnouncements.interfacePattern='^eth0$'
helm upgrade cilium oci://quay.io/cilium/charts/cilium 1.20.2 \
   --namespace kube-system \
   --reuse-values \
   --set l2podAnnouncements.enabled=true \
   --set l2podAnnouncements.interfacePattern='^eth0$'
enable-l2-pod-announcements: true
l2-pod-announcements-interface-pattern: "^eth0$"

l2podAnnouncements.interfacePattern/l2-pod-announcements-interface-pattern 옵션은 광고를 보내는 데 사용되는 인터페이스와 매칭하는 정규식을 받아요. 여러 인터페이스를 매칭하려면 ^(eth0|ens1)$ 같은 정규식 패턴을 사용하세요.

Helm ConfigMap Helm Repository OCI Registry

helm upgrade cilium cilium/cilium --version 1.20.2 \
   --namespace kube-system \
   --reuse-values \
   --set l2podAnnouncements.enabled=true \
   --set l2podAnnouncements.interfacePattern='^(eth0|ens1)$'
helm upgrade cilium oci://quay.io/cilium/charts/cilium 1.20.2 \
   --namespace kube-system \
   --reuse-values \
   --set l2podAnnouncements.enabled=true \
   --set l2podAnnouncements.interfacePattern='^(eth0|ens1)$'
enable-l2-pod-announcements: true
l2-pod-announcements-interface-pattern: "^(eth0|ens1)$"

Note 이 기능은 아직 IPv6 지원이 없으므로 ARP 메시지만 보내며, Unsolicited Neighbor Advertisements는 보내지 않아요.

더 알아보기 (Learn more)