はじめに
クラウド管理型のKubernetes(GKEやEKSなど)では type: LoadBalancer のServiceを作成するだけで外部IPが自動的に割り当てられます。しかしConoHa VPSのようなベアメタル環境では、そのままでは EXTERNAL-IP が永遠に <pending> のままになってしまいます。
今回はConoHa VPS上の2ノード構成KubernetesにMetalLBを導入し、Nginx Ingressを LoadBalancer タイプで外部公開するまでの手順をまとめます。
環境
| ノード | IPアドレス | 役割 |
|---|---|---|
| Control Plane | 203.0.113.X |
コントロールプレーン(Pod展開も可能) |
| Worker Node | 203.0.113.Y |
ワーカー専用 |
- Kubernetes v1.28
- MetalLB v0.14.5
- Nginx Ingress Controller
MetalLBとは
MetalLBはベアメタル環境向けのロードバランサー実装です。クラウド環境が提供するLB機能をソフトウェアで再現します。
動作モードは2種類あります。
- L2モード:ARPでIPを宣伝する。シンプルで設定が簡単。VPS環境に向く
- BGPモード:BGPプロトコルでルーターと通信する。本格的なオンプレ向け
今回はConoHa VPSのネットワーク構成に合わせてL2モードを採用します。
Step 1:MetalLBのインストール
ファイルをローカルに保存するとターミナルの制御文字が混入する場合があるため、URLから直接applyするのが確実です。
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.5/config/manifests/metallb-native.yaml
Podの起動を待ちます。
kubectl wait pod \
-n metallb-system \
--for=condition=Ready \
--timeout=120s \
-l app=metallb
2ノード構成では speaker が各ノードに1つずつ起動します。
kubectl get pods -n metallb-system -o wide
NAME READY STATUS NODE
controller-xxx 1/1 Running control-plane
speaker-aaa 1/1 Running control-plane
speaker-bbb 1/1 Running worker
Step 2:kube-proxyのstrictARP設定
ipvs モードを使用している場合は strictARP: true が必要です。
kubectl edit configmap -n kube-system kube-proxy
mode: "ipvs"
ipvs:
strictARP: true # false → true に変更
iptablesモードの場合はこの手順は不要です。
Step 3:IPAddressPoolの作成
両ノードのパブリックIPをプールに登録します。
# ip-pool.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: conoha-pool
namespace: metallb-system
spec:
addresses:
- 203.0.113.X/32 # Control PlaneのパブリックIP
- 203.0.113.Y/32 # Worker NodeのパブリックIP
kubectl apply -f ip-pool.yaml
Step 4:L2Advertisementの設定
どのノードがARP宣伝に参加するかを matchLabels で指定します。ここで matchLabels を省略すると unknown field エラーになるので注意が必要です。
# 実際のノード名を確認
kubectl get nodes
# l2-advertisement.yaml
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: conoha-l2adv
namespace: metallb-system
spec:
ipAddressPools:
- conoha-pool
nodeSelectors:
- matchLabels:
kubernetes.io/hostname: <control-planeのノード名>
- matchLabels:
kubernetes.io/hostname: <workerのノード名>
kubectl apply -f l2-advertisement.yaml
Step 5:Nginx Ingressのインストール
公式のmanifestをベースに spec.type を LoadBalancer に設定してapplyします。
curl -Lo deploy.yaml \
https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/baremetal/deploy.yaml
取得したファイルを編集する際、よくある落とし穴が2つあります。
落とし穴1:type の大文字小文字
# NG
spec:
type: loadBalancer
# OK
spec:
type: LoadBalancer
落とし穴2:loadBalancerIP のフィールド名
# NG(大文字Lになってしまった場合)
spec:
LoadBalancerIP: 203.0.113.Y
# OK
spec:
loadBalancerIP: 203.0.113.Y
修正後にapplyします。
kubectl apply -f deploy.yaml
EXTERNAL-IPが割り当てられれば成功です。
kubectl get svc -n ingress-nginx ingress-nginx-controller
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.x.x.x 203.0.113.Y 80:xxxxx/TCP,443:xxxxx/TCP 20m
Step 6:ConoHaファイアウォールの設定
ここが2ノード構成で特に重要なポイントです。
MetalLBがWorker NodeのIPをEXTERNAL-IPとして選択した場合、Worker Node側のセキュリティグループにもポート開放が必要です。ConoHaのコントロールパネルで以下を確認・追加してください。
| ノード | 開放するポート | プロトコル |
|---|---|---|
| Worker Node | 80 | TCP |
| Worker Node | 443 | TCP |
今回はConoHaの「IPv4v6Web」セキュリティグループをWorker Nodeに適用することで解決しました。
Step 7:動作確認
テスト用アプリをデプロイします。
# test-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-app
spec:
replicas: 2
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
spec:
containers:
- name: hello
image: hashicorp/http-echo
args: ["-text=Hello from Ingress!"]
ports:
- containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
name: hello-svc
spec:
selector:
app: hello
ports:
- port: 80
targetPort: 5678
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hello-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: 203.0.113.Y.nip.io # EXTERNAL-IP(Worker NodeのIP)を使う
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello-svc
port:
number: 80
hostには必ずEXTERNAL-IP(MetalLBが割り当てたIP=Worker NodeのIP)を使います。Control PlaneのIPを指定するとルーティングが一致せずアクセスできません。独自ドメインがない場合は nip.io を使うとIPアドレスをそのままドメインとして利用できます。
kubectl apply -f test-app.yaml
# Ingressの状態確認
kubectl get ingress hello-ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
hello-ingress nginx 203.0.113.Y.nip.io 203.0.113.Y 80 1m
curl http://203.0.113.Y.nip.io/
# Hello from Ingress!
トラブルシューティングまとめ
今回の作業で遭遇したエラーと対処法です。
YAMLに制御文字が混入する
ターミナルからコピペしてYAMLを作成すると、ANSIエスケープシーケンス(^[[O など)が混入することがあります。URLから直接applyするか、curl でダウンロードするのが確実です。
# NG:ブラウザやターミナルからコピペして作成
# OK:curlで直接ダウンロード
curl -Lo manifest.yaml https://...
# または直接apply
kubectl apply -f https://...
spec.type: Unsupported value: "loadBalancer"
LoadBalancer の L が小文字になっています。sed で一括修正できます。
sed -i 's/type: loadBalancer/type: LoadBalancer/g' deploy.yaml
unknown field "spec.LoadBalancerIP"
loadBalancerIP の l が大文字になっています。前述の sed の副作用で起きがちです。
sed -i 's/LoadBalancerIP/loadBalancerIP/g' deploy.yaml
unknown field "spec.nodeSelectors[1].kubernetes.io/hostname"
nodeSelectors の書き方に matchLabels が抜けています。
# NG
nodeSelectors:
- kubernetes.io/hostname: worker
# OK
nodeSelectors:
- matchLabels:
kubernetes.io/hostname: worker
まとめ
| 項目 | ポイント |
|---|---|
| MetalLBモード | L2モード(VPS環境に最適) |
| IPAddressPool | 両ノードのIPを登録する |
| L2Advertisement |
matchLabels を忘れずに |
| EXTERNAL-IP | Worker NodeのIPが割り当てられる |
| Ingressのhost | EXTERNAL-IPに合わせる(Control PlaneのIPではない) |
| ファイアウォール | EXTERNAL-IPのノード(Worker)側も開放が必要 |
2ノード構成ではどちらのノードがリーダーになるかがポイントです。MetalLBはL2モードでどちらかのノードをリーダーとして選出し、そのノードのIPをEXTERNAL-IPとして使います。リーダーノードがダウンした場合は自動でフェイルオーバーします(数秒の断が発生)。