APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

Application Gateway で公開する Private Link Service

はじめに

こんにちは。ACS 事業部の中島です。

前回の記事では Private Link Service について紹介しました。

techblog.ap-com.co.jp

今回は Application Gateway Private Link について紹介します。
※ 現在まだパブリックプレビューです。

learn.microsoft.com

Application Gateway Private Link

Private Link ServiceApplication Gateway Private Link の大きな違いは 公開できる対象 です。

Private Link Service では、制約上 VM/VMSS にホストするアプリケーションしか公開できませんでした。しかし Application Gateway Private Link では、Application Gateway が公開可能なものはすべて公開できます。より具体的には、Application Gateway がサポートする プロトコルバックエンド の組み合わせが公開可能な対象です。

Application Gateway がサポートするプロトコルは以下の通りです。

  • HTTP
  • HTTPS
  • WebSocket

バックエンドには以下の対象を指定できます。

  • IP アドレスまたは FQDN
  • VM/VMSS
  • App Service

IP アドレスまたは FQDN には ExpressRoute や VPN で接続されたものもターゲットにできます。

learn.microsoft.com

このように Private Link Service と比較してかなり広範の対象を公開できます。また Application Gateway の L7 ロードバランサーとしての機能 (SSL/TLS 終端、マルチサイトホスティング、パスベースルーティング 等)や、Azure Web Application Firewall も利用できます。

これまで公開できなかった App Service/Functions 等の PaaS 系のサービスが公開できるようになるのはとても大きなポイントです。また Private Link Service でも公開可能だった AKS についても、AGIC の選択肢が取れる、ネイティブで WAF を適用できるといったメリットがあります。

デザインパターン

基本的には Private Link Service と同じ構成が可能です。

前回の記事で紹介しているので、よろしければ下記記事内の「デザインパターン」をご覧ください。

techblog.ap-com.co.jp

サンプル構成

下記のドキュメントで Application Gateway の Private Link 構成手順が紹介されています。 learn.microsoft.com

構成するだけでは味気ないので、例のごとく少し付け足します。
Private Link Service では公開できない App Service/Functions を公開してみましょう。

構成は以下の通りです。

Application Gateway のマルチサイトホスティングで App Service を app.example.com、Functions を func.example.com として公開し、それぞれに Application Gateway Private Link 経由でアクセスできることを確認します。

では実際にリソースを作成していきましょう。Azure CLI で作成します。
※ 今回の対象リソースは Azure CLI で作成すると手数が多く大変でした…かなり長くなってしまいましたがご容赦ください。

変数定義

RESOURCE_GROUP="rg-nakajima-common-001"
LOCATION="japaneast"
HUB_VNET_NAME="vnet-agwpls-hub-001"
HUB_VM_SUBNET_NAME="snet-agwpls-hub-vm"
HUB_PE_SUBNET_NAME="snet-agwpls-hub-pe"
SPOKE_VNET_NAME="vnet-agwpls-spoke-001"
SPOKE_AGWPLS_SUBNET_NAME="snet-agwpls-spoke-agwpls"
SPOKE_AGW_SUBNET_NAME="snet-agwpls-spoke-agw"
SPOKE_PE_SUBNET_NAME="snet-agwpls-spoke-pe"
APP_PLAN_NAME="plan-agwpls-spoke-app"
APP_NAME="app-agwpls-spoke-001"
FUNC_PLAN_NAME="plan-agwpls-spoke-func"
FUNC_NAME="func-agwpls-spoke-001"
FUNC_ST_NAME="stagwplsfunc001"
PIP_NAME="pip-agwpls-agw"
AGW_NAME="agw-agwpls-spoke-001"

VNet & サブネット

今回利用する VNet とサブネットをまとめて作成します。

