1. はじめに
ご無沙汰しております。
普段の専門はアプリ~ミドル領域ですが、最近業務で 「Azure × IaC」 について触れることがあったため本記事を執筆するに至りました。
少し私見も含んでいるのですが、ご容赦ください。
近年、ソフトウェア開発の現場では、「Infrastructure as Code (IaC)」というキーワードが急速に広がっています。
DevOps が浸透し CI/CD との相性が良いことも広がりを後押ししているのではないでしょうか。
一昔前までは、サーバーやネットワークといったインフラストラクチャは専門のエンジニアが手作業で設定し、管理していました。
しかし現在ではそのような複雑な手続きをコードで自動化することにより、開発フローやシステムの運用を効率化しようというプロジェクトが増えているように感じます。
IaC とは?
Infrastructure as Code (IaC)とは、一言で言うとインフラ構成をコードで管理するということです。
サーバーやネットワークの設定、ストレージの作成など従来手作業で行われていた作業をコードに落とし込むことで、以下のようなメリットを享受することができます。
- 手作業に起因するミスの防止
- 設定内容の共有・再現性の確保
- バージョン管理
さらに一度書いたコードは再利用が可能であるため、同じ設定を何度も手作業で行う必要が無くなり効率化につながります。嬉しいことがたくさんありますね。
Azure における IaC の必要性
Azure は Microsoft が提供するクラウドサービスですが、その規模と全機能を手動で管理することは困難であり、効率的かつ確実に運用していくためには IaC が必須です。
また Azure は頻繁に新しいサービスや機能が追加され、それに伴い設定内容も随時変更していく必要があります。
そこで、IaC を利用することでこれらの新しい変更を素早く反映させることが可能です。
Azure では特に「ARM Template」、「Bicep」、「Terraform」、「Pulumi」の 4 つが主な IaC ツールとして活用されています。
各ツールには特徴と利用ケースがあり、最適なツールを選択することが求められます。
この記事では私が実際に使ってみた上で、各ツールの特徴と具体的な使用方法を述べていきます。Azure を活用して IaC を進めたいと考えている方々の一助となれば幸いです。
今回作成するリソースと各ツールのバージョン
今回作成するリソースは以下となります。(リソースグループはすでに存在している(m2-sakai-rg
)ことを前提としております。リージョンは Japan East
です。)
リソース | 名前 | 詳細(特記事項のみ) | |
---|---|---|---|
1 | ストレージアカウント | m2sakaistorageaccount |
|
2 | App Service Plan | m2-sakai-asp |
|
3 | Function App | m2-sakai-functionapp |
また、各ツールのバージョン情報は下記になります。
ツール | バージョン | 確認方法 | 備考 |
---|---|---|---|
Azure CLI | 2.58.0 | az version |
|
bicep CLI | 0.26.54 | az bicep version |
|
terraform | v1.7.5 | terraform version |
|
pulumi | v3.111.1 | pulumi version |
|
node | v20.9.0 | node -v |
Pulumi を Typescript で扱うため |
今回実装したソースコードについては以下のリポジトリで公開しておりますので、ご興味ある方がご参照いただけると幸いです。
それでは各ツールの紹介に移りますが、読む時間のない方やてっとり早く比較表を参照したい方は 6. 各ツールの比較に飛んでいただければと思います。
2. ARM Template
ARM Template の概要
Azure で IaC 化を進める際に、最も親しまれているのはこの「ARM Template」ではないでしょうか。
Azure Resource Manager (ARM) Template は、Azure の独自の IaC ツールであり、JSON(JavaScript Object Notation)形式のテンプレートを通じて Azure リソースのプロビジョニングを自動化します。
ARM Template は Azure のサービスに深く統合されており、Azure 環境内の任意のリソースを作成、構成、または削除することが可能です。
ARM Template の特徴
ARM Template は、以下のような特徴を持っています。
- ネイティブ IaC ツール:Azure のネイティブ IaC ツールであり、Azure の全リソースに対応しています。
- 新機能の迅速なサポート:新しくリリースされた Azure サービスや機能がすぐにサポートされます。
- JSON 形式での扱いやすさ:JSON 形式であるため、プログラミングの経験があれば容易に扱うことができ、VS Code などのエディタ上での編集が最適化されています。
ARM Template による IaC のメリット/デメリット
メリット
- Azure のすべてのリソースをサポートしている
- 新機能がすぐにサポートされる
- 公式のテンプレートライブラリが豊富である
デメリット
- JSON 形式のため記述量が多くなり、コードの可読性に問題がある場合がある
- テストやデバッグが難しくテストフレームワークがない
- 他のクラウドサービスとの互換性がない(Azure 専用)
ARM Template の実装方法
それでは実際に ARM Template を用いてリソースを作成していきます。
ARM Template を作成する際、一から自身で作り始めてももちろん構いません。
ですが、Azure のリソースには様々な機能があります。そのため、すべてのパラメータを理解して設定することは難しいと考えています(もちろんそうなった方が良いのですが)。
そのため、今回はまず Azure Portal でリソースを作成し、そこから以下のように「オートメーション > テンプレートのエクスポート」を実行し、ARM Template を出力する方法を取っております。
以下に作成した ARM Template のファイルを示します。
デメリットにも記載しましたが、やはり JSON 形式というだけあって記述量がかなり多いですね。。。
App Service Plan のような他のリソースに依存しないようなリソースは記述量が減りますが、それでも多いように感じます。
----------------------------------------
ソースコードを表示(ストレージアカウント)
------------------------------------------
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"param_storageAccount_name": {
"type": "String"
}
},
"variables": {},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "[parameters('param_storageAccount_name')]",
"location": "japaneast",
"sku": {
"name": "Standard_LRS",
"tier": "Standard"
},
"kind": "StorageV2",
"properties": {
"dnsEndpointType": "Standard",
"defaultToOAuthAuthentication": false,
"publicNetworkAccess": "Enabled",
"allowCrossTenantReplication": false,
"minimumTlsVersion": "TLS1_2",
"allowBlobPublicAccess": false,
"allowSharedKeyAccess": true,
"networkAcls": {
"bypass": "AzureServices",
"virtualNetworkRules": [],
"ipRules": [],
"defaultAction": "Allow"
},
"supportsHttpsTrafficOnly": true,
"encryption": {
"requireInfrastructureEncryption": false,
"services": {
"file": {
"keyType": "Account",
"enabled": true
},
"blob": {
"keyType": "Account",
"enabled": true
}
},
"keySource": "Microsoft.Storage"
},
"accessTier": "Hot"
}
},
{
"type": "Microsoft.Storage/storageAccounts/blobServices",
"apiVersion": "2023-01-01",
"name": "[concat(parameters('param_storageAccount_name'), '/default')]",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', parameters('param_storageAccount_name'))]"
],
"sku": {
"name": "Standard_LRS",
"tier": "Standard"
},
"properties": {
"containerDeleteRetentionPolicy": {
"enabled": true,
"days": 7
},
"cors": {
"corsRules": []
},
"deleteRetentionPolicy": {
"allowPermanentDelete": false,
"enabled": true,
"days": 7
}
}
},
{
"type": "Microsoft.Storage/storageAccounts/fileServices",
"apiVersion": "2023-01-01",
"name": "[concat(parameters('param_storageAccount_name'), '/default')]",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', parameters('param_storageAccount_name'))]"
],
"sku": {
"name": "Standard_LRS",
"tier": "Standard"
},
"properties": {
"protocolSettings": {
"smb": {}
},
"cors": {
"corsRules": []
},
"shareDeleteRetentionPolicy": {
"enabled": true,
"days": 7
}
}
},
{
"type": "Microsoft.Storage/storageAccounts/queueServices",
"apiVersion": "2023-01-01",
"name": "[concat(parameters('param_storageAccount_name'), '/default')]",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', parameters('param_storageAccount_name'))]"
],
"properties": {
"cors": {
"corsRules": []
}
}
},
{
"type": "Microsoft.Storage/storageAccounts/tableServices",
"apiVersion": "2023-01-01",
"name": "[concat(parameters('param_storageAccount_name'), '/default')]",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', parameters('param_storageAccount_name'))]"
],
"properties": {
"cors": {
"corsRules": []
}
}
}
]
}
----------------------------------------
ソースコードを表示(App Service Plan)
------------------------------------------
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"param_asp_name": {
"type": "String"
}
},
"variables": {},
"resources": [
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2023-01-01",
"name": "[parameters('param_asp_name')]",
"location": "Japan East",
"sku": {
"name": "B1",
"tier": "Basic",
"size": "B1",
"family": "B",
"capacity": 1
},
"kind": "app",
"properties": {
"perSiteScaling": false,
"elasticScaleEnabled": false,
"maximumElasticWorkerCount": 1,
"isSpot": false,
"reserved": false,
"isXenon": false,
"hyperV": false,
"targetWorkerCount": 0,
"targetWorkerSizeId": 0,
"zoneRedundant": false
}
}
]
}
----------------------------------------
ソースコードを表示(Function App)
------------------------------------------
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"param_functions_name": {
"type": "String"
},
"param_subscription_id": {
"type": "String"
},
"param_resource_group_name": {
"type": "String"
},
"param_asp_name": {
"type": "String"
}
},
"variables": {},
"resources": [
{
"type": "Microsoft.Web/sites",
"apiVersion": "2023-01-01",
"name": "[parameters('param_functions_name')]",
"location": "Japan East",
"kind": "functionapp",
"properties": {
"enabled": true,
"hostNameSslStates": [
{
"name": "[concat(parameters('param_functions_name'), '.azurewebsites.net')]",
"sslState": "Disabled",
"hostType": "Standard"
},
{
"name": "[concat(parameters('param_functions_name'), '.scm.azurewebsites.net')]",
"sslState": "Disabled",
"hostType": "Repository"
}
],
"serverFarmId": "[concat('/subscriptions/', parameters('param_subscription_id'), '/resourceGroups/', parameters('param_resource_group_name'), '/providers/Microsoft.Web/serverfarms/', parameters('param_asp_name'))]",
"reserved": false,
"isXenon": false,
"hyperV": false,
"vnetRouteAllEnabled": false,
"vnetImagePullEnabled": false,
"vnetContentShareEnabled": false,
"siteConfig": {
"numberOfWorkers": 1,
"acrUseManagedIdentityCreds": false,
"alwaysOn": true,
"http20Enabled": false,
"functionAppScaleLimit": 0,
"minimumElasticInstanceCount": 0
},
"scmSiteAlsoStopped": false,
"clientAffinityEnabled": false,
"clientCertEnabled": false,
"clientCertMode": "Required",
"hostNamesDisabled": false,
"customDomainVerificationId": "D70E92245E299D40808AD89BD4B0569E4666FC7468C3E84AA97A929A865000D0",
"containerSize": 1536,
"dailyMemoryTimeQuota": 0,
"httpsOnly": true,
"redundancyMode": "None",
"publicNetworkAccess": "Enabled",
"storageAccountRequired": false,
"keyVaultReferenceIdentity": "SystemAssigned"
}
},
{
"type": "Microsoft.Web/sites/basicPublishingCredentialsPolicies",
"apiVersion": "2023-01-01",
"name": "[concat(parameters('param_functions_name'), '/ftp')]",
"location": "Japan East",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('param_functions_name'))]"
],
"properties": {
"allow": false
}
},
{
"type": "Microsoft.Web/sites/basicPublishingCredentialsPolicies",
"apiVersion": "2023-01-01",
"name": "[concat(parameters('param_functions_name'), '/scm')]",
"location": "Japan East",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('param_functions_name'))]"
],
"properties": {
"allow": false
}
},
{
"type": "Microsoft.Web/sites/config",
"apiVersion": "2023-01-01",
"name": "[concat(parameters('param_functions_name'), '/web')]",
"location": "Japan East",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('param_functions_name'))]"
],
"properties": {
"numberOfWorkers": 1,
"defaultDocuments": [
"Default.htm",
"Default.html",
"Default.asp",
"index.htm",
"index.html",
"iisstart.htm",
"default.aspx",
"index.php"
],
"netFrameworkVersion": "v8.0",
"requestTracingEnabled": false,
"remoteDebuggingEnabled": false,
"httpLoggingEnabled": false,
"acrUseManagedIdentityCreds": false,
"logsDirectorySizeLimit": 35,
"detailedErrorLoggingEnabled": false,
"publishingUsername": "[concat('$', parameters('param_functions_name'))]",
"scmType": "None",
"use32BitWorkerProcess": false,
"webSocketsEnabled": false,
"alwaysOn": true,
"managedPipelineMode": "Integrated",
"virtualApplications": [
{
"virtualPath": "/",
"physicalPath": "site\\wwwroot",
"preloadEnabled": true
}
],
"loadBalancing": "LeastRequests",
"experiments": {
"rampUpRules": []
},
"autoHealEnabled": false,
"vnetRouteAllEnabled": false,
"vnetPrivatePortsCount": 0,
"publicNetworkAccess": "Enabled",
"cors": {
"allowedOrigins": ["https://portal.azure.com"],
"supportCredentials": false
},
"localMySqlEnabled": false,
"ipSecurityRestrictions": [
{
"ipAddress": "Any",
"action": "Allow",
"priority": 2147483647,
"name": "Allow all",
"description": "Allow all access"
}
],
"scmIpSecurityRestrictions": [
{
"ipAddress": "Any",
"action": "Allow",
"priority": 2147483647,
"name": "Allow all",
"description": "Allow all access"
}
],
"scmIpSecurityRestrictionsUseMain": false,
"http20Enabled": false,
"minTlsVersion": "1.2",
"scmMinTlsVersion": "1.2",
"ftpsState": "FtpsOnly",
"preWarmedInstanceCount": 0,
"functionAppScaleLimit": 0,
"functionsRuntimeScaleMonitoringEnabled": false,
"minimumElasticInstanceCount": 0,
"azureStorageAccounts": {}
}
},
{
"type": "Microsoft.Web/sites/hostNameBindings",
"apiVersion": "2023-01-01",
"name": "[concat(parameters('param_functions_name'), '/', parameters('param_functions_name'), '.azurewebsites.net')]",
"location": "Japan East",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('param_functions_name'))]"
],
"properties": {
"siteName": "[parameters('param_functions_name')]",
"hostNameType": "Verified"
}
}
]
}
上記のファイルは、 Azure CLI を用いてデプロイすることができます。
# Azure CLI にログイン
az login
# ARM Template を用いてリソースを作成
az deployment group create --resource-group <リソースグループ名> --template-file <ARM Template ファイル> --parameters <パラメータファイル>
以上で、ARM Template を用いてリソースを作成することができました。
3. Bicep
Bicep の概要
Bicep は、ARM Template の後継として Microsoft が提供している新しい DSL(Domain Specific Language)ベースの IaC ツールです。
ARM Template とは異なり、Bicep は JSON ではなく、独自のより簡潔な構文を提供しており、これにより Azure リソースの管理を容易にしています。
Bicep のコードは ARM Template にコンパイルされるため、同様に ARM REST API を通じて Azure リソースに対する操作を行います。
これにより、ARM Template と同等の Azure リソースのサポートと、新サービスや機能の迅速なサポートを実現しています。
Bicep の特徴
Bicep は、以下のような特徴を持っています。
- ARM Template よりもシンプルで簡潔な構文:Bicep の構文はシンプルで簡潔なため、コードの読みやすさと保守性が向上しています。
- 具体的な型システム:Bicep には具体的な型システムがあり、エラーチェックとインテリセンスが可能です。
- モジュラーと再利用可能:Bicep ではカスタムリソースを定義するためのモジュールを作成し、再利用することができます。
Bicep による IaC のメリット/デメリット
メリット
- ARM Template からコンパイルされるため、ARM Template と同じく Azure の全リソースをサポートしている
- よりシンプルで簡潔な構文により、可読性と保守性が向上する
- モジュールでカスタムリソースを定義し再利用可能となる
デメリット
- 他のクラウドプロバイダーとの互換性がない(Azure 専用)
Bicep の実装方法
それでは ARM Template と同じく、Bicep を用いてリソースを作成していきます。
Bicep も ARM Template 同様、一から作成しても問題はございません(その方が勉強にはなるかもしれません)が、同じく設定値を全て把握するのは難しいため別の方法でファイルを作成します。
Bicep ファイルはデプロイされる際に、内部で ARM Template ファイルにコンパイルされます。そのため、ARM Template を Bicep は相互に変換可能となっております。
従って、ますは先程作成した ARM Template を Bicep ファイルにデコンパイルします。
az bicep decompile --file <ARM Template ファイル>
こちらを実行して作成された Bicep が以下となります。
確かに ARM Template に比べると多少は読みやすくなったと感じます。
----------------------------------------
ソースコードを表示(ストレージアカウント)
------------------------------------------
param param_storageAccount_name string
resource param_storageAccount_name_resource 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: param_storageAccount_name
location: 'japaneast'
sku: {
name: 'Standard_LRS'
tier: 'Standard'
}
kind: 'StorageV2'
properties: {
dnsEndpointType: 'Standard'
defaultToOAuthAuthentication: false
publicNetworkAccess: 'Enabled'
allowCrossTenantReplication: false
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
allowSharedKeyAccess: true
networkAcls: {
bypass: 'AzureServices'
virtualNetworkRules: []
ipRules: []
defaultAction: 'Allow'
}
supportsHttpsTrafficOnly: true
encryption: {
requireInfrastructureEncryption: false
services: {
file: {
keyType: 'Account'
enabled: true
}
blob: {
keyType: 'Account'
enabled: true
}
}
keySource: 'Microsoft.Storage'
}
accessTier: 'Hot'
}
}
resource param_storageAccount_name_default 'Microsoft.Storage/storageAccounts/blobServices@2023-01-01' = {
parent: param_storageAccount_name_resource
name: 'default'
sku: {
name: 'Standard_LRS'
tier: 'Standard'
}
properties: {
containerDeleteRetentionPolicy: {
enabled: true
days: 7
}
cors: {
corsRules: []
}
deleteRetentionPolicy: {
allowPermanentDelete: false
enabled: true
days: 7
}
}
}
resource Microsoft_Storage_storageAccounts_fileServices_param_storageAccount_name_default 'Microsoft.Storage/storageAccounts/fileServices@2023-01-01' = {
parent: param_storageAccount_name_resource
name: 'default'
sku: {
name: 'Standard_LRS'
tier: 'Standard'
}
properties: {
protocolSettings: {
smb: {}
}
cors: {
corsRules: []
}
shareDeleteRetentionPolicy: {
enabled: true
days: 7
}
}
}
resource Microsoft_Storage_storageAccounts_queueServices_param_storageAccount_name_default 'Microsoft.Storage/storageAccounts/queueServices@2023-01-01' = {
parent: param_storageAccount_name_resource
name: 'default'
properties: {
cors: {
corsRules: []
}
}
}
resource Microsoft_Storage_storageAccounts_tableServices_param_storageAccount_name_default 'Microsoft.Storage/storageAccounts/tableServices@2023-01-01' = {
parent: param_storageAccount_name_resource
name: 'default'
properties: {
cors: {
corsRules: []
}
}
}
----------------------------------------
ソースコードを表示(App Service Plan)
------------------------------------------
param param_asp_name string
resource param_asp_name_resource 'Microsoft.Web/serverfarms@2023-01-01' = {
name: param_asp_name
location: 'Japan East'
sku: {
name: 'B1'
tier: 'Basic'
size: 'B1'
family: 'B'
capacity: 1
}
kind: 'app'
properties: {
perSiteScaling: false
elasticScaleEnabled: false
maximumElasticWorkerCount: 1
isSpot: false
reserved: false
isXenon: false
hyperV: false
targetWorkerCount: 0
targetWorkerSizeId: 0
zoneRedundant: false
}
}
----------------------------------------
ソースコードを表示(Function App)
------------------------------------------
param param_functions_name string
param param_subscription_id string
param param_resource_group_name string
param param_asp_name string
resource param_functions_name_resource 'Microsoft.Web/sites@2023-01-01' = {
name: param_functions_name
location: 'Japan East'
kind: 'functionapp'
properties: {
enabled: true
hostNameSslStates: [
{
name: '${param_functions_name}.azurewebsites.net'
sslState: 'Disabled'
hostType: 'Standard'
}
{
name: '${param_functions_name}.scm.azurewebsites.net'
sslState: 'Disabled'
hostType: 'Repository'
}
]
serverFarmId: '/subscriptions/${param_subscription_id}/resourceGroups/${param_resource_group_name}/providers/Microsoft.Web/serverfarms/${param_asp_name}'
reserved: false
isXenon: false
hyperV: false
vnetRouteAllEnabled: false
vnetImagePullEnabled: false
vnetContentShareEnabled: false
siteConfig: {
numberOfWorkers: 1
acrUseManagedIdentityCreds: false
alwaysOn: true
http20Enabled: false
functionAppScaleLimit: 0
minimumElasticInstanceCount: 0
}
scmSiteAlsoStopped: false
clientAffinityEnabled: false
clientCertEnabled: false
clientCertMode: 'Required'
hostNamesDisabled: false
customDomainVerificationId: 'D70E92245E299D40808AD89BD4B0569E4666FC7468C3E84AA97A929A865000D0'
containerSize: 1536
dailyMemoryTimeQuota: 0
httpsOnly: true
redundancyMode: 'None'
publicNetworkAccess: 'Enabled'
storageAccountRequired: false
keyVaultReferenceIdentity: 'SystemAssigned'
}
}
resource param_functions_name_ftp 'Microsoft.Web/sites/basicPublishingCredentialsPolicies@2023-01-01' = {
parent: param_functions_name_resource
name: 'ftp'
location: 'Japan East'
properties: {
allow: false
}
}
resource param_functions_name_scm 'Microsoft.Web/sites/basicPublishingCredentialsPolicies@2023-01-01' = {
parent: param_functions_name_resource
name: 'scm'
location: 'Japan East'
properties: {
allow: false
}
}
resource param_functions_name_web 'Microsoft.Web/sites/config@2023-01-01' = {
parent: param_functions_name_resource
name: 'web'
location: 'Japan East'
properties: {
numberOfWorkers: 1
defaultDocuments: [
'Default.htm'
'Default.html'
'Default.asp'
'index.htm'
'index.html'
'iisstart.htm'
'default.aspx'
'index.php'
]
netFrameworkVersion: 'v8.0'
requestTracingEnabled: false
remoteDebuggingEnabled: false
httpLoggingEnabled: false
acrUseManagedIdentityCreds: false
logsDirectorySizeLimit: 35
detailedErrorLoggingEnabled: false
publishingUsername: '$${param_functions_name}'
scmType: 'None'
use32BitWorkerProcess: false
webSocketsEnabled: false
alwaysOn: true
managedPipelineMode: 'Integrated'
virtualApplications: [
{
virtualPath: '/'
physicalPath: 'site\\wwwroot'
preloadEnabled: true
}
]
loadBalancing: 'LeastRequests'
experiments: {
rampUpRules: []
}
autoHealEnabled: false
vnetRouteAllEnabled: false
vnetPrivatePortsCount: 0
publicNetworkAccess: 'Enabled'
cors: {
allowedOrigins: [
'https://portal.azure.com'
]
supportCredentials: false
}
localMySqlEnabled: false
ipSecurityRestrictions: [
{
ipAddress: 'Any'
action: 'Allow'
priority: 2147483647
name: 'Allow all'
description: 'Allow all access'
}
]
scmIpSecurityRestrictions: [
{
ipAddress: 'Any'
action: 'Allow'
priority: 2147483647
name: 'Allow all'
description: 'Allow all access'
}
]
scmIpSecurityRestrictionsUseMain: false
http20Enabled: false
minTlsVersion: '1.2'
scmMinTlsVersion: '1.2'
ftpsState: 'FtpsOnly'
preWarmedInstanceCount: 0
functionAppScaleLimit: 0
functionsRuntimeScaleMonitoringEnabled: false
minimumElasticInstanceCount: 0
azureStorageAccounts: {}
}
}
resource param_functions_name_param_functions_name_azurewebsites_net 'Microsoft.Web/sites/hostNameBindings@2023-01-01' = {
parent: param_functions_name_resource
name: '${param_functions_name}.azurewebsites.net'
location: 'Japan East'
properties: {
siteName: param_functions_name
hostNameType: 'Verified'
}
}
上記のファイルは、 Azure CLI を用いてデプロイすることができます。
# Azure CLI にログイン
az login
# Bicep を用いてリソースを作成
az deployment group create --resource-group <リソースグループ名> --template-file <Bicep ファイル> --parameters <パラメータファイル>
以上で、Bicep を用いてリソースを作成することができました。
4. Terraform
Terraform の概要
Terraform は HashiCorp が開発した OSS の IaC ツールで、クラウドリソースのプロビジョニングと管理を自動化します。
Terraform はプロバイダーと呼ばれるプラグインを通じて様々なクラウドサービスに対応しており、Azure だけでなく AWS や Google Cloud Platform など、多くのクラウドプロバイダーのリソースを管理することができます。
Terraform の特徴
Terraform の主な特徴は以下のとおりです。
- 様々なクラウドに対応:AWS、Azure、GCP、その他のクラウドプロバイダーをサポートしており、同じ言語で異なる環境のコードを記述することができます。
- 独自の言語、HCL(HashiCorp Configuration Language):独自のプログラミング言語で、JSON との互換性と人間が読みやすい構文を提供します。
- Plan と Apply ステップ:
terraform plan
コマンドで変更の予測と確認を行い、terraform apply
コマンドでそれを適用するフローを持ちます。
Terraform による IaC のメリット/デメリット
メリット
- 主要なクラウドプロバイダーすべてをサポートしている
- 独自の言語 HCL を使用し、開発者が読みやすい構文である
- 記述的な構文で、実装したい具体的なインフラ構造を直接記述できる
- インフラの変更予測機能があり、変更を適用する前に影響を確認できる
デメリット
- 独自の言語となるため、学習コストが比較的高い
- デバッグが難しい場合がある
- 最新のクラウドサービスの更新に追従するのが遅い場合がある
Terraform の実装方法
それでは Terraform を用いてリソースを作成していきます。以下の MS Learn を参考にしながら作成していきます。
Terraform は(後述する Pulumi も)Azure ネイティブではないため、基本的にはドキュメントを読みながら設定値を記述していくことになります。
基本的には Azure Portal で作成するような項目を設定値に記載すれば同じようなリソースが出来上がります。
以下が出来上がった main.tf
です。変数ファイル等は省略させていただいてます。
こちらのファイルにストレージアカウント、App Service Plan、Function App のすべてのリソースが含まれています。
私自身、Terraform を触るのは初めてだったのですが、独自の書き方だからと言って書き方で戸惑うところはありませんでした。
ARM Template、Bicep は Azure ネイティブという強みはありますが、そこにこだわる必要がなければ選択肢に十分入るであろうと感じました。
resource "azurerm_storage_account" "storage_account" {
name = "${var.param_storageAccount_name}"
resource_group_name = "${var.param_resource_group_name}"
location = "${var.param_location}"
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_service_plan" "app_service_plan" {
name = "${var.param_asp_name}"
resource_group_name = "${var.param_resource_group_name}"
location = "${var.param_location}"
os_type = "Windows"
sku_name = "B1"
}
resource "azurerm_windows_function_app" "function_app" {
name = "${var.param_functions_name}"
resource_group_name = "${var.param_resource_group_name}"
location = "${var.param_location}"
storage_account_name = azurerm_storage_account.storage_account.name
storage_account_access_key = azurerm_storage_account.storage_account.primary_access_key
service_plan_id = azurerm_service_plan.app_service_plan.id
site_config {}
}
上記のファイルは、 以下のコマンドを用いてデプロイすることができます。
# Azure CLI にログイン
az login
# Terraform を初期化
terraform init -upgrade
# Terraform 実行プランの作成
terraform plan -out main.tfplan
# Terraform 実行プランを適用
terraform apply main.tfplan
以上で Terraform を用いてリソースを作成することができました。
5. Pulumi
Pulumi の概要
Pulumi は Terraform と同じくクラウドインフラストラクチャーを作成、配置、および管理するための IaC ツール OSS です。
Pulumi は、通常のプログラミング言語(JavaScript、TypeScript、Python、Go、.NET など)を使用してクラウドインフラストラクチャを定義します。これにより、開発者は既に知っている言語を利用して IaC を実装できます。
Pulumi は、プラグインシステムを介して様々なクラウドプロバイダに対応しています。Azure、AWS、GCP、その他多くのクラウド、サービスプロバイダのリソースを管理することが可能です。
Pulumi の特徴
Pulumi の主な特徴は以下のとおりです。
- 一般的なプログラミング言語の使用:Pulumi は、JavaScript、TypeScript、Python、Go、.NET などの普通のプログラミング言語を使用してインフラをコード化します。これにより、制御フロー、エラーハンドリング、パッケージ管理、テスト、その他の言語機能を活用できます。
- 様々なクラウドに対応:Pulumi はあらゆるクラウドプロバイダをサポートします。一つのプログラムでマルチクラウド構成を表現したり、一つのクラウドプロバイダから別のクラウドプロバイダに移行するといったシナリオも実現可能です。
Pulumi による IaC のメリット/デメリット
メリット
- 一般的なプログラミング言語が利用できるため、既存の知識やツールを用いて利用が可能となる
- クラウドプロバイダに依存せず、どのクラウドでもリソースも同じ言語でコード化できる
- npm や Python Packages など、既存のパッケージ管理システムを使ってコードの再利用が可能となる
デメリット
- 既存の Terraform などの IaC ツールに比べてコミュニティやエコシステムが小さい
- プログラミング経験がない人にとっては学習コストが高い
- 無料枠を超えるとプロジェクトごとに料金が発生する
Pulumi の実装方法
それでは Pulumi を用いてリソースをデプロイしていきます。
Pulumi は一般的なプログラミング言語を使用しますが、今回は TypeScript を使用します。
そのため、まずは Pulumi のプロジェクトを作成します。ここでは対話形式で実行される設定の詳細は割愛します。
pulumi new azure-typescript
それでは、実際のリソースを作成するコードを実装します。まずはストレージアカウントを作成するコードです。
Pulumi で Azure を扱う際は Azure Native を使用することが推奨されておりますが、今回は Terraform との違いを正しく把握するため、Azure Classicを用いてリソースを定義しております。
import * as pulumi from '@pulumi/pulumi';
import * as azure from '@pulumi/azure';
import { Account } from '@pulumi/azure/storage';
export const createStorageAccount = async (
resourceGroupName: string = 'm2-sakai-rg',
storageAccountName: string = 'm2sakaistorageaccount'
): Promise<Account> => {
const config = new pulumi.Config();
const location = config.get('location') || 'Japan East';
const storageAccount = new azure.storage.Account(storageAccountName, {
name: storageAccountName,
resourceGroupName: resourceGroupName,
location: location,
accountTier: 'Standard',
accountReplicationType: 'LRS',
});
return storageAccount;
};
続いて、App Service Plan を作成するコードです。
import * as pulumi from '@pulumi/pulumi';
import * as azure from '@pulumi/azure';
import { ServicePlan } from '@pulumi/azure/appservice/servicePlan';
export const createAppServicePlan = async (
resourceGroupName: string = 'm2-sakai-rg',
appServicePlanName: string = 'm2-sakai-asp'
): Promise<ServicePlan> => {
const config = new pulumi.Config();
const location = config.get('location') || 'Japan East';
const appServicePlan = new azure.appservice.ServicePlan(appServicePlanName, {
osType: 'Windows',
resourceGroupName: resourceGroupName,
skuName: 'B1',
location: location,
name: appServicePlanName,
perSiteScalingEnabled: false,
zoneBalancingEnabled: false,
});
return appServicePlan;
};
続いて、Function App を作成するコードです。
import * as pulumi from '@pulumi/pulumi';
import * as azure from '@pulumi/azure';
import { WindowsFunctionApp } from '@pulumi/azure/appservice';
export const createFunctions = async (
resourceGroupName: string = 'm2-sakai-rg',
functionsName: string = 'm2-sakai-functionapp',
appServicePlanId: pulumi.Output<string>,
storageAccountName: pulumi.Output<string>,
storageAccountPrimaryAccessKey: pulumi.Output<string>
): Promise<WindowsFunctionApp> => {
const config = new pulumi.Config();
const location = config.get('location') || 'Japan East';
const functionapp = new azure.appservice.WindowsFunctionApp(functionsName, {
name: functionsName,
resourceGroupName: resourceGroupName,
location: location,
storageAccountName: storageAccountName,
storageAccountAccessKey: storageAccountPrimaryAccessKey,
servicePlanId: appServicePlanId,
siteConfig: {},
});
return functionapp;
};
最後にこれらの関数を一度に呼び出す index.ts
を作成すれば完成です。
import * as pulumi from '@pulumi/pulumi';
import { createStorageAccount } from './createStorageAccount';
import { createAppServicePlan } from './createAppServicePlan';
import { createFunctions } from './createFunctions';
export const main = async () => {
const config = new pulumi.Config();
// 1. Create Storage Account
const storageAccount = await createStorageAccount(
config.get('rgName'),
config.get('storageAccountName')
);
// 2. Create App Service Plan
const appServicePlan = await createAppServicePlan(
config.get('rgName'),
config.get('aspName')
);
// 3. Create Functions
const functions = await createFunctions(
config.get('rgName'),
config.get('functionsName'),
appServicePlan.id,
storageAccount.name,
storageAccount.primaryAccessKey
);
};
main();
それでは実行してリソースを作成します。
# Azure CLI にログイン
az login
# 実行
pulumi up
以上で、Pulumi を用いてリソースを作成することができました。
普段からアプリ開発をしている身としては同じ言語で書ける Pulumi の開発体験は非常に良いと思いました。そして何と言っても型安全で開発できるのは正義ですね。
ただ、同時に普段からプログラミング言語を触っていない方が大半の場合は採用するのに勇気がいりますね。。。
6. 各ツールの比較
以下に上記で記載したツールを比較します。
ARM Template | Bicep | Terraform | Pulumi | |
---|---|---|---|---|
メリット |
|
|
|
|
デメリット |
|
|
|
|
主に利用されるケース |
|
|
|
|
難易度 |
|
|
|
|
使い分けのポイント
ARM Template | Bicep | Terraform | Pulumi | |
---|---|---|---|---|
対象となるクラウドプロバイダー | Azure 専用 | Azure 専用 | マルチクラウド対応 | マルチクラウド対応 |
サポートするリソースや機能 | Azure の全リソース | Azure の全リソース | 様々なリソースやプロバイダ | 様々なリソースやプロバイダ |
学習コスト | JSON 理解者向け | シンプルな新言語に対する意欲がある人向け | 独自言語(HCL)に対する意欲がある人向け | 既存のプログラミング言語を活用したい人向け |
既存のスキルセットや言語の好み | JSON が得意な人向け | Azure に特化して独自言語を学びたい人向け | 独自言語 (HCL) が好きな人向け | 既存のプログラミング言語を活用したい人向け |
エコシステムやコミュニティの大きさ | Azure コミュニティに集中したい人向け | Azure コミュニティに集中したい人向け | 豊富なエコシステムと大きなコミュニティ | クラウドに依らないコミュニティを求める人向け |
これらの要素を総合的に判断し、自身の目的や要件に最適な IaC ツールを選択することが重要かなと思います。
7. まとめ
本記事で紹介した ARM Template、Bicep、Terraform、そして Pulumi は、全て IaC(Infrastructure as Code)を実現するツール群です。
これらのツールは、各々特性やメリット、デメリットを持ち、用途や状況によって使い分けられます。
ARM Template と Bicep は Azure 専用で、Azure ネイティブの機能を活用できる一方で、他のクラウドプロバイダに対応できないという制約があります。
一方、Terraform と Pulumi はクラウドプロバイダに依存せず、マルチクラウドの環境でも使うことができます。
具体的な使い分けとしては、対象となるクラウドプロバイダ、サポートしてほしいリソースや機能、学習コストの承受範囲、既存のスキルセットやプログラミング言語の好み、そして求めるエコシステムやコミュニティの大きさ等のポイントを考慮する必要があります。
いずれのツールも一長一短であり、それぞれのニーズに最適なツールを選ぶことが求められます。
本記事が皆様のインフラ管理に役立つ情報を提供できていれば幸いです。最後まで読んでいただきありがとうございました。