今更ながらBicepの勉強。ただのMSLearnのまとめです。
第二弾中級についてこちら。
第三弾DevOps piplelineからはこちら。
Azure Resource Managerとは
Azure Resource Manager は、Azure 内でリソースをデプロイおよび管理するときに使用されるサービス。Resource Manager を使用して、リソースを作成、更新、削除することができます。
Deployもリソースである
Azure での "デプロイ" とは、デプロイ操作を表す特別なリソースです。 デプロイは、リソースの種類が Microsoft.Resources/deployments である Azure リソースです。icep デプロイを送信すると、デプロイ リソースが作成または更新されます。
文法
準備
事前にSubscriptionIDを控えておいて以下を実施。
az bicep install && az bicep upgrade
az login
az account set --subscription {subscriptionid}
az account show
// 既定のリソース グループを設定する
az configure --defaults group="{Resource Group名}"
基礎的なBicep
なんとなく眺めていれば構造が見えてくる。
// コマンド実行時に引数として受け付けるにはparam
// =が定義されていれば規定値
param location string = 'japaneast'
// uniquestringを利用して、ResourceGroup単位でユニークな値を生成
// こんな感じになる、toylaunchxunjewfivysf2
param storageAccountName string = 'toylaunch${uniqueString(resourceGroup().id)}'
param appServiceAppName string = 'toylaunch${uniqueString(resourceGroup().id)}'
// 引数に制限を設ける
@allowed([
'nonprod'
'prod'
])
param environmentType string
// Bicep内の変数はvar、引数によって三項演算子で条件分岐
var appServicePlanName = 'toy-product-launch-plan'
var storageAccountSkuName = (environmentType == 'prod') ? 'Standard_GRS' : 'Standard_LRS'
var appServicePlanSkuName = (environmentType == 'prod') ? 'P2v3' : 'F1'
// deployするリソースはresourceで定義
resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = {
name: storageAccountName
location: location
sku: {
name: storageAccountSkuName
}
kind: 'StorageV2'
properties: {
accessTier: 'Hot'
minimumTlsVersion: 'TLS1_2'
}
}
resource appServicePlan 'Microsoft.Web/serverFarms@2022-03-01' = {
name: appServicePlanName
location: location
sku: {
name: appServicePlanSkuName
}
}
resource appServiceApp 'Microsoft.Web/sites@2022-03-01' = {
name: appServiceAppName
location: location
properties: {
serverFarmId: appServicePlan.id
httpsOnly: true
}
}
// こんな感じで実行
az deployment group create `
--template-file main2.bicep `
--parameters environmentType=nonprod
// 制限でダメだったらInvalidTemplateエラーになる
'The provided value for the template parameter 'environmentType' is not valid. The value 'nonprodsfdas' is not part of the allowed value(s): 'nonprod,prod'.'
Module
親のmainテンプレートから部品としてのModuleを呼び出す。Moduleでは、関係のあるリソースの固まりをできるだけ自己完結する形にまとめる。
// mainテンプレート
param location string = 'japaneast'
param appServiceAppName string = 'toylaunch${uniqueString(resourceGroup().id)}'
@allowed([
'nonprod'
'prod'
])
param environmentType string
// mainテンプレートからModuleの呼び出し
// mainからの相対パスで指定
module appService 'modules/appService.bicep' = {
name: 'appService'
params: {
location: location
appServiceAppName: appServiceAppName
environmentType: environmentType
}
}
// モジュールからの値を取得して出力する
output appServiceAppHostName string = appService.outputs.appServiceAppHostName
// module側
param location string
param appServiceAppName string
@allowed([
'nonprod'
'prod'
])
param environmentType string
var appServicePlanName = 'toy-product-launch-plan'
// mainから受け取った引数で条件分岐
var appServicePlanSkuName = (environmentType == 'prod') ? 'P2v3' : 'F1'
resource appServicePlan 'Microsoft.Web/serverFarms@2022-03-01' = {
name: appServicePlanName
location: location
sku: {
name: appServicePlanSkuName
}
}
resource appServiceApp 'Microsoft.Web/sites@2022-03-01' = {
name: appServiceAppName
location: location
properties: {
serverFarmId: appServicePlan.id
httpsOnly: true
}
}
// ModuleからMainテンプレートに必要な値を返却 ※複数定義可能
output appServiceAppHostName string = appServiceApp.properties.defaultHostName
Azureポータルからのデプロイ履歴ではModuleが分かれて表示される。Module毎にDeployリソースが作成される。同じModuleを再度Deployすると上書きされてしまうので、デプロイ履歴を持ちたい場合はModule名を日付+時間等で一意にする必要がある。(Pipelineでやる場合、MainとModuleをコピー+RenameしてそれをDeployするのがいいかも?)
デプロイ履歴の詳細のInputとOutputからBicep
内で定義した入力(param)と出力(output)が確認できる。
Moduleの依存関係
以下の様にすればVNetを先に作って、VMで利用するように依存関係を設定できる。自動的に関係が認識され、順番に待機&作成してくれる。逆に言うと、待機するので時間がかかる。
@description('Username for the virtual machine.')
param adminUsername string
@description('Password for the virtual machine.')
@minLength(12)
@secure()
param adminPassword string
module virtualNetwork 'modules/vnet.bicep' = {
name: 'virtual-network'
}
module virtualMachine 'modules/vm.bicep' = {
name: 'virtual-machine'
params: {
adminUsername: adminUsername
adminPassword: adminPassword
// vnetのOutputを利用している
subnetResourceId: virtualNetwork.outputs.subnetResourceId
}
}
Module化のTips
- Bicep Visualizerを使ってModule化の単位の目安とするとよいかも
- モジュールは入れ子にすることも可能だが、階層は浅めにとどめる
- 原則としてはモジュールは再利用されるものなので、モジュールにビジネスルールの既定値(StorageAccountのSku等)を持たせるのは推奨されない、Main側で持つ
- 複雑なDeployの場合は大きなMainと多数のModuleより、複数の小さ目のMainとそれに紐づく少数のModuleの方がTest、Debugしやすい
- Moduleを作ると、Transpile時には1つのARMTemplateに結合される
パラメータ
- string: 任意のテキストを入力できます
- int: 数値を入力できます
- bool: ブール値 (true または false) 値を表します
- object と array: 構造化データとリストを表します
object
// objectで関連する値をまとめる
// objectのプロパティは型定義がない
param appServicePlanSku object = {
name: 'F1'
tier: 'Free'
capacity: 1
}
// 利用
sku: {
name: appServicePlanSku.name
tier: appServicePlanSku.tier
capacity: appServicePlanSku.capacity
}
array
param cosmosDBAccountLocations array = [
{
locationName: 'australiaeast'
}
{
locationName: 'southcentralus'
}
{
locationName: 'westeurope'
}
]
パラメータファイル
パラメータは直打ちではなくてあらかじめファイルとして準備しておくことができる。環境毎に作成するのが推奨。
Microsoftの例の命名規則としては、{main等の利用ファイル名}.{dev等の環境名}.parameters.jsonとなる。例:main.parameters.dev.json。
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appServicePlanInstanceCount": {
"value": 3
},
"appServicePlanSku": {
"value": {
"name": "P1v3",
"tier": "PremiumV3"
}
},
"cosmosDBAccountLocations": {
"value": [
{
"locationName": "australiaeast"
},
{
"locationName": "southcentralus"
},
{
"locationName": "westeurope"
}
]
}
}
}
デプロイ時にはこんな感じでパラメータファイルを利用するように指定する。以下の様に一部ファイルの値をコマンドでデプロイ時にオーバーライドすることも可能。
// appServicePlanInstanceCountをオーバーライド
az deployment group create `
--template-file main.bicep `
--parameters main.parameters.json `
appServicePlanInstanceCount=5
デコレータ
descriptionは最低限推奨。Intellisenseやエラーメッセージ等いろんなところで表示される。
// パラメータの条件等を指定可能
@allowed([
'dev'
'test'
'prod'
])
@minLength(5)
@maxLength(24)
@minValue(1)
@maxValue(10)
@description('desription for the parameter.')
param someParameter string
セキュアなパラメータ
基本的にはManaged Identityを利用して資格情報をファイルから追い出すのが推奨。
secureデコレータ
secureを使うとログに出力されないようになる。
// secureの場合は以下は非推奨
//- 既定値を定義してべた書き
//- outputで出力
//- パラメータファイルに定義してGitにチェックイン
@secure()
param sqlServerAdministratorLogin string
KeyVault連携
KeyVaultで事前に以下の設定が必要。
az keyvault update --name ExampleVault --enabled-for-template-deployment true
また、ユーザーはMicrosoft.KeyVault/vaults/deploy/actionの権限が必要。
パラメータファイル内
Bicepの中からKeyVaultを参照する。
// パラメータファイルの中で普通のvalueの代わりにreferenceを利用
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"sqlServerAdministratorLogin": {
"reference": {
"keyVault": {
"id": "/subscriptions/f0750bbe-ea75-4ae5-b24d-a92ca601da2c/resourceGroups/PlatformResources/providers/Microsoft.KeyVault/vaults/toysecrets"
},
"secretName": "sqlAdminLogin"
}
}
}
}
モジュールにシークレットを渡す
mainからmoduleにシークレットを渡すには、main内でexistingを利用して既存のKeyVaultからデータを取得する。
// 新規作成ではなく既存資源の参照
resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
name: keyVaultName
}
module applicationModule 'application.bicep' = {
name: 'application-module'
params: {
apiKey: keyVault.getSecret('ApiKey')
}
}
モジュール側ではsecretデコレータ付きのparamで受け取る。
この状態でデプロイしてみると、入力を求められ、入力時に表示されないようになる。
Please provide securestring value for 'sqlServerAdministratorLogin' (? for help):
Please provide securestring value for 'sqlServerAdministratorPassword' (? for help):
条件付きDeploy:if文と三項演算子
条件に応じたResourceのデプロイ有無判断。if文をModuleに紐づけてしまうのもよい。
@allowed([
'Development'
'Production'
])
param environmentName string
var auditingEnabled = environmentName == 'Production'
resource auditingSettings 'Microsoft.Sql/servers/auditingSettings@2021-11-01-preview' = if (auditingEnabled) {
parent: server
name: 'default'
properties: {
state: 'Enabled'
storageEndpoint: environmentName == 'Production' ? auditStorageAccount.properties.primaryEndpoints.blob : ''
storageAccountAccessKey: environmentName == 'Production' ? listKeys(auditStorageAccount.id, auditStorageAccount.apiVersion).keys[0].value : ''
}
}
Loop
ループの基本
- foreach
param storageAccountNames array = [
'saauditus'
'saauditeurope'
'saauditapac'
]
resource storageAccountResources 'Microsoft.Storage/storageAccounts@2021-09-01' = [for storageAccountName in storageAccountNames: {
name: storageAccountName
location: resourceGroup().location
kind: 'StorageV2'
sku: {
name: 'Standard_LRS'
}
}]
- for i in next
param subnetNames array = [
'api'
'worker'
]
resource virtualNetwork 'Microsoft.Network/virtualNetworks@2021-08-01' = {
name: 'teddybear'
location: resourceGroup().location
properties: {
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
]
}
subnets: [for (subnetName, i) in subnetNames: {
name: subnetName
properties: {
addressPrefix: '10.0.${i}.0/24'
}
}]
}
}
- object配列をfor
param subnets array = [
{
name: 'frontend'
ipAddressRange: '10.10.0.0/24'
}
{
name: 'backend'
ipAddressRange: '10.10.1.0/24'
}
]
var subnetsProperty = [for subnet in subnets: {
name: subnet.name
properties: {
addressPrefix: subnet.ipAddressRange
}
}]
resource virtualNetwork 'Microsoft.Network/virtualNetworks@2021-08-01' = {
name: 'teddybear'
location: resourceGroup().location
properties:{
addressSpace:{
addressPrefixes:[
addressPrefix
]
}
subnets: subnetsProperty
}
}
- 条件付き for(イメージ的にはwhere)
param sqlServerDetails array = [
{
name: 'sqlserver-we'
location: 'westeurope'
environmentName: 'Production'
}
{
name: 'sqlserver-eus2'
location: 'eastus2'
environmentName: 'Development'
}
{
name: 'sqlserver-eas'
location: 'eastasia'
environmentName: 'Production'
}
]
@secure()
@description('The administrator login username for the SQL server.')
param sqlServerAdministratorLogin string
@secure()
@description('The administrator login password for the SQL server.')
param sqlServerAdministratorLoginPassword string
resource sqlServers 'Microsoft.Sql/servers@2021-11-01-preview' = [for sqlServer in sqlServerDetails: if (sqlServer.environmentName == 'Production') {
name: sqlServer.name
location: sqlServer.location
properties: {
administratorLogin: administratorLogin
administratorLoginPassword: administratorLoginPassword
}
tags: {
environment: sqlServer.environmentName
}
}]
batchSizeデコレータでn並列でループ
通常は並列でDeployされるが、batchSizeのnの値によって、何並列で実行するかを決定可能。1にすれば1,2,3...という風に順次デプロイされる。
@batchSize(n)
resource appServiceApp 'Microsoft.Web/sites@2021-03-01' = [for i in range(1,3): {
name: 'app${i}'
// ...
}]
for loopで回して複数のoutputがあるとき
// main.bicepからfor loopでModuleを複数回呼び出す
module databases 'modules/database.bicep' = [for location in locations: {
name: 'database-${location}'
params: {
location: location
sqlServerAdministratorLogin: sqlServerAdministratorLogin
sqlServerAdministratorLoginPassword: sqlServerAdministratorLoginPassword
}
}]
...省略...
// moduleの出力を配列で受け取ってoutputする
output serverInfo array = [for i in range(0, length(locations)): {
name: databases[i].outputs.serverName
location: databases[i].outputs.location
fullyQualifiedDomainName: databases[i].outputs.serverFullyQualifiedDomainName
}]
// module側、database.bicep
...省略...
// outputが複数個、これが複数回呼ばれてmainに返却される
output serverName string = sqlServer.name
output location string = location
output serverFullyQualifiedDomainName string = sqlServer.properties.fullyQualifiedDomainName
静的解析ツール
bicepを静的解析ツールで作成段階からセキュリティ等をチェックできればShift Leftにもつながる。VSCodeのBicep拡張やSonarでもある程度できる。
- PSRule for Azure
これがどれくらいメジャー、キャッチアップできているのか正直不明なのだが、Bicepに対してMicrosoftのWell Architected Frameworkに沿って自動テストをしてくれる。ちゃんと運用できればこれはすごい。
Deployした後ならCloud Posture ManagementでAzure Defender for CloudのAzure Secure Scoreとかもあるけど。