1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ansibleが理解できない理由はLinuxにあった【Ansible編】第1回:なぜshellモジュールは危険なのか

1
Last updated at Posted at 2026-06-08

📚 Ansibleが理解できない理由はLinuxにあった 【Ansible編】第1回:なぜshellモジュールは危険なのか

⓪ なぜShellの知識がないとAnsibleは壊れるのか
① なぜshellモジュールは危険なのか
② なぜmoduleを使うと安全になるのか
③ なぜ毎回changedになるのか
④ なぜ条件分岐が壊れるのか(Ansible編)
⑤ なぜ変数が意図通りに扱えないのか
⑥ なぜタスクの順序で壊れるのか
⑦ なぜエラー制御が破綻するのか
⑧ なぜ非同期・待機で失敗するのか
⑨ なぜPlaybookが読めないのか
⑩ なぜAnsibleを正しく設計できるようになるのか


🗺️ 初めての方・シリーズの全体像を知りたい方はこちら

本記事は、OSの仕組みからAnsible設計までを繋ぐ連載シリーズの一部です。
「どこから読み始めればいいか」あるいは、「OS/Shell/Ansible編の関係性」 について把握されたい場合は、以下の統合ガイドで整理しています。

Ansibleが理解できない理由はLinuxにあった|統合ガイド


📑 【Ansible編】全体のまとめはこちら
Ansibleが理解できない理由はLinuxにあった【Ansible編】まとめ ※近日公開予定


📑 連載の移動
前の記事:【Ansible編編】第0回 | 次の記事:【Ansible編】第2回 ※近日公開予定


📋 目次

  1. 前回の振り返り(第0回)
  2. 逆引き辞典との連動
  3. はじめに(この記事のスタンス)
  4. 問題定義:なぜshellは“普通に使えてしまう”のか
  5. 構造解剖:shellモジュールがやっていること
  6. 具体例:どこで構造が崩れるのか
  7. 本質:shellの何が危険なのか
  8. commandモジュールとの構造的違い
  9. 設計思想への接続
  10. まとめ
  11. 次回予告
  12. 連載一覧:Ansibleが理解できない理由はLinuxにあった【Ansible編】

※本記事の位置とシリーズ全体の関係を先に確認します。


:map: シリーズ全体構造(学習 × 問題解決)


本シリーズは
「理解(各記事)」と「問題解決(逆引き辞典)」を組み合わせて
スキルを身につける構成になっています。

この図は、どこから学び、どこに進めばよいかを示した“ロードマップ”です。


📍 現在の位置


現在はこの図の「Ansible編の第1回」になります。


1. 前回の振り返り(第0回)

前回は、Ansibleの正体が 「Shellの挙動を抽象化したラッパー」 であることを確認しました。

Ansible
  ↓ (Jinja2で変数展開)
コマンド文字列生成
  ↓ (SSH転送)
リモートShell
  ↓ (解釈・実行)
結果 (rc / stdout / stderr)

Ansibleで起きる問題の多くはAnsible自身のバグではなく、背後で動く Shellの仕様(環境差分・単語分割・変数展開) に起因します。


↑ 目次に戻る


2. 逆引き辞典との連動

「設計上の原因」を特定したい場合は、以下の逆引き辞典を活用してください。

※随時公開予定
Ansibleが理解できない理由はLinuxにあった【Ansible編】:トラブル逆引き辞典(設計パターン版)

  • まず「逆引き辞典」で該当ステップを特定する
  • 次に「本編(各連載記事)」で仕組みを理解する

↑ 目次に戻る


3. はじめに(この記事のスタンス)

今回のテーマは 「shellモジュールの是非」 です。
「shellモジュールは避けるべき」という言葉はエンジニアの間でよく聞きますが、その理由を「なんとなく危ないから」「行儀が悪いから」といった感覚論で済ませてしまうケースは少なくありません。本記事では、shellモジュールが抱えるリスクを 「構造的な不確実性」 という観点から整理し、なぜそれが自動化の信頼性を損なうのかを説明します。


↑ 目次に戻る


4. 問題定義:なぜshellは“普通に使えてしまう”のか

