Ansible Playbook実践ガイド:インベントリー・冪等性・Vault・ローリングデプロイ
EdwardMoon
Ansible Playbookで複数Linuxサーバーのパッケージ、設定、サービス状態を宣言的に管理すると、手動操作の抜けや環境差を減らせます。しかしping成功だけで全台へ実行し、command・shellを乱用し、YAMLへパスワードを書くと、自動化は障害と情報漏えいも速く広げます。
このガイドは特定ディストリビューションや古いAnsibleへ固定せず、隔離したPython環境、YAMLインベントリー、ansible.builtinのFQCN、冪等性、Handler、check・diff、Vault、serial、block・rescueを検証可能な手順として説明します。適用前に使用中のansible-coreとコレクションの公式資料を確認してください。

主要な構成要素
| 要素 | 役割 | 運用基準 |
|---|---|---|
| Inventory | 対象ホスト、グループ、接続変数 | dev・stage・prodを分離し、重複変数を減らす |
| Play | 対象グループと実行方針 | hosts、become、serial、失敗方針を明示 |
| Task | モジュール呼出単位 | 名前、FQCN、明示的な状態を使う |
| Module | パッケージ、ファイル、サービスなどの実操作 | 可能ならcommand・shellより専用モジュール |
| Handler | 変更時だけ実行する後続処理 | 設定変更後の検証済み再起動に使う |
| Role | 再利用可能なtasks、handlers、templates、defaults | 責任を小さく分け、公開変数を文書化 |
| Collection | モジュール、プラグイン、Roleの配布単位 | 試験済みバージョンへ固定し、変更を確認 |
okはすでに期待状態、changedは実変更、failedは失敗です。成功だけでなく、再実行でchanged=0になるか、予定したHandlerだけが動くかを見て冪等性を確認します。
実行環境を準備する
制御ノードのシステムPythonへ混在させず、プロジェクトごとにvenvを作ります。ansible-core、Python、設定ファイル、コレクションパスを記録すると再現できます。
venvへAnsibleを導入する
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install ansible-core ansible-lint
ansible --version
ansible-playbook --version
ansible-lint --version
python -m pip freeze > requirements-lock.txt
本番では検証済みansible-coreとライブラリをlockファイルか承認済みリポジトリへ固定します。無条件に最新版へすると、コレクション互換性やPython要件の変化で再現性を失います。
プロジェクトディレクトリ
install -d inventories/dev/group_vars/all inventories/prod/group_vars/all
install -d roles/web/{tasks,handlers,templates,defaults}
install -d playbooks/templates
find . -maxdepth 3 -type d | sort
安全なansible.cfgの既定値
[defaults]
inventory = inventories/dev/hosts.yml
roles_path = roles
host_key_checking = True
retry_files_enabled = False
interpreter_python = auto_silent
forks = 10
timeout = 15
[privilege_escalation]
become = False
become_ask_pass = True
host_key_checkingを無効にすると中間者攻撃を検出できません。対象SSH公開鍵のフィンガープリントを信頼できる経路で確認してknown_hostsへ登録します。becomeは必要なPlayかTaskでだけ有効にし、パスワードを設定へ保存しません。
YAMLインベントリー
グループ名は役割と障害範囲を表し、ansible_hostへ接続先を記述します。本番と開発を1ファイル内のタグだけで分けるより、inventory sourceそのものを分離すると、誤ってprodを選ぶリスクが減ります。
inventories/dev/hosts.yml
all:
children:
web:
hosts:
dev-web-01:
ansible_host: 192.0.2.11
dev-web-02:
ansible_host: 192.0.2.12
database:
hosts:
dev-db-01:
ansible_host: 192.0.2.21
vars:
ansible_user: automation
ansible_become: true
構文と対象を確認する
ansible-inventory -i inventories/dev/hosts.yml --graph
ansible-inventory -i inventories/dev/hosts.yml --list | jq .
ansible web -i inventories/dev/hosts.yml -m ansible.builtin.ping --limit dev-web-01
ansible.builtin.pingはICMPではなく、遠隔Python実行とAnsible接続を確認します。SSH、Python、権限の正常性を示すだけで、パッケージリポジトリ、空き容量、サービス依存関係まで準備済みという意味ではありません。
最初のPlaybookとHandler
次のPlayはパッケージ、テンプレート、サービス状態を宣言します。Taskには読みやすい名前とFQCNを使い、templateの結果が変わった場合だけHandlerへ通知して不要な再起動を避けます。
playbooks/web.yml
---
- name: Configure web servers
hosts: web
become: true
gather_facts: true
tasks:
- name: Ensure Nginx is installed
ansible.builtin.package:
name: nginx
state: present
- name: Render Nginx virtual host
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
validate: '/usr/sbin/nginx -t -c %s'
notify: Restart Nginx
- name: Ensure Nginx is enabled and running
ansible.builtin.service:
name: nginx
enabled: true
state: started
handlers:
- name: Restart Nginx
ansible.builtin.service:
name: nginx
state: restarted
この例は新規演習ホストの/etc/nginx/nginx.conf全体を管理します。nginx -t -c %sで一時ファイルを独立したメイン設定として検証できるよう、テンプレートにeventsとhttpを含めています。本番のメイン設定を上書きすると他の仮想ホストが消える可能性があるため、そのまま適用しないでください。vhostの一部だけを配布するRoleでは、全体を組み合わせて検査する別手順が必要です。
Jinjaテンプレート
以下をplaybooks/templates/nginx.conf.j2へ保存します。変数はinventories/dev/group_vars/all/app.ymlに置き、app_portへ実際に稼働しているアプリケーションポートを指定します。
app_server_name: app.example.internal
app_port: 8080
events {
worker_connections 1024;
}
http {
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
server {
listen 80;
server_name {{ app_server_name }};
location /healthz {
access_log off;
return 200 "ok\n";
}
location / {
proxy_pass http://127.0.0.1:{{ app_port }};
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
事前検証:syntax・check・diff
構文確認後、開発inventoryの単一ホストでcheck modeを実行し、diffを確認します。checkは変更を模擬しますが全モジュールが対応するわけではなく、前のTaskで生成した値を条件式に使うPlayでは実行時と異なる場合があります。
ansible-playbook -i inventories/dev/hosts.yml playbooks/web.yml --syntax-check
ansible-playbook -i inventories/dev/hosts.yml playbooks/web.yml --check --diff --limit dev-web-01
diffには変更前後の値が出るため、secretが漏れる可能性があります。機密テンプレートではdiff: falseとno_log: trueを検討し、CIログのアクセスと保持期間も制限します。検証後も–limitを維持して実行し、その後グループ単位で広げます。
ansible-lint playbooks/web.yml
ansible-playbook -i inventories/dev/hosts.yml playbooks/web.yml --limit dev-web-01
ansible-playbook -i inventories/dev/hosts.yml playbooks/web.yml --check --limit dev-web-01
冪等性を作る
| 不安定な方法 | 推奨方法 | 理由 |
|---|---|---|
| shell: echo >> file | lineinfile・blockinfile・template | 再実行の重複を防ぐ |
| shell: yum install | package・dnf | 現状態を読み、必要な変更だけ行う |
| commandで毎回初期化 | creates・removesか専用モジュール | 完了条件を明示 |
| 毎回サービスrestart | Handler notify | 設定が変わったときだけ再起動 |
| 全エラーを無視 | failed_when・block/rescue | 想定エラーと実失敗を分ける |
commandへ完了条件を付ける
- name: Initialize application database once
ansible.builtin.command:
argv:
- /usr/local/bin/myapp-init
- --database
- /var/lib/myapp/app.db
creates: /var/lib/myapp/.initialized
register: init_result
# commandモジュールに、createsに応じたskipped/changedを報告させます。
createsは指定パスがあればcommandを実行しません。初期化成功後にプログラムが本当にmarkerを作るか確認します。changed_whenを常にfalseにすると変更とHandlerの契機を隠すため、終了コードと出力の意味に基づいて指定します。
Vaultとsecretの保護
Vaultは変数とファイルを暗号化し、リポジトリ内の平文secretを減らします。ただし保護するのは保存状態のデータです。実行中の復号値はモジュール引数、diff、debug、エラーへ現れる可能性があるため、no_log、diff制限、ログアクセス制御が必要です。
端末入力した文字列を暗号化する
read -rsp 'Secret value: ' SECRET_VALUE
echo
printf '%s' "$SECRET_VALUE" | ansible-vault encrypt_string --vault-id prod@prompt --stdin-name 'db_password' > inventories/prod/group_vars/all/vault.yml
unset SECRET_VALUE
chmod 0600 inventories/prod/group_vars/all/vault.yml
Vault IDを指定して実行する
ansible-playbook -i inventories/prod/hosts.yml playbooks/web.yml --vault-id prod@prompt --check --limit prod-web-01
# 変数単位の!vault YAMLは、--vault-idを渡したPlaybook実行時に復号されます。
機密Taskの出力を抑止する
- name: Render application secret configuration
ansible.builtin.template:
src: app-secret.conf.j2
dest: /etc/myapp/secret.conf
owner: root
group: root
mode: '0600'
no_log: true
diff: false
no_logは該当Taskの出力を減らしますが、悪意あるコード、別のdebug Task、対象システムのログまで守る境界ではありません。可能なら外部secret managerと短命な資格情報を使い、VaultパスワードファイルもGitに入れないでください。
ローリングデプロイ
配布後はmeta: flush_handlersで通知済み再起動を先に実行し、その後health checkを行います。既存版を更新する場合は承認済みバージョンをパッケージ名で指定するか、変更時間帯にmyapp_package_stateをlatestへ明示設定します。presentは既存パッケージを自動更新しません。
全台を同時変更せず、serialでバッチを制限します。max_fail_percentageは現在のバッチの失敗率が設定値を超えたときに止まります。2台中1台の失敗で止めるなら、50ではなく49など、超過条件を考慮します。
---
- name: Roll out application safely
hosts: web
become: true
serial: 2
max_fail_percentage: 49
pre_tasks:
- name: Confirm target batch
ansible.builtin.debug:
msg: "Deploying to {{ ansible_play_batch }}"
tasks:
- name: Deploy application package
ansible.builtin.package:
name: myapp
state: "{{ myapp_package_state | default('present') }}"
notify: Restart MyApp
- name: Restart changed services before checking health
ansible.builtin.meta: flush_handlers
- name: Verify local health endpoint
ansible.builtin.uri:
url: http://127.0.0.1:8080/healthz
status_code: 200
return_content: false
register: health
retries: 10
delay: 3
until: health.status == 200
handlers:
- name: Restart MyApp
ansible.builtin.service:
name: myapp
state: restarted
これは構造を説明する最小例です。実ロードバランサー環境では、各ホストを通信対象から外し、接続が終了するのを待ってから配布、health check、再登録します。delegate_toやAPIモジュールでも証明書検証、失敗処理、再実行安全性を確認してください。
エラー処理と復旧
blockは関連処理に共通のbecomeやwhenを適用し、rescueとalwaysで失敗時の流れを表します。rescueが成功すると元の失敗は復旧済みと扱われ、Playが続く場合があります。構文エラーとunreachableはrescueの対象外なので、接続失敗方針と監視は別途必要です。
- name: Back up current configuration
ansible.builtin.copy:
src: /etc/myapp/myapp.conf
dest: /var/backups/myapp.conf.pre-ansible
remote_src: true
owner: root
group: root
mode: '0600'
- name: Update service configuration with recovery
block:
- name: Render candidate configuration
ansible.builtin.template:
src: myapp.conf.j2
dest: /etc/myapp/myapp.conf
owner: root
group: root
mode: '0640'
validate: '/usr/local/bin/myapp --check-config %s'
notify: Restart MyApp
rescue:
- name: Restore previous configuration
ansible.builtin.copy:
src: /var/backups/myapp.conf.pre-ansible
dest: /etc/myapp/myapp.conf
remote_src: true
owner: root
group: root
mode: '0640'
- name: Stop this host after recovery
ansible.builtin.fail:
msg: Configuration deployment failed and was restored
always:
- name: Record completion state
ansible.builtin.debug:
msg: "Configuration block finished for {{ inventory_hostname }}"
設定の復旧ファイル1つでアプリケーションやDBスキーマまで戻せるとは限りません。前後検証、パッケージのロールバック可否、データ変更、Handler実行時点を含む別のrunbookを作ります。
品質確認と実行順序
- ansible-core、Python、コレクション、適用するGit commitを記録する。
- inventory –graph・–listで対象、変数、グループを確認する。
- ansible-lintと–syntax-checkを通す。
- 開発の単一ホストで–check –diffを実行する。
- 機密diffとno_logの範囲を確認する。
- 単一ホストへ実行後、health checkと再実行changed=0を確認する。
- stageの小さいserialバッチで障害停止と復旧を試験する。
- 承認された時間帯にprodへ配布し、recap、アプリケーション指標、ログを保存する。
ansible-config dump --only-changed
ansible-inventory -i inventories/prod/hosts.yml --graph
ansible-lint playbooks roles
ansible-playbook -i inventories/prod/hosts.yml playbooks/web.yml --syntax-check
ansible-playbook -i inventories/prod/hosts.yml playbooks/web.yml --check --diff --limit prod-web-01 --vault-id prod@prompt
運用チェックリスト
- dev・stage・prodのinventory sourceと書き込み権限を分離した。
- SSH host key検証を維持し、自動化アカウントとsudoを最小化した。
- Taskに名前とFQCNを付け、command・shellに完了・変更条件がある。
- validateとHandlerで設定変更を検証し、再実行changed=0を確認する。
- Vault、no_log、diff制限、ログアクセス制御を併用した。
- syntax、lint、check、diff、単一ホスト、小さいserialバッチの順に広げる。
- unreachable、構文エラー、データロールバックの手順がある。
- commit、inventory、limit、実行者、recap、事後検証を記録する。
公式資料と関連記事
- Ansibleインベントリー
- check modeとdiff mode
- Ansible Handler
- block・rescue・always
- Ansible Vault
- ansible-lint
- Python venvの作成と運用
まとめ
安全な自動化は、コマンドの寄せ集めではなく、期待状態、変更条件、失敗範囲、検証順序をコードで表すことです。環境別inventory、専用FQCNモジュール、Handlerで冪等性を作り、Vaultの保護範囲を正確に理解します。syntax、lint、check、diff、単一ホスト、serial、復旧試験を通った自動化だけを本番へ拡大してください。