概要 #
Ansibleは、たまにしか触らない期間があると「あのオプション、どう書くんだっけ」となりがちです。特に--checkと--diffの組み合わせ、--tagsや--limitでの絞り込み、ansible-vaultの各サブコマンドは、そのたびに調べ直すことになります。手が止まる時間をなくしたいので、実際に使うコマンドを出力つきでまとめておきます。
よく使うgit・ghコマンドまとめと同じ趣旨です。忘れたときにこのページを見れば済むようにします。
対象読者は、Ansibleのplaybookを書いたことがあり、hostsやtasksの構造は分かる人です。Ansibleそのものの入門は扱いません。
各モジュールの詳細なパラメータ(公式ドキュメントを参照する前提)は扱いません。AWX / Ansible Automation Platform、Terraformとの連携も扱わず、コマンドの使い方に絞ります。
検証環境 #
出力例はすべて、Dockerで組んだ検証環境で実際に実行した結果です。手元のOSを汚さず、壊してもdocker compose downで作り直せます。
- Ansible core 2.18.1(コントロールノード)
- 操作対象ノード3台(
web1、web2、db1。いずれもDebian 12 + sshd)
検証環境の作り方 #
コントロールノード(ansibleを実行する側)のイメージです。
# Dockerfile
FROM python:3.12-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
openssh-client \
sshpass \
&& rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir ansible-core==2.18.1
WORKDIR /work
操作対象ノード(Ansibleがsshで接続する側)のイメージです。
# Dockerfile.node
FROM debian:12-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
openssh-server \
python3 \
sudo \
&& rm -rf /var/lib/apt/lists/*
RUN useradd -m -s /bin/bash ansible \
&& echo 'ansible:ansible' | chpasswd \
&& echo 'ansible ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/ansible \
&& mkdir -p /var/run/sshd
RUN sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config \
&& sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D", "-e"]
# docker-compose.yml
services:
control:
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/work
working_dir: /work
command: sleep infinity
depends_on:
- web1
- web2
- db1
web1:
build:
context: .
dockerfile: Dockerfile.node
web2:
build:
context: .
dockerfile: Dockerfile.node
db1:
build:
context: .
dockerfile: Dockerfile.node
コンテナ名がそのままホスト名として解決されるため、インベントリにはweb1のように書くだけで繋がります。
docker compose up -d --build
docker compose exec control ansible --version
以降、コマンドはdocker compose exec control ...で実行しますが、記事内ではansible以降だけを示します。
インベントリを確認する #
まずは操作対象がどう認識されているかを確認します。
# inventory.ini
[web]
web1
web2
[db]
db1
[all:vars]
ansible_user=ansible
ansible_python_interpreter=/usr/bin/python3
app_env=staging
ansible-inventory --graph
@all:
|--@ungrouped:
|--@web:
| |--web1
| |--web2
|--@db:
| |--db1
グループの階層がひと目で分かります。ホスト名だけを確認したいときは--list-hostsです。
ansible all --list-hosts
ansible web --list-hosts
hosts (3):
web1
web2
db1
hosts (2):
web1
web2
特定のホストに適用される変数を確認したいときは--hostです。
ansible-inventory --host web1
{
"ansible_python_interpreter": "/usr/bin/python3",
"ansible_user": "ansible",
"app_env": "staging"
}
「変数が期待通り渡っているか」を調べるとき、playbookを実行する前にここで確認できます。
疎通を確認する(ad-hoc) #
playbookを書く前に、まず繋がるかを確認します。
ansible all -m ping
web1 | SUCCESS => {
"changed": false,
"ping": "pong"
}
web2 | SUCCESS => {
"changed": false,
"ping": "pong"
}
db1 | SUCCESS => {
"changed": false,
"ping": "pong"
}
pingモジュールはICMPのpingではなく、「sshで接続でき、Pythonが動く」ことを確認するものです。
任意のコマンドを流したいときはshell(またはcommand)モジュールを使います。
ansible web -m shell -a "hostname"
web1 | CHANGED | rc=0 >>
f7f49d263a8f
web2 | CHANGED | rc=0 >>
d00b687a4d3a
変数の値を確認したいときはdebugモジュールが手軽です。
ansible all -m debug -a "var=app_env"
web1 | SUCCESS => {
"app_env": "staging"
}
web2 | SUCCESS => {
"app_env": "staging"
}
db1 | SUCCESS => {
"app_env": "staging"
}
playbookを実行する #
検証に使ったplaybookです。
# site.yml
- name: Configure web servers
hosts: web
become: true
vars:
app_dir: /opt/demo-app
app_message: "hello from {{ app_env }}"
tasks:
- name: Create application directory
ansible.builtin.file:
path: "{{ app_dir }}"
state: directory
owner: ansible
mode: "0755"
tags: [setup]
- name: Deploy application config
ansible.builtin.copy:
content: |
env={{ app_env }}
message={{ app_message }}
dest: "{{ app_dir }}/app.conf"
owner: ansible
mode: "0644"
tags: [deploy, config]
- name: Show the deployed config
ansible.builtin.command: cat {{ app_dir }}/app.conf
register: config_out
changed_when: false
tags: [verify]
- name: Print the config content
ansible.builtin.debug:
var: config_out.stdout_lines
tags: [verify]
実行前に構造を確認する #
ansible-playbook site.yml --syntax-check # 文法チェックのみ
ansible-playbook site.yml --list-tasks # 実行されるタスク一覧
ansible-playbook site.yml --list-tags # 使われているタグ一覧
playbook: site.yml
play #1 (web): Configure web servers TAGS: []
tasks:
Create application directory TAGS: [setup]
Deploy application config TAGS: [config, deploy]
Show the deployed config TAGS: [verify]
Print the config content TAGS: [verify]
playbook: site.yml
play #1 (web): Configure web servers TAGS: []
TASK TAGS: [config, deploy, setup, verify]
どのタスクにどのタグが付いているかを、実行せずに確認できます。
ドライラン(--check --diff)
#
実行前にいちばん使うのがこれです。 --checkは変更を加えずに「何が変わるか」だけを見せ、--diffはその差分を表示します。
ansible-playbook site.yml --check --diff
TASK [Create application directory] ********************************************
--- before
+++ after
@@ -1,4 +1,4 @@
{
"path": "/opt/demo-app",
- "state": "absent"
+ "state": "directory"
}
changed: [web1]
TASK [Deploy application config] ***********************************************
--- before
+++ after: /opt/demo-app/app.conf
@@ -0,0 +1,2 @@
+env=staging
+message=hello from staging
changed: [web1]
PLAY RECAP *********************************************************************
web1 : ok=4 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
ディレクトリがabsentからdirectoryになり、設定ファイルの中身が追加されることが、実行前に分かります。
なお、--checkではまだファイルが存在しないため、それを読む後続タスク(cat)はスキップされます(skipped=1)。ドライランの結果がすべて実行時と一致するわけではない点は意識しておきます。
実行する #
ansible-playbook site.yml
TASK [Print the config content] ************************************************
ok: [web1] => {
"config_out.stdout_lines": [
"env=staging",
"message=hello from staging"
]
}
PLAY RECAP *********************************************************************
web1 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
PLAY RECAPのchanged=2が、実際に変更が入った数です。
2回目の実行(冪等性の確認) #
同じplaybookをもう一度流します。
PLAY RECAP *********************************************************************
web1 : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
changed=0になりました。すでに目的の状態になっているため何もしていません。playbookを書いたら2回流してchanged=0になることを確認すると、冪等に書けているかが分かります。
タグで絞る(--tags / --skip-tags)
#
ansible-playbook site.yml --tags verify # verifyタグのタスクだけ
ansible-playbook site.yml --skip-tags setup # setupタグ以外
PLAY RECAP *********************************************************************
web1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ok=5だったものがok=3になり、タスクが絞られたことが分かります(Gathering Factsは常に走ります)。
ホストを絞る(--limit)
#
ansible-playbook site.yml --limit web1
TASK [Create application directory] ********************************************
ok: [web1]
PLAY RECAP *********************************************************************
web1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2が実行対象から外れています。本番で1台だけ先に試したいときに使います。
変数を上書きする(-e)
#
ansible-playbook site.yml -e app_env=production --check --diff --tags config
--- before: /opt/demo-app/app.conf
+++ after: /opt/demo-app/app.conf
@@ -1,2 +1,2 @@
-env=staging
-message=hello from staging
+env=production
+message=hello from production
changed: [web1]
-e(--extra-vars)で渡した値が、インベントリの値より優先されます。--check --diffと組み合わせると、変数を変えたときに何が変わるかを事前に確認できます。
その他よく使うオプション #
ansible-playbook site.yml -v # 詳細出力(-vvv でさらに詳しく)
ansible-playbook site.yml --step # タスクごとに実行するか確認
ansible-playbook site.yml --start-at-task "Deploy application config" # 途中から
ansible-vault:機密情報を暗号化する #
パスワードやトークンをそのままリポジトリに置くわけにはいきません。ansible-vaultで暗号化します。
ファイルを暗号化する #
# 暗号化(既存ファイルをその場で暗号化)
ansible-vault encrypt group_vars/all.yml
# 中身を見る(復号せずに表示)
ansible-vault view group_vars/all.yml
# 編集(自動で復号→編集→再暗号化)
ansible-vault edit group_vars/all.yml
# 新規作成
ansible-vault create group_vars/secrets.yml
# 復号(ファイルを平文に戻す)
ansible-vault decrypt group_vars/all.yml
暗号化後のファイルはこうなります。
$ANSIBLE_VAULT;1.1;AES256
61323763393239376364346531623632653834363463363964303237313864356634306165376539
3963336638333662383362373866383235383430396562660a313130393331613332653338616532
37663834363363653062663662363461363663646237373065666636663939393737343437633432
ansible-vault viewなら、ファイルを平文に戻さずに中身を確認できます。
ansible_password: ansible
ansible_become_password: ansible
値ひとつだけ暗号化する(encrypt_string)
#
ファイル全体ではなく、変数1つだけを暗号化してplaybookに埋め込めます。
ansible-vault encrypt_string 'super-secret-value' --name 'db_password'
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
37626536343236643061383161356563303138636466346530396236653833336464333166383536
3835326663316238393861333839373965323062623165630a363832356537623965346662613136
61646265656638663139623664333662303939303962353132303037383131633662363566623562
3261636233356337370a303263653337346432613833663239356331326136643466666166626638
39303531313830303135613138343563303631623233633232666235653861346130
この出力をそのままYAMLに貼り付けられます。ファイル全体を暗号化すると差分が読めなくなりますが、この方式なら平文の部分はそのままレビューできます。
暗号化した状態で実行する #
パスワードの渡し方は主に3つです。
ansible-playbook site.yml --ask-vault-pass # 対話的に入力
ansible-playbook site.yml --vault-password-file .vault_pass # ファイルから読む
export ANSIBLE_VAULT_PASSWORD_FILE=.vault_pass # 環境変数で指定
パスワードファイルは必ず.gitignoreに入れます。コミットしてしまうと暗号化した意味がなくなります。
ansible-galaxy:コレクションを管理する #
ansible-galaxy collection install community.general
ansible-galaxy collection list
ansible-galaxy collection install -r requirements.yml
Starting galaxy collection install process
Process install dependency map
Starting collection install process
Downloading https://galaxy.ansible.com/api/v3/plugin/ansible/content/published/collections/artifacts/community-general-13.3.0.tar.gz to ...
Installing 'community.general:13.3.0' to '/root/.ansible/collections/ansible_collections/community/general'
community.general:13.3.0 was installed successfully
# /root/.ansible/collections/ansible_collections
Collection Version
---------------------------------------- -------
community.general 13.3.0
community.library_inventory_filtering_v1 1.1.5
依存も含めてインストールされます。ansible-coreには最小限のモジュールしか含まれないため、community.generalのようなコレクションを別途入れる場面は多いです。
エラーの読み方 #
Vaultのパスワードを渡していない #
ERROR! Attempting to decrypt but no vault secrets found
暗号化されたファイルがあるのに、パスワードの指定を忘れています。--vault-password-fileか--ask-vault-passを付けます。
ホストに接続できない #
nosuchhost | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname nosuchhost: Name or service not known",
"unreachable": true
}
UNREACHABLE!はタスクの失敗ではなく、そもそも接続できていないという意味です。failedとは区別して読みます。インベントリのホスト名、名前解決、SSHの設定を確認します。
つまずいたときの確認先 #
| やりたいこと | コマンド |
|---|---|
| 対象ホストが正しいか見たい | ansible-inventory --graph |
| 変数が期待通り渡っているか | ansible-inventory --host <ホスト> |
| 繋がるか確認したい | ansible all -m ping |
| 実行前に何が変わるか知りたい | ansible-playbook site.yml --check --diff |
| どのタスクが走るか知りたい | ansible-playbook site.yml --list-tasks |
| 1台だけで試したい | ansible-playbook site.yml --limit <ホスト> |
| 一部のタスクだけ流したい | ansible-playbook site.yml --tags <タグ> |
| 暗号化した中身を見たい | ansible-vault view <ファイル> |
| 冪等に書けているか確かめたい | 2回実行してchanged=0になるか見る |
まとめ #
--check --diffは実行前に必ず通す。何が変わるかが差分で見える。ただし前段のタスクが実行されていないぶん、後続がスキップされることがある- playbookは2回流して
changed=0になるか確認する。冪等に書けているかがすぐ分かる --limitで1台だけ、--tagsで一部のタスクだけ、と段階的に試せる- 機密情報は
ansible-vaultで暗号化する。ファイル全体か、encrypt_stringで値ひとつだけかを使い分ける UNREACHABLE!は接続の問題、failedはタスクの問題。切り分けて読む