MisskeyをDockerからおうちKubernetesに移行する


# 概要
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
    - https://www.amazon.co.jp/gp/product/B00PK27K5Q
- 電源ケーブル
    - https://www.amazon.co.jp/gp/product/B01GGKYKQM
- SSD
  - silicon power
  - KIOXIA
- クラスタケース
- LANケーブル https://www.amazon.co.jp/gp/product/B09MY5HVNM/
- SATA to USB 3
    - たくさんあるので変なものに注意！
    - 私はこれを買いました
        - https://www.amazon.co.jp/gp/product/B08X6NCHF5
- ラズパイケース
    - https://www.amazon.co.jp/gp/product/B07TJ15YL1/

~~※なおMicroSDは使用しません。MicroSD+SSDの場合一部手順が不要になるかも...?~~
➡私の環境では出来ませんでした。ただ、簡単にしかしていないのでもしかしたら出来るかもしれません。

# 環境構築 ～前提及びOS編～

まずRaspberrypiにOSをインストールしていきます。
例のごとくRaspberry Pi ImagerをWindows上にインストールします。

https://www.raspberrypi.com/software/

Raspberry piにOSをインストールしていくわけですが、ここで重要なのが**OSはUbuntuを選ぶ**です。

いろいろ流派がありますが、今回はRaspberry Pi OSではなくUbuntuです。
Ubuntu選択の理由としては後ほどインストールする「Rook / Ceph」と呼ばれるストレージ関連の構築を行うソフトウェアがRaspberry Piだとインストールできないからですね...

https://rook.io/

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のパーティション作成

以下の記事を参考にさせていただきました！

https://qiita.com/Y-Shikase/items/c6fffbbd11a3d039b380

ということでやっていきます。
前述のとおり、Raspberry pi にはmicroSDは刺さっていないので、ほかのRaspberry pi にSSDを差し込んでフォーマットします。

以下にコマンドを記載しておきます。
**ファイルが消える可能性があります！！！！**
**あくまで自己責任で実行してください**

出力については一部省略しています。

```terminal
// コマンドまとめ
$ 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
```

```terminal
// 接続デバイスを確認
$ 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を接続後にパーティションが反映されているか確認します。

```terminal
$ 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の記事をもとに環境構築を行います。

https://www.technicalife.net/raspberry-pi-kubernetes/

多少階層が違うので読み替えて行います。

私の場合はスワップは最初から無効になっているみたいだったので省略しました。

バージョンは以下の通りです。
- kubernetes 1.25.1

```terminal
// 更新
$ 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の固定もしてあげます。

参考：

https://qiita.com/Yui-00/items/9bb8a164b9b97fc52cc0

```terminal
$ 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
```

```54-cloud-init.yaml
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構築

ここまで来たらあとは公式ページ通りに構築します！

https://rook.io/docs/rook/v1.11/Getting-Started/quickstart/

前提は既にパーティションを区切って満たしている為、そのまま実行できると思います。

```terminal
// 念のためコマンドを示しますが、今後変更の可能性もあるため、公式ページを見て構築する事をお勧めします。
$ 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
// しばらく待つ...
```

```terminal

$ 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

```
ここまで簡単にいくとあの時間は何だったんだってなりますね...

ツールもインストールしてみます。

https://rook.io/docs/rook/v1.11/Troubleshooting/ceph-toolbox/

```terminal
$ 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を使えるように

https://metallb.universe.tf/installation/

よくわからないですがこちらを参考に以下のコマンドをworkerで実行しました。
よくよくみたらページの下のほうにhelmがあったのでそちらを使えばよかった...

```cmd
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.9/config/manifests/metallb-native.yaml
```

applyしてしまったものを元に戻すのは面倒そうなのでそのままconfigを書いて適用します。

```yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.13.200-192.168.13.250
```
内容については公式ページのL2レイヤを参考にしました

https://metallb.universe.tf/configuration/#bgp-configuration

### レイヤーってなんぞや
L2、L7などなんか聞いたことあるけど一体どれを指しているのかと調べてみたらOSI参照モデルの七階層のことを指していたんですね。

https://academy.gmocloud.com/qa/20170810/4591

なので今回はデータリンク層でのロードバランサの設定という事になります。

### 引っかかったところ
- Metallbを適当に設定後に案の定workerノードにアクセスできなくなる問題が発生して再インストールしたのですが、ワーカー以外のノードで``kubeadm reset``を実行してもクラスタにjoinできませんでした。
    - 原因は権限的な問題で完全にリセットできていないだけだったのでsudoを付け足してリセットした後にjoinしたら動きました。


## helmのインストール
helmはパッケージをインストールする際に簡単に操作できるパッケージマネージャー的なものです。便利。

インストールは公式の自動インストーラーを使用しました。

```cmd
$ 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コントローラーをインストールします。

