アプリコネクタのベストプラクティス
最終検証日:
翻訳: 竹洞 陽一郎
アプリコネクタはすべてのプランでご利用いただけます。
アプリコネクタの信頼性の高いパフォーマンスを確保するため、いくつかのベストプラクティスをお勧めします。
設定の事前構成や遅延承認の活用により、セットアップを効率化し、潜在的な障害を低減できます。
また、複雑なマルチテナント環境におけるトラフィックのルーティング方法を理解することで、パフォーマンス上の問題やアプリケーへの意図しないアクセスを防ぐことができます。
ルート探索のためのアプリ設定
ドメイン名、CNAMEレコード、ワイルドカードを活用することで、アプリコネクタのルート探索と効率を最適化できます。
ドメイン
アプリコネクタには、有効な完全修飾ドメイン名(FQDN)のリストを設定できます。
TailscaleはIPv4アドレス用のAレコード、IPv6アドレス用のAAAAレコードなどのDNSレコードをアプリコネクタ経由でルーティングします。
アプリコネクタは、設定にドメインを追加すると自動的にルートを探索します。
ルートが事前設定されていない場合、アプリコネクタはエンドユーザからの設定済みドメインへのDNSリクエストをプロキシする学習期間に入ります。
アプリコネクタはこれらのリクエストからルーティングレコードを特定し、ルートとしてアドバタイズします。
自動承認が有効な場合、新たに探索されたルートは自動的にtailnetに受け入れられ、デバイスへ即時にアドバタイズされます。
tailnetがリージョナルルーティングを使用している場合、同じリージョンのアプリコネクタ経由でトラフィックが送信されます。
すべてのルートが探索されると学習期間は終了します。
小規模なドメインでは通常すぐに完了しますが、ドメインリストが大きい場合やワイルドカードを含む場合はより長くかかることがあります。
DNSルックアップ
アプリコネクタは、アプリに設定されたドメインの権威ネームサーバとして機能します。
例えば、あるドメイン(*.example.com など)に対してアプリを設定すると、不可視のスプリットDNSエントリが作成されます。
Tailscaleはその後の*.example.comへのDNSルックアップを適切なアプリコネクタに送信します。
各アプリコネクタがDNSルックアップを実行し、成功した場合は解決されたIPアドレスに対して新しいサブネットルートを追加してから、DNSルックアップを開始したデバイスにレスポンスを返します。
DNSディスカバリ用のポートアクセス
DNSディスカバリを機能させるには、クライアントデバイスがアプリコネクタタグにアクセスできる必要があります。
ディスカバリの仕組みはPeerAPIを使用しており、アプリコネクタがピアとして見える必要があります。
ピアディスカバリを有効にするには、ポート53(tcp:53またはudp:53)への最小限のアクセスを付与してください。
このアクセスがないと、DNSルックアップがアプリコネクタに到達せず、ルート探索が行われません。
CNAMEレコード
アプリコネクタはCNAMEレコードをサポートしており、ターゲットアドレスに解決して結果のルートを自動的にアドバタイズします。
CNAMEはDNSエイリアスであり(ルーティングレコードではないため)、コネクタはチェーンをたどり、その結果のルートを自動的にアドバタイズします。
CNAMEチェーンとは、各CNAMEが別のドメイン名を指すDNSレコードのシーケンスで、最終的に宛先のIPアドレスを示すAレコードに解決されます。
digコマンドを使用してCNAMEの設定を確認できます。
以下のコードブロックは期待される出力例です。
.1. app.acme.com IN CNAME acme.backend.com .2. acme.backend.com A ...
上記の例では、CNAMEチェーンのターゲットであるacme.backend.comが、app.acme.comの代わりにルートのアドバタイズを行います。
Webサービスが地理的なDNS負荷分散を使用している場合、CNAMEレコードは場所によって異なる結果を返す場合があります。
このような場合、組織のグローバルな展開に近い場所にアプリコネクタをデプロイし、Tailscaleのリージョナルルーティングを活用することがベストプラクティスです。
ワイルドカード
アプリコネクタの設定にワイルドカード(*)を含めることができます。
ワイルドカードの動作は親ドメインを包含しません。
例えば
acme.comとそのすべてのサブドメインを含めるには、acme.comと*.acme.comの両方を使用します。- トップレベルドメインのみを含めるには、
acme.comを記載し*.acme.comは省略します。 - サブドメインのみを含めるには、
*.acme.comを記載しacme.comは省略します。
アクセス制御ポリシーによるルート設定
アクセス制御ポリシーとして、tailnetポリシーファイルのnodeAttrsトップレベルフィールド内に、ドメイン設定を含むルートを手動で事前設定できます。
例えば
{
"target": ["*"],
"app": {
"tailscale.com/app-connectors": [
{
"name": "example-app",
"connectors": ["tag:example-connector"],
"domains": ["example.com"],
"routes": ["192.0.2.0/24"],
}
],
},
}
ビジュアルポリシーエディタを使用してtailnetポリシーファイルを管理することもできます。
ビジュアルエディタの利用方法については、ビジュアルエディタリファレンスを参照してください。
tailnetのアプリコネクタデバイスは、tailnetポリシーファイルにアクセス制御ポリシーの変更を適用するとすぐに、これらのルートを自動的にアドバタイズします。
アプリケーション用のアプリコネクタはこれらのルートを即座にアドバタイズし、tailnetポリシーファイルのアクセス制御ポリシーセクションを使用して追加されたルートは暗黙的に承認されるため、autoApproversの記述は不要です。
これにより、アクセス制御と承認プロセスの一部として厳格なルーティング制御が維持されます。
また多くの場合、長い探索期間や混乱を招くルーティング変更を避けるために、デバイス設定に大量の--advertise-routes引数を使用する必要がなくなります。
ルートの集約
アクセス制御ポリシーによるルート設定で追加されたルートは、新しいアドバタイズのサブルートとなるルートを集約または置換します。
例えば、アプリコネクタが以前に192.0.2.5/32と192.0.2.67/32を探索していた場合、アクセス制御ポリシーを更新して192.0.2.0/24を追加すると、コネクタは両方の/32ルートを単一の192.0.2.0/24エントリに置き換えます。
ルート探索中の再設定を減らす
アプリコネクタのルート探索は、厳格なファイアウォール設定の環境では混乱を引き起こす可能性があります。
このような環境では、新しいルートがGoogle Chromeなどのルート対応ソフトウェアを混乱させることがあります。
アプリコネクタは、単一のドメイン解決で探索されたすべてのIPアドレスを、そのドメイン解決で新たに探索されたすべてのルートを含む単一のルート変更として即座にアドバタイズします。
探索中の再設定時間を減らすには、以下のいずれかの戦略を選択することをお勧めします。
- 事前設定とアクセス制御ポリシーによるルート設定を可能な限り活用する。
- 遅延承認を使用する。遅延承認では、未承認状態でルートを探索してからまとめて承認するまでの間、アプリファイアウォールの有効化を遅らせます。
マルチテナントIPスペースを使用するアプリの設定
一部のアプリは、コンテンツデリバリネットワーク(CDN)や共有IPスペースを使用して静的アセットを配信します。
例えば、*.static.example.comや*.assets.acme.comなどです。
これらのルートをアプリコネクタに公開すると、CDNはIPを共有していることが多く、同じパブリックIPアドレス上の他のコンテンツへのトラフィックが含まれる可能性があるため、無関係なトラフィックがアプリコネクタ経由で強制されることがあります。
副作用として、対象アプリと無関係なサイトのために、それらのコネクタ経由の帯域幅が高くなる場合があります。
そのため、CDNやマルチテナントコンテンツはアプリコネクタの外で配信することをお勧めします。
大規模SaaSサービスへの接続
新しいルートのアドバタイズにより、クライアントのネットワーク変更が発生してエラーが発生することがあります。
例えば、Google ChromeがERR_NETWORK_CHANGEDのようなエラーを報告することがあります。
遅延承認または事前設定を使用することで、これらのイベントの頻度を減らすことができます。
遅延承認
遅延承認アプローチは、新しいアプリコネクタに対して混乱を引き起こさない学習期間を設けます。ルート承認を一時的に無効にするか、上流のIPファイアウォールを使用することで承認を遅延できます。
アプリコネクタを別途インストールして、autoApproversが重複しないようにします。アプリコネクタはクライアント群からDNSクエリを受信し、アドバタイズされるルートを設定します。
ただしルートは未承認のままで、トラフィックは通常のパターンを維持します。
この状態で代表的なビジネス運用期間(例:1週間)実行すると、影響を受けるルートの大部分が収集されます。
その後、収集したルートをautoApprovers句に移行できます。
遅延承認では、ルートを承認するまでユーザはアプリケーションにアクセスできません。
事前設定
遅延承認のアプローチに必要なすべての影響ルートを収集するほど長い非混乱的な学習期間を設けられない場合は、アプリを事前設定できます。
アプリコネクタのセットアップ時にルートを設定し、アプリケーション設定時に既知のサブドメインを設定することをお勧めします。
遅延承認が可能な場合でも、事前設定によって学習フェーズを短縮できます。
ルート
GitHubやOktaなど多くのサービスは、IPアドレスリストを公開しており、アプリコネクタの--advertise-routes設定に収集できます。
ルートを事前設定することで、以下のように最適化されます。
- 単一IPアドレスではなくアドレス範囲を使用することで、ルーティングテーブルのサイズとコストを削減できます。
- 起動時にサービスが使用するほとんどのルートが設定されているため、上流WAFを即座に有効化できます。
ドメイン
多くのアプリケーションには、解決すべきすべてのサブドメインをアプリコネクタが事前に把握できないワイルドカードドメインがあります。
すべてのサブドメインが分かっている場合は、ワイルドカードドメインと一緒に明示的に追加することで、事前設定されたルートのリストを生成できます。
ヒント:サブドメインはブラウザ履歴、DNSサーバ履歴、DNSログから収集できます。
事前設定の自動化
Tailscaleが提供するconnector-genというツールで事前設定を自動化できます。
一般的なプロバイダの解析と、tailnetポリシーファイルのスニペットや--advertise-routesフラグの生成に関するシンプルな例を提供しており、ルートの事前設定戦略を手動で実行するために使用できます。
connector-genツールを実行するには、以下のようにコマンドを実行します。
./tool/go run ./cmd/connector-gen github
上記の例は、アクセス制御ポリシー用のJSONスニペットと--advertise-routesフラグを生成します。
Terraformによる自動化
注意:Terraformを使用してtailnetポリシーファイルを管理すると、次回の同期時に管理コンソールで手動加えた変更が上書きされます。
設定の損失を防ぐため、すべてのアプリコネクタ設定をTerraformで管理するようにしてください。
TerraformにはHTTPデータプロバイダがあり、上流プロバイダからIPアドレスリストを取得するために使用できます。
そのIPアドレスリストをアプリコネクタのデプロイ時にルートを事前設定するために使用できます。
以下の例は、Okta・GitHub・Amazon Web Services(AWS)などのサービスの事前設定を示しています。
data "http" "okta_ip_range_json" {
url = "https://s3.amazonaws.com/okta-ip-ranges/ip_ranges.json"
}
locals {
okta_ip_range_data = jsondecode(data.http.okta_ip_range_json.response_body)
okta_cell_ranges = [for key in keys(local.okta_ip_range_data) : local.okta_ip_range_data["${key}"].ip_ranges if key == "us_cell_1"]
okta_ip_ranges = sort(distinct(flatten(local.okta_cell_ranges)))
}
data "http" "github_meta_json" {
url = "https://api.github.com/meta"
}
locals {
github_meta = jsondecode(data.http.github_meta_json.response_body)
github_ips = concat(local.github_meta.hooks, local.github_meta.web, local.github_meta.api, local.github_meta.git, local.github_meta.github_enterprise_importer, local.github_meta.packages, local.github_meta.pages, local.github_meta.importer, local.github_meta.actions, local.github_meta.dependabot)
github_domains = concat(local.github_meta.domains.website, local.github_meta.domains.codespaces, local.github_meta.domains.copilot, local.github_meta.domains.packages, local.github_meta.domains.actions)
}
data "http" "aws_ip_ranges_json" {
url = "https://ip-ranges.amazonaws.com/ip-ranges.json"
}
locals {
aws_ip_range_data = jsondecode(data.http.aws_ip_ranges_json.response_body)
aws_ip_ranges = sort(distinct(concat([for prefix in local.aws_ip_range_data.prefixes : prefix.ip_prefix], [for prefix in local.aws_ip_range_data.ipv6_prefixes : prefix.ipv6_prefix])))
}
locals {
all_ip_ranges = concat(local.okta_ip_ranges, local.github_ips, local.aws_ip_ranges)
}