ランタイム環境(RTE)

RTE とは何か、プレイブックをどのように実行するか、何に触れてよいか

  1. RTE とは
  2. RTE の動作
  3. セキュリティの境界
  4. データベースモード(非推奨)

管理者向けです。RTE を実行する前、サイズを決める前、別のネットワークに移す前に、RTE を理解しておいてください。

ページ 内容
イメージ 三つの RTE イメージと、プレイブックが使うコレクションを含む独自イメージのビルド
モード アウトバウンド、インバウンド、データベース:誰が接続を開くか、スプール、ジョブのファイル
RTE の実行 各モードの設定と、それぞれの docker run の例
複数の RTE 容量、一つのアドレスの背後のレプリカ、RTE が停止または異常終了したときに起きること
別のデータセンター 手順書:何もないホストから最初のジョブまで、2 つ目のデータセンターに RTE を追加します

RTE とは

AnsibleForms 7 は、アプリのコンテナーではプレイブックを実行しません。 ansible フォームのジョブは、種類が rte の ランナー 、つまりランタイム環境に渡されます。これは AF_ROLE=rte で起動した AnsibleForms サーバーで、python と ansible を 備え、Web インターフェースはありません。アプリとは一本の WebSocket で通信し、データベースの認証情報は持ちません ( RTE_MODE が outbound または inbound 。 モード を参照)。アプリがジョブを送り、 RTE が ansible-playbook を実行し、送り返された出力と最終ステータスをアプリが保存し、ブラウザーはそれを読み込みます。

アプリはジョブの認証情報を解決し、その RTE とそのジョブだけが開けるように封をして RTE に渡します。RTE は認証情報のテーブルも シークレットストアも読まず、プレイブックは AnsibleForms 自身の環境変数( ENCRYPTION_SECRET 、 DB_* 、 RTE_TOKEN など)を 引き継ぎません。そうした値は extravar または認証情報として渡してください。データベースを自分で読み書きしていた 7.0 から 7.2 の RTE は、 7.x では非推奨のデータベースモードとして引き続き動作します( データベースモード を参照)。


RTE の動作

RTE は、アプリの隣または別のネットワークで自分で実行するコンテナーです。AnsibleForms が作成することはありません。ここでは、 どこで動作するか、アプリと何を共有するか、そしてフォームが起動されてからプレイブックのプロセスがなくなるまでに その中で何が起きるかを順に説明します。

RTE がプレイブックを実行する仕組み 運用者が管理するコンテナーホストまたは Kubernetes クラスター上で、アプリのコンテナーと一つ以上の RTE コンテナーが、それぞれの間に一本の WebSocket を持ちます。ソケットはアプリ(インバウンド)または RTE(アウトバウンド)が開きます。データベースと通信するのはアプリだけです。1:ブラウザーがアプリでフォームを起動します。2:アプリはジョブをデータベースに保存し、ランナーを選び、そのランナー向けにジョブのシークレットを封印します。3:アプリがソケットでジョブを送ります。4:RTE がジョブを受け取り、ジョブのファイルをキャッシュまたはアプリから取得します。5:RTE が ansible-playbook を子プロセスとして、ジョブごとに一つ、RTE_MAX_JOBS まで起動します。6:プレイブックが管理対象のホストに接続します。7:RTE が出力、イベント、終了をスプールに書き込み、ソケットで送ります。アプリはそれらをデータベースに保存して確認応答します。8:ブラウザーはアプリを通じてジョブを追跡します。アプリのボリュームが RTE にマウントされるのは RTE_FILES=local の場合だけです。 コンテナーホストまたは Kubernetes クラスター 管理は利用者。アウトバウンドの RTE は別のネットワークでも可 ブラウザー フォーム、ジョブページ アプリのコンテナー Web アプリ、API、ワーカー ランナー シークレットを封印 RTE ハブ 保存して確認応答 RTE コンテナー AF_ROLE=rte, uid 1000 ジョブのファイル ハッシュでキャッシュ ansible-playbook ansible-playbook 最大 RTE_MAX_JOBS スプール 確認応答まで保持 一本の Web- Socket 管理対象のホスト ssh, WinRM, APIs アプリのボリューム プレイブック、インベントリ RTE_FILES=local のみ MySQL / MariaDB アプリ専用 1 2 3 4 5 6 7 7 8

図は、どちらの側が開いたかにかかわらず、RTE ごとに一つのソケットを示しています。インバウンドの RTE にはアプリが接続し、アウトバウンドの RTE はアプリに接続します( モード を参照)。開いた後は同じメッセージが双方向に流れ、データベースを読み書きするのはアプリだけです。

誰が起動するか。 RTE は長時間動作するコンテナーで、ほかのコンテナーを実行しているものが起動します。ジョブを待ち、 ジョブの合間も動作し続けます。

AnsibleForms の実行方法 RTE を起動するもの
docker run または podman run 自分で起動します。 RTE の実行 にあるようなコマンドを、アプリに到達できる、またはアプリから到達できる任意のホストで実行します
Docker Compose docker-compose.yml の rte サービス。 app と mysqldb の隣にあります。構成シードで、インバウンドの既定のランナー rte として http://rte:8000 に宣言されています
Helm チャート Kubernetes。アプリの Pod 内の rte という 2 つ目のコンテナー( containers.rte )として起動します。アプリは 127.0.0.1 でインバウンドとして接続し、チャートのシードで既定のランナーとして宣言されています
インストーラー 上記の Docker Compose または Helm チャート、あるいは コンテナーを使わない systemd サービス。独自のランナーで、参加を申請して管理者の承認を待ちます

コンテナーエンジンは利用者のものです。ホスト上の Docker や Podman、または Kubernetes クラスターのコンテナーランタイムで、インストールと更新は 利用者が行います。AnsibleForms がそれと通信することはありません。アプリも RTE も Docker や Podman を呼び出さず、そのソケットをマウントせず、 コンテナーを起動しません。アプリはソケットを通じて RTE と通信するだけです。

中身。 プレイブックの実行に必要なものだけを備えた、一つのコンテナーです。

  • サーバー :アプリと同じ Node.js サーバーで、 tini の下で AF_ROLE=rte (イメージで設定)で起動します
  • 省かれているもの :Web インターフェース、ログイン、スケジューラー、ワーカー、すべてのデータベースモジュール。このロールでは読み込まれません
  • 待ち受けるもの :その PORT ( 8000 )。プローブ用の /live と /ready 、Prometheus 用の /metrics 、そしてインバウンドの場合は アプリが接続するソケット /rte/v1/socket 。アウトバウンドの RTE はアプリからの接続を受けません
  • Python と ansible :選んだイメージのレイヤーが提供します( RTE イメージ を参照)
  • ユーザー :サーバーとすべてのプレイブックは node (uid 1000)で実行され、root では実行されません

アプリと共有するもの。 トークンと、 RTE_FILES=local の場合はアプリのファイルです。既定の RTE_FILES=transfer では、 プレイブックとインベントリは各ジョブとともに届き、RTE に共有ディスクは不要です。

アプリと共有するもの RTE での使い方
RTE_TOKEN アプリに接続するとき(アウトバウンド)に提示するか、アプリが接続してきたとき(インバウンド)に確認します。アプリはランナーの RTE 認証情報に同じ値を保持し、ジョブのシークレットはそれで封印されます
永続ボリューム /app/dist/persistent 。 RTE_FILES=local の場合のみ プレイブックのフォルダー( ANSIBLE_PATH )、またはワーカーが REPO_PATH の下にクローンしたプレイブックのリポジトリ、インベントリ( INVENTORY_PATH とインベントリのリポジトリ)、アップロード。RTE はジョブの extravars ファイルをプレイブックのフォルダーに書き込むので、読み書き可能でマウントしてください
ssh フォルダー /home/node/.ssh プレイブックの ssh 接続が使う秘密鍵と known_hosts 。設定ページで管理しているアプリのものをマウントするか、RTE に独自のものを与えます

RTE は何もクローンしません。リポジトリはジョブのファイルとして、またはアプリと共有するボリューム上で RTE に届きます。コレクション、ロール、 ansible.cfg は、イメージから、 /etc/ansible/collections 、 /etc/ansible/roles 、 /etc/ansible/ansible.cfg にマウントした場合はそれらのフォルダーから (Docker Compose プロジェクトはそうしています)、またはプレイブックの隣にある ansible.cfg から取得されます。

ジョブのライフサイクル。 以下の番号は、ブラウザーまたは API から起動したプレイブックフォームについての、図の番号です。

  1. 起動 :アプリはフォームとその値を確認し、extravars を組み立て、ステータス running でジョブを保存します。 承認 のあるフォームは、承認者が承認するまでここで止まり、それまでどの RTE にも届きません
  2. ランナーの選択 :アプリは、フォームが runner: <name> で指定した RTE、なければ種類が RTE の既定のランナーを選び、その 名前をジョブに保存します。データベースまたは シークレットストア からジョブの認証情報、ansible のログイン、 vault パスワードを解決し、そのランナーのトークンとジョブ ID から導出したキーで AES-256-GCM により封印します
  3. 引き渡し :RTE のソケットを持つアプリノードが、ジョブを RTE に送ります。extravars、フォームのオプション、封印されたシークレット、 ジョブのファイルのハッシュです。どのアプリノードでもジョブを起動でき、ソケットを持つノードが 2 秒以内に送ります。RTE が満杯か 接続していない場合、ジョブは最大 RTE_QUEUE_MINUTES まで待機します
  4. 受け取り :RTE は claimed と答えるか、満杯、停止中、シークレットを開けない、またはジョブが自分とは異なる 契約 で届いた場合は refused と答えます。 RTE_FILES=transfer では、プレイブックとインベントリをハッシュで キャッシュから取り出し、持っていないハッシュはアプリに要求し、ジョブ専用のフォルダーに展開します
  5. 開始 :プレイブックのフォルダー(存在する必要があるフォームの playbookSubPath を加えたもの)に二つのファイルを書き込みます。フォームの値と 認証情報を含む extravars_<id>.json と、ansible のログインを含む隠しファイルです。その後、 ansible-playbook を自分の子プロセスとして、 シェルを使わず、独自のプロセスグループで起動します。それらのファイルを -e @ で、インベントリごとに -i を一つ渡し、フォームのタグ、limit、check、 diff、詳細度も渡します。vault パスワードは stdin で渡され、ファイルやコマンドラインには決して置かれません。プロセスは フィルターされた環境で node として実行されます(下記を参照)
  6. 実行 :プレイブックは RTE のコンテナーから管理対象のホストに接続します。 /home/node/.ssh のキーと known_hosts を使った ssh、 パスワードログインの場合は sshpass 、またはモジュールが呼び出す API 経由です。到達できる必要があるのはアプリではなく RTE です
  7. 出力と終了 :RTE は stdout と stderr を 0.5 秒ごとまたは 64 KB ごとにまとめ、認証情報をマスクし、各バッチに番号を付けます。 送信する前に、各項目をディスク上のジョブのスプールに書き込みます。出力、イベント、 .joblogs/job_log_<id>.log のジョブログ(2 秒ごと)、開始、そして最終ステータス( success 、 failed 、 aborted )を伴う終了です。アプリは 各項目を一度だけ保存して確認応答し、終了が保存されると RTE はスプールを破棄します。一つのストリームで PROCESS_MAX_BUFFER バイト(既定は 50 MB)を超えるとそれ以上は送らず、 プレイブックは続行します。その後、アプリはフォームの 通知 を送ります
  8. 追跡 :ブラウザーが RTE と通信することはありません。フォームのページは 2 秒ごとにアプリを通じてジョブを読み直し、ジョブページは アプリが各項目を保存するたびにライブで追跡します

ソケットが切断された場合。 プレイブックは続行します。RTE が再接続する(アウトバウンド)か、アプリノードが再び接続し(インバウンド)、 アプリがまだ確認応答していないものを再送します。アプリはすでに保存したものを飛ばすので、行が失われたり二重に保存されたりすることはありません。これが成り立つのは、 2 分以内にソケットが戻る場合です。アプリはソケットが開いている間だけ RTE のハートビートを書き込み、そのハートビートが 2 分経過すると、 プレイブックがまだ動作していても、ワーカーはその RTE の実行中のジョブを放棄されたものとして終了します。再起動した RTE が保持していたものを送れるよう、 スプール( RTE_SPOOL_PATH )はボリューム上に置いてください。RTE の再起動時に 実行中だったプレイブックは続行できません。RTE がそれを持たずに戻ってくると、アプリはそのジョブを放棄されたものとして終了します。

中止。 中止するとジョブの中止フラグが設定され、RTE のソケットを持つアプリノードが 2 秒以内に cancel を送ります。RTE は プレイブックのプロセスグループに SIGTERM を送るので、 ansible-playbook とそれが起動したすべてのフォークが一緒に停止し、ジョブは aborted で終了します。ジョブより先に届いた cancel は 1 分間保持され、そのジョブは中止された状態で開始されます。

後片付け。 プロセスが終了するとすぐに、結果にかかわらず、RTE は二つの extravars ファイル( keepExtravars のあるフォームは 表示されるほうを残しますが、隠しファイルは決して残しません)、 /tmp のアーティファクトファイル、ジョブログのファイル、ジョブ専用のファイルのフォルダーを削除します。 終了は一度だけ保存されます。RTE がすでに終了したジョブをアプリが失敗にするなど、二度目の書き込みは何も変えません。

ジョブごとに一つのプロセスであり、ジョブごとに一つのコンテナーではありません。 AWX と AAP は、ジョブごとに実行環境のイメージから新しい Pod またはコンテナーを 起動します。AnsibleForms は何も起動しません。RTE のコンテナーはすでに動作しており、各ジョブはその中の子プロセスです。ジョブは イメージのプルも Pod のスケジューリングもなしに始まり、AnsibleForms にはコンテナーエンジンやクラスターに対する権限は不要です。その代わり、 一つの RTE で動作するプレイブックは、その CPU、メモリ、 /tmp 、ホームフォルダー、python を共有します。フォークを含めた RTE_MAX_JOBS 個のプレイブックに十分なリソースをコンテナーに与えてください。プレイブックが使えるものを変えるには、ジョブではなくイメージを変更します。


セキュリティの境界

RTE はフォームの作成者が書いたコードを実行するので、保持するものをできるだけ少なくするように作られています。

  • トークンだけ :RTE はデータベースの認証情報も ENCRYPTION_SECRET も持ちません。トークン付きのアプリのソケット以外からの呼び出しは受けず、 ユーザーもログインも Web ページもありません。ソケットを通じてできるのは、アプリが送ったジョブを実行し、それについて報告し、そのファイルを 要求することだけです。ジョブを作成したり、別のジョブを読んだり、認証情報を渡したりすることはできません
  • 封印されたシークレット :ジョブを実行するために、RTE は認証情報のテーブルもシークレットストアも読みません。実行するジョブのシークレットを、 自分のトークンとそのジョブ ID だけで開けるように封印された状態で受け取るので、別の RTE や別のジョブ向けのバンドルは開けず、1 バイトでも変更されたものは 拒否されます
  • 検証された接続 :アウトバウンドでは、RTE がアプリの証明書を検証します。HTTPS のインバウンドでは、アプリは RTE の参加時にその証明書を固定し、 その証明書を提示した場合にだけ接続します( モード を参照)。プレーンな HTTP のインバウンドの RTE は、 Docker Compose プロジェクトのようなプライベートネットワークに置くものです
  • 環境を引き継がない : ansible-playbook は、AnsibleForms 自身が読み込むすべての変数( ENCRYPTION_SECRET 、 DB_PASSWORD 、 RTE_TOKEN 、 VAULT_TOKEN 、 ACCESS_TOKEN_SECRET 、 MYSQL_* など)を除いた RTE の環境を受け取るので、 lookup('env', ...) でもそれらには届きません。プレイブック向けに設定したもの( PATH 、プロキシ、 ANSIBLE_* )はそのまま渡されます
  • ユーザーからのテンプレートなし : {{ 、 {% 、 {# を含むフォームの値は、unsafe な文字列としてプレイブックに届き、 フォームが allowJinjaInExtravars を設定しない限り ansible がテンプレートとして処理することはありません。また、コマンドラインをシェルが実行することもありません
  • root ではない :RTE とそのプレイブックは node (uid 1000)で実行されるので、プレイブックにできるのは、そのファイルや ssh キーを読むなど、 コンテナー内でそのユーザーにできることだけです

そのコンテナー、イメージ、環境は、アプリのものと同じように慎重に扱ってください。


データベースモード(非推奨)

RTE_MODE のない 7.0 から 7.2 の RTE は 7.x の間ずっと動作し、8.0.0 で削除されます。上の手順とは四つの点で異なります。

  • データベースを持つ :その DB_* 設定と、自分自身を登録する場合は ENCRYPTION_SECRET ( RTE_REGISTER )
  • 引き渡しが HTTP の呼び出し :アプリがトークンと封印されたシークレットを付けて POST <address>/rte/v1/jobs を呼び出し、RTE は データベースのジョブに自分の名前を書き込んで受け取ります(手順 3 と 4)
  • 出力を自分で書き込む :データベースに、バッチで、スプールなしで書き込みます(手順 7)。中止は、2 秒ごとに読み込むジョブの中止フラグと、 /cancel の呼び出しで届きます
  • ファイルをアプリのボリュームから読む : RTE_FILES=local の場合と同じく、アプリと同じディスクが必要です

そのランナーのページには RTE の設定 は一つもありません。自分の変数を読み込むためです。ソケットモードに 移すには、 モード を参照してください。