Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ headlamp が 1 文字なのは、規則どおりだと head + lamp でも hubble
| [`k8up/`](apps/k8up/) | バックアップ(restic → Cloudflare R2)。**Talos でホストのスクリプトが使えなくなる**ぶんの受け皿 |
| [`infisical/`](apps/infisical/) [`infisical-operator/`](apps/infisical-operator/) [`infisical-push-bridge/`](apps/infisical-push-bridge/) | 秘密の配布。本体は `bootstrap/` にあり、ここには公開経路と operator と即時反映のブリッジ |
| [`headlamp/`](apps/headlamp/) | Kubernetes の Web UI(`h.doany.io`、Portainer の置き換え)。認証は Entra、権限は `bootstrap/apiserver/` |
| [`rybbit/`](apps/rybbit/) | Rybbit(`rb.doany.io`、アクセス解析。クッキー無し)+ ClickHouse + PostgreSQL + Redis。トドロクの離脱をファネル・ジャーニー・セッションリプレイで見る。**使い始めは [apps/rybbit/README.md](apps/rybbit/README.md)** |

**アプリの多くはこのリポジトリに無い。** 各アプリのリポジトリの `deploy/` に置いてあり、
`bootstrap/argocd/repos.yaml` の ApplicationSet が拾う(上の「仕組み」)。
Expand Down
1 change: 1 addition & 0 deletions apps/k8up/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ sudo k3s kubectl -n k8up logs -f job/k8up-retire
| --- | --- | --- |
| mattermost | postgres の `pg_dump` | [../mattermost/postgres.yaml](../mattermost/postgres.yaml) |
| forgejo | postgres の `pg_dump` + リポジトリの PVC(ファイル) | [../forgejo/postgres.yaml](../forgejo/postgres.yaml) |
| rybbit | postgres の `pg_dump` + ClickHouse の PVC(ファイル。パートは追記だけなので動いたまま取る) | [../rybbit/postgres.yaml](../rybbit/postgres.yaml) / [../rybbit/clickhouse.yaml](../rybbit/clickhouse.yaml) |
| erpnext | mariadb の `mariadb-dump` | 上流 chart の `worker.gunicorn.podAnnotations`([application.yaml](../erpnext/application.yaml)) |
| infisical | postgres の `pg_dump` | `bootstrap/infisical/helmchart.yaml` の `postgresql.primary.podAnnotations`(**SOPS 済みなので編集は `sops set`**) |
| lgtm / xool / worklog / denpa / blog / todoroku | SQLite を `serialize()` した 1 ファイル | 各アプリのリポジトリの `deploy/`(denpa と yosegaki は chart) |
Expand Down
23 changes: 23 additions & 0 deletions apps/k8up/schedules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,29 @@ spec:
keepMonthly: 6
tags: [k8up]
---
apiVersion: k8up.io/v1
kind: Schedule
metadata:
name: rybbit
namespace: rybbit
annotations:
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true
spec:
# 15:00〜17:45 の枠は埋まっているので、その前の 14:30(14:45 も既に使っている)
backup:
schedule: "30 14 * * *"
tags: [k8up]
podSecurityContext: {runAsUser: 0, runAsGroup: 0}
failedJobsHistoryLimit: 2
successfulJobsHistoryLimit: 1
prune:
schedule: "45 23 * * 0"
retention:
keepDaily: 7
keepWeekly: 4
keepMonthly: 6
tags: [k8up]

# リポジトリの健全性チェック。これだけ 1 本。
# ホストの `backup/k3s-backup` が `restic check --read-data-subset=5%` を回していたぶんで、
# Talos に移るとあれが無くなるので k8up 側に置いておく。
Expand Down
44 changes: 44 additions & 0 deletions apps/rybbit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Rybbit(rb.doany.io)

