AnsibleとシェルスクリプトSSHループの違い|構成管理に踏み出す判断

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Ansible > AnsibleとシェルスクリプトSSHループの違い|構成管理に踏み出す判断
「サーバーが10台を超えたあたりから、SSHループのシェルスクリプトが手に負えなくなってきた」
そういう相談を受けることが多くなりました。

シェルスクリプトでSSHを束ねる手法は、台数が少ないうちは確かに便利です。しかしある規模を超えると、スクリプトの複雑さ・冪等性の欠如・メンテナンスコストが一気に牙を剥いてきます。

この記事では、シェルスクリプトSSHループとAnsibleが「どこが違うのか」を概念レベルで整理し、どのタイミングでAnsibleに踏み出すべきかを判断できる知識を提供します。コマンド一覧のリファレンスではなく、「なぜAnsibleが必要なのか」という設計思想の解説を軸にしています。

この記事のポイント

・SSHループは「手順の自動化」、Ansibleは「状態の自動化」という根本的な違いがある
・冪等性(何度実行しても同じ結果になる性質)の有無が最大の分岐点
・サーバー台数が10台超・設定変更が月1回以上ならAnsibleへの移行が効果的
・inventoryとrole分離で、設定の再利用性と可読性が大幅に向上する


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

シェルスクリプト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をインストールしてhttpdサービスを起動する」という処理を、シェルスクリプトとAnsible Playbookで書き比べると、2つのアプローチの違いが直感的にわかります。Ansible Playbookでは次のように宣言します。

# 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

シェルスクリプトでは「今の状態を確認してから処理する」if文が増え続けますが、Ansibleが接続先のサーバーに送るのは「コマンド」ではなく「Pythonモジュール」です。このモジュールが対象サーバー上で現在の状態を確認し、望ましい状態と異なる場合のみ変更を加えます。すでに目的の状態であれば何もしない(changed=Falseとして報告される)。これが冪等性の実装です。

「冪等性」と「宣言的記述」が変えるもの

冪等性と宣言的記述は抽象的に聞こえますが、現場での実感は具体的です。

冪等性が変えること
たとえば「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

これにより「webserversグループだけに対してPlaybookを流す」「本番とステージングでinventoryを切り替える」という操作が設定ファイルのレベルで制御できます。シェルスクリプトでこれを実現しようとすると、if文や引数処理が増え、スクリプト本体が膨らんでいきます。

inventoryファイルに変数を直書きするのではなく、group_vars/host_vars/ディレクトリを使うと管理が格段にしやすくなります。

# ディレクトリ構成の例 inventory/ ├── hosts # ホスト・グループの定義 ├── group_vars/ │ ├── all.yml # 全グループ共通の変数 │ ├── webservers.yml # webserversグループのみの変数 │ └── dbservers.yml └── host_vars/ └── web01.example.com.yml # 特定ホスト固有の変数

roleとは
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 # 依存関係の定義

roleを使うと「このサーバーにはwebserver roleとfirewall roleを当てる」という組み合わせ指定がPlaybookの数行で表現できます。

# site.yml(RoleをPlaybookから呼び出す例) --- - name: Webサーバーの構成 hosts: webservers become: yes roles: - common # 全サーバー共通設定 - webserver # Apache設定 - firewall # ファイアウォール設定

現場ではroleを次の3層に分けて管理するパターンが保守しやすいとされています。
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」はすでに望ましい状態(変更なし)、「changed」は変更を適用したことを意味します。web01はすべて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

3. become(sudo)でPermission deniedが出る場合
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が仕様書を兼ねる
移行コスト エージェントレスで低い
シェルスクリプトSSHループは「今すぐ動けばいい」という状況では有効な手段です。しかし規模が拡大し、複数人での管理・定期的な設定変更・本番環境への安全な適用が求められる段階になったとき、Ansibleへの移行は開発・運用のコスト削減に直接つながります。

「まず1つのroleを作ってみる」という小さな一歩から、構成管理の世界に踏み出してみてください。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
Ansible実践ハンズオンの詳細を見る >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。