Hybrid DNS Solution with Azure
Tags: azure, cloud
Published: 2022-11-04 01:07:56

หลังจากที่ [Azure ประกาศ GA Azure DNS Private Resolver](https://azure.microsoft.com/en-us/updates/general-availability-azure-dns-private-resolver-hybrid-name-resolution-and-conditional-forwarding/) ก็ได้ฤกษ์ ลองเล่นซะที หลังจากเก็บไว้ในกองดองอยู่นาน มาเข้าเรื่องกันเลยครับ

### Azure DNS Private Resolver คืออะไร

สั้นๆ มันคือ resource ที่ทำหน้าที่เป็น DNS server นั้นเอง ในโลก internet DNS เป็นส่วนที่สำคัญมากๆ มันทำให้เราพิมพ์ google.co.th แล้วสามารถ connect ไปที่ google ได้เลยแทนที่จะต้องพิมพ์ `142.251.12.94` ซึ่งเป็น IP address ของ goole ที่ผม resolve ได้ในตอนที่เขียน blog นี้

โดย resource ตัวนี้จะทำงานใน private network ของเราบน Azure  คอย resolve private domain name ของเราให้เป็น private IP address

### แล้วก่อนหน้านี้ resolve domain name กันอย่างไรล่ะ

ใน Azure จริงๆ แล้ว มัมมี built-in DNS อยู่แล้ว  เรียกว่า Azure-provided DNS มันจะเป็น configuration เนียนๆ อยู่ใน virtual network ของเรา ตรงนี้


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mkm2uewlxzwmjki45inj.png)

โดย มันจะปรากฏในรูปแบบของ IP `168.63.129.16` ใน ใน VM ของเรา ที่ deploy ใน virtual network ที่ configure DNS server ไว้ตามข้างต้น โดย `168.63.129.16` สามารถ connect จาก Azure resources เท่านั้น

```
$ systemd-resolve --status | grep 'DNS Servers'
DNS Servers: 168.63.129.16
```

