インフラ

マルチテナントSaaSをエアギャップ・ボックスに押し込む

クラウドSaaSを1つのオンプレミスアプライアンスとして提供することは、デプロイメントの変更ではありません。それは、テナント、クレジット、マネージド認証、そしてあらゆるクラウド依存関係を、適切な場所で削除することを意味します。

この記事は英語の原文をAIモデルが翻訳したものです。表現が原文と異なる場合があります。 英語の原文を読む

顧客が、あなたのマルチテナント型メディアSaaSを、自社ネットワーク上の単一のボックスとして、インターネット接続なし、従量課金なし、スタッフ向けの無制限の処理能力で利用したいと考えています。それは単なるパッケージング作業のように思えるかもしれません。しかし、そうではありません。製品はあらゆるレイヤーでテナント、クレジット、マネージドサインイン、そしてクラウドコントロールプレーンを前提としており、それらの前提はそれぞれ、それが存在するまさにその場所で取り除かれなければなりません。1つでも見逃すと、そのアプライアンスはクラウドへの呼び出しを漏洩させるか、あるいは販売時の要件であった「無制限」を満たせなくなります。

落とし穴:UIを隠しても制限は解除されない

要件は無制限の処理でした。最初に思いつくのは、アップグレードページとクレジットメーターを非表示にすることです。それは全くの間違いです。クレジットのチェックは1か所で行われるわけではありません。次の3か所で実行されます。

  • フロントエンドは、残高がゼロのときにアクションを非表示にします。
  • APIは、残高を超えるリクエストを拒否します。
  • ワーカーは、ジョブが完了したときに台帳をデクリメントします。

デモで見えるのは最初のものだけであり、それだけが決定的なものではありません。APIの他のあらゆるクライアント、例えば管理コンソール、古いモバイルビルド、キューから取り出されたリトライ処理などは、あなたがパッチを当てたページを読み込むことなく、同じエンドポイントに到達します。

そのため、結局は制限が表面化してしまいます。残高がまだプラスのうちに時間のかかるジョブが開始され、1時間後にワーカーが残高をゼロ以下にデクリメントすると、次のリクエストはクレジットの購入を促す支払い要求エラーを返します。しかも、請求ページもインターネット接続もないマシン上でです。その後、夜間の調整ジョブがマイナスの残高を検知し、アカウントを停止してしまいます。

「無制限」を提供するということは、3つのレイヤーすべてのチェックを一度に削除することを意味します。つまり、UIゲートをなくし、APIでの拒否をなくし、台帳のデクリメントをno-op(無操作)にするのです。これがうまく機能するかどうかは、2つの詳細にかかっています。1つは、両方を削除するのではなく、計測と強制適用を分離することです。なぜなら、顧客は依然として、先月チームが何分処理したかを知りたがるからです。もう1つは、フォークするのではなく、設定ファイルに切り替えスイッチを設けることです。チェックを削除したブランチは、1つのリリースサイクルの中でクラウドビルドから乖離していき、顧客がアップグレードするまで誰もその乖離に気づかないのです。

すべてのクラウド依存関係にはオンプレミスの代替が必要

エアギャップとは、ボックスが明示的に許可していない呼び出しを一切行わないことを意味します。SaaSは、起動時とほぼすべてのリクエストでクラウドサービスにアクセスしていましたが、それらのアクセスはそれぞれローカルで代替されることになりました:

エアギャップ境界: 許可された下り通信 vs 削除済みのクラウド依存 エアギャップ・ボックス app + worker ローカルdb、ストレージ 1 2 翻訳API TTSベンダー // 許可される下り呼び出しは2つのみ。サインオン、CDN、課金、設定ストアは削除済み

具体的な置き換えは以下の通りです。

  • Configは起動時にクラウドのパラメータストアから取得していました。これを、インストーラーが書き込むシード済みのローカル .env ファイルに置き換えました。
  • 署名付きメディアURLは、マネージドエッジワーカーによって発行されていました。これを、パス上のHMACを検証する nginx リバースプロキシに置き換えました。
  • サインインはクラウドのシングルサインオンでした。これを、管理者が事前に作成するローカルのIDとパスワードのアカウントに置き換え、セルフサービスのサインアップはなしにしました。
  • オブジェクトストレージはクラウドのバケットでした。これを、同じクライアントを異なるエンドポイントで再利用し、ローカルのS3互換サーバーに置き換えました。
  • ログとメトリクスはマネージドバックエンドに送られていました。これを、ローカルディスク上のファイルに置き換え、サイズ上限でローテーションするようにしました。

