Bicepの冪等性(べきとうせい)について確認してみた

Azure
前の記事

前回、キーコンテナを使ったコードに書き直して接続をチェックしようとしたところ、

パブリックからのアクセスができるNICがなかったため、パブリックIPの設定を追加します。

特定箇所のみ設定変更したい場合のIaCでの運用について、「冪等性」という言葉が非常に重要らしいので掘り下げていきます。

冪等性とは

ある操作を何度繰り返しても結果が同じになるという性質

IT用語辞典出典

冪等性とは?意味を分かりやすく解説 - IT用語辞典 e-Words
冪等性とは、ある操作を何度繰り返しても結果が同じになるという性質。元は数学の概念だがIT分野でもほぼ同じ意味で用いられる。同じ演算や処理、操作などを1回行っても、何度繰り返し行っても常に同じ結果になる性質を指す。例えば、「与えられた実数の絶...

 

言葉はわかりますが、いまいちピンとこないですね。

 

前回、Bicepファイルを使ってデプロイしましたが、

今回も同じファイル、同じコマンドを使います。

同じファイルですが、NICに関する部分のみコードを追加する形になります。

そうすると、VMをデプロイしたファイルなので、再度VMとかももう1台作成されるのでは・・・?

というのに対する答えが「冪等性」があるので大丈夫ということになります。

もうちょっと具体的に

既にリソースが作成されている場合は「スキップ」

差分があれば「作成・変更」

の処理を自動で判断して行ってくれるのが冪等性という性質です。

そのため、変更点があればその部分のみコードの変更は必要ですが、

特定箇所のみを変更するためのスコープを絞ったデプロイは不要で、ファイルごとデプロイすれば差分のみが反映されます。

実際に変更してみる

NICに関するBicepファイルは以下のような記述になっていました。

nic.bicep
// modules/nic.bicep 

@description('デプロイする場所 (リージョン)')
param location string

@description('NIC名')
param nicName string

@description('サブネットのリソースID')
param subnetId string

@description('プライベートIPアドレスの割り当て方法 (Dynamic or Static)')
param privateIpAllocationMethod string = 'Dynamic'

// ネットワークインターフェースのリソース定義
resource nic 'Microsoft.Network/networkInterfaces@2023-09-01' = {
  name: nicName
  location: location
  properties: {
    ipConfigurations: [
      {
        name: 'ipconfig1'
        properties: {
          privateIPAllocationMethod: privateIpAllocationMethod
          subnet: {
            id: subnetId // VNetのサブネットIDを参照
          }
        }
      }
    ]
  }
}

// NICのリソースIDを外部に出力
output nicId string = nic.id

確かにプライベートIPに関する設定しか書かれていないので、以下のように変更します。

<全体構成>

.
├── main.bicep             # メインデプロイファイル (全てのモジュールを連携)
├── keyvault/
│   └── keyvault.bicep     # Key Vault
└── modules/
    ├── vnet.bicep         
    ├── nic.bicep          # 変更あり: Public IPとNSGのIDを受け取る
    ├── vm.bicep           
    ├── publicip.bicep     # 新規追加
    └── nsg.bicep          # 新規追加
publicip.bicep
// modules/publicip.bicep

@description('デプロイする場所 (リージョン)')
param location string

@description('Public IPリソース名')
param publicIpName string

resource publicIP 'Microsoft.Network/publicIPAddresses@2023-09-01' = {
  name: publicIpName
  location: location
  properties: {
    publicIPAllocationMethod: 'Static'
  }
  sku: {
      name: 'Standard'
      tier: 'Regional'
  }
}

// 出力: NICモジュールで参照するためにIDと接続確認用にIPアドレスを出力
output publicIpId string = publicIP.id
output publicIpAddress string = publicIP.properties.ipAddress

パブリックIPアドレスの設定を入れてます。

AzureはパブリックIPアドレス自体のリソースがあるので、これをデプロイします。