> รายละเอียดเพิ่มเติม ของ `168.63.129.16` สามารถอ่านเพิ่มได้จาก [What is IP address 168.63.129.16?](https://learn.microsoft.com/en-us/azure/virtual-network/what-is-ip-address-168-63-129-16)

ทีนี้หากเราต้องการ เพิ่ม private domain name ก็แค่สร้าง private dns zone แล้ว link มันกับ virtual network ของเรา เท่านี้ Azure DNS ก็จะสามารถ resolve private domain name ของเราได้แล้ว

พอเราใช้ไปซักพักความต้องการเริ่มมากขึ้น เริ่มมี requirement ให้เราไปต่อกับ on-premise network ในส่วนของ connetivity นั้นก็ใช้ VPN หรือ Express Route ได้ตามปกติ แต่ในเรื่อง domain name resolution ล่ะ จะทำอย่างไรดี เพราะ on-prem ไม่สามารถ connect มาที่ `168.63.129.16` ที่อยู่บน Azure ของเราได้

ก่อนการมาของ **Azure DNS Private Resolver** เราใช้วิธีการสร้าง   VM-Based DNS server ขึ้นมา พูดง่ายๆ สร้าง VM แล้วเอา DNS software เช่น Bind9 มา install เอง ถ้าอยากได้ SLA ที่เพิ่มขึ้นก็เอา VM มาใส่ VM Scale Set แล้ว ขั้นหน้าด้วย Load Balancer

นั่นหมายความว่าเราต้อง manage ทุกอย่างเอง ไม่ว่าจะเป็นการ hardening, OS update, Software upgrade, Backup และอื่นๆ อีกมากมาย  

แล้วให้ on-premise DNS server ทำ Conditional Forward มาที่ VM-Based DNS server ของเรา 

ในทางกลับกัน เราต้องต้อง configure Conditional Forward ลงไปที่ on-prem ด้วย เพื่อให้ server บน Azure สามารถ resolve domain name ของ on-premise ได้

> **Conditional Forward** คือการ configure ใน DNS server ทำการ forward request ต่อยัง อีก DNS server หนึ่ง เช่น เรามี zone `*.example.com` อยู่ที่ server-A เราสามารถ configure ให้ DNS ของเราไปถามต่อที่ server-A ได้ หากมี client ถามว่า `demo.example.com` เป็น IP อะไร โดยที่ client ไม่ต้องรู้จัก server-A เลย

### Azure DNS Private Resolver ทำงานอย่างไร

การที่จะทำ Hybrid DNS Solution บน Azure จะมี 2 resources ที่ทำงานร่วมกัน
1. DNS Private Resolver
2. DNS Forwarding Ruleset

#### DNS Private Resolver

ทำหน้าที่เป็น endpoint ในการทำ name resolution โดยประกอบด้วย 2 endpoints ที่ deploy อยู่คนละ dedicated subnet คือ

- **Inbound endpoints**: เป็น ingress subnet โดย 1 endpoint จะมี 1 IP address ของ Load Balancer สำหรับ รับ domain name resolution request จาก on-premise หรือ resource ใน virtual network อื่น

![Inbound endpoints](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ar0cjtwq2mgvg5zndjng.png)

- **Outbound endpoints**: เป็น egress subnet ที่ใช้ออกไป query DNS servers อื่นๆ ใน subnet นี้จะไม่มี IP address ปรากฏ นั่นหมายความว่า outgoing IP address มีโอกาสเป็นไปได้ทั้ง subnet (ผมเดาว่าน่าจะใช้ technic เดียวกับ vnet integration ของ app service)

![Outbound endpoints](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8c2f8w5essyc7noyr7u5.png)

#### DNS Forwarding Ruleset

ทำหน้าที่เป็น conditional forward configuration ใน vnet ของเรา โดย Ruleset ก็ตามชื่อเลยครับ เป็นกลุ่มของ conditional forward rule โดย rule จะมีหน้าตาดังนี้

![DNS Forwarding Rule](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ick5jz3xflcpfwmd47m.png)

และการจะใช้งาน DNS Forwarding Ruleset ได้ ก็ต้องเอามันไป link กับ virtual network ตาม concept เดียวกับ Private DNS Zone

![DNS Forwarding Ruleset Link](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hlquy5zo2034p8cghmx3.png)

ยังจำได้ไหม ตัว resolver มี outbound endpoint ซึ่ง deploy ใน dedicated subnet ใน virtual network ดังนั้น ตัว outbound endpoint ก็จะทำตาม DNS Forwarding Ruleset ที่เรา link ไว้ด้วย

เอ๊ะ! ถ้าเรามีทั้ง Private DNS Zone Link และ DNS Forwarding Ruleset Link ด้วยมันจะทำงานอย่างไร ?

มันจะ resolve ตาม order นี้ครับ 
1. Private DNS Zone
2. DNS Forwarding Ruleset
3. Azure-provided DNS Servers

**สรุป** ถ้า resolver ได้รับ name resolution request มาจาก client ผ่านทาง inbound endpoint IP address มันจะ forward ออกไปยัง outbound endpoint IP address ซึ่งจะทำการ resolve domain name ตามลำดับข้างต้น แล้ว response สิ่งที่ได้ไปให้กับ client

### ถ้าพอนึกภาพออกแล้วเรามาลองลงมือทำกันดีกว่า

Architecure ที่เราจะลองทำหน้าตาประมาณนี้ครับ

![demo-architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k7jvarmgfacjh427qzpr.png)

เราจะมี 3 virtual network ดังนี้
1. **On-Prem**: เป็นตัวแทนของ on-premise
2. **Hub**: เป็นตัวแทนของ Hub network บน Azure ที่เป็นศูนย์รวมของ connectivity ต่างๆ 
3. **Spoke**: เป็น virtual network ของ application ที่จะมา deploy ใน Azure

โดย 
- ที่ **On-Prem** จะมี DNS server ที่มี DNS records ของ domain `*.onprem.contoso.com` และทำ conditional forward domain `*.private.contoso.com` ไปที่ Resolver ที่ Hub
- ที่ **Hub** จะมี DNS Private Resolver พระเอกของเรา, Private DNS Zone `*.private.contoso.com` และ DNS Forwarding Ruleset พระเอกของเราอีกคน เอาไว้ทำ conditional forward `*.onprem.contoso.com` ลงไปที่ On-Prem โดยใน virtual network นี้จะมี VM 1 server เอาไว้ทดสอบ name resolution
- ที่ **Spoke** มี VM 1 server เอาไว้ทดสอบ name resolution เช่นกัน

เรามาเริ่มสร้างกันเลย

##### Create Resource Group

```
az group create \
--name rg-demodns-az-asse-sbx-001 \
--location southeastasia
```

##### Create Hub

- Virtual Network

```
az network vnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vnet-demodnshub-az-asse-sbx-001 \
--address-prefixes 10.1.0.0/16

az network vnet subnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name snet-default-az-asse-sbx-001 \
--vnet-name vnet-demodnshub-az-asse-sbx-001 \
--address-prefixes 10.1.1.0/24

az network vnet subnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name snet-inbound-az-asse-sbx-001 \
--vnet-name vnet-demodnshub-az-asse-sbx-001 \
--address-prefixes 10.1.2.0/28

az network vnet subnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name snet-outbound-az-asse-sbx-001 \
--vnet-name vnet-demodnshub-az-asse-sbx-001 \
--address-prefixes 10.1.2.16/28
```

- Virtual Machine

```
SUBNET_ID=$(
az network vnet subnet show \
--resource-group rg-demodns-az-asse-sbx-001 \
--vnet-name vnet-demodnshub-az-asse-sbx-001 \
--name snet-default-az-asse-sbx-001 \
--query "id" \
--output tsv
)

az vm create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vm-hub-az-asse-sbx-001 \
--computer-name vm-hub-az-asse-sbx-001 \
--image UbuntuLTS \
--size Standard_D2s_v4 \
--accelerated-networking true \
--subnet ${SUBNET_ID} \
--nsg-rule SSH \
--admin-username azureuser \
--ssh-key-values /home/sawitmee/.ssh/id_rsa.pub
```

- Private DNS Zone

```
az network private-dns zone create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name private.contoso.com

az network private-dns record-set a add-record \
--resource-group rg-demodns-az-asse-sbx-001 \
--zone-name private.contoso.com \
--record-set-name test \
--ipv4-address 10.10.10.10

az network private-dns link vnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name link-vnet-demodnshub-az-asse-sbx-001 \
--zone-name private.contoso.com \
--virtual-network vnet-demodnshub-az-asse-sbx-001 \
-e false
```

- Private Resolver

```
VNET_ID=$(
az network vnet show \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vnet-demodnshub-az-asse-sbx-001 \
--query "id" \
--output tsv
)

az dns-resolver create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name adpr-hub-az-asse-sbx-001 \
--id ${VNET_ID}

SUBNET_ID=$(
az network vnet subnet show \
--resource-group rg-demodns-az-asse-sbx-001 \
--vnet-name vnet-demodnshub-az-asse-sbx-001 \
--name snet-inbound-az-asse-sbx-001 \
--query "id" \
--output tsv
)

az dns-resolver inbound-endpoint create \
--resource-group rg-demodns-az-asse-sbx-001 \
--dns-resolver-name adpr-hub-az-asse-sbx-001 \
--name snet-inbound-az-asse-sbx-001 \
--ip-configurations "[{private-ip-address:'',private-ip-allocation-method:'Dynamic',id:'$SUBNET_ID'}]"

SUBNET_ID=$(
az network vnet subnet show \
--resource-group rg-demodns-az-asse-sbx-001 \
--vnet-name vnet-demodnshub-az-asse-sbx-001 \
--name snet-outbound-az-asse-sbx-001 \
--query "id" \
--output tsv
)

az dns-resolver outbound-endpoint create \
--resource-group rg-demodns-az-asse-sbx-001 \
--dns-resolver-name adpr-hub-az-asse-sbx-001 \
--name snet-outbound-az-asse-sbx-001 \
--id "${SUBNET_ID}"
```

##### Create Spoke ( + Peering )

- Virtual Network

```
az network vnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vnet-demodnsspoke-az-asse-sbx-001 \
--address-prefixes 10.2.0.0/16 \
--dns-servers 10.1.2.4

az network vnet subnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name snet-default-az-asse-sbx-001 \
--vnet-name vnet-demodnsspoke-az-asse-sbx-001 \
--address-prefixes 10.2.1.0/24

az network vnet subnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name snet-inbound-az-asse-sbx-001 \
--vnet-name vnet-demodnsspoke-az-asse-sbx-001 \
--address-prefixes 10.2.2.0/28

az network vnet subnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name snet-outbound-az-asse-sbx-001 \
--vnet-name vnet-demodnsspoke-az-asse-sbx-001 \
--address-prefixes 10.2.2.16/28
```

- Peering

```
az network vnet peering create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vpeer-vnet-demodnsspoke-az-asse-sbx-001-vnet-demodnshub-az-asse-sbx-001 \
--vnet-name vnet-demodnsspoke-az-asse-sbx-001 \
--remote-vnet vnet-demodnshub-az-asse-sbx-001 \
--allow-vnet-access

az network vnet peering create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vpeer-vnet-demodnshub-az-asse-sbx-001-vnet-demodnsspoke-az-asse-sbx-001 \
--vnet-name vnet-demodnshub-az-asse-sbx-001 \
--remote-vnet vnet-demodnsspoke-az-asse-sbx-001 \
--allow-vnet-access
```

- Virtual Machine

```
SUBNET_ID=$(
az network vnet subnet show \
--resource-group rg-demodns-az-asse-sbx-001 \
--vnet-name vnet-demodnsspoke-az-asse-sbx-001 \
--name snet-default-az-asse-sbx-001 \
--query "id" \
--output tsv
)

az vm create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vm-spoke-az-asse-sbx-001 \
--computer-name vm-spoke-az-asse-sbx-001 \
--image UbuntuLTS \
--size Standard_D2s_v4 \
--accelerated-networking true \
--subnet ${SUBNET_ID} \
--nsg-rule SSH \
--admin-username azureuser \
--ssh-key-values /home/sawitmee/.ssh/id_rsa.pub
```

- Test

```
nslookup test.private.contoso.com
```

##### Create On-Premise ( +Peering )

- Virtual Network

```
az network vnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vnet-demodnsonprem-az-asse-sbx-001 \
--address-prefixes 10.3.0.0/16

az network vnet subnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name snet-default-az-asse-sbx-001 \
--vnet-name vnet-demodnsonprem-az-asse-sbx-001 \
--address-prefixes 10.3.1.0/24
```

- Peering

```
az network vnet peering create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vpeer-vnet-demodnsonprem-az-asse-sbx-001-vnet-demodnshub-az-asse-sbx-001 \
--vnet-name vnet-demodnsonprem-az-asse-sbx-001 \
--remote-vnet vnet-demodnshub-az-asse-sbx-001 \
--allow-vnet-access

az network vnet peering create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vpeer-vnet-demodnshub-az-asse-sbx-001-vnet-demodnsonprem-az-asse-sbx-001 \
--vnet-name vnet-demodnshub-az-asse-sbx-001 \
--remote-vnet vnet-demodnsonprem-az-asse-sbx-001 \
--allow-vnet-access
```

- DNS Server ( Windows )

```
SUBNET_ID=$(
az network vnet subnet show \
--resource-group rg-demodns-az-asse-sbx-001 \
--vnet-name vnet-demodnsonprem-az-asse-sbx-001 \
--name snet-default-az-asse-sbx-001 \
--query "id" \
--output tsv
)

az vm create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vm-dns-az-asse-sbx-001 \
--computer-name vmdns101s01 \
--image Win2019Datacenter \
--size Standard_D4s_v3 \
--accelerated-networking true \
--subnet ${SUBNET_ID} \
--nsg-rule RDP \
--admin-username azureuser \
--admin-password "<YOUR-PASSWORD>"
```

- Setup DNS server

ทำตามนี้นะครับ [Install and Configure DNS Server on Windows Server 2019](https://computingforgeeks.com/install-and-configure-dns-server-in-windows-server/)

- Create Zone And Conditional Forward

![1 Open DNS Manager](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4pyvo7m31non5ypuc2tf.png)

![2 Create New Zone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xqatdad2rwyjjgf6kqjr.png)

![3 Create New Zone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w8kyr9an38rgl7excbv0.png)

![4 Create New Zone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fkkg97juhtmap62defyz.png)

![5 Create New Zone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p1k4wtjo5a6qyyagcc4e.png)

![6 Create New Zone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jbttu08dnl80hhvphs0f.png)

![7 Create New Zone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aahirzswey2caht8aeak.png)

![8 Create New Zone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g9vsbt44q2wgoq743t2x.png)

![9 Create New Zone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7wjlpyer8kd2zh1z7zyz.png)

![10 Set conditional forward to cloud](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xv4ctrope8c95bop5kq4.png)

![11 Set conditional forward to cloud](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pq9cnful64eccysj3tp4.png)

- Test

```
nslookup test.onprem.contoso.com 127.0.0.1
nslookup test.private.contoso.com 127.0.0.1
```

#### Create DNS Forwarding Ruleset

- Create Rulesets

```

OUTBOUND_ID=$(
az dns-resolver outbound-endpoint show \
--resource-group rg-demodns-az-asse-sbx-001 \
--dns-resolver-name adpr-hub-az-asse-sbx-001 \
--name snet-outbound-az-asse-sbx-001 \
--query id \
--output tsv
)

az dns-resolver forwarding-ruleset create \
--resource-group rg-demodns-az-asse-sbx-001 \
--dns-forwarding-ruleset-name dfrs-onpremcontosocom-az-asse-sbx-001 \
--outbound-endpoints "[{id:'${OUTBOUND_ID}'}]"

VM_ID=$(
az vm show \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vm-dns-az-asse-sbx-001 \
--query id \
--output tsv
)

VM_IPADDR=$(
az vm list-ip-addresses \
--ids ${VM_ID} \
--query '[].virtualMachine.network.privateIpAddresses' \
--output tsv
)

az dns-resolver forwarding-rule create \
--resource-group rg-demodns-az-asse-sbx-001 \
--ruleset-name dfrs-onpremcontosocom-az-asse-sbx-001 \
--forwarding-rule-name onprem-contoso-com \
--domain-name onprem.contoso.com. \
--target-dns-servers "[{ip-address:'${VM_IPADDR}',port:53}]"
```

- Link DNS forwarding ruleset to VNET

```
VNET_ID=$(
az network vnet show \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vnet-demodnshub-az-asse-sbx-001 \
--query "id" \
--output tsv
)

az dns-resolver vnet-link create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name link-vnet-demodnshub-az-asse-sbx-001 \
--ruleset-name dfrs-onpremcontosocom-az-asse-sbx-001 \
--id ${VNET_ID}
```

- Test from VM in Hub

```
nslookup test.private.contoso.com
nslookup test.onprem.contoso.com
```

- Test from VM in Spoke

```
nslookup test.private.contoso.com
nslookup test.onprem.contoso.com
```

> **เพิ่มเติม** 
ถ้าเรามี firewall ที่ Hub 
- แนะนำให้ enable DNS Proxy ที่ firewall แล้วชี้ DNS Server ของ firewall มาที่ Inbound Endpoint ของ DNS Private Zone Resolver
- ส่วนที่ Spoke Virtual Network ให้ set DNS Server เป็น Private IP address ของ Firewall

---
## References
- [Name resolution for resources in Azure virtual networks](https://learn.microsoft.com/en-us/azure/virtual-network/virtual-networks-name-resolution-for-vms-and-role-instances)
- [General availability: Azure DNS Private Resolver – hybrid name resolution and conditional forwarding](https://azure.microsoft.com/en-us/updates/general-availability-azure-dns-private-resolver-hybrid-name-resolution-and-conditional-forwarding/)
- [Design a hybrid Domain Name System solution with Azure](https://learn.microsoft.com/en-us/azure/architecture/hybrid/hybrid-dns-infra)