Ansibleのdelegate_toとrun_onceの使い方|ロードバランサー操作を含むオーケストレーション設計

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Ansible > Ansibleのdelegate_toとrun_onceの使い方|ロードバランサー操作を含むオーケストレーション設計
「複数のWebサーバーを順番に更新したいが、ロードバランサーの切り離し処理をAnsibleでどう書けばいいかわからない」「delegate_toとrun_onceを使えばいいと聞いたが、どう組み合わせればいいのか見えてこない」

複数ホストを跨ぐ依存関係のある処理をAnsibleで自動化しようとすると、この壁に当たります。ロードバランサーの操作がその代表例です。

この記事では、delegate_toとrun_onceの仕組みをゼロから整理し、ロードバランサー操作を含む無停止デプロイのオーケストレーション設計を実装例つきで解説します。動作確認環境はRHEL 10 / Rocky Linux 9(Ansible 2.17以降)です。

この記事のポイント

・delegate_toでタスクを別ホスト(LBなど)に移譲できる
・run_once: trueで全ホスト中1回だけ実行を保証できる
・delegate_to+run_onceで実行場所と回数を同時に制御できる
・serial: 1と組み合わせて無停止ローリングデプロイを設計できる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

なぜdelegate_toとrun_onceが必要なのか

Ansibleのplaybookは、デフォルトで「hostsに指定した全ホストで各タスクを実行」します。この動作でほとんどのケースは問題ないのですが、オーケストレーションが絡む場面では不十分です。

具体的な例で考えてみましょう。NginxとアプリサーバーをまとめたWebサーバー群(web01、web02、web03)をHAProxy(lb01)の背後に並べた環境で、ローリングアップデートを自動化したいとします。

必要な処理順序は次のとおりです。

・lb01でweb01をバックエンドプールから解除する
・web01でアプリを更新し、ヘルスチェック通過を確認する
・lb01でweb01をプールに復帰させる
・同様の処理をweb02 → web03の順に繰り返す

この「lb01上でweb01に関する操作をする」という処理は、Ansibleの標準の動作(web01のコンテキストでタスクを実行)だけでは表現できません。そこで登場するのがdelegate_torun_onceです。

delegate_toの仕組みと基本的な使い方

1. delegate_toの基本構文

delegate_toは、あるホストのループ中に「別のホスト上でこのタスクを実行する」ための指令です。

# web01の処理ループ中にlb01上でコマンドを実行する例 - name: HAProxyでweb01をバックエンドから解除する shell: > echo "disable server myapp/{{ inventory_hostname }}" | socat stdio /var/run/haproxy/admin.sock delegate_to: lb01

このタスクがweb01のループで処理されるとき、shellコマンドはlb01上で実行されます。{{ inventory_hostname }}の値は「web01」のまま保持されているため、lb01上でweb01を対象にした操作が自然に書けます。

2. delegate_to実行時の変数スコープ

delegate_to使用時の変数挙動を理解しておくことが重要です。

{{ inventory_hostname }}:ループ中の元ホスト名(web01、web02…)のまま
{{ ansible_host }}:delegate先のホスト(lb01)のIPアドレスに切り替わる
{{ hostvars[inventory_hostname] }}:元ホストの変数を明示的に参照できる

この仕様を理解していないと、意図しないIPアドレスが使われてはまるケースがあります。

3. hostvarsで現在のホスト情報をdelegate先に渡す

lb01上で元ホスト(web01)のIPアドレスを参照したい場合はhostvarsを使います。

# lb01の上でweb01のIPアドレスを使った操作の例 - name: HAProxy統計ページで対象バックエンドの状態を確認 shell: > echo "show stat" | socat stdio /var/run/haproxy/admin.sock | grep {{ hostvars[inventory_hostname]['ansible_host'] }} delegate_to: lb01 register: backend_stat

run_onceの仕組みと使いどころ

1. run_onceの基本構文と動作

run_once: trueは、「hostsに複数のホストが指定されていても、このタスクをplay全体で1回だけ実行する」指令です。

# 全Webサーバーのうち1台だけでDBマイグレーションを実行する - name: DBスキーママイグレーション(1回だけ実行) shell: /app/bin/migrate.sh run_once: true

このとき、どのホストで実行されるかはAnsibleが「hostsリストの先頭ホスト」を選びます。実行ホストを固定したい場合はdelegate_toと組み合わせるのが定石です。

2. delegate_toとrun_onceを組み合わせる

「特定のホストで1回だけ実行する」という要件を確実に満たすには、両者を組み合わせます。

# Ansible実行PCからSlackへ1回だけ通知する - name: デプロイ開始をSlackに通知(1回だけ) uri: url: "{{ slack_webhook_url }}" method: POST body_format: json body: text: "Rolling deploy started: {{ ansible_date_time.iso8601 }}" delegate_to: localhost run_once: true

delegate_to: localhostはplaybookを実行しているPC自身を指します。run_onceと組み合わせることで、「Ansible実行PCからSlackへ1回だけ通知する」という処理が正確に実現できます。

よくある誤解として「delegate_toだけで1回になるんじゃないか」というものがあります。答えはNOです。delegate_toは「どこで実行するか」を変えるだけで、hostsに指定した全ホスト分ループします。run_onceを加えて初めて「1回だけ」になります。

Ansibleをより体系的に学びたい方には、Ansible実践講座でdelegate_to・run_once・serialを含む実務オーケストレーション設計を包括的に扱っています。

ロードバランサー操作を含む無停止デプロイ設計

1. 設計の全体像

構成は次のとおりです。

インベントリ:lb01(HAProxy)+ web01、web02、web03(Nginx+アプリ)
serial: 1:Webサーバーを1台ずつ順番に処理(全台同時更新によるサービス断を防止)
delegate_to: lb01:切り離し・復帰タスクをlb01上で実行
run_once:デプロイ開始・終了通知を全体で1回だけ実行

インベントリの定義は次のとおりです。

# inventory/hosts.ini [lb] lb01 ansible_host=192.168.1.10 [web] web01 ansible_host=192.168.1.21 web02 ansible_host=192.168.1.22 web03 ansible_host=192.168.1.23

2. Playbookの実装例

# deploy.yml --- ローリングアップデート with LB操作 - name: WebサーバーのローリングアップデートPlaybook hosts: web serial: 1 become: true tasks: # 1. デプロイ開始通知(全体で1回だけ) - name: デプロイ開始をSlackに通知 uri: url: "{{ slack_webhook_url }}" method: POST body_format: json body: text: "Rolling deploy started on {{ inventory_hostname }}" delegate_to: localhost run_once: true # 2. LBからこのホストを切り離す - name: HAProxyでバックエンドを無効化 shell: > echo "disable server myapp/{{ inventory_hostname }}" | socat stdio /var/run/haproxy/admin.sock delegate_to: lb01 # 3. コネクションドレインを待機する - name: ドレイン待機(30秒) pause: seconds: 30 # 4. アプリを更新する - name: アプリパッケージを最新化 dnf: name: myapp state: latest - name: アプリサービスを再起動 systemd: name: myapp state: restarted # 5. ヘルスチェックが通るまで待機する - name: ヘルスチェックエンドポイントをポーリング uri: url: "http://{{ hostvars[inventory_hostname]['ansible_host'] }}:8080/health" status_code: 200 delegate_to: localhost retries: 12 delay: 5 register: health_check until: health_check.status == 200 # 6. LBにホストを復帰させる - name: HAProxyでバックエンドを再有効化 shell: > echo "enable server myapp/{{ inventory_hostname }}" | socat stdio /var/run/haproxy/admin.sock delegate_to: lb01

3. 実行時の動作確認

