ステータス:解決済み(2026-10-14)、記録時点。 その日の数値(アナウンスノードが入れ替わった回数、スピーカーの再起動数)は作業メモから、現在の設定とノードラベルは2026-10-16のクラスター 確認で確認しました。ルーター内部で何が起きていたのかはルーターのログを確認できなかったため 推測として記載します。 作成ルール:記録にある事実のみ、推測は「推測」、測定できなかったものは「未実測」。
目次
- 一言まとめ
- 1. 構成:ルーター → ゲートウェイIP → そのIPを「持つ」ノード1つ
- 2. 症状:外部からのみアクセスできない
- 3. 診断:アナウンスノードが1千回以上入れ替わった
- 4. 最初の処方箋が間違っていた
- 5. 修正内容:有線接続された2台のワーカー
- 6. しかし社内ネットワークでは相変わらず見えなかった:ヘアピン
- 7. 次に「アクセスできません」と聞いたとき
- 8. 残された課題と限界
- 参考
一言まとめ
| 影響 | 外部からのサイトアクセス不可(80・443で No route to host)。最初の処方箋の後、一時的に 社内ネットワークからも 不可 |
| 原因 | ゲートウェイIPにARPで応答するノード(アナウンスノード)が変わり続け、その中にはルーターが到達できないノードもあった(推測) |
| 最初の処方箋のミス | アナウンスノードをコントロールプレーンのセットに固定 → 3台すべてに exclude-from-external-load-balancers ラベルがあるため 誰もアナウンスしない |
| 修正内容 | 有線接続された2台のワーカーに固定。外部の3地点から200を確認 |
| 再発防止 | アナウンスノードを選ぶ際、除外ラベルを真っ先に確認する。診断の順序を定めておく。MetalLBのドキュメントにこの罠を記載するPRを送信した |
1. 構成:ルーター → ゲートウェイIP → そのIPを「持つ」ノード1つ
サイトへのリクエストは自宅ルーターのポートフォワーディングを経由します。ルーターは80・443をクラスターのゲートウェイサービスIPに転送し、そのIPはMetalLBがL2モードで割り当てたLoadBalancerアドレスです。
L2モードにおいて、そのIPはいずれかのマシンのLANカードに固定されているアドレスではありません。ルーターが「このIPのMACアドレスは?」とARPで問い合わせると、MetalLBスピーカーが稼働しているノード 1つが「私だ」と応答します。MetalLBのドキュメントでは、このノードを決定する方法を次のように説明しています(ドキュメント基準)。
- すべてのスピーカーが同じルールで「ノード+IP」のハッシュリストを作成し、先頭のノードがアナウンスする。そのため、アナウンスノードは常に1つである。
- ノードの停止はmemberlistによって検知され、稼働中のノードが引き継ぎ、グリシャスARP(gratuitous ARP)をブロードキャストして「MACアドレスが変更された」ことを通知する。
- 限界:すべてのトラフィックが1つのノードに集中し、引き継ぎに数秒かかり、その間にARPキャッシュの更新が遅れたクライアントは古いMACアドレス宛てに送信してしまう。
2. 症状:外部からのみアクセスできない
- 外部からサイトアドレスで80・443に接続すると
No route to host. - クラスター内からゲートウェイサービスに直接送信する場合は正常。ドメインが指す公認IPも正しく、DNSも正常。
「内側からはアクセスできる」というシグナルが最も混乱を招きました。ゲートウェイも、その背後のWebポッドも正常でした。途絶えていた場所はその手前、 ルーターとアナウンスノードの間でした。
3. 診断:アナウンスノードが1千回以上入れ替わった
ゲートウェイサービスのイベントには、スピーカーがアナウンスを開始するたびに announcing from node "…" が記録されます(MetalLBのトラブルシューティングドキュメントでも推奨されている確認方法です)。その日、このイベントを集計したところ、 アナウンスノードが1,275回入れ替わっていました(作業メモ)。
- あるワーカーのスピーカーがクラッシュループに陥っていました。その日までの再起動回数は360回(2026-10-16に確認した再起動数も360回 — その後は停止しました)。スピーカーが停止・起動を繰り返すたびにmemberlistのメンバーが変化するため、第1節のルールに従えばハッシュリストの先頭もその都度変わる可能性があります。
- アナウンスノードの候補には、ノートPCとして接続したノードも含まれていました。
No route to host が発生した経路は次のように 推測されます。ルーターはフォワーディング対象IPのMACアドレスをARPで問い合わせますが、その瞬間、アナウンスノードがルーターから到達できない場所(無線や仮想ネットワークの背後にあるノートPCノードなど)にあったり、引き継ぎの最中だったりすると応答が返ってきません。応答がない場合、ルーターは「ホストに到達できません」というICMPを返し、クライアントはそれを No route to host として表示します。ルーターのARPテーブルとログは確認できませんでした(未確認)。
4. 最初の処方箋が間違っていた
アナウンスノードがさまよっているのが問題であれば、最も揺るぎないノードに固定すれば解決します。最初に思い浮かんだのは コントロールプレーンのセットでした。再起動も少なく、ワークロードも少なくなっています。L2Advertisementの nodeSelectors をその3台に絞り込みました。
結果はさらに悪くなりました。 社内ネットワークからも サイトが途絶えました。今度はアナウンスするノードが完全に存在しなかったためです。
原因は1つのノードラベルでした。
- kubeadmはコントロールプレーンノードを表示する際、
node-role.kubernetes.io/control-planeと共にnode.kubernetes.io/exclude-from-external-load-balancersラベルを付与します。kubeadmソースのmarkcontrolplaneステージがこれら2つのラベルを値なし(空文字列)で付与し、定数のコメントには「以前の動作との互換性のためにコントロールプレーンに付与する」と説明されています(ソース基準)。Kubernetesの公式ラベルリストでは、このラベルを「外部ロードバランサーのバックエンドリストからノードを除外するラベル」として記載しています。 - 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. 修正内容:有線接続された2台のワーカー
コントロールプレーンを除外した上で残る基準は、「ルーターと同じ有線LANに接続されており、頻繁に再起動しないワーカー」です。そのようなミニPC 2台に固定しました。
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
spec:
ipAddressPools: [<게이트웨이 풀>]
nodeSelectors:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: [<워커 A>, <워커 B>]
2台にした理由は、1台が停止してももう1台が引き継げるようにするためです。1台に固定すると、そのノードを再起動するたびにサイトが途絶えてしまいます。ワーカーを1台再起動した後、外部の3地点からHTTP 200を確認しました。
6. しかし社内ネットワークでは相変わらず見えなかった:ヘアピン
修正後も、自宅内から公認アドレス(ドメイン)でアクセスすると開きませんでした。これは別の問題です。社内ネットワーク → 公認IP → 再び社内ネットワークへと折り返して入ってくる経路(ヘアピンNAT、NATループバック)をこのルーターがサポートしていません。そのため、自宅内から「アクセスできない」という外部の状態を把握できず、逆に自宅内から「アクセスできる」ことも外部への接続を保証するものではありません。
確認は2つのルートに分けます。
# 안: 공인 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=<서비스>). - スピーカーのログからmemberlistへの参加失敗、再起動数を確認します。
- アナウンスノードを変更する必要がある場合は、除外ラベルを 真っ先に 確認します。
8. 残された課題と限界
- スピーカーがクラッシュループに陥っていたワーカーの根本的な原因は、この記事では取り上げませんでした。そのノードは現在アナウンス候補ではないためサイトに影響はありませんが、原因が解消されたわけではありません。
- アナウンスノードが切り替わる際に実際に何秒途絶えるのかは測定していませんでした(未実測)。MetalLBのドキュメントではほとんどのOSで数秒と記載されています。
- アナウンスノード2台が 同時に ダウンすると再び途絶えます。これを防ぐ仕組み(同時再起動の禁止のようなルールやアラート)はまだありません。
一言。 「内側からはアクセスできるのですが?」が一番怖い言葉です。内側と外側では経路が異なるため、片方の緑信号がもう片方の状態を保証してはくれないからです。そしてノードを選ぶときは名前よりもラベルから! 1つのラベルがひっそりと「私は外れるね」と言っているかもしれません。
参考
すべての資料は2026年10月16日に閲覧しました。
- MetalLB L2の概念: https://metallb.universe.tf/concepts/layer2/
- MetalLBのトラブルシューティング(除外ラベル、
--ignore-exclude-lb、アナウンスノードの確認): https://metallb.universe.tf/troubleshooting/ - Kubernetes公式ラベル一覧: 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