【Ansible入門】Ansibleで手軽に監視サーバを構築してみた

はじめに
こんにちは。入社2年目のS.Fです。 昨今、インフラ領域において、コードで構成を管理する IaC(Infrastructure as Code)は、手作業から脱却し再現性を担保するための必須スキルとなりました。 特に最近では、AIを活用してコードを生成する「バイブコーディング」といった手法も登場しています。
AIがコードを書く時代だからこそ、出力されたコードを正しく理解し、インフラを管理するIaCの設計理解がより一層求められていると感じています。
本記事は、これからIaC(特にAnsible)を学びたい方に向けて、基本的な考え方から実際の設定例まで、また実際に構築していく中で気づいた点や工夫したポイントなども織り交ぜて紹介しています。
目次:
- IaC (Infrastructure as Code)とは
- Ansibleとは
- Ansibleの基礎知識
- システム構成
- 今回のAnsibleのディレクトリ構成
- 各taskのポイント
- playbookの実行
- 構築時の躓きと、学んだTips
- まとめ
1. IaC (Infrastructure as Code)とは
IaC (Infrastructure as Code)とはその名の通り、サーバなどのインフラ構成を”コード“として定義・管理する手法のことです。
従来の手動オペレーションと比較して、IaCには主に以下のメリットがあります。
• 再利用性と再現性: コードがあれば、誰がいつ実行しても全く同じ環境を構築可能
• バージョン管理: プログラムのコードと同様に、Git等でインフラの変更履歴を管理可能
• 自動化による効率化: 手作業によるミスを無くし、構築時間を大幅に短縮可能
このIaCを実現するための代表的なツールの一つが、本記事の主役Ansible です。
2. Ansibleとは
Ansibleは、サーバの構成管理や設定を IaCとして自動化するためのツールです 。主な特徴は3つあります。
- 高い可読性と一括適用
Ansibleはyaml形式で記述できるため可読性が高いです。また、一度書いた設定をまとめて複数サーバへ適用できるため、手作業よりも効率的に作業を進められます。 - 構成の統一化
処理内容がすべてコードとして定義されるため、手動オペレーションのような作業者によるブレや差異が発生しません。構成の統一化や属 人化の防止に役立ちます。 - 冪等性(べきとうせい)
Ansibleの大きな特徴のひとつです。
「同じ操作を何度やっても、最終的な状態が変わらない」という性質を持っています。途中で処理が止まったとしても、再度そのまま実行すれば正しい状態に整えてくれるため、安心して作業を繰り返せます。
以上のような性質から、Ansibleは複雑な作業を分かりやすくし、
誰が実行しても同じ結果が得られる柔軟で安全な自動化ツールとして活用できます。
3. Ansible の基礎知識
ここでは、Ansibleの基本的な構成要素について説明します。
inventory / playbook / role に加えて、設定ファイルを柔軟に扱うための Jinja2 も重要な要素として使用します。
今回の構築で実際に用いた項目にしぼり、ポイントを整理して紹介します。
3.1. inventory
inventory は、Ansibleに「どのサーバへ処理を行うのか」を伝えます。
監視サーバ・監視対象サーバなどをグループ化して定義することで、役割ごとに処理を振り分けられます。
3.2. playbook
playbookはAnsibleの実行入口であり、どのホストへ、どの処理(role)を適用するかを記述します。
重要なポイントは以下の2点です。
• Playbook には処理の詳細を書かない
• 対象サーバの指定と、実行するroleの指定に徹する
Playbook は「何を、どの順番で実行するか」という全体の流れを定義する役割を担っており、実際の具体的な処理はすべて role 側に委ねる設計が基本となります。
3.3. role
実際の処理内容をまとめたのがroleです。
Dockerの導入、Prometheusの展開、NodeExporter(監視用エージェント)のセットアップなど、機能ごとに分けて整理できます。
roleでよく使われるディレクトリは以下です。
・tasks/:実際に実行する処理を記述
・templates/:変数展開が必要なテンプレートを配置(必要応じ、値が変わる設定ファイル)
・files/:対象サーバへそのまま配置する静的ファイルを配置(静的な設定ファイル)
・vars/:role内で利用する変数を定義
roleで役割ごとにファイルを分けることで、再利用性、保守性が大きく向上します。
3.4. Jinja2テンプレート(.j2)
Ansibleでは、設定ファイルをそのまま配布するだけでなく、環境ごとに値を変更するケースが必ず発生します。このとき役立つのが Jinja2テンプレートです。
特徴は以下の3点です。
・{{ 変数 }} を使った動的な値埋め込みが可能
・条件分岐、ループが可能
・環境差分を統一的に扱える
以上からJinja2はAnsibleで柔軟なテンプレート管理を行うための手段として使用されます。
4. 構成図
今回用意する構成は以下の図の通りです。

Ansibleの挙動を手軽に試すため、今回はAWS上にt3.microのAmazon Linux 2023インスタンスを2台配置した検証環境を用意しました。
私たちが普段業務で扱う監視システムを題材に、以下の役割を持たせて構築を行います。
・Ansible実行ノード
Ansibleをインストールし、Playbookを実行するサーバ。今回はここにPrometheus/Grafanaも同居させ、監視サーバの役割も兼任します。
・監視対象サーバ
Node Exporter(監視エージェント)をインストールし、Prometheusからメトリクスを収集される側のサーバ。監視対象として動作するシンプルな構成としています。
今回は Ansible を学ぶことが主目的のため、Prometheus/Grafanaの詳細なセットアップには触れません。
そこで、環境を素早く準備できる Docker Compose を利用し、Prometheus/Grafanaの構築コストは最小化しています。
5. Ansibleのディレクトリ構成
全体のディレクトリ構成は以下の通りです。

フォルダが分かれていて一見複雑に見えますが、役割はシンプルです。
・inventory: 「どこに」するか(対象サーバのリスト)
・playbooks: 「何を」するか(全体の指示書)
・roles: 「どう」するか(具体的な手順書)
今回は学習用なので最小限ですが、実務でも役割ごとにフォルダを分けるのがAnsibleの基本作法です。最初は「ファイルが多くて面倒」と感じるかもしれませんが、ここを分けておくとエラーの原因調査や保守が驚くほど楽になります。
では次の章では、実際のメイン処理を記述している tasks/main.yml の要点について詳しく解説します。
6. 各 taskのポイント
ここでは実際にコードを記述する中で重要だと感じたポイントなどについて解説します。
※コードは一部抜粋して記載
6.1. docker_engine(roles/docker_engine/tasks/main.yml)
PrometheusとGrafanaをDocker Composeで動かすための事前準備を行う Role です。
###①##########
###Docker インストール
- name: Install Docker engine
package: # OS標準のパッケージマネージャ(yum/dnf/aptなど)で管理
name: docker # パッケージ名を指定するとansibleが内部的に`apt install docker`を自動実行してくれる
state: present # パッケージがインストールされている状態を指定
###############
###②##########
### Docker 起動+自動起動
- name: Enable and start Docker service
service: # systemdサービスを制御するモジュール
name: docker
state: started # 起動状態を指定(既に起動済みであれば変更されない)
enabled: true # 自動起動を設定
##############
###docker-composeインストール
- name: Download docker-compose binary
get_url: # 指定URLからファイルをダウンロードするモジュール
url: "https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64"
dest: /usr/local/bin/docker-compose # ダウンロード先のパスを指定
mode: "0755"
①インストールコマンドのOS差異吸収
package モジュールを利用すると、dnf や apt など OS ごとの差異を意識せずに、共通の記述でパッケージ管理が行えます。また、state: present は「そのパッケージがインストールされた状態であるべき」という宣言的な指定であり、すでに導入済みであれば処理を行いません。
Ansibleは、タスクを順に実行していく“手続き型”の仕組みを基本にしつつ、state 句のような“宣言的”な記述も含んでいます。
手続き型とは、例えばシェルスクリプトのように「このコマンドを実行する」「次にこれを実行する」と、操作の手順そのものを記述する方法です。書いたとおりの処理が必ず実行されるため、状態を問わず毎回コマンドが走ります。
一方で宣言的アプローチは「あるべき状態」を指定し、必要な変更があるときだけ処理が実行されます。
この区別を誤ると、例えば command モジュールを使って毎回サービスを再起動してしまったり、file モジュールで state の指定を誤って意図せずファイルが削除される、といった問題につながります。
そのため、タスクが“状態を指定しているのか”“手順を実行させているのか”を明確に意識して書くことが重要です。
②systemdサービスの自動起動
serviceモジュールで enabled: true を指定することで、systemctl enable node-exporterのような自動起動設定をAnsible側で確実に行ってくれます。サーバ構築の中でも「忘れがちな作業」をタスクとして明示できる点がとても便利です。また、シェルスクリプトのようにコマンドをそのまま貼り付けるのではなく、抽象的な記述で必要な状態を整えてくれるところに、Ansibleの便利さを感じます。
6.2. monitoring_stack(roles/monitoring_stack/tasks/main.yml)
Prometheus / Grafanaが参照するディレクトリを事前に作成する処理です。
###①##########
###監視スタックに必要なディレクトを作成
- name: Create monitoring directory
file:
path: "{{ item }}" # loopモジュールで渡される要素が順に格納される
state: directory # ディレクトリを作成する宣言
mode: '0755'
loop:
- /opt/monitor
- /opt/monitor/prometheus
- /opt/monitor/grafana
##############
###②##########
### docker-compose.yml 配置
- name: Deploy docker-compose file
template: # .j2テンプレートを設定ファイルとして配置するモジュール
src: docker-compose.yml.j2 # 使用するテンプレートを指定
dest: /opt/monitor/docker-compose.yml # テンプレートファイルの配置先を指定
mode: '0644'
##############
###③##########
### docker-compose 起動(Prometheus/Grafana起動)
- name: Start docker-compose
command: docker-compose up -d # コマンドをそのまま実行させるモジュール
args: # コマンド実行前に以下の動きを実行する
chdir: /opt/monitor
##############
①loop処理による効率化
loopに渡したパスが{{ item }}に順に展開されるため、同じ処理を繰り返し書かずに複数ディレクトリを1つのタスクで生成できます。
単に記述量が減るだけでなく、「作成するパスのリスト」と「ディレクトリ作成」を分離できる点がメリットです。後からパスが増えた際もリストに行を追加するだけで済むため、保守性が向上します。
②templateモジュールで変数入りファイル展開して配置
ただのファイルコピー(copyモジュール)と違い、templateモジュールは変数展開ができます。今回は単純な配置ですが、将来的にIPアドレスやパスを変数化したい場合でも、拡張子を .j2 にしておけば柔軟に対応できる点は実運用では必須テクニックです。
③ 作業ディレクトリの指定
docker-composeをAnsibleから実行する際は、実行ディレクトリを明示的に指定することが重要です。
commandモジュールはタスクごとに独立して実行されるため、シェルのようにcdで移動した状態を次に引き継ぐ仕組みはありません。
そのため、docker-compose.yml が置かれているディレクトリで確実にコマンドを動かすには、command モジュールに対して args: chdir を指定するのが Ansible における正しいアプローチです。
6.3. agent_install(roles/agent_install/tasks/main.yml)
監視エージェント(NodeExporter)をインストールし、systemdサービスとして永続的に稼働させる処理です。
###①##########
### NodeExporterバージョンの指定
- name: Set NodeExporter version
set_fact: # ここでバージョンを指定
node_exporter_ver: "1.7.0"
##############
### NodeExporterバイナリのダウンロード
- name: Download NodeExporter archive
get_url: # 指定URLからファイルを取得するモジュール
url: "https://github.com/prometheus/node_exporter/releases/download/v{{ node_exporter_ver }}/node_exporter-{{ node_exporter_ver }}.linux-amd64.tar.gz"
dest: /tmp/node_exporter.tar.gz # tmpディレクトリ内に展開前ファイルを指定
mode: "0644"
###②##########
### アーカイブ展開(外側フォルダをstripして展開)
- name: Extract NodeExporter archive
unarchive: # アーカイブを展開する標準モジュール
src: /tmp/node_exporter.tar.gz
dest: /usr/local/bin # strip するため直接配置
remote_src: true
extra_opts:
- --strip-components=1 # そのまま展開すると/usr/local/bin/ node_exporter.tar.gz/ node_exporter となるため必要なオプション
mode: "0755"
##############
### systemdサービスファイルの配置
- name: Deploy systemd unit file for NodeExporter
template:
src: node_exporter.service.j2
dest: /etc/systemd/system/node_exporter.service
mode: "0644"
### systemdデーモン再読み込み
- name: Reload systemd
systemd:
daemon_reload: true
### NodeExporterサービス起動+自動起動設定
- name: Enable and start NodeExporter
service:
name: node_exporter
state: started
enabled: true
①set_factモジュールによるバイナリバージョンの動的な管理
set_fact を使いバージョン番号を変数定義し、URL内で参照するようにしました。
URLの中に具体的なバージョン数値が埋め込まれていると、更新時に修正ミスが起きやすくなります。変数として切り出すことで、保守性が格段に向上します。
(補足)今回は記事の読みやすさを優先して tasks/main.yml 内に set_fact を書いていますが、実運用ではここには書きません。本来は vars/main.yml などに変数を定義して、管理するのが Ansible のベストプラクティスです。「値を変えるだけなら、vars を編集するだけ」という状態にするのが理想形です。
②アーカイブ展開時のパス調整
tar.gzを展開する際、unarchiveモジュールのextra_optsで–strip-components=1を指定し、親フォルダを取り除いています。
そのまま解凍すると /usr/local/bin/node_exporter-1.7.0…/node_exporter のように解凍前の不要なフォルダが挟まってしまいます。systemdの起動ファイルに書くパスが変わってしまうため、このオプションで親フォルダを排除することで、保守性の向上につながります。
6.4. Ansible Role, Taskのまとめ
6章では、各Roleで実行される具体的なタスクとその要点を確認しました 。
これらの処理をAnsibleで標準化することで、構築手順のばらつきを防ぎ、再利用性・保守性の高い「再現性のある監視環境」の実現が可能となります。
7. playbookの実行
コードの準備ができたら、いよいよ実際にPlaybookを実行し、想定通りに監視環境が立ち上がるかを確認します。
7.1. Playbookの実行と結果確認
実行前の状態確認(監視対象サーバ)
監視対象サーバでは、Node Exporter がまだインストールされていない状態であることをwatch コマンドで確認します。watch -n 1 "systemctl status node_exporter"
実行前は以下のようにサービスが未だ無い事を示しています。
Unit node_exporter.service could not be found.
実行と結果の確認
処理内容を詳細に確認したいので、-vv オプションを付けて実行します。
ansible-playbook -i inventory/hosts.ini playbooks/monitor.yml -vv
playbook の実行が完了すると、ターミナル下部にPLAY RECAP(実行結果のサマリ)が表示されます。
PLAY RECAP *********************************************************************
10.0.1.163 : ok=16 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.0.2.55 : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
failedが0であればエラーなく完走したことを示します。
changed / ok は、変更が行われたタスク数 / 既に正しい状態だったため変更が無い事を示します。
実行後の状態確認(監視対象サーバ)
監視対象サーバ側の変化を見てみます。
Node Exporter(監視エージェント)が起動したタイミングで以下のように切り替わります。
Active: active (running)
これにより、Node Exporter が正常に自動起動したことが確認できました。
(補足)okが多い理由
PLAY RECAPのok=16は、エラー修正のために複数回実行した影響で既存タスクが再評価されているものです。エラーの内容については、8. で説明します。
7.2. 監視ツールの動作確認
実際にPrometheus/Grafanaが起動しているか、ブラウザから確認してみます。
① Prometheus