https://kubernetes.io/docs/concepts/services-networking/ingress/

正直よくわからないのですが、L7(アプリケーション層)でのロードバランシングを提供するとのこと。

https://github.com/kubernetes/ingress-nginx

とりあえず以下のURL先の文書を呼んで構築してみます。

https://kubernetes.github.io/ingress-nginx/deploy/

ここでhelmの出番という事で早速ドキュメント通りに以下のコマンドを実行。

```cmd
helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace
```

ちょっと時間がかかるのでドキドキします。
インストール後に以下のようなメッセージが。
```terminal
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が存在するらしいのでそちらを利用します。

https://gitea.com/gitea/helm-chart/

``Installation with Helm (on Kubernetes)``に書いてある通り実行してもいいのですが、Ingress nginx-controllerとの組み合わせがよくわからなかったので``helm pull gitea-charts/gitea``を使用してローカルに圧縮ファイルをダウンロードします。
その後解凍を行って内部ファイルを見てみます。

```terminal
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のファイル構造についてよくわからなかったため以下を参考にしました。

https://knowledge.sakura.ad.jp/23603/

上記によると

>templatesディレクトリ内にはDeploymentを使ってコンテナをデプロイしたり、ルーティングを行うためのServiceリソースを定義したり、外部からサービスにアクセスするためのIngressリソースを定義するための設定ファイルが一通り用意されている。これらはパラメータ設定ファイル（values.yaml）内で定義されているパラメータを参照するようになっており、シンプルなサービスであればこのvalues.yamlファイルを編集するだけで動作するChartが完成する。

「事実上の標準ツールとなっているKubernetes向けデプロイツール「Helm」入門」より引用

との事。という事でvalues.yamlを編集してingress-nginxと接続してみます。

今回httpsで接続してほしいので公式ページを参考に設定してみます。

https://kubernetes.github.io/ingress-nginx/user-guide/tls/

``Automated Certificate Management with cert-manager``セクションの所にLet's Encryptを用いた自動証明書発行のための記述があるので、その設定をそのまま記述します。
ついでにgiteaのadminに関する部分も編集します。

```yaml
# Replace this with a production issuer once you've tested it
# (テストが終わったら本番環境用に置き換える by deepl)
```
なんかちょっと怖いこと書いてありますけど証明書はこのままです...!

結果ingressの部分はこんな感じに
```yaml
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の設定は適切に設定してあげてください。

### あらかじめ書いておくと、この設定では動かないので次のセクションで更に変更します！！！！


## 起動

以下のコマンドで起動します。

```terminal
helm install gitea .
```

### エラー発生
エラーが発生して永遠に起動できなくなったのでログを解析してみたところ、PVCなるものが必要とのこと。

Podは一時的な物で消したらデータも消えるのですが、消えてほしくないデータが存在する場合にPVCなるものを利用するとのこと。

今回は一切設定をしていなかったので、いろいろ調べてみます。
調べたところ「Rook/Ceph」を利用した永続化が簡単そうでしたのでそちらを利用します。
(これは一か月前の私のコメントです。本来であればKubernetesの再構築がこの後に来てました。)
(既にみなさんはRook/Cephをインストールするための環境は整っています。)

## Rook/Cephインストール(Old)
(このセクション前半は後からインストールする人(いるかわかりませんが)の為に残しておきます。)
(既にUbuntuをインストールしている方は次のセクションへ進んでください。)

公式ドキュメントよりインストールしてみます。

がエラーが発生。

Still connect to...が大量に発生。

どうやらmodprobeというモジュールが必要とのこと。
インストールします。が、

https://qiita.com/Y-Shikase/items/c6fffbbd11a3d039b380#ceph%E3%81%AE%E6%A7%8B%E7%AF%89

>前提に戻っちゃいますが、「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があるのでそちらを利用します。

```terminal
rook$ cd deploy/examples/csi/rbd/

