Skip to content

feat(ansible): journald永続化をcontrol-planeに追加 [BOXP-127] - #11562

Merged
boxp merged 25 commits into
mainfrom
feature/BOXP-127-control-plane-hardening
Jul 23, 2026
Merged

feat(ansible): journald永続化をcontrol-planeに追加 [BOXP-127]#11562
boxp merged 25 commits into
mainfrom
feature/BOXP-127-control-plane-hardening

Conversation

@boxp

@boxp boxp commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • kubernetes_components Ansible ロールに journald 永続ストレージ設定を追加
  • SD カード障害(INC-2/INC-4/BOXP-127)時に障害直前のカーネル/ストレージログが揮発的に失われる問題を解消

変更内容

  • tasks/journald.yml: /var/log/journal 作成 + Storage=persistent + 容量上限設定(新規)
  • tasks/main.yml: journald タスクを include
  • defaults/main.yml: journald_persistent_storage: true, SystemMaxUse=200M, MaxRetentionSec=1month
  • handlers/main.yml: Restart systemd-journald ハンドラ追加
  • docs/project_docs/BOXP-127/plan.md: 恒久対策計画(新規)

背景

OrangePI Zero3 の control-plane(shanghai-1/2/3)が繰り返し SD カード障害で停止している(INC-2, INC-4, BOXP-127)。journald が volatile のため、障害後に OS 再起動すると直前のエラーログが消える。SD カードが読み取り可能な状態でリブートした場合でも証拠が残らない状態を改善する。

容量: SystemMaxUse=200M で SD カードの過度な消費を防止。

Test plan

  • cd ansible && uv run ansible-playbook --syntax-check playbooks/control-plane.yml
  • cd ansible && uv run ansible-lint roles/kubernetes_components
  • 実機適用後: journalctl --disk-usage で永続ログ確認
  • 実機適用後: ls /var/log/journal/ でディレクトリ存在確認

🤖 Generated with Claude Code

github-actions Bot and others added 14 commits July 13, 2026 08:28
…very

Implements a GitHub Actions workflow that downloads an Orange Pi image
from S3 and writes it to an SD card writer connected to a worker node.
Adds S3 read permission to the Ansible Apply IAM role to support image
download without needing a separate role.

Safety guards: removable device check, mount state check, checksum
verification, dry-run mode, and explicit device path requirement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add shellcheck disable=SC2029 for intentional client-side variable expansion in SSH commands
- Group consecutive GITHUB_STEP_SUMMARY redirects with { } >> file (SC2129)
- Add `set -o pipefail` to the write step so xzcat failure propagates correctly
- Track write and verify completion with IMAGE_WRITTEN/WRITE_VERIFIED flags
- Write summary now only reports success when both write and verify succeeded

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The build action uploaded the timestamp-named image checksum but omitted
a corresponding checksum for latest.img.xz, causing write-sdcard-image.yml
to always skip verification. Now creates latest.img.xz.sha256 with the
correct filename reference before uploading.
…ailable

Replace `|| true` silent fallback with a mandatory checksum download.
If `latest.img.xz.sha256` cannot be fetched from S3, the workflow now
exits with an error rather than writing an unverified image to the SD card.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace `mount | grep '^$DEVICE'` with `lsblk -no MOUNTPOINT` to
  detect partitions mounted via UUID/label/by-* paths that string
  matching would miss, preventing accidental writes to in-use SD cards
- Add workflow-level concurrency group keyed by worker_node+device_path
  to prevent two simultaneous runs from both passing safety checks and
  running dd concurrently

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Verify write step now checks that lsblk reports at least one partition
with TYPE=part, failing if none are found. Previously, partprobe failures
were silently ignored and lsblk was called without checking its output
content — a blank or corrupt card would still set WRITE_VERIFIED=true
since lsblk always shows the disk itself regardless of partition state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… verify partitions

- Remove DEVICE_PATH=... prefix before ssh which does not forward env vars to remote;
  expand $DEVICE_PATH locally in double-quoted command strings instead (format already
  validated as /dev/sd[a-z] or /dev/mmcblk[0-9]+, so local expansion is safe)