shellモジュールの問題は、その手軽さにあります。既存のシェルスクリプトやワンライナーをそのままコピー&ペーストすれば、Ansible経由で即座に実行できます。学習コストが低く、手元の操作をそのまま自動化に持ち込めるため、使い始めの段階では「正解」のように見えます。

しかし、この手軽さの実態は「ただの文字列実行」です。中身が保証されない文字列をリモートに投げているだけで、Ansibleが本来持つ状態管理の仕組みは働いていません。この 「動いてしまうが、中身は不透明」 という状態が、運用フェーズで問題として顕在化します。


↑ 目次に戻る


5. 構造解剖:shellモジュールがやっていること

shellモジュールが実行される際の流れは以下の通りです。

[Ansible 制御ノード]
      ↓
1. Jinja2展開(変数を文字列に置換)
2. コマンド文字列生成(実行コマンドの完成)
      ↓
 (SSH転送)
      ↓
[ターゲットノード]
      ↓
3. リモートShell起動
4. 再解釈(クォートの処理・引数の分割・環境変数の展開)
      ↓
5. コマンド実行

注目すべきは「4. 再解釈」のステップです。Ansibleが送り出した文字列は、この時点でリモートOS上のShellに渡り、Shellのルールで改めて解釈されます。Ansible側で意図した構造が、リモートShellの仕様(IFSやエスケープルール)によって書き換えられてしまう。 これが、shellモジュールの不確実性の根本原因です。


↑ 目次に戻る


6. 具体例:どこで構造が崩れるのか

実際の挙動をログ形式で確認します。いずれもShellの仕様に起因する問題です


例①:引数の境界崩壊(単語分割)

ディレクトリ作成をshellで行うケースです。

- name: ディレクトリ作成
  shell: mkdir {{ target_dir }}

変数の値にスペースが含まれていた場合(例:/tmp/my app)、ログには一見正常に終了したかのような以下の結果が残ります。

"cmd": "mkdir /tmp/my app"
"rc": 0
"changed": true

Ansibleは「1つのディレクトリ」を作ろうとしましたが、リモートShellはスペースを区切り文字(IFS)と見なし、「2つの独立した引数」として実行しました。
mkdir は複数の引数を受け取れる仕様のため、OSはエラーを出さずに処理を完了します。
結果として、/tmp/my(絶対パス)と app(実行時のカレントディレクトリへの相対パス)という2つのディレクトリが別々の場所に作成されます。

ログ上は成功(rc: 0)しているにもかかわらず、実態は意図しない場所にディレクトリが作成されている。これが単語分割による「サイレントな失敗」です。

この挙動の再現手順や、詳細な実機検証ログ(Playbookやターゲットノードの実態)については、【Shell編】第10回:単語分割のメカニズム で詳しく解説しています。


例②:変数の二重評価による構造破壊(引用符の衝突)

変数内に引用符が含まれる文字列を、shellモジュールでそのまま展開するケースです(例:search_filter の値が name="admin_user" )。

- name: 設定値の検索
  shell: mysql -D test_db -e "SELECT * FROM users WHERE {{ search_filter }}"

Jinja2による変数展開の時点では、SQL文として正しく組み立てられているように見えます。しかしリモートでの実行結果は rc: 1(異常終了)となります。

"cmd": "mysql -D test_db -e \"SELECT * FROM users WHERE name=\"admin_user\"\"",
"rc": 1,
"stderr": "ERROR 1054 (42S22): Unknown column 'admin_user' in 'where clause'"

Jinja2は指定された位置に値を埋め込むだけで、SQLの構文チェックは行いません。問題はその後、リモートShellがこの文字列を受け取った段階で発生します。
-e の直後にある外側のダブルクォートと、変数内のダブルクォートをShellが「閉じペア」と判断し、admin_user を囲んでいたクォートが取り除かれた状態でmysqlプロセスへ渡されます。
MySQLは admin_user を文字列ではなくカラム名として解釈するため、構文エラーになります。

