本記事で記載している月額料金は2025年9月26日時点のものです。
私の記事を参照して閉域環境内でAzureのPaaSリソースに対してプライベートIPアドレスを返す、という記事が書かれていました。
参照してもらった私の記事はAzure FirewallのDNSプロキシ機能を試してみた(前編 ~Azure Private EndpointとAzure DNS Private Zone~)という記事です。
大変ありがたいですね。
Azure Firewallが既にある環境であれば良いのですが、わざわざ名前解決のためだけにAzure Firewallを構築するのは勿体ないですね。
Azure FirewallにもSKU(レベル)がありますが、最も安価なBasicを選択して東日本リージョンに構築したとしても月額41,670円かかります。
Azure Firewallは暗黙的に冗長化されていますのでDNSサーバとして利用するにあたり文句は無いのです。
ただ価格がネックですね。
ではAzure Firewallのように冗長化したDNSサーバをLinuxで構築した場合どうなるか?と考えて見積もってみました。
東日本リージョン、SKUは現時点で最新のDv5の最小サイズD2v5(2コア、8GiB)、OSライセンス料金のかからないLinux(Ubuntu)を選択、OSディスクはStandard SSDでAzure VMのLinux OSのデフォルトサイズ32GiB、DNSレコードの領域としてOSディスクとは別に追加ディスクをStandard SSDで512GiBをそれぞれ追加する構成にすると、合計で月額37,984円です。(画像は思いっきり小さくしましたが合計金額見切れてます。)
月額料金が少し安くなりましたね。
しかしVMで構築するとなるとOS以上のレイヤーでのメンテナンス、今回だとUbuntuなのでapt update
やapt upgrade
、do-release-upgrade
など管理面での煩雑さが発生してしまいます。
他にもDNSレコードの量にもよりますが、追加ディスクの拡張や追加、ディスク拡張や追加によるLVMの変更なども考えられます。
面倒ですね。
こうなるとやはりIaaSではなくPaaSを使いたいなとなってきますので、今回も前振りが長かったですが本題のAzure DNS Private Zone、Azure DNS Private Resolverの登場です。
Azure DNS Private Resolverだと料金が月額26,012円なので、最も安価です。
Azure DNS単体だとめちゃくちゃ安価ですが、お金はかかるので一応掲載しておくと月額130円です。
Azure DNS Private ZoneとAzure DNS Private Resolverの合算で月額26,142円ですね。
構成図
はい。
構成図です。
毎回四の五の言わずにすぐに構成図載せて説明したいのですが、どうしても背景というか流れの分かる前段部分を記載しないと気が済まないんですよね。
生来おしゃべりなんで仕方ないですね。
本記事のタイトルでは閉域環境内と記載していますが、検証なのでインターネットVPN環境で構築しています。
インターネットVPN環境で構築しているので構成図は以下のようになります。
名前解決の通信が以下の図のようになります。
赤い線が名前解決の通信になります。
自宅PC01が自宅内にあるDomain Controller兼DNSサーバを向いており、このDNSサーバ内で条件付きフォワーダーを設定してAzure DNS Private Resolver(Azure DNS Private Zoneも含めて記載)に特定のゾーンの名前解決の通信を転送します。
名前解決後の実際のデータ通信が以下の図のようになります。
赤い線が実通信、データ通信になります。
(名前解決と同じ赤線だとわかりづらいですかね・・・
構成図書くたびに迷っているのでアドバイスあればお手数ですがコメントください。)
名前解決も実際のデータもどちらの通信もインターネットVPNを経由するので、閉域環境での通信と同じで通信パケットはインターネットには出ません。
Terraform
はい。
では構成図の紹介と説明も済みましたのでコードのご紹介です。
providers.tf
#----------------------------------------
# Define Provider
#----------------------------------------
terraform {
required_providers {
azapi = {
source = "azure/azapi"
version = "~>1.5"
}
azurerm = {
source = "hashicorp/azurerm"
version = "~>3.0"
}
random = {
source = "hashicorp/random"
version = "~>3.0"
}
}
}
provider "azapi" {
}
provider "azurerm" {
features {}
subscription_id = "${var.ARM_SUBSCRIPTION_ID}"
tenant_id = "${var.ARM_TENANT_ID}"
client_id = "${var.ARM_CLIENT_ID}"
client_secret = "${var.ARM_CLIENT_SECRET}"
}
はい。
providers.tf
です。
何の変哲もないTerraformでAzure構築する際のproviders.tf
ですね。
varriables.tf(変数設定)
# --------------------------------------
# Define Varriables
# --------------------------------------
# Import Subscription ID variable from OS's Enviroment variable
variable "ARM_SUBSCRIPTION_ID" {
type = string
# default = "${env.ARM_SUBSCRIPTION_ID}"
}
# Import Tenant ID variable from OS's Enviroment variable
variable "ARM_TENANT_ID" {
type = string
# default = "${env.ARM_TENANT_ID}"
}
# Import Client ID variable from OS's Enviroment variable
variable "ARM_CLIENT_ID" {
type = string
# default = "${env.ARM_CLIENT_ID}"
}
# Import Client Secret variable from OS's Enviroment variable
variable "ARM_CLIENT_SECRET" {
type = string
# default = "${env.ARM_CLIENT_SECRET}"
}
# Generate random text for a unique storage account name
resource "random_id" "random_id" {
keepers = {
# Generate a new ID only when a new resource group is defined
resource_group = azurerm_resource_group.rg.name
}
byte_length = 8
}
# system name
variable "system_name" {
type = string
default = "azuredns-pr01"
description = "pr=privateresolver"
}
# region
variable "region" {
type = string
default = "southeastasia"
}
# resource group (rg) name prefix
variable "rg_name_pre" {
type = string
default = "rg-"
}
# virtual network (vnet) name prefix
variable "vnet_name_pre" {
type = string
default = "vnet-"
}
# vnet ip address prefix01
variable "vnet_addr_pre01" {
type = string
default = "192.168."
}
# vnet ip address suffix
variable "vnet_addr_suf01" {
type = string
default = "200.0/23"
}
# subnet (sn) name prefix
variable "sn_name_pre" {
type = string
default = "sn-"
}
# sn for client name suffix for Private Resolver (pr)
variable "sn_name_suf02" {
type = string
default = "pr01"
}
# sn for client name suffix for Private Endpoint (pe)
variable "sn_name_suf03" {
type = string
default = "pe01"
}
# sn for clinet ip address suffix for Virtual Network Gateway
variable "sn_addr_suf01" {
type = string
default = "200.0/25"
}
# sn for clinet ip address suffix for Private Resolver
variable "sn_addr_suf02" {
type = string
default = "200.128/25"
}
# sn for clinet ip address suffix for Private Endpoint
variable "sn_addr_suf03" {
type = string
default = "201.0/24"
}
# Local Gateway name prefix
variable "Lg_name_pre" {
type = string
default = "lg-"
}
# Local Gateway name suffix
variable "Lg_name_suf" {
type = string
default = "RTX1300"
}
# Local Gateway DDNS FQDN
variable "Lg_DDNS" {
type = string
default = "RTX1300のDDNSで指定しているFQDN"
}
# Local Gateway Local Subnet01 (General Subnet)
variable "Lg_local_sn01" {
type = string
default = "192.168.11.0/24"
}
# Local Gateway Local Subnet02 (Server Subnet)
variable "Lg_local_sn02" {
type = string
default = "172.16.0.0/24"
}
# VPN connection name prefix
variable "connection_name_pre" {
type = string
default = "con-"
}
# Private Endpoint (pe) name prefix
variable "pe_name_pre" {
type = string
default = "pe-"
}
# Azure DNS Private Zone Name
variable "dns_zone_name" {
type = string
default = "file.core.windows.net"
description = "Name of the DNS zone. This value is dns's suffix"
}
# Azure DNS Private Resolver name prefix
variable "azuredns_pr_name_pre01" {
type = string
default = "pr-"
}
# Azure DNS Private Resolver name suffix
variable "azuredns_pr_name_suf01" {
type = string
default = "azuredns01"
}
# Azure DNS Private Resolver inbound endpoint name prefix
variable "azuredns_pr_in_name_pre01" {
type = string
default = "in-"
}
varriables.tf
の中で特筆すべき点はvariable "dns_zone_name"
のブロックです。
Azure DNS Private Zoneは、Azure DNS Private Zoneのリソース名がDNSで定義したいゾーン名(apexレコード)になります。
ですのでこのvariable "dns_zone_name"
ではオンプレミスから名前解決させたいゾーン名を指定しなければなりません。
これはAzure DNS Private Zoneの仕様です。
resource_group.tf
# --------------------------------------
# Create Resource Group
# --------------------------------------
resource "azurerm_resource_group" "rg" {
location = var.region
name = "${var.rg_name_pre}${var.system_name}"
}
こちらも何の変哲もないリソースグループ作成用の.tfファイルです。
vnet_subnet.tf
#----------------------------------------
# Create vnet and Subnet
#----------------------------------------
resource "azurerm_virtual_network" "vnet" {
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
name = "${var.vnet_name_pre}${var.system_name}"
address_space = [ "${var.vnet_addr_pre01}${var.vnet_addr_suf01}" ]
}
# sn-pr01 Create (Prvate Resolver Subnet)
resource "azurerm_subnet" "sn-pr01" {
resource_group_name = azurerm_resource_group.rg.name
name = "${var.sn_name_pre}${var.sn_name_suf02}"
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = [ "${var.vnet_addr_pre01}${var.sn_addr_suf02}" ]
delegation {
name = "azuredns"
service_delegation {
name = "Microsoft.Network/dnsResolvers"
actions = [
"Microsoft.Network/virtualNetworks/subnets/join/action"
]
}
}
}
# sn-pe01 Create (Private Endpoint Subnet)
resource "azurerm_subnet" "sn-pe01" {
resource_group_name = azurerm_resource_group.rg.name
name = "${var.sn_name_pre}${var.sn_name_suf03}"
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = [ "${var.vnet_addr_pre01}${var.sn_addr_suf03}" ]
}
ここで特筆すべきは13行目にあるresource "azurerm_subnet" "sn-pr01"
のブロックです。
Azure DNS Private Resolverは仕様としてPrivate ResolverのプライベートIPアドレスを保持するサブネットに対して、サブネット内にNICを構築する権限(/join/action)を委任(delegation)を行わなければなりません。
ですのでこの委任を13行目にあるresource "azurerm_subnet" "sn-pr01"
のブロック内のservice_delegation
という部分で行っています。
この委任を行うことにより委任されたサブネットに対するAzure DNS Private Resolverによる仮想NICの作成が許可され、仮想NICが作成されるとDHCP(今回はDHCPで若番からプライベートIPアドレスを払い出しましたが固定IPアドレス設定ももちろん可能です)でプライベートIPアドレスが払い出されますので、結果Azure DNS Private ResolverがプライベートIPアドレスを持つことになります。
深いですねー
storage.tf
# --------------------------------------
# Create Storage Account
# --------------------------------------
# Create storage account for azure files
resource "azurerm_storage_account" "storage_account" {
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
name = "st${random_id.random_id.hex}"
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_storage_account_network_rules" "for_storage_account_multiple_subnet_id" {
storage_account_id = azurerm_storage_account.storage_account.id
default_action = "Deny"
ip_rules = ["構築元のグローバルIPアドレス"]
}
resource "azurerm_storage_share" "azure_files" {
name = "azurefileshare01"
storage_account_name = azurerm_storage_account.storage_account.name
quota = 5120 #GiB
}
いつもVMのdiagnosticsの出力先としての利用用途しかないのですが、今回はAzure FilesにアクセスさせたいのでAzure Filesの構築まで行っています。
今回の構成ではVMもないので、名称もdiagというプレフィックスは追記していないです。
private_endpoint.tf
#----------------------------------------
# Create Private Endpoint
#----------------------------------------
# Create private endpoint for Storage Account
resource "azurerm_private_endpoint" "pe01" {
# name = azurerm_storage_account.storage_account.name
name = "${var.pe_name_pre}st${random_id.random_id.hex}"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
subnet_id = azurerm_subnet.sn-pe01.id
private_service_connection {
name = "private-serviceconnection"
private_connection_resource_id = azurerm_storage_account.storage_account.id
subresource_names = ["file"]
is_manual_connection = false
}
private_dns_zone_group {
name = "dns-zone-group"
private_dns_zone_ids = [azurerm_private_dns_zone.privatelink_database_windows_net.id]
}
}
プライベートエンドポイントですね。
ここで特筆すべきはprivate_service_connection
のブロック内にあるsubresource_names = ["file"]
です。
これで構築したAzure Storage Account内に存在するAzure Filesにプライベートエンドポイントを割り当てることが可能です。
azure_dns_pz.tf
#----------------------------------------
# Create Azure DNS Private Zone (AzureDNSpz)
#----------------------------------------
# Create private DNS zone
resource "azurerm_private_dns_zone" "privatelink_database_windows_net" {
name = "${var.dns_zone_name}"
resource_group_name = azurerm_resource_group.rg.name
}
# Create private DNS record
resource "azurerm_private_dns_a_record" "azurefiles_dns_record" {
name = "st${random_id.random_id.hex}"
zone_name = azurerm_private_dns_zone.privatelink_database_windows_net.name
resource_group_name = azurerm_resource_group.rg.name
ttl = 300
records = ["192.168.201.4"]
}
# Create virtual network link
resource "azurerm_private_dns_zone_virtual_network_link" "azuredns_pz_vnet_link" {
name = "vnet-link"
resource_group_name = azurerm_resource_group.rg.name
private_dns_zone_name = azurerm_private_dns_zone.privatelink_database_windows_net.name
virtual_network_id = azurerm_virtual_network.vnet.id
}
はい。
Azure DNS Private Zoneですね。
特筆すべき一点目はvarriables.tf
で記載済みです。
このコードだと7行目のname = "{var.dns_zone_name}"
ですね。
もう一か所特筆すべき点があり、レコード作成部分、resource "azurerm_private_dns_a_record" "azurefiles_dns_record"
のブロック内にあるrecords = ["192.168.201.4"]
です。
プライベートエンドポイントは仕様上DHCPのみです。
固定IPアドレスを予め指定することはできません。
ですのでプライベートエンドポイントのサブネットで払い出される最も若い番号を手動で指定してレコードを作成しています。
azure_dns_pr.tf
#----------------------------------------
# Create Azure DNS Private Resolver (AzureDNSpr)
#----------------------------------------
resource "azurerm_private_dns_resolver" "pr-azuredns01" {
name = "${var.azuredns_pr_name_pre01}${var.azuredns_pr_name_suf01}"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
virtual_network_id = azurerm_virtual_network.vnet.id
}
resource "azurerm_private_dns_resolver_inbound_endpoint" "azuredns-pr-in01" {
name = "${var.azuredns_pr_in_name_pre01}${var.azuredns_pr_name_pre01}${var.azuredns_pr_name_suf01}"
private_dns_resolver_id = azurerm_private_dns_resolver.pr-azuredns01.id
location = azurerm_private_dns_resolver.pr-azuredns01.location
ip_configurations {
subnet_id = azurerm_subnet.sn-pr01.id
private_ip_allocation_method = "Dynamic"
}
}
はい。
やっとお出まし。
Azure DNS Private Resolverです。
でもなんてことはありません。
特筆すべき点はこのAzure DNS Private Resolver自身ではなく、サブネットやDNS Zoneで説明済みです。
このリソースがきちんと動作するようにこれまでのコードで準備済みです。
vnetgateway.tf
# ------------------------------------------
# Create Virtual Network Gateway Subnet
# ------------------------------------------
resource "azurerm_subnet" "GatewaySubnet" {
resource_group_name = azurerm_resource_group.rg.name
name = "GatewaySubnet"
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = [ "${var.vnet_addr_pre01}${var.sn_addr_suf01}" ]
}
# ------------------------------------------
# Create Virtual Network Gateway
# ------------------------------------------
resource "azurerm_public_ip" "pip-vgw" {
name = "pip-vgw01"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
allocation_method = "Dynamic"
}
resource "azurerm_virtual_network_gateway" "vgw01" {
name = "vgw01"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
type = "Vpn"
vpn_type = "RouteBased"
active_active = false
enable_bgp = false
sku = "Basic"
ip_configuration {
name = "vnetGatewayConfig"
public_ip_address_id = azurerm_public_ip.pip-vgw.id
private_ip_address_allocation = "Dynamic"
subnet_id = azurerm_subnet.GatewaySubnet.id
}
}
VPNを張るのに必要なAzure仮想ネットワークゲートウェイですね。
ここは本題とは無関係なので流しちゃいましょう。
以降に続くLocal GatewayやVPN Connectionも特筆すべき点はありません。
localgateway.tf
# --------------------------------------
# Create Local Network Gateway
# --------------------------------------
resource "azurerm_local_network_gateway" "lgw" {
name = "${var.Lg_name_pre}${var.Lg_name_suf}"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
gateway_fqdn = "${var.Lg_DDNS}"
address_space = ["${var.Lg_local_sn01}","${var.Lg_local_sn02}"]
}
vpn_connection.tf
# --------------------------------------
# Create VPN Connection
# --------------------------------------
resource "azurerm_virtual_network_gateway_connection" "vpn-connection" {
name = "${var.connection_name_pre}${var.vnet_name_pre}${var.system_name}-${var.Lg_name_pre}${var.Lg_name_suf}"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
type = "IPsec"
virtual_network_gateway_id = azurerm_virtual_network_gateway.vgw01.id
local_network_gateway_id = azurerm_local_network_gateway.lgw.id
shared_key = "いつもの超強力なPSK"
}
動作確認
はい。
やっと動作確認ですね。
VPN張ったりAzureの各種リソースが設計通り、コード通りに動作しているか確認しますが、今回はブラックボックステスト風にいきなりオンプレミスのDNSサーバ(実際はDomain Controller)にゾーン転送設定を行い、通信できるか確認します。
オンプレミスDNSサーバにゾーン転送設定追加
厳密には動作確認ではなく事前設定扱いですが、Terraformのコードに含まれない部分であるということと、Azure PortalでAzure DNS Private ResolverのIPアドレスを未確認ですので、便宜上動作確認としています。
はい。
何の変哲もない条件付きフォワーダーの設定です。
名前解決の通信
キタ!キタ!
デスクトップの背景が青でDoS窓の背景が黒なので黄色で集中線付けました。
めっちゃ変な感じですね。
ですがまぁ設計通りの動作です。
実通信の確認
Azure Filesなのでネットワークドライブとして設定することが可能です。
プライベートIPアドレスで指定できるかやってみましょう。
キター!
はい。
Azure DNS Private Resolverを使えばDNSサーバが暗黙的に冗長化されており、かつ閉域網内でも無事名前解決できる!
というこがわかりました!
本日はここまで!