- Replace unconditional WRITE_VERIFIED=true with explicit partition count check using
  wc -l (always exits 0), avoiding grep -c returning "0\n0" on no matches and ensuring
  the step fails when no TYPE=part partitions are found on the written device

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SD カード障害(INC-2/INC-4/BOXP-127)発生時に障害直前のカーネル・ストレージ
エラーログが揮発的に失われる問題を解消するため、全 control-plane ノードに
journald 永続ストレージを設定する Ansible タスクを追加する。

- /var/log/journal ディレクトリ作成 + Storage=persistent 設定
- SystemMaxUse=200M / MaxRetentionSec=1month で SD カード消費を抑制
- journald_persistent_storage フラグでオプトアウト可能(デフォルト有効)
- Restart systemd-journald ハンドラを追加

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lve conflicts)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

- defaults/main.yml: journald_persistent_storage デフォルトをfalseに変更
  (kubernetes_componentsはworker-image.ymlでも使用されるためworkerへの影響を排除)
- control-plane.yml: journald_persistent_storage: trueをcontrol-plane専用で明示設定
- tasks/journald.yml: false時にStorage=volatileドロップインを配置し/var/log/journalを削除
  (既存設定が残存して永続化を解除できない問題を修正)
- molecule/default/converge.yml: journald_persistent_storage: trueを設定してテストパスを有効化
- molecule/default/verify.yml: journaldディレクトリ・ドロップイン内容・Storage=persistentを検証するテストを追加

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@boxp

boxp commented Jul 23, 2026

Copy link
Copy Markdown
Owner Author

codex-reviewの指摘事項修正 (BOXP-127 retry)

前回のレビューで指摘された3点をすべて修正しました。

P1修正: journald設定をcontrol-plane限定に変更

  • defaults/main.yml: journald_persistent_storage: false に変更(デフォルトはvolotile)
  • playbooks/control-plane.yml: journald_persistent_storage: true をcontrol-plane専用で明示設定
  • これにより worker-image.yml でroleを使用しても永続journaldは有効化されません

P2修正: false時の収束処理を追加

  • tasks/journald.yml: journald_persistent_storage: false 時に Storage=volatile を含むドロップインファイルを配置してjournaldを再起動
  • /var/log/journal ディレクトリも削除して完全にvolatile状態に収束

P2修正: Moleculeテスト追加

  • molecule/default/converge.yml: journald_persistent_storage: true を設定してテストパスを有効化
  • molecule/default/verify.yml: 以下を検証するテストを追加
    • /var/log/journal ディレクトリの存在
    • /etc/systemd/journald.conf.d/10-persistent-storage.conf の存在
    • ファイル内に Storage=persistent が含まれることの確認

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

converge.ymlではtrueに設定しているが、verifyプレイブックはロールのdefaultsを
自動継承しないためwhen条件式で未定義エラーが発生していた。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

デフォルトはfalseで、control-planeプレイブック側でのみtrueに設定される。
worker nodeには適用されないことを明記。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

…le scenario

- Add `notify: Restart systemd-journald` to the persistent journal directory
  creation task so journald picks up the drop-in even when only the directory
  is recreated (drop-in unchanged → no change event without this notify).
- Add molecule/journald-disabled scenario that converges with
  journald_persistent_storage=false and asserts the drop-in is absent,
  covering the code path that was previously untested.
- Run `molecule test --all` in CI so both default and journald-disabled
  scenarios are exercised on every ansible role change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

Remove drop-in on disable was insufficient: if /var/log/journal already
exists, journald Storage=auto continues persistent logging. Write
Storage=volatile explicitly so the role always converges correctly.

Update Molecule journald-disabled verify to assert Storage=volatile is
present in the drop-in rather than checking the file is absent.
@github-actions

This comment has been minimized.

When journald_persistent_storage=false, remove the role-managed drop-in
(state: absent) instead of writing Storage=volatile. This aligns with
the plan.md safety policy: only remove role-owned config, never
overwrite external journald configuration.

