おうちN100マシンとさくらVPSでk0sをやっていく

前回の記事で書いた「LA10超えて限界すぎるのでN100マシン買ってcontrol plane移したい」を実際にやったので続編いきます。

買ったのはBeelink Mini S12 ProのN100/メモリ16G/SSD500Gモデル。購入価格は31800 - 9000 = 22800円でした。

Windowsのライセンスがあれとかいう話がありますが、Ubuntu入れるので特に気にせず買いました。 Ubuntu 22.04 LTSのkernelにはWiFiBluetoothのドライバがない1ので、このデバイスについての動作確認はWindowsないしUbuntu 23.10以降等で行う必要があります。

既存のクラスタ、controll planeを移行できないか試行錯誤したけど結局諦めてPVだけ保全クラスタ爆破し再作成したので、以下に書かれているのはクラスタ作成した手順です。

おうち N100マシンとさくらVPSをつなぐ

繋ぐうえで必要な条件がNetworking (CNI) - Documentationに説明があります。具体的には、以下2つ。

  1. controller planeとworkerでいくつかのTCP/UDP接続
  2. worker間ではpod CIDRとservice CIDR
    1. pod CIDRは10.244.0.0/16で、service CIDRは10.96.0.0/12
    2. service CIDR宛接続は各node上のkube-proxy(が設定するiptablesやipvs)によってpodCIDR宛にDNATされるので、LBがないならpod CIDRだけVPN通せば良い。

以上2つを実現すれば良く、今回はWireGuardを使ってつなぎました。なお、おうちN100マシンはNATの内側にいるので、おうちN100マシンからさくらVPSへ接続を開始します。

# さくらVPS(worker)
[Interface]
Address = 10.220.0.1
ListenPort = 51100
PrivateKey = さくら秘密鍵
MTU=1460

[Peer]
PublicKey = おうち公開鍵
AllowedIps = 10.220.0.2/32, 10.244.0.0/16
PersistentKeepalive = 25
# おうちN100(controller plane + worker)
[Interface]
Address = 10.220.0.2
PrivateKey = おうち秘密鍵
MTU = 1460

[Peer]
PublicKey = さくら公開鍵
AllowedIps = 10.220.0.1/32, 10.244.0.0/16
PersistentKeepalive = 25
Endpoint = (さくらVPS):51100

あとはさくらVPSiptablesで51100/udpの接続を通す。MTUはデバイス側でちゃんと設定されていれば不要な気もしますが、よしなに。

AllowedIpsの設定で前掲の条件2は満たせます。条件1はこの設定に加えてcontroller planeのインストール時にk0sの設定をいじることで対応します。

クラスタの設営

基本的にはManual (advanced) - Documentationにしたがっていきます。

controller planeの設営

変更点は2. Bootstrap a controller nodeで2つあります。

k0s config createで生成した初期設定ファイルを書き換える

設定ファイルのspec.api.addressspec.storage.etcd.peerAddressを書き換え。

デフォルトだと、ここのIPアドレス非ローカルなアドレスが入るんですが、spec.api.addressをWireGuardのインタフェイスIPアドレスにしてやります(今回の例では10.220.0.2)。これによって、workerがWireGuardを経由してcontroll planeへ接続ができるようになります(詳細は後述)。

なお、spec.storage.etcd.peerAddressについてはこの時点ではいじらなくていいけど、後日etcd増やしたくなったときのためにいじっておきます。

--enable-workerをつける

sudo k0s install controllerの引数に--enable-workerをつけると、controller planeかつnodeになります。

これでnodeにはなるんですが、Worker Node Configuration - Documentation > Taintsにあるとおり、nodeにrole.kubernetes.io/master:NoExecutのtaintがついて2podが置かれません。この挙動はcontrollerを入れる際に--no-taintsをつけることで回避できます。

taintsは後から剥がしてもいいので、どっちでも。

worker の設営

controllerをつくったら、workerをつくります。controller planeで3. Create a join tokenにあるようにjoin tokenをつくってworkerに食わせます。トークンの有効期限はjoinするときだけ気にするようで、参加後はトークンがexpire/invalidateされていても正常にクラスタが動いてます。

ちなみにこのtokenにはcontroller planeの設定ファイルにあるspec.api.addressが接続先として入っています3。前節で書き換え済なので、このトークンを使うとにWireGuardトンネル経由でworkerからcontroller planeに接続します。

ちなみに、WireGuardでVPNを設定する際にさくらVPSのwgk0sデバイスに対し何らiptablesでACCEPTしていないので、worker(さくらVPS)→controller(おうちN100マシン)の片方向にしか接続を開始できません。ですが、Controller-Worker communicationにあるとおり、k0sは最初からKonnectivityが入っていてworkerからcontrollerに対してtcpセッションを発行するのでしっかりつながる。kubectlコマンドも、worker nodeで実行した場合は正常にトンネルを通過し、controller nodeで実行する場合はlocal接続なので無事に繋がります。

前回の記事との差異

externalIPsをやめた

あるsvc manifestにexternalIPsを指定すると、ここに書かれたIPアドレスを持たないノードに対し「書かれたexternalIPsアドレス宛接続をIPVSによって当該svcのportについて書かれたpodへDNAT」する設定が入ります。このIPVSに流すための前段処理4によってWireGuardのパケットもloに吸い取られて接続断となります。

kube-proxyをiptablesモードにすれば回避できるような気もしますが、そこまでしてexternalIPsを使う必要はないので大人しくNodePortを使う。

OpenEBS Local PVをやめた

どうせlocalPVなので、素朴なlocal PVにしました。また、2ノード構成にしたことからnfs PVを導入しています。

既にあるOpenEBS Local PVから、local PVおよびnfs PVに移行することになります。PV移行時にpersistentVolumeReclaimPolicyがRetainでなければPVC消したときにPVが消えたりするので要注意。

使い分けですが、Feed ReaderとしてMinifluxを使っている都合で立ててるPostgreSQLとひたすら溜めてるEEWをlocal PVに置き、それ以外はnode意識せずに済むようnfs PVに置くことにします。

StorageClassですが、volumeBindingMode変えなくてImmediateでもいいかな~ということで端折っています。

k8s local PV

local PVの特徴としてnodeAffinityを書くんですが、ここで使うnodeSelectorTerm.matchFieldsmetadata.nameしかkeyに使えません。APINodeSelectorTerm v1 coreには特段の記述がありませんが、実装を見るとmetadata.nameしか使えない。それ以外の項目を使うには、matchExpressionを使う必要があります。

また、PVCはPVを選ぶ際に、容量やアクセスモードなど限られたやつで適当に良さげなのをマッチするので、今回マイグレーションする際には確実にPVとPVCがマッチできないと困る。なので、PVにラベルを張り、PVCにラベルセレクターを書きます。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: local-eewlog
  labels:
    target: eewlog
spec:
  capacity:
    storage: 2Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  local:
    path: /var/lib/eewlog/data
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchFields:
        - key: metadata.name
          operator: In
          values:
          - name-of-sakura-vps
      - matchExpressions:
        - key: status.nodeInfo.machineID
          operator: In
          values:
          - hogepoyohogepoyo
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: eewlog-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: local-storage
  resources:
    requests:
      storage: 2Gi
  selector:
    matchLabels:
      target: eewlog

NFS

インターネット and WireGuard越しなのでlatencyが気になりますが、qperfでlatency測ってみると5ms程度なので、たまにちょっと読み書きする程度なら気にならないはず。

$ qperf -vvu -t 1 -m 1k 10.220.0.2 tcp_lat
tcp_lat:
    latency   =  5.75 ms
    msg_size  =     1 KB
    time      =     1 sec
    timeout   =     5 sec

まず素朴にnfsのclientとserverをNetwork File System (NFS) | Ubuntuの説明に従って入れる。

そしてNFS Serverの設定として、/etc/exportsを書く。k8snfs PVは指定したnfs targetをpodが走ってるnode上の適当なpathへmountし、当該pathを更にcontainer runtimeによってpod containerにbind mountする形で実装されています5。よって、nfsのアクセス許可ホストはnodeがWireGuard経由で繋いでくる10.220.0.0/24になります。他のオプションは雰囲気で書き、sudo systemctl reload nfs-kernel-serverなどで反映。

/export/nfs 10.220.0.0/24(rw,async,crossmnt,no_root_squash,no_subtree_check)

暫くしてから気づいたけど/exportにするとtab補完で/etcが一発で出なくなってだるいですね。そのうち変えよう。

あとはPV(とPVC)のYAMLを書くだけ。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-dgmtx-status
  labels:
    target: dgmtx-status
spec:
  capacity:
    storage: 2Mi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: nfs
  nfs:
    server: 10.220.0.1
    path: /export/nfs/dgmtx-status
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dgmtx-status-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: nfs
  resources:
    requests:
      storage: 2Mi
  selector:
    matchLabels:
      target: dgmtx-status

HitchのALPNでHTTP/2を有効化

ことし2月末にingress-nginxが更新されてnginxが1.25系に更新され、h2cがhttp/1.1へfallbackできるようになりました。これに伴いingress-nginxがh2cに対応しました。クラスタを再構築したことでingress-nginxも最新になりました。やったね。

対応としてはHitchのALPNにh2を追加します。追加と言ってもdocker-hitch見るとわかりますが、default valueh2,http/1.1なので、deploymentで--alpn-protos=http/1.1を消すだけ。

ここでのh2通信は対Cloudflareなので、従前よりユーザとCloudflare間はh2だったはずだから見た目は変わんないけど、h2特有のなんかしらが出来たら良いな。

やってみた感想

めっちゃ安定した。現状はcontrollerのtaint剥がしてないのでpodあらかた従前のさくらVPS上workerにいますが、controller/workerどちらもLAは0.1ぐらいをふらふら。やはりメモリが足らなかった。

将来構想として東京にもN100ノード置くことをちょっと考えていますが、WireGuardの設定6さえなんとかすれば良さそう?


  1. 詳細はMini S12 Pro ubuntu wifi driver - Beelink ForumAX101 Ubuntu 22.04 or 22.10 driver - Intel Communityを参照。
  2. 実装を見ると、--single-nodeのときはtaintつかない。
  3. このtokenはYAMLをzipしたあとbase64したものなので、cat join.token | base64 -d | gunzip -あたりで中身を覗くことが出来ます。
  4. IPVSはINPUTチェインを通った直後に処理するので、INPUTチェインまでパケットが流れる状態になってる必要がある。この順序はnetfilter/ipvs/ip_vs_core.cにコメントあり。
  5. 詳細はpodにNFSがmountされるまで #kubernetes #コードリーディング - クリエーションライン株式会社を参照。
  6. さくらVPSをhubにすると100Mbpsでサチるから東西どっちかのおうちノードをhubにする、みたいな。