背景
Kubernetesの勉強用に、クラウド上で2つのVMを作りクラスター(Masterノード、Workerノード)の構築をしていた。が、勉強用でたまにしか使わないのに2つのVMぶんの料金が毎月掛かってもったいないので、Workerノードは家にあるLinuxマシンに作って、Masterノードのクラウドと繋げられるといいなあと思い始めた。
このようなモチベーションから試行錯誤した試みを本稿でまとめる。
実現したい構成
2022/12/20 追記
本記事は「クラスターの2ノードは別々のプライベートネットワークに属する前提」で書いており、かなり七面倒くさいネットワーク設定を行っている。
これはTailscaleというP2PのVPNサービスを使えばより簡単に解決できることを確認した。Tailscaleを用いたクラスター構築方法を以下のQiitaにまとめたため、特別な理由がなければそちらを見ていただくことをお勧めする。
下図が実現したい構成。
クラウド上のVMと自宅のLinuxマシンは公開IPでインターネットを経由しないとつながらないので、ここをどのように実現するかが一番のポイントとなる。
k8sクラスターを構築するマシン、ネットワークの情報を念のため記載
- クラウドサービス側
- Masterノードにしたいマシン
- ホスト名 : k8s-master
- OS : Ubuntu 18.04
- 公開IP : 20.30.40.50/24
- プライベートIP : 10.0.0.4/24
- Masterノードにしたいマシン
- 自宅ネットワーク側
-
ルーター(HG8045Q)
- OS : 不明(Linux系?)
- 公開IP : 60.70.80.90/24
- プライベートIP : 192.168.1.1/24 (今後は登場しないがご参考まで)
-
Workerノードにしたいマシン
- ホスト名 : k8s-worker
- OS : Debian 9
- 公開IP : なし(ルーター経由でしかインターネット接続できない)
- ここに対処するための工夫が後々必要となる
- プライベートIP : 192.168.1.4/24
-
Kubernetesのクラスター構築
今回は最も一般的な(?)kubeadmを使って構築する。基本的な手順は以下サイトに基づくが、幾つかカスタマイズが必要なので後ほど説明する。
- kubeadmのインストール
(1) セキュリティ設定(ポート解放)
Tailscaleなどで構築したプライベートネットワークが使える場合は不要の手順
双方で、インターネットに対して以下のポートを開放する必要あり
23/03/03 追記
以下に示すポートは筆者の試行錯誤で記載したもの。 Kubernetes利用時に使うポートはkubernetes.ioの必須ポートの確認に記載されているので、素直にこちらの内容に従うほうが間違いないはず。
- Masterノード(クラウド側)
- 以下のポート(6443だけだが…)を、各クラウドサービスのセキュリティ設定で通信許可するようにする
- 尚、kube-apiserverのポート番号6443はデフォルト値で別の値にカスタマイズも可能
| 通信許可するIP | プロトコル | ポート番号 | 通信の方向 | 用途 |
|---|---|---|---|---|
| 60.70.80.90 (自宅LANの公開IP) |
TCP | 6443 | Inbound | Workerノードがkube-apiserverを介して通信するのに利用 |
- Workerノード(自宅LAN側)
- 以下のポート(10250だけだが…)を、自宅ルーターのWeb管理ツールなどを使って通信許可するようにする。(他のルーターでもおそらく同じように設定できるはず?)
- 筆者宅のルーターはHG8045Qを使っており、こちらのサイトなどを参考にするとインターネットからの通信を受け入れ可能にできる
- 注意すべきは、実際にポート番号10250でサービス提供するのは「公開IPをもつルーターではなく、自宅LAN内にあるマシン」だということ。つまり、「インターネットから
20.30.40.50:10250で受けたリクエスト」を「WorkerノードにするマシンのIP:ポート(=192.168.1.4:10250)」に転送する設定が必要となる。
| 通信許可するIP | プロトコル | ポート番号 | 通信の方向 | 用途 |
|---|---|---|---|---|
| 20.30.40.50 (クラウドサーバの公開IP) |
TCP | 10250 | Inbound | MasterノードがWorkerノード内のPodにアクセスする際に必要 |
(2) kubeletサービス起動時のノードIP指定
kubeadmを使ってクラスター構築する前に、各ノードがkubeadm initやkubeadm joinコマンドでKubernetesクラスターに仲間入りする際に使うIPアドレス(ノードのIP)を明示的に指定する。ノードIPが他のノードから認識できないIPに設定されてしまうとクラスターが正常に構築できなくなってしまうので注意が必要である。
適切でない例として、例えば2つのノードが公開IPを介してしかアクセスできないのに、ノードIPがプライベートIPで設定されてしまう等。逆にプライベートネットワークでつながれるのに、公開IPがノードIPに設定されてしまうのも(一概には言えないが)よろしくないかも。
具体的には各マシンに/etc/default/kubeletを作成して、以下のように設定
Tailscaleなどで構築したプライベートネットワークが使える場合はそのIPを記載すればいいはず
- Masterノード(クラウド側)
-
--node-ipにクラウドサーバの公開IPを設定する
-
KUBELET_EXTRA_ARGS=--node-ip=20.30.40.50
- Workerノード(自宅LAN側)
-
--node-ipに自宅LAN内のプライベートIPを指定する
-
KUBELET_EXTRA_ARGS=--node-ip=192.168.1.4
Workerノード側のIPは、先ほど説明した内容と矛盾した値になっている。「公開IP経由でノードがつながるのだから、自宅LANルーターの公開IP(60.70.80.90)をWorkerの--node-ipに設定するべきではないか?」と思われただろう。ところが公開IPを指定しても、kubeadm joinコマンドでクラスターにWorkerノード追加後に、kube-apiserverでWorkerノードの公開IPが反映されない。具体的には、以下のようにWorkerノードのINTERNAL-IPが<none>となってしまう。
user@k8s-master$ kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE ...
k8s-master Ready master 8d v1.18.8 20.30.40.50 <none> Ubuntu 18.04.3 LTS ...
k8s-worker Ready <none> 23h v1.18.8 <none> <none> Debian GNU/Linux 9 (stretch) ...
推測だが、--node-ipに指定して有効となるIPは、そのマシンに各ネットワークインターフェースで付与されているIPのみの模様。例えば筆者のWorkerノードとなる自宅LAN内のマシンの付与IPを調べると、有効なIPは以下のように192.168.1.4と172.17.0.1であり、自宅LANのルーターに付与されている公開IPは出てこない。
user@k8s-worker$ ip a | egrep -e ^[1-9] -e "inet "
...
4: wlp2s0: ...
inet 192.168.1.4/24 brd 192.168.1.255 scope global dynamic wlp2s0
5: docker0: ...
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
そこで既に述べたように、自宅LAN内のマシンでの設定ファイル/etc/default/kubeletには--node-ip=192.168.1.4を指定する。
これらの設定を行った後、Masterノードとなるクラウドサーバではkubeadm init、Workerノードとなる自宅LAN内のマシンではkubeadm joinで始まるコマンドを実行する。
- クラウドサーバでのクラスター追加&Masterノード生成コマンド
user@k8s-master$ sudo kubeadm init \
--pod-network-cidr=10.128.0.0/16 \ # Pod内ネットワーク範囲(好きなように設定可能)
--apiserver-advertise-address=20.30.40.50 # クラウドサーバの公開IP
- 自宅LAN内のマシンでのWorkerノード追加コマンド
- tokenとhashは
kubeadm initで最後に出力されるものをコピペすればOK
- tokenとhashは
user@k8s-worker$ sudo kubeadm join 20.30.40.50:6443 --token xxxxxxxx... \
--discovery-token-ca-cert-hash sha256:yyyyyyyy...
ここまでやると、2つのマシンでKubernetesクラスターが一応、出来上がる。
user@k8s-master$ kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE ...
k8s-master Ready master 8d v1.18.8 20.30.40.50 <none> Ubuntu 18.04.3 LTS ...
k8s-worker Ready <none> 23h v1.18.8 192.168.1.4 <none> Debian GNU/Linux 9 (stretch) ...
だがまだ問題が残っている。Kubernetesクラスターは「WorkerノードのIPアドレスは192.168.1.4だ」と思っており、Workerノード内のPodにkubectl execコマンド等でアクセスする際に、このIPを使おうとしてしまう。192.168.1.4はWorkerノードの自宅LAN内でしか使えないアドレスなので、当然アクセスできない。
クラウドサーバ(Masterノード)のNAT設定
Tailscaleなどで構築したプライベートネットワークが使える場合は不要
MasterノードからWorkerノードにアクセス可能にするために、宛先IP192.168.1.4へのリクエストをWorkerノードの自宅LANの公開IP60.70.80.90に変換する。これをNAT(Network Address Transform)という。(実際は自宅LAN内でまた192.168.1.4に変換されるので、あまりスマートなやり方ではないのだが…)
NATを実現する方法はいくつかあるが、ここではiptablesコマンドによる設定を以下に紹介する。
user@k8s-master$ sudo iptables -t nat -A OUTPUT \
-p tcp -d 192.168.1.4 -j DNAT \
--to-destination 60.70.80.90
ちなみに、上記の設定を消したい場合は-Aの部分を -D に変換してコマンド実行すればOK。(24/06/04 間違って -Xと書いていたので修正)
NATが上手くできて、Workerノード内のPodにアクセスできるかどうかを確認する。まずは、kube-systemのNamespaceにできているPodを確認。
user@k8s-master$ kubectl get pod -n kube-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-66bff467f8-djn4w 1/1 Running 0 9d 10.128.117.2 k8s-master <none> <none>
coredns-66bff467f8-gf842 1/1 Running 0 9d 10.128.117.3 k8s-master <none> <none>
etcd-k8s-master 1/1 Running 0 9d 20.30.40.50 k8s-master <none> <none>
kube-apiserver-k8s-master 1/1 Running 0 9d 20.30.40.50 k8s-master <none> <none>
kube-controller-manager-k8s-master 1/1 Running 5 9d 20.30.40.50 k8s-master <none> <none>
kube-proxy-p6bwq 1/1 Running 1 2d 192.168.1.4 k8s-worker <none> <none>
kube-proxy-xdtzc 1/1 Running 0 9d 20.30.40.50 k8s-master <none> <none>
kube-scheduler-k8s-master 1/1 Running 5 9d 20.30.40.50 k8s-master <none> <none>
確認すると、kube-proxy-p6bwqはWorkerノードに出来ていることがわかる。上手く行っていれば、以下のようにWorkerノードのPod(コンテナ)にアクセス可能。
user@k8s-master$ kubectl exec kube-proxy-p6bwq -n kube-system -it -- /bin/sh
# date
Tue Dec 8 14:06:51 UTC 2020
#
残る課題
(くどいですが)Tailscaleなどで構築したプライベートネットワークが使える場合は課題にはならない
実はもう一つ課題が残っている。現状だとKubernetesのPod同士でノードをまたいだ通信ができない。その方法については後編に記載。
