PaaS における閉域化を Private Link ＋リージョン VNET 統合で考える

（この記事は2020年6月末の情報をもとに作成されております。）

# はじめに

企業システムのクラウド移行を計画する際に、既存システムをどのように移行するのか（仮想マシンなどの IaaS への移行 or PaaS への移行）を検討されることが多いと思います。そのような中、いわゆるパブリッククラウドにおける PaaS を利用するメリットとして、「仮想マシン等の IaaS と比較して管理・運用面で考えることが少ない」・「スケールメリットを享受でき価格が比較的安価である」・「柔軟性に富んでおりスケールアウトやスケールアップが実施しやすい」といったものが挙げられると思います。また、ビジネスを加速させるうえで、インフラの管理や運用にかかるコストを減らし、よりアプリやビジネスに集中できる PaaS の注目は高まってきております。しかしながら、PaaS はパブリック IP をエンドポイントとして持つものが多く、企業の要件として挙げられることの多い「通信が外（パブリック）に出てはいけない」といった要件を満たせず、なかなか導入が難しいサービスでもありました。そこで今回は PaaS であっても通信が外へ出ない閉域構成について、 Microsoft Azure で提供される PaaS の中でも App Service にフォーカスして考えていきます。

[公式のブログ](https://docs.microsoft.com/ja-jp/archive/msdn-magazine/2017/february/azure-inside-the-azure-app-service-architecture)にも書かれているように、ユーザーが App Service にデプロイしたアプリは、他のユーザーと共用の App Service スケールユニット上に展開されます。この App Service スケールユニットは、受信トラフィック用に利用される IP アドレスとしてパブリック IP が1つ確保され、送信用 IP アドレスは5つ程度確保されるといった仕組みとなっており、ユーザーが持つ VNET（仮想ネットワーク）とのプライベートな通信は難しいです。

そのような問題の解決案の1つとしては、App Service Environment を利用することが挙げられます。App Service Environment はユーザーが持つ VNET（仮想ネットワーク）にあるサブネットに展開できる App Service であり、そこに展開されたアプリはその VNET のサブネットにあるプライベート IP を持つことができます。このように、 App Service Environment は App Service を利用しつつもユーザーの環境から外に出ない通信を実現可能なサービスとなっております。しかしながら、App Service Environment の価格は App Service と比較して非常に高額となります。1インスタンスのみの最低限の構成であっても、下記画像のように月額18万円を超える見積もりとなっております（計算には [Azure 料金計算ツール](https://azure.microsoft.com/ja-jp/pricing/calculator/)を使用）。通常の App Service で上位プランである Premium V2 の P1V2 インスタンスでも月額2万円程度なので、 App Service Environment の価格が通常の App Service に比べて高価であることが確認できると思います。さらに、App Service Environment 上のアプリをスケールアウトするためにかかる時間は、通常の App Service よりも長いといった制限もあります。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161850/c53b7279-51fc-2836-99fc-8c5321c50ede.png)


このように、App Service Environment を用いて通信の閉域化を行うことは可能とはなっておりますが、価格面がハードルとなっていることが現状です。しかしながら、最近になって登場した Private Link とリージョン VNET 統合という機能を利用するもう1つの選択肢が出てきました。こちらは、通常の App Service を利用しつつも、ユーザーが持つ VNET へのプライベートな通信を実現できる機能となっており、ユーザーは App Service（安価で柔軟性が高い） と App Service Environment（プライベートな通信が可能） の良いところどりをした構成が構築可能となりました。ただし、App Service Environment と比較し、Private Link は、デプロイスロットのうち運用スロットのみがプライベートアクセスに対応している点（それ以外のスロットはパブリックからのアクセスになる）や Private Endpoint に NSG が設定できず、アクセス制限の設定も無視される点といった制限もあります。このような制限により要件を満たせない場合には、App Service Environment が選択肢に入ってきます。App Service Environment と Private Link ともにメリットや制限がありますが、App Service の閉域化を考える際の選択肢が増えたと考え、今回は新しい選択肢である Private Link について取り上げます。

ここからは、Private Link とリージョン VNET 統合の概要を簡単に触れつつ、実際に以下の画像のような環境を構築する手順を説明します。（※今回バックエンドで利用する [Function App の制約](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-networking-options#restrict-your-storage-account-to-a-virtual-network)により、複数のインスタンスの状態を管理するための Storage Account をパブリックに晒す必要があるため、2020年6月末現在は完全な閉域化は実現できないことをご了承ください。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161850/3ccf4467-6c95-4467-00c8-4f6119665dea.png)

# App Service の閉域化について

App Service をプライベートな通信のみで利用する閉域化構成をとるためには、App Service が受信する通信と App Service が送信する通信の2つのフェーズを考える必要があります。つまり、1つの操作で App Service をプライベートな通信のみで利用することはできないということです。ここでは App Service が受信する通信の閉域化として Private Link（Private Endpoint）、App Service が送信する通信の閉域化としてリージョン VNET 統合についての概要に触れたいと思います。

### 受信側
（※Private Link には Private Endpoint と Private Link サービスの2つの機能がありますが、今回は Azure PaaS に接続する Private Endpoint について取り上げます）
Private Link（Private Endpoint） は、ユーザーが持つ VNET から Private Endpoint 経由で PaaS（App Service） へアクセスすることを可能にする機能で、上記で示した図「今回の環境」の中では、Private Endpoint から出ている矢印部分に該当する機能となっております。Private Link（Private Endpoint） は VNET → PaaS（App Service）の通信を実現する機能です。Private Link（Private Endpoint） は App Service をはじめ、様々な Azure の PaaS サービスで提供されています（一部プレビューあり、詳細は[こちら](https://docs.microsoft.com/ja-jp/azure/private-link/private-link-overview#availability)）。App Service の場合、2020年6月末現在は Premium V2 と Elastic Premium（Function App）プランでのみ Private Link（Private Endpoint） を使った接続が可能です。

Private Endpoint とよく似た機能としてサービスエンドポイントと呼ばれるものがありますが、2つの機能の大きく異なる点は、「価格」と「プライベート IP で接続できるかどうか」の2点です。Private Endpoint が VNET 内に Private Endpoint を構成し、Private Endpoint の接続先 PaaS が VNET 内にあるようにふるまうことを可能とするサービスに対し、サービスエンドポイントは PaaS サービスへアクセスできるリソースを特定の VNET のサブネットにあるものに制限できるサービスとなっております。両機能ともに、実質的な閉域化を提供していますが、Private Endpoint の場合1度もパブリック IP を使用することなく通信が完結します。また Private Endpoint は、接続先サービス内で指定したリソースとの1対1の接続を提供します。つまり、その Private Endpoint へアクセスできるユーザーがアクセスできるリソースは、Private Endpoint と接続されたリソースのみであり、他のリソースへのアクセスはブロックされるため、データ漏洩に対する保護の観点からサービスエンドポイントよりも優れています。サービスエンドポイントの場合、指定したリソースと同じリージョンにある別のリソースにもネットワーク的に到達可能となってしまいます。以下に Private Endpoint とサービスエンドポイントの簡単な比較表を示しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161850/e25f86ae-6781-4b46-b1bd-0b95316e54c2.png)

（参考リンク： https://docs.microsoft.com/ja-jp/azure/private-link/private-link-overview ）
（参考リンク： https://docs.microsoft.com/ja-jp/azure/private-link/private-endpoint-overview ）
（参考リンク： https://docs.microsoft.com/ja-jp/azure/virtual-network/virtual-network-service-endpoints-overview ）

### 送信側

リージョン VNET 統合は、App Service から送信される通信をユーザーが持つ VNET に流す機能で、上記で示した図「今回の環境」の中では、Region VNET Integration と示されている部分に該当する機能となっております。「リージョン」と名前がついているように App Service と VNET が同じリージョンに存在する場合に利用できる機能となっております。また、この機能によって VNET からアプリへのプライベートなアクセスは提供されません。つまり、App Service → VNET 方向の通信を実現する機能となっております。以下に、リージョン VNET 統合の特徴を示しました。

- サポートされる App Service 価格プランは、Standard、Premium、Premium V2、Elastic Premium（Function App）
- 他のリージョンにある VNET との接続は不可（VPN ゲートウェイを利用すると制限付きではあるが VNET 統合は実現可能）
- アプリは、統合した VNET 内のリソースにアクセス可能（ExpressRoute 接続先のリソースにもアクセス可能）

リージョン VNET 統合の設定はポータルから可能で、App Service のネットワークオプションから GUI で設定することができます（詳細は後述）。ただし、リージョン VNET 統合は PremiumV2 をサポートする新しい App Service スケールユニットからのみ利用可能という制限があります。そのため、Standard ～ Premium までのプランでリージョン VNET 統合を利用する場合は、一度 PremiumV2 でApp Service を作成し、その後スケールダウンするといった操作が必要となります。

さらに既定では、リージョン VNET 統合をした場合でも、宛先がプライベート IP （RFC1918）の通信のみが VNET 側へ送信されるため、すべてのトラフィックを VNET に流す設定と、VNET にある Private DNS Zone で名前解決する設定をする必要があります（2020年6月末現在）。

リージョン VNET 統合とよく似た機能として、ハイブリッド接続と呼ばれるものがあります。ハイブリッド接続を利用するためには、接続先のハイブリッド接続エンドポイントをホストしているネットワーク上に、Hybrid Connection Manager と呼ばれるエージェントを作成する必要があります。またハイブリッド接続では TCP 通信のみがサポートされており、UDP 通信は利用できません。（2020年6月のアップデートで Linux 版でも[ハイブリッド接続をご利用いただけるようになりました](https://azure.microsoft.com/ja-jp/updates/azure-app-service-hybrid-connections-for-linux-apps-is-now-available/)）

（参考リンク： https://docs.microsoft.com/ja-jp/azure/app-service/web-sites-integrate-with-vnet ）
（参考リンク： https://docs.microsoft.com/ja-jp/azure/app-service/app-service-hybrid-connections ）

# 実際の構築手順

ここまで、Private Link（Private Endpoint）やリージョン VNET 統合について概要を説明しました。ここでは、実際にそれらの機能を利用するための手順を説明します。もう一度、今回構築する環境の概要図を載せます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161850/3ccf4467-6c95-4467-00c8-4f6119665dea.png)

はじめに、以下の4つのサービスはポータルからデプロイしてください。Function App にて Private Endpoint やリージョン VNET 統合を利用する場合、選択肢となるホスティングプランは Premium プランと、App Service（Premium V2）プランの選択肢がありますが、今回は受信イベントの数に応じて自動でスケーリングしてくれる Premium プランを検証として利用します（Function App のホスティングプランの比較については[こちら](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-scale)）。

- Web App（App Service は PremiumV2 プラン）
- Function App（Premium プラン）
- Storage Account （何か適当なファイルを Blob ストレージへ入れておいてください）
- VNET （サブネットを少なくとも3つ用意してください）

次に、Web App と Function App の送信トラフィックを VNET へ向けるためにリージョン VNET 統合を構成します。両方とも動いている基盤は同じ App Service なので設定の画面は同じです。今回は Function App の画面を用いています。Azure ポータル上作成した Function App を選択し、メニューの中の「ネットワーク」を選択し、Vnet 統合の「構成するにはここをクリック」を選択してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161850/592a6eca-e724-24c5-046f-47ed9af071c0.png)

Vnet の追加をクリックし、統合対象としたい VNET とサブネットを選択し、「OK」をクリックしてください。同様の作業を Web App 側でも実施するのですが、1点注意事項があり、サブネットがリージョン VNET 統合を受けられるアプリの数は1つであるため、Function App と Web App それぞれの統合先は別のサブネットである必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161850/285a4bda-4aaf-ff6a-af2c-9b4987f2e48a.png)

これで、リージョン VNET 統合の設定は完了しましたが、この状態では、すべてのトラフィックが VNET に流れず、パブリック IP 宛の通信は普通に外へ流れてしまいます。そこで、すべての通信を VNET 側へ流し、VNET にある DNS サーバーで名前解決するようにアプリの設定を変更します。アプリの設定から「構成」をクリックし、「新しいアプリケーション設定」をクリックしてください。そこで、

- 名前 `WEBSITE_VNET_ROUTE_ALL` 値 `1` 
- 名前 `WEBSITE_DNS_SERVER` 値 `168.63.129.16`

と2つの設定を追加してください。これでリージョン VNET 統合の設定はすべて完了しました。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161850/1b10bfc7-42be-df1d-731f-4c2f7f349479.png)