アクセス解析。Google Analytics の置き換えで、クッキーを使わない(同意バナーが要らない)。
トドロク(tk.doany.io)の LP から購入までのどこで離脱するかを、ファネル・ユーザージャーニー・
セッションリプレイで見るために置いた(2026-09-19)。AGPL-3.0、自分で動かす分には縛りはない。

上流: <https://github.com/rybbit-io/rybbit>。構成は上流の docker-compose.yml をそのまま k8s に写した。

| ファイル | 中身 |
| --- | --- |
| [rybbit.yaml](rybbit.yaml) | backend(Fastify、`/api`)と client(Next.js)。`ghcr.io/rybbit-io/rybbit-{backend,client}:vX.Y.Z` を 2 つ同じタグで |
| [clickhouse.yaml](clickhouse.yaml) | イベントの倉庫。上流の設定 4 本を ConfigMap に、メモリの上限だけこの箱向けに下げた(limit 3 Gi、1 クエリ 2 GB) |
| [postgres.yaml](postgres.yaml) | ユーザー・サイト・設定。forgejo と同じ 17 系、`pg_dump` を k8up に |
| [redis.yaml](redis.yaml) | セッション追跡のカウンタ。PVC は持たない |
| [rybbit-secrets.yaml](rybbit-secrets.yaml) | Infisical `/rybbit/rybbit` の 4 キー → Secret `rybbit` |
| [httproute.yaml](httproute.yaml) | `rb.doany.io`。`/api` は backend、ほかは client。上流の Caddyfile にある `/.well-known/oauth-*`(MCP 向け)は使わないので置かない |

バックアップは [../k8up/schedules.yaml](../k8up/schedules.yaml) の `rybbit`(ClickHouse の PVC をファイルとして、PostgreSQL は `pg_dump`)。

## 使い始め

1. Infisical(il.doany.io)に フォルダ `/rybbit/rybbit` を作り、`clickhouse-password` / `postgres-password` /
`redis-password` / `better-auth-secret` を英数字のランダム(32 文字くらい)で入れる。
入れるまで Pod は `CreateContainerConfigError` で待つ(壊れてはいない)
2. <https://rb.doany.io> を開き、最初のアカウントを作る(これが管理者)
3. **すぐに** [rybbit.yaml](rybbit.yaml) の `DISABLE_SIGNUP` と `NEXT_PUBLIC_DISABLE_SIGNUP` を `"true"` にして
PR を出す。false のままだと誰でもこのインスタンスにアカウントを作れる
4. 画面で「サイトを追加」→ `tk.doany.io`。出てくるサイト ID をトドロクの `PUBLIC_RYBBIT_SITE_ID`
(todoroku の `deploy/deployment.yaml`)に入れる。トドロクの root layout が
`https://rb.doany.io/api/script.js` を読み、購入完了と車両の登録をイベントで送る
5. Rybbit のファネルに「LP → ログイン → 台帳 → 車両の登録 → 料金 → 購入」を作る

## 気にしておくこと

- **訪問者の IP と国。** Cloudflare の後ろにいるので、backend に届く `X-Forwarded-For` の先頭が
訪問者の IP かどうかを最初に確かめる(Rybbit の「国」が Cloudflare のデータセンターの所在地ばかりなら
ずれている)。上流の Caddyfile は Cloudflare の範囲を `trusted_proxies` にして対処している。
Cilium Gateway(Envoy)は既定で XFF を追記するので、たぶんそのままで正しい
- **メモリ。** ClickHouse は limit 3 Gi。1 サイトなら実測 数百 MB で足りるはず。足りなければ
clickhouse.yaml の limit と `resource_limits.xml` は比率なので limit だけ上げればよい
- **版。** GitHub のリリースの `vX.Y.Z` を 2 つのイメージに同じタグで。Renovate が PR を出す。
ClickHouse・PostgreSQL・Redis は上流の compose が使っている版に合わせる
- **PSA は baseline。** privileged にした他の namespace と違い、hostPort も特権も要らない。
Talos に移ったときそのまま通る
167 changes: 167 additions & 0 deletions apps/rybbit/clickhouse.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Rybbit のイベントを溜める ClickHouse。上流の docker-compose.yml の設定(network / logging / resource_limits /
# user_settings)をそのまま ConfigMap にし、メモリの上限だけこの箱に合わせて下げた。
#
# 版は上流の compose に合わせる(26.3 系)。Renovate が PR を出す。
apiVersion: v1
kind: Service
metadata:
name: clickhouse
namespace: rybbit
spec:
selector:
app: clickhouse
ports:
- name: http
port: 8123
targetPort: 8123
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: clickhouse-data
namespace: rybbit
annotations:
# ファイルとして取る。MergeTree のパートは書き終わると変わらないディレクトリなので、
# 動いたままコピーしても壊れにくい(書きかけは tmp_ の下)。アクセス解析なので、
# 最悪でも直近の数時間が欠けるだけ。論理バックアップ(BACKUP 文)は S3 先が要るので使わない
k8up.io/backup: "true"
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path-retain
resources:
requests:
storage: 20Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: clickhouse-config
namespace: rybbit
data:
network.xml: |
<clickhouse>
<listen_host>0.0.0.0</listen_host>
</clickhouse>
logging_rules.xml: |
<clickhouse>
<logger>
<level>warning</level>
<console>true</console>
</logger>
<query_thread_log remove="remove"/>
<query_log remove="remove"/>
<query_views_log remove="remove"/>
<query_metric_log remove="remove"/>
<error_log remove="remove"/>
<opentelemetry_span_log remove="remove"/>
<text_log remove="remove"/>
<trace_log remove="remove"/>
<metric_log remove="remove"/>
<asynchronous_metric_log remove="remove"/>
<session_log remove="remove"/>
<part_log remove="remove"/>
<latency_log remove="remove"/>
<processors_profile_log remove="remove"/>
</clickhouse>
resource_limits.xml: |
<clickhouse>
<!-- 比率はコンテナの limit(cgroup)に対して効く。limits.memory 3Gi の 8 割 -->
<max_server_memory_usage_to_ram_ratio>0.80</max_server_memory_usage_to_ram_ratio>
<concurrent_threads_soft_limit_ratio_to_cores>1</concurrent_threads_soft_limit_ratio_to_cores>
<merges_mutations_memory_usage_to_ram_ratio>0.25</merges_mutations_memory_usage_to_ram_ratio>
</clickhouse>
# プロファイル(ユーザー側)の設定は users.d からしか読まない
user_settings.xml: |
<clickhouse>
<profiles>
<default>
<enable_json_type>1</enable_json_type>
<async_insert>1</async_insert>
<wait_for_async_insert>1</wait_for_async_insert>
<log_queries>0</log_queries>
<log_query_threads>0</log_query_threads>
<log_processors_profiles>0</log_processors_profiles>
<!-- 上流は 32 GB / 16 スレッド。ここは 1 クエリ 2 GB / 4 スレッドに落とす -->
<max_memory_usage>2000000000</max_memory_usage>
<max_threads>4</max_threads>
</default>
</profiles>
</clickhouse>
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: clickhouse
namespace: rybbit
annotations:
secrets.infisical.com/auto-reload: "true"
spec:
replicas: 1
# ReadWriteOnce なので古い Pod が消えてから新しい Pod が付ける
strategy:
type: Recreate
selector:
matchLabels:
app: clickhouse
template:
metadata:
labels:
app: clickhouse
spec:
# イメージは uid 101(clickhouse)。作りたての PV は root のものなので、グループで書けるようにする
securityContext:
fsGroup: 101
fsGroupChangePolicy: OnRootMismatch
containers:
- name: clickhouse
image: clickhouse/clickhouse-server:26.3.17.4
ports:
- containerPort: 8123
env:
- name: CLICKHOUSE_DB
value: analytics
- name: CLICKHOUSE_USER
value: default
- name: CLICKHOUSE_PASSWORD
valueFrom:
secretKeyRef:
name: rybbit
key: clickhouse-password
# backend が起動時に SQL で最小権限の rybbit_query ユーザーを作れるように
- name: CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT
value: "1"
readinessProbe:
httpGet:
path: /ping
port: 8123
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
memory: 3Gi
volumeMounts:
- name: data
mountPath: /var/lib/clickhouse
- name: config
mountPath: /etc/clickhouse-server/config.d/network.xml
subPath: network.xml
- name: config
mountPath: /etc/clickhouse-server/config.d/logging_rules.xml
subPath: logging_rules.xml
- name: config
mountPath: /etc/clickhouse-server/config.d/resource_limits.xml
subPath: resource_limits.xml
- name: config
mountPath: /etc/clickhouse-server/users.d/user_settings.xml
subPath: user_settings.xml
volumes:
- name: data
persistentVolumeClaim:
claimName: clickhouse-data
- name: config
configMap:
name: clickhouse-config
32 changes: 32 additions & 0 deletions apps/rybbit/httproute.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# 公開経路。証明書は Gateway(bootstrap/gateway/)のワイルドカードなので指定しない。
# 振り分けは /api が backend、ほかは client。上流の Caddyfile はほかに /.well-known/oauth-* と
# /.well-known/openid-configuration(MCP クライアント向けの OAuth の案内。Rybbit が OAuth サーバになる側)も
# backend に流すが、MCP は使わないので置かない。Gateway API の PathPrefix は / 区切りの要素単位で、
# `/.well-known/oauth-` のような途中までの前方一致は書けない(要るなら Exact でフルパスを並べる)。
# 計測のスクリプトは https://rb.doany.io/api/script.js、イベントの送り先は /api/track。
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: rybbit
namespace: rybbit
spec:
parentRefs:
- name: doany
namespace: kube-system
sectionName: https
hostnames: ["rb.doany.io"]
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: backend
port: 3001
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: client
port: 3002
10 changes: 10 additions & 0 deletions apps/rybbit/namespace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# PSA は baseline。hostPort も privileged も無い(ClickHouse は uid 101、postgres は起動時に root から落とすだけ)。
# 一覧と背景は docs/talos.md「PSA のラベル」(k3s では効かない。Talos のために書いておく)。
apiVersion: v1
kind: Namespace
metadata:
name: rybbit
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/audit: baseline
pod-security.kubernetes.io/warn: baseline
87 changes: 87 additions & 0 deletions apps/rybbit/postgres.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Rybbit のユーザー・サイト・設定の DB。forgejo/postgres.yaml と同じ形(17 系、pg_dump を k8up に取らせる)。
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: rybbit
spec:
selector:
app: postgres
ports:
- name: postgres
port: 5432
targetPort: 5432
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: rybbit
annotations:
# DB はファイルとして取らない。下の k8up.io/backupcommand の pg_dump で足りる。
k8up.io/backup: "false"
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path-retain
resources:
requests:
storage: 2Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: rybbit
spec:
replicas: 1
# ReadWriteOnce なので古い Pod が消えてから新しい Pod が付ける。RollingUpdate だと詰まる。
strategy:
type: Recreate
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
annotations:
k8up.io/backupcommand: /bin/sh -c 'pg_dump -U rybbit -d analytics --clean --if-exists'
k8up.io/file-extension: .sql
spec:
containers:
- name: postgres
image: postgres:17.11
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: analytics
- name: POSTGRES_USER
value: rybbit
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: rybbit
key: postgres-password
# initdb は空でないディレクトリを嫌うので、マウント先の下の段に置く
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
readinessProbe:
exec:
command: [pg_isready, -U, rybbit, -d, analytics]
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
cpu: 20m
memory: 128Mi
limits:
memory: 512Mi
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: postgres-data
Loading
Loading