az network vnet create \
  --name ${HUB_VNET_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --location ${LOCATION} \
  --address-prefixes 192.168.10.0/24 \
  --subnet-name ${HUB_VM_SUBNET_NAME} \
  --subnet-prefixes 192.168.10.0/25

az network vnet subnet create \
  --name ${HUB_PE_SUBNET_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --vnet-name ${HUB_VNET_NAME} \
  --address-prefixes 192.168.10.128/25

az network vnet create \
  --name ${SPOKE_VNET_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --location ${LOCATION} \
  --address-prefixes 192.168.10.0/24 \
  --subnet-name ${SPOKE_AGWPLS_SUBNET_NAME} \
  --subnet-prefixes 192.168.10.0/26

az network vnet subnet create \
  --name ${SPOKE_AGW_SUBNET_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --vnet-name ${SPOKE_VNET_NAME} \
  --address-prefixes 192.168.10.64/26

az network vnet subnet create \
  --name ${SPOKE_PE_SUBNET_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --vnet-name ${SPOKE_VNET_NAME} \
  --address-prefixes 192.168.10.128/26

App Service & Functions

動作確認で利用する App Service/Functions を作成します。
ランタイムは Python を選択していますが、もちろん何でも構いません。

# 
az appservice plan create \
  --name ${APP_PLAN_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --location ${LOCATION} \
  --is-linux

az appservice plan create \
  --name ${FUNC_PLAN_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --location ${LOCATION} \
  --is-linux

az webapp create \
  --name ${APP_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --plan ${APP_PLAN_NAME} \
  --runtime PYTHON:3.9

az storage account create \
  --name ${FUNC_ST_NAME} \
  --resource-group ${RESOURCE_GROUP}

az functionapp create \
  --name ${FUNC_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --storage-account ${FUNC_ST_NAME} \
  --plan ${FUNC_PLAN_NAME} \
  --functions-version 4 \
  --runtime python \
  --runtime-version 3.9

APP_ID=$(
  az webapp show \
    --name ${APP_NAME} \
    --resource-group ${RESOURCE_GROUP} \
    --query "id" \
    --output tsv
)

FUNC_ID=$(
  az functionapp show \
    --name ${FUNC_NAME} \
    --resource-group ${RESOURCE_GROUP} \
    --query "id" \
    --output tsv
)

az network private-endpoint create \
  --name pe-${APP_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --connection-name app \
  --vnet-name ${SPOKE_VNET_NAME} \
  --subnet ${SPOKE_PE_SUBNET_NAME} \
  --private-connection-resource-id ${APP_ID} \
  --group-id sites

az network private-endpoint create \
  --name pe-${FUNC_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --connection-name func \
  --vnet-name ${SPOKE_VNET_NAME} \
  --subnet ${SPOKE_PE_SUBNET_NAME} \
  --private-connection-resource-id ${FUNC_ID} \
  --group-id sites

az network private-dns zone create \
  --name privatelink.azurewebsites.net \
  --resource-group ${RESOURCE_GROUP}

az network private-dns link vnet create \
  --name link-${SPOKE_VNET_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --registration-enabled false \
  --virtual-network ${SPOKE_VNET_NAME} \
  --zone-name privatelink.azurewebsites.net

az network private-endpoint dns-zone-group create \
  --name default \
  --resource-group ${RESOURCE_GROUP} \
  --endpoint-name pe-${APP_NAME} \
  --private-dns-zone privatelink.azurewebsites.net \
  --zone-name privatelink.azurewebsites.net

az network private-endpoint dns-zone-group create \
  --name default \
  --resource-group ${RESOURCE_GROUP} \
  --endpoint-name pe-${FUNC_NAME} \
  --private-dns-zone privatelink.azurewebsites.net \
  --zone-name privatelink.azurewebsites.net

Application Gateway

Application Gateway と App Service/Functions へのルーティングを設定します。

az network public-ip create \
  --name ${PIP_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --location ${LOCATION} \
  --allocation-method Static \
  --sku Standard

az network application-gateway create \
  --name ${AGW_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --location ${LOCATION} \
  --sku Standard_v2 \
  --public-ip-address ${PIP_NAME} \
  --vnet-name ${SPOKE_VNET_NAME} \
  --subnet ${SPOKE_AGW_SUBNET_NAME} \
  --private-ip-address 192.168.10.70 \
  --priority 100

DEFAULT_HTTP_SETTING_NAME=$(
  az network application-gateway http-settings list \
  --gateway-name ${AGW_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --query "[0].name" \
  --output tsv
)

DEFAULT_LISTENER_NAME=$(
  az network application-gateway http-listener list \
    --gateway-name ${AGW_NAME} \
    --resource-group ${RESOURCE_GROUP} \
    --query "[0].name" \
    --output tsv
)

DEFAULT_FRONTEND_IP_NAME=$(
  az network application-gateway frontend-ip list \
    --gateway-name ${AGW_NAME} \
    --resource-group ${RESOURCE_GROUP} \
    --query "[?contains(name,\`Private\`)].name" \
    --output tsv 
)

DEFAULT_FRONTEND_PORT_NAME=$(
  az network application-gateway frontend-port list \
    --gateway-name ${AGW_NAME} \
    --resource-group ${RESOURCE_GROUP} \
    --query "[0].name" \
    --output tsv
  )

DEFAULT_ADDRESS_POOL_NAME=$(
  az network application-gateway address-pool list \
    --gateway-name ${AGW_NAME} \
    --resource-group ${RESOURCE_GROUP} \
    --query "[0].name" \
    --output tsv
)

az network application-gateway http-settings update \
  --name ${DEFAULT_HTTP_SETTING_NAME} \
  --gateway-name ${AGW_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --host-name-from-backend-pool true

az network application-gateway http-listener update \
  --name ${DEFAULT_LISTENER_NAME} \
  --gateway-name ${AGW_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --frontend-ip ${DEFAULT_FRONTEND_IP_NAME} \
  --host-name "app.example.com"

az network application-gateway http-listener create \
  --name ${DEFAULT_LISTENER_NAME}02 \
  --gateway-name ${AGW_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --frontend-ip ${DEFAULT_FRONTEND_IP_NAME} \
  --frontend-port ${DEFAULT_FRONTEND_PORT_NAME} \
  --host-name "func.example.com"

az network application-gateway address-pool update \
  --name ${DEFAULT_ADDRESS_POOL_NAME} \
  --gateway-name ${AGW_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --servers ${APP_NAME}.azurewebsites.net

az network application-gateway address-pool create \
  --name ${DEFAULT_ADDRESS_POOL_NAME}02 \
  --gateway-name ${AGW_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --servers ${FUNC_NAME}.azurewebsites.net

az network application-gateway rule create \
  --name rule2 \
  --gateway-name ${AGW_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --priority 110 \
  --http-listener ${DEFAULT_LISTENER_NAME}02 \
  --http-settings ${DEFAULT_HTTP_SETTING_NAME} \
  --address-pool ${DEFAULT_ADDRESS_POOL_NAME}02

今回のメインである Application Gateway Private Link の設定です。
設定自体はシンプルで、Private Link の有効化と関連付ける Private Endpoint を指定するだけです。

az network vnet subnet update \
  --name ${SPOKE_AGWPLS_SUBNET_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --vnet-name ${SPOKE_VNET_NAME} \
  --disable-private-link-service-network-policies true

AGWPLS_SUBNET_ID=$(
  az network vnet subnet show \
    --resource-group ${RESOURCE_GROUP} \
    --vnet-name ${SPOKE_VNET_NAME} \
    --name ${SPOKE_AGWPLS_SUBNET_NAME} \
    --query "id" \
    --output tsv
)

az network application-gateway private-link add \
  --name pls \
  --resource-group ${RESOURCE_GROUP} \
  --gateway-name ${AGW_NAME} \
  --subnet ${AGWPLS_SUBNET_ID} \
  --frontend-ip ${DEFAULT_FRONTEND_IP_NAME}


AGW_ID=$(
  az network application-gateway show \
    --name ${AGW_NAME} \
    --resource-group ${RESOURCE_GROUP} \
    --query "id" \
    --output tsv
)

az network private-endpoint create \
  --name pe-${AGW_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --connection-name pls \
  --vnet-name ${HUB_VNET_NAME} \
  --subnet ${HUB_PE_SUBNET_NAME} \
  --private-connection-resource-id ${AGW_ID} \
  --group-id ${DEFAULT_FRONTEND_IP_NAME}

動作確認

リソース作成と設定を一通り終えたら実際にアクセスして確認してみましょう。

snet-agwpls-hub-vm に適当な VM を準備して、Application Gateway Private Link に紐づいた Private Endpoint にアクセスします。
今回は Application Gateway で FQDN ベースのアクセス振り分けを設定しています。
確認の際は、VM の hosts で app.example.comfunc.example.com と Private Endpoint の IP アドレスを紐づけて、FQDN でアクセスできるようにしておきましょう。

まずは App Service (app.example.com) です。スタート画面が表示されました。

app.example.com (pe-agw-agwpls-spoke-001) にアクセスした結果

続いて Functions (func.example.com) です。こちらも同じくスタート画面が表示されました。

func.example.com (pe-agw-agwpls-spoke-001) にアクセスした結果

Private Link 経由で公開、かつ Application Gateway での振り分けができました。

おわりに

Application Gateway Private Link を紹介しました。

PaaS 系のサービスも公開できるようになり、Private Link Service よりさらに便利になりました。
登場機会は多くないですが、刺さるときはとことん刺さるサービスです。GA が待ち遠しい…!

私達ACS事業部はAzure・AKSを活用した内製化のご支援をしております。
ご相談等ありましたらぜひご連絡ください。

www.ap-com.co.jp

また、一緒に働いていただける仲間も募集中です!
切磋琢磨しながらスキルを向上できる、エンジニアには良い環境だと思います。
ご興味を持っていただけたら嬉しく思います。

www.ap-com.co.jp

本記事の投稿者: 中島 勇人
AKSをメインにインフラ系のご支援を担当しています。ネットワーク系の技術が好きです。