次に、Private Link（Private Endpoint）の設定をします。Storage Account の設定から「プライベートエンドポイント接続」をクリックし、「＋プライベートエンドポイント」をクリックしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161850/5877501d-4281-d764-1360-c52d23d5a70f.png)

プライベートエンドポイントの作成ウィザードが始まり、「基本」では Private Endpoint の名前やリージョンを設定してください。次の「リソース」では下記画像のように Storage Account の Blob に対する Private Link を設定するように入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161850/07e7aa15-dbcf-1690-5eb4-57c6438bf0fb.png)

次の「構成」では、Private Endpoint を展開する VNET とサブネットを入力します。ここのサブネットは、リージョン VNET 統合をしていないサブネットを選択してください。また、「プライベート DNS ゾーンと統合する」のオプションは「はい」を選択してください。その後「確認および作成」をクリックしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161850/de926bb1-53eb-59fc-17e3-8a1c415cf01c.png)

これで Private Link（Private Endpoint）の設定が完了しました。ここでポータルから Storage Account の Blob を見に行くと「アクセス権がありません」と表示され、無事パブリックからのアクセスを拒否できていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161850/ece923e4-07c6-5a08-7089-b60d79250209.png)

Function App の場合も同じ手順で Private Link（Private Endpoint）の設定は可能ですが、2020年6月末現在、Function App の「ネットワーク」設定（VNET 統合を設定したところ）から構成すると Private DNS Zone が作成されない不具合があるため、新規作成と同じフローで Private Endpoint を作成する必要があります。新規作成から「Private Link」を選択すると下記画像のような画面に到達するので、そこで「プライベートエンドポイントの作成」をクリックしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161850/a5886360-8f70-0344-c135-3a39c5ab73ad.png)