💡 ログを読み解く際の注意点
ログの "cmd" 内にあるバックスラッシュ(\")は、AnsibleがログをJSON形式で出力する際に自動でエスケープしたものです。実際のShellには \ は渡っておらず、純粋な " 同士が衝突して剥離が起きています。

この挙動の再現手順や、詳細な実機検証ログ(Playbookやターゲットノードの実態)については、【Shell編】第9回:なぜ変数が意図通りに展開されないのか で詳しく解説しています。


例③:入力ソースの誤認によるデータ不整合(および対話待ちハング)

パイプによる標準入力と、コマンドの引数(ファイル名指定)を同時に記述してしまうケースです。

- name: 設定値の検索(パイプと引数の同時指定)
  shell: echo 'DATA_FROM_PIPE' | grep 'DATA' /tmp/test_input.txt

実行ログは changed となり、出力結果は以下のようになります。

"cmd": "echo 'DATA_FROM_PIPE' | grep 'DATA' /tmp/test_input.txt",
"stdout": "DATA_FROM_FILE"

パイプ経由の DATA_FROM_PIPE を検索するつもりでも、出力されるのはファイル内の DATA_FROM_FILE のみです。grepawk などのコマンドは、引数にファイル名が指定されている場合、パイプからの標準入力を無視する仕様になっているためです。

📌 補足:「引数もパイプもない場合」のハング
引数(ファイル名)もパイプ(標準入力)も与えずに shell: "grep 'search_word'" のように実行した場合、OSはキーボード等からの入力を待ち続けます。Ansibleは対話的な入力ができないため、タスクはその場でハングし、最終的にタイムアウトエラーになります。

引数と標準入力の優先順位(検証1)や、タスクがハングする内部メカニズム(検証2)の詳細については、【Shell編】第3回:なぜgrepで見つからないのか で詳しく解説しています。


↑ 目次に戻る


7. 本質:shellの何が危険なのか

これまでの例から、shellモジュールの問題は以下の3点に整理できます。

  1. 文字列を実行している: Ansibleは「何を投げたか」は把握できても、それがShell側で「どう解釈されるか」を保証できません。
  2. Shellが再解釈する: 実行直前にShellのルール(IFS、クォート、展開)が介在するため、挙動が動的に変化します。
  3. 環境に依存する: OSやShellの種類、実行コンテキスト(PATH等)に依存するため、環境が変わると再現性が失われます。

これらはバグではなく、Shellというツールの強力すぎる「仕様」です。 この仕様をAnsibleという抽象化レイヤーの上で制御しようとすること自体に、設計上の無理があります。


↑ 目次に戻る


8. commandモジュールとの構造的違い

shellの代替として推奨される command モジュールは、構造が異なります。

  • shell: リモートで /bin/sh などのShellを起動し、その上で文字列を評価させる。
  • command: 指定されたバイナリを直接実行する。Shellを介さない。
[shell]   : Ansible -> SSH -> Shell -> 再解釈 -> 実行
[command] : Ansible -> SSH -> 実行

command はShellを通さないため、パイプやリダイレクト、Shell変数の展開が利用できません。
その代わり、Shellによる再解釈が発生しないため、コマンドが意図しない形に変質するリスクを避けられます。
ただし、command はあくまで「コマンド実行」を安全にする仕組みであり、「状態管理」を行うものではありません。
そのため設計としては、依然としてコマンド実行ベースに留まります。


↑ 目次に戻る


9. 設計思想への接続

shellモジュールに頼る設計を続ける限り、エンジニアは常に「Shellがどう解釈するか」を意識し続けなければなりません。

Shellの再解釈による不確実性をなくすには、「文字列を実行する」設計から 「状態を定義する」設計 へ移行する必要があります。

コマンドを投げてその成否を追うのではなく、「あるべき状態(Desired State)」を定義し、その実現をツールに任せる。この考え方を実現する仕組みが、次回扱う「module(モジュール)」です。


↑ 目次に戻る


10. まとめ

  • shellは一見便利だが、二重の解釈プロセスにより挙動が安定しない。

  • 発生するトラブルは使い方のミスではなく、Shellの「再解釈」という仕様に起因する。

  • 安定した自動化のためには、コマンド実行ではなくmoduleによる「状態管理」への移行が不可欠である。


↑ 目次に戻る


11. 次回予告

第1回では、shellモジュールが抱える「再解釈」という問題と、それによって生じる不確実性の仕組みを整理しました。
次回は、Shellの解釈プロセスを介さずにターゲットの状態を直接制御する仕組み、すなわちAnsibleの専用モジュールと 「状態管理(State Management)」 という設計思想を解説します。

次回:
【Ansible編】第2回:なぜmoduleを使うと安全になるのか

「命令」から「宣言」へ。Shell編で学んだ不確実性を、moduleの構造がどのように解消するのかを解説します。


↑ 目次に戻る


📑 連載の移動
前の記事:【Ansible編編】第0回| 次の記事:【Ansible編】第2回 ※近日公開予定


📑 【Ansible編】全体のまとめはこちら
Ansibleが理解できない理由はLinuxにあった【Ansible編】まとめ ※近日公開予定


🗺️ 初めての方・シリーズの全体像を知りたい方はこちら

本記事は、OSの仕組みからAnsible設計までを繋ぐ連載シリーズの一部です。
「どこから読み始めればいいか」あるいは、「OS/Shell/Ansible編の関係性」 について把握されたい場合は、以下の統合ガイドで整理しています。

Ansibleが理解できない理由はLinuxにあった|統合ガイド


📚12. 連載一覧:Ansibleが理解できない理由はLinuxにあった【Ansible編】

※以下は随時更新

回数とタイトル 内容(概要)
【Ansible編】第0回:なぜShellの知識がないとAnsibleは壊れるのか AnsibleはShellのラッパーとして動作している。Shell編で学んだ構造(Ansible → SSH → Shell → Linux)を再確認し、「なぜmoduleを使うべきなのか」の前提を整理する。
【Ansible編】第1回:なぜshellモジュールは危険なのか shellモジュールはShellの仕様(分割・展開・環境)に依存するため壊れやすい。commandとの違いを理解し、「どこまでShellを許容すべきか」を判断できるようになる。
【Ansible編】第2回:なぜmoduleを使うと安全になるのか file / copy / lineinfile などのmoduleは状態管理を前提としている。「結果」ではなく「状態」を扱うことで、Shell依存を排除し安全な構成を実現する。
【Ansible編】第3回:なぜ毎回changedになるのか shellモジュールは状態を判定できないため、常にchangedになる。changed_whenを使った制御と冪等性(idempotency)の設計を理解する。
【Ansible編】第4回:なぜ条件分岐が壊れるのか(Ansible編) when条件が意図通りに動かない原因は、rc / stdout / stderrの扱いにある。Shell編で学んだ終了ステータスと組み合わせ、正しい条件分岐を設計する。
【Ansible編】第5回:なぜ変数が意図通りに扱えないのか vars / host_vars / group_vars のスコープや、registerの扱いを誤ると値が壊れる。Ansible内での変数管理と評価タイミングを整理する。
【Ansible編】第6回:なぜタスクの順序で壊れるのか Ansibleのタスクはそれぞれ独立したプロセスとして実行される。chdir / environment の制御と、「前の結果に依存する設計」の危険性を理解する。
【Ansible編】第7回:なぜエラー制御が破綻するのか ignore_errors や failed_when の使い方を誤るとエラーが隠蔽または暴発する。Shellのrcと連動させた正しいエラー制御を設計する。
【Ansible編】第8回:なぜ非同期・待機で失敗するのか サービス起動や外部依存処理はタイミング問題を引き起こす。async / poll / wait_for / retries を使い、安定した実行制御を実現する。
【Ansible編】第9回:なぜPlaybookが読めないのか role構成やタスク分割が不適切だと可読性が崩壊する。実務で保守できるPlaybook設計と構造化の原則を理解する。
【Ansible編】第10回:なぜAnsibleを正しく設計できるようになるのか これまでの知識を統合し、Shell編と接続することで「壊れない設計」ができるようになる。Ansibleを実務で使いこなすための設計思想を完成させる。

↑ 目次に戻る


1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?