Fullmoon System

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とコレクションの公式資料を確認してください。

Ansible自動化:インベントリー、制御ノード、SSH、サーバーグループ、検証、復旧
制御ノードで対象と宣言的タスクを検証し、SSH経由で環境別サーバーへ順次適用して結果を確認します

主要な構成要素

要素役割運用基準
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で一時ファイルを独立したメイン設定として検証できるよう、テンプレートにeventshttpを含めています。本番のメイン設定を上書きすると他の仮想ホストが消える可能性があるため、そのまま適用しないでください。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 >> filelineinfile・blockinfile・template再実行の重複を防ぐ
shell: yum installpackage・dnf現状態を読み、必要な変更だけ行う
commandで毎回初期化creates・removesか専用モジュール完了条件を明示
毎回サービスrestartHandler 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_statelatestへ明示設定します。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を作ります。

品質確認と実行順序

  1. ansible-core、Python、コレクション、適用するGit commitを記録する。
  2. inventory –graph・–listで対象、変数、グループを確認する。
  3. ansible-lintと–syntax-checkを通す。
  4. 開発の単一ホストで–check –diffを実行する。
  5. 機密diffとno_logの範囲を確認する。
  6. 単一ホストへ実行後、health checkと再実行changed=0を確認する。
  7. stageの小さいserialバッチで障害停止と復旧を試験する。
  8. 承認された時間帯に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、事後検証を記録する。

公式資料と関連記事

まとめ

安全な自動化は、コマンドの寄せ集めではなく、期待状態、変更条件、失敗範囲、検証順序をコードで表すことです。環境別inventory、専用FQCNモジュール、Handlerで冪等性を作り、Vaultの保護範囲を正確に理解します。syntax、lint、check、diff、単一ホスト、serial、復旧試験を通った自動化だけを本番へ拡大してください。