ここから先は、先ほどの Storage Account の場合と同じ作成フローですが、「リソース」の部分は Function App であっても「sites」を選択してください（Function App も App Service で動いているため）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161850/1f1062f9-7974-6f5b-84e6-edc00a511581.png)

これで、Function App の閉域化が**ほぼ**完了（前述の Function App 用 Storage Account は現在閉域化不可）し、Function App のエンドポイントにアクセスしても下記画像のようなエラーが返ってくると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161850/9591a367-bdb5-baa9-e313-02878571b6d2.png)

## 動作確認

上記環境で、Web App から Blob の情報を返す Function App へアクセスできるのかを試してみました。Web App は非常に単純で、ページがロードされると Function App のエンドポイントへアクセスし、その結果を表示するだけのアプリです。

```csharp:index.html.cs
//functionAppEndpoint はパブリックでアクセスするときと同じエンドポイント
var content = new StringContent(json, Encoding.UTF8, "application/json");
var responce = client.PostAsync(functionAppEndpoint, content);
var resultString = await responce.Result.Content.ReadAsStringAsync();
```
Function App 側も単純なアプリで、Azure の Storage に関する SDK を用いて Storage Account へアクセスし、Blob をダウンロードするだけのアプリです。

```csharp:function1.cs
//connectionString はパブリックでアクセスするときと同じ接続文字列
BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString);
BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient("test");
BlobClient blobClient = containerClient.GetBlobClient("something.png");
// Download the blob's contents and save it to a file
BlobDownloadInfo download = await blobClient.DownloadAsync();
```

両方とも、Private Link やリージョン VNET 統合を利用しているからといって、特別なコードは書いていません。動作確認結果は以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161850/7ade916b-1e1b-5f18-78b5-430fef598585.png)

無事に閉域化環境が完成できたことを確認できました。お疲れ様でした。
またこのことから、パブリックでアクセスするときと同じ FQDN でも Private DNS Zone の名前解決によって正しく VNET 内のリソースにアクセスできていることがわかりました。


# おわりに
なかなか PaaS を利用したくても、セキュリティの観点から利用が難しかったサービスでしたが、最近のアップデートで PaaS をセキュアにご利用いただける機能が追加されました。すぐにアップデートが入り、ここの情報が古くなる可能性があるので、最新の情報は Azure の公式ドキュメントを参照いただけましたら幸いです。

余談ですが、[閉域化環境に対してアプリをデプロイする方法についての記事](https://qiita.com/tenjoufire/items/2a80a22dd0fb89a3a05f)を過去に書いたのでそちらもあわせて読んでくださればと存じます。

（この記事は2020年6月末の情報をもとに作成されております。）