rook/deploy/examples/csi/rbd/$ kubectl apply -f storageclass.yaml
```

これでPVCを用意する準備が整いました。


# Giteaのインストール(再)
という事で前述のとおり、再度設定します。
PVCの利用のために一部ファイルを変更しました。

```value.yaml
# Persistanceのみ
persistence:
  enabled: true
#  existingClaim:
  size: 10Gi
  accessModes:
    - ReadWriteOnce
  labels: {}
  annotations: {}
  storageClass: rook-ceph-block
```

そして起動してみたらなぜかgitea-0が動かない...!

```terminal
:~/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にアクセスできないという問題が発生。

調べてみるとこんなものが。

https://gitea.com/gitea/helm-chart/issues/318#issuecomment-701894

あんまり関係なさそうですが、とりあえず試してみることに。

https://gitea.com/gitea/helm-chart/issues/318#issuecomment-701894

こちらに書いてあるとおりにvalues.yamlに追記します。

```values.yaml
// ctrl+wで検索して追記
dnsConfig:
  options:
    - name: ndots
      value: "1"
```

これで何回か実行したのですが治らず...

ダメ元でRaspberry Pi を再起動したら上手くいきました！！！どういうこと？！

```terminal
:~/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
```

```terminal
curl -H "Host: git.example.com" 192.168.13.20
```
これを打って、それっぽいのが出て来たので大丈夫そう。

### sshを外部公開する (動作未確認)

※CloudFlareを使用する為、私の環境では動作確認を行っていません。あくまで参考という事で...

Ingress-nginxにssh接続できるように設定します。

Ingressのmanifestをコピー
```terminal
helm get manifest  ingress-nginx  -n ingress-nginx > custom-values.yaml 
```

以下の記事のように編集

https://zenn.dev/tingtt/articles/ee239a40aaca7f#350%E8%A1%8C%E7%9B%AE%E3%81%82%E3%81%9F%E3%82%8A%E3%81%AEservice%E3%81%AEingress-nginx-controller

既にingressはInstallされているので以下のコマンドを打って適用します。

```terminal
helm upgrade ingress-nginx ingress-nginx/ingress-nginx -n ingress-nginx -f custom-values.yaml
```

なお、helmでインストールした際にrepoを直接指定している為ingress-nginxがないといわれます。
その際以下のコマンドでrepoを追加してあげました。

```terminal
helm repo add ingress-nginx  https://kubernetes.github.io/ingress-nginx
```

Upgraede後のREVISIONが2となっていたので無事適用されたと思います。

# misskeyを構築する
## DBの構築 : Postgresql
Misskeyではpostgresqlが使われていますが、これを踏襲する形で実行していきたいと思います。

折角Kubernetesを使用するのでDBもいい感じにしたい！
という事で色々調べてみるとPostgresql operatorなるものが存在するとのこと。

詳しい内容については割愛しますが、いい感じになりそうです。

https://atmarkit.itmedia.co.jp/ait/articles/2208/10/news007.html

### HA クラスタとは
https://jpn.nec.com/clusterpro/blog/20170801.html#:~:text=%E3%81%A4%E3%81%BE%E3%82%8A%E3%80%81HA%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%BF%E3%83%BC%E3%81%A8%E3%81%AF,%E3%81%AE%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%81%AB%E5%BC%95%E3%81%8D%E7%B6%99%E3%81%8E%E3%81%BE%E3%81%99%E3%80%82

調べてみるとたびたび出てくる「HAクラスタ」ですが、どうやら上記であるとの事。

ただちょっと調べてみるとPGPool-Ⅱなるものも発見。

Misskey.ioはこちらを使っているみたい。

https://misskey.io/notes/9d75mz729b

