↓メインコンテンツへスキップ
  1. Blogs/

よく使うansibleコマンドまとめ(ad-hocからplaybook・Vaultまで)

5 分
Ansible Docker CLI
0222-nnn
著者
0222-nnn
猫が好き
目次

概要
#

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はタスクの問題。切り分けて読む

参考資料
#

関連記事

よく使うgit・ghコマンドまとめ(ブランチ作成からPRマージまで)
5 分
Git GitHub CLI
TerraformでArtifact RegistryのDocker Repositoryを作成してみた
2 分
Terraform GoogleCloud Docker
nginx WebサーバーでのDocker Live Restore検証
8 分
Docker Kubernetes
Marpで編集可能なPowerPoint(.pptx)出力をDocker環境で試してみた
2 分
VSCode Docker Markdown