可用性セットを組んだ仮想マシンをバックエンドプールに使うロードバランサーを構築する Terraform サンプル

Azure にて、仮想マシンスケールセットではなく、可用性セットを組んだ仮想マシン 2 台を、ロードバランサーのバックエンドプールにしてみた。

まぁ、最終的には仮想マシンスケールセットを使ったけどね。せっかく試したんでメモを残しておく。

resource "azurerm_resource_group" "example" {
  name     = "rg-example"
  location = "japaneast"
}

# 仮想ネットワーク
resource "azurerm_virtual_network" "example" {
  name                = "vnet-example"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
}

# 仮想ネットワークのサブネット
resource "azurerm_subnet" "example" {
  name                 = "snet-example"
  resource_group_name  = azurerm_resource_group.example.name
  virtual_network_name = azurerm_virtual_network.example.name
  address_prefixes     = ["10.0.2.0/24"]
}

# ロードバランサー用パブリックIPアドレス
resource "azurerm_public_ip" "example" {
  name                = "pip-example"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
  allocation_method   = "Static"
  sku                 = "Standard"
}

# ネットワークセキュリティグループ
resource "azurerm_network_security_group" "example" {
  name                = "nsg-example"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
}

# TCP 443 を許可するネットワークセキュリティルール
resource "azurerm_network_security_rule" "example" {
  name                        = "nsrule-example"
  resource_group_name         = azurerm_resource_group.example.name
  network_security_group_name = azurerm_network_security_group.example.name
  priority                    = 1001
  direction                   = "Inbound"
  access                      = "Allow"
  protocol                    = "Tcp"
  source_port_range           = "*"
  destination_port_range      = 443
  source_address_prefix       = "*"
  destination_address_prefix  = "*"
}

# 仮想ネットワークインターフェイスカード
resource "azurerm_network_interface" "example" {
  count               = 2
  name                = "nic-example-${format("%03d", count.index + 1)}"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name

  ip_configuration {
    name                          = "nicconf-example"
    subnet_id                     = azurerm_subnet.example.id
    private_ip_address_allocation = "Dynamic"
  }
}

# ネットワークインタフェースをセキュリティグループに接続
resource "azurerm_network_interface_security_group_association" "example" {
  count                     = length(azurerm_network_interface.example)
  network_interface_id      = azurerm_network_interface.example[count.index].id
  network_security_group_id = azurerm_network_security_group.example.id
}


# 仮想マシンの可用性セット
resource "azurerm_availability_set" "example" {
  name                         = "avail-example"
  location                     = azurerm_resource_group.example.location
  resource_group_name          = azurerm_resource_group.example.name
  platform_fault_domain_count  = 2
  platform_update_domain_count = 2
  managed                      = true
}

# 仮想マシン(Windows)
resource "azurerm_windows_virtual_machine" "example" {
  count                 = 2
  name                  = "vmexample${format("%03d", count.index + 1)}"
  resource_group_name   = azurerm_resource_group.example.name
  location              = azurerm_resource_group.example.location
  size                  = "Standard_F2s_v2"
  admin_username        = "Administrator"
  admin_password        = "P@assw0rd12345"
  availability_set_id   = azurerm_availability_set.example.id
  network_interface_ids = [
    azurerm_network_interface.example[count.index].id
  ]

  os_disk {
    name                 = "osdisk-example-${format("%03d", count.index + 1)}"
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2019-Datacenter"
    version   = "latest"
  }
}

# ロードバランサー
resource "azurerm_lb" "example" {
 name                = "lb-example"
 location            = azurerm_resource_group.example.location
 resource_group_name = azurerm_resource_group.example.name
 sku                 = "Standard"

 # フロントエンドIP構成
 frontend_ip_configuration {
   name                 = "fipconf-example"
   public_ip_address_id = azurerm_public_ip.example.id
 }
}

# バックエンドプール
resource "azurerm_lb_backend_address_pool" "example" {
 name                = "bep-example"
 loadbalancer_id     = azurerm_lb.example.id
}

# バックエンドプールの仮想マシン
# 厳密にはネットワークインターフェース
resource "azurerm_network_interface_backend_address_pool_association" "example" {
  count                   = length(azurerm_network_interface.example)
  network_interface_id    = azurerm_network_interface.example[count.index].id
  ip_configuration_name   = "nicconf-example"
  backend_address_pool_id = azurerm_lb_backend_address_pool.example.id
}

# 正常性プローブ
resource "azurerm_lb_probe" "example" {
 name                = "probe-example"
 resource_group_name = azurerm_resource_group.example.name
 loadbalancer_id     = azurerm_lb.example.id
 port                = 443
}

# 負荷分散規則
resource "azurerm_lb_rule" "example" {
   name                           = "lbrule-example"
   resource_group_name            = azurerm_resource_group.example.name
   loadbalancer_id                = azurerm_lb.example.id
   protocol                       = "Tcp"
   frontend_port                  = 443
   backend_port                   = 443
   backend_address_pool_id        = azurerm_lb_backend_address_pool.example.id
   frontend_ip_configuration_name = "fipconf-example"
   probe_id                       = azurerm_lb_probe.example.id
}

Azure App Service で Managed Certificate を使う Terraform サンプル

カスタムドメインは Azure DNS ゾーンに登録してある前提。

resource "azurerm_resource_group" "example" {
  name     = "rg-example"
  location = "japaneast"
}

resource "azurerm_app_service_plan" "example" {
  name                = "plan-example"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name

  sku {
    tier = "Standard"
    size = "S1"
  }
}

resource "azurerm_app_service" "example" {
  name                = "app-example"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
  app_service_plan_id = azurerm_app_service_plan.example.id
}

# App Service でカスタムドメインを使う
resource "azurerm_app_service_custom_hostname_binding" "example" {
  hostname            = "your-custom-domain.example.com"
  app_service_name    = azurerm_app_service.example.name
  resource_group_name = azurerm_resource_group.example.name

  # Managed Cetificate により自動更新されるので変更を無視
  lifecycle {
    ignore_changes = [ssl_state, thumbprint]
  }
}

# App Service で Managed Cetificate を使う
resource "azurerm_app_service_managed_certificate" "example" {
  custom_hostname_binding_id = azurerm_app_service_custom_hostname_binding.example.id
}

# Managed Cetificate をカスタムドメインにバインドする
resource "azurerm_app_service_certificate_binding" "example" {
  hostname_binding_id = azurerm_app_service_custom_hostname_binding.example.id
  certificate_id      = azurerm_app_service_managed_certificate.example.id
  ssl_state           = "SniEnabled"
}

渡くんの××が崩壊寸前(10)

石原さんに釣り合うために変わろうとした渡くんは素晴らしい心がけなんだけど、お互い自分を良く見せようとしていて、それをずっと続けて行くのはシンドそう。石原さんは暴走機関車だし、渡くんは流されやすいしで、ハッピーエンドが想像できないな。ただ、石原さんは、周りに流されそうになるところをこらえて、ちゃんと自分の意思を表明したので、成長が窺える。一縷の望み。

紗月の方が渡くんにとって気やすそうで、お似合いだけどなぁ。でもまぁ渡くんは石原さんに未練ありまくりだから、復縁するんだろうな。と思いきや、渡くんまさかの爆弾発言。そして11巻に続く。え、どういうこと。家族みたいなものだからって言葉が続いたりする?まったく、良いところで切ってくれるもんだ。

それはそうと、マキナ結構好きなんだけど、完全に噛ませ犬というか、子守役というか。どうしてこうなった。残念。

ぼくたちは勉強ができない(21)

ぼく勉はとうとう最終巻。トリを飾るのは人気投票で1位を獲得した真ヒロイン真冬センセのルートだった。

クールなようで、実はポンコツで生活力皆無なため、結局成幸に助けられてしまう。年上らしくあろうとするけど、教師と元教え子という関係に悶々とする、そのギャップが愛おしいヒロイン。本作のコスプレ担当でもある。その集大成はズバリ、ウエディングドレス。いやこれはコスプレ違うか。今までヒロインが着ている姿はなかったな。真冬センセのために取っておいたのかと邪推してしまう。そのお美しい姿はトリに相応しい。

一方で成幸は、そんな真冬センセが学生時代に送れなかった青春を取り戻させようと、当時のクラスメイトや教え子たちに掛け合って、見事に実現させた。5人のヒロインのルート中で一番の行動力を発揮したかも。そりゃあ、真冬センセもトゥクンってなるわ。

マンガでマルチエンディングに挑戦するなんて前代未聞。でも、成幸と結ばれるのは3人娘の誰かだと思っていたので、無いと思っていた真冬先生ルートが読めて自分的に僥倖。アリかナシかで言ったら、断然アリ。他のマンガも真似して、これが当たり前の手法になっていいくらいだ。

Packer で VM イメージビルド中に Azure ファイル共有をマウントする

Packer で Windows Server 2019 Datacenter の VM イメージを作成しているが、アプリケーションをインストールために、WinRM でインストーラーをアップロードするのは遅すぎてまったく実用的ではなかった。

