Ansibleを学びはじめたエンジニアの多くが、こういう疑問を持ちます。
Linuxコマンドを書き慣れていると、
ansible.builtin.shell や ansible.builtin.command は「シェルと同じ感覚で書ける」便利なモジュールに見えます。しかし現場で構成管理の規模が大きくなるにつれ、shell/cmdへの依存度が高いPlaybookはメンテナンスコストを急上昇させます。この記事では、ansible shell cmdモジュールの依存から脱却するための代替モジュール選択指針と、role設計によるアーキテクチャレベルの防衛策を解説します。RHEL 9.4 / Ubuntu 24.04 LTSで動作確認した実行例を交えながら、現場で通用する構成管理の「型」を身につけていきましょう。
この記事のポイント
・shell/cmdは冪等性がなくPlaybookが毎回「changed」になる根本原因となる
・パッケージ・サービス・ファイル操作は専用モジュールで安全に代替できる
・role設計でshell/cmdの混入を構造的に防ぐレイヤー分離が有効
・changed_whenとcreatesオプションで止むを得ない場合の冪等性を確保できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜAnsibleでもshell/cmdに頼ってしまうのか
Linuxコマンドを日常的に使っているエンジニアにとって、sshで接続してコマンドを手打ちする操作と、Ansibleのshellモジュールに同じコマンドを貼り付ける操作は、見た目上ほぼ変わりません。「動いた」という即効性があるため、最初のうちは深く考えずにshell/cmdを使い続けてしまいます。具体的によく見かけるパターンを挙げます。
・パッケージインストールを
shell: yum install -y httpd と書く・サービス起動を
command: systemctl start nginx と書く・設定ファイルの1行追加を
shell: echo "option=value" >> /etc/myapp.conf と書くいずれも「Ansibleを使わずに手でやるとしたらこう打つ」コマンドをそのまま写したものです。動作はします。しかしこの書き方は、Ansibleの本来の機能を活かし切れていません。
shell/cmdへの過依存が招く3つのリスク
1. Playbookが毎回「changed」になる
shell/cmdモジュールはコマンドを実行するだけで、実行後の状態を確認しません。そのため2回目以降の実行でも「コマンドを実行した=何かが変わった」としてchanged を返します。実際にshellモジュールで
yum install -y httpd を繰り返し実行した出力がこちらです。[tomohiro@sv01 ~]$ ansible-playbook -i inventory.ini site.yml PLAY [webservers] **************************************************** TASK [install httpd via shell] *************************************** changed: [192.168.1.101] changed: [192.168.1.102] PLAY RECAP *********************************************************** 192.168.1.101 : ok=1 changed=1 unreachable=0 failed=0 192.168.1.102 : ok=1 changed=1 unreachable=0 failed=0
2. checkモードが機能しない
ansible-playbook --check によるドライラン検証は、shell/cmdを含むPlaybookでは正確に動作しません。専用モジュールであれば「このタスクは実行時にchangedになるか」を事前に確認できますが、shell/cmdは実行してみるまで状態がわかりません。checkモードを本番前の安全弁として使えなくなります。3. クロスプラットフォーム展開が壊れる
yum install -y httpd はRHEL系でしか動きません。同じPlaybookをUbuntuホストに適用しようとすると、そもそも yum コマンドが存在せず即座に失敗します。専用モジュールであれば ansible_pkg_mgr などのfactを活用してOSに応じた分岐を簡潔に書けます。代替モジュール選択の実践指針
1. パッケージ管理はdnf/apt/yumモジュールへ
「インストール済みであればスキップ」という冪等性が自動的に保証されます。# NG: shell/commandモジュール - name: install httpd ansible.builtin.shell: yum install -y httpd # OK: dnfモジュール(RHEL 8以降) - name: install httpd ansible.builtin.dnf: name: httpd state: present
dnf モジュールを使えば、httpdがすでにインストール済みの場合は ok を返します。変更が必要な場合のみ changed となり、ログが意味を持つようになります。2. 設定ファイル操作はlineinfile/blockinfile/templateモジュールへ
設定ファイルへの1行追加にはlineinfile、複数行の追加には blockinfile、変数を含む動的なファイル生成には template が使えます。# NG: shellで追記(冪等性なし・二重追加の危険あり) - name: add config line ansible.builtin.shell: echo "MaxClients 150" >> /etc/httpd/conf/httpd.conf # OK: lineinfileモジュール(行の存在を確認して追加) - name: set MaxClients ansible.builtin.lineinfile: path: /etc/httpd/conf/httpd.conf regexp: '^MaxClients' line: 'MaxClients 150' state: present
lineinfile は「その行がすでに存在すれば変更しない・なければ追加する」動作を保証します。shellの >> では同じ行が何度も追記される危険があります。3. サービス制御はserviceモジュールへ
# NG: commandモジュール - name: start nginx ansible.builtin.command: systemctl start nginx # OK: serviceモジュール - name: ensure nginx is running ansible.builtin.service: name: nginx state: started enabled: true
service モジュールは「すでに起動していれば何もしない」という冪等な動作をします。OSのinit システム(systemd / SysVinit)の違いも吸収します。4. ユーザー管理はuserモジュールへ
# NG: shellで直接useradd - name: create deploy user ansible.builtin.shell: useradd -m -s /bin/bash deployuser # OK: userモジュール - name: ensure deploy user exists ansible.builtin.user: name: deployuser shell: /bin/bash create_home: true state: present
useradd はユーザーが存在するとエラーを返すため、2回目以降の実行でPlaybookが失敗します。user モジュールは存在を確認してから操作するため安全です。shell/cmdが許容される判断基準
すべてを専用モジュールに置き換えられるわけではありません。以下の条件が揃う場合に限り、shell/cmdは許容されます。・専用モジュールが存在しない操作:独自のインストールスクリプトや複数コマンドを組み合わせた複雑な初期化処理
・冪等性を自分で保証できる場合:
creates オプション(指定ファイルが存在すればスキップ)や changed_when: false を適切に使える場合# createsオプションで冪等性を確保 - name: run setup script only once ansible.builtin.command: cmd: /opt/app/setup.sh creates: /opt/app/.setup_done
/opt/app/.setup_done が存在すれば setup.sh をスキップします。スクリプト自身が完了後にこのファイルを作成する設計にしておくことで冪等性を確保できます。Ansibleの構成管理を体系的に学びたい方は、Ansible実践講座で実機を使ったハンズオンカリキュラムを確認できます。
role設計でshell/cmd混入を防ぐ
個々のモジュール選択のルールを守るだけでなく、roleの設計段階からshell/cmdの混入を防ぐ仕組みを作ることが重要です。tasksとhandlersの分離:
設定変更後の再起動処理をtasks内に直接書かず、handlersに切り出すことで「必要な時だけ再起動」が自動的に保証されます。
# roles/webserver/tasks/main.yml - name: deploy httpd.conf ansible.builtin.template: src: httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf owner: root group: root mode: '0644' notify: restart httpd # roles/webserver/handlers/main.yml - name: restart httpd ansible.builtin.service: name: httpd state: restarted
notify / handler のペアを使うことで、設定ファイルが変更された場合のみhttpdが再起動されます。shellの systemctl restart httpd を毎回実行する書き方とは根本的に異なります。defaultsとvarsの活用:
role内で使うパラメーターは
defaults/main.yml にデフォルト値を定義し、呼び出し側で上書きできる設計にします。環境ごとの差異をshellコマンドの条件分岐で吸収するのではなく、変数の差し替えだけで制御できます。# roles/webserver/defaults/main.yml webserver_max_clients: 150 webserver_port: 80 # roles/webserver/tasks/main.yml - name: set MaxClients ansible.builtin.lineinfile: path: /etc/httpd/conf/httpd.conf regexp: '^MaxClients' line: "MaxClients {{ webserver_max_clients }}"
webserver_)ことで、複数roleを組み合わせたときの変数名衝突を防ぎます。shell/cmdからの移行でよく出るエラーと対処法
「使えるモジュールが見つからない」と感じたとき
「shellで書けばすぐ動くのに、なぜわざわざ調べないといけないのか」という気持ちになることがあります。そのような場合はansible-doc -l | grep <キーワード> でモジュール一覧を検索するか、Ansible公式ドキュメントの「All modules」ページで操作カテゴリごとに絞り込むと目的のモジュールが見つかります。「dnfモジュールでインストールしたのにchangedになり続ける」
state: latest を指定すると「最新バージョンかどうか」を毎回確認し、アップデートがあれば changed を返します。「インストール済みであれば何もしない」ならば state: present を使います。# state: latest は毎回更新確認してchangedになりやすい - name: install httpd (keeps updating) ansible.builtin.dnf: name: httpd state: latest # NG: 毎回最新確認 # state: present はインストール済みならokを返す - name: install httpd (idempotent) ansible.builtin.dnf: name: httpd state: present # OK: 冪等
「lineinfileで設定が重複して書き込まれた」
regexp パラメーターを省略すると「その行が存在するか」のチェックなしに毎回追記します。必ず regexp で既存行のパターンを指定してください。正規表現が実際のファイル内容にマッチしていない場合も二重追加が起きます。まず --check --diff で変更内容を確認してから本番に適用する習慣をつけることが重要です。本記事のまとめ
| 操作の種類 | shell/cmdの代わりに使うモジュール |
|---|---|
| パッケージインストール | ansible.builtin.dnf / apt / yum |
| 設定ファイルの1行追加・変更 | ansible.builtin.lineinfile |
| 設定ファイルの複数行追加 | ansible.builtin.blockinfile |
| 静的ファイルの配布 | ansible.builtin.copy |
| 変数を含む設定ファイルの生成 | ansible.builtin.template |
| サービスの起動・停止・有効化 | ansible.builtin.service |
| ユーザー作成・管理 | ansible.builtin.user |
| ファイル権限・所有者の変更 | ansible.builtin.file |
| 専用モジュールがない操作 | ansible.builtin.command(createsで冪等性確保) |
shell / command モジュールを使っているタスクをリストアップし、上の表を参考に置き換えられるものから着手してみてください。roleの設計まで踏み込むと、handlersによる変更検知・defaultsによる変数管理・tasksとtemplatesの分離によって、shell/cmdに頼らなくても複雑な構成管理が実現できます。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:Ansible Automation Controllerで実現するITチームの権限委譲|承認フローと監査ログ設計
- この記事の属するカテゴリ:Ansibleへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます