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