後ほど、NICリソースに関連付けてプライベートIPとパブリックIPのNICリソースを一纏めにします。

nsg.bicep
// modules/nsg.bicep

@description('デプロイする場所 (リージョン)')
param location string

@description('NSGリソース名')
param nsgName string

resource nsg 'Microsoft.Network/networkSecurityGroups@2023-09-01' = {
  name: nsgName
  location: location
  properties: {
    securityRules: [
      {
        name: 'AllowRDPInbound'
        properties: {
          priority: 100
          direction: 'Inbound'
          access: 'Allow'
          protocol: 'Tcp'
          sourceAddressPrefix: '*'       // ⚠️ 運用時は特定のIPに制限すべき
          sourcePortRange: '*'
          destinationAddressPrefix: '*'
          destinationPortRange: '3389'   // Windows VM (RDP) のポート
        }
      }
    ]
  }
}

// 出力: NICモジュールで参照するためにIDを出力
output nsgId string = nsg.id

一旦、テストなのでインターネットから3389でアクセスできるようにしました。

nic.bicep
// modules/nic.bicep

@description('デプロイする場所 (リージョン)')
param location string

@description('NIC名')
param nicName string

@description('サブネットのリソースID')
param subnetId string

// --- 新規追加パラメータ ---
@description('NSGのリソースID')
param nsgId string

@description('Public IPのリソースID')
param publicIpId string

// NIC(publicIP)のリソース定義
resource nic 'Microsoft.Network/networkInterfaces@2023-09-01' = {
  name: nicName
  location: location
  properties: {
    // 1. NSGの関連付け
    networkSecurityGroup: {
      id: nsgId
    }
    ipConfigurations: [
      {
        name: 'ipconfig1'
        properties: {
          privateIPAllocationMethod: 'Dynamic'
          subnet: {
            id: subnetId 
          }
          // 2. Public IPの関連付け
          publicIPAddress: {
            id: publicIpId
          }
        }
      }
    ]
  }
}

output nicId string = nic.id

AzureのNICリソースは1つのリソースにプライベートIPとパブリックIPを持つことができるので、同じリソース内でどちらも定義しています。

NSGもNICに割り当てています。

main.bicep
// main.bicep

@description('デプロイする場所 (リージョン)')
param location string = resourceGroup().location

// --- 必須パラメータ ---
@description('VM名')
param vmName string
@description('管理者ユーザー名')
param adminUsername string

// --- Key Vault 関連パラメータ (外部からIDを受け取る) ---
@description('既に作成されているKey Vaultの完全なリソースID')
param existingKeyVaultId string

@description('Key Vaultに保存されているVM管理者パスワードのシークレット名')
param adminPasswordSecretName string = 'vm-admin-password'

// --- ネットワーク/VM設定パラメータ ---
@description('仮想ネットワーク名')
param vnetName string = 'vnet-deploy-bicep'
@description('サブネット名')
param subnetName string = 'subnet-deploy-bicep'
@description('NIC名')
param nicName string = 'nic-deploy-bicep'
@description('VMのサイズ (例: Standard_B1s)')
param vmSize string = 'Standard_B1s'

// --- Key Vault 参照の準備 ---
// Key Vault IDから Key Vault 名を抽出
var keyVaultName = last(split(existingKeyVaultId, '/'))

// Key Vault のプロバイダー情報を取得するための変数 (Bicepが参照を解決するために必要)
// Bicepでは外部リソースへの参照に `resourceId()` が使われますが、Key Vault参照には特殊な構文が必要です。
var keyVaultReference = resourceId(
  subscription().subscriptionId,
  resourceGroup().name,
  'Microsoft.KeyVault/vaults',
  keyVaultName
)

// --- 1. Public IP モジュールのデプロイ ---
module publicIpModule 'modules/publicip.bicep' = {
  name: 'publicip-deployment'
  params: {
    location: location
    publicIpName: '${vmName}-pip'
  }
}