それぞれの行には、これまで無償で得られていた特性が隠されています。パラメータストアは、ホットリロードと変更履歴を備えた単一の信頼できる情報源でした。そのため、ディスク上のファイルは、設定変更ごとの再起動と、起動ログに出力される設定バージョンを意味します。ストレージは同じクライアントを維持し、エンドポイントのみを変更するため、一見無償に見えますが、クラウドのバケットは満杯になることはなく、ディスクは満杯になります。その置き換えにより、リテンションジョブ、現場の誰もダッシュボードを見ていないために製品内部に表示されるディスク警告、そしてボリュームが満杯になった場合の定義された動作が必要になります。

サインオンは、パスワードポリシー、MFA、セッションの無効化、監査証跡を提供していましたが、ローカルアカウントはこれら4つすべてを自前で持つことを意味します。また、メール機能もなくなります。パスワードリセットはオンボックスのコンソールでの管理者操作となり、インストーラーは最初の管理者を作成し、顧客は初回ログイン時にその認証情報を変更しなければなりません。

アウトバウンドコールは、翻訳APIとテキスト読み上げベンダーのちょうど2つを許可リストに入れました。それ以外はすべて、ボックス上のローカルプロセスです。ベンダーのアドレスは移動するため、ネットワークチームにはIPではなくホスト名とポートを伝え、両方のコールが目に見えるエラーで即座に失敗するようにします。ロックダウンされたネットワークは予期しない接続をサイレントにドロップし、ハングはファイアウォールルールではなく製品の故障として解釈されます。

起動を拒否する環境ゲート

そのサービスには、既知のクラウド環境以外での実行を拒否する起動チェックがありました。これは、誰かが誤って本番環境の設定で起動するのを防ぐためのガードです。エアギャップ環境のボックスでは、そのガードが起動のたびに作動します。解決策は、設定バリデーターが受け入れる新しい環境モード「on-prem」でした。これにより、同じバイナリが、決して満たすことのできないクラウドチェックなしで、ローカルインフラストラクチャーに対して起動します。これはガードを削除するよりもクリーンな方法です。なぜなら、そのガードは依然としてクラウドビルドを保護するからです。

起動を拒否することが要点です。中途半端に設定された状態で起動したアプライアンスは、後になって、あなたが到達できないネットワーク上で、あなたのログを読むことができない顧客の前で失敗します。不足している設定を名指しして起動時に停止すれば、エンジニアがその場にいるインストール中に失敗します。そのため、on-premモードは、クラウドのチェックをスキップするだけでなく、独自のアサーションを追加します。例えば、ストレージエンドポイントに到達可能であること、データディレクトリが書き込み可能であること、ライセンスファイルが存在すること、シークレットが空でないことなどです。

コストは3つ目のコードパスが生まれることであり、誰も開発対象としないモードは陳腐化していくモードです。モードごとに一度バリデーターを実行するテーブル駆動テストが、安価な防御策となります。on-prem用の値なしで必須設定が追加された場合、アプライアンスではなくビルドが失敗するようになります。

自身のアーキテクチャ図を信頼する前にコードを読む

移行作業の一環として、すべてのサービスからクラウド設定クライアントを切り離す作業がありました。あるワーカーがリストに載っていましたが、その理由は「実行時にクラウドから設定を読み取る」からでした。しかし、そうではありませんでした。コードを読んでみると、常にローカルのプレーンファイルを読み込んでおり、クラウドのパラメータストアは実行中のプロセスには一切現れず、デプロイスクリプトにのみ登場していたことがわかりました。そのワーカーはコードの変更は一切不要で、異なるシードファイルが必要なだけでした。

この間違いは逆の方向にも起こります。誰もがローカルだと思っていたメディアステップが、初回使用時にバケットからアセットバンドルを取得することが判明しました。すべてのスモークテストに合格したのは、それらのマシンが数ヶ月前にバンドルをキャッシュしていたからで、初日に新しいマシンで実行したところ失敗しました。

うまくいく方法は両面的です。名前がわかる呼び出しをコードで探します。SDKのインポート、クライアントのコンストラクタ、環境変数名、設定ファイル内のホスト名リテラルなどです。次に、ネットワークに答えさせます。接続のブロックとDNSルックアップをログに記録するデフォルト拒否の出力ルールを設定したマシンを起動し、受け入れテストを実行して、拒否ログを読みます。コードを読むことで疑いのある依存関係が見つかり、ファイアウォールによって予期しない依存関係が見つかります。

「エアギャップ」の実際のコスト

顧客は隔離性を手に入れます。その代償は4つの点に現れます。

アップデートは継続的でなくなります。リリースは誰かが持ち込む署名付きオフラインバンドルとなり、顧客は数バージョン遅れで実行することになります。そして、誰も監視しておらずロールバックボタンもないため、マイグレーションは再開可能でなければなりません。

ライセンス権限はオフラインで機能する必要があります。ライセンスは、顧客が管理する時計と照合される署名付きファイルになります。そのため、出荷前に有効期限切れの動作を決定しておく必要があります。本番ワークフローを実行するマシンでの起動を拒否すれば、サポートインシデントを招きます。有効期限切れ後に読み取り専用にすることで、ビジネスを止めることなくプレッシャーをかけ続けることができます。

サポートはその手段を失います。ログは送信されず、メトリクスもなく、通常はシェルもありません。そのため、ログ、墨塗りされた設定、バージョン、ディスクの状態を1つのファイルにまとめるバンドルコマンドを同梱しない限り、すべてのチケットは誰かに画面を読み上げてもらう電話から始まります。

デバッグはその速度を失います。再現には顧客の正確なバージョンが動作するラボのアプライアンスが必要となり、修正は次のバンドルに乗ってメンテナンスウィンドウに投入されます。

SaaSをアプライアンスに変えることは、主に慎重に行われる引き算です:

  • フロントエンド、API、ワーカーからテナントごとおよび使用量ごとの制限をまとめて削除します。さもなければ、「無制限」という約束がデモでは見えないところで破られます。
  • すべてのクラウド依存関係に名前付きのローカル代替手段を提供し、アウトバウンドコールにハードな許可リストを設定して、忘れていたものがホームコールするのではなく、明確に失敗するようにします。
  • クラウドビルドを保護するガードを削除するのではなく、環境モードを追加します。
  • 各依存関係をソースと照合して検証します。なぜなら、リスクがあるように見えるものはデプロイ時にしか使われないことが多く、本当の依存関係はリクエストパスに隠れているからです。

エアギャップ環境のアプライアンスに関するよくある質問

アプライアンスは別のブランチにすべきか、同じコードベースにすべきか?

同じコードベースで、環境モードと設定フラグで対応します。チェックを削除したブランチは、1つのリリースではきれいに見えますが、その後乖離していき、顧客のアップグレード時にその乖離が表面化します。その代償として、CIはオンプレミスモードでも設定バリデーターを実行する必要があります。

顧客が一部のアウトバウンドアクセスを許可する場合はどうするか?

その場合、許可リストは厳格な制約ではなく、交渉事項となります。正当性を主張できる範囲でリストを短く保ち、ホスト名とポートを指定し、許可されたすべての呼び出しを次の四半期には取り消し可能であるものとして扱います。許可された呼び出しに依存して構築された機能は、明確なメッセージを表示してデグレードすべきであり、決してハングしてはなりません。

到達できないマシン上のバグをどう修正するか?

サポートバンドルを受け取り、パッチを適用したバンドルを渡します。サポート対象の各リリースでラボ用アプライアンスを保持し、再現が顧客に依存しないようにします。また、移行を再開可能にして、顧客自身の作業時間内に修正を適用できるようにします。そのやり取りのサイクルは日単位で計測されるため、アプライアンスのリリースにはクラウド版よりも厳しい品質基準が求められます。