📚 連載:Ansibleが理解できない理由はLinuxにあった【OS編】第7回:【No space left on device】ディスク容量不足の原因と調査方法(df / du / log)
Linuxの仕組みからAnsibleを理解するシリーズです。
0️⃣ Ansibleの仕組み
1️⃣ Linuxの構造(Kernel / Shell / Filesystem)
2️⃣ command not foundの原因:PATH
3️⃣ SSH接続できない理由:鍵認証とポート
4️⃣ Permission deniedの原因:ユーザーと権限
5️⃣ サービスが起動しない理由:systemd
6️⃣ cronが動かない理由:PATH問題
7️⃣ ディスク満杯になる理由
8️⃣ サーバーが遅くなる理由
🗺️ 初めての方・シリーズの全体像を知りたい方はこちら
本記事は、OSの仕組みからAnsible設計までを繋ぐ連載シリーズの一部です。
「どこから読み始めればいいか」あるいは、「OS/Shell/Ansible編の関係性」 について把握されたい場合は、以下の統合ガイドで整理しています。
📑 【OS編】全体のまとめはこちら
→ Ansibleが理解できない理由はLinuxにあった【OS編】まとめ
📑 連載の移動
前の記事:【OS編】第6回 | 次の記事:【OS編】第8回
シリーズ全体構造(学習 × 問題解決)
本シリーズは
「理解(各記事)」と「問題解決(逆引き辞典)」を組み合わせて
スキルを身につける構成になっています。
この図は、どこから学び、どこに進めばよいかを示した“ロードマップ”です。
📍 現在の位置
現在はこの図の「OS編 第7回」になります
📍 はじめに:この記事のスタンス
定期実行(cron)の設定まで終え、運用フェーズに入った際に直面するのが次のエラーです。
- 「FAILED! => msg: No space left on device と表示され、書き込みが失敗する」
- 「ファイルを削除して容量を空けたはずなのに、Ansibleの実行結果がエラーのまま変わらない」
- 「df -h で容量(GB)は余っているように見えるのに、新規ファイルの作成が拒否される」
⚠️ 注意事項
この記事は、Linuxのファイルシステム(ext4/xfs等)の全機能を網羅する「教科書」ではありません。
目的はただひとつです。
Ansibleで copy / template / unarchive モジュール 等が失敗したとき、
- 「OS側のどのステップで書き込みが拒否されたのか」を即座に特定すること
です。
「容量は空けたはずだ。だが、書き込めない」
この実務上の壁を突破するために、Linuxの「Filesystem」による書き込み判定の仕組みと、Ansibleがエラーを出す物理的な理由を紐解いていきます。
📋 目次
- 前回の振り返り(第6回)
- 逆引き辞典との連動
- Filesystemの構造:データと管理情報の分離
- なぜ失敗するのか:OSによる「3つの実行チェック」
- 状態コマンドの読み方:OSが残した「数字」を確認する
- Ansibleとディスク容量:転送・展開時の判定ロジック
- 【実証】OSの拒絶反応:ディスクフルでAnsibleを動かす
- 実務で遭遇する「書き込み失敗」の要因と根本的な改善策
- まとめ:原因の見え方が変わる「調査の原則」
- 次回予告
- 連載一覧:Ansibleが理解できない理由はLinuxにあった
1. 前回の振り返り(第6回)
前回の記事では、定期実行の仕組み cron について解説しました。OS側の実行フロー(Step 1〜3)に当てはめることで、ログの有無から失敗箇所を特定しました。
今回は、その実行結果を保存する物理的な限界、Filesystem の判定ロジックについて学びます。
2. 逆引き辞典との連動
具体的なエラー名から原因を特定したい場合は、以下の逆引き辞典を活用してください。
Ansibleが理解できない理由はLinuxにあった :【保存版】Ansibleトラブル逆引き辞典|エラー別の原因と解決フローまとめ
- まず「逆引き辞典」で該当ステップを特定する
- 次に「本編(各連載記事)」で仕組みを理解する
3. Filesystemの構造:データと管理情報の分離
Linuxがディスクにデータを書き込む際、内部では「データそのもの」と「管理情報」を分けて扱っています。
| 構成要素 | 役割 | 実務での指標 |
|---|---|---|
| Data Block | 実際のデータ内容を保存する領域 | 物理容量(GB/MB) |
| Inode | ファイルの属性や場所を記録する索引 | ファイル個数(個) |
| File Descriptor | プロセスがファイルを開いている状態 | プロセス保持(Handle) |
- Ansibleでファイルを配置する際、OSはこれら全ての要素に「空き」があるかをチェックしています。
4. なぜ失敗するのか:OSによる「3つの実行チェック」
OSがファイルを作成・書き込みしようとする際、以下の 「3つのステップ」 を順番に確認します。この判定ロジックのどこかで「NO」が出た瞬間、処理は失敗し、Ansibleはエラーを返します。
-
重要:OSはこの順番で1つずつチェックし、どこかでNGになるとその時点で停止します
-
この3ステップを「上から順に潰していく」と、原因を特定できます
🔍 OSの判定ロジック(チェックリスト)
| ステップ | 判定内容 | 拒絶される主な理由(実務例) |
|---|---|---|
| Step 1: 物理容量 (Capacity) | データを書き込む容量はあるか? | ログファイルの肥大化により、ディスク容量(GB)を使い切っている。 |
| Step 2: 管理情報 (Inode) | 新しいファイルを作る索引はあるか? | 一時ファイル等の大量生成により、ファイル個数の上限に達している。 |
| Step 3: 実使用状態 (Usage State) | OSが「使用中」と判断していないか? | ファイルをrmしたが、プロセスが開き続けているため容量が解放されない。 |
【ディスク書き込みフロー(OSの内部)】
- トラブル時は「どのStepで止まっているか」をこの図に当てはめて考えます
OS 書き込み判定プロセス (Logical Flow)
※どこかでNGになると、その時点で停止する(=後続のStepには進まない)
5. 状態コマンドの読み方:OSが残した「数字」を確認する
ディスク問題はエラーログだけでなく、OSが保持している 「現在の数字(使用率)」 を直接確認します。
🔍 数字から失敗箇所を特定する(Step逆引き)
| 確認コマンド | 判定指標 | 失敗時の対応Step |
|---|---|---|
df -h |
Use% が 100% |
Step 1(Capacity) |
df -i |
IUse% が 100% |
Step 2(Inode) |
lsof | grep deleted |
消したファイルが残っている | Step 3(Handle) |
- 「df -h が 100%」= Step 1。巨大なファイルを削除する必要がある。
- 「df -h は余裕だが df -i が 100%」= Step 2。大量の小ファイルを削除する必要がある。
- 「消したのに df -h が減らない」= Step 3。対象のプロセスを再起動する必要がある。
※詳細はセクション7参照
6. Ansibleとディスク容量:転送・展開時の判定ロジック
Ansibleの copy や unarchive モジュールは、ターゲットOS上で一時ファイルを作成・展開します。
🔍 判定ロジック
Ansibleは書き込みの「成功/失敗」をOSのシステムコール(write)の結果から判断します。
-
一時転送: Ansibleはまず
/tmp等にファイルを転送。この時、OSが Step 1 をチェック。 - 展開: 解凍時に大量のファイルを生成。この時、OSが Step 2 をチェック。
-
成功の意味: Ansibleが
SUCCESSを出すのは、「OS側ですべてのStepをパスし、ディスクに書き込みが完了した」 場合のみです。
💡 裏付け:Ansibleは「書き込み」までしか責任を持たない
AnsibleタスクがNo space left on deviceで失敗するのは、Ansibleの不具合ではなく、「OS側のStep判定で拒絶された」 実証を報告をしているに過ぎません。Ansibleは設定内容を運ぶ役割であり、それを受け入れる(ディスクに書き込む)のはOSの責任範囲です。
7. 【実証】OSの拒絶反応:ディスクフルでAnsibleを動かす
セクション4で解説した「OSの3つの判定ロジック」が、実際にAnsibleの動作をどう制限するのか。実機での検証結果をもとに確認します。
7-1. 【Step 1】物理容量の枯渇 (Capacity)
物理容量(GB/MB)が不足している環境で、Ansibleの copy モジュールを実行した際の挙動を確認します。
① 検証環境の構築(リモートノード側)
ルートパーティションへの影響を避けるため、イメージファイルを用いたループバックデバイスを作成し、意図的に容量を枯渇させます。
テスト用仮想ディスクの作成
# 作成前の状態確認
[ansibleuser@localhost ~]$ df -h /tmp
ファイルシス サイズ 使用 残り 使用% マウント位置
/dev/mapper/rlm-root 17G 2.9G 15G 17% /
# 500MBの空ファイルを作成
[ansibleuser@localhost ~]$ sudo dd if=/dev/zero of=/tmp/test_disk.img bs=1M count=500
500+0 レコード入力
500+0 レコード出力
524288000 bytes (524 MB, 500 MiB) copied, 1.94802 s, 269 MB/s
# ファイル作成後の確認
[ansibleuser@localhost ~]$ ls -l /tmp
合計 1534976
-rw-r--r--. 1 root root 1047527424 5月 5 09:41 disk_filler
drwx------. 3 root root 17 5月 5 09:12 systemd-private-4cfa94790d5e4a12b010b95cb6abf87f-chronyd.service-S1tmvU
...(中略)...
-rw-r--r--. 1 root root 524288000 5月 5 09:59 test_disk.img
[ansibleuser@localhost ~]$ df -h /tmp
ファイルシス サイズ 使用 残り 使用% マウント位置
/dev/mapper/rlm-root 17G 3.4G 14G 20% /
仮想ディスクのフォーマット
# イメージファイルを XFS 形式で初期化する
[ansibleuser@localhost ~]$ sudo mkfs.xfs /tmp/test_disk.img
meta-data=/tmp/test_disk.img isize=512 agcount=4, agsize=32000 blks
= sectsz=512 attr=2, projid32bit=1
= crc=1 finobt=1, sparse=1, rmapbt=0
= reflink=1 bigtime=1 inobtcount=1 nrext64=0
data = bsize=4096 blocks=128000, imaxpct=25
= sunit=0 swidth=0 blks
naming =version 2 bsize=4096 ascii-ci=0, ftype=1
log =internal log bsize=4096 blocks=16384, version=2
= sectsz=512 sunit=0 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0
マウントと容量の枯渇(100%状態の作成)
# マウントポイントの作成とマウント実行
[ansibleuser@localhost ~]$ sudo mount -o loop /tmp/test_disk.img /mnt/test_area
[ansibleuser@localhost ~]$ sudo chown ansibleuser:ansibleuser /mnt/test_area
# ddコマンドで限界まで書き込む
[ansibleuser@localhost ~]$ dd if=/dev/zero of=/mnt/test_area/filler bs=1M count=490
dd: '/mnt/test_area/filler' の書き込みエラー: デバイスに空き領域がありません
408+0 レコード入力
407+0 レコード出力
426770432 bytes (427 MB, 407 MiB) copied, 12.467 s, 34.2 MB/s
# 最終的な空き容量の確認
[ansibleuser@localhost ~]$ df -h /mnt/test_area
ファイルシス サイズ 使用 残り 使用% マウント位置
/dev/loop0 436M 436M 728K 100% /mnt/test_area
② Ansibleによる書き込みテスト(コントローラーノード側)
空き容量が 728KB の状態にある /mnt/test_area ディレクトリに対し、コントロールノードから 10MB のファイルを転送します。
実行するPlaybook (step1_capacity_test.yml)
---
- name: Step 1 物理容量不足の検証
hosts: test_servers
gather_facts: false
tasks:
- name: 10MBのファイルを転送(空き728Kの領域が対象)
ansible.builtin.copy:
content: "{{ 'a' * 1024 * 1024 * 10 }}"
dest: /mnt/test_area/test_file.txt
実行コマンド
ansible-playbook -i inventory.ini step1_capacity_test.yml -v
実行結果(Ansibleログ)
(ansible) [root@localhost workspace]# ansible-playbook -i inventory.ini step1_capacity_test.yml -v
TASK [10MBのファイルを転送(空き728Kの領域が対象)] ********************************************
An exception occurred during task execution...
The error was: OSError: [Errno 28] デバイスに空き領域がありません: b'/home/ansibleuser/.ansible/tmp/ansible-tmp-...' -> b'/mnt/test_area/.ansible_tmppy36b5vctest_file.txt'
fatal: [192.168.1.21]: FAILED! => {
"changed": false,
"msg": "Failed to replace file: ... [Errno 28] デバイスに空き領域がありません"
}
```bash
(ansible) [root@localhost workspace]# ansible-playbook -i inventory.ini step1_capacity_test.yml -v
Using /opt/ansible/workspace/ansible.cfg as config file
PLAY [Step 1 物理容量不足の検証] ************************************************************************************************************************************************************
TASK [10MBのファイルを転送(空き728Kの領域が対象)] *****************************************************************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: OSError: [Errno 28] デバイスに空き領域がありません: b'/home/ansibleuser/.ansible/tmp/ansible-tmp-1777946952.157519-1603-246394282255642/source' -> b'/mnt/test_area/.ansible_tmppy36b5vctest_file.txt'
fatal: [192.168.1.21]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "changed": false, "checksum": "fbcf384f1c96e3d24fb25b749b00241a5cc4690f", "msg": "Failed to replace file: b'/home/ansibleuser/.ansible/tmp/ansible-tmp-1777946952.157519-1603-246394282255642/source' to /mnt/test_area/test_file.txt: [Errno 28] デバイスに空き領域がありませ ん: b'/home/ansibleuser/.ansible/tmp/ansible-tmp-1777946952.157519-1603-246394282255642/source' -> b'/mnt/test_area/.ansible_tmppy36b5vctest_file.txt'"}
PLAY RECAP **********************************************************************************************************************************************************************************
192.168.1.21 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
③ ログの読み解きと考察
今回のテスト結果から、以下が実証されました。
-
OSエラーの直接検知:
エラーログに含まれる[Errno 28]デバイスに空き領域がありません は、Linuxカーネルが返す標準的なエラーコード(ENOSPC)です。これはAnsible自体の不具合ではなく、書き込み先のOSが Step 1(物理容量) の判定において「容量不足」を回答したことをそのまま出力しています。 -
リネーム(移動)処理での失敗:
ログのsource -> destinationのパスを確認すると、Ansibleが一時ディレクトリに作成したファイルを、最終目的地である/mnt/test_area/内のファイルへリネームしようとした際にエラーが発生していることが分かります。 -
判定順序の裏付け:
今回の検証環境では、後続の Step 2(Inode)や Step 3(Handle)にはまだ余裕があります。しかし、最初の Step 1(物理容量) でNGが出たため、OSは即座に処理を中断しました。
テスト結果のまとめ:
Ansibleタスクが失敗したのは、OS側で定義されている「書き込みルール(物理容量の空きが必要)」に抵触したためです。AnsibleはOSのシステムコールの結果を忠実に報告する役割を果たしていると言えます。
7-2. 【Step 2】管理情報(Inode)の枯渇 (Inode)
物理容量(GB/MB)に空きがある状態で、ファイル作成に必要な管理情報(Inode)が上限に達した場合のAnsibleの挙動を検証します。
① 検証環境の構築(ターゲットノード)
テスト用領域のリセット
Step 1で使用した充填用ファイルを削除し、物理容量に余裕を持たせた状態から開始します。
[ansibleuser@localhost ~]$ ls -l /mnt/test_area/filler
-rw-r--r--. 1 ansibleuser ansibleuser 426770432 5月 5 10:04 /mnt/test_area/filler
[ansibleuser@localhost ~]$ rm /mnt/test_area/filler
[ansibleuser@localhost ~]$ ls -l /mnt/test_area/
合計 0
Inodeを 100% にする(大量の空ファイル生成)
空ファイルを連続して作成し、ファイルシステムのInode(索引領域)を枯渇させます。
# [ターゲットノード] 空ファイル作成による Inode 消費
[ansibleuser@localhost ~]$ cd /mnt/test_area
[ansibleuser@localhost test_area]$ i=0; while true; do touch file_$i || break; i=$((i+1)); done
# 実行結果例
touch: 'file_256061' に touch できません: デバイスに空き領域がありません
OSの状態確認
Inodeの使用率が 100% に達していることを確認します。
# [ターゲットノード]
[ansibleuser@localhost test_area]$ df -i /mnt/test_area
ファイルシス Iノード I使用 I残り I使用% マウント位置
/dev/loop0 256064 256064 0 100% /mnt/test_area
容量(GB)には余裕がありますが、Inodeの残り数が 0 となり、新規ファイルの作成が不可能な状態となりました。
② Ansibleによる書き込みテスト
Inodeが枯渇しているターゲットノードに対し、コントロールノードから最小サイズのファイルを転送します。
実行するPlaybook (step2_inode_test.yml)
---
- name: Step 2 管理情報(Inode)不足の検証
hosts: test_servers
gather_facts: false
tasks:
- name: 小さなファイルを転送(Inode不足の領域が対象)
ansible.builtin.copy:
content: "Inode check"
dest: /mnt/test_area/inode_test.txt
実行コマンド
ansible-playbook -i inventory.ini step2_inode_test.yml -v
実行結果(Ansibleログ)
(ansible) [root@localhost workspace]# ansible-playbook -i inventory.ini step2_inode_test.yml -v
Using /opt/ansible/workspace/ansible.cfg as config file
PLAY [Step 2 管理情報(Inode)不足の検証] *****************************************************************************************************************************************************
TASK [小さなファイルを転送(Inode不足の領域が対象)] ****************************************************************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: OSError: [Errno 18] 無効なクロスデバイスリンクです: b'/home/ansibleuser/.ansible/tmp/ansible-tmp-1777954979.9483721-1722-191021103580996/source' -> b'/mnt/test_area/inode_test.txt'
fatal: [192.168.1.21]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "changed": false, "checksum": "254d736b7de69a28930ebc509e614674a171a7fa", "msg": "The destination directory (/mnt/test_area) is not writable by the current user. Error was: [Errno 28] デバイスに空き領域がありません: b'/mnt/test_area/.ansible_tmpxa5re8lvinode_test.txt'"}
PLAY RECAP **********************************************************************************************************************************************************************************
192.168.1.21 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
③ ログの読み解きと考察
(補足)
※補足:Errno 18 (Invalid cross-device link) について
ログに見える Errno 18 は、Ansibleが /home(一時作成先)から /mnt(最終目的地)という異なるファイルシステムを跨いでファイルを移動(rename)しようとしたために発生しています。この時、Ansibleは内部的に「コピー&削除」の手順に切り替えてリトライを試みますが、最終的に移動先のOS側で行われた Step 2(Inode) の空き状況判定をパスできず、Errno 28 で停止しています。
今回のテスト結果から、Inodeが枯渇した際のOSおよびAnsibleの挙動について、以下が実証されました
-
エラーメッセージの重複(実務上の注意点):
ログを確認すると、物理容量不足(Step 1)の際と同じ [Errno 28] デバイスに空き領域がありません が出力されています。OS(Linuxカーネル)は、物理的なバイト数が足りない場合も、管理情報(Inode)が足りない場合も、同じエラーコードを返すことがあります。そのため、Ansibleのエラーメッセージだけを見て「ディスク容量(GB)を空ければ解決する」と判断するのは危険であり、必ずdf -hとdf -iの両方を確認する必要があることが分かります。 -
一時ディレクトリとの関係性:
エラーログ内のThe destination directory (/mnt/test_area) is not writableという記述は、権限不足を意味しているのではなく、「新規のファイル(ラベル)を作成するための枠(Inode)がないために書き込めない」 状況を指しています。Ansibleは転送プロセスにおいて一時ファイルを作成しますが、そのメタ情報をファイルシステムに登録する段階で、OSにより処理が制限された形です。 -
判定順序の証明:
物理容量(GB)には数億バイトの空きがあるにもかかわらず、OSは Step 2(Inode) のリソース不足を検知して処理をブロックしました。これにより、ファイルシステムが「データ保存領域」と「管理情報領域」を個別のリソースとして管理しており、いずれかのリソースが枯渇した時点で後続の処理が不能になる構造が実証されました。
テスト結果のまとめ:
Ansibleが No space left on device と報告した際、物理容量に余裕がある場合は Inodeの枯渇 を疑うべきです。この判定はOSの深部(システムコールレベル)で行われており、Ansibleはあくまでその結果を正確に反映しているに過ぎません。
7-3. 【Step 3】実使用状態の不一致 (Usage State)
ファイルを削除してもプロセスがハンドルを保持している場合の挙動を検証する。
① 検証環境の構築(ターゲットノード)
テスト用領域のクリーンアップ
Step 2で使用したファイルを削除し、Inodeおよび物理容量を初期状態に戻します。
[ansibleuser@localhost test_area]$ find . -name "file_*" -delete
[ansibleuser@localhost test_area]$ ls -l /mnt/test_area/
合計 0
# 状態確認(容量・Inodeともに空きがあることを確認)
[ansibleuser@localhost test_area]$ df -h /mnt/test_area
ファイルシス サイズ 使用 残り 使用% マウント位置
/dev/loop0 436M 29M 408M 7% /mnt/test_area
「ファイル削除後も容量が解放されない状態」の再現
以下の手順で、ディレクトリ上からは消失しているが、ファイルシステム上は容量を消費し続けている状態を作成します。
-
400MBのファイルを作成する
[ansibleuser@localhost test_area]$ dd if=/dev/zero of=/mnt/test_area/holding_file bs=1M count=400 400+0 レコード入力 400+0 レコード出力 419430400 bytes (419 MB, 400 MiB) copied, 6.06608 s, 69.1 MB/s -
プロセスにファイルを開かせ続ける(バックグラウンド実行)
tailコマンドを使用して、プロセスがファイルハンドルを保持し続ける状態を作ります。[ansibleuser@localhost test_area]$ tail -f /mnt/test_area/holding_file > /dev/null & [1] 260791 - ファイルを削除する
[ansibleuser@localhost test_area]$ rm /mnt/test_area/holding_file
OSの状態確認
lsコマンドでは確認できませんが、dfコマンドでは容量が解放されていないことを確認します。
# ファイルリストには表示されない
[ansibleuser@localhost test_area]$ ls -l /mnt/test_area/
合計 0
# 使用率は高止まりしたまま(400MBを消費中)
[ansibleuser@localhost test_area]$ df -h /mnt/test_area
ファイルシス サイズ 使用 残り 使用% マウント位置
/dev/loop0 436M 429M 7.8M 99% /mnt/test_area
# lsofで削除済みファイル(deleted)を保持しているプロセスを確認
[ansibleuser@localhost test_area]$ lsof | grep deleted
tail 260791 ansibleuser 3r REG 7,0 419430400 131 /mnt/test_area/holding_file (deleted)
② Ansibleによる書き込みテスト
ディレクトリ上にファイルは存在しないが、物理的な空き領域(残り16MB)が不足している状態に対し、30MBのファイルをAnsibleで転送します。
実行するPlaybook (step3_usage_test.yml)
---
- name: Step 3 実使用状態の不一致の検証
hosts: test_servers
gather_facts: false
tasks:
- name: 30MBのファイルを転送
ansible.builtin.copy:
content: "{{ 'a' * 1024 * 1024 * 30 }}"
dest: /mnt/test_area/ghost_test.txt
実行コマンド
ansible-playbook -i inventory.ini step3_usage_test.yml -v
(ansible) [root@localhost workspace]# ansible-playbook -i inventory.ini step3_usage_test.yml -v
Using /opt/ansible/workspace/ansible.cfg as config file
PLAY [Step 3 実使用状態の不一致の検証] ******************************************************************************************************************************************************
TASK [30MBのファイルを転送] *****************************************************************************************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: OSError: [Errno 28] デバイスに空き領域がありません: b'/home/ansibleuser/.ansible/tmp/ansible-tmp-1777957953.9492881-1796-156269176373927/source' -> b'/mnt/test_area/.ansible_tmpsi506eaoghost_test.txt'
fatal: [192.168.1.21]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "changed": false, "checksum": "941391288d4280da1ac102b0a83904f3d8c64a18", "msg": "Failed to replace file: b'/home/ansibleuser/.ansible/tmp/ansible-tmp-1777957953.9492881-1796-156269176373927/source' to /mnt/test_area/ghost_test.txt: [Errno 28] デバイスに空き領域がありま せん: b'/home/ansibleuser/.ansible/tmp/ansible-tmp-1777957953.9492881-1796-156269176373927/source' -> b'/mnt/test_area/.ansible_tmpsi506eaoghost_test.txt'"}
PLAY RECAP **********************************************************************************************************************************************************************************
192.168.1.21 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
③ ログの読み解きと考察
今回のテスト結果から、ファイル削除後のリソース保持状態におけるOSおよびAnsibleの挙動について、以下が実証されました
-
物理空き容量に基づく書き込み制限:
lsコマンドの結果ではディレクトリ内にファイルが存在しないにもかかわらず、Ansible は[Errno 28]デバイスに空き領域がありません を出力して停止しました。これは、OS(Linuxカーネル)が「ディレクトリのエントリ(ファイル名)」ではなく、「ファイルシステム全体の物理的な空きブロック数」を書き込みの判定基準としているためです。 -
削除済みファイルによる占有の継続:
lsofの結果が示す通り、tailプロセスがファイルハンドルを保持している間は、rm コマンドを実行してもディスク上のデータブロックは解放されません。OSはこの占有領域を「使用中」と見なすため、Ansibleが新規に作成しようとする30MBのデータに対して、残り7.8MBの空き領域では不足していると正しく判断しています。 -
エラーメッセージの共通性:
今回の結果から、物理容量の枯渇(Step 1)、Inodeの枯渇(Step 2)、および今回のプロセスによる保持(Step 3)のいずれのケースにおいても、OSがAnsible(およびPythonのOSError)に返すエラーメッセージはErrno 28で共通していることが確認できました。
テスト結果のまとめ:
「ファイルが見当たらないのにAnsibleが容量不足で失敗する」という状況は、プロセスが削除済みファイルのハンドルを保持している場合に発生します。この検証により、トラブルシューティング時には df や ls だけでなく、lsof 等を用いてプロセスのファイル保持状況まで調査する必要があることが実証されました。
8. 実務で遭遇する「書き込み失敗」の要因と根本的な改善策
実機検証(セクション7)で証明された通り、AnsibleがFAILEDを返してタスクが停止した場合、その原因はAnsibleの設定ではなくOS側の判定ロジック(Step 1〜3)に潜んでいます。ここでは、現場で多発する要因と、その根本的な改善策をまとめます。
8-1. 【要因1】物理リソース枯渇(Capacity/Inode)による書き込み停止
実機検証 7-1 および 7-2 で確認した通り、OSが物理容量(Step 1)または Inode(Step 2)の不足を検知すると、Ansible は Errno 28 を出力して即座に停止します。これはAnsibleの不備ではなく、OSによるリソース保護の結果です。
- 現象:
ログにNo space left on device (Errno 28)が記録される。copyモジュールの転送中、あるいはunarchiveモジュールの展開中に発生する。 - 改善策:
・Ansibleassertモジュールによる事前事後チェック
タスク実行前に、必要な空き容量やInode数が確保されているかを検証するタスクを組み込みます。
・不要リソースの自動削除
一時ファイルや古いログを削除するタスクを先行して実行し、OS側の「Step 1/2」の判定をパスする状態を確実に作ります。
※このタスクはFacts収集(gather_facts: true)が必要です。
- name: ディスク空き容量の事前チェック(Step 1 対策)
ansible.builtin.assert:
that:
# 空き容量が1GB以上あることを確認
- item.size_available > 1024 * 1024 * 1024
fail_msg: "ターゲット領域({{ item.mount }})の空き容量が不足しています"
loop: "{{ ansible_mounts }}"
# 書き込み先(dest)が含まれるマウントポイントを特定してチェック
when: "item.mount in dest_path"
8-2. 【要因2】プロセス保持(Handle)による見かけ上の容量不足
実機検証 7-3 で証明された通り、rm コマンド等でファイルを削除しても、プロセスがファイルハンドルを保持(Step 3)している限り、OSはディスク領域を解放しません。この状態でAnsibleタスクを実行しても、Errno 28 により阻止されます。
-
現象:
df -h上では空きがあるように見えても(あるいは削除直後でも)、Ansible の書き込みが容量不足で失敗し続ける。 -
改善策:
・lsofによるプロセス保持の特定と解消
ファイルを削除するだけの運用を避け、削除後に該当プロセスを再起動(restart)またはリロード(reload)するタスクをセットで記述します。 -
ログローテーション(logrotate)の適切な運用
アプリケーションログは手動削除ではなくlogrotateを介して適切に処理し、プロセスへのシグナル送信(HUP等)を確実に行うことで、Step 3 の状態不整合を防止します。
- name: 肥大化したログを削除し、サービスを再起動(Step 3 対策)
block:
- name: ログファイルを削除
ansible.builtin.file:
path: /var/log/myapp/access.log
state: absent
- name: サービスを再起動
ansible.builtin.systemd:
name: myapp
state: restarted
※サービス停止が許容されない場合は、copytruncate オプション等の検討が必要です。
9. まとめ:原因の見え方が変わる「調査の原則」
Ansibleでディスクエラーが出たときは、以下の3ステップを順番に確認してください。
【ディスク 実行失敗の調査手順】
-
「物理容量(Capacity)」は正しいか?
・df -hを確認。100%なら巨大ファイルを疑う。
・ OSの判定: 物理ブロックに空きがない場合、OSは書き込み処理を即座に中断します。 -
「管理情報(Inode)」は足りているか?
・df -iを確認。100%なら大量の小ファイルを疑う。
・ OSの判定: インデックス領域が枯渇している場合、OSは新規ファイルの作成を制限します。 -
「プロセスの掴み(Handle)」はないか?
・lsof | grep deletedを確認。
・OSの判定: プロセスがファイルを保持している間、OSは対象のデータブロックを解放しません。
- 調査の原則
ディスクエラーは、単なる「容量不足」だけではありません。
「消せば空く」という思い込みを捨て、
「OSがどのStepで使用率を計算しているか」の視点に立つこと。
そして
-
「
df -h」「df -i」「lsof」を起点に、どのStepで止まっているかを切り分けること -
数字の変化(状態) = OSがどこまで進んだかを確認すること
これが解決の最短ルートです。
ディスク問題は「Step判定の理解」で必ず特定できます。
10. 次回予告
ディスクリソースの問題を解決し、Playbookが正常に書き込めるようになりました。しかし、実際の運用環境では、リソースの枯渇が「書き込み失敗」ではなく「処理遅延(タイムアウト)」として現れるケースがあります。
「Ansibleタスクが、実行時間の閾値を超えてタイムアウトする」
「特定の条件下でのみ、タスクの完了までに想定以上の時間を要する」
-
なぜ、SSHの応答性能が低下し、接続維持が困難になるのか?
-
CPU・メモリ・プロセスの各リソースにおいて、どの「Step」でボトルネックが発生しているか?
-
Ansibleのオーバーヘッドか、あるいは「OS側の計算リソースの限界」か。
次回、シリーズ最終回。
【第8回】サーバーが遅い原因:CPU・メモリ・プロセスの見方 をお送りします。
「設定は正常。だが、処理が完了しない」
その要因をOSのリソース消費傾向から即座に特定し、OS編を完結させましょう。
📑 連載の移動
前の記事:【OS編】第6回 | 次の記事:【OS編】第8回
📑 【OS編】全体のまとめはこちら
→ Ansibleが理解できない理由はLinuxにあった【OS編】まとめ
🗺️ 初めての方・シリーズの全体像を知りたい方はこちら
本記事は、OSの仕組みからAnsible設計までを繋ぐ連載シリーズの一部です。
「どこから読み始めればいいか」あるいは、「OS/Shell/Ansible編の関係性」 について把握されたい場合は、以下の統合ガイドで整理しています。
11. 連載一覧:Ansibleが理解できない理由はLinuxにあった
| 回数とタイトル | 内容(概要) |
|---|---|
| Ansibleが理解できない理由はLinuxにあった【OS編】:【保存版】Ansibleトラブル逆引き辞典(Linux / SSH / Shellエラー対応 | Ansibleで発生するエラー(UNREACHABLE / Permission denied / command not found など)を「エラーから逆引き」で調べられる実務向け記事。各回の内容と対応しており、トラブル時の入口として使える。 |
| 第0回:【なぜAnsibleは理解できないのか】仕組みを分解してみる | Ansibleは「Linux操作の自動化」ツール。Ansible → SSH → Shell → Linux の構造を理解し、シリーズ全体の土台を作る。 |
| 第1回:【なぜLinuxが分からないと詰むのか】Kernel / Shell / Filesystemの全体像 | Linuxの基本構造を理解することで、Ansibleが内部で何をしているのか(Shell実行・プロセス・ファイル操作)を把握できるようになる。 |
| 第2回:【Ansibleでハマる】command not foundの原因はPATHだった(環境変数の正体) | Ansible実行時に発生する「command not found」の原因はPATHにある。環境変数の仕組みを理解しないとエラーを解決できない。 |
| 第3回:AnsibleのSSH接続が失敗すると UNREACHABLE エラーになる。(Connection refused / Permission denied / Timeout など) | AnsibleのSSH接続が失敗すると UNREACHABLE エラーになる。SSH(鍵認証・ポート・Firewall)を理解することで原因を特定できる。 |
| 第4回:【Permission deniedの正体】Linuxユーザーと権限の仕組み | Ansibleのbecome(sudo)で発生する Permission denied の原因はLinux権限にある。ユーザー・グループ・権限の仕組みを理解する。 |
| 第5回:【サービスが起動しない理由】systemdの仕組みを理解する | Ansibleのservice / systemdタスクが失敗する原因はLinux側にある。systemdの仕組みを理解することで原因を特定できる。 |
| 第6回:【cronが動かない理由】手動では動くのに失敗する原因 | Ansibleで設定したcronが動かない原因は環境差分(PATHなど)にある。cron特有の実行環境を理解することで解決できる。 |
| 第7回:【No space left on device】ディスク容量不足の原因と調査方法(df / du / log) | Ansible実行時のディスク不足エラーはLinuxのログ肥大化や容量管理が原因。df / duを使った調査方法を解説する。 |
| 第8回:【サーバーが遅い】原因の見つけ方(CPU / メモリ / プロセス) | Ansible実行が遅い・失敗する原因はサーバー負荷の可能性がある。CPU・メモリ・プロセスの見方を理解して原因を特定する。 |