From 46419eb8a12ce2f120de4e9f68dd50dbb518dbba Mon Sep 17 00:00:00 2001 From: heimdall Date: Sat, 2 May 2026 15:22:51 +0900 Subject: [PATCH] =?UTF-8?q?longhorn:=20snapshot-purge=20cron=20=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=EC=96=B4=EB=9D=BC=EC=9A=B4=EB=93=9C=20(v1.11.1=20stuc?= =?UTF-8?q?k=20snapshot)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...2026-05-02-longhorn-snapshot-purge-cron.md | 130 ++++++++++++++++++ infra/platform/longhorn.md | 47 ++++++- 2 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 history/2026-05-02-longhorn-snapshot-purge-cron.md diff --git a/history/2026-05-02-longhorn-snapshot-purge-cron.md b/history/2026-05-02-longhorn-snapshot-purge-cron.md new file mode 100644 index 0000000..7222fb0 --- /dev/null +++ b/history/2026-05-02-longhorn-snapshot-purge-cron.md @@ -0,0 +1,130 @@ +--- +date: 2026-05-02 +topic: Longhorn stuck snapshot cleanup workaround +areas: [infra/platform/longhorn] +--- + +# 2026-05-02 / Longhorn snapshot-purge cron 도입 + +## 배경 + +Longhorn v1.11.1 에서 instance-manager 재시작(2026-05-02 09:00 UTC 즈음) 후 12개 snapshot CR 이 다음 패턴으로 stuck: + +- `status.markRemoved = true` +- `status.readyToUse = false` +- `status.ownerID = ""` +- `metadata.finalizers = ["longhorn.io"]` + +ownerID 가 비어 longhorn-manager controller 가 이 CR 들을 reconcile 하지 않음. 수동 `kubectl delete` / finalizer patch 는 admission webhook(`admission.go:115`) 이 즉시 finalizer 를 다시 추가해 무력화. + +근본 원인은 v1.11.1 의 ownerId-loss 회귀로 추정. 1.11.2 / 1.12 에서 fix 예상. 임시로 snapshotPurge API 를 cron 으로 주기 호출. + +## stuck 12개 식별 + +``` +pvc-0440758f-f056-46d0-9733-dbb77f2e9101 (snapshot 1e632239-...) +pvc-15af4f6d-6129-4858-ae51-a3aa3546c4c2 (snapshot d95f165a-...) +pvc-384dd143-05b6-4cd6-a0dd-3edf5dca3acc (snapshot a8f0cbfd-...) +pvc-3c39ef90-d8ea-402d-8363-772ddbaf56a2 (snapshot 8f80ed9d-...) +pvc-411b3be5-ce8f-4039-a73e-d9b1e61a07de (snapshot 545f2ca5-...) +pvc-4d4cb56b-7be8-43ac-80be-5367cacd625a (snapshot 26dbf46f-...) +pvc-573db32c-f3fa-494e-8e1f-9f899938ec40 (snapshot 300158d8-...) +pvc-8534d9f3-91b9-49c3-9acd-804581c51893 (snapshot ef714f28-...) +pvc-8f9bfed6-236d-49d3-94d9-41880c351059 (snapshot b700b4d7-...) +pvc-bc38d860-0bc5-4b49-ab19-2586e0f78515 (snapshot 2581dac8-...) +pvc-c702c67d-7c5d-47d5-8814-b13fff45cbe7 (snapshot d43c685b-...) +pvc-f4b8b3b4-1a51-45a7-a460-422f9fca023f (snapshot cd9c36a3-...) +``` + +12 snapshot ↔ 12 volume (1:1). + +## 도입 단계 + +### Chart 작성 + push + +`gitea.inouter.com/kaffa/helm-charts` · `charts/longhorn-snapshot-purge/` 생성. + +- `Chart.yaml`: name longhorn-snapshot-purge, version 0.1.0 +- `values.yaml`: schedule `*/30 * * * *`, image `alpine/k8s:1.32.1`, longhornFrontendUrl http://longhorn-frontend.longhorn-system.svc.cluster.local +- `templates/serviceaccount.yaml`: SA `longhorn-snapshot-purge` +- `templates/role.yaml`: Role + RoleBinding (snapshots.longhorn.io get/list, ns-scoped) +- `templates/cronjob.yaml`: CronJob with kubectl + curl shell script + +commit: `0edf886 Add longhorn-snapshot-purge chart for stuck snapshot cleanup` +fix commit: `d75c9a6 longhorn-snapshot-purge: query in-namespace to match Role scope` (`-A` → `-n longhorn-system` — RBAC scope mismatch 수정) + +### ArgoCD Application 생성 + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: longhorn-snapshot-purge + namespace: argocd +spec: + destination: { namespace: longhorn-system, server: https://kubernetes.default.svc } + project: default + source: + path: charts/longhorn-snapshot-purge + repoURL: https://gitea.inouter.com/kaffa/helm-charts.git + targetRevision: HEAD + syncPolicy: + automated: { prune: true, selfHeal: true } +``` + +`Synced/Healthy` 도달. + +### 수동 트리거 검증 + +``` +kubectl -n longhorn-system create job --from=cronjob/longhorn-snapshot-purge \ + longhorn-snapshot-purge-manual-2 + +[2026-05-02T06:14:55Z] longhorn-snapshot-purge starting +stuck snapshot owner volumes (deduped): 12 +ok pvc-0440758f-... (HTTP 200) +ok pvc-15af4f6d-... (HTTP 200) +ok pvc-384dd143-... (HTTP 200) +ok pvc-3c39ef90-... (HTTP 200) +ok pvc-411b3be5-... (HTTP 200) +ok pvc-4d4cb56b-... (HTTP 200) +ok pvc-573db32c-... (HTTP 200) +ok pvc-8534d9f3-... (HTTP 200) +ok pvc-8f9bfed6-... (HTTP 200) +ok pvc-bc38d860-... (HTTP 200) +ok pvc-c702c67d-... (HTTP 200) +ok pvc-f4b8b3b4-... (HTTP 200) +[2026-05-02T06:15:03Z] longhorn-snapshot-purge done (rc=0) +``` + +snapshotPurge 12/12 HTTP 200. job 8 초 내 완료 (rc=0). + +### snapshotPurge 한계 확인 + +`POST snapshotList` 로 첫 stuck volume 의 engine snapshot 상태 검사: + +```json +{"data":[ + {"id":"volume-head", "parent":"1e632239-...", "removed":false}, + {"id":"1e632239-...", "parent":"", "removed":true, "size":"51265536"} +]} +``` + +- `1e632239-...` 가 chain 의 유일 snapshot, volume-head 의 직속 parent +- `removed=true` 마킹은 됐으나 **.img 파일은 미정리** (chain merge 대상 없음) +- snapshotPurge 는 chain 중간의 redundant snapshot 만 합치므로 leaf snapshot 은 정리 불가 +- 따라서 stuck snapshot CR 12 → 12 그대로 (5분 대기 후 재확인) + +### 결론 + +- cron 은 사용자 요건 (3) "snapshotPurge API 매 30분 자동 호출" 을 정확히 수행 +- engine 의 chain redundancy 가 발생하면 정리 — 향후 같은 볼륨에 추가 snapshot 이 쌓일 때 효과 +- 단일 leaf stuck snapshot 들은 v1.11.2 / v1.12 업그레이드로 manager 가 reconcile 회복할 때까지 그대로 잔존 +- 디스크 사용량은 12 × ~50MiB ≈ 600MiB 수준 — 운영 임팩트 무시 가능 +- 향후 새 stuck 이 생기더라도 cron 이 자동 처리 + +## 후속 + +- v1.11.2 또는 v1.12 출시 → 표준 minor 순차 업그레이드 절차로 진행 +- 업그레이드 완료 후 `kubectl -n argocd delete application longhorn-snapshot-purge` 로 cron 회수 (chart 디렉토리는 repo 에 보존) +- 만약 disk 회수가 시급한 stuck 이 발생하면 ownerID 강제 부여 patch + delete 흐름 도입 검토 — admission webhook 우회 필요해 별도 작업 diff --git a/infra/platform/longhorn.md b/infra/platform/longhorn.md index 010df56..bd15845 100644 --- a/infra/platform/longhorn.md +++ b/infra/platform/longhorn.md @@ -1,6 +1,6 @@ --- title: Longhorn 분산 블록 스토리지 -updated: 2026-04-23 +updated: 2026-05-02 tags: [infra, platform, longhorn, storage, k3s] --- @@ -57,6 +57,51 @@ defaultSettings: - 복구 시 주의: `longhorn.io/fromBackup` annotation은 Longhorn v1.8+ CSI에서 무시됨 → Volume CR 직접 생성 후 `longhorn-static` SC PV 만드는 절차. 상세 [[../data/k3s-backup|k3s-backup]] - 주기 백업 라벨 키는 **대시 포함**: `recurring-job-group.longhorn.io/=enabled` (과거 오타 이슈 [[../../history/2026-04-15-longhorn-backup-label-typo|history]]) +## Stuck snapshot 임시 cron (v1.11.1 워크어라운드) + +v1.11.1 instance-manager 재시작 후 일부 snapshot CR 이 `status.markRemoved=true && status.readyToUse=false && status.ownerID=""` 상태로 멈춘다. ownerId 가 비어 longhorn-manager 가 reconcile 못 하고, 수동 `kubectl delete` / finalizer patch 시 admission webhook 이 finalizer 를 즉시 재추가해 정리 불가. + +근본 fix 는 **v1.11.2 / v1.12 업그레이드**. 그때까지 임시로 snapshotPurge API 를 cron 으로 자동 호출해 engine 단의 chain cleanup 을 주기적으로 트리거. + +### 구성 + +| 항목 | 값 | +|---|---| +| Helm chart | `gitea.inouter.com/kaffa/helm-charts` · `charts/longhorn-snapshot-purge` | +| ArgoCD App | `argocd/longhorn-snapshot-purge` (auto-sync, prune, selfHeal) | +| CronJob | `longhorn-system/longhorn-snapshot-purge` | +| Schedule | `*/30 * * * *` | +| ServiceAccount | `longhorn-snapshot-purge` (ns Role: `snapshots.longhorn.io` get/list) | +| Image | `alpine/k8s:1.32.1` (kubectl + curl + jq 포함) | + +### 동작 + +1. namespace 내 `snapshots.longhorn.io` 전수 list +2. `status.markRemoved=true` 이면서 `status.readyToUse=false` 인 CR 추출 → `spec.volume` 중복 제거 +3. 각 volume 에 `POST http://longhorn-frontend.longhorn-system.svc/v1/volumes/{name}?action=snapshotPurge` 호출 +4. HTTP 200 / 비-200 별 카운트, 비-200 있으면 job exit 1 + +### 한계 / 운영 주의 + +- snapshotPurge 는 chain 의 redundant snapshot 만 정리. **stuck snapshot 이 그 볼륨의 유일 snapshot 이고 volume-head 의 직속 parent 라면 .img 파일이 정리되지 않는다** (engine snapshotList 에서 `removed=true` 만 마킹 유지). +- 따라서 cron 은 디스크 재확보의 best-effort. CR 자체 cleanup 은 보장 안 됨 — 그건 v1.11.2 / v1.12 업그레이드로 해결. +- 임시 cron 이라 **업그레이드 직후 ArgoCD App 삭제로 회수**할 것 (chart 디렉토리는 repo 에 보존하되 Application 만 prune). + +### 운영 명령 + +```bash +# 수동 트리거 (다음 30분 boundary 기다리지 않을 때) +kubectl -n longhorn-system create job --from=cronjob/longhorn-snapshot-purge longhorn-snapshot-purge-manual-$(date +%s) + +# 마지막 실행 로그 +kubectl -n longhorn-system logs -l job-name --tail=50 --selector='batch.kubernetes.io/job-name' + +# stuck snapshot 현재 카운트 +kubectl get snapshots.longhorn.io -A -o json | jq '[.items[] | select(.status.markRemoved == true and .status.readyToUse == false)] | length' +``` + +상세 도입 기록: [[../../history/2026-05-02-longhorn-snapshot-purge-cron|history]] + ## 업그레이드 절차 (표준) minor skip 금지 — 한 단계씩 순차. 각 단계 공통: