Azure Synapse Spark Poolからオンプレミスのリソースにアクセスする

# はじめに
Azure Synapseの仮想ネットワーク系のオプションがいろいろあるが、これらを使用して、Spark Poolからオンプレミス内のリソース (今回の場合はSQL Server)にアクセスするのを試してみた。Synapseで利用できる主要なネットワークオプションは以下の通り。

- **[マネージド仮想ネットワーク](https://docs.microsoft.com/ja-jp/azure/synapse-analytics/security/synapse-workspace-managed-vnet)**
    - Synapseワークスペースを仮想ネットワークに関連付けることができる。この仮想ネットワークはAzure Synapseによって管理されるため、ユーザーは通常の仮想ネットワークのようにコントロールすることができない。
- **[プライベートエンドポイント](https://docs.microsoft.com/ja-jp/azure/synapse-analytics/security/how-to-connect-to-workspace-with-private-links)**
    - Synapseワークスペースに接続するためのエンドポイント。以下の3種類のターゲットサブリソースを選択可能。
        - Sql - Dedicated SQL PoolでSQLクエリを実行するためのもの
        - SqlOnDemand - Serverless SQL PoolでSQLクエリを実行するためのもの
        - Dev - ワークスペース内のすべてにアクセスするためのもの
- **[マネージドプライベートエンドポイント](https://docs.microsoft.com/ja-jp/azure/synapse-analytics/security/synapse-workspace-managed-private-endpoints)**
    - マネージド仮想ネットワーク内にデプロイされる。SynapseがAzureのリソースにプライベート接続するためのエンドポイント
    - Azureのサービス全体に対してではなく、特定のリソースにマップされる
    - 規定でサポートされているAzure上のサービスであればそのリソースにマップしたマネージドプライベートエンドポイントを使用してアクセスすることができるが、そうでない場合は今回のようなソリューションを適用する必要がある
- **[プライベートリンクハブ](https://docs.microsoft.com/ja-jp/azure/synapse-analytics/security/synapse-private-link-hubs)**
    - プライベートリンクを使用して、仮想ネットワークからAzure Synapse Studioに接続するためのもの

上記マネージドプライベートエンドポイントのところで述べたように、Spark Poolからプライベートネットワーク (VNet)内かつ規定で選択できるマネージドプライベートエンドポイント用のリソースではないデータソース、またはオンプレミスネットワークなどのAzure外のリソースにアクセスする場合は、現在のところここでのソリューションを利用する必要がある。

なお、規定でサポートされているマネージドプライベートエンドポイント用のリソースは、現在のところ以下の通り。この中に無いデータソースにアクセスしたい場合は、 [Private Link Service](https://docs.microsoft.com/ja-jp/azure/private-link/private-link-service-overview) とマップさせたマネージドプライベートエンドポイントを利用する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144789/92d27a4c-8367-1c70-8829-3abbdfdbedee.png)

# 試した構成
実際にオンプレミスとの接続構成を作成するような環境を持ち合わせていなかったため、できるだけ近い構成にするために以下の図のように、オンプレミスと仮定した仮想ネットワークを作成して、SQL Serverをインストールした仮想マシンをその中に配置した。
仮想ネットワークはSynapseのマネージド仮想ネットワーク(Synapse Managed VNet)、トラフィック転送用の仮想ネットワーク(FWVNET)、オンプレミス想定の仮想ネットワーク(ONPREVNET)の3つ。実際のプロダクション環境では、オンプレミスとAzureのプライベート接続には通常はExpressRouteやVPNなどを使用するが、ここでは簡単にするためにVNet Peeringを使用して、転送用の仮想ネットワークとオンプレ仮定の仮想ネットワークを接続した。この構成はData Factoryのドキュメンテーション内の [チュートリアル: プライベート エンドポイントを使用して Data Factory マネージド VNet からオンプレミスの SQL Server にアクセスする方法](https://docs.microsoft.com/azure/data-factory/tutorial-managed-virtual-network-on-premise-sql-server) をSynapse Spark Poolに応用したもの。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144789/af2368cd-02ce-f35a-ac24-d22bf55146bf.png)

# 構成のポイント
この試した構成のポイントは以下の通り。

- Synapseワークスペースをマネージド仮想ネットワークに関連付けてデプロイ。
- マネージド仮想ネットワーク内のマネージドプライベートエンドポイントを使用してSpark Poolから外部のデータソースへのプライベート接続をする。
- マネージドプライベートエンドポイントは、Private Link Serviceとマップさせる。
- Private Link ServiceはAzure Load BalancerのStandardにのみ関連付けることができる。
- Azure Load Balancerはオンプレミスのリソースに直接トラフィックを転送することができないため、バックエンドプールに仮想マシンを配置して、仮想マシンにトラフィックを転送する。
- バックエンドプール内の仮想マシンにNATの設定をして、オンプレミス上の対象マシンにトラフィックが送信されるようにする。
- オンプレミスとAzure間で名前解決ができるようにDNSの設定をする。今回のテスト環境はAzureの仮想ネットワークのみで完結しているため、ショートカットのためにAzureのPrivate DNS Zoneを作成して仮想ネットワークに関連付けたが、実際のところはオンプレミスリソースの名前解決ができるようにDNSの設定が必要。

# 設定方法
## 各種リソースのデプロイ
### リソースグループの作成

```bash
export resourceGroupName="myResourceGroup"
export location="japaneast"

# Create Reource Groups
az group create --name $resourceGroupName --location $location
```

### 仮想ネットワークの作成

```bash
export FWVnetName="FWVNET"
export FWVnetPrefix="10.10.0.0/16"
export FWSubnet="FW-Subnet"
export FWSubnetPrefix="10.10.10.0/24"

export PLSubnet="PE-Subnet"
export PLSubnetPrefix="10.10.20.0/24"

export BESubnet="BE-Subnet"
export BESubnetPrefix="10.10.30.0/24"

export ONPREVnet="ONPREVNET"
export ONPREVnetPrefix="10.100.0.0/16"
export ONPRESubnet="DB-Subnet"
export ONPRESubnetPrefix="10.100.110.0/24"

# Create Forwarding VNET with a Subnet
az network vnet create -g $resourceGroupName -n $FWVnetName --address-prefix $FWVnetPrefix \
    --subnet-name $FWSubnet --subnet-prefix $FWSubnetPrefix

# Create Subnet for Private Link Servie
az network vnet subnet create -g $resourceGroupName --vnet-name $FWVnetName -n $PLSubnet \
    --address-prefixes $PLSubnetPrefix

# Create Subnet for Backend Service
az network vnet subnet create -g $resourceGroupName --vnet-name $FWVnetName -n $BESubnet \
    --address-prefixes $BESubnetPrefix

# Create On-Premise VNET with a Subnet
az network vnet create -g $resourceGroupName -n $ONPREVnet --address-prefix $ONPREVnetPrefix \
    --subnet-name $ONPRESubnet --subnet-prefix $ONPRESubnetPrefix

```

### オンプレ仮想ネットワークと転送用仮想ネットワークのVNet Peering

```bash
az network vnet peering create -g resourceGroupName -n FWVnetToFOnpre --vnet-name $FWVnetName \
    --remote-vnet $ONPREVnet --allow-vnet-access
```

### Azure Synapse workspaceのデプロイ

```bash
export synapseName="mysynapsetest20210927"
export storageAccountName="myadls20210927"
export fileSystenName="myfilesystem"
export sqlAdmin="sqladminuser1"
export sqlAdminPass="your-sql-admin-password"

# Create data lake storage gen2 account
az storage account create \
    --name $storageAccountName\
    --resource-group $resourceGroupName \
    --https-only true \
    --kind StorageV2 \
    --location $location \
    --sku Standard_LRS \
    --enable-hierarchical-namespace true

# Create filesystem in data lake storage gen2 account
az storage fs create -n $fileSystenName \
    --account-name $storageAccountName \
    --auth-mode login

# Create Synapse workspace enabling managed vnet
az synapse workspace create --name $synapseName--resource-group $resourceGroupName \
  --storage-account $storageAccountName --file-system $fileSystenName \
  --sql-admin-login-user $sqlAdmin --sql-admin-login-password $sqlAdminPass! --location $location \
  --enable-managed-virtual-network true
```

### Azure Load Balancerのデプロイ

```bash
export loadBalancerName="myLoadBalancer"
export frontEndIpName="myFrontEnd"
export backEndPoolName="myBackEndPool"
export probeName="myHealthProbe"
export lbRuleName="myRule"

# Create Azure Load Balancer
az network lb create \
    --resource-group $resourceGroupName \
    --location $location \
    --name $loadBalancerName \
    --sku Standard \
    --vnet-name $FWVnetName \
    --subnet $FWSubnet \
    --frontend-ip-name $frontEndIpName \
    --public-ip-address-allocation dynamic \
    --backend-pool-name $backEndPoolName

# Create Health Probe
az network lb probe create \
    --resource-group $resourceGroupName \
    --lb-name $loadBalancerName \
    --name $probeName \
    --protocol tcp \
    --port 22 \
    --interval 15 \
    --threshold 2

# Create Load Balancer Rule
az network lb rule create \
    --resource-group $resourceGroupName \
    --lb-name $loadBalancerName \
    --name $lbRuleName \
    --protocol tcp \
    --frontend-port 1433 \
    --backend-port 1433 \
    --frontend-ip-name $frontEndIpName \
    --backend-pool-name $backEndPoolName \
    --probe-name $probeName \
    --idle-timeout 15 \
    --enable-tcp-reset false
```

### プライベートリンクサービスの作成

```bash
export privateLinkServiceName="myPrivateLinkService"
az network private-link-service create \
    --resource-group $resourceGroupName \
    --name $privateLinkServiceName \
    --vnet-name $FWVnetName \
    --subnet $PLSubnet \
    --lb-name $loadBalancerName \
    --lb-frontend-ip-configs $frontEndIpName \
    --location $location
```

### バックエンドサーバの作成

バックエンドサーバの仮想マシンはこちらの [内部ロードバランサー向けのクイックスタート](https://docs.microsoft.com/ja-jp/azure/load-balancer/quickstart-load-balancer-standard-internal-cli?tabs=option-1-create-load-balancer-standard) を参考に作成。長いのでここでの詳細は省略するが、作成の際に以下の設定を使用した。

- OSはUbuntu Server 18.04LTS - Gen1
- パブリック受信ポートは無し。外から直接アクセスできないので、Bastionなどを使用してアクセスする必要がある
- 仮想ネットワークは作成済みのトラフィック転送用のネットワーク(FWVNET)を選択し、サブネットはバックエンド用(BE-Subnet)を選択
- パブリックIPはアサインしない
- 作成済みのロードバランサーのプールに追加する

### データソース用のデータベースを作成
Azure仮想マシン上にSQL Serverをセットアップする。こちらの[クイックスタート](https://docs.microsoft.com/ja-jp/azure/azure-sql/virtual-machines/windows/sql-vm-create-portal-quickstart) に従ってオンプレミス用仮想ネットワーク(ONPREVNET)の中にデプロイする。ここでリモートからのログインができるようにSQL Serverを設定しておくことが必要。

## DNSの設定
今回のテストはAzureの仮想ネットワークのみで行ったので、Private DNS zoneを使用して簡単にレコードを作成して参照可能にしたが、実際のオンプレミスなどの環境との接続の場合には、お互いのリソースの名前解決ができるようにするためにDNSをセットアップする必要がある。
今回のテスト用のPrivate DNS zoneに関してはこちらの [クイック スタート:Azure portal を使用して Azure プライベート DNS ゾーンを作成する[(https://docs.microsoft.com/ja-jp/azure/dns/private-dns-getstarted-portal) を参考にセットアップし、ターゲットリソースの名前解決ができるようにレコードを追加する。ポイントは以下の通り。

- ゾーンを作成し、仮想ネットワーク (ここではオンプレ用仮想ネットワーク(ONPREVNET)とトラフィック転送用仮想ネットワーク(FWVNET)にリンク
- ターゲットリソース (ここではSQL Server on VM) のAレコードを登録
- FWVNET上に構築した仮想マシンからdigコマンド等で名前解決ができればOK

ここでは例として、FQDNとして `sqlvm01.synapsetest.takagi` で名前が引けるように設定した

### NATゲートウェイVM上で転送ルールを作成

この [ドキュメント](https://docs.microsoft.com/ja-jp/azure/data-factory/tutorial-managed-virtual-network-on-premise-sql-server#creating-forwarding-rule-to-endpoint) の通りとなるが、転送設定は以下の通り。

1. OSにログインして、 [ip_fwd.sh](https://github.com/sajitsasi/az-ip-fwd/blob/main/ip_fwd.sh) スクリプトをダウンロードする。
2. 以下のようにスクリプトを実行する。これによりiptablesのNATルールが設定される

```bash
sudo ./ip_fwd.sh -i eth0 -f 1433 -a sqlvm01.synapsetest.takagi -b 1433
```

登録されたルールを確認するためには以下のiptablesコマンドを実行する。

```bash
sudo iptables -t nat -v -L PREROUTING -n --line-number
Chain PREROUTING (policy ACCEPT 75 packets, 3900 bytes)
num    pkts bytes target      prot opt in      out      source                destination
1         2   120 DNAT        tcp  --  eth0    *        0.0.0.0/0             0.0.0.0/0                tcp dpt:1433 to:10.100.110.5:1433
```

登録されたルールを削除したい場合は以下のiptablesコマンドを実行する。上記確認コマンド結果から、削除したいルールの`num`の値をしていする。

```bash
sudo iptables -t nat -D POSTROUTING 1
```

## Synapseワークスペースでプライベートリンクサービスに対するマネージドプライベートエンドポイントを作成
Synapse studioにログインして、`管理` メニュー -> `Managed private endpoints` -> `+New` と進む。
リンクするサービスとして `Private Link Service` を選択して `Continue`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144789/161ab9a9-6045-7094-d7a2-6c76e0711208.png)

次の画面に遷移したら、自分のAzure Subscriptionを選択し、作成済みのプライベートリンクサービスを選択する。
最後にターゲットのSQL Server仮想マシンのFQDNを追加して `Create`
![Capture.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144789/d098db7e-c8da-463d-71d3-f921acb0afb8.png)

しばらくするとマネージドプライベートエンドポイントのステータスが承認待ちになるので、作成済みのプライベートリンクサービスの管理コンソールに移動し、承認待ちのリクエストを選択して承認する。その後しばらくすると接続が確立される。

これで接続設定は完了。

# 接続テスト
Synapseワークスペース内にSpark Poolを作成して、Spark Notebookを起動して以下のようにFQDNを使用してSQL Serverに接続する。事前にSQL Serverにデータベースおよびテーブルを作成して、1レコード追加しておいたが、このように結果がSpark DataFrameに返ってくれば接続成功となる。
![Capture.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144789/32951969-108f-ab76-788b-05363f2ac6fd.png)

今回はデータソースとしてSQL Serverを使用して試した。少し複雑な構成になるが、この方法であればSparkがサポートするデータソース(例えばOracle, MySQL, PostgreSQLのようなRDBMSやApache Kafkaなど・・・)にもアクセスが可能となる。

# 参考資料

- [Azure Synapse Analytics のマネージド仮想ネットワーク](https://docs.microsoft.com/ja-jp/azure/synapse-analytics/security/synapse-workspace-managed-vnet)
- [Synapse マネージド プライベート エンドポイント](https://docs.microsoft.com/ja-jp/azure/synapse-analytics/security/synapse-workspace-managed-private-endpoints)
- [プライベート リンクを使用して Azure Synapse ワークスペースに接続する](https://docs.microsoft.com/ja-jp/azure/synapse-analytics/security/how-to-connect-to-workspace-with-private-links)
- [Azure Private Link サービスとは](https://docs.microsoft.com/ja-jp/azure/private-link/private-link-service-overview)
- [チュートリアル: プライベート エンドポイントを使用して Data Factory マネージド VNet からオンプレミスの SQL Server にアクセスする方法](https://docs.microsoft.com/ja-jp/azure/data-factory/tutorial-managed-virtual-network-on-premise-sql-server)
- [クイック スタート:Azure portal で Windows 仮想マシン上に SQL Server 2017 を作成する](https://docs.microsoft.com/ja-jp/azure/azure-sql/virtual-machines/windows/sql-vm-create-portal-quickstart)
- [クイックスタート: Azure CLI を使用した内部ロード バランサーの作成](https://docs.microsoft.com/ja-jp/azure/load-balancer/quickstart-load-balancer-standard-internal-cli?tabs=option-1-create-load-balancer-standard)
- [クイック スタート:Azure portal を使用して Azure プライベート DNS ゾーンを作成する](https://docs.microsoft.com/ja-jp/azure/dns/private-dns-getstarted-portal)