diff --git a/.github/workflows/test-ansible.yml b/.github/workflows/test-ansible.yml index 61d81b0068..e8c523d95b 100644 --- a/.github/workflows/test-ansible.yml +++ b/.github/workflows/test-ansible.yml @@ -125,7 +125,7 @@ jobs: if [ -d "$role_path/molecule" ]; then echo "Testing role: $role_path" cd "$role_path" - molecule test + molecule test --all else echo "No molecule tests found for role: $role_path, skipping" fi diff --git a/ansible/playbooks/control-plane.yml b/ansible/playbooks/control-plane.yml index 5cec1a5a06..671ce06741 100644 --- a/ansible/playbooks/control-plane.yml +++ b/ansible/playbooks/control-plane.yml @@ -106,3 +106,4 @@ kubelet_cluster_dns: "{{ cluster_dns }}" kubelet_cluster_domain: "{{ cluster_domain }}" kubelet_node_ip: "{{ node_ip }}" + journald_persistent_storage: true diff --git a/ansible/roles/kubernetes_components/defaults/main.yml b/ansible/roles/kubernetes_components/defaults/main.yml index 769581c102..befc3c1bd6 100644 --- a/ansible/roles/kubernetes_components/defaults/main.yml +++ b/ansible/roles/kubernetes_components/defaults/main.yml @@ -69,3 +69,12 @@ sysctl_config: # Increased from default 100m CPU / 100Mi memory to accommodate ARM processor characteristics etcd_resource_requests_cpu: "200m" etcd_resource_requests_memory: "200Mi" + +# journald persistent storage configuration +# Disabled by default; enable explicitly for control-plane nodes where persistent logs are needed. +# See playbooks/control-plane.yml for the enabling override. +journald_persistent_storage: false +# Set to true only when removing existing persistent journal history is intended. +journald_purge_persistent_logs: false +journald_system_max_use: "200M" +journald_max_retention_sec: "1month" diff --git a/ansible/roles/kubernetes_components/handlers/main.yml b/ansible/roles/kubernetes_components/handlers/main.yml index b3d5abea27..c35efa214d 100644 --- a/ansible/roles/kubernetes_components/handlers/main.yml +++ b/ansible/roles/kubernetes_components/handlers/main.yml @@ -43,3 +43,13 @@ - ansible_virtualization_type != "docker" - chroot_build is not defined or not chroot_build listen: "Restart kubelet" + +- name: Restart systemd-journald + ansible.builtin.systemd: + name: systemd-journald + state: restarted + become: true + when: + - ansible_virtualization_type != "docker" + - chroot_build is not defined or not chroot_build + listen: "Restart systemd-journald" diff --git a/ansible/roles/kubernetes_components/molecule/default/converge.yml b/ansible/roles/kubernetes_components/molecule/default/converge.yml index 02140d18c1..2fe0f8c4fe 100644 --- a/ansible/roles/kubernetes_components/molecule/default/converge.yml +++ b/ansible/roles/kubernetes_components/molecule/default/converge.yml @@ -7,4 +7,5 @@ vars: # Test-specific overrides for container environment kubernetes_disable_swap: false # Skip swap operations in container - kubernetes_enable_modules: false # Skip module loading in container \ No newline at end of file + kubernetes_enable_modules: false # Skip module loading in container + journald_persistent_storage: true # Validate the persistent-storage code path \ No newline at end of file diff --git a/ansible/roles/kubernetes_components/molecule/default/verify.yml b/ansible/roles/kubernetes_components/molecule/default/verify.yml index cbbe614751..fafad04408 100644 --- a/ansible/roles/kubernetes_components/molecule/default/verify.yml +++ b/ansible/roles/kubernetes_components/molecule/default/verify.yml @@ -2,6 +2,8 @@ - name: Verify hosts: all gather_facts: true + vars: + journald_persistent_storage: true tasks: - name: Check if CRI-O is installed ansible.builtin.package_facts: @@ -144,3 +146,50 @@ that: - "'1.32' in kubectl_version.stdout" fail_msg: "Kubectl version is not as expected" + + - name: Check if persistent journal directory exists + ansible.builtin.stat: + path: /var/log/journal + register: journal_dir + + - name: Assert persistent journal directory exists + ansible.builtin.assert: + that: + - journal_dir.stat.exists + - journal_dir.stat.isdir + fail_msg: "/var/log/journal directory does not exist — journald persistent storage not configured" + when: journald_persistent_storage | bool + + - name: Check if journald drop-in config exists + ansible.builtin.stat: + path: /etc/systemd/journald.conf.d/10-persistent-storage.conf + register: journald_dropin + + - name: Assert journald drop-in config is absent when persistent storage is disabled + ansible.builtin.assert: + that: + - not journald_dropin.stat.exists + fail_msg: "journald persistent-storage drop-in must be absent when persistent storage is disabled" + when: not (journald_persistent_storage | bool) + + - name: Assert journald drop-in config exists when persistent storage is enabled + ansible.builtin.assert: + that: + - journald_dropin.stat.exists + fail_msg: "journald drop-in /etc/systemd/journald.conf.d/10-persistent-storage.conf does not exist" + when: journald_persistent_storage | bool + + - name: Read journald drop-in config + ansible.builtin.slurp: + path: /etc/systemd/journald.conf.d/10-persistent-storage.conf + register: journald_dropin_content + when: journald_persistent_storage | bool + + - name: Assert journald is configured for persistent storage + ansible.builtin.assert: + that: + - "'Storage=persistent' in (journald_dropin_content.content | b64decode)" + - "'SystemMaxUse=200M' in (journald_dropin_content.content | b64decode)" + - "'MaxRetentionSec=1month' in (journald_dropin_content.content | b64decode)" + fail_msg: "journald drop-in is missing Storage=persistent, SystemMaxUse=200M, or MaxRetentionSec=1month" + when: journald_persistent_storage | bool diff --git a/ansible/roles/kubernetes_components/molecule/journald-disabled/converge.yml b/ansible/roles/kubernetes_components/molecule/journald-disabled/converge.yml new file mode 100644 index 0000000000..1ae4d3f405 --- /dev/null +++ b/ansible/roles/kubernetes_components/molecule/journald-disabled/converge.yml @@ -0,0 +1,10 @@ +--- +- name: Converge + hosts: all + become: true + roles: + - role: ../../../../kubernetes_components + vars: + kubernetes_disable_swap: false + kubernetes_enable_modules: false + journald_persistent_storage: false # Validate the disabled (volatile) code path diff --git a/ansible/roles/kubernetes_components/molecule/journald-disabled/molecule.yml b/ansible/roles/kubernetes_components/molecule/journald-disabled/molecule.yml new file mode 100644 index 0000000000..93b7b1020d --- /dev/null +++ b/ansible/roles/kubernetes_components/molecule/journald-disabled/molecule.yml @@ -0,0 +1,29 @@ +--- +dependency: + name: galaxy +driver: + name: docker +platforms: + - name: instance + image: "geerlingguy/docker-ubuntu2404-ansible:latest" + platform: "${MOLECULE_DOCKER_PLATFORM:-linux/amd64}" + pre_build_image: true + privileged: true + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host + command: /lib/systemd/systemd + env: + DEBIAN_FRONTEND: noninteractive + groups: + - orange_pi_zero3 +provisioner: + name: ansible + inventory: + host_vars: + instance: + ansible_python_interpreter: /usr/bin/python3 + hardware_type: orange_pi_zero3 + architecture: arm64 +verifier: + name: ansible diff --git a/ansible/roles/kubernetes_components/molecule/journald-disabled/prepare.yml b/ansible/roles/kubernetes_components/molecule/journald-disabled/prepare.yml new file mode 100644 index 0000000000..5cc6d744f5 --- /dev/null +++ b/ansible/roles/kubernetes_components/molecule/journald-disabled/prepare.yml @@ -0,0 +1,97 @@ +--- +- name: Prepare Orange Pi Zero 3 simulation environment + hosts: all + gather_facts: true + tasks: + - name: Update package cache + ansible.builtin.package: + update_cache: true + become: true + + - name: Install required packages for Orange Pi Zero 3 simulation + ansible.builtin.package: + name: + - systemd + - dbus + - init + - iproute2 + - kmod + - procps + - curl + - gnupg + - ca-certificates + state: present + become: true + + - name: Create kernel modules directory + ansible.builtin.file: + path: /lib/modules + state: directory + mode: '0755' + become: true + + - name: Ensure systemd is running + ansible.builtin.systemd: + name: systemd-logind + state: started + become: true + failed_when: false + + - name: Create SSH host keys + ansible.builtin.command: ssh-keygen -A + become: true + changed_when: false + failed_when: false + + - name: Create mock kernel modules for testing + ansible.builtin.shell: | + set -o pipefail + mkdir -p /lib/modules/$(uname -r) + touch /lib/modules/$(uname -r)/modules.dep + touch /lib/modules/$(uname -r)/modules.symbols + become: true + changed_when: false + failed_when: false + + - name: Mock modprobe for container environment + ansible.builtin.copy: + dest: /usr/local/bin/modprobe + content: | + #!/bin/bash + # Mock modprobe for container testing + echo "Mock modprobe: $*" + exit 0 + mode: '0755' + become: true + + - name: Ensure journald drop-in directory exists + ansible.builtin.file: + path: /etc/systemd/journald.conf.d + state: directory + mode: '0755' + become: true + + - name: Pre-create role-managed journald drop-in (simulates prior persistent-enable run) + ansible.builtin.copy: + dest: /etc/systemd/journald.conf.d/10-persistent-storage.conf + mode: '0644' + content: | + [Journal] + Storage=persistent + SystemMaxUse=200M + MaxRetentionSec=1month + become: true + + - name: Pre-create /var/log/journal to simulate existing persistent journal + ansible.builtin.file: + path: /var/log/journal + state: directory + mode: '2755' + become: true + + - name: Create a sentinel file inside /var/log/journal to confirm non-destructive behavior + ansible.builtin.copy: + dest: /var/log/journal/.keep + content: "pre-existing log marker" + mode: '0644' + become: true diff --git a/ansible/roles/kubernetes_components/molecule/journald-disabled/verify.yml b/ansible/roles/kubernetes_components/molecule/journald-disabled/verify.yml new file mode 100644 index 0000000000..3f5782c9ed --- /dev/null +++ b/ansible/roles/kubernetes_components/molecule/journald-disabled/verify.yml @@ -0,0 +1,44 @@ +--- +- name: Verify journald disabled path + hosts: all + gather_facts: false + tasks: + - name: Stat journald drop-in file + ansible.builtin.stat: + path: /etc/systemd/journald.conf.d/10-persistent-storage.conf + register: journald_dropin_stat + + - name: Assert drop-in is absent when persistent storage is disabled + ansible.builtin.assert: + that: + - not journald_dropin_stat.stat.exists + fail_msg: >- + journald drop-in must be absent when journald_persistent_storage is false, + but the file exists at /etc/systemd/journald.conf.d/10-persistent-storage.conf + + - name: Stat /var/log/journal directory + ansible.builtin.stat: + path: /var/log/journal + register: journal_dir_stat + + - name: Assert /var/log/journal is preserved (non-destructive when disabled) + ansible.builtin.assert: + that: + - journal_dir_stat.stat.exists + - journal_dir_stat.stat.isdir + fail_msg: >- + /var/log/journal must be preserved when journald_persistent_storage is false — + the role must not destroy existing log data + + - name: Stat sentinel file inside /var/log/journal + ansible.builtin.stat: + path: /var/log/journal/.keep + register: journal_keep_stat + + - name: Assert sentinel file is preserved (pre-existing logs not deleted) + ansible.builtin.assert: + that: + - journal_keep_stat.stat.exists + fail_msg: >- + Pre-existing /var/log/journal/.keep was deleted — the role must not purge existing logs + unless journald_purge_persistent_logs is explicitly set to true diff --git a/ansible/roles/kubernetes_components/molecule/journald-purge/converge.yml b/ansible/roles/kubernetes_components/molecule/journald-purge/converge.yml new file mode 100644 index 0000000000..39e1652099 --- /dev/null +++ b/ansible/roles/kubernetes_components/molecule/journald-purge/converge.yml @@ -0,0 +1,11 @@ +--- +- name: Converge + hosts: all + become: true + roles: + - role: ../../../../kubernetes_components + vars: + kubernetes_disable_swap: false + kubernetes_enable_modules: false + journald_persistent_storage: false + journald_purge_persistent_logs: true # Validate the explicit purge opt-in path diff --git a/ansible/roles/kubernetes_components/molecule/journald-purge/molecule.yml b/ansible/roles/kubernetes_components/molecule/journald-purge/molecule.yml new file mode 100644 index 0000000000..93b7b1020d --- /dev/null +++ b/ansible/roles/kubernetes_components/molecule/journald-purge/molecule.yml @@ -0,0 +1,29 @@ +--- +dependency: + name: galaxy +driver: + name: docker +platforms: + - name: instance + image: "geerlingguy/docker-ubuntu2404-ansible:latest" + platform: "${MOLECULE_DOCKER_PLATFORM:-linux/amd64}" + pre_build_image: true + privileged: true + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host + command: /lib/systemd/systemd + env: + DEBIAN_FRONTEND: noninteractive + groups: + - orange_pi_zero3 +provisioner: + name: ansible + inventory: + host_vars: + instance: + ansible_python_interpreter: /usr/bin/python3 + hardware_type: orange_pi_zero3 + architecture: arm64 +verifier: + name: ansible diff --git a/ansible/roles/kubernetes_components/molecule/journald-purge/prepare.yml b/ansible/roles/kubernetes_components/molecule/journald-purge/prepare.yml new file mode 100644 index 0000000000..8d19222f50 --- /dev/null +++ b/ansible/roles/kubernetes_components/molecule/journald-purge/prepare.yml @@ -0,0 +1,97 @@ +--- +- name: Prepare environment for journald purge scenario + hosts: all + gather_facts: true + tasks: + - name: Update package cache + ansible.builtin.package: + update_cache: true + become: true + + - name: Install required packages + ansible.builtin.package: + name: + - systemd + - dbus + - init + - iproute2 + - kmod + - procps + - curl + - gnupg + - ca-certificates + state: present + become: true + + - name: Create kernel modules directory + ansible.builtin.file: + path: /lib/modules + state: directory + mode: '0755' + become: true + + - name: Ensure systemd is running + ansible.builtin.systemd: + name: systemd-logind + state: started + become: true + failed_when: false + + - name: Create SSH host keys + ansible.builtin.command: ssh-keygen -A + become: true + changed_when: false + failed_when: false + + - name: Create mock kernel modules for testing + ansible.builtin.shell: | + set -o pipefail + mkdir -p /lib/modules/$(uname -r) + touch /lib/modules/$(uname -r)/modules.dep + touch /lib/modules/$(uname -r)/modules.symbols + become: true + changed_when: false + failed_when: false + + - name: Mock modprobe for container environment + ansible.builtin.copy: + dest: /usr/local/bin/modprobe + content: | + #!/bin/bash + # Mock modprobe for container testing + echo "Mock modprobe: $*" + exit 0 + mode: '0755' + become: true + + - name: Ensure journald drop-in directory exists + ansible.builtin.file: + path: /etc/systemd/journald.conf.d + state: directory + mode: '0755' + become: true + + - name: Pre-create role-managed journald drop-in (simulates prior persistent-enable run) + ansible.builtin.copy: + dest: /etc/systemd/journald.conf.d/10-persistent-storage.conf + mode: '0644' + content: | + [Journal] + Storage=persistent + SystemMaxUse=200M + MaxRetentionSec=1month + become: true + + - name: Pre-create /var/log/journal directory (simulates existing persistent journal) + ansible.builtin.file: + path: /var/log/journal + state: directory + mode: '2755' + become: true + + - name: Pre-create sentinel file inside /var/log/journal + ansible.builtin.copy: + dest: /var/log/journal/existing-log.journal + content: "sentinel" + mode: '0644' + become: true diff --git a/ansible/roles/kubernetes_components/molecule/journald-purge/verify.yml b/ansible/roles/kubernetes_components/molecule/journald-purge/verify.yml new file mode 100644 index 0000000000..897ee69dbe --- /dev/null +++ b/ansible/roles/kubernetes_components/molecule/journald-purge/verify.yml @@ -0,0 +1,43 @@ +--- +- name: Verify journald purge path + hosts: all + gather_facts: false + tasks: + - name: Check if role-managed journald drop-in was removed + ansible.builtin.stat: + path: /etc/systemd/journald.conf.d/10-persistent-storage.conf + register: journald_dropin + + - name: Assert role-managed drop-in is absent + ansible.builtin.assert: + that: + - not journald_dropin.stat.exists + fail_msg: >- + /etc/systemd/journald.conf.d/10-persistent-storage.conf must be absent + when journald_persistent_storage is false. + + - name: Check if /var/log/journal was purged + ansible.builtin.stat: + path: /var/log/journal + register: journal_dir + + - name: Assert /var/log/journal was deleted when purge flag is set + ansible.builtin.assert: + that: + - not journal_dir.stat.exists + fail_msg: >- + /var/log/journal must be deleted when journald_purge_persistent_logs is true, + but the directory still exists. + + - name: Check if sentinel file was purged + ansible.builtin.stat: + path: /var/log/journal/existing-log.journal + register: sentinel_file + + - name: Assert sentinel file inside /var/log/journal was also deleted + ansible.builtin.assert: + that: + - not sentinel_file.stat.exists + fail_msg: >- + Sentinel file /var/log/journal/existing-log.journal must be deleted + when journald_purge_persistent_logs is true. diff --git a/ansible/roles/kubernetes_components/tasks/journald.yml b/ansible/roles/kubernetes_components/tasks/journald.yml new file mode 100644 index 0000000000..68d44f9155 --- /dev/null +++ b/ansible/roles/kubernetes_components/tasks/journald.yml @@ -0,0 +1,49 @@ +--- +# Persist host logs so pre-reboot storage/kernel errors survive when media is still readable. +# Bounded by journald_system_max_use to avoid exhausting the SD card on control-plane nodes. +# Only enabled when journald_persistent_storage: true (set per-playbook, not globally). + +- name: Ensure persistent journald directory exists + ansible.builtin.file: + path: /var/log/journal + state: directory + mode: '2755' + become: true + when: journald_persistent_storage | bool + notify: Restart systemd-journald + +- name: Ensure journald drop-in directory exists + ansible.builtin.file: + path: /etc/systemd/journald.conf.d + state: directory + mode: '0755' + become: true + +- name: Configure bounded persistent journald storage + ansible.builtin.copy: + dest: /etc/systemd/journald.conf.d/10-persistent-storage.conf + mode: '0644' + content: | + [Journal] + Storage=persistent + SystemMaxUse={{ journald_system_max_use }} + MaxRetentionSec={{ journald_max_retention_sec }} + become: true + when: journald_persistent_storage | bool + notify: Restart systemd-journald + +- name: Remove role-managed journald drop-in when persistent storage is disabled + ansible.builtin.file: + path: /etc/systemd/journald.conf.d/10-persistent-storage.conf + state: absent + become: true + when: not (journald_persistent_storage | bool) + notify: Restart systemd-journald + +- name: Purge persistent journal directory (explicit opt-in only) + ansible.builtin.file: + path: /var/log/journal + state: absent + become: true + when: not (journald_persistent_storage | bool) and (journald_purge_persistent_logs | bool) + notify: Restart systemd-journald diff --git a/ansible/roles/kubernetes_components/tasks/main.yml b/ansible/roles/kubernetes_components/tasks/main.yml index d573e3bf47..7f02eece3e 100644 --- a/ansible/roles/kubernetes_components/tasks/main.yml +++ b/ansible/roles/kubernetes_components/tasks/main.yml @@ -15,3 +15,6 @@ - name: Include kubeadm configuration tasks ansible.builtin.include_tasks: kubeadm.yml + +- name: Include journald configuration tasks + ansible.builtin.include_tasks: journald.yml diff --git a/docs/project_docs/BOXP-127/plan.md b/docs/project_docs/BOXP-127/plan.md new file mode 100644 index 0000000000..b0dc64ce23 --- /dev/null +++ b/docs/project_docs/BOXP-127/plan.md @@ -0,0 +1,65 @@ +# BOXP-127: control-plane ノード電源断原因調査と恒久対策 + +## 根本原因 + +Orange Pi Zero3 の control-plane ノード(shanghai-1/2/3)が繰り返し到達不能になる原因は、**microSD カードの書き込み耐久性不足**による OS フリーズ・ファイルシステム read-only 化。 + +- INC-2(2026-07-06, shanghai-2): SD カード障害 → OS 再イメージで復旧 +- INC-4(2026-07-12, shanghai-2): 交換後わずか 7 日で再障害 → etcd WAL 書き込み負荷による急速摩耗 +- BOXP-127(2026-07-22, shanghai-1): 同パターン + +## 即座の対応(実施済み・手動) + +1. shanghai-1 の電源サイクル(ユーザーが 2026-07-23 に実施) +2. 起動しない場合は `shanghai-control-plane-sdcard-failure.md` runbook に従い SD カード交換 → etcd member re-join + +## 恒久対策の優先順位 + +### 1. journald 永続化(本 PR: arch repo) + +`kubernetes_components` Ansible ロールに journald.yml タスクを追加。 + +- `/var/log/journal` ディレクトリを作成し `Storage=persistent` に設定 +- `SystemMaxUse=200M`, `MaxRetentionSec=1month` で SD カード消費を抑制 +- デフォルト無効(`journald_persistent_storage: false`)。control-plane playbook 側で `true` に設定することで control-plane ノード限定で有効化。worker ノードには適用されない +- 次回 Ansible Apply 時に全 control-plane ノードに適用される + +**効果**: 次回 SD カード障害時に障害直前のカーネルログ・ストレージエラー(`EXT4-fs error`, `mmcblk0: error -110`)が保存され、根本原因の確定が可能になる。 + +### 2. Alertmanager ルール追加(lolice repo: feature/control-plane-node-alert ブランチ) + +`control-plane-node-rules.yaml` に以下のアラートを追加: + +| アラート名 | 条件 | 重要度 | +|---|---|---| +| `ControlPlaneNodeNotReady` | control-plane ノードが 5 分以上 NotReady | critical | +| `EtcdMemberDown` | etcd member 数が 2 未満 | critical | +| `EtcdHighFsyncDuration` | WAL fsync p99 > 500ms が 10 分継続 | warning | +| `ControlPlaneFilesystemReadOnly` | `/`, `/var`, `/var/lib` が read-only | critical | +| `ControlPlaneHighDiskIOWait` | mmcblk デバイスの I/O 使用率 > 90% が 15 分継続 | warning | + +### 3. 高耐久メディアへの移行(今後の実施) + +優先順位: +1. **高耐久 microSD(A2/SLC キャッシュ付き)への交換**: Samsung PRO Endurance / Western Digital Purple SC QD101 / SanDisk MAX Endurance などを推奨。TBW 値が通常品の 10〜40 倍。 +2. **USB SSD への移行(最優先推奨)**: etcd データディレクトリ(`/var/lib/etcd`)を USB SSD にバインドマウント。SD カードを読み取り専用 OS 起動用に限定。 +3. **eMMC への移行**: Orange Pi Zero3 には eMMC スロットがない(ボード非対応)ため対象外。 + +## 実施済み変更 + +- `arch/ansible/roles/kubernetes_components/tasks/journald.yml`: journald 永続化タスク(新規) +- `arch/ansible/roles/kubernetes_components/tasks/main.yml`: journald.yml を include +- `arch/ansible/roles/kubernetes_components/defaults/main.yml`: `journald_persistent_storage` デフォルト変数追加 +- `arch/ansible/roles/kubernetes_components/handlers/main.yml`: `Restart systemd-journald` ハンドラ追加 +- `lolice/argoproj/prometheus-operator/control-plane-node-rules.yaml`: etcd・SDカード I/O アラート追加 + +## journald 無効時の設計方針 + +`journald_persistent_storage: false`(ロールデフォルト)では: + +1. **ロール管理の drop-in のみを削除する**(`state: absent`)。`Storage=volatile` を書き込むことはしない。 +2. **既存の `/var/log/journal` は保持する**。ログの破壊的削除は `journald_purge_persistent_logs: true` の明示指定時だけ行う。 +3. **Storage=auto 動作への委任は意図的な設計**。drop-in 不在で journald はデフォルトの `Storage=auto` に戻る。新規ノードや `/var/log/journal` を持たないノードでは volatile になり永続化しない。これが worker ノードの想定動作。 +4. **Molecule 検証**では、無効時に drop-in が存在しないことを確認する(`journald-disabled` シナリオ)。 + +> `/var/log/journal` が既に存在するホストで false に切り替えた場合、`Storage=auto` により journald が引き続き永続化する可能性がある。これは既存ログを保全するための意図的な非破壊設計。そのようなホストで完全に volatile に戻したい場合は `journald_purge_persistent_logs: true` を使用すること。