Update Molecule journald-disabled verify to assert drop-in is absent.
Document in plan.md that Storage=auto delegation is intentional:
new nodes without /var/log/journal default to volatile. Hosts that
previously had persistence can use journald_purge_persistent_logs=true
to fully revert.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

…n-destructive disabled path

- default/verify.yml: assert SystemMaxUse=200M and MaxRetentionSec=1month in
  the journald drop-in (in addition to Storage=persistent)
- journald-disabled/prepare.yml: pre-create /var/log/journal with a sentinel
  file to simulate an existing persistent journal setup
- journald-disabled/verify.yml: assert /var/log/journal and sentinel file are
  preserved after role runs with journald_persistent_storage=false

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

…urge paths

- journald.yml: when disabled, remove role-managed drop-in (state: absent)
  instead of creating a Storage=volatile file; Storage=auto takes effect,
  which is correct for nodes with no /var/log/journal directory
- journald-disabled/prepare.yml: pre-create role-managed drop-in to prove
  the converge step deletes it (deletion is now load-bearing in the test)
- journald-disabled/verify.yml: assert drop-in is absent after converge
- default/verify.yml: add SystemMaxUse=200M and MaxRetentionSec=1month
  assertions alongside the existing Storage=persistent check
- molecule/journald-purge/: new scenario that pre-creates /var/log/journal
  with a sentinel file and the drop-in, then runs the role with
  journald_purge_persistent_logs=true and verifies both are deleted

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

purge タスクで /var/log/journal を削除しても journald に再起動通知がなければ、
稼働中の journald が以前のストレージ設定を保持し続けてしまう問題を修正。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

Resolve conflicts: keep origin/main version of write-sdcard-image.yml
and update test-ansible.yml to use matrix approach with molecule test --all.
@github-actions

Copy link
Copy Markdown
Contributor

Ansible Plan Results

Mode: --check --diff (dry run)

⚠️ Changes detected


golyat-4: worker-image

Host OK Changed Skipped Failed Unreachable
golyat-4 76 2 27 0 0

2 changed

Changed Tasks (2)
# Task Module
1 user_management : Update package cache unknown
2 kubernetes_components : Ensure journald drop-in directory exists unknown

shanghai-1: control-plane

Host OK Changed Skipped Failed Unreachable
shanghai-1 80 4 20 0 0

4 changed

Changed Tasks (4)
# Task Module
1 user_management : Update package cache unknown
2 kubernetes_components : Ensure journald drop-in directory exists unknown
3 kubernetes_components : Configure bounded persistent journald storage unknown
4 kubernetes_components : Restart systemd-journald unknown

shanghai-1: node-shanghai-1

Host OK Changed Skipped Failed Unreachable
shanghai-1 9 0 0 0 0

No changes

shanghai-2: control-plane

Host OK Changed Skipped Failed Unreachable
shanghai-2 80 4 20 0 0

4 changed

Changed Tasks (4)
# Task Module
1 user_management : Update package cache unknown
2 kubernetes_components : Ensure journald drop-in directory exists unknown
3 kubernetes_components : Configure bounded persistent journald storage unknown
4 kubernetes_components : Restart systemd-journald unknown

shanghai-2: node-shanghai-2

Host OK Changed Skipped Failed Unreachable
shanghai-2 9 0 0 0 0

No changes

shanghai-3: control-plane

Host OK Changed Skipped Failed Unreachable
shanghai-3 80 4 20 0 0

4 changed

Changed Tasks (4)
# Task Module
1 user_management : Update package cache unknown
2 kubernetes_components : Ensure journald drop-in directory exists unknown
3 kubernetes_components : Configure bounded persistent journald storage unknown
4 kubernetes_components : Restart systemd-journald unknown

shanghai-3: node-shanghai-3

Host OK Changed Skipped Failed Unreachable
shanghai-3 9 0 0 0 0

No changes


Plan executed on all nodes in parallel.

@boxp
boxp merged commit 7487625 into main Jul 23, 2026
21 checks passed
@boxp
boxp deleted the feature/BOXP-127-control-plane-hardening branch July 23, 2026 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant