1. はじめに
1-1 ご挨拶
初めまして、井村と申します。
Terraformについてディレクトリ構造を意識して作成したくトライしました。
デプロイ環境はAzureになります。デプロイするサービスとしてはプライベートエンドポイントです。プライベートエンドポイントは他のAzureサービスと接続して利用します。そのためTerraformの変数を出し入れする練習にちょうどよいと思いました。
備忘録として記事にします。
1-2 対象読者
- Azureに興味がある
- Terrafomに興味がある
1-3 成果物
プライベートエンドポイントをデプロイすると自動的にNICが生成されます。今回はストレージアカウント用のプライベートエンドポイント、ストレージアカウント、仮想ネットワーク、プライベートDNSゾーンをデプロイします。

ディレクトリ構造としては以下の通りになります。envディレクトリ配下に各環境(devしか作成していませんが。。)ごとのルートモジュールフォルダを作成します。modulesディレクトリ配下にAzureサービスごとのフォルダを作成します。
.
├── env
│ └── dev
│ ├── main.tf
│ ├── provider.tf
│ ├── terraform.tfvars
│ └── variable.tf
└── modules
├── pep
│ ├── main.tf
│ └── variables.tf
├── rg
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
├── st
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
└── vnet
├── main.tf
├── outputs.tf
└── variables.tf
ソースコードはGitHubにあります。
※上記構成図には省略していますが、サブネットが複数作成されます。
https://github.com/hawk0722/Azure_Terraform_01
2. 語る
ここでは後に自分が読み返すように記述します。
変数の流れがわかりやすいと感じた記事はMakoto Okadaさまの「TerraformのModuleの仕組みを図解してみた」になります。ぜひ確認してほしいです。
2-1 ルートモジュール
「./env/dev/main.tf」が司令塔ですね。サッカーでいうMFの10番的存在です。同じフォルダの「./env/dev/variable.tf」と各モジュールの「./env/modules//outputs.tf」から変数を取得します。そして取得した変数を各モジュール「./env/modules//variables.tf」に必要な変数のみを渡します。
module "pep" { ~省略~ }の中についてはざっくり以下の認識です。
※pepは「Private endpoint」の略称。
・sourceは後述にある変数を渡す相手である「./env/modules/pep/variables.tf」があるフォルダを記述する。
・引き渡す変数はlocation,env,code,...,st_idである。
・var.~は同じフォルダの「./env/dev/variable.tf」から変数を取得している。
・module.~は別階層にある「./env/modules/*/outputs.tf」から変数を取得している。
プライベートエンドポイントは仮想ネットワーク内(サブネット)にデプロイされます。
また、ストレージアカウントと接続しますので、仮想ネットワークとサブネットのid、ストレージアカウントの名前、idを必要とし変数を渡しているのがわかりますね。
module "rg" {
source = "../../modules/rg"
location = var.location
env = var.env
code = var.code
}
module "vnet" {
source = "../../modules/vnet"
location = var.location
env = var.env
code = var.code
cidr = var.cidr
rg_name = module.rg.rg_name
}
module "st" {
source = "../../modules/st"
location = var.location
env = var.env
code = var.code
rg_name = module.rg.rg_name
}
module "pep" {
source = "../../modules/pep"
location = var.location
env = var.env
code = var.code
rg_name = module.rg.rg_name
vnet_id = module.vnet.vnet_id
snet_pep_id = module.vnet.snet_pep_id
st_name = module.st.st_name
st_id = module.st.st_id
}
2-2 プライベートエンドポイントのモジュール
modules配下のmain.tfは実際にデプロイするファイルです。司令塔から「./env/modules/pep/variables.tf」でパス(変数)を受け取り「./env/modules/pep/main.tf」でシュートし、ゴール(デプロイ)的なイメージです(笑)
「./env/dev/main.tf」の"module "pep" { ~省略~ }"と「./env/modules/pep/variables.tf」については「./env/modules/pep/main.tf」で必要な変数を受け渡すことが大事です。
# "pep" { ~省略~ }"抜粋
module "pep" {
source = "../../modules/pep"
location = var.location
env = var.env
code = var.code
rg_name = module.rg.rg_name
vnet_id = module.vnet.vnet_id
snet_pep_id = module.vnet.snet_pep_id
st_name = module.st.st_name
st_id = module.st.st_id
}
# ./env/modules/pep/variables.tf
variable "location" {}
variable "env" {}
variable "code" {}
variable "rg_name" {}
variable "vnet_id" {}
variable "snet_pep_id" {}
variable "st_id" {}
variable "st_name" {}
「./env/modules/pep/main.tf」を以下に記載します。上記で取得した変数は「var.~」として使われてます。
# Create Private DNS Zone
resource "azurerm_private_dns_zone" "pdns_blob" {
name = "privatelink.blob.core.windows.net"
resource_group_name = var.rg_name
}
# Create Private DNS Zone Network Link
resource "azurerm_private_dns_zone_virtual_network_link" "vnet_link" {
name = "vnl-${azurerm_private_dns_zone.pdns_blob.name}"
resource_group_name = var.rg_name
private_dns_zone_name = azurerm_private_dns_zone.pdns_blob.name
virtual_network_id = var.vnet_id
}
# Create Private Endpint
resource "azurerm_private_endpoint" "pep" {
name = "pep-${var.env}-${var.code}"
resource_group_name = var.rg_name
location = var.location
subnet_id = var.snet_pep_id
private_service_connection {
name = "psc-${var.env}-${var.code}"
private_connection_resource_id = var.st_id
is_manual_connection = false
subresource_names = ["blob"]
}
}
# Create DNS A Record
resource "azurerm_private_dns_a_record" "pdns_a" {
name = var.st_name
zone_name = azurerm_private_dns_zone.pdns_blob.name
resource_group_name = var.rg_name
ttl = 300
records = [azurerm_private_endpoint.pep.private_service_connection.0.private_ip_address]
}
2-3 各モジュールにある「outputs.tf」について
プライベートエンドポイントにはありませんでしたが、他のAzureリソースモジュールには「outputs.tf」があります。サッカーで例えると司令塔にパスをしています。真面目な話をすると「./env/modules//main.tf」が他の「./env/modules//main.tf」で生成する値を必要とするため、「./env/dev/main.tf」(司令塔)に変数を渡してます。
具体例でいうと、ほとんどのAzureリソース(本記事では仮想ネットワーク、ストレージアカウント、プライベートエンドポイント)はリソースグループ内に作成されます。したがって、ほとんどのAzureリソースはリソースグループの名前を必要としています。
そのため、リソースグループ「./env/modules/rg/outputs.tf」はリソースグループ名を司令塔に渡しているのです。
# 「./env/modules/rg/outputs.tf」
output "rg_name" {
value = azurerm_resource_group.rg.name #司令塔にリソースグループ名を渡す
}
# 「./env/modules/rg/main.tf」
resource "azurerm_resource_group" "rg" {
name = "rg-${var.env}-${var.code}" #リソースグループ名
location = var.location
}
3. 構築
それではデプロイまでの流れを以下に記載します。
# ローカルに作業用フォルダの作成と移動
mkdir work
cd work/
# Githubからソースをダウンロード
git clone https://github.com/hawk0722/Azure_Terraform_01.git
# ルートモジュールへ移動
cd Azure_Terraform_01/env/dev
# ワークスペースを初期化する
terraform init
# 実行計画の参照
terraform plan
# リソースのデプロイ
terraform apply
4. 実行結果と動作確認
IaCを用いてプライベートエンドポイントを作成する際に毎回これなんだっけと思う箇所があるのでスクショします。
4-1 リソースグループ
デプロイされるサービス一覧です。

4-2 プライベートDNSゾーン
プライベートDNSゾーンの概要

4-3 プライベートDNSゾーン-仮想ネットワークリンク
プライベートDNSゾーン内にある仮想ネットワークリンク
※「./env/modules/pep/main.tf」の"# Create Private DNS Zone Network Link"

4-4 ストレージアカウント-ネットワーク設定
ストレージアカウント内にあるネットワーク設定
※「./env/modules/st/main.tf」の"# Create Azure Storage Account Network Rules"

4-5 サブネット
仮想ネットワーク内にあるサブネット

4-6 動作確認
AzureBastionSubnetにBastion、snet-pub-dev-hawkにVMをたてて、ログイン。その後、ストレージアカウントに対してnslookupを実行。無事にプライベートIPアドレスが返却されました。


5. おわりに
本記事を最後まで読んで頂きましてありがとうございます。
今回は個人での作成でしたが、実際の仕事では複数人で作業します。「terraform.tfstate」をクラウドストレージ上に格納し、それぞれが参照し作業を行うのですが、考えることが多そうだなと感じました。
いつも思いますが、いろんな方の記事は本当に参考になります。これからもお世話になります!
6. 参考記事
- 安心安全!Azure Private Linkを使ったAzure PaaSへのプライベート接続
- Azure Terraform Vnet
- 【細かすぎて伝わらないかもしれない tips】Terraform の Ramdom プロバイダ が地味に便利です
- TerraformのModuleの仕組みを図解してみた
- 【Terraform de Azure】 Azureで事前に準備しておくべきこと
- セキュアなTerraformの使い方 ~ 機密情報をコードに含めず環境構築するにはどうしたらいいの?
- Using Private Endpoint in Azure Storage Account with Terraform
- Terraform Moduleの使い方をAzure仮想マシンで学ぶ