概要
2022年11月からRaspberrypiで稼働している「にりらみすきー部」ではユーザー数の急増から実用に耐えられなくなってきている為、Raspberrypiを増やしてKubernetes運用するぞ!という方向になりました。
Kubernetesを採用した理由として
・misskey.ioで使用されている情報が散見される
・Kubernetesに関連する情報が比較的存在する
・負荷分散で使用するときに定番なイメージ
が挙げられます。
今回はmisskeyの移行について記事を書いていきたいと思います。
この記事の注意事項ですが、現状データベースを複数用いた運用の際にMisskeyでInternalエラーが発生するなどがあったため、あくまで例として参照ください
損害が発生した場合、筆者は責任を負いかねます。
8/20日時点で殆ど書き上げましたが、一部編集中です。ご了承ください。
前提条件
筆者はネットワーク経験が一切なく、仮想化についてもDockerをちょこっと開発に用いている程度で何もできません。
あと2023年の2月頭くらいにTwitterのFFの人がIX2215を買っているのを見て乗りでヤフオクで落札しました(6台届いた)。根元につないでいます。
ハードウェア
- Raspberry pi 8GB ×4
- Anker PowerPort 6
- 電源ケーブル
- SSD
- silicon power
- KIOXIA
- クラスタケース
- LANケーブル https://www.amazon.co.jp/gp/product/B09MY5HVNM/
- SATA to USB 3
- たくさんあるので変なものに注意!
- 私はこれを買いました
- ラズパイケース
※なおMicroSDは使用しません。MicroSD+SSDの場合一部手順が不要になるかも...?
➡私の環境では出来ませんでした。ただ、簡単にしかしていないのでもしかしたら出来るかもしれません。
環境構築 ~前提及びOS編~
まずRaspberrypiにOSをインストールしていきます。
例のごとくRaspberry Pi ImagerをWindows上にインストールします。
Raspberry piにOSをインストールしていくわけですが、ここで重要なのがOSはUbuntuを選ぶです。
いろいろ流派がありますが、今回はRaspberry Pi OSではなくUbuntuです。
Ubuntu選択の理由としては後ほどインストールする「Rook / Ceph」と呼ばれるストレージ関連の構築を行うソフトウェアがRaspberry Piだとインストールできないからですね...
Rook/Cephとは何ぞやという人はこの記事のRook/Cephセクションまで読み飛ばして確認してみてください。
という事で買ったSSDをパソコンに接続してRaspberry Pi Imagerで最低限パスワードやローカルドメイン(なおsshアクセスでは使えない模様)を設定したりした後にインターネットへ接続!電源投入!
ルーターや何らかのソフトウェアでRaspberry PiのIPを確認します。
とりあえず全台にUbuntuを入れます。
インストール終了後、次のステップに進みます。
Ubuntu 環境構築
まずはパーティションの設定をします。
Rook / Cephでは前提条件として以下の4つのいずれかに当てはまらなければいけません。
・Raw devices (no partitions or formatted filesystems)
・Raw partitions (no formatted filesystem)
・LVM Logical Volumes (no formatted filesystem)
・Persistent Volumes available from a storage class in block mode
要は何もないパーティションとか必要ってことですね!
ちなみにMicroSD+SSDの方はフォーマットされていないSSDがあれば特段この手順はいらないはず...?
現在昔稼働していたRaspberrypiを新たに追加する予定なので確認次第追記します。
追記
実施した結果上手く稼働できなかったため、SSDにたいしてOSのインストールを行いました。
SSDのパーティション作成
以下の記事を参考にさせていただきました!
ということでやっていきます。
前述のとおり、Raspberry pi にはmicroSDは刺さっていないので、ほかのRaspberry pi にSSDを差し込んでフォーマットします。
以下にコマンドを記載しておきます。
ファイルが消える可能性があります!!!!
あくまで自己責任で実行してください
出力については一部省略しています。
// コマンドまとめ
$ lsblk -f
$ sudo e2fsck -f /dev/sdb2
$ sudo resize2fs /dev/sdb2 75G
$ sudo fdisk /dev/sdb
Command (m for help): p
Command (m for help): d
Partition number (1,2, default 2): 2
Command (m for help): n
Select (default p): p
Partition number (2-4, default 2):
First sector (526336-500118191, default 526336):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (526336-500118191, default 500118191): +75G
Do you want to remove the signature? [Y]es/[N]o: y
Command (m for help): w
$ sudo partprobe
$ sudo e2fsck -f /dev/sdb2
$ sudo fdisk /dev/sdb
Command (m for help): n
Select (default p):
Using default response p.
Partition number (3,4, default 3):
First sector (157812736-500118191, default 157812736):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (157812736-500118191, default 500118191):
Command (m for help): t
Partition number (1-3, default 3): 3
Hex code or alias (type L to list all): 8e
Command (m for help):w
$ sudo partprobe
$ sudo fdisk -l /dev/sdb
// 接続デバイスを確認
$ lsblk -f
NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS
loop0 squashfs 4.0 0 100% /snap/core20/1856
loop1 squashfs 4.0 0 100% /snap/lxd/24326
loop2 squashfs 4.0 0 100% /snap/snapd/18363
loop3 squashfs 4.0 0 100% /snap/snapd/18600
loop4 squashfs 4.0 0 100% /snap/core20/1826
// OSが入っているSSD (既にフォーマットしているので表示される内容が異なると思います)
sda
├─sda1 vfat FAT32 system-boot B1C2-332F 178.6M 29% /boot/firmware
├─sda2 ext4 1.0 writable 3b687fda-fd50-40d7-9407-5f6103485cda 68.2G 3% /
└─sda3
// フォーマット対象のSSD
sdb
├─sdb1 vfat FAT32 system-boot B1C2-332F
└─sdb2 ext4 1.0 writable 3b687fda-fd50-40d7-9407-5f6103485cda
// sdb2を対象に行います。
// ファイルシステムチェック 途中何か聞かれるので対応します。(私はとりあえずyにしました)
$ sudo e2fsck -f /dev/sdb2
// 75GBにリサイズします。サイズは適当です。
$ sudo resize2fs /dev/sdb2 75G
resize2fs 1.46.5 (30-Dec-2021)
Resizing the filesystem on /dev/sdb2 to 19660800 (4k) blocks.
The filesystem on /dev/sdb2 is now 19660800 (4k) blocks long.
// fdiskでsdbのパーティション編集画面に入ります
$ sudo fdisk /dev/sdb
// pでパーティションの情報を表示
Command (m for help): p
Device Boot Start End Sectors Size Id Type
/dev/sdb1 * 2048 526335 524288 256M c W95 FAT32 (LBA)
/dev/sdb2 526336 500118158 499591823 238.2G 83 Linux
// パーティションの削除 /dev/sdb2/
Command (m for help): d
Partition number (1,2, default 2): 2
Partition 2 has been deleted.
Command (m for help): n
Select (default p): p
// enter押すだけ
Partition number (2-4, default 2):
First sector (526336-500118191, default 526336):
// 75GB
Last sector, +/-sectors or +/-size{K,M,G,T,P} (526336-500118191, default 500118191): +75G
Created a new partition 2 of type 'Linux' and of size 75 GiB.
Partition #2 contains a ext4 signature.
Do you want to remove the signature? [Y]es/[N]o: y
The signature will be removed by a write command.
// 保存して終了
Command (m for help): w
$ sudo partprobe
// ファイルチェック
$ sudo e2fsck -f /dev/sdb2
// またfdiskで入る
$ sudo fdisk /dev/sdb
// 残りの要領でLVMのパーティションを作ります
Command (m for help): n
// 全部default
Select (default p):
Using default response p.
Partition number (3,4, default 3):
First sector (157812736-500118191, default 157812736):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (157812736-500118191, default 500118191):
Created a new partition 3 of type 'Linux' and of size 163.2 GiB.
// パーティション種別変更
Command (m for help): t
Partition number (1-3, default 3): 3
// 8e: Linux LVMを指定 (変更されている可能性もある為、「L」で確認を行ってください)
Hex code or alias (type L to list all): 8e
Changed type of partition 'Linux' to 'Linux LVM'.
Command (m for help):w
$ sudo partprobe
$ sudo fdisk -l /dev/sdb
Device Boot Start End Sectors Size Id Type
/dev/sdb1 * 2048 526335 524288 256M c W95 FAT32 (LBA)
/dev/sdb2 526336 157812735 157286400 75G 83 Linux
/dev/sdb3 157812736 500118191 342305456 163.2G 8e Linux LVM
いい感じですね。これをすべてのデバイスに行います。
各Raspberry piにSSDを接続後にパーティションが反映されているか確認します。
$ sudo fdisk -l /dev/sda
Device Boot Start End Sectors Size Id Type
/dev/sda1 * 2048 526335 524288 256M c W95 FAT32 (LBA)
/dev/sda2 526336 157812735 157286400 75G 83 Linux
/dev/sda3 157812736 500118191 342305456 163.2G 8e Linux LVM
続いてKubernetesを導入していきます。
Kubernetes導入
Kubernetesの情報が様々ありますが、比較的年代が古いものから新しいものが存在しています。
その結果記事毎に書いてある内容が違う?!という事が多々あり、よくわからなくなりました。
本記事ではGoogle検索より直近の物を選択し、使用させていただきました。
環境構築についてはこのままなので割愛します!
色々進めていったらそうはいかなくなったのでこちらに記載しておきます。
こちらのkubernetesの記事をもとに環境構築を行います。
多少階層が違うので読み替えて行います。
私の場合はスワップは最初から無効になっているみたいだったので省略しました。
バージョンは以下の通りです。
- kubernetes 1.25.1
// 更新
$ sudo apt update
$ sudo apt dist-upgrade
// raspberry pi osでは動かない
$ sudo apt install linux-modules-extra-raspi
// reboot
$ sudo reboot
//cmdline.txtの編集
$ sudo nano /boot/firmware/cmdline.txt
--- // 末尾に追記
cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1
---
// rebootで反映
$ sudo reboot
稀に「 missing optional cgroups: blkio」とかで起動に失敗するので、そういう時はcmdlineについて再度確認しておくといい感じになると思います。
ということで起動後に各nodeをつなげてあげてRook/Cephの環境を構築します。
ここでIPの固定もしてあげます。
参考:
$ cd /etc/netplan/
$ sudo cp 50-cloud-init.yaml 54-cloud-init.yaml
$ sudo mv 50-cloud-init.yaml default.yaml.disable
$ sudo nano 54-cloud-init.yaml
$ sudo netplan apply
network:
ethernets:
eth0:
addresses: [192.168.13.20/24]
routes:
- to: default
via: 192.168.13.1
nameservers:
addresses: [192.168.13.1]
optional: true
version: 2
Rook / Ceph構築
ここまで来たらあとは公式ページ通りに構築します!
前提は既にパーティションを区切って満たしている為、そのまま実行できると思います。
// 念のためコマンドを示しますが、今後変更の可能性もあるため、公式ページを見て構築する事をお勧めします。
$ git clone --single-branch --branch v1.11.2 https://github.com/rook/rook.git
$ cd rook/deploy/examples
// create operator...
$ kubectl create -f crds.yaml -f common.yaml -f operator.yaml
// check
$ kubectl -n rook-ceph get pod -w
NAME READY STATUS RESTARTS AGE
rook-ceph-operator-c489cccb5-5jvnb 0/1 ContainerCreating 0 52s
rook-ceph-operator-c489cccb5-5jvnb 1/1 Running
// OperatorがRunningになったら実行
$ kubectl create -f cluster.yaml
// しばらく待つ...
$ kubectl -n rook-ceph get pod
NAME READY STATUS RESTARTS AGE
csi-cephfsplugin-5rrxb 2/2 Running 0 6m
csi-cephfsplugin-7bnn7 2/2 Running 0 5m59s
csi-cephfsplugin-provisioner-9f7449fc9-27v2j 5/5 Running 0 5m59s
csi-cephfsplugin-provisioner-9f7449fc9-64lgr 5/5 Running 0 5m59s
csi-cephfsplugin-snvxl 2/2 Running 0 5m59s
csi-rbdplugin-b9hcx 2/2 Running 0 6m
csi-rbdplugin-bhspk 2/2 Running 0 6m
csi-rbdplugin-nrm8z 2/2 Running 0 6m
csi-rbdplugin-provisioner-649c57b978-6rjw9 5/5 Running 0 6m
csi-rbdplugin-provisioner-649c57b978-mvdpx 5/5 Running 0 6m
rook-ceph-crashcollector-nirilab02-59c867c6c-7fns8 1/1 Running 0 4m3s
rook-ceph-crashcollector-nirilab03-5c64555858-695sk 1/1 Running 0 3m29s
rook-ceph-crashcollector-nirilab04-cbf6f5ff7-5l59z 1/1 Running 0 2m50s
rook-ceph-mgr-a-6558d694f9-knhxs 3/3 Running 0 4m14s
rook-ceph-mgr-b-7ff7c7ff56-9wdgf 3/3 Running 0 4m13s
rook-ceph-mon-a-6dbb575c59-8nhmk 2/2 Running 0 6m4s
rook-ceph-mon-b-6747ccf947-9crz5 2/2 Running 0 5m34s
rook-ceph-mon-c-bf984fb49-m8sx4 2/2 Running 0 4m43s
rook-ceph-operator-c489cccb5-5jvnb 1/1 Running 0 10m
rook-ceph-osd-0-7bf9bfcb79-zdrqd 2/2 Running 0 3m32s
rook-ceph-osd-1-766bdddf4-m6vcg 2/2 Running 0 3m29s
rook-ceph-osd-2-5d49cf6d55-dtkdm 2/2 Running 0 2m50s
rook-ceph-osd-prepare-nirilab02-vzb7v 0/1 Completed 0 3m48s
rook-ceph-osd-prepare-nirilab03-2tp6h 0/1 Completed 0 3m48s
rook-ceph-osd-prepare-nirilab04-2d9wg 0/1 Completed 0 3m48s
ここまで簡単にいくとあの時間は何だったんだってなりますね...
ツールもインストールしてみます。
$ kubectl create -f deploy/examples/toolbox.yaml
$ kubectl -n rook-ceph rollout status deploy/rook-ceph-tools
$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- bash
// bashでstatus確認
bash-4.4$ ceph status
cluster:
id: c2b40178-2a5d-420d-899e-70666c854e5d
health: HEALTH_OK
services:
mon: 3 daemons, quorum a,b,c (age 19m)
mgr: b(active, since 16m), standbys: a
osd: 3 osds: 3 up (since 17m), 3 in (since 17m)
data:
pools: 1 pools, 1 pgs
objects: 2 objects, 705 KiB
usage: 67 MiB used, 490 GiB / 490 GiB avail
pgs: 1 active+clean
health: HEALTH_OKで終わりです!
metalbのインストール
オンプレ環境なのでLoadBalancerを使えるように
よくわからないですがこちらを参考に以下のコマンドをworkerで実行しました。
よくよくみたらページの下のほうにhelmがあったのでそちらを使えばよかった...
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.9/config/manifests/metallb-native.yaml
applyしてしまったものを元に戻すのは面倒そうなのでそのままconfigを書いて適用します。
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default-pool
namespace: metallb-system
spec:
addresses:
- 192.168.13.200-192.168.13.250
内容については公式ページのL2レイヤを参考にしました
レイヤーってなんぞや
L2、L7などなんか聞いたことあるけど一体どれを指しているのかと調べてみたらOSI参照モデルの七階層のことを指していたんですね。
なので今回はデータリンク層でのロードバランサの設定という事になります。
引っかかったところ
- Metallbを適当に設定後に案の定workerノードにアクセスできなくなる問題が発生して再インストールしたのですが、ワーカー以外のノードで
kubeadm reset
を実行してもクラスタにjoinできませんでした。- 原因は権限的な問題で完全にリセットできていないだけだったのでsudoを付け足してリセットした後にjoinしたら動きました。
helmのインストール
helmはパッケージをインストールする際に簡単に操作できるパッケージマネージャー的なものです。便利。
インストールは公式の自動インストーラーを使用しました。
$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
$ chmod 700 get_helm.sh
$ ./get_helm.sh
Ingress, Ingressコントローラーのインストール
クラスタ内サービスへの外部アクセスを管理するAPI オブジェクトとのこと。別途コントローラが必要なのでNGINXが元のIngressコントローラーをインストールします。
正直よくわからないのですが、L7(アプリケーション層)でのロードバランシングを提供するとのこと。
とりあえず以下のURL先の文書を呼んで構築してみます。
ここでhelmの出番という事で早速ドキュメント通りに以下のコマンドを実行。
helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx --create-namespace
ちょっと時間がかかるのでドキドキします。
インストール後に以下のようなメッセージが。
Release "ingress-nginx" does not exist. Installing it now.
NAME: ingress-nginx
LAST DEPLOYED: Mon Mar 20 08:22:57 2023
NAMESPACE: ingress-nginx
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
The ingress-nginx controller has been installed.
It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status by running 'kubectl --namespace ingress-nginx get services -o wide -w ingress-nginx-controller'
An example Ingress that makes use of the controller:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example
namespace: foo
spec:
ingressClassName: nginx
rules:
- host: www.example.com
http:
paths:
- pathType: Prefix
backend:
service:
name: exampleService
port:
number: 80
path: /
# This section is only required if TLS is to be enabled for the Ingress
tls:
- hosts:
- www.example.com
secretName: example-tls
If TLS is enabled for the Ingress, a Secret containing the certificate and key must also be provided:
apiVersion: v1
kind: Secret
metadata:
name: example-tls
namespace: foo
data:
tls.crt: <base64 encoded cert>
tls.key: <base64 encoded key>
type: kubernetes.io/tls
コンフィグについて出力してくれました。親切ですね。
起動中のpodの確認
以下のコマンドを実行して起動中のPodを確認してみます。
kubectl get pod -A
helmとngix-controllerを使ってサービスを公開して動作確認をしてみる
helmとnginx-controllerのそれぞれの機能については分かりました。
しかしそれぞれが組み合わさった時にどのようにインストールするのかよくわからなかったので実際に動かして確認してみます。
Giteaの公開
githubライクなgitソフトのGiteaを使用して構築してみます。
必要がない方は次のセクションへ。
ちなみになぜGitlabではなくGiteaかというと、別件のチーム開発でGithubではなく他のローカルGitホストを使うという話が持ち上がり、チームメンバーがGiteaに詳しかったからですね。
調べてみたところhelm用のpackageが存在するらしいのでそちらを利用します。
Installation with Helm (on Kubernetes)
に書いてある通り実行してもいいのですが、Ingress nginx-controllerとの組み合わせがよくわからなかったのでhelm pull gitea-charts/gitea
を使用してローカルに圧縮ファイルをダウンロードします。
その後解凍を行って内部ファイルを見てみます。
tar xvzf gitea-<任意のバージョン>
cd gitea/
tree -L 2 . (command not foundなど言われる場合はapt install treeなどしてあげてください)
.
├── Chart.lock
├── charts
│ ├── mariadb
│ ├── memcached
│ ├── mysql
│ └── postgresql
├── Chart.yaml
├── LICENSE
├── README.md
├── templates
│ ├── gitea
│ ├── _helpers.tpl
│ ├── NOTES.txt
│ └── tests
└── values.yaml
helmのファイル構造についてよくわからなかったため以下を参考にしました。
上記によると
templatesディレクトリ内にはDeploymentを使ってコンテナをデプロイしたり、ルーティングを行うためのServiceリソースを定義したり、外部からサービスにアクセスするためのIngressリソースを定義するための設定ファイルが一通り用意されている。これらはパラメータ設定ファイル(values.yaml)内で定義されているパラメータを参照するようになっており、シンプルなサービスであればこのvalues.yamlファイルを編集するだけで動作するChartが完成する。
「事実上の標準ツールとなっているKubernetes向けデプロイツール「Helm」入門」より引用
との事。という事でvalues.yamlを編集してingress-nginxと接続してみます。
今回httpsで接続してほしいので公式ページを参考に設定してみます。
Automated Certificate Management with cert-manager
セクションの所にLet's Encryptを用いた自動証明書発行のための記述があるので、その設定をそのまま記述します。
ついでにgiteaのadminに関する部分も編集します。
# Replace this with a production issuer once you've tested it
# (テストが終わったら本番環境用に置き換える by deepl)
なんかちょっと怖いこと書いてありますけど証明書はこのままです...!
結果ingressの部分はこんな感じに
ingress:
enabled: true
# className: nginx
className: nginx
annotations:
cert-manager.io/issuer: "letsencrypt-staging"
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: git.example.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- git.example.com
secretName: git.example.com
...
gitea:
admin:
#existingSecret: gitea-admin-secret
existingSecret:
username: tekisetuna_name
password: pasuwadodayo
email: "hoge@hoehoge"
git.example.comは適時自身のドメインに置き換えてください。またgiteaのadminの設定は適切に設定してあげてください。
あらかじめ書いておくと、この設定では動かないので次のセクションで更に変更します!!!!
起動
以下のコマンドで起動します。
helm install gitea .
エラー発生
エラーが発生して永遠に起動できなくなったのでログを解析してみたところ、PVCなるものが必要とのこと。
Podは一時的な物で消したらデータも消えるのですが、消えてほしくないデータが存在する場合にPVCなるものを利用するとのこと。
今回は一切設定をしていなかったので、いろいろ調べてみます。
調べたところ「Rook/Ceph」を利用した永続化が簡単そうでしたのでそちらを利用します。
(これは一か月前の私のコメントです。本来であればKubernetesの再構築がこの後に来てました。)
(既にみなさんはRook/Cephをインストールするための環境は整っています。)
Rook/Cephインストール(Old)
(このセクション前半は後からインストールする人(いるかわかりませんが)の為に残しておきます。)
(既にUbuntuをインストールしている方は次のセクションへ進んでください。)
公式ドキュメントよりインストールしてみます。
がエラーが発生。
Still connect to...が大量に発生。
どうやらmodprobeというモジュールが必要とのこと。
インストールします。が、
前提に戻っちゃいますが、「rbd」というkernelモジュールが必要です。
そして、Raspberry Pi OS 64bit(Beta)ではそもそも「rbd」モジュールがコンパイルされていません。
つまり、kernelのコンパイルをしなければなりません。
なんだってー!公式ページも確認したら同様に書いてありました。
ということで記事のKernelビルド編よりお借りしてコンパイルとインストールしました。が、ここで問題が発生。
カーネルをビルドしてもrbdカーネルが含まれていない...!
module rbd not found in directory /lib/modules/<ver>
この辺りを4日くらいずっと探していたのですが、同じ状況にあるものの具体的な解決策がなく諦め。
どうしようもないのでOSの問題もあるのかと思い、Ubuntu系で調べてみたらそれらしきコマンドがありました。
なのでOS毎変えてしまおうとMasterをUbuntuに変更。
以下環境構築を行います。
(ここで前述のUbuntuをインストールにつながるんですね。ちなみにRaspberry Pi OSでした。)
Rook/Cephインストール new
基本的に公式ドキュメント通りで動くと思います。
StorageClassの設定
インストール時にgitをCloneしたと思いますが、その中のフォルダにExampleがあるのでそちらを利用します。
rook$ cd deploy/examples/csi/rbd/
rook/deploy/examples/csi/rbd/$ kubectl apply -f storageclass.yaml
これでPVCを用意する準備が整いました。
Giteaのインストール(再)
という事で前述のとおり、再度設定します。
PVCの利用のために一部ファイルを変更しました。
# Persistanceのみ
persistence:
enabled: true
# existingClaim:
size: 10Gi
accessModes:
- ReadWriteOnce
labels: {}
annotations: {}
storageClass: rook-ceph-block
そして起動してみたらなぜかgitea-0が動かない...!
:~/Programs/gitea$ kubectl get pod
NAME READY STATUS RESTARTS AGE
gitea-0 0/1 Init:CrashLoopBackOff 6 (3m15s ago) 10m
gitea-memcached-6d5b5d4bfd-lfpfh 1/1 Running 0 10m
gitea-postgresql-0 1/1 Running 0 10m
:~/Programs/gitea$ kubectl logs gitea-0 -c configure-gitea
==== BEGIN GITEA CONFIGURATION ====
2023/04/04 05:03:07 cmd/migrate.go:33:runMigrate() [I] AppPath: /usr/local/bin/gitea
2023/04/04 05:03:07 cmd/migrate.go:34:runMigrate() [I] AppWorkPath: /data
2023/04/04 05:03:07 cmd/migrate.go:35:runMigrate() [I] Custom path: /data/gitea
2023/04/04 05:03:07 cmd/migrate.go:36:runMigrate() [I] Log path: /data/log
2023/04/04 05:03:07 cmd/migrate.go:37:runMigrate() [I] Configuration file: /data/gitea/conf/app.ini
2023/04/04 05:03:07 .../cli@v1.22.10/app.go:277:Run() [I] PING DATABASE postgres
2023/04/04 05:03:12 cmd/migrate.go:40:runMigrate() [F] Failed to initialize ORM engine: dial tcp: lookup gitea-postgresql.default.svc.cluster.local: Try again
Gitea migrate might fail due to database connection...This init-container will try again in a few seconds
なぜかpostgresqlにアクセスできないという問題が発生。
調べてみるとこんなものが。
あんまり関係なさそうですが、とりあえず試してみることに。
こちらに書いてあるとおりにvalues.yamlに追記します。
// ctrl+wで検索して追記
dnsConfig:
options:
- name: ndots
value: "1"
これで何回か実行したのですが治らず...
ダメ元でRaspberry Pi を再起動したら上手くいきました!!!どういうこと?!
:~/Programs/gitea$ kubectl get pod
NAME READY STATUS RESTARTS AGE
gitea-0 1/1 Running 0 37m
gitea-memcached-6d5b5d4bfd-lfpfh 1/1 Running 0 37m
gitea-postgresql-0 1/1 Running 0 37m
curl -H "Host: git.example.com" 192.168.13.20
これを打って、それっぽいのが出て来たので大丈夫そう。
sshを外部公開する (動作未確認)
※CloudFlareを使用する為、私の環境では動作確認を行っていません。あくまで参考という事で...
Ingress-nginxにssh接続できるように設定します。
Ingressのmanifestをコピー
helm get manifest ingress-nginx -n ingress-nginx > custom-values.yaml
以下の記事のように編集
既にingressはInstallされているので以下のコマンドを打って適用します。
helm upgrade ingress-nginx ingress-nginx/ingress-nginx -n ingress-nginx -f custom-values.yaml
なお、helmでインストールした際にrepoを直接指定している為ingress-nginxがないといわれます。
その際以下のコマンドでrepoを追加してあげました。
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
Upgraede後のREVISIONが2となっていたので無事適用されたと思います。
misskeyを構築する
DBの構築 : Postgresql
Misskeyではpostgresqlが使われていますが、これを踏襲する形で実行していきたいと思います。
折角Kubernetesを使用するのでDBもいい感じにしたい!
という事で色々調べてみるとPostgresql operatorなるものが存在するとのこと。
詳しい内容については割愛しますが、いい感じになりそうです。
HA クラスタとは
調べてみるとたびたび出てくる「HAクラスタ」ですが、どうやら上記であるとの事。
ただちょっと調べてみるとPGPool-Ⅱなるものも発見。
Misskey.ioはこちらを使っているみたい。
Operatorと組み合わせて運用できるのか調べてみたところ出来そうな予感。
という事でZalandoのPostgresql Operatorを使ってみます。と思っていたのですが、どうやらARM系は対応してなさそうな雰囲気があるためCloudNativePGを使用します。
(Issueなどを探したところRepositoryがARMなどのマルチプラットフォームに対応してない...らしい? Pull-reqなどもありますがCloseされているなど、現状公式でのサポートされているのを見つけられませんでした。2023年6月)
CloudNativePGとは
CloudNativePGとはKubernetes上でPostgresqlのクラスターを作成し、始まりから終わりまで全体のライフサイクルを管理してくれるKubernetesのOperatorです。
microk8sなどでも使用できます。
基本的な設定が可能な点とバックアップやリカバリが簡単に設定できます!
構築
こちらに書いてある通りに実行します。
versionは以下の通り。
- cloudnative-pg 1.19.1
なお、2023年8月現在新たなバージョンが存在します。
$ kubectl apply -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.19/releases/cnpg-1.19.1.yaml
$ kubectl get deploy -n cnpg-system cnpg-controller-manager
NAME READY UP-TO-DATE AVAILABLE AGE
cnpg-controller-manager 1/1 1 1 85m
以下のconfigについて、ドキュメントを元に記載していますが、不十分な可能性がありますので注意して使用してください。
# postgresql cluster
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: misskey-postgres-cluster
spec:
instances: 1 # 2以上での動作確認をしていますが、なぜか3つめ以上立ち上がらなかったり...
primaryUpdateStrategy: unsupervised
imageName: ghcr.io/cloudnative-pg/postgresql:15
# minSyncReplicas: 1
# maxSyncReplicas: 2 # なぜか機能しなかったためコメントアウト
storage:
pvcTemplate:
accessModes:
- ReadWriteOnce
storageClassName: rook-ceph-block # storageClassを自身の合ったものに変更する
resources:
requests:
storage: 70Gi
# ここでPostgresqlの設定
postgresql:
parameters:
max_connections: "128"
shared_buffers: "2GB"
effective_cache_size: "6GB"
maintenance_work_mem: "512MB"
checkpoint_completion_target: "0.7"
wal_buffers: "16MB"
default_statistics_target: "100"
random_page_cost: "1.1"
effective_io_concurrency: "300"
work_mem: "8MB"
min_wal_size: "1GB"
max_wal_size: "4GB"
max_worker_processes: "4"
max_parallel_workers_per_gather: "2"
max_parallel_workers: "4"
max_parallel_maintenance_workers: "2"
# superUserのパスワードとIDを設定 Userは固定されていたはずなのでconfigを確認してください
superuserSecret:
name: postgres-superuser-secret
# 起動時の設定
bootstrap:
initdb:
database: misskey # misskeyのコンフィグとの対応を確認する
owner: misskey # misskeyのコンフィグとの対応を確認する
secret:
name: postgres-local-user-secret # 上記ownerのパスワードについて。kubernetesの公式を参照
# backupの構築
backup:
barmanObjectStore:
destinationPath: "s3://misskey-backup/backup/" # ストレージ内の階層
endpointURL: "" # 各クラウドのエンドポイント
# S3のパスワード設定。これはKubernetesのsecretを利用している。
# https://kubernetes.io/ja/docs/concepts/configuration/secret/
s3Credentials:
accessKeyId:
name: creds
key: ACCESS_KEY_ID
secretAccessKey:
name: creds
key: ACCESS_SECRET_KEY
wal:
compression: gzip
retentionPolicy: "30d" # 30日で削除
# Prometheusを使用する場合に設定。CloudNativePGの設定ページに記載されているので参照。本サービスでは紹介しません。
monitoring:
enablePodMonitor: true
---
# backupサイクル設定 CloudNativePGのページを参照
# https://cloudnative-pg.io/documentation/1.16/backup_recovery/
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: backup-example
spec:
schedule: "0 0 0 * * *" # goの時間表記だった気がする...
backupOwnerReference: self
cluster:
name: misskey-postgres-cluster # ここはClusterの方のmetadata.nameを設定する
なお、念のため1つでの起動としています。
$ kubectl apply -f pg-cluster.yaml
$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/misskey-postgres-cluster-1 1/1 Running 0 5m53s
pod/misskey-postgres-cluster-2 1/1 Running 0 4m29s
pod/misskey-postgres-cluster-3 1/1 Running 0 3m14s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d23h
service/misskey-postgres-cluster-any ClusterIP 10.102.53.251 <none> 5432/TCP 6m27s
service/misskey-postgres-cluster-r ClusterIP 10.99.36.216 <none> 5432/TCP 6m26s
service/misskey-postgres-cluster-ro ClusterIP 10.110.27.45 <none> 5432/TCP 6m26s
service/misskey-postgres-cluster-rw ClusterIP 10.98.194.46 <none> 5432/TCP 6m26s
デプロイされたサービスのsuffixは以下の通り
- rw primaryからのread/write
- ro replicaからの読み取りonly
- r いずれかのインスタンスから読み取り
とりあえず動いたので設定をしていきます。
実は既にmisskey.niri.laにて動いているPostgreSQLからデータを持ってこようとして.sqlファイルを指定しようとしたのですが、どうやらReferenceを見てみたところCloudNativePGは外部のPostgresqlに直接アクセスしてPG_dumpを実行するようで、既に出力したsqlファイルは受け付けてくれないようです。
流石にDocker内にあるPostgresqlを公開してさらにアクセスするのはそれはそれで面倒なので、一旦起動してからデータを突っ込みます。
# -Uではpostgresを指定してあげないと権限エラーで色々言われました。-dは使用していたmisskeyのデータベース名を設定してください。
cat backup.sql | kubectl exec -i misskey-postgres-cluster-1 -- psql -U postgres -d misskey
PGPoolインストール
PGPool-2を使っているMisskey.ioが4/9あたりのメンテナンスで直接レプリケーションを行う方式に変更したとのことでPGPool-2の使用は中止しました。
Misskey本体
ついにMisskey本体側に入ります。
移行するために色々調べていたところ、つい最近KubernetesでMisskeyを起動していた方がいらっしゃいました。
こちらで使用していた設定を元に色々設定を変更します。
こちらの記事ではストレージにRook/Cephは使用していないため、こちらの記述を削除しつつRook/Cephに対応させます。
例の如くMisskeyの設定についてはGithubにアップロードしてありますので適時参照ください。
Misskeyに必要な設定ファイルですが、Dockerでの例で使用している.config内のdocker_example.yamlが必要です。
これをどうにかしてMisskeyのPVCにマウントします。
マウントにはConfigMapが有効そうでしたのでこちらを使います。
# 基本的に内容については同じなのでgithubを参照してください。
apiVersion: v1
data:
default.yml: |-
...
db:
host: <service-name>.<namespace-name>.svc.cluster.local
port: 5432
# Database name
db: misskey
# Auth
user: example-misskey-user # ここは先ほどImportしたデータベースのユーザーを指定してください
pass: example-misskey-pass # ここは先ほどImportしたデータベース名を指定してください
...
kind: ConfigMap
metadata:
name: misskey-default-config
適用します。
$ kubectl apply -f niril/config/config-misskey-default.yaml -n misskey
Kubernetes側が見つけられない為、ネームスペースは後述のyaml関連と同じものにしてください。
今回はnamespaceを設定せずに保存してしまいましたが、configのmetadataで設定するのが忘れなくていいと思います。
次にMisskey及びRedisの設定構築をします。
各Yamlについては以下のようになります。基本的にgithubに載せてあります。
apiVersion: v1
kind: Service
metadata:
name: web-service
namespace: misskey
spec:
# type: ClusterIP
selector:
app: web #-deployment ここで名前解決できずに失敗したので名前を変更する際は気を付けてください。
clusterIP: None
ports:
- name: http
port: 80
targetPort: 3000
---
kind: StatefulSet
apiVersion: apps/v1
metadata:
name: web-deployment
namespace: misskey
labels:
deploy: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: # ここは私のGithub上のmisskeyのパッケージを指定しました。
imagePullPolicy: Always
volumeMounts:
- mountPath: /misskey/files
name: misskey-files
- mountPath: /misskey/.config
name: misskey-default-conf-file
ports:
- containerPort: 3000
# Podの準備が出来ているか確認用
readinessProbe:
httpGet:
path: /
port: 3000
scheme: HTTP
volumes:
- name: misskey-default-conf-file
configMap:
name: misskey-default-config
volumeClaimTemplates:
- metadata:
name: misskey-files
namespace: misskey
spec:
accessModes:
- ReadWriteOnce
storageClassName: rook-ceph-block # 自身で設定した名前
resources:
requests:
storage: 5Gi
---
# ingress の設定
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
namespace: misskey
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 200m
spec:
ingressClassName: nginx
rules:
- host: misskey.example.com # 任意のホスト (configと同じにしたほうが良いと思われ)
http:
paths:
- pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
path: /
redisについては基本参考記事とほとんど同じの為、githubを参考にしてください。
なお、起動前に次セクションのRedis移行を確認した後に以下コマンドを実行してください。
この状態で起動を行います。
# 最初にredisを起動
$ kubectl apply -f redis.yaml
# misskeyを起動
$ kubectl apply -f stateful-web.yaml
Redis移行 (失敗)
Redisのデータを一切移行せずに起動したら通知が全部消えたので移行しようとしました。
ただどうしてもデータベースの適用が出来なかった点と、v13.11.xにアップデートすると強制的に通知が消えてしまうそうでしたので消し飛ばしました。
※通知およびアンテナのデータも消えるため、現在のサーバー人数やアンテナ使用人数を加味して決定してください。
原因
使用しているRedisをbitnamiのリポジトリに移行した後、backupで出てきたrbdを適用してあげると上手く復元することが出来ました。
ここでは省略させていただきます。
動作確認
コマンドを実行して確認します。
$ kubectl get pod -n misskey
NAME READY STATUS RESTARTS AGE
redis 1/1 Running 0 2d1h
wasabi-proxy-deployment-7f4f64c6d9-fw97t 1/1 Running 1 5d8h
wasabi-proxy-deployment-7f4f64c6d9-mzgd9 1/1 Running 4 (2d22h ago) 5d1h
web-deployment-0 1/1 Running 0 25h
web-deployment-1 1/1 Running 0 25h
上記コマンドは既に運用してから大分経つのでAGEが大きめです。最終的に上記のようになると思われます。
運用で発生した事象
データ消失
Rook/Cephは比較的不安定なのかわかりませんが、よくNode3が落ちます。データベースがNode3に入っていた為そのままミスキーインスタンスも落ちました。
この時焦ってKubeadm resetをしてしまったためにPVCのデータが消えてしまい、約半日分のデータが損失しました。
また、この事象が原因かはわかりませんがデータベースが規定数起動しなくなりました。(厳密に言うと2個までは起動し、それ以上は起動しなくなる。)
どれか一つでもDeleteすると二個までは起動するのでよくわかりません。とりあえず障害が発生した時は何とかなりそうです。
GTLが見れない
何とか安定したと思ったのですが、今度はGTLが見れなくなる不具合が発生しました。
ログを確認するとどうやらMisskey側で一定以上の時間が経過するとTimeoutしてCancelするとの事。
分散型SNSの情報交換場所であるDiscordグループ「鯖缶工場」にて質問させていただいた所、Misskeyのタイムアウト設定をしたらどうかという回答が得られました。
# dbセクション
extra:
# ssl: true
statement_timeout: 300000 # ここを追記
結果として時間はかかりますが無事に見ることが出来るようになりました。
最初はDBに何か不具合があるのでは...?と思いましたが、しばらく使用しているとGTLが通常の速度で見えるようになったので本当に不思議。。。
Misskey側のDB Replicationsで設定すると画像やお知らせなどのサービスが利用できなくなる。
タイトル通りですが、複数DBがある状態でReplicationsの設定を行うと画像などの投稿が出来なくなります。
厳密には画像のアップロード時にInternalErrorが発生します。ただし、ストレージ上にはアップロードできており、ドライブから選択する事でノートをすることが出来ます。
この現象はアップロード以外にお知らせでも発生しました。
原因の特定はできていませんが、予想としてはprimaryへのデータ書き込みに加えてレプリケーションされているDBへの同期が間に合っておらず、データが存在しないままMisskey側がレプリケーションDBにアクセスすることによって発生しているのではないかと考えています。
この辺りはよくわからないので現在情報待ちです。
有識者の方いらっしゃいませんか...!!!
結局PostgresqlはRaspberrypi Clusterから性能の良いデスクトップコンピュータに移行したお話
当初RaspberrypiのKubernetes上で動かしていましたが、性能的な面でMisskeyの運用は難しいと判断し、Intelのデスクトップコンピュータに移行しました。
ネットワーク経由での通信となりましたが、Raspberrypi上で動かすよりは大分快適になりました。
前述のCloudNativePGは引き続きmicrok8s上で動かしています。(リカバリが凄い簡単でした。)
負荷の増加で全てをIntel製のサーバーへ移行したお話
7月頃に発生したTwitter(現X)の障害に関連してmisskey.niri.laのユーザー数が大幅に増加しました。
この時RaspberryPi 5台体制(4Node)で運用していたのですが、Misskeyの負荷が大きくなり、全NodeでCPU使用率が100%を超えた為、既に構築していたmicrok8sへ移行しました。
microk8sでも同じ設定で動いたので移行が大変簡単でした。
注意
S3Wasabiについて
どうやら3/3からWasabiは新規アカウントでのPublicアクセスが使えなくなるそうです。
https://blog.noellabo.jp/entry/wasabi-public-access
ローカルテストについて
既にデータが存在する場合、configも併せて変更しないと連合の不整合が発生する可能性があります。
ローカルテストの際はProxyを設定して連合を行わない設定にしてください
おわりに
色々調べてみると一部前提を省いて書いている点があったり、そもそも情報が無かったりと色々翻弄されました。
本来であれば2日程度で終わらせるつもりだったのですが、安定するまでにひと月掛かりました。