おうちKubernetesにLonghornを入れる

  1. Raspberry Piを買っておうちKubernetesクラスタを作る
  2. おうちKubernetesにSealedSecretsを入れる
  3. Raspberry Piの固定IP割当をルーターから指定する
  4. おうちKubernetesのcontrolplaneを3台に増やす
  5. おうちKubernetesLonghornを入れる <- イマココ

Nodeとなるマシンを一つ増やした.

ChuwiのLarkBox X2023.N100のCPUがほしいなーと思っていたんだけど,N100のMiniPCでDDR5メモリで500GB程度のSSDがあるものを探したら,これに落ち着いた. メモリが12GBと,8GBでも16GBでもないのだけれど,まぁnodeに使うだけだし特に気にせず買った.

これでAmazonのクーポン使って24900円.安い.

www.chuwi.com

で,そろそろ真面目に使えるようなクラスタになってきたので,PVCを作れるようにしたいと思う.というわけでSDSを入れよう. NASという手もあるんだけど,どうせnodeはみんな500GB程度のディスクを持っているし,そこまで大量のデータを入れるつもりもないので,とりあえずSDSを入れてみたいと思う.

SDSの選定

最終的にLonghornを選んでいるわけだけど,正直いまLonghornはそこまで主流ではないと思う.やはり今の主流はRook-Cephじゃないだろうか.

ただ,個人的にはそこまでパフォーマンスを求めていない.台数をあまり増やそうとも思っていないし,Cephの恩恵があるほどの規模になる予定がない. そうなると,むしろLonghornのように復旧が容易な方がメリットが大きいように思う.

ここで述べられている通り,LonghornであればLonghorn自体が壊れていてもデータを救出することができる.

ちなみにデータの救出については公式ドキュメントにも書かれている.

longhorn.io

ノード側の準備

LVMを有効化しておく

longhorn.io

ここに書かれている通り,LonghornではLVMを推奨している.こうすることで,/var/lib/longhorn に割り当てるパーティションの容量を,あとから変更することができるため,拡張性に優れている.

というわけで,Ubuntu Serverインストール時にLVMを有効化しておく.パーティションとして,適当なサイズを用意して /var/lib/longhorn にマウントしておく.

Longhornのインストール

longhorn.io

Helmでインストールするだけ.

service:
  ui:
    type: LoadBalancer

こうしておくと,MetalLBとかでIPを割り当ててくれるので,手元からアクセスできたりする.

バックアップの設定

これだけだとちょっと不安なので,大事なデータはバックアップを設定しておく.

まず,適当なS3 Bucketを用意して,そこにアクセスできるIAM Userを作り,

を,以前いれたSealedSecretsで暗号化してaws-secretsとか適当な名前のSecretsとして適用しておく.

あとはLonghorn UIから行う. Settings -> Generalを開いて,Backupセクションを探す.

Backup Target: s3://my-bucket@ap-northeast-1/
Backup Target Credential Secret: aws-secrets

こういう設定を入れる.

これであとは,同じくLonghorn UIから,個別のVolumeごとにBackupを取得できる. Backup作成のジョブは定期ジョブにすることもできる.

longhorn.io

参考

tech.opst.co.jp

light-of-moe.ddo.jp

www.aimhighergg.com

おうちKubernetesのControlPlaneを3台に増やす

  1. Raspberry Piを買っておうちKubernetesクラスタを作る
  2. おうちKubernetesにSealedSecretsを入れる
  3. Raspberry Piの固定IP割当をルーターから指定する
  4. おうちKubernetesのcontrolplaneを3台に増やす <- イマココ

ついに追加のRaspberry Piを買った.

こいつらを使ってControlPlaneを合計3台に拡張する.

joinコマンドを再発行する

1台目のControlPlaneを作った時点で,kubeadmからjoinコマンドが提示されていた.

$ kubeadm join kube-apiserver.h3poteto.local:10443 --token hogehoge.fugafuga \
  --discovery-token-ca-cert-hash sha256:hogehogefugafuga \
  --control-plane

このtokenは24時間で有効期限が切れてしまうので,今回のようなケースではとうの昔に期限切れになっている.というわけで,新しいtokenを使ったjoinコマンドを再発行してもらう.

1台目に入り,

$ kubeadm token create --print-join-command

とかすると,新しいtokenを使ったjoinコマンドが再発行される. これを2台目以降のControlPlaneで実行する.

しかし,ControlPlaneの場合,これだけだと不足していて,