ブラウザで http://<監視元サーバのIP>:9090/targets にアクセスします。Endpointに10.0.2.55:9100が表示され、Stateが UPとなっています。
PrometheusがNode Exporterに対して、メトリクス情報を正常にプルできていることが確認できました。
② Grafana

ブラウザで http://<監視対象サーバのIP>:3000 にアクセスし、自動作成されたダッシュボードを確認します。 CPU使用率、メモリ、ディスク容量などのグラフが描画されていることが確認できました。
8. 構築時の躓きと、学んだTips
ここでは、実際に躓いたポイントや悩んだ箇所などについてお話しします。
8.1. 【躓き】コンテナのUIDの不一致問題
playbook実行時、Grafanaコンテナが「書き込み権限エラー」で起動直後に停止しました。原因は、Ansibleが作成したディレクトリ(root所有)に対し、Grafana公式イメージの実行ユーザー(UID 472)が書き込めなかったためです。
解決策として、Ansible側でディレクトリ作成時に所有者を明示的に472に指定しました 。
# roles/monitoring_stack/tasks/main.yml
- name: Grafana data directory permissions
file:
path: /opt/monitor/grafana/data
state: directory
owner: 472 # Grafana公式イメージのUIDを指定
group: 472
プロセスの実行ユーザが誰なのかという点は、設計段階でクリアにすべきだと学びました。
8.2. 【躓き】modeの値をクォートしなかったため実行エラー
ディレクトリ作成タスクで、mode: 0755 のようにクォート無しで記述していました。見た目は正しく書けていたのに、作成されたディレクトリの権限が明らかにおかしなことになっていました。
調べてみると、原因はAnsibleではなく yaml の仕様でした。かつての yaml 1.1 では0から始まる数値は8進数として扱われていましたが、現在の yaml 1.2 の仕様では、先頭が0でも10進数として扱われます。そのため、0755 と記述すると、10進数の 755 という数値がそのまま渡り、結果として全く異なる値として処理されていたのです。
Ansible公式ドキュメントでも、この誤解釈を防ぐために「modeはクォート付きで指定すること」が推奨されていました。そのため、今回は以下の書き方をしています。
mode: ‘0755’
Ansibleの仕様だけでなく、yaml の仕様やバージョンごとの挙動も前提知識として理解する必要があると感じました。
8.3. 【Tips】ブール値の表現について
エラーではありませんが、コーディング中に迷ったのが、ブール値(真偽値)の表記です。ネット上の記事を参考にしていると become: true や become: yes , enabled: yes などブール値が混在しており、私のコードでもバラバラになっていました。
# playbooks/monitor.yml
- hosts: monitor
become: true # こちらは “true”
# roles/docker_engine/tasks/main.yml
- name: Enable and start Docker service
service:
enabled: yes # こちらは “yes”
調べてみると、yaml 1.2 の仕様では yes/no は文字列扱いとなり、ブール値は true/false のみが正式な仕様となっています。Ansible は互換性のため yes も受け入れてくれますが、今後は true に統一すべきだと学びました。
8.4. 【Tips】commandモジュールはなぜ毎回Changedになるのか
Playbookを何度実行しても、最後の Docker Compose 起動タスクだけが常に「黄色(Changed)」になるのが気になりました。
# roles/monitoring_stack/tasks/main.yml
- name: Start monitoring stack
command: docker-compose up -d
コンテナが既に起動していれば、何もしない(ok)のがAnsibleの正しい挙動ではないのか?と疑問に思い調べたところ、これはcommandモジュールの仕様でした。service や yum といった専用モジュールとは異なり、commandモジュールは「コマンドを実行した=変更した」と無条件に判定します。
つまり、commandモジュールは「状態を管理する仕組みを持たない」ため、何度実行しても changed になるのは仕様通りの動きです。
こうした実行結果の背景にある細かな仕様を理解しておくことが、エラー原因の切り分けや playbook の効率性改善の観点から重要だと感じました。
9. まとめ
今回の構築を通じて、Ansible の基本的な挙動と、IaC の実用性を改めて学ぶことができました。
特に、roleによる処理の分割は非常に有用でした。正直最初は「ファイル数が増えて管理が煩雑になるのではないか」と懸念していましたが、実際にエラーが発生した際の原因切り分けがスムーズになり、保守の観点からもディレクトリ構成を整理しておく重要性を実感しました。
一方で、今回はシンプルな構成だったため、varsを用いた変数管理などは割愛しました。実務では環境ごとに設定値を変える事が必須となるため、次はそういった実践的な設定にも挑戦してみたいです。
頭では理解しているつもりでも、実際に構築してみると予想外の箇所でエラーが出るなど、実際に触って初めて分かることが多くありました。この記事が、私と同じようにこれからAnsibleを触り始める方の参考になれば幸いです。
最後までお読みいただき、ありがとうございました。