// --- 2. NSG モジュールのデプロイ ---
module nsgModule 'modules/nsg.bicep' = {
  name: 'nsg-deployment'
  params: {
    location: location
    nsgName: '${vmName}-nsg'
  }
}

// 3. 仮想ネットワークモジュールのデプロイ
module vnetModule 'modules/vnet.bicep' = {
  name: 'vnet-deployment'
  params: {
    location: location
    vnetName: vnetName
    subnetName: subnetName
  }
}

// 4. ネットワークインターフェースモジュールのデプロイ
module nicModule 'modules/nic.bicep' = {
  name: 'nic-deployment'
  params: {
    location: location
    nicName: '${vmName}-nic'
    subnetId: vnetModule.outputs.subnetId 
    
    // Public IPとNSGモジュールの出力を渡す
    nsgId: nsgModule.outputs.nsgId
    publicIpId: publicIpModule.outputs.publicIpId
  }
}

// 5. 仮想マシンモジュールのデプロイ
module vmModule 'modules/vm.bicep' = {
  name: 'vm-deployment'
  params: {
    location: location
    vmName: vmName
    adminUsername: adminUsername
    vmSize: vmSize
    nicId: nicModule.outputs.nicId
    
    // Key Vault 参照をシークレットURIとしてVMモジュールに渡す
    // この構文により、デプロイ時にAzure Resource ManagerがKey Vaultからシークレットを取得します。
    adminPassword: '[reference(\'${keyVaultReference}\', \'2023-07-01\').secretUriWithVersion(\'${adminPasswordSecretName}\')]'
  }
}

// 最終的なVM IDを出力
output virtualMachineId string = vmModule.outputs.vmId

// --- 接続用のPublic IPを出力に追加 ---
output publicIpAddress string = publicIpModule.outputs.publicIpAddress

モジュールを追加したのでmain.bicepで呼び出してデプロイするために、publicipのnicとNSGモジュールを呼び出すコードを追記しました。

※デプロイした後に気づきましたが、nicの名前が変わっています。

さて、変更点としては

  • パブリックIP、NSGリソースの追加
  • NICリソースの修正
  • main.bicepでのモジュール呼び出し処理の修正

のみで、VNETやVMをデプロイするコードは特に変更を加えていません。

これを以下のコマンドで実行します。

# パラメータをハッシュテーブルまたはインラインで指定する例
Connect-AzAccount

# Key Vault IDの取得
$existingKeyVaultId = (Get-AzKeyVault -VaultName $keyVaultName).ResourceId

# $HOME\.azure\bin を現在のセッションのPATHに一時的に追加
$env:Path += ";$HOME\.azure\bin"

$vmParameters = @{
    vmName                  = "vm-deploy-bicep"
    adminUsername           = "adminUser"
    existingKeyVaultId      = $existingKeyVaultId
    adminPasswordSecretName = "vm-admin-password"
    location                = $location
}

# main.bicep のデプロイ実行
New-AzResourceGroupDeployment -ResourceGroupName rg-vmdeploy-bicep `
    -TemplateFile "main.bicep" `
    -TemplateParameterObject $vmParameters

main.bicepをデプロイしているので、素直に考えるとVNETのモジュールやVMのモジュールも呼び出されて新規作成する動きをしようとするはずです。

が、結果としてはVMやVNETのリソースは作成されず、コードに変更を加えた箇所のみが実行されています。

<実行前>

<実行後>

※全然本筋と関係ないですが、NICの名前を間違えてデプロイしてしまいvm-deploy-bicepからvm-deploy-bicep-nicになっています。前の名前のリソースは残置され新しいNICリソースができていてややこしかったので前のNICは消しました。

繰り返しになりますが、このコードに変更を加えた箇所のみが変更される性質を「冪等性(べきとうせい)」といいます。

以上。

次はCI/CDパイプラインを検証してみたいです。

コメント