[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
error execution phase preflight:
One or more conditions for hosting a new control plane instance is not satisfied.

[failure loading certificate for CA: couldn't load the certificate file /etc/kubernetes/pki/ca.crt: open /etc/kubernetes/pki/ca.crt: no such file or directory, failure loading key for service account: couldn't load the private key file /etc/kubernetes/pki/sa.key: open /etc/kubernetes/pki/sa.key: no such file or directory, failure loading certificate for front-proxy CA: couldn't load the certificate file /etc/kubernetes/pki/front-proxy-ca.crt: open /etc/kubernetes/pki/front-proxy-ca.crt: no such file or directory, failure loading certificate for etcd CA: couldn't load the certificate file /etc/kubernetes/pki/etcd/ca.crt: open /etc/kubernetes/pki/etcd/ca.crt: no such file or directory]

Please ensure that:
 The cluster has a stable controlPlaneEndpoint address.
 The certificates that must be shared among control plane instances are provided.


To see the stack trace of this error execute with --v=5 or higher

というようなエラーになる.

Certificateを探す

前述のエラーを見ると, /etc/kubernetes/pki/ca.crt がないと言われている.確かにそんなものはない. これは手動でコピーしてきて,ここに配置してもいいのだけれど,kubeadmで自動的に行っても良い.

まず,1台目のControlPlaneに戻る.

ここで

$ sudo kubeadm init phase upload-certs --upload-certs
[upload-certs] Storing the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace
[upload-certs] Using certificate key:
xxxxxxx

をやる.

kubeadmはこのようにinit時のphaseを個別に指定して実行することができる.で,upload-certsをやると,certificateの内容がSecretsに書き込まれる. で, --certificate-key を指定することで,そのSecretから /etc/kubernetes/pki/ca.crt 等の必要な証明書を作ってくれる.

というわけで,2台目で

$ kubeadm join kube-apiserver.h3poteto.local:10443 --token hogehoge.fugafuga \
  --discovery-token-ca-cert-hash sha256:hogehogefugafuga \
  --control-plane \
  --certificate-key xxxxxx

--certificate-keyupload-certsで出てきたcertificate keyを貼り付けると,無事joinできる.

あとは3台目も同じコマンドでいける.

HAProxyを3台用に更新

h3poteto.hatenablog.com

ここで紹介していた,haproxyの設定を更新して,3台にリクエストを分散できるようにしていく.

backend kube-apiserver
        mode tcp
        option tcplog
        option tcp-check
        balance roundrobin
        default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 250 maxqueue 256 weight 100
        server kube-apiserver-1 192.168.0.50:6443 check
        server kube-apiserver-2 192.168.0.51:6443 check
        server kube-apiserver-3 192.168.0.51:6443 check

keepalivedも3台対応しておく.

vrrp_instance haproxy-vip {
  state BACKUP
  priority 100
  interface wlan0                       # Network card
  virtual_router_id 60
  advert_int 1
  authentication {
    auth_type PASS
    auth_pass 1111
  }
  unicast_src_ip 192.168.0.50      # The IP address of this machine
  unicast_peer {                        # The IP address of peer machines
    192.168.0.50
    192.168.0.51
    192.168.0.52
  }

  virtual_ipaddress {
    192.168.0.40/24                  # The VIP address
  }

  track_script {
    chk_haproxy
  }
}

これで,たとえ1台が死んだとしても,VIPでアクセスしていれば2台目以降がレスポンスを返してくれる.

参考

kun432.hatenablog.com

github.com

Raspberry Piの固定IP割当をルーターから指定する

  1. Raspberry Piを買っておうちKubernetesクラスタを作る
  2. おうちKubernetesにSealedSecretsを入れる
  3. Raspberry Piの固定IP割当をルーターから指定する <- イマココ

以前の記事で,IP固定を行う際に /etc/netplan/99-manual-conf.yaml を編集してIPを固定していた.

network:
  wifis:
    wlan0:
      dhcp4: false
      access-points:
        "access_point":
          password: "password"
      addresses:
      - 192.168.0.50/24
      routes:
        - to: default
          via: 192.168.0.1
      nameservers:
        addresses:
        - 192.168.0.1
        - 8.8.8.8
        search: []
  version: 2

これを止めて,ルータ側で固定IPを指定する方式に変更した.

ルータで指定するかマシン側で指定するか

どちらでもいいんだけど,今まで他のマシンはすべてルータ側で指定しており,そちらに揃えたいという思いは最初からあった.

もう一点,長期間運用していると稀にRaspberry PiがLANにアクセスできなくなるトラブルに見舞われた. 何かしらの要因によりネットワークが再設定されるようなイベントが観測できているのだが

 systemd-timesyncd[599]: Network configuration changed, trying to establish connection.

このあとマシンがLANに接続できなくなる.もちろんルータにもつながっていないのでインターネットにつながることもできずに,孤立してしまう. OOMや他のトラブルを疑ったが,どうやらそうではないらしい.

どうも現象としてはIP固定に起因する部分がかなり怪しい.というのもsystemd-networkdとしてはなにもエラーを出しておらず,もちろんsystemd-networkdを再起動しても状況が変わらない. そして,ip a する限りIPは割り当てられているように見える.が,この現象はたまに固定IPの設定でミスって,ルータ側からIPを割り当てられていない状態に見える.

なお,再起動すると正常にIPが割り当てられて接続できる.この状態で固定IP割当を無効化し,netplan apply すると,自動割当のIPが割り振られて接続が復活するのである.

というわけで,どうもこの辺の挙動が怪しいため,ルータ側で固定IPを指定するようにした.

送っている識別子がMACアドレスじゃない

今までのUbuntu Desktop等では,適当にMACアドレスと固定IPの対応をルータ側に設定するだけで事足りていた.

しかしRaspberry Pi 4に関してはどうにもこの方式でうまく行かなかった.

というかみんなマシン側でIP固定する話しか出てこないので,メジャーじゃないのかもしれない.が,ようやく見つけた.

qiita.com

どうやらDHCPリクエスト時に送る識別子が MACアドレスではない らしい.これはRaspberry Pi固有の問題というより,おそらくsystemd-networkdの挙動なんじゃないかと思っているけれども.

とりあえず,これをMACにしてやればよい.

network:
  version: 2
  wifis:
    wlan0:
      dhcp4: true
      dhcp-identifier: mac
      access-points:
        "access_point":
          password: "password"
$ sudo netplan apply

これで固定IPはルータ側から割り当てることができた.

おうちKubernetesにSealedSecretsを入れる

  1. Raspberry Piを買っておうちKubernetesクラスタを作る
  2. おうちKubernetesにSealedSecretsを入れる <- イマココ

前回の続き. 次はSecrets管理をどうするかを考える. 別にお家クラスタなのでなんでもいいんだけど,なんでもいいんだけどやっぱりbase64エンコードされただけのSecretをgitにコミットしたくない. GitOpsする都合上どうしてもgitにコミットするのは必須として,たとえprivate repositoryであろうともgitにはあんまりコミットしたくない.

というわけでなんとかして暗号化されたものをコミットするだけで済ませたい.

HashiCorp Vaultに悩む

Secrets管理といったら一番最初に思いつくのはVaultだ.ただVaultで一番問題なのはVault Serverをどこに立てて誰が管理するのかという問題だ. Kubernetesクラスタを作ろうとしているのに,サーバをどこに建てるかを悩むのはなんかおかしい.

いや,おうちに適当なVaultサーバがあればいいんだけど,それすらKubernetesで管理したくなるし,それってつまり依存関係がループしているのでは?

というわけでこの案は却下.

次に考えるのはHashiCorp Cloudだ.ここなら外部にVault Serverを立てて,その管理はHashiCorpに丸投げすることができる.

問題があるとすると,カネがかかることだ.いや,厳密にうとカネがかかることそれ自体が問題なわけではない. すでに毎月結構な額をAWSに払っているのに,なんでVaultのためだけにHashiCrop Cloudにカネを払うんだ?という疑問だ.

www.hashicorp.com

$1.58 per hour

まぁしかも結構な金額である.

もう一つ見えるHCP Vault Secretsは無料らしいし,まぁそれで十分だと思うんだけど,それってVault Secrets Operator内からどうやって使うの?

github.com

というのがまったく謎なので,諦めた.

というかね,名前のググラビリティが低すぎるんだよ.HCP Vault SecretsでSecretsを管理してVault Secrets OperatorでSecretsに戻すって,何回Secrets書かせるんだよ.調べてもHCP Vaultの利用例しか出てこないんだよ.もっとユニークな名前つけないと調べられないだろうが.

External Secrets Operator

github.com

さっき書いたとおりAWSにはどうせ課金しているので,SecretsManagerを使ってしまえば,かなり楽に管理できる. External Secrets Operatorのいいところは,GitにコミットするリソースはSecretsManagerのパスくらいしか記述されていないところだ. 暗号化されたSecretsすら記述されないので,セキュアという意味ではかなり良い. まぁパスをミスったりすると復号できないので悲しいんだけど.

ただ問題はおうちKubernetesAWS上のリソースではないので,OIDC(IRSA)するのが結構むずいという点だ. あと,せっかくオンプレミスで作ってるんだから,ここでAWS OIDCの設定をあまり入れたくはない.

kOpsでも最初の頃は自分でS3作って鍵を作ってKubernetesのmasterに配置して……というのをやっていた.

h3poteto.hatenablog.com

同じことをすればできるだろうが,あんまりこういうのはやりたくない.

専用のIAM Userを作ってAccessKeyを吐き出し,それだけGit管理せずにSecretsとして突っ込んでおいてExternalSecretsOperatorから参照するということもできる.

ただ,AccessKeyを,もうあんまり使いたくない…….

Sealed Secrets

github.com

というわけでここに落ち着いた. AWS上で使うときは,むしろ

  • AWS KMSがあるのになんでクラスタごとに変化するmaster.keyで暗号化するの,KMSでいいじゃん
  • 現在設定されている値を見るときにいちいちKubernetesクラスタにアクセスして復号化するのが面倒

とかいろいろ思うことがあって,だいたいいつもExternalSecretsOperatorを使ってしまう. だが,IRSAが気軽にできない環境では,こっちの方が便利な気がした.

あと,これなら追加でカネがかからない. そして,復号化とSecretリソース作成はSealedSecretsのコントローラ側でやってくれるので,我々はSealedSecretリソースをコミットするだけで良い. こういう方式だとGitOpsツール側で,復号化等の追加作業がいらないので,何を採用しても安心感がある.

というわけで

$ helm install sealed-secrets -n kube-system sealed-secrets/sealed-secrets

入れて,

$ kubeseal --fetch-cert --controller-name=sealed-secrets --controller-namespace=kube-system > pub-sealed-secrets.pem

証明書を取得して,

$ kubeseal --scope cluster-wide --format=yaml --cert=pub-sealed-secrets.pem < secrets.yaml > sealed-secrets.yaml

適当なSecretリソースをSealedSecretsに変換できる.

あとは sealed-secrets.yaml をGit管理して,applyさせたら良いだけ.

Shokzのイヤホンを買ったのでLinuxで使えるようにする

Bluetooth接続の骨伝導イヤフォンを買ったのだが,これをLinuxで使えるようにする.

A2DPHFP/HSP

Bluetoothでイヤフォンを扱う場合には,プロファイルを選択する必要がある. A2DPHFP/HSPというのはそのプロファイルのことであり,

wiki.archlinux.org

この辺に書いてある.

大きく分けでA2DPは主に音楽を聞くためにステレオになるのだが,HFP/HSPは必ずモノラルになる.

で,大きな問題として普通Linuxで入れているPulseAudioはA2DPのプロファイルは用意しているのだが,HFP/HSPに対応していないということ. これの何が困るのかというと,HFP/HSPに対応していないとイヤフォン付属のマイクを入力として認識できないということだ. pavucontrolとかで入力デバイスを探しても,Bluetoothイヤフォンに付属するマイクは入力として認識されていない.

そして,PulseAudioはHFP/HSPに対応していないので,そもそもプロファイルを切り替えることができない.

PipeWireを使う

この解決策は,PipeWireを使うことだ.

wiki.archlinux.org

まず,pulseaudioっぽいパッケージをすべてアンインストールする.

$ sudo pacman -Rdd manjaro-pulse pulseaudio pulseaudio-alsa pulseaudio-equalizer pulseaudio-jack pulseaudio-lirc pulseaudio-rtp pulseaudio-zeroconf pulseaudio-bluetooth pulseaudio-ctl sof-firmware

で,pipewireを入れる.

$ sudo pacman -S manjaro-pipewire
$ systemctl --user enable pipewire
$ systemctl --user start pipewire

としておく.これでOSを再起動すると,

$ pactl info
...
Server Name: PulseAudio (on PipeWire 0.3.70)
Server Version: 15.0.0
...

となればOK.

これで,pavucontrolのデバイス一覧から,ShokzのプロファイルをHFP/HSPに変更することで,付属マイクを入力デバイスとして認識してくれる.

参考: https://blastoise.hashnode.dev/wireless-headset-microphone-issue-for-linux

Polybarでマイクのボリュームを表示する

今までは

github.com

これを使っていたんだけど,こいつは内部的にpacmdに依存している.

https://github.com/MarcDonald/polybar-mic-volume/blob/47fce14e39c709171c234e7aa713903ee53d9906/mic-volume/mic-volume.sh#L23

pacmdはPulseAudioと一緒にアンインストールされてしまったので,別の方法を探す必要がある.

面倒なので自分でスクリプトを書いて

#!/bin/sh

volume=$(wpctl get-volume @DEFAULT_AUDIO_SOURCE@ | awk -F '[:] ' '{print $2}')
percent=$(awk "BEGIN { print $volume*100 }")
echo "${percent}%"

とかやると現状デフォルトになっているマイクのボリュームを取得できる. pactlは使えるので,ボリュームを変更したい場合は,

$ pactl set-source-volume $DEFAULT_SOURCE_INDEX +7%

とかやれば良い.