
K3sでTraefik無効化のフラグを実行したのに、ingress-nginxを入れてもなぜか443番ポートが取れない――この現象に心当たりがあるなら、それはTraefikのせいではない。K3sが裏で動かしているもう一つのデフォルトコンポーネントが原因だ。
1. Traefikを無効化したのに、ingress-nginxの443番ポートが取れない
--disable=traefik を実行し、kubectl get pods -n kube-system でTraefik関連のPodが消えたことを確認した。そのうえでingress-nginxをLoadBalancerサービスとしてインストールした。それなのに外部IPは割り当てられるのに、443番へのアクセスがタイムアウトする、あるいは別のPodに繋がってしまう。
これは決して珍しい話ではない。K3sの公式ドキュメントによれば、K3sは起動時に2つの独立したネットワークコンポーネントを自動デプロイする(K3s公式ドキュメント: Networking Services)。1つはTraefik(Ingress Controller)で、K3sクラスタのデフォルトIngress Controllerとして長年採用されている(Traefik Labs公式ブログ: Easy Ingress Management on the Edge with K3s and Traefik)。もう1つはServiceLB(旧称Klipper LoadBalancer)だ。NGINXとTraefikはどちらも80/443番ポートを使うため、どちらかが残っていれば競合が起きる(k3s-io/k3s Issue #2403では、k3s導入後にnginxがIPv4の80番だけ応答しなくなった事例が報告されている)。
こうした「デフォルトの見えない依存関係」に気づかないまま本番投入してしまうのは、K3sクラスタ管理者なら誰しも一度は通る道だ。Kuboのようにクラスタ構成が可視化された環境であれば、この種の見落としに早い段階で気づける。

2. 犯人はTraefikではない。K3sが黙って動かしていた「ServiceLB」
ここで多くのエンジニアが見落とすのが、TraefikとServiceLBは別々のフラグで管理されている独立したコンポーネントという事実だ。--disable=traefik はTraefikだけを止める。ServiceLBは指定しない限り動き続ける。
ServiceLBはDaemonSetとして動作し、svclb- というprefixのPodをクラスタ内のノードに配置し、hostPortを使ってLoadBalancerサービスにポートを割り当てる(K3s公式ドキュメント: Networking Services)。つまりTraefikのLoadBalancerサービスが無くなっても、ServiceLBは他のLoadBalancerサービス(ingress-nginxを含む)にも同じ仕組みでポートを割り当てようとする。これがTraefikを無効化したのに443番が取れない直接の原因になる。
ServiceLBが実際に何をしているかは、k3s-io/k3s公式リポジトリのディスカッションで開発者自身が説明している。ServiceLBはノード上にDNATのiptablesルールを設定するだけの存在で、DaemonSetのPod自体はトラフィックを処理しない。ServiceLBが無ければtype: LoadBalancerのServiceは永久にpending状態のままになる、というのがそもそもの役割だ(k3s-io/k3s Discussion #9927: What does ServiceLB (Klipper LB) actually do?)。実装としては、Service1つにつき1つのhostPortを割り当て、iptablesでクラスタIPへ転送する仕組みになっている(k3s-io/klipper-lb)。
TraefikがなぜK3sにデフォルト同梱されているのかについても、K3sメンテナー自身が公式ディスカッションで理由を説明している。「vanilla Kubernetesより一歩進んだ、フル機能のクラスタを最初から提供する」という設計方針であり、--disable traefikフラグで無効化できることも明言されている(k3s-io/k3s Discussion #6215: Consider dropping Traefik as a bundled component)。しかし実際には「--no-deploy traefik を指定してもTraefikのPodが再起動してくる」という報告(Issue #1160)も存在し、/var/lib/rancher/k3s/server/manifests/traefik.yaml を直接削除してもsystemd再起動時にK3sが再生成してしまう、という設定ファイル管理の誤解が背景にあることが多い。

3. 診断コマンドで「見えない同居人」の正体を確認する
ServiceLBが残っているかどうかは、次のコマンドで一発で確認できる。
kubectl get pods -A | grep svclb
このコマンドで svclb- から始まるPodが1つでも出力されれば、ServiceLBはまだ動いている。そもそもKubernetes公式ドキュメントが説明するように、Ingress ControllerはKubernetes本体には組み込まれておらず、クラスタごとに個別のControllerを動かす前提の仕組みだ(Kubernetes公式ドキュメント: Ingress Controllers)。K3sがTraefikをデフォルトで動かしていること自体は便利だが、「代替Ingress Controllerを入れた瞬間にポート競合を引き起こす」根本原因でもあり、Traefikを止めるだけでは対策として不十分なケースがある。
また、/var/lib/rancher/k3s/server/manifests/ 配下のマニフェストは直接編集してはならない。K3sの起動プロセスが差分を検知して元の状態に戻してしまうためだ。カスタマイズしたい場合は同じディレクトリに HelmChartConfig リソースを追加で置くのが正しい方法だと公式ドキュメントは説明している(K3s公式ドキュメント: Networking Services)。

これはKubo Cloudのように構成状態が管理画面で可視化されていれば、CLIでの確認すら不要になる領域だ。KuboではCaptain UIがクラスタのIngress/LoadBalancer構成を可視化するため、「デフォルトのまま残っているコンポーネント」に気づかず本番投入してしまうリスクを減らせる。
4. 正しい無効化手順とMetalLBを使う場合の応用
TraefikとServiceLBを両方無効化したい場合、既存クラスタでは次の手順を踏む。
- すべてのサーバーノードで
/etc/rancher/k3s/config.yamlに以下を追記する
disable:
- traefik
- servicelb
sudo systemctl restart k3sでK3sを再起動するkubectl get pods -A | grep svclbを再実行し、出力が無いことを確認する
新規構築であれば、インストール時点で両方を指定すればよい(oneuptime.comのMetalLB導入ガイド)。
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable servicelb --disable traefik" sh -
MetalLBを使う場合も同じ問題が起きる
ServiceLBはingress-nginxだけでなく、MetalLBとも競合する。両者はどちらもLoadBalancerサービスを監視し、それぞれが外部IPを割り当てようとするため、Serviceがpendingになったり誤ったIPが割り当てられたりする「予測不能な動作」が起きる(k3s-io/k3s Issue #2323では、ServiceLBを無効化したはずなのにMetalLBのBGPルートがflappingし続けたという報告がある)。
klipper-lb(ServiceLB)とMetalLBはそもそも設計思想が異なる。ServiceLBはノード上のiptablesルールだけで動く軽量な仕組みで、外部ネットワークへのIPアドバタイズメントは行わない。一方MetalLBはL2(ARP)モードとBGPモードの両方をサポートし、外部ネットワークに対して実際にIPを広告できるため、本番のベアメタルクラスタでの利用に適している(k3s-io/k3s Discussion #9927)。MetalLBを本番投入するなら、ServiceLBの無効化は前提条件として必ず外さないようにしたい。

K3sをベースにしたマネージドK8sであるKuboなら、こうしたIngress/LoadBalancerの構成差分をAI-Driven Deploymentが検知するため、disableフラグの反映漏れに気づかず本番でハマるリスク自体を減らせる。オンプレミス環境で同様の構成管理を求める場合は、Kubo On-Premiseでも同じ思想の管理画面が提供されている。
5. まとめ
K3sのTraefik無効化は、--disable=traefik を実行しただけでは終わらない。K3sはTraefik(Ingress)とServiceLB(LoadBalancer、旧Klipper)という2つの独立したコンポーネントをデフォルトで同梱しており、それぞれ別のフラグで無効化する必要がある。
- 症状: Traefikを無効化したのに、ingress-nginxが443番ポートを取れない
- 原因: ServiceLBがhostPortを使って他のLoadBalancerサービスにもポートを割り当て続けている
- 診断:
kubectl get pods -A | grep svclb - 修正:
disable: [traefik, servicelb]をconfig.yamlに追記して再起動 - 応用: MetalLBを使う場合も同様にServiceLBの無効化が前提条件になる
「デフォルトだから」で済ませず、K3sが同梱する2つのネットワークコンポーネントを個別に把握しておくことが、本番投入前の最後のチェックポイントになる。構成の可視化ごとKuboに任せたいという場合は、お問い合わせから相談してみてほしい。