sou (08thse) のはてなブログ

今のところ Azure まわりの情報発信が主体です

[Azure] Terraform で Blob Storage の Private Endpoint (Private Link) を作成する

Terraform を利用して、Linux (Ubuntu) VM および同一サブネットに Blob Storage の Private Endpoint を作成してみました。

■providers.tf

terraform {
  required_version = ">= 1.4"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
    random = {
      source  = "hashicorp/random"
      version = "~> 3.0"
    }
  }
}

provider "azurerm" {
  features {}
}

■variables.tf

variable "resource_group_location" {
  default     = "japaneast"
  description = "Location of the resource group."
}

variable "resource_group_name_prefix" {
  default     = "<resource_group_name-prefix>"
  description = "Prefix of the resource group name that's combined with a random ID so name is unique in your Azure subscription."
}

■main.tf

resource "random_pet" "rg_name" {
  prefix = var.resource_group_name_prefix
}

resource "azurerm_resource_group" "rg" {
  location = var.resource_group_location
  name     = random_pet.rg_name.id
}

# Create a virtual network
resource "azurerm_virtual_network" "vnet" {
  name                = "${random_pet.rg_name.id}-vnet"
  address_space       = ["10.10.0.0/16"]
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
}

# Create a subnet
resource "azurerm_subnet" "subnet" {
  name                 = "${random_pet.rg_name.id}-subnet"
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.10.10.0/24"]
}

# Create a network security group
resource "azurerm_network_security_group" "nsg" {
  name                = "${random_pet.rg_name.id}-nsg"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
}

# Create a network security group rule
resource "azurerm_network_security_rule" "nsg_rule" {
  name                        = "SSH"
  priority                    = 1001
  direction                   = "Inbound"
  access                      = "Allow"
  protocol                    = "Tcp"
  source_port_range           = "*"
  destination_port_range      = "22"
  source_address_prefix       = "*"
  destination_address_prefix  = "*"
  resource_group_name         = azurerm_resource_group.rg.name
  network_security_group_name = azurerm_network_security_group.nsg.name
}

# Create a network interface
resource "azurerm_network_interface" "nic" {
  name                = "${random_pet.rg_name.id}-vm01-nic"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  ip_configuration {
    name                          = "${random_pet.rg_name.id}-vm01-ip1-config"
    subnet_id                     = azurerm_subnet.subnet.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.publicip.id
  }
}

# Create a public IP address
resource "azurerm_public_ip" "publicip" {
  name                = "${random_pet.rg_name.id}-vm01-publicip"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  allocation_method   = "Dynamic"
}

# Create a Linux virtual machine
resource "azurerm_linux_virtual_machine" "vm" {
  name                = "${random_pet.rg_name.id}-vm01"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  size                = "Standard_DS1_v2"
  admin_username      = "admin"
  network_interface_ids = [
    azurerm_network_interface.nic.id,
  ]
  admin_ssh_key {
    username       = "admin"
    public_key     = file("./ssh/id_rsa.pub")
  }
  source_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts-gen2"
    version   = "latest"
  }
  os_disk {
    name                 = "myOsDisk"
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }
}

# create a storage account
resource "azurerm_storage_account" "storage" {
  name                     = replace("${random_pet.rg_name.id}st01", "-", "")
  resource_group_name      = azurerm_resource_group.rg.name
  location                 = azurerm_resource_group.rg.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

# create a blob container
resource "azurerm_storage_container" "container" {
  name                  = "blob01"
  storage_account_name  = azurerm_storage_account.storage.name
  container_access_type = "private"
}

# create a private endpoint for blob storage
resource "azurerm_private_endpoint" "blob_endpoint" {
  name                = "${random_pet.rg_name.id}-blob-endpoint"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  subnet_id           = azurerm_subnet.subnet.id

  private_service_connection {
    name                           = "${random_pet.rg_name.id}-blob-endpoint-connection"
    private_connection_resource_id = azurerm_storage_account.storage.id
    subresource_names              = ["blob"]
    is_manual_connection           = false
  }
}

# Create Azure Storage Account Network Rules
# ref : https://gmusumeci.medium.com/using-private-endpoint-in-azure-storage-account-with-terraform-49b4734ada34
resource "azurerm_storage_account_network_rules" "rules" {
  storage_account_id = azurerm_storage_account.storage.id
  default_action     = "Deny"
  bypass             = ["Metrics", "Logging", "AzureServices"]
}

 
ちなみに、今回 GitHub Copilot を使って書いたのですが、途中から「次はサブネットね」「次はストレージアカウントね」という感じで先回りして提示してくれました。恐ろしく、そしてスゴイと思いつつ、サンプル作成の生産性は爆上がりだなというのは改めて感じました。

動作確認として、Ubuntu に azcopy をインストールしてコピーしてみます。インストールは簡単で、バイナリを配置 (gzip ファイルをダウンロードして解凍) するだけです。

learn.microsoft.com

$ ./azcopy copy NOTICE.txt "https://<account>.blob.core.windows.net/blob01?<SAS Token>"
INFO: Scanning...
INFO: Any empty folders will not be processed, because source and/or destination doesn't have full folder support

Job 3ab8a757-e3d3-8d47-423a-18cd10a39d63 has started
Log file is located at: /home/sou/.azcopy/3ab8a757-e3d3-8d47-423a-18cd10a39d63.log

100.0 %, 1 Done, 0 Failed, 0 Pending, 0 Skipped, 1 Total, 2-sec Throughput (Mb/s): 2.0447


Job 3ab8a757-e3d3-8d47-423a-18cd10a39d63 summary
Elapsed Time (Minutes): 0.0334
Number of File Transfers: 1
Number of Folder Property Transfers: 0
Number of Symlink Transfers: 0
Total Number of Transfers: 1
Number of File Transfers Completed: 1
Number of Folder Transfers Completed: 0
Number of File Transfers Failed: 0
Number of Folder Transfers Failed: 0
Number of File Transfers Skipped: 0
Number of Folder Transfers Skipped: 0
TotalBytesTransferred: 512602
Final Job Status: Completed

 
一点、名前解決として注意点があり、「hoge.privatelink.blob.core.windows.net」という名前ではなく、「hoge.blob.core.windows.net」という名前でプライベートエンドポイントの IP に解決するようにします。これは、接続時の SSL 証明書において前者の名前では有効な証明書と認識されないためです。今回は手抜きで hosts に書きました😅

learn.microsoft.com

Microsoft 社のプラクティスとしても、そのような名前解決とするよう示唆があります。

learn.microsoft.com

 
なお、今回は Private DNS まで作成しませんでしたが、それらを含めた参考情報は下記のサイトなどが参考になると思います。

gmusumeci.medium.com