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
116 changes: 110 additions & 6 deletions .github/workflows/talos-boot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ env:
jobs:
boot:
runs-on: ubuntu-latest
# 実測 8 分
timeout-minutes: 30
# 実測 8 分(PVC と再起動を足す前)
timeout-minutes: 40
steps:
- uses: actions/checkout@v7

Expand Down Expand Up @@ -93,7 +93,7 @@ jobs:
curl -fsSL --retry 3 \
"https://factory.talos.dev/image/${{ steps.v.outputs.schematic }}/${{ steps.v.outputs.version }}/metal-amd64.raw.zst" \
| zstd -d -o /tmp/vm/disk.raw
# EPHEMERAL の maxSize 64GiB と u-local-path の minSize 50GiB が両方取れる大きさ
# EPHEMERAL の maxSize 64GiB と r-lvm(LVM の PV)の minSize 50GiB が両方取れる大きさ
truncate -s 140G /tmp/vm/disk.raw
cp /usr/share/OVMF/OVMF_VARS_4M.fd /tmp/vm/OVMF_VARS.fd

Expand Down Expand Up @@ -206,14 +206,18 @@ jobs:
until kubectl -n kube-system get secret hubble-server-certs hubble-relay-client-certs >/dev/null 2>&1; do sleep 5; done
ro kube-system deploy/hubble-relay
ro kube-system deploy/hubble-ui
ro local-path-storage deploy/local-path-provisioner
# PVC の置き場(bootstrap/storage.yaml)。thin プールが出来てラベルが付くまで OpenEBS のノードの Pod は出てこない
ro kube-system ds/lvm-thinpool
ro kube-system ds/lvm-localpv-node
ro kube-system deploy/lvm-localpv-controller
ro kube-system deploy/snapshot-controller
ro cert-manager deploy/cert-manager
ro cert-manager deploy/cert-manager-webhook
ro cert-manager deploy/cert-manager-cainjector
ro argocd deploy/argocd-server
ro argocd deploy/argocd-repo-server
ro argocd sts/argocd-application-controller
# postgres の PVC は local-path なので、ここで PV の経路も見ている
# postgres の PVC は既定の StorageClass(OpenEBS LVM の thin)なので、ここで PV の経路も見ている
ro infisical deploy/infisical
kubectl -n infisical wait --for=jsonpath='{.status.phase}'=Bound pvc --all --timeout=1m
# 上の一覧に無い Pod の取りこぼし止め
Expand Down Expand Up @@ -263,7 +267,7 @@ jobs:
set -euo pipefail
fail() { echo "::error::$*"; exit 1; }
talosctl get volumestatus -o json > /tmp/vol.json
for v in EPHEMERAL u-local-path; do
for v in EPHEMERAL r-lvm; do
P=$(jq -rs --arg v "$v" '.[] | select(.metadata.id == $v) | .spec.phase' /tmp/vol.json)
[ "$P" = ready ] || fail "volume $v が ready でない($P)"
echo " volume $v: $P"
Expand Down Expand Up @@ -294,6 +298,94 @@ jobs:
done
fail "bond0 に RA 由来の IPv6 既定経路が無い"

- name: PVC・スナップショット・スナップショットからの戻し
timeout-minutes: 8
run: |
set -euo pipefail
fail() { echo "::error::$*"; exit 1; }
# thin プールは VG の 90%(bootstrap/storage.yaml)。OpenEBS が最初の PVC の大きさで作ったものではないこと
kubectl -n kube-system exec ds/lvm-thinpool -c watch -- lvs -a -o lv_name,lv_size,data_percent,metadata_percent,lv_attr data
VG=$(kubectl -n kube-system exec ds/lvm-thinpool -c watch -- vgs --noheadings --units b --nosuffix -o vg_size data | tr -d ' ')
POOL=$(kubectl -n kube-system exec ds/lvm-thinpool -c watch -- lvs --noheadings --units b --nosuffix -o lv_size data/data_thinpool | tr -d ' ')
[ $((POOL * 100 / VG)) -ge 85 ] || fail "thin プールが VG の $((POOL * 100 / VG))%(90% のはず)"

# 既定の StorageClass で書き、スナップショットを取り、そこから別の PVC を作って同じ中身が読めるか(VolSync の populator の前提)
cat > /tmp/pvc.yaml <<'YAML'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: t-src
spec:
accessModes: [ReadWriteOnce]
resources: {requests: {storage: 1Gi}}
---
apiVersion: v1
kind: Pod
metadata:
name: t-src
spec:
securityContext: {runAsNonRoot: true, runAsUser: 1000, fsGroup: 1000, seccompProfile: {type: RuntimeDefault}}
containers:
- name: c
image: mirror.gcr.io/library/busybox:1.38
# 再起動で起き直しても書き直さない(下の「再起動しても PV が戻る」が中身の残りを見る)
command: [sh, -c, '[ -f /data/marker ] || echo hello > /data/marker; sleep 86400']
securityContext: {allowPrivilegeEscalation: false, capabilities: {drop: [ALL]}}
volumeMounts: [{name: v, mountPath: /data}]
volumes: [{name: v, persistentVolumeClaim: {claimName: t-src}}]
YAML
kubectl apply -f /tmp/pvc.yaml
kubectl wait --for=condition=Ready pod/t-src --timeout=3m
kubectl wait --for=jsonpath='{.status.phase}'=Bound pvc/t-src --timeout=1m
[ "$(kubectl exec t-src -- cat /data/marker)" = hello ] || fail "PVC に書けていない"
FS=$(kubectl exec t-src -- sh -c "grep ' /data ' /proc/mounts | cut -d' ' -f3")
[ "$FS" = xfs ] || fail "PVC が xfs でない($FS)"
echo " PVC t-src: Bound, xfs, 書けた"

cat > /tmp/snap.yaml <<'YAML'
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: t-snap
spec:
source: {persistentVolumeClaimName: t-src}
YAML
kubectl apply -f /tmp/snap.yaml
kubectl wait --for=jsonpath='{.status.readyToUse}'=true volumesnapshot/t-snap --timeout=3m
echo " VolumeSnapshot t-snap: readyToUse(既定の VolumeSnapshotClass $(kubectl get volumesnapshot t-snap -o jsonpath='{.spec.volumeSnapshotClassName}'))"

# t-dst はスナップショットの中身を読むだけ(書かない)
sed -e 's/name: t-src$/name: t-dst/' -e 's/claimName: t-src/claimName: t-dst/' \
-e 's/\[ -f \/data\/marker \] || echo hello > \/data\/marker; //' /tmp/pvc.yaml \
| yq '(select(.kind == "PersistentVolumeClaim") | .spec.dataSource) = {"apiGroup": "snapshot.storage.k8s.io", "kind": "VolumeSnapshot", "name": "t-snap"}' \
> /tmp/dst.yaml
! grep -q 'echo hello' /tmp/dst.yaml || fail "t-dst が書いてしまう形になっている"
grep -q 'kind: VolumeSnapshot' /tmp/dst.yaml || fail "t-dst に dataSource が付いていない"
kubectl apply -f /tmp/dst.yaml
kubectl wait --for=condition=Ready pod/t-dst --timeout=3m
[ "$(kubectl exec t-dst -- cat /data/marker)" = hello ] || fail "スナップショットから戻した PVC に中身が無い"
echo " PVC t-dst(t-snap から): 中身が戻った"
kubectl -n kube-system exec ds/lvm-thinpool -c watch -- lvs -a -o lv_name,lv_size,pool_lv,origin,data_percent data

- name: 再起動しても PV が戻る
timeout-minutes: 10
run: |
set -euo pipefail
fail() { echo "::error::$*"; exit 1; }
# thin プールと LV は Talos が起動時に有効にする(thin_check が無いのは飛ばされる)。
# lvm-thinpool の pool も起動のたびに走るので、壊さないことも見ている
talosctl reboot --wait --timeout 8m
until kubectl get --raw /readyz >/dev/null 2>&1; do sleep 5; done
kubectl wait --for=condition=Ready node/main --timeout=5m
for p in t-src t-dst; do
until kubectl wait --for=condition=Ready "pod/$p" --timeout=10s >/dev/null 2>&1; do sleep 5; done
[ "$(kubectl exec "$p" -- cat /data/marker)" = hello ] || fail "再起動のあと $p の中身が無い"
done
kubectl -n infisical rollout status statefulset/postgresql --timeout=5m
kubectl -n kube-system rollout status ds/lvm-thinpool --timeout=3m
kubectl -n kube-system logs ds/lvm-thinpool -c pool
echo " 再起動のあとも t-src・t-dst・infisical の Postgres が上がった"

