イベントページを接続する

AnsibleBeginner
オンラインで実践に進む

はじめに

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 があります。

現行 Riverlight プログラムと予定表のダウンロード

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 を使って変更のある設定だけを再読み込みさせました。その後、到達可能でも承認されていないソースをルート書き込み前に拒否し、現在のサイトを保ちました。次の独立したラボでは、旧ソースとデータを残しながらサービスのメンバーを変えます。