インストーラーみたいな大きいファイルは、あらかじめ Azure ファイル共有に置いておき、VM イメージをビルド中にマウントしコピーするしかなさそう。

Azure ファイル共有をマウントするスクリプトは、Azure ポータルで下記のようなものを入手できる。

$connectTestResult = Test-NetConnection -ComputerName ストレージアカウント名.file.core.windows.net -Port 445
if ($connectTestResult.TcpTestSucceeded) {
    # 再起動時にドライブが維持されるように、パスワードを保存する
    cmd.exe /C "cmdkey /add:`"ストレージアカウント名.file.core.windows.net`" /user:`"Azure\ストレージアカウント`" /pass:`"パスワード`""
    # ドライブをマウントする
    New-PSDrive -Name Z -PSProvider FileSystem -Root "\\ストレージアカウント名.file.core.windows.net\ファイル共有名" -Persist
} else {
    Write-Error -Message "Unable to reach the Azure storage account via port 445. Check to make sure your organization or ISP is not blocking port 445, or use Azure P2S VPN, Azure S2S VPN, or Express Route to tunnel SMB traffic over a different port."
}

このスクリプトpowershell provisioner で実行する。

variable "client_id" {
  type = string
}
variable "client_secret" {
  type = string
}
variable "tenant_id" {
  type = string
}
variable "subscription_id" {
  type = string
}
variable "managed_image_resource_group_name" {
  type = string
  default = "rg-example"
}
variable "managed_image_name" {
  type = string
  default = "vmi-example"
}
variable "build_resource_group_name" {
  type = string
  default = "rg-example"
}
variable "vm_size" {
  type = string
  default = "Standard_D2_v2"
}

source "azure-arm" "windowsserver2019" {
  client_id = var.client_id
  client_secret = var.client_secret
  tenant_id = var.tenant_id
  subscription_id = var.subscription_id

  managed_image_resource_group_name = var.managed_image_resource_group_name
  managed_image_name = var.managed_image_name

  os_type = "Windows"
  image_publisher = "MicrosoftWindowsServer"
  image_offer = "WindowsServer"
  image_sku = "2019-Datacenter"

  communicator = "winrm"
  winrm_use_ssl = true
  winrm_insecure = true
  winrm_timeout = "10m"
  winrm_username = "packer"
  winrm_password = "P@ssw0rd12345"

  azure_tags = {
    dept = "Engineering"
    task = "Image deployment"
  }

  build_resource_group_name = var.build_resource_group_name
  vm_size = var.vm_size
}

build {
  sources = [
    "sources.azure-arm.windowsserver2019"
  ]

  provisioner "powershell" {
    inline = [
      "while ((Get-Service RdAgent).Status -ne 'Running') { Start-Sleep -s 5 }",
      "while ((Get-Service WindowsAzureGuestAgent).Status -ne 'Running') { Start-Sleep -s 5 }",
    ]
  }

  # Azure ファイル共有をマウントする PowerShell スクリプトを実行
  provisioner "powershell" {
    script = ".\mount-azure-file-share.ps1"
    elevated_user = "packer"
    elevated_password = "P@ssw0rd12345"
  }

  provisioner "powershell" {
    inline = [
      "& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /quiet /quit",
      "while($true) { $imageState = Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State | Select ImageState; if($imageState.ImageState -ne 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { Write-Output $imageState.ImageState; Start-Sleep -s 10  } else { break } }",
    ]
  }
}

elevated_user と elevated_password を指定し、管理者権限に昇格して実行する必要があるので注意。

指定しないで実行すると、「ネットワークパスが見つかりません」のようなエラーでマウントに失敗した。「権限が無い」というエラーならまだ気付きやすかったのに…。このせいで1日以上潰れた。

アオアシ(23)

エスペリオン対青森星蘭が開戦。アシトはスタメンに復帰したのは良いけど、刈りどころとして狙われてしまった。まぁ、フィジカル紙だし、かわす技術もないので、エース級をぶつけられたらひとたまりもない。勝負の世界で相手の弱点を狙うのは、ごく普通のことなので致し方なし。

キーパーを11人目のフィールドプレイヤーとして組み立てに参加させ、常に数的優位を作ることで回避はしたものの、ミスが即失点に直結するわけで、このまま凌ぎ続けられるとは思えないな。ていうか、現代サッカーのゴールキーパーにはここまで求められるのか。

アシトが俯瞰の力を活かし、ディフェンダーとして覚醒して攻守に試合を支配しないと、エスペリオンには勝機が無いような気がする。アシトがアラバロールをやるかもしれない。もしそうなったらアツイな。

Packer で .NET Framework 4.8 をインストールした Windows Server 2019 の Azure VM イメージを作成

はじめに

Azure にある公式の Windows Server 2019 Datacenter の VM イメージは、記事執筆時点で .NET Framework 4.8 が入っていなかったので、 .NET Framework 4.8 入りの VM イメージをビルドした。Packer で。

www.packer.io

Azure リソース グループを作成

ビルドした VM イメージの置き場となるリソースグループを作成しておく。PowerShell ではなく Azure CLI で行った。

az group create -n rg-example -l japaneast

Azure 資格情報の作成

Packer で Azure VM イメージを作成する際に必要な、Azure の資格情報を取得。

az ad sp create-for-rbac --query "{ client_id: appId, client_secret: password, tenant_id: tenant }"

サブスクリプションの ID も。

az account show --query "{ subscription_id: id }"

Packer テンプレートを定義

Packer は HCL に対応しているので、JSON ではなく HCL でテンプレートを記述した。ファイル名は windows.pkr.hcl。

variable "client_id" {
  type = string
}
variable "client_secret" {
  type = string
}
variable "tenant_id" {
  type = string
}
variable "subscription_id" {
  type = string
}
variable "managed_image_resource_group_name" {
  type = string
  default = "rg-example"
}
variable "managed_image_name" {
  type = string
  default = "vmi-example"
}
variable "build_resource_group_name" {
  type = string
  default = "rg-example"
}
variable "vm_size" {
  type = string
  default = "Standard_D2_v2"
}

source "azure-arm" "windowsserver2019" {
  client_id = var.client_id
  client_secret = var.client_secret
  tenant_id = var.tenant_id
  subscription_id = var.subscription_id

  managed_image_resource_group_name = var.managed_image_resource_group_name
  managed_image_name = var.managed_image_name

  os_type = "Windows"
  image_publisher = "MicrosoftWindowsServer"
  image_offer = "WindowsServer"
  image_sku = "2019-Datacenter"

  communicator = "winrm"
  winrm_use_ssl = true
  winrm_insecure = true
  winrm_timeout = "10m"
  winrm_username = "packer"

  azure_tags = {
    dept = "Engineering"
    task = "Image deployment"
  }

  build_resource_group_name = var.build_resource_group_name
  vm_size = var.vm_size
}

build {
  sources = [
    "sources.azure-arm.windowsserver2019"
  ]

  provisioner "powershell" {
    inline = [
      "while ((Get-Service RdAgent).Status -ne 'Running') { Start-Sleep -s 5 }",
      "while ((Get-Service WindowsAzureGuestAgent).Status -ne 'Running') { Start-Sleep -s 5 }",
    ]
  }

  # .NET Framework 4.8 をインストール
  provisioner "windows-shell" {
    inline = [
      "curl -fSLo dotnet-framework-installer.exe https://download.visualstudio.microsoft.com/download/pr/7afca223-55d2-470a-8edc-6a1739ae3252/abd170b4b0ec15ad0222a809b761a036/ndp48-x86-x64-allos-enu.exe",
      ".\\dotnet-framework-installer.exe /q",
      "del .\\dotnet-framework-installer.exe",
    ]
  }
  provisioner "windows-restart" {}

  provisioner "powershell" {
    inline = [
      "& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /quiet /quit",
      "while($true) { $imageState = Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State | Select ImageState; if($imageState.ImageState -ne 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { Write-Output $imageState.ImageState; Start-Sleep -s 10  } else { break } }",
    ]
  }
}

Packer イメージをビルド

取得しておいた Azure の資格情報を指定して packer build 実行。

packer build \
  -var "client_id=<Azure 資格情報の client_id>" \
  -var "client_secret=<Azure 資格情報の client_secret>" \
  -var "tenant_id=<Azure 資格情報の tenant_id>" \
  -var "subscription_id=<Azure 資格情報の subscription_id>" \
   .\windows.pkr.hcl

ビルドは結構時間がかかる。10分以上、30分未満くらい。コーヒーでも飲みながら待つといい。

おわりに

今回は powershell provisioner を使って、 .NET Framework 4.8 のインストーラーを VM 内にダウンロードし、インストールした。

記事には書かなかったが、ローカルにあらかじめ用意したインストーラーを、file provisioner を使ってアップロードする方法も試したが、WinRM でのアップロードが激遅でまったく実用的ではなかった。

大きいファイルを VM イメージ内に含めたい場合は、Azure ファイル共有を介した方がいいだろうな。