- name: 失敗したときの様子
if: failure()
run: |
Expand All @@ -309,6 +401,18 @@ jobs:
echo "::group::GatewayClass / Gateway / Certificate"
kubectl get gatewayclass,gateway -A -o yaml; kubectl get certificate -A
echo "::endgroup::"
echo "::group::PVC の置き場(LVM)"
kubectl get sc,volumesnapshotclass,pvc,pv,volumesnapshot,volumesnapshotcontent -A
kubectl -n kube-system get lvmnode,lvmvolume,lvmsnapshot -o yaml | tail -120
kubectl -n kube-system logs ds/lvm-thinpool -c pool --tail=50
kubectl -n kube-system logs ds/lvm-localpv-node -c openebs-lvm-plugin --tail=80
kubectl -n kube-system logs deploy/lvm-localpv-controller -c openebs-lvm-plugin --tail=50
kubectl -n kube-system logs deploy/lvm-localpv-controller -c csi-provisioner --tail=50
kubectl -n kube-system logs deploy/lvm-localpv-controller -c csi-snapshotter --tail=50
kubectl -n kube-system logs deploy/snapshot-controller --tail=50
talosctl get volumestatus
talosctl get rd | awk 'tolower($4) ~ /lvm/ {print $4}' | while read -r r; do talosctl get "$r" -o yaml 2>/dev/null | head -60; done
echo "::endgroup::"
echo "::group::talosctl services"; talosctl services; echo "::endgroup::"
echo "::group::talosctl get links / addresses / routes"
talosctl get links; talosctl get addresses; talosctl get routes
Expand Down
7 changes: 5 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ bond0 の MAC は設定で固定してあるのでルーターは触らない。
- [ ] AMT(10.0.0.5)、Z440 用の schematic(GPU の extension と SOL の `console=`)、NIC の別名(`LinkAliasConfig`)
- [ ] 移して確かめる。docs の「iLO(10.0.0.3)」を AMT に直す
- [ ] **ストレージとバックアップを入れ替える**(入れ直しのついでに。理由は [docs/decisions.md](docs/decisions.md)「Z440 で、ストレージを OpenEBS LVM LocalPV に…」)
- [ ] ディスクの切り方: `u-local-path` をやめ、`RawVolumeConfig` + `LVMVolumeGroupConfig` でボリュームグループ(thin プール)にする
- [ ] OpenEBS LVM LocalPV と snapshot-controller(CRD 込み)を入れる。Talos で動くかを先に QEMU(talos-boot)で確かめる
- [x] ディスクの切り方: `u-local-path` をやめ、`RawVolumeConfig` + `LVMVolumeGroupConfig` で VG `data` にする。
thin プールは Talos で作れないので `bootstrap/storage.yaml` の DaemonSet が作る(書いてあるだけ。効くのは Z440 で入れ直したとき)
- [x] OpenEBS LVM LocalPV と snapshot-controller(CRD 込み)を入れる。StorageClass は名前を `local-path` のまま OpenEBS に。
talos-boot で PVC・VolumeSnapshot・スナップショットからの PVC・再起動のあとまでを確かめている
- [ ] Z440 で入れ直し、`recovery/pv.sh - restore-all` で戻す([docs/z440-migration.md](docs/z440-migration.md) の 4〜7)
- [ ] VolSync(restic mover、copyMethod: Snapshot)を入れ、PVC ごとに ReplicationSource。R2 は PVC ごとに別の経路
- [ ] 戻しは populator(PVC の `dataSourceRef`)。移設のときは `recovery/pv.sh restore-all`(k8up の restic から)で戻し、そのあと VolSync に切り替える
- [ ] k8up・`backupcommand`・notify・restore-check を VolSync に合わせて畳む。k8up のリポジトリは保持期間(月次 6 か月)が切れるまで残す
Expand Down
6 changes: 4 additions & 2 deletions apps/adguardhome/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ metadata:
# AdGuard 自身の認証は切ってある(`users: []`)。守っているのは前段の oauth2-proxy
# (apps/auth/adguard-proxy.yaml、Entra のアプリロール `admin`)。
# Web の口はクラスタ内からは無認証になる。外からは Gateway 経由だけで、その手前に SSO がある。
# クエリログの保持(querylog.interval)は 90d だと querylog.json が 2 GB に育ちバックアップに乗るので 7d。
# クエリログの保持(querylog.interval)は 1d。2026-10 末に 1 日 10 GB 近く書くようになり、7d では querylog.json が
# 68 GB に育った(バックアップにも乗る)。PVC は LVM で大きさどおりの上限があるので、ファイル 2 本(今と 1 つ前)が
# adguardhome-work(pvc.yaml)に収まる長さにする。量が戻ったら延ばしてよい。
spec:
replicas: 1
# hostPort を持つので同じポートを 2 つの Pod が同時に掴めない。RollingUpdate だと
Expand Down Expand Up @@ -139,7 +141,7 @@ spec:
querylog:
dir_path: ""
ignored: []
interval: 7d
interval: 1d
size_memory: 1000
enabled: true
ignored_enabled: false
Expand Down
3 changes: 2 additions & 1 deletion apps/adguardhome/pvc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ spec:
storageClassName: local-path
resources:
requests:
storage: 5Gi
# クエリログ(deployment.yaml の querylog.interval)の 2 本ぶん。LVM なのでこれが本当の上限になる
storage: 30Gi
28 changes: 24 additions & 4 deletions bootstrap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ ArgoCD はここを見ない(例外は [argocd-repos.yaml](argocd-repos.yaml)

| | 何を | いつ |
| --- | --- | --- |
| **machine config**(このディレクトリ) | Talos の設定、Helm で入れるもの(cilium・cert-manager・argocd・infisical)、`apiserver-rbac.yaml`、`local-path.yaml`、SOPS 済みの Secret 2 つ | `render.sh` → `apply-config` → `upgrade-k8s`([talos.md](talos.md)「上げ方 / 当て直し方」) |
| **machine config**(このディレクトリ) | Talos の設定、Helm で入れるもの(cilium・cert-manager・argocd・infisical・lvm-localpv)、`apiserver-rbac.yaml`、`storage.yaml`、SOPS 済みの Secret 2 つ | `render.sh` → `apply-config` → `upgrade-k8s`([talos.md](talos.md)「上げ方 / 当て直し方」) |
| **ArgoCD**(`apps/`) | 平文のマニフェスト(`ClusterIssuer`・Gateway・auth・`InfisicalSecret` など)。2026-10 まではここにあって、Application `bootstrap` が当てていた | main へのマージ(prune / selfHeal 付き) |

## ファイル
Expand All @@ -24,7 +24,8 @@ ArgoCD はここを見ない(例外は [argocd-repos.yaml](argocd-repos.yaml)
| [infisical-values.sops.yaml](infisical-values.sops.yaml) | chart の値と版(DB と Redis のパスワードだけ SOPS)。2 つめのドキュメントが infisical の起動に要る Secret(`ENCRYPTION_KEY` など) |
| [cert-manager-values.sops.yaml](cert-manager-values.sops.yaml) | chart の値と版。2 つめのドキュメントが DNS-01 の Cloudflare のトークンの Secret(トークンだけ SOPS) |
| [apiserver-rbac.yaml](apiserver-rbac.yaml) / [apiserver-ca.crt](apiserver-ca.crt) | API サーバの入口の RBAC と CA(下の「API サーバの入口」) |
| [local-path.yaml](local-path.yaml) | local-path-provisioner |
| [lvm-localpv-values.yaml](lvm-localpv-values.yaml) | chart の値と版(OpenEBS LVM LocalPV。PVC の置き場。下の「PVC の置き場」) |
| [storage.yaml](storage.yaml) | StorageClass `local-path`・VolumeSnapshotClass・thin プールを作る DaemonSet(chart が描かないもの) |
| [argocd-repos.yaml](argocd-repos.yaml) | ArgoCD が当てる唯一のファイル(下の「ArgoCD が当てるもの」) |

**SOPS(age)で暗号化したファイルは名前を `*.sops.yaml` で終える**(`.sops.yaml` の規則もその名前にしか一致しないので、
Expand Down Expand Up @@ -69,7 +70,7 @@ Talos・Kubernetes・Gateway API の版も同じ形で、[talos.sops.yaml](talos
`render.sh` が復号してから helm に渡す。**2 つめのドキュメントがあれば、それは chart とは別に machine config に載せる Secret**
(infisical の `infisical-secrets`、cert-manager の Cloudflare のトークン。ArgoCD は復号できないのでここに置く)で、
`render.sh` が `---` で分けて別の inlineManifest にする。
argocd・infisical の版は自動マージしない(`renovate.json` の `bootstrap charts`)。
argocd・infisical の版は自動マージしない(`renovate.json` の `bootstrap charts`)。lvm-localpv も(`openebs lvm`。`storage.yaml` の lvm-driver のイメージと同じ PR)。
Namespace は chart が描かないので、`render.sh` が kube-system 以外のものに付ける。

## git と live がずれていないか
Expand Down Expand Up @@ -106,14 +107,33 @@ kubectl -n kube-system exec ds/cilium -c cilium-agent -- cilium-dbg service list
kubectl -n kube-system get gateway doany
```

## PVC の置き場

**OpenEBS LVM LocalPV**(thin)。PVC ごとに VG `data` の thin プールから LV を切り、xfs で渡す。
infisical の Postgres が要るので bootstrap 層に居る。選んだ理由と組んだ形は [../docs/decisions.md](../docs/decisions.md)「Z440 で、ストレージを OpenEBS LVM LocalPV に…」。

| 何を | 誰が |
| --- | --- |
| パーティション `r-lvm` と VG `data` | Talos([talos.sops.yaml](talos.sops.yaml) の `RawVolumeConfig` / `LVMVolumeGroupConfig`)。**入れ直したときにしか作られない** |
| thin プール `data_thinpool`(VG の 90%) | [storage.yaml](storage.yaml) の DaemonSet `lvm-thinpool`。出来たらノードに `doany.io/lvm-thinpool=ready` を付ける |
| CSI ドライバ(`local.csi.openebs.io`) | chart([lvm-localpv-values.yaml](lvm-localpv-values.yaml))。ノードの Pod は上のラベルが付いてから動く |
| StorageClass `local-path`(既定)・VolumeSnapshotClass `lvm`(既定) | [storage.yaml](storage.yaml) |
| snapshot-controller と VolumeSnapshot の CRD | `render.sh` が external-snapshotter のタグの URL で(版は [talos.sops.yaml](talos.sops.yaml) の先頭) |

- **PVC の requests が本当の上限。** 足りなくなったら PVC の requests を上げる(`allowVolumeExpansion: true`。スナップショットのある PVC は広げられない)
- **thin プールが溢れると全部の PVC の書き込みが止まる。** `kubectl -n kube-system get pods -l app=lvm-thinpool` が 90% で NotReady になる。
埋まり具合は `kubectl -n kube-system exec ds/lvm-thinpool -c watch -- lvs data`。足すときは VG の残り(1 割)から
`kubectl -n kube-system exec ds/lvm-thinpool -c watch -- lvextend -L +10G data/data_thinpool`
- **新しい PVC の直下は root の 0755。** root 以外で書くアプリは Pod に `fsGroup` を付ける

## Secret

平文の Secret はアプリのリポジトリにはコミットしない。値は **Infisical**(https://il.doany.io、このクラスタでセルフホスト)が持ち、
**Infisical 純正 operator** が各リポジトリの `InfisicalSecret` を見て Infisical から値を取り、普通の `Secret` を作る。

- **本体**(chart + Postgres/Redis)だけがここ(machine config)。`infisical-values.sops.yaml` の 2 つめのドキュメントの
`ENCRYPTION_KEY` が DB の暗号鍵で、これを失うと Infisical の中身が全部読めなくなる(クラスタで一番失ってはいけない値)。
Postgres の PVC は `local-path` で、`pg_dump` を k8up が日次で取っている。
Postgres の PVC は既定の `local-path`(OpenEBS LVM)で、`pg_dump` を k8up が日次で取っている。
- **公開経路・operator・push-bridge は [`apps/infisical/`](../apps/infisical/)**(ArgoCD 自身は operator に依存しないので、ここに置く理由がない)。
- 認証は Infisical の Kubernetes auth: operator が SA `infisical-auth` のトークンで Infisical にログインし、
Infisical がそのトークンで TokenReview を呼んで本物か確かめる(なので SA に `system:auth-delegator` を付けてある。
Expand Down
Loading
Loading