Azure Bicep による Infrastructure as Code 実践ガイド
Azure Bicep による Infrastructure as Code 実践ガイド
はじめに
Azure Bicep は、Azure リソースをデプロイするための宣言型のドメイン固有言語(DSL)です。ARM テンプレートよりもシンプルで読みやすい構文を提供し、Infrastructure as Code(IaC)の実践を容易にします。本記事では、Bicep を使用した実践的なインフラ管理手法を解説します。
Bicep の基本概念
1. なぜ Bicep を使うのか
// ARM テンプレート(JSON)での記述
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"type": "string",
"minLength": 3,
"maxLength": 24
}
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-04-01",
"name": "[parameters('storageAccountName')]",
"location": "[resourceGroup().location]",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2"
}
]
}
// Bicep での同じリソース定義
param storageAccountName string
resource storageAccount 'Microsoft.Storage/storageAccounts@2021-04-01' = {
name: storageAccountName
location: resourceGroup().location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
}
2. 基本構文と型システム
// パラメータ定義
@description('環境名(dev, staging, prod)')
@allowed([
'dev'
'staging'
'prod'
])
param environment string
@description('リージョン名')
param location string = resourceGroup().location
@secure()
@description('SQL Server 管理者パスワード')
param sqlAdminPassword string
// 変数定義
var storageAccountName = 'st${uniqueString(resourceGroup().id)}${environment}'
var appServicePlanName = 'asp-${environment}-${location}'
// リソース定義
resource appServicePlan 'Microsoft.Web/serverfarms@2021-02-01' = {
name: appServicePlanName
location: location
sku: {
name: environment == 'prod' ? 'P2v3' : 'B1'
tier: environment == 'prod' ? 'PremiumV3' : 'Basic'
}
properties: {
reserved: true // Linux
}
}
// 出力定義
output appServicePlanId string = appServicePlan.id
output appServicePlanName string = appServicePlan.name
実践的な Bicep モジュール設計
1. モジュール構造
infrastructure/
├── main.bicep # メインテンプレート
├── modules/ # モジュールディレクトリ
│ ├── networking/
│ │ ├── vnet.bicep
│ │ └── nsg.bicep
│ ├── compute/
│ │ ├── vm.bicep
│ │ └── aks.bicep
│ ├── data/
│ │ ├── sql.bicep
│ │ └── cosmos.bicep
│ └── security/
│ ├── keyvault.bicep
│ └── identity.bicep
├── environments/ # 環境別パラメータ
│ ├── dev.parameters.json
│ ├── staging.parameters.json
│ └── prod.parameters.json
└── scripts/ # デプロイスクリプト
└── deploy.ps1
2. ネットワークモジュール
// modules/networking/vnet.bicep
@description('仮想ネットワーク名')
param vnetName string
@description('アドレス空間')
param addressPrefix string = '10.0.0.0/16'
@description('サブネット構成')
param subnets array = [
{
name: 'web-subnet'
addressPrefix: '10.0.1.0/24'
nsgId: ''
}
{
name: 'app-subnet'
addressPrefix: '10.0.2.0/24'
nsgId: ''
}
{
name: 'db-subnet'
addressPrefix: '10.0.3.0/24'
nsgId: ''
}
]
@description('タグ')
param tags object = {}
resource vnet 'Microsoft.Network/virtualNetworks@2021-05-01' = {
name: vnetName
location: resourceGroup().location
tags: tags
properties: {
addressSpace: {
addressPrefixes: [
addressPrefix
]
}
subnets: [for subnet in subnets: {
name: subnet.name
properties: {
addressPrefix: subnet.addressPrefix
networkSecurityGroup: empty(subnet.nsgId) ? null : {
id: subnet.nsgId
}
privateEndpointNetworkPolicies: 'Disabled'
privateLinkServiceNetworkPolicies: 'Disabled'
}
}]
}
}
output vnetId string = vnet.id
output vnetName string = vnet.name
output subnets array = [for (subnet, i) in subnets: {
name: subnet.name
id: vnet.properties.subnets[i].id
}]
3. AKS クラスタモジュール
// modules/compute/aks.bicep
@description('AKS クラスタ名')
param clusterName string
@description('Kubernetes バージョン')
param kubernetesVersion string = '1.27.7'
@description('ノードプール設定')
param systemNodePool object = {
name: 'system'
count: 3
vmSize: 'Standard_D4s_v3'
osDiskSizeGB: 128
maxPods: 30
}
@description('ユーザーノードプール設定')
param userNodePools array = []
@description('ネットワーク設定')
param networkProfile object = {
networkPlugin: 'azure'
networkPolicy: 'calico'
serviceCidr: '10.0.0.0/16'
dnsServiceIP: '10.0.0.10'
dockerBridgeCidr: '172.17.0.1/16'
}
@description('サブネット ID')
param subnetId string
@description('Log Analytics ワークスペース ID')
param logAnalyticsWorkspaceId string = ''
resource aksIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = {
name: '${clusterName}-identity'
location: resourceGroup().location
}
resource aks 'Microsoft.ContainerService/managedClusters@2023-01-01' = {
name: clusterName
location: resourceGroup().location
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${aksIdentity.id}': {}
}
}
properties: {
kubernetesVersion: kubernetesVersion
dnsPrefix: clusterName
agentPoolProfiles: concat([
{
name: systemNodePool.name
count: systemNodePool.count
vmSize: systemNodePool.vmSize
osDiskSizeGB: systemNodePool.osDiskSizeGB
maxPods: systemNodePool.maxPods
type: 'VirtualMachineScaleSets'
mode: 'System'
osType: 'Linux'
vnetSubnetID: subnetId
enableAutoScaling: true
minCount: 3
maxCount: 5
}
], [for nodePool in userNodePools: {
name: nodePool.name
count: nodePool.count
vmSize: nodePool.vmSize
osDiskSizeGB: nodePool.osDiskSizeGB
maxPods: nodePool.maxPods
type: 'VirtualMachineScaleSets'
mode: 'User'
osType: 'Linux'
vnetSubnetID: subnetId
enableAutoScaling: nodePool.enableAutoScaling
minCount: nodePool.minCount
maxCount: nodePool.maxCount
nodeTaints: nodePool.?nodeTaints ?? []
nodeLabels: nodePool.?nodeLabels ?? {}
}])
networkProfile: {
networkPlugin: networkProfile.networkPlugin
networkPolicy: networkProfile.networkPolicy
serviceCidr: networkProfile.serviceCidr
dnsServiceIP: networkProfile.dnsServiceIP
dockerBridgeCidr: networkProfile.dockerBridgeCidr
loadBalancerSku: 'standard'
}
addonProfiles: {
azureKeyvaultSecretsProvider: {
enabled: true
config: {
enableSecretRotation: 'true'
rotationPollInterval: '2m'
}
}
omsagent: empty(logAnalyticsWorkspaceId) ? {} : {
enabled: true
config: {
logAnalyticsWorkspaceResourceID: logAnalyticsWorkspaceId
}
}
}
enableRBAC: true
aadProfile: {
managed: true
enableAzureRBAC: true
}
}
}
output clusterName string = aks.name
output clusterFqdn string = aks.properties.fqdn
output identityPrincipalId string = aksIdentity.properties.principalId
4. メインテンプレート
// main.bicep
targetScope = 'subscription'
@description('環境名')
@allowed(['dev', 'staging', 'prod'])
param environment string
@description('リージョン')
param location string
@description('プロジェクト名')
param projectName string
// リソースグループ
resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: 'rg-${projectName}-${environment}'
location: location
tags: {
Environment: environment
Project: projectName
ManagedBy: 'Bicep'
}
}
// ネットワーク
module networking './modules/networking/vnet.bicep' = {
scope: rg
name: 'networking'
params: {
vnetName: 'vnet-${projectName}-${environment}'
addressPrefix: environment == 'prod' ? '10.0.0.0/16' : '10.1.0.0/16'
tags: {
Environment: environment
Project: projectName
}
}
}
// AKS クラスタ
module aks './modules/compute/aks.bicep' = if (environment != 'dev') {
scope: rg
name: 'aks'
params: {
clusterName: 'aks-${projectName}-${environment}'
subnetId: networking.outputs.subnets[1].id
systemNodePool: {
name: 'system'
count: environment == 'prod' ? 3 : 1
vmSize: environment == 'prod' ? 'Standard_D4s_v3' : 'Standard_D2s_v3'
osDiskSizeGB: 128
maxPods: 30
}
userNodePools: environment == 'prod' ? [
{
name: 'user'
count: 3
vmSize: 'Standard_D4s_v3'
osDiskSizeGB: 128
maxPods: 30
enableAutoScaling: true
minCount: 3
maxCount: 10
}
] : []
}
}
// Key Vault
module keyVault './modules/security/keyvault.bicep' = {
scope: rg
name: 'keyvault'
params: {
keyVaultName: 'kv-${projectName}-${environment}'
enableSoftDelete: environment == 'prod'
enablePurgeProtection: environment == 'prod'
accessPolicies: environment != 'dev' ? [
{
tenantId: subscription().tenantId
objectId: aks.outputs.identityPrincipalId
permissions: {
secrets: ['get', 'list']
}
}
] : []
}
}
CI/CD パイプライン統合
1. Azure DevOps Pipeline
# azure-pipelines.yml
trigger:
branches:
include:
- main
- develop
paths:
include:
- infrastructure/*
variables:
- group: azure-credentials
- name: environment
${{ if eq(variables['Build.SourceBranch'], 'refs/heads/main') }}:
value: 'prod'
${{ else }}:
value: 'dev'
stages:
- stage: Validate
jobs:
- job: ValidateBicep
pool:
vmImage: 'ubuntu-latest'
steps:
- task: AzureCLI@2
displayName: 'Validate Bicep templates'
inputs:
azureSubscription: 'Azure Service Connection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
# Bicep のビルドと検証
az bicep build --file infrastructure/main.bicep
# What-if 実行
az deployment sub what-if \
--location japaneast \
--template-file infrastructure/main.bicep \
--parameters infrastructure/environments/$(environment).parameters.json
- stage: Deploy
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployInfrastructure
pool:
vmImage: 'ubuntu-latest'
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- task: AzureCLI@2
displayName: 'Deploy Bicep templates'
inputs:
azureSubscription: 'Azure Service Connection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
# デプロイ実行
az deployment sub create \
--location japaneast \
--template-file infrastructure/main.bicep \
--parameters infrastructure/environments/$(environment).parameters.json \
--name "deploy-$(Build.BuildId)"
2. GitHub Actions
# .github/workflows/deploy-infrastructure.yml
name: Deploy Infrastructure
on:
push:
branches: [ main, develop ]
paths:
- 'infrastructure/**'
pull_request:
branches: [ main ]
paths:
- 'infrastructure/**'
env:
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Validate Bicep
run: |
az bicep build --file infrastructure/main.bicep
- name: Run What-If
run: |
ENVIRONMENT=${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }}
az deployment sub what-if \
--location japaneast \
--template-file infrastructure/main.bicep \
--parameters infrastructure/environments/${ENVIRONMENT}.parameters.json
deploy:
needs: validate
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v3
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Deploy Infrastructure
run: |
az deployment sub create \
--location japaneast \
--template-file infrastructure/main.bicep \
--parameters infrastructure/environments/prod.parameters.json \
--name "deploy-${{ github.run_id }}"
高度な Bicep パターン
1. 条件付きデプロイとループ
// 条件付きリソース
param deployRedis bool = true
param deployCosmosDB bool = false
resource redis 'Microsoft.Cache/redis@2021-06-01' = if (deployRedis) {
name: 'redis-${uniqueString(resourceGroup().id)}'
location: resourceGroup().location
properties: {
sku: {
name: 'Standard'
family: 'C'
capacity: 1
}
}
}
// 配列からのリソース作成
param webApps array = [
{
name: 'api'
runtime: 'DOTNET|6.0'
}
{
name: 'web'
runtime: 'NODE|18-lts'
}
]
resource appServices 'Microsoft.Web/sites@2021-02-01' = [for app in webApps: {
name: 'app-${app.name}-${uniqueString(resourceGroup().id)}'
location: resourceGroup().location
properties: {
serverFarmId: appServicePlan.id
siteConfig: {
linuxFxVersion: app.runtime
}
}
}]
// 既存リソースの参照
resource existingKeyVault 'Microsoft.KeyVault/vaults@2021-06-01-preview' existing = {
name: 'kv-shared-prod'
scope: resourceGroup('rg-shared-prod')
}
// シークレットの取得と使用
module sqlServer './modules/data/sql.bicep' = {
name: 'sql'
params: {
administratorLogin: 'sqladmin'
administratorPassword: existingKeyVault.getSecret('sql-admin-password')
}
}
2. User Defined Types (プレビュー機能)
// types.bicep
@export()
type environmentConfigType = {
name: ('dev' | 'staging' | 'prod')
settings: {
sku: string
capacity: int
enableHA: bool
}
}
@export()
type networkConfigType = {
vnet: {
addressSpace: string
subnets: {
name: string
addressPrefix: string
delegations: string[]?
}[]
}
}
// main.bicep
import * as types from './types.bicep'
param environmentConfig types.environmentConfigType
param networkConfig types.networkConfigType
トラブルシューティングとベストプラクティス
1. デバッグテクニック
// デバッグ用の出力
output debugInfo object = {
resourceGroupId: resourceGroup().id
deploymentName: deployment().name
parametersUsed: {
environment: environment
location: location
}
}
// 検証用のアサーション
resource assertValidConfig 'Microsoft.Resources/deploymentScripts@2020-10-01' = {
name: 'validate-config'
location: location
kind: 'AzurePowerShell'
properties: {
azPowerShellVersion: '7.0'
scriptContent: '''
$config = $DeploymentScriptOutputs
if ($config.environment -eq 'prod' -and $config.replicaCount -lt 3) {
throw "Production environment requires at least 3 replicas"
}
'''
cleanupPreference: 'OnSuccess'
retentionInterval: 'P1D'
}
}
2. セキュリティベストプラクティス
// セキュアなパラメータ処理
@secure()
param databasePassword string
// Key Vault 統合
resource keyVault 'Microsoft.KeyVault/vaults@2021-06-01-preview' = {
name: keyVaultName
location: location
properties: {
enableRbacAuthorization: true
enableSoftDelete: true
enablePurgeProtection: true
softDeleteRetentionInDays: 90
tenantId: subscription().tenantId
sku: {
family: 'A'
name: 'standard'
}
networkAcls: {
defaultAction: 'Deny'
bypass: 'AzureServices'
ipRules: [
{
value: '203.0.113.0/24'
}
]
}
}
}
// シークレットの保存
resource secret 'Microsoft.KeyVault/vaults/secrets@2021-06-01-preview' = {
parent: keyVault
name: 'database-connection-string'
properties: {
value: 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Database=${databaseName};User ID=${adminLogin};Password=${databasePassword};'
}
}
まとめ
Azure Bicep は、Infrastructure as Code を実践する上で強力なツールです。主な利点:
- 読みやすさ: JSON より簡潔で理解しやすい構文
- 型安全性: コンパイル時の型チェック
- モジュール性: 再利用可能なコンポーネント
- 統合性: Azure エコシステムとの完全な統合
エンハンスド株式会社では、Bicep を活用した Azure インフラストラクチャの設計・構築・運用を支援しています。IaC の導入により、インフラ管理の効率化と品質向上を実現します。