そういう相談を受けることが多くなりました。
シェルスクリプトでSSHを束ねる手法は、台数が少ないうちは確かに便利です。しかしある規模を超えると、スクリプトの複雑さ・冪等性の欠如・メンテナンスコストが一気に牙を剥いてきます。
この記事では、シェルスクリプトSSHループとAnsibleが「どこが違うのか」を概念レベルで整理し、どのタイミングでAnsibleに踏み出すべきかを判断できる知識を提供します。コマンド一覧のリファレンスではなく、「なぜAnsibleが必要なのか」という設計思想の解説を軸にしています。
この記事のポイント
・SSHループは「手順の自動化」、Ansibleは「状態の自動化」という根本的な違いがある
・冪等性(何度実行しても同じ結果になる性質)の有無が最大の分岐点
・サーバー台数が10台超・設定変更が月1回以上ならAnsibleへの移行が効果的
・inventoryとrole分離で、設定の再利用性と可読性が大幅に向上する
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
シェルスクリプトSSHループが抱える3つの限界
シェルスクリプトでSSHループを書く手法は、現場に長くいるエンジニアなら誰もが一度は使ったことがあるでしょう。hosts.txtにIPアドレスを並べ、whileループで回しながら各サーバーにsshコマンドを叩く、というパターンです。しかしこのアプローチには、台数が増えるにつれて深刻になる3つの限界があります。
1. 冪等性がない
シェルスクリプトは「手順を実行する」ことしかできません。「パッケージAをインストールする」という処理を書いた場合、対象サーバーにすでにAがインストール済みであっても、スクリプトはそれを確認せずに再度インストールしようとします。場合によってはエラーが出て処理が止まったり、設定ファイルが上書きされたりします。
この問題を回避するには「すでにインストール済みか確認してからインストールする」という条件分岐を自分で書かなければなりません。以下は、Apacheのインストールと設定配置を冪等に扱おうとした例です。
#!/bin/bash # Apacheセットアップ(シェルスクリプト 手続き型の例) # 冪等性は自前で担保しなければならない if ! rpm -q httpd &>/dev/null; then dnf install -y httpd fi # 設定ファイルも「すでに同じ内容か」を確認する必要がある if ! diff -q /tmp/httpd.conf /etc/httpd/conf/httpd.conf &>/dev/null; then cp /tmp/httpd.conf /etc/httpd/conf/httpd.conf systemctl reload httpd fi systemctl enable --now httpd
2. 失敗時のリカバリが難しい
50台にSSHループで処理を流しているとき、25台目でエラーが発生したとします。残りの26台目以降に処理が届いているかどうか、スクリプトを読んで確認しなければなりません。失敗したサーバーだけをリトライする仕組みを作るには、成功・失敗をファイルに記録するロジックを自前で書く必要があります。これはかなりの手間で、かつバグを生みやすい部分です。
3. 並列実行とタイムアウト管理が煩雑
SSHをシリアルに叩くと、100台では1台あたり3秒でも5分以上かかります。xargsやGNU parallelで並列化するには別の工夫が必要ですし、タイムアウト設定やSSHのコントロールマスター(接続再利用)の管理も自前になります。Ansibleはこれらを設定ファイルで一元管理できます。
AnsibleはなぜSSHループと根本的に異なるのか
Ansibleが「構成管理ツール」と呼ばれる理由は、「手順を実行するツール」ではなく「インフラの状態を定義して、その状態に収束させるツール」だからです。Ansibleは管理対象のサーバーへSSHで直接接続して処理を実行します。管理対象サーバーに専用のエージェントをインストールする必要はありません(エージェントレス設計)。管理対象サーバー側に必要なのはPython 3系とSSHサーバーだけで、RHEL 9 / Rocky Linux 9のデフォルト環境であれば追加インストールなしで動作します。
Ansibleは次の2役で構成されています。
・制御ノード(Control Node):Ansibleがインストールされた自分のPC。ここでPlaybookを書いて実行する
・管理対象ノード(Managed Node):設定を適用されるLinuxサーバー群。PythonとSSHがあれば動作する
SSHループのシェルスクリプトと比べたとき、Ansibleの動作モデルは次のように異なります。
| 観点 | シェルスクリプトSSHループ | Ansible |
|---|---|---|
| 思想 | 手順の逐次実行 | 状態の宣言と収束 |
| 冪等性 | 自前で条件分岐を書く必要あり | モジュールが冪等性を保証 |
| 接続方式 | sshコマンドを毎回起動 | SSHを再利用(コントロールマスター相当) |
| 並列実行 | xargs/parallel等で自前実装 | forks設定で一元管理 |
| 失敗時の扱い | スクリプト設計に依存 | failed_when・ignore_errors・retryで制御 |
| 対象の選択 | hosts.txtのリスト管理 | inventoryでグループ・変数を統合管理 |
# Apacheセットアップ(Ansible Playbook 宣言型の例) --- - name: Apacheのセットアップ hosts: webservers become: yes tasks: - name: httpdをインストール ansible.builtin.dnf: name: httpd state: present - name: httpd.confを配置 ansible.builtin.copy: src: files/httpd.conf dest: /etc/httpd/conf/httpd.conf mode: '0644' - name: httpdを起動・自動起動設定 ansible.builtin.service: name: httpd state: started enabled: yes
「冪等性」と「宣言的記述」が変えるもの
冪等性と宣言的記述は抽象的に聞こえますが、現場での実感は具体的です。冪等性が変えること
たとえば「Nginxをインストールして設定ファイルを配置する」というPlaybookを書いたとします。このPlaybookを本番サーバーに対して月1回の定期メンテナンスで流す運用にしていた場合、シェルスクリプトでは「すでにインストール済みのNginxを再度インストールしようとして設定が上書きされた」というインシデントが起きる可能性があります。Ansibleのyumモジュール(またはdnfモジュール)はインストール済みを確認してからスキップするため、このリスクがありません。
本番にPlaybookを流すことへの心理的ハードルが下がり、「定期的に状態を揃え直す」という運用スタイルが定着します。これはシェルスクリプトSSHループでは実現しにくい運用文化です。
Playbookを実行するとタスクごとに次の3つのいずれかで結果が報告されます。
・ok:すでに望ましい状態だったため変更なし
・changed:変更を適用した
・failed:エラーが発生した
この3状態が明示されるため「何が変わったか・変わらなかったか」が一目でわかります。シェルスクリプトでこのような状態レポートを自前で実装するのは手間のかかる作業です。
宣言的記述が変えること
「Nginxのバージョンは1.26.x、設定ファイルはこの内容、サービスは起動状態、firewallの80番は開放」という状態をPlaybook(YAMLファイル)として記述します。これは「手順書」ではなく「仕様書」です。誰が読んでもサーバーの望ましい姿が一目でわかります。
シェルスクリプトのSSHループは手順の塊であり、読み解くには「このコマンドを実行した結果どういう状態になるか」を頭の中で追いかけなければなりません。Ansibleのplaybookは状態の定義であり、コードがそのままドキュメントとして機能します。
Ansibleをゼロから体系的に学びたい方はAnsible入門講座をご覧ください。
inventory設計とrole分離で保守性を高める
AnsibleがSSHループと決定的に異なる2点目が、inventoryとroleという概念です。inventoryとは
inventoryはAnsibleが操作する対象サーバーの一覧と、そのグループ定義・変数をまとめたファイルです。シェルスクリプトのhosts.txtに相当しますが、グループ化・変数定義・グループの入れ子が可能です。
# inventory/hosts の例(INI形式) [webservers] web01.example.com web02.example.com [dbservers] db01.example.com [webservers:vars] # webserversグループ全体に適用する変数 nginx_version=1.26.3 listen_port=80
inventoryファイルに変数を直書きするのではなく、group_vars/とhost_vars/ディレクトリを使うと管理が格段にしやすくなります。
# ディレクトリ構成の例 inventory/ ├── hosts # ホスト・グループの定義 ├── group_vars/ │ ├── all.yml # 全グループ共通の変数 │ ├── webservers.yml # webserversグループのみの変数 │ └── dbservers.yml └── host_vars/ └── web01.example.com.yml # 特定ホスト固有の変数
roleはPlaybookの「機能単位」を独立したディレクトリ構造にパッケージ化したものです。
ansible-galaxy init ロール名コマンドで雛形が自動生成されます。たとえばWebサーバーのセットアップroleは以下の構成になります。roles/ └── webserver/ ├── tasks/ │ └── main.yml # メインのタスクリスト ├── handlers/ │ └── main.yml # 変更時のみ実行されるハンドラ ├── templates/ │ └── httpd.conf.j2 # Jinja2テンプレートファイル ├── files/ │ └── index.html # そのまま配置する静的ファイル ├── defaults/ │ └── main.yml # デフォルト変数(上書き可能) └── meta/ └── main.yml # 依存関係の定義
# site.yml(RoleをPlaybookから呼び出す例) --- - name: Webサーバーの構成 hosts: webservers become: yes roles: - common # 全サーバー共通設定 - webserver # Apache設定 - firewall # ファイアウォール設定
・commonロール:全サーバーに適用する設定(タイムゾーン・時刻同期・不要サービス停止・ユーザー管理)
・アプリケーションロール:役割固有の設定(webserver / dbserver / mailserver等)
・securityロール:セキュリティ強化設定(SSHのroot禁止・auditd設定・firewalld開放ポートの最小化)
シェルスクリプトなら関数として切り出すことはできますが、変数スコープ・テンプレート処理・ハンドラ(変更があった時だけ実行する処理)という仕組みはシェルにはありません。
handlerは「設定ファイルを変更したときだけnginxをreloadする」という処理を、Playbook全体で1回だけ実行させる仕組みです。10個のtaskが同じハンドラをnotifyしても、実行されるのは1回だけです。シェルスクリプトでこれを同等に実装するのはかなり手間がかかります。
実際のAnsible実行出力を読む
Ansibleを実際に動かすと次のような出力が表示されます。自分のPC(Rocky Linux 9)から2台のWebサーバーへPlaybookを実行した際の出力例です。$ ansible-playbook -i inventory/hosts site.yml PLAY [Webサーバーの構成] ************************************* TASK [Gathering Facts] ************************************** ok: [web01.example.com] ok: [web02.example.com] TASK [webserver : httpdをインストール] ************************ ok: [web01.example.com] changed: [web02.example.com] TASK [webserver : httpd.confを配置] ************************** ok: [web01.example.com] changed: [web02.example.com] TASK [webserver : httpdを起動・自動起動設定] ****************** ok: [web01.example.com] changed: [web02.example.com] PLAY RECAP ************************************************** web01.example.com : ok=4 changed=0 unreachable=0 failed=0 web02.example.com : ok=4 changed=3 unreachable=0 failed=0
okなので前回の実行から変更がなく、冪等性どおりの動作です。web02はchanged=3なので、3つのタスクが新たに適用されました。PLAY RECAPの行を確認すれば、全サーバーに対して「変更があったか」「エラーが出たか」「疎通できなかったか」が1行でわかります。シェルスクリプトSSHループでこのようなサマリを得るには、自前でロギングロジックを組む必要があります。Ansibleが動かない場合のよくあるエラーと対処
Ansibleを初めて使う際に詰まりやすいポイントと、その切り分け方法を解説します。1. UNREACHABLE エラー(SSH接続失敗)
最も頻繁に発生するエラーです。
# エラー例 fatal: [web01.example.com]: UNREACHABLE! => { "msg": "Failed to connect to the host via ssh: ..." } # 切り分け手順 # 1. SSH鍵認証が通るか直接確認 $ ssh -i ~/.ssh/id_ed25519 rocky@web01.example.com # 2. Ansibleのping疎通確認(ICMPではなく接続テスト) $ ansible -i inventory/hosts webservers -m ping
・鍵認証の設定ミス:inventoryの
ansible_ssh_private_key_fileが正しいパスか確認する・ユーザー名の不一致:
ansible_userがRHEL/Rockyのデフォルト(rootまたはrocky等)と一致しているか確認する・SSHポートが変更されている:
ansible_port=2222のように非標準ポートをinventoryに指定する2. MODULE_NOT_FOUND(Pythonが見つからない場合)
# エラー例 fatal: [web01.example.com]: FAILED! => { "msg": "/usr/bin/python3: not found" } # 対処:group_vars/all.yml にPythonパスを指定 ansible_python_interpreter: /usr/bin/python3.11
become: yesを使ってrootに昇格する際、sudoersの設定が不十分だとエラーになります。sudo実行ユーザーにNOPASSWDを設定するか、Playbook実行時に--ask-become-pass(-K)オプションでパスワードを入力します。sudoers設定を変更する際は必ずバックアップを取り、visudoで編集してください(直接編集は構文エラーでロックアウトになる危険があります)。どのタイミングでAnsibleに移行すべきか
「いつAnsibleに移行すべきか」という問いへの答えは、シェルスクリプトSSHループのコストが見えてきたタイミング、具体的には以下の条件が重なった時です。移行を検討すべき状況
・管理対象サーバーが10台を超えた
・設定変更作業が月1回以上発生する
・複数人でサーバー管理を担当している(属人化の排除が必要)
・本番に同じ設定変更を適用するのが怖い(冪等性がないことへの不安)
・SSHループのスクリプトが200行を超えて読みにくくなった
逆に、以下の状況ならシェルスクリプトSSHループのままでも十分です。
・管理対象が5台以下で増える予定もない
・設定変更は年に数回程度の単発作業
・1人で全サーバーを管理しており、ナレッジ共有の必要がない
移行の進め方としては、いきなり全サーバーに適用しようとするのではなく「まず1つのroleを作る」ことをお勧めします。既存のSSHループで行っていた処理(たとえば「Nginxのインストールと設定」)を1つのroleとして書き直し、ステージング環境で動作を確認します。成功したら本番に当てる。この繰り返しで、シェルスクリプトをAnsibleに置き換えていく方法が最もリスクが低くなります。
移行前に確認すべき技術的前提
・管理対象サーバーにPythonがインストールされていること(RHEL/CentOS/Ubuntu系はデフォルトでOK)
・Ansibleを実行するPC(コントロールノード)からSSHで各サーバーに接続できること
・SSHの公開鍵認証が設定されていること(パスワード認証でも動くが実用上は公開鍵が必須)
AnsibleはSSHさえ通っていれば追加のエージェントインストールは不要です。この「エージェントレス」な設計が、既存のSSH環境からの移行コストを下げている理由の一つです。
まとめ
シェルスクリプトSSHループとAnsibleの違いを整理します。| 項目 | シェルスクリプトSSHループ | Ansible |
|---|---|---|
| 実行モデル | 手順の逐次実行 | 状態への収束(宣言的) |
| 冪等性 | なし(自前実装が必要) | モジュールが担保 |
| 実行結果の可視性 | 自前でロギングが必要 | ok/changed/failedを自動レポート |
| 適正規模 | 5台以下、単発作業 | 10台超、定期的な設定変更 |
| 保守性 | 台数増加で急激に悪化 | role分離で高い再利用性 |
| ドキュメント性 | 手順書と乖離しやすい | Playbookが仕様書を兼ねる |
| 移行コスト | — | エージェントレスで低い |
「まず1つのroleを作ってみる」という小さな一歩から、構成管理の世界に踏み出してみてください。
Ansible実践ハンズオンの詳細を見る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
- 次のページへ:Ansible Vaultでシークレットを安全に管理する方法|パスワードファイルや暗号化変数の実践例
- 前のページへ:Terraformのmoduleとfor_each・countで構成を再利用する設計パターン
- この記事の属するカテゴリ:Ansibleへ戻る

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