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
65 changes: 64 additions & 1 deletion apps/k8up/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,69 @@ FAILED を出している**ので、そこで気づけなかった、という
[k8up] OK ✅ (32 系統すべて 25 時間以内)
```

## やめたアプリの残り(2026-09-19)

通知は**過去 8 日に出てきた (host, path) を「あるべきもの」**とみなす。namespace ごと
消したアプリや名前を変えたアプリは、その 8 日のあいだ毎日「25 時間以上更新されていない」と
鳴り続ける(2026-09-19 に noren 3 本と shadai 3 本で起きた。noren は 09-12 に撤去、
shadai は 09-16 に todoroku に改名)。窓が閉じれば黙るが、5 日も FAILED を眺めるものではない。

やめ方は 2 つ。どちらも `restic` を repo に向けて 1 回打つだけ(下の Job)。

- **消してよいもの**(noren): `restic forget --host noren`。スナップショットが消え、
中身は日曜の prune で解放される
- **残しておきたいもの**(shadai = トドロクの改名前の履歴):
`restic tag --host shadai --add retired`。通知は `retired` の付いたスナップショットを
見ない([notify.yaml](notify.yaml))。**中身はそのまま**で、`restic restore` も
`snapshots` も今までどおり効く。保持は schedule の prune(`--keep-daily` 等)に
そのまま従うので、時間が経てば自然に減る

打ち方。k8up の CronJob と同じイメージ・同じ秘密(`k8up-global`)で、Job を 1 つ流す。
**バックアップの時間帯(14:45〜18:30 UTC、日曜は 23:15 まで)を外す** ── forget と tag は
repo をロックする。gitops には置かない(Argo CD が毎回流してしまう)ので、その場で
`kubectl create -f -` に食わせる。1 時間で自分で消える(`ttlSecondsAfterFinished`)。

```sh
sudo k3s kubectl -n k8up create -f - <<'JOB'
apiVersion: batch/v1
kind: Job
metadata:
name: k8up-retire
spec:
backoffLimit: 0
ttlSecondsAfterFinished: 3600
template:
spec:
restartPolicy: Never
containers:
- name: restic
image: ghcr.io/k8up-io/k8up:v2.16.0
command:
- sh
- -ec
- |
export RESTIC_REPOSITORY="s3:${ENDPOINT}/${BUCKET}"
# forget は「方針」か ID を要る(--host だけだと何も消さずに落ちる)。ID を並べる。
# 2 つの操作は独立。消すものが無くても(2 回目など)tag のほうまで進む
ids=$(restic snapshots --host noren --compact | awk '/^[0-9a-f]{8} /{print $1}')
[ -n "$ids" ] && restic forget $ids
restic tag --host shadai --add retired
restic snapshots --compact --host noren --host shadai
env:
- name: ENDPOINT
valueFrom: {secretKeyRef: {name: k8up-global, key: endpoint}}
- name: BUCKET
valueFrom: {secretKeyRef: {name: k8up-global, key: bucket}}
- name: AWS_ACCESS_KEY_ID
valueFrom: {secretKeyRef: {name: k8up-global, key: accessKeyId}}
- name: AWS_SECRET_ACCESS_KEY
valueFrom: {secretKeyRef: {name: k8up-global, key: secretAccessKey}}
- name: RESTIC_PASSWORD
valueFrom: {secretKeyRef: {name: k8up-global, key: repoPassword}}
JOB
sudo k3s kubectl -n k8up logs -f job/k8up-retire
```

## 何をどう取っているか

`Schedule` は [schedules.yaml](schedules.yaml) に 14 本まとめてある(時刻と決まりごともあちら)。
Expand All @@ -133,7 +196,7 @@ FAILED を出している**ので、そこで気づけなかった、という
| forgejo | postgres の `pg_dump` + リポジトリの PVC(ファイル) | [../forgejo/postgres.yaml](../forgejo/postgres.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 / noren | SQLite を `serialize()` した 1 ファイル | 各アプリのリポジトリの `deploy/`(denpa と yosegaki は chart) |
| lgtm / xool / worklog / denpa / blog / todoroku | SQLite を `serialize()` した 1 ファイル | 各アプリのリポジトリの `deploy/`(denpa と yosegaki は chart) |
| netbird | `store.db` / `idp.db` / `events.db` を tar 1 本に | [../netbird/deployment.yaml](../netbird/deployment.yaml) |
| adguardhome | ファイルだけ(下記) | ─ |

Expand Down
8 changes: 8 additions & 0 deletions apps/k8up/notify.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,16 @@ data:
const hours = (t) => (now - Date.parse(t)) / 3600e3;

// (host, path) ごとに最新を拾う。ホストのスクリプト(k3s-host)のぶんも一緒に見る。
//
// **やめたアプリの残りは見ない。** namespace ごと消したアプリ(noren)や名前を
// 変えたアプリ(shadai → todoroku)のスナップショットは、8 日の学習窓が閉じるまで
// 毎日「更新されていない」と鳴る。消してよいものは `restic forget --host <名前>` で
// 消すが、**残しておきたいもの(改名前の履歴)は `restic tag --host <旧名> --add retired`**
// で印を付ける(README「やめたアプリの残り」)。印の付いたものはここで落とす。
// 中身はそのままなので、戻すときは今までどおり restore できる。
const latest = new Map();
for (const s of snapshots) {
if ((s.tags ?? []).includes("retired")) continue;
for (const path of s.paths ?? []) {
const h = hours(s.time);
if (h > LEARN_H) continue;
Expand Down
Loading