상태: 해결 (2026-10-14), 기록 기준. 그날의 숫자(광고 노드가 바뀐 횟수, speaker 재시작 수)는 작업 메모에서, 지금 설정과 노드 라벨은 2026-10-16 클러스터 읽기로 확인했습니다. 공유기 안에서 무슨 일이 있었는지는 공유기 로그를 보지 못해 추정으로 적습니다. 작성 규칙: 기록에 있는 사실만, 짐작은 「추정」, 재지 못한 것은 「미실측」.
목차
- 한 줄 요약
- 1. 구성: 공유기 → 게이트웨이 IP → 그 IP 를 「가진」 노드 하나
- 2. 증상: 밖에서만 안 된다
- 3. 진단: 광고 노드가 천 번 넘게 바뀌었다
- 4. 첫 처방이 틀렸다
- 5. 고친 것: 유선으로 붙은 워커 둘
- 6. 그런데 사내망에서는 여전히 안 보였다: 헤어핀
- 7. 다음에 「접속이 안 돼요」 를 들으면
- 8. 남은 것과 한계
- 참고
한 줄 요약
| 영향 | 바깥에서 사이트 접속 불가(80·443 에서 No route to host). 첫 처방 뒤 잠시 사내망에서도 불가 |
| 원인 | 게이트웨이 IP 를 ARP 로 답해 주는 노드(광고 노드)가 계속 바뀌었고, 그중에는 공유기가 닿지 못하는 노드도 있었다(추정) |
| 첫 처방의 실수 | 광고 노드를 컨트롤 플레인 셋으로 고정 → 셋 다 exclude-from-external-load-balancers 라벨이 있어 아무도 광고하지 않음 |
| 고친 것 | 유선으로 붙은 워커 둘로 고정. 바깥 세 지점에서 200 확인 |
| 재발 방지 | 광고 노드를 고를 때 제외 라벨을 먼저 본다. 진단 순서를 정해 둔다. MetalLB 문서에 이 함정을 적는 PR 을 보냈다 |
1. 구성: 공유기 → 게이트웨이 IP → 그 IP 를 「가진」 노드 하나
사이트로 오는 요청은 집 공유기의 포트 포워딩을 거칩니다. 공유기는 80·443 을 클러스터의 게이트웨이 서비스 IP 로 넘기고, 그 IP 는 MetalLB 가 L2 모드로 나눠 준 LoadBalancer 주소입니다.
L2 모드에서 그 IP 는 어느 한 기계의 랜 카드에 붙어 있는 주소가 아닙니다. 공유기가 「이 IP 의 MAC 주소는?」 하고 ARP 로 물으면, MetalLB speaker 가 도는 노드 하나가 「나」 라고 답합니다. MetalLB 문서는 이 노드를 정하는 방법을 이렇게 설명합니다(문서 기준).
- 모든 speaker 가 같은 규칙으로 「노드+IP」 해시 목록을 만들고, 맨 앞의 노드가 광고한다. 그래서 광고 노드는 한 번에 하나다.
- 노드가 죽은 것은 memberlist 로 알아채고, 살아 있는 노드가 넘겨받으며 gratuitous ARP 를 뿌려 「MAC 이 바뀌었다」고 알린다.
- 한계: 모든 트래픽이 노드 하나로 몰리고, 넘겨받는 데 몇 초가 걸리며, 그 사이 ARP 캐시를 늦게 고치는 클라이언트는 옛 MAC 으로 보낸다.
2. 증상: 밖에서만 안 된다
- 바깥에서 사이트 주소로 80·443 에 붙으면
No route to host. - 클러스터 안에서 게이트웨이 서비스로 직접 보내면 정상. 도메인이 가리키는 공인 IP 도 맞고, DNS 도 정상.
「안에서는 된다」 는 신호가 가장 헷갈렸습니다. 게이트웨이도, 그 뒤의 웹 파드도 건강했습니다. 끊긴 곳은 그 앞, 공유기와 광고 노드 사이였습니다.
3. 진단: 광고 노드가 천 번 넘게 바뀌었다
게이트웨이 서비스의 이벤트에는 speaker 가 광고를 시작할 때마다 announcing from node "…" 가 남습니다(MetalLB 문제 해결 문서가
권하는 확인 방법이기도 합니다). 그날 이 이벤트를 세어 보니 광고 노드가 1,275번 바뀌어 있었습니다(작업 메모).
- 워커 하나의 speaker 가 크래시 루프였습니다. 그날까지 재시작이 360번(2026-10-16 에 읽은 재시작 수도 360 — 그 뒤로는 멈췄습니다). speaker 가 죽고 살 때마다 memberlist 의 구성원이 바뀌므로, 1절의 규칙대로라면 해시 목록의 맨 앞도 그때마다 바뀔 수 있습니다.
- 광고 노드 후보에는 노트북으로 붙인 노드도 들어 있었습니다.
No route to host 가 나온 경로는 이렇게 추정합니다. 공유기는 포워딩할 대상 IP 의 MAC 을 ARP 로 묻는데, 그 순간 광고 노드가
공유기에서 닿지 않는 곳(무선이나 가상 네트워크 뒤의 노트북 노드 등)이거나 막 넘겨받는 중이면 답이 오지 않습니다. 답이 없으면
공유기는 「호스트에 닿을 수 없음」 ICMP 를 돌려주고, 클라이언트는 그것을 No route to host 로 보여 줍니다. 공유기의 ARP 표와
로그는 보지 못했습니다(미확인).
4. 첫 처방이 틀렸다
광고 노드가 떠도는 게 문제라면, 가장 흔들리지 않는 노드에 묶으면 됩니다. 처음 떠올린 곳은 컨트롤 플레인 셋이었습니다.
재부팅도 드물고 워크로드도 적습니다. L2Advertisement 의 nodeSelectors 를 그 셋으로 좁혔습니다.
결과는 더 나빠졌습니다. 사내망에서도 사이트가 끊겼습니다. 이번에는 광고하는 노드가 아예 없었기 때문입니다.
원인은 노드 라벨 하나였습니다.
- kubeadm 은 컨트롤 플레인 노드를 표시할 때
node-role.kubernetes.io/control-plane과 함께node.kubernetes.io/exclude-from-external-load-balancers라벨을 붙입니다. kubeadm 소스의markcontrolplane단계가 두 라벨을 값 없이(빈 문자열) 붙이고, 상수 주석은 「예전 동작과의 호환을 위해 컨트롤 플레인에 붙인다」고 설명합니다(소스 기준). 쿠버네티스 공식 라벨 목록은 이 라벨을 「외부 로드밸런서의 백엔드 목록에서 노드를 빼는 라벨」로 적고 있습니다. - MetalLB 는 이 라벨을 존중해서, 라벨이 붙은 노드에서는 광고하지 않습니다. 문제 해결 문서에 이 내용과 우회 플래그
(
--ignore-exclude-lb)가 있습니다. 다만 L2 의nodeSelectors를 설명하는 쪽에는 이 이야기가 없었습니다.
라벨 값이 비어 있어도 키가 있으면 제외입니다. 2026-10-16 에 읽어 보니 컨트롤 플레인 셋 모두 이 라벨을 값 없이 달고 있었습니다.
# 광고 노드를 고르기 전에 이것부터
kubectl get nodes -L node.kubernetes.io/exclude-from-external-load-balancers
nodeSelectors 가 고른 노드가 모두 제외 라벨을 달고 있으면, 서비스는 외부 IP 를 받은 채로 아무 데서도 응답하지 않습니다.
에러도 이벤트도 눈에 띄게 남지 않습니다. 이것을 문서에 적어 두려고 MetalLB 에 PR 을 보냈습니다 —
metallb/metallb#3156(2026-10-16 기준 열려 있음).
5. 고친 것: 유선으로 붙은 워커 둘
컨트롤 플레인을 빼면 남는 기준은 「공유기와 같은 유선 랜에 있고, 자주 재부팅하지 않는 워커」입니다. 그런 미니 PC 두 대로 고정했습니다.
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
spec:
ipAddressPools: [<게이트웨이 풀>]
nodeSelectors:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: [<워커 A>, <워커 B>]
두 대로 둔 이유는 하나가 죽어도 다른 하나가 넘겨받게 하려는 것입니다. 한 대로 묶으면 그 노드를 재부팅할 때마다 사이트가 끊깁니다. 워커 하나를 재부팅한 뒤 바깥의 세 지점에서 HTTP 200 을 확인했습니다.
6. 그런데 사내망에서는 여전히 안 보였다: 헤어핀
고친 뒤에도 집 안에서 공인 주소(도메인)로 접속하면 열리지 않았습니다. 이것은 다른 문제입니다. 사내망 → 공인 IP → 다시 사내망으로 꺾여 들어오는 경로(헤어핀 NAT, NAT 루프백)를 이 공유기가 지원하지 않습니다. 그래서 집 안에서 「안 된다」는 바깥 상태를 알려 주지 못하고, 반대로 집 안에서 「된다」도 바깥을 보장하지 않습니다.
확인은 두 갈래로 나눕니다.
# 안: 공인 DNS 를 건너뛰고 게이트웨이 IP 로 바로 — 이름(SNI·Host)은 그대로 둔다
curl -sS -o /dev/null -w '%{http_code}\n' --resolve <도메인>:443:<게이트웨이 IP> https://<도메인>/
# 밖: 여러 나라에서 HTTP 요청을 보내 주는 외부 점검 서비스(check-host.net 등)로
7. 다음에 「접속이 안 돼요」 를 들으면
- 게이트웨이 IP 로 직접(
--resolve). 여기서 안 되면 클러스터 안 문제입니다. - 바깥 점검 서비스. 1은 되는데 2가 안 되면 공유기와 광고 노드 사이입니다.
- 게이트웨이 서비스의 이벤트에서 지금 광고 노드와 바뀐 횟수를 봅니다(
kubectl get events --field-selector involvedObject.name=<서비스>). - speaker 로그에서 memberlist 가입 실패, 재시작 수를 봅니다.
- 광고 노드를 바꿔야 하면 제외 라벨을 먼저 확인합니다.
8. 남은 것과 한계
- speaker 가 크래시 루프였던 워커의 근본 원인은 이 글에서 다루지 않았습니다. 그 노드는 이제 광고 후보가 아니어서 사이트에 영향이 없지만, 원인을 고친 것은 아닙니다.
- 광고 노드가 바뀔 때 실제로 몇 초 끊기는지는 재지 않았습니다(미실측). MetalLB 문서는 대부분의 OS 에서 몇 초라고 적습니다.
- 광고 노드 둘이 동시에 내려가면 다시 끊깁니다. 이것을 막는 장치(동시 재부팅 금지 같은 규칙이나 경보)는 아직 없습니다.
티티 한마디. 「안에서는 되는데요?」 가 제일 무서운 말이에요. 안과 밖은 길이 달라서, 한쪽의 초록불이 다른 쪽을 말해 주지 않거든요. 그리고 노드를 고를 땐 이름보다 라벨부터! 라벨 하나가 조용히 「난 빠질게」 하고 있을 수 있어요.
참고
모든 자료는 2026-10-16 에 열람했습니다.
- MetalLB L2 개념: https://metallb.universe.tf/concepts/layer2/
- MetalLB 문제 해결(제외 라벨,
--ignore-exclude-lb, 광고 노드 확인): https://metallb.universe.tf/troubleshooting/ - 쿠버네티스 공식 라벨 목록: https://kubernetes.io/docs/reference/labels-annotations-taints/#node-kubernetes-io-exclude-from-external-load-balancers
- kubeadm 소스(컨트롤 플레인 표시 단계): https://github.com/kubernetes/kubernetes/blob/master/cmd/kubeadm/app/phases/markcontrolplane/markcontrolplane.go
- 이번 일로 보낸 문서 PR: https://github.com/metallb/metallb/pull/3156