はじめに
Riverlight Weekend のホームページは準備できていますが、来場者はプログラムや会場のページを開けません。2 つのソースサービス自体はすでに動作しています。Ansible で入口を選択したインベントリのメンバーにつなぎ、設定が変わるときだけ再読み込みさせ、不適切な代替ソースから動作中のルートを守ります。
基本的な端末操作と、小さな Ansible インベントリ、変数、playbook を読む練習が前提です。HTTP サービスの役割、グループのメンバー、別ホストの変数、JSON、読み取り専用のプローブ、再読み込み handler は使用前に説明します。ソフトウェアとイベントサービスは用意されています。所要時間は読解と通常の修正を含む著者の見積もりです。
プログラムと会場のページを接続する
このステップでは、テンプレートにアドレスを固定せず、インベントリのメンバーを使ってイベントサイトを用意されたプログラムと会場のソースにつなぎます。
Riverlight Weekend のホームと来場者ガイドは動作しています。プログラムと会場のリンクは、現在は利用できないページのメッセージを表示します。入口は用意された小さな HTTP リバースプロキシです。リクエストを別サービスへ転送し、その実際の応答を返します。ホームにプログラムのデータをコピーする仕組みではありません。入口はコントローラーで動作し、2 つのソースノードはこの学習用 VM のカーネルを共有するコンテナーです。3 台の独立 VM ではありません。ソフトウェアとサービスは準備済みで、管理するのは入口の接続だけです。
Event Site Tab を開きます。ホームには A little closer to the river. と表示されます。Explore the program をクリックすると、This page is taking a break. が表示されます。ルートが未接続なので、これは想定された HTTP 502 応答です。そのページの Back to the weekend で戻り、ナビゲーションの Visitor guide を開きます。このページはすでに動作しており、その状態を保つ必要があります。
Controller Tab に切り替えます。開始ディレクトリは /home/labex/project です。pwd は現在のディレクトリを表示します。
pwd
作業ディレクトリが分からなくなったら、cd /home/labex/project で戻れます。ファイル名を間違えると「No such file or directory」と表示されます。綴りを確認し、必要なら ls templates でそのディレクトリを一覧表示してから再試行してください。VM のリセットは不要です。
cat はファイルを表示します。承認されたサービスの選択、インベントリ、ホストごとのデータを読みます。
cat approved-services.txt
cat inventory.ini
cat host_vars/web1.yml
cat host_vars/web2.yml
インベントリの [program] と [venue] は、それぞれ 1 つのソースを選びます。web1 が現在のプログラム、web2 が会場を提供します。ホストファイルには実際の service_address とサービスのポートがあります。アドレスは VM ごとに変わる場合があります。アーカイブサービスも持つノードがありますが、到達できるだけでは承認済みの現行プログラムとはいえません。
[gateway] グループには entry が入っています。ansible_connection=local は、SSH を経由せずにコントローラー上で入口を管理する指定です。/usr/bin/python3 は用意されたローカル Python インタープリターです。HTTP のポートは、あるアドレスで待ち受けるサービスを識別します。同じノードで複数のサービスを異なるポートに配置できます。
入口を変える前に、選んだ各ソースを直接読みます。用意された curl 設定ファイルには実際の URL があります。--config はファイルから curl のオプションを読みます。fail は HTTP エラーを失敗として扱い、silent は進行表示を隠し、show-error は役立つエラーを残します。これらのファイルにパスワードやトークンはありません。
cat requests/web1.conf
curl --config requests/web1.conf
cat requests/web2.conf
curl --config requests/web2.conf
JSON 応答は program と venue、サイト riverlight、それぞれの版を示します。JSON は名前付きフィールドを持つ構造化テキストです。ソースへのリクエスト成功は直接の動作を示しますが、サイトのルートが動くことまでは証明しません。
続いてルートのテンプレートを作ります。Ansible の groups はグループ名をメンバーの一覧に対応付け、[0] は 1 メンバーのグループから先頭を選びます。hostvars はそのメンバーの変数を取得します。以下の playbook では、それぞれを program_host と venue_host と呼びます。Jinja 式の ~ は数値のポートも含めて値をテキストとしてつなぎます。| to_json は正しく引用された JSON 文字列を生成し、引用符の危険な手作業での組み立てを避けます。
以下のシェルコマンドでは > でこの 1 ファイルを置き換えます。<<'EOF' は後続行をそのまま cat に渡し、単一引用符はシェルによる Jinja の波括弧の解釈を防ぎます。単独行の終了記号 EOF まで貼り付けてください。区切りを忘れてシェルが継続入力を待つ場合は、Ctrl+C を押してコマンド全体を貼り直します。
cat > templates/routes.json.j2 <<'EOF'
{
"program": {{ ('http://' ~ hostvars[program_host].service_address ~ ':' ~ hostvars[program_host].program_port) | to_json }},
"venue": {{ ('http://' ~ hostvars[venue_host].service_address ~ ':' ~ hostvars[venue_host].venue_port) | to_json }}
}
EOF
入口の playbook を書きます。gather_facts: false は使わないマシン情報の収集を省きます。vars は現在のインベントリから評価される式を定義します。template はファイルを生成し、モードを 0644(所有者は書き込み可、全員読み取り可)に設定して、ルートファイルが変わったときだけ handler に通知します。handler は通知後に実行されるタスクで、通常は play の最後に動きます。
入口はルートをメモリーに保持するため、ファイルの置換だけでは足りません。用意された再読み込みヘルパーは、この入口の既存プロセスだけに実際の SIGHUP 再読み込みシグナルを送り、確認を待ちます。handler の command は、個別の引数の一覧である argv を使います。playbook_dir は playbook が置かれたディレクトリで、管理対象の入口パスをこのプロジェクト内に保ちます。sudo やサービス再起動は不要です。
cat > connect.yml <<'EOF'
- name: Connect the event services
hosts: gateway
gather_facts: false
vars:
program_host: "{{ groups['program'][0] }}"
venue_host: "{{ groups['venue'][0] }}"
tasks:
- name: Write the inventory-derived routes
ansible.builtin.template:
src: templates/routes.json.j2
dest: "{{ playbook_dir }}/entry/routes.json"
mode: '0644'
notify: Reload entry
handlers:
- name: Reload entry
ansible.builtin.command:
argv:
- python3
- /opt/labex/service-links/reload.py
- "{{ playbook_dir }}/entry"
EOF
まず YAML の構造を調べ、予測されるファイル変更をプレビューします。--syntax-check はタスクを適用しません。--check は対応する変更を予測し、--diff は従来の内容と提案内容を表示します。このプレビューはゲートウェイにシグナルを送らず、完成したサイトの動作も証明しません。
ansible-playbook connect.yml --syntax-check
ansible-playbook connect.yml --check --diff
差分は未接続の 127.0.0.1:9 ルートを現在のソースのアドレスとポートに置き換えるはずです。通常の方法で playbook を適用します。
ansible-playbook connect.yml
テンプレートの変更と RUNNING HANDLER [Reload entry] が表示されます。Event Site に戻り、ナビゲーションの Program で現在の 3 セッション、Paper and ink、Acoustic afternoon、Shared supper を読みます。Download the schedule をクリックして schedule.csv をダウンロードします。時間、セッション、場所が表と一致する必要があります。Visit から Riverside Yard を開き、次に Visitor guide へ進みます。ホームとガイドは引き続き使える必要があります。
接続後のプログラムにはこの 3 セッションが表示され、表の直下に Download the schedule があります。

