k8sでmisskeyインスタンス構築してみた
Table of Contents
おうちk8sでmisskeyインスタンスを建てて、分散型SNSを体感した。
はじめに
分散型SNSといえば、ActivityPubのMastodonやMisskeyが有名だ。最近だとBlueskyがTwitterの後継として注目を集めている。
ということで今回、Misskeyをおうちk8sクラスタに導入して、分散型SNSの仕組みについて学んでみることにした。
Misskey HubにはKubernetes/TrueNASを使ったMisskey構築というk8sでのmisskey構築方法を解説する記事が公開されているが、
- バージョンがちょっと古い
- 積極的にメンテされている様子ではない
- そもそも素の状態で動かなかった
ので、misskeyのリポジトリのdocker-compose.ymlの内容と、こちらのQiitaの記事を参考に、マニフェストから手動でmisskeyを構築した。
因みに、上記記事ではPi3Bでギリギリ動作すると書いてあったけれど、手元のPi4B(8GB)x3なら余裕をもって動作している様子だった。
k8sからのNFSサーバ利用については以前の記事で扱っているので、ここでは詳しく解説しない。
構築手順
misskeyコンテナは大きく分けてweb・DB(Postgre)・NoSQL(Redis)からなる。このうち、PostgreとRedisは永続化の必要があるのでPVを用意する。
また、アップロードしたデータを格納するためのPVも用意する必要がある。
NAS側での前準備
- /k8s/misskey/db
- /k8s/misskey/config
- /k8s/misskey/files
- /k8s/misskey/redis
上記ディレクトリを作成しておく。
/k8s/misskey/config
ディレクトリにはこのyamlファイルをdefault.yml
という名前でコピーして、URLやPostgre、Redisの設定をした後保存しておく。urlの末尾は/
じゃないとエラーになる(1敗)。DBおよびRedisのhost名はmy-svc.my-namespace.svc.cluster.local
形式1で指定する。
また、この後の作業でRedisのコンテナ起動時、CrashLoopbackOff
になってしまったので、
NFS側のsquashオプションでno_root_squash
を設定しておいた。
参考:https://github.com/kubernetes/kubernetes/issues/54601
Redis
以下をapply
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: misskey
spec:
selector:
app: redis
ports:
- name: http
port: 6379
---
kind: PersistentVolume
apiVersion: v1
metadata:
name: pv-misskey-redis
namespace: misskey
spec:
storageClassName: misskey-redis
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
nfs:
server: {NFSのIP}
path: /k8s/misskey/redis
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-misskey-redis
namespace: misskey
spec:
storageClassName: misskey-redis
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
name: redis
namespace: misskey
labels:
app: redis
spec:
restartPolicy: Always
containers:
- name: redis
image: redis:7
volumeMounts:
- mountPath: /data
name: redis-volume
resources:
limits:
memory: "100Mi"
cpu: "250m"
ports:
- containerPort: 6379
volumes:
- name: redis-volume
persistentVolumeClaim:
claimName: pvc-misskey-redis
Postgre
postgreのユーザ名・パスワード・DB名は環境変数から流し込めるようだったので、 別途secretリソースを作成して反映させる。
apiVersion: v1
kind: Secret
metadata:
name: postgre-secret
namespace: misskey
data:
POSTGRES_PASSWORD: {base64でエンコードしたパスワード}
type: Opaque
stringData:
POSTGRES_USER: {ユーザ名}
POSTGRES_DB: misskey
---
apiVersion: v1
kind: Service
metadata:
name: db
namespace: misskey
spec:
selector:
app: db
ports:
- name: http
port: 5432
---
kind: PersistentVolume
apiVersion: v1
metadata:
name: pv-misskey-db
namespace: misskey
spec:
storageClassName: misskey-db
capacity:
storage: 4Gi
accessModes:
- ReadWriteOnce
nfs:
server: {NFSのIP}
path: /k8s/misskey/db
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-misskey-db
namespace: misskey
spec:
storageClassName: misskey-db
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 4Gi
---
apiVersion: v1
kind: Pod
metadata:
name: db
namespace: misskey
labels:
app: db
spec:
restartPolicy: Always
containers:
- name: psql
image: postgres:15
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: db-volume
envFrom:
- secretRef:
name: postgre-secret
resources:
limits:
memory: "800Mi"
cpu: "1"
ports:
- containerPort: 5432
volumes:
- name: db-volume
persistentVolumeClaim:
claimName: pvc-misskey-db
Web
コンフィグファイル用のpv/pvcとアップロードしたファイル用のpv/pvcを作成する。
kind: PersistentVolume
apiVersion: v1
metadata:
name: pv-misskey-config
namespace: misskey
spec:
storageClassName: misskey-config
capacity:
storage: 5Mi
accessModes:
- ReadWriteOnce
nfs:
server: {NFSのIP}
path: /k8s/misskey/config
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-misskey-config
namespace: misskey
spec:
storageClassName: misskey-config
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Mi
---
kind: PersistentVolume
apiVersion: v1
metadata:
name: pv-misskey-files
namespace: misskey
spec:
storageClassName: misskey-files
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
nfs:
server: {NFSのIP}
path: /k8s/misskey/files
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-misskey-files
namespace: misskey
spec:
storageClassName: misskey-files
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
続いて、サービスのデプロイ
apiVersion: v1
kind: Service
metadata:
name: web
namespace: misskey
spec:
type: NodePort
selector:
app: web
ports:
- name: http
protocol: TCP
port: 3000
targetPort: 3000
nodePort: 30100
externalTrafficPolicy: Local
selector:
app: web
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: web-deployment
namespace: misskey
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: "50%"
maxSurge: "50%"
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: misskey/misskey:latest
volumeMounts:
- mountPath: /misskey/files
name: misskey-files
- mountPath: /misskey/.config
name: misskey-config
ports:
- containerPort: 3000
resources:
limits:
cpu: "2"
command: ["pnpm", "run", "migrateandstart"]
volumes:
- name: misskey-files
persistentVolumeClaim:
claimName: pvc-misskey-files
- name: misskey-config
persistentVolumeClaim:
claimName: pvc-misskey-config
DBのマイグレーションも含めてそれなりに時間がかるので、
kubectl logs -n misskey web-deployment... -f
とかで確認しておくと良い。
あとは適当なサービスでpodを公開する。
Ingressを利用して公開する場合は以下のように設定する。
...
- host: {misskey host name}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 3000
...
使ってみる
デプロイ完了後、default.yml
で設定したurlにアクセスすれば、以下のような初回ログイン画面が出てくる。
ユーザ名とパスワードを入力した後、その他プロフィール情報を入力するよう求められる(後で設定することも可能)。
プロフィール画像がエラーでアップロードできなかったので、以下のエラー内容で検索したところ、NASにsshログインしてchown -hR 991.991 ./files
してやる必要があることがわかった。
INFO * [drive register] {"size":5915,"md5":"74c5e9a818e9be5ceb0c85b6899b915c","type":{"mime":"image/png","ext":"png"},"width":128,"height":128,"blurhash":"eEK|[3|c6h]92E|c,Y$5$5A=2EFIWpJl$56hWp$5,YFI2E$5FIfQJl","sensitive":false,"porn":false,"warnings":[]}
INFO * [drive register] web image not created (original satisfies webpublic)
Error: EACCES: permission denied, copyfile '/tmp/tmp-117-dsjY37tT1s3S' -> '/misskey/files/aaf99761-2544-4eb8-a589-33f31a71e4db'
at Module.copyFileSync (node:fs:2894:3)
at InternalStorageService.saveFromPath (file:///misskey/packages/backend/built/core/InternalStorageService.js:49:12)
at DriveService.save (file:///misskey/packages/backend/built/core/DriveService.js:137:53)
at async DriveService.addFile (file:///misskey/packages/backend/built/core/DriveService.js:457:20)
at async file:///misskey/packages/backend/built/server/api/endpoints/drive/files/create.js:124:35
at async ApiCallService.call (file:///misskey/packages/backend/built/server/api/ApiCallService.js:285:16) {
errno: -13,
syscall: 'copyfile',
code: 'EACCES',
path: '/tmp/tmp-117-dsjY37tT1s3S',
dest: '/misskey/files/aaf99761-2544-4eb8-a589-33f31a71e4db'
[v13] Drive upload failed (permission denied) · Issue #9564 · misskey-dev/misskey
↑上記issueを参考にした
プロフィール情報を入力し終えると、すぐに呟ける状態になった(misskey的にはノートを作成出来るようになった。だろうか)
初期状態だと、どのmisskeyインスタンスとも通信していないので、 たとえグローバルタイムラインを開こうと、閑古鳥が鳴いているばかりである。
試しに誰かフォローしようにも、検索欄からユーザ名を入れても何も出てこない。 どうやらリモートのユーザをフォローするには、照会からユーザ名を直接入力してフォローする必要があることがようだ。
指定の仕方は@ユーザ名@サーバ名
。たとえばmisskey.io
の4nm1tsu
をフォローするなら@4nm1tsu@misskey.io
と言った具合。
UIがシンプルすぎて、一般ユーザには敷居が高そうだなというか、あくまで現状はGeek向けのSNSなんだという印象。
因みに、互いに通信状態にあるインスタンスをmisskeyでは連合というらしい。
試しにmisskey.io
のユーザをフォローしたらmisskey.io
が連合になった。
ここらへんの仕様は、はじめからユーザ数の多いインスタンスなら気にならないだろうけど、マイナーなインスタンスで初めてmisskeyを使い始める人は混乱しそうだなと感じた。
それと、リモートのユーザに関する情報は正確ではない。フォロー・フォロワーは同じインスタンス内で当該リモートユーザとフォロー・フォロワー関係にある人の分しか反映されないし、ノートはリモートユーザを観測した時点での直近のノートおよび観測以降のノートしか見れない。
基本的にリモートユーザの正確な情報を見るには、リモートユーザが所属しているインスタンスを直接見に行く必要があるが、ローカルの検索機能でリモートユーザのノートの詳細URLを入力すると、ローカル上にもそのノートが反映される。リモートユーザの過去のノートをローカル上でRenoteしたい際にはこの手順を踏む必要がある。
1つMisskeyを使ってみて嬉しい誤算だったのが、ローカルに追加したカスタム絵文字は、フォローしているリモートのユーザに対してもリアクションとして使えるということだ。misskey.io
等大手インスタンスでは、カスタム絵文字の追加をするためにはPatreonで月額課金が必要なので、これだけでも自鯖でmisskeyを運用するメリットがあると感じる。
他には、
- 新規登録時に招待コードが必要になる設定
- プッシュ通知が出来るように、ServiceWorkerの設定
npx web-push generate-vapid-keys
を実行して得られるPublik keyとPrivate keyをを貼り付けるだけ
- botプロテクション設定から、Cloudflare Turnstileの有効化
- ここからサインアップしてサイトを追加後、キーをmisskey側で入力する
- メールサーバの設定
- 検索機能を使えるようにベースロールの修正
- 見た目関連の設定
- デフォルトテーマ
- テーマカラー
- favicon設定
等のサーバ設定をしておいた。
PWA対応なので、iOSネイティブアプリのようにプッシュ通知が来て便利
リレー設定
一通り快適に使えるようにはなったが、いかんせんGTL(グローバルタイムライン)が静かすぎて悲しい。
解決方法を調べてみると、どうやらActivity Pubを実装するSNSではpub-relayによる連合をすることで、リレーサーバに登録している他のサーバのアクティビティを受信出来るようになるらしいということがわかった。 早速下記のページから適当に3つ程リレーサービスを選んで、自鯖に登録した。
Fediverseリレーサーバー一覧2023(for Mastodon / Misskey / Pleroma)
1時間程待つとステータスが承認済になり、またたく間にTL上に全てを目で追うのが難しい位のノートが流れ込んできた。
リレーサーバに登録した途端に一気にSNSらしくなった感じがする。これ、相当ストレージ容量食いそうだな。。
↑気休め程度に「コントロールパネル」→「全般」から「リモートのファイルをキャッシュする」を無効化しておいた。
おわりに
最後に、misskeyでインターネット上の他のインスタンスとやりとりすれば、そのインスタンス上に自分のデータが残ることになる。
いくらローカルで管理しているとはいえ、アカウントを削除すれば綺麗さっぱりインターネット上からデータが消えるというわけではないということを肝に銘じておく必要がある。
基本的な注意事項 | Misskey Hub
↑このページにあるように、
- ノートの公開範囲をローカルに限定しようと、それを他のインスタンスが同じように非公開として扱ってくれるか
- ノートを削除しても、他のインスタンスでそのノートを削除してくれるかどうか
は保証してくれないようなので、情報の取り扱いには気をつけようと思った。