https://blog.arkjp.net/2020/12/%E8%B2%A0%E8%8D%B7%E5%88%86%E6%95%A3%E3%81%A8%E5%86%97%E9%95%B7%E5%8C%96%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6/

Operatorと組み合わせて運用できるのか調べてみたところ出来そうな予感。

https://www.pgpool.net/docs/pgpool-II-4.2.3/ja/html/example-kubernetes.html

という事で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月現在新たなバージョンが存在します。

https://cloudnative-pg.io/documentation/current/installation_upgrade/

```terminal
$ kubectl apply -f \
  https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.19/releases/cnpg-1.19.1.yaml
```

```terminal
$ 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について、ドキュメントを元に記載していますが、不十分な可能性がありますので注意して使用してください。

```pg-cluster.yaml
# 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つでの起動としています。

```terminal
$ 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   いずれかのインスタンスから読み取り

https://cloudnative-pg.io/documentation/1.18/applications/

とりあえず動いたので設定をしていきます。

実は既にmisskey.niri.laにて動いているPostgreSQLからデータを持ってこようとして.sqlファイルを指定しようとしたのですが、どうやらReferenceを見てみたところCloudNativePGは外部のPostgresqlに直接アクセスしてPG_dumpを実行するようで、既に出力したsqlファイルは受け付けてくれないようです。
流石にDocker内にあるPostgresqlを公開してさらにアクセスするのはそれはそれで面倒なので、一旦起動してからデータを突っ込みます。


```terminal
# -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の使用は中止しました。**

https://misskey.niri.la/notes/9dcj8zayic

https://misskey.niri.la/notes/9dcjmyn6s8

## Misskey本体
ついにMisskey本体側に入ります。

移行するために色々調べていたところ、つい最近KubernetesでMisskeyを起動していた方がいらっしゃいました。

https://qiita.com/commojun/items/b9cb1ac7fb1b6c80d70a

こちらで使用していた設定を元に色々設定を変更します。
こちらの記事ではストレージにRook/Cephは使用していないため、こちらの記述を削除しつつRook/Cephに対応させます。

例の如くMisskeyの設定についてはGithubにアップロードしてありますので適時参照ください。

https://github.com/niwaniwa/misskey-k8s-ni_rilana

Misskeyに必要な設定ファイルですが、Dockerでの例で使用している.config内のdocker_example.yamlが必要です。
これをどうにかしてMisskeyのPVCにマウントします。
マウントにはConfigMapが有効そうでしたのでこちらを使います。

```config-misskey-default.yaml
# 基本的に内容については同じなので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
```

https://github.com/niwaniwa/misskey-k8s-ni_rilana/blob/main/nirila/config/config-misskey-default.yaml

適用します。

```terminal
$　kubectl apply -f niril/config/config-misskey-default.yaml -n misskey
```

Kubernetes側が見つけられない為、ネームスペースは後述のyaml関連と同じものにしてください。
今回はnamespaceを設定せずに保存してしまいましたが、configのmetadataで設定するのが忘れなくていいと思います。

次にMisskey及びRedisの設定構築をします。
各Yamlについては以下のようになります。基本的にgithubに載せてあります。

```stateful-web.yaml
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移行を確認した後に以下コマンドを実行してください。

この状態で起動を行います。

```terminal
# 最初にredisを起動
$ kubectl apply -f redis.yaml
# misskeyを起動
$ kubectl apply -f stateful-web.yaml
```

## Redis移行 (失敗)

Redisのデータを一切移行せずに起動したら通知が全部消えたので移行しようとしました。
ただどうしてもデータベースの適用が出来なかった点と、v13.11.xにアップデートすると強制的に通知が消えてしまうそうでしたので消し飛ばしました。

※通知およびアンテナのデータも消えるため、現在のサーバー人数やアンテナ使用人数を加味して決定してください。

### 原因
使用しているRedisをbitnamiのリポジトリに移行した後、backupで出てきたrbdを適用してあげると上手く復元することが出来ました。

ここでは省略させていただきます。


# 動作確認

コマンドを実行して確認します。
```terminal
$ 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のタイムアウト設定をしたらどうかという回答が得られました。

```config-misskey-default.yaml
# 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日程度で終わらせるつもりだったのですが、安定するまでにひと月掛かりました。