このplaybookを実行すると、次のような出力が得られます。

PLAY [WebサーバーのローリングアップデートPlaybook] **** TASK [デプロイ開始をSlackに通知] **** ok: [web01 -> localhost] TASK [HAProxyでバックエンドを無効化] **** changed: [web01 -> lb01] TASK [ドレイン待機(30秒)] **** Pausing for 30 seconds ok: [web01] TASK [アプリパッケージを最新化] **** changed: [web01] TASK [アプリサービスを再起動] **** changed: [web01] TASK [ヘルスチェックエンドポイントをポーリング] **** ok: [web01 -> localhost] TASK [HAProxyでバックエンドを再有効化] **** changed: [web01 -> lb01] (以降、web02、web03と同様の処理が順次実行される) PLAY RECAP **** web01 : ok=7 changed=4 web02 : ok=7 changed=4 web03 : ok=7 changed=4

「web01 -> lb01」という表記が、delegate先(lb01)で実行されていることを示します。serial: 1により、web01の全タスクが完了してからweb02のループが始まっている点も確認できます。

設計上の注意点とトラブルシュート

1. run_onceの実行ホストが不定になる問題

run_once: trueだけを指定した場合、タスクを実行するホストは「hostsリストの先頭ホスト」になります。ただし、--limitオプションやタグで実行対象を絞った際に「先頭ホスト」が変わることがあるため、実行ホストを固定したい場合は必ずdelegate_toを組み合わせてください。

実行ホストを固定したい場合run_once: truedelegate_to: 特定ホスト名
Ansible実行PC上で1回だけ処理したい場合run_once: truedelegate_to: localhost

2. delegate_toとloopの組み合わせ時の注意

delegate_toにloopを組み合わせると、ループの各アイテムについてdelegate先でタスクが実行されます。「同じdelegate先に対して複数回接続が発生する」ことを理解した上で、SSH接続コストが問題になる場合はControlPersistの設定を検討してください。

# ansible.cfg にControlPersistを設定する例 [ssh_connection] ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ControlPath=/tmp/.ansible-ssh-%h-%p-%r

3. よくあるエラーと対処法

「The task includes an option with an undefined variable」がdelegate先で出る場合

delegate先でhostvarsを参照しようとしている場合、gather_factsが無効になっているとfactが収集されずにエラーになります。playbook冒頭のgather_facts: true(デフォルト)が明示的にfalseにされていないか確認してください。

「unreachable: true」がdelegate先で出る場合

delegate_toに指定したホストがインベントリに存在しないか、SSH接続できない状態です。次のコマンドでdelegate先への疎通を確認してください。

# lb01へのSSH疎通確認 ansible lb01 -m ping -i inventory/hosts.ini

run_onceが「serialバッチごとに繰り返し実行される」ように見える場合

serial指定時、Ansibleのバージョンによってはrun_onceが「play全体で1回」ではなく「serialのバッチ単位で1回」として動作するケースがあります(Ansible 2.9系の既知の挙動)。run_once: truedelegate_to: localhostの組み合わせにすることで、実行環境に依存しない安定した動作が得られます。

本記事のまとめ

機能 役割 典型的なユースケース
delegate_to: ホスト名 タスクを別ホストで実行する LBの切り離し・復帰操作、監視ツールへの通知
delegate_to: localhost Ansible実行PC上でタスクを実行 Slack通知、ヘルスチェックのcurl実行
run_once: true 全ホストで1回だけ実行を保証 DBマイグレーション、デプロイ開始・終了通知
両者の組み合わせ 特定ホストで厳密に1回だけ実行 スキーマ変更、リリースタグ付け
serial: 1との併用 1台ずつLBを制御しながら順次更新 無停止ローリングアップデート
Ansibleをゼロから体系的に学びたい方は、Ansible実践講座をご覧ください。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
Ansible実践ハンズオンの詳細を見る >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。