Controller に戻ります。VM 内でも実際のダウンロードを保存して確認できます。curl の -f は HTTP エラーで失敗し、-sS は進行表示を隠しつつエラーを表示し、-o は出力ファイル名を指定します。
curl -fsS http://127.0.0.1:8081/program/schedule.csv -o schedule.csv
cat schedule.csv
期待される内容:
time,session,place
10:30,Paper and ink,Making table
13:00,Acoustic afternoon,River stage
16:00,Shared supper,Long table
127.0.0.1 はこのコントローラーを指し、入口はポート 8081 で待ち受けます。サイトの公開ブラウザーアドレスも同じサービスへ転送されます。
同じ playbook を再実行します。
ansible-playbook connect.yml
集計は changed=0、failed=0 を示し、再読み込み handler は動かないはずです。これが収束です。現在の宣言がすでにファイルと一致しています。実際のページとダウンロードが利用可能な成果を示し、集計だけでは足りません。
置き換える前に動作中のルートを守る
このステップでは、ルート書き込み前に実際の HTTP 識別情報を検査し、動作中のサイトを保ちながら到達可能なアーカイブサービスを拒否します。
Controller の /home/labex/project で続けます。HTTP 200 はサーバーが成功応答を返したことを意味し、サービス種別、イベント、版は証明しません。現行プログラムと会場は /identity で自身を識別します。用意された group_vars/all.yml に承認済みのサイトと版があります。
cat group_vars/all.yml
変更前の検査である事前チェックを追加します。assert は条件が偽なら play を停止します。最初のアサーションは、各サービスグループにちょうど 1 メンバーを要求します。各 uri タスクはコントローラーから実際の HTTP GET を送ります。return_content: true は本文を保持し、JSON 応答は .json でも参照できます。register はタスク結果を変数名の下に保存し、後のタスクに渡します。最後のアサーションは実際の識別情報と承認値を比較します。
HTTP タスクは意図的に check_mode: false を使います。読み取り専用リクエストはプレビューでも実行される必要があるためです。通常の uri は check mode でスキップされる場合があります。この設定は読み取り専用プローブだけに使い、プレビュー中のテンプレート適用や再読み込みは許可しません。3 秒のタイムアウトにより、存在しないソースが演習を長く止めるのを防ぎます。
以下の完全な事前チェック付き版に playbook を置き換えます。同じテンプレートを保ち、識別情報のアサーションをすべてテンプレートタスクの前に置いてください。
cat > connect.yml <<'EOF'
- name: Connect the event services
hosts: gateway
gather_facts: false
vars:
program_host: "{{ groups['program'][0] }}"
venue_host: "{{ groups['venue'][0] }}"
tasks:
- name: Require one member for each service
ansible.builtin.assert:
that:
- groups['program'] | length == 1
- groups['venue'] | length == 1
- name: Read the selected program identity
ansible.builtin.uri:
url: "http://{{ hostvars[program_host].service_address }}:{{ hostvars[program_host].program_port }}/identity"
return_content: true
timeout: 3
check_mode: false
register: program_probe
- name: Read the selected venue identity
ansible.builtin.uri:
url: "http://{{ hostvars[venue_host].service_address }}:{{ hostvars[venue_host].venue_port }}/identity"
return_content: true
timeout: 3
check_mode: false
register: venue_probe
- name: Check the actual service identities
ansible.builtin.assert:
that:
- program_probe.json.service == 'program'
- program_probe.json.site == event_site
- program_probe.json.edition == program_edition
- venue_probe.json.service == 'venue'
- venue_probe.json.site == event_site
- venue_probe.json.edition == venue_edition
- name: Write the inventory-derived routes
ansible.builtin.template:
src: templates/routes.json.j2
dest: "{{ playbook_dir }}/entry/routes.json"
mode: '0644'
notify: Reload entry
handlers:
- name: Reload entry
ansible.builtin.command:
argv:
- python3
- /opt/labex/service-links/reload.py
- "{{ playbook_dir }}/entry"
EOF
ansible-playbook connect.yml --syntax-check
動作中のルートファイルの指紋を保存します。sha256sum は内容のハッシュを計算し、> はその出力を別ファイルに保存して後で比較できるようにします。
sha256sum entry/routes.json > routes-before.sha256
用意された別インベントリは、プログラムとしてアーカイブを選びます。内容を読み、このコマンドだけで使うインベントリを指定する -i で意図的に試します。通常のインベントリファイルは変わりません。
cat inventory-wrong-service.ini
ansible-playbook -i inventory-wrong-service.ini connect.yml
このコマンドは非ゼロ終了で失敗し、failed=1 を示すはずです。アーカイブは到達可能で JSON を返しますが、版は承認されていません。識別情報のアサーションは Write the inventory-derived routes より前で失敗し、集計は changed=0、再読み込み handler は実行されないはずです。これは、uri が応答自体を取得できない接続拒否とは異なります。どちらも書き込みを止める必要があります。
保存した指紋を確認します。-c は現在のファイルと記録されたハッシュの比較を sha256sum に指示します。
sha256sum -c routes-before.sha256
entry/routes.json: OK が期待されます。Event Site で Program を開き、現在の予定表をもう一度ダウンロードします。提案を拒否した後も、ファイルが存在するだけでなく、現行プログラムを使える必要があります。
Controller に戻り、通常のインベントリを使います。
ansible-playbook connect.yml --check --diff
ansible-playbook connect.yml
選択した両方の識別情報が通るはずです。すでに正しいルートに変更は不要なので、集計は changed=0 で handler は動きません。YAML のインデントエラーが出たら、上の完全なブロックと比較し、タブではなくスペースを使って connect.yml を書き直します。構文エラーは、事前チェックによる正常な拒否を意味しません。
管理範囲は connect.yml、templates/routes.json.j2、その playbook 経由の入口ルートです。提供されたインベントリ、ホストデータ、承認された版、curl リクエスト、バックエンド内容、主催者メモ、ホーム、来場者ガイドを保ってください。再読み込みには /opt/labex/service-links/reload.py とこのプロジェクトの入口ディレクトリを使います。再利用するテンプレートやプローブに現在のノード名やアドレスを固定せず、ソースページを入口へコピーしたり、失敗を隠したり、ソースを再起動したり、用意されたゲートウェイを改変したりしないでください。インベントリ変更後も、設定されたメンバーとポートが実際の接続を決める必要があります。
まとめ
インベントリに基づくルートで実際のプログラムと会場サービスを接続し、サイトと正確な CSV ダウンロードを確認しました。また、handler を使って変更のある設定だけを再読み込みさせました。その後、到達可能でも承認されていないソースをルート書き込み前に拒否し、現在のサイトを保ちました。次の独立したラボでは、旧ソースとデータを残しながらサービスのメンバーを変えます。



