AzureのARMTemplateでApplicationGatewayの静的Webサイトやってみた
AWSでもGCPでも標準のIaCやってみたのでAzureでも やらなきゃダメでしょということでAzureのIaCであるARMTemplate試してみました。
構成としてはApplicationGateway+BlobStorageので、ついでにDNSもARMTemplateやってみました。SSL証明書はZEROSSLで発行し、ドメインはfreenomで発行しています。
ARMTemplate自体はAWSとかGCPと違ってJSON形式みたいですが、今後はBicepとかいうやつが標準になる様です。ではいってみましょう。
◆作業およびARMテンプレート
1.事前準備
#PowerShellインストール $ mkdir armtemplate $ cd armtemplate $ sudo apt-get update $ sudo apt-get install -y wget apt-transport-https software-properties-common $ wget -q https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb $ sudo dpkg -i packages-microsoft-prod.deb $ sudo apt-get update $ sudo apt-get install -y powershell $ pwsh #テストツールダウンロード PS /home/user/armtemplate> cd armtemplate PS /home/user/armtemplate> git clone https://github.com/Azure/arm-ttk.git
2.DNS
・ARMテンプレート
$ vi dns__template.json
------------------------------------------------
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.5.6.12127",
"templateHash": "17563758552659577113"
}
},
"parameters": {
"zoneName": {
"type": "string",
"defaultValue": "[format('{0}.azurequickstart.org', uniqueString(resourceGroup().id))]",
"metadata": {
"description": "The name of the DNS zone to be created. Must have at least 2 segements, e.g. hostname.org"
}
},
"recordName": {
"type": "string",
"defaultValue": "www",
"metadata": {
"description": "The name of the DNS record to be created. The name is relative to the zone, not the FQDN."
}
},
"cName": {
"type": "string",
"defaultValue": "cname.hoge.com"
}
},
"resources": [
{
"type": "Microsoft.Network/dnsZones",
"apiVersion": "2018-05-01",
"name": "[parameters('zoneName')]",
"location": "global"
},
{
"type": "Microsoft.Network/dnszones/CNAME",
"apiVersion": "2018-05-01",
"name": "[format('{0}/{1}', parameters('zoneName'), parameters('recordName'))]",
"properties": {
"TTL": 60,
"CNAMERecord": {
"cname": "[parameters('cName')]"
}
},
"dependsOn": [
"[resourceId('Microsoft.Network/dnsZones', parameters('zoneName'))]"
]
}
],
"outputs": {
"nameServers": {
"type": "array",
"value": "[reference(resourceId('Microsoft.Network/dnsZones', parameters('zoneName'))).nameServers]"
}
}
}
------------------------------------------------
・ ARMテンプレートを適用(適用前にテストツールで確認)
$ pwsh PS /home/user/armtemplate> Import-Module ./arm-ttk/arm-ttk/arm-ttk.psd1 PS /home/user/armtemplate> Test-AzTemplate -TemplatePath dns_template.json $ az deployment group create \ --name dnstohonokaitk \ --resource-group yourResourceGroup \ --template-file dns_template.json \ --parameters zoneName=yourdomain recordName=_Publishedzerossl.comodoca.com
3.Blob
・ARMテンプレート
$ vi blob_template.json
------------------------------------------------
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.5.6.12127",
"templateHash": "14810353806615330445"
}
},
"parameters": {
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "The location into which the resources should be deployed."
}
},
"storageAccountName": {
"type": "string",
"defaultValue": "[format('stor{0}', uniqueString(resourceGroup().id))]",
"metadata": {
"description": "The name of the storage account to use for site hosting."
}
},
"storageSku": {
"type": "string",
"defaultValue": "Standard_LRS",
"metadata": {
"description": "The storage account sku name."
},
"allowedValues": [
"Standard_LRS",
"Standard_GRS",
"Standard_RAGRS",
"Standard_ZRS",
"Premium_LRS",
"Premium_ZRS",
"Standard_GZRS",
"Standard_RAGZRS"
]
},
"indexDocumentPath": {
"type": "string",
"defaultValue": "index.html",
"metadata": {
"description": "The path to the web index document."
}
},
"indexDocumentContents": {
"type": "string",
"defaultValue": "<h1>Example static website</h1>",
"metadata": {
"description": "The contents of the web index document."
}
},
"errorDocument404Path": {
"type": "string",
"defaultValue": "error.html",
"metadata": {
"description": "The path to the web error document."
}
},
"errorDocument404Contents": {
"type": "string",
"defaultValue": "<h1>Example 404 error page</h1>",
"metadata": {
"description": "The contents of the web error document."
}
}
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-06-01",
"name": "[parameters('storageAccountName')]",
"location": "[parameters('location')]",
"kind": "StorageV2",
"sku": {
"name": "[parameters('storageSku')]"
}
},
{
"type": "Microsoft.ManagedIdentity/userAssignedIdentities",
"apiVersion": "2018-11-30",
"name": "DeploymentScript",
"location": "[parameters('location')]"
},
{
"type": "Microsoft.Authorization/roleAssignments",
"apiVersion": "2020-04-01-preview",
"scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]",
"name": "[guid(resourceGroup().id, resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'DeploymentScript'), subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab'))]",
"properties": {
"roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]",
"principalId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'DeploymentScript')).principalId]",
"principalType": "ServicePrincipal"
},
"dependsOn": [
"[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'DeploymentScript')]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
]
},
{
"type": "Microsoft.Resources/deploymentScripts",
"apiVersion": "2020-10-01",
"name": "deploymentScript",
"location": "[parameters('location')]",
"kind": "AzurePowerShell",
"identity": {
"type": "UserAssigned",
"userAssignedIdentities": {
"[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'DeploymentScript'))]": {}
}
},
"properties": {
"azPowerShellVersion": "3.0",
"scriptContent": "$ErrorActionPreference = 'Stop'\n$storageAccount = Get-AzStorageAccount -ResourceGroupName $env:ResourceGroupName -AccountName $env:StorageAccountName\n\n# Enable the static website feature on the storage account.\n$ctx = $storageAccount.Context\nEnable-AzStorageStaticWebsite -Context $ctx -IndexDocument $env:IndexDocumentPath -ErrorDocument404Path $env:ErrorDocument404Path\n\n# Add the two HTML pages.\n$tempIndexFile = New-TemporaryFile\nSet-Content $tempIndexFile $env:IndexDocumentContents -Force\nSet-AzStorageBlobContent -Context $ctx -Container '$web' -File $tempIndexFile -Blob $env:IndexDocumentPath -Properties @{'ContentType' = 'text/html'} -Force\n\n$tempErrorDocument404File = New-TemporaryFile\nSet-Content $tempErrorDocument404File $env:ErrorDocument404Contents -Force\nSet-AzStorageBlobContent -Context $ctx -Container '$web' -File $tempErrorDocument404File -Blob $env:ErrorDocument404Path -Properties @{'ContentType' = 'text/html'} -Force\n",
"retentionInterval": "PT4H",
"environmentVariables": [
{
"name": "ResourceGroupName",
"value": "[resourceGroup().name]"
},
{
"name": "StorageAccountName",
"value": "[parameters('storageAccountName')]"
},
{
"name": "IndexDocumentPath",
"value": "[parameters('indexDocumentPath')]"
},
{
"name": "IndexDocumentContents",
"value": "[parameters('indexDocumentContents')]"
},
{
"name": "ErrorDocument404Path",
"value": "[parameters('errorDocument404Path')]"
},
{
"name": "ErrorDocument404Contents",
"value": "[parameters('errorDocument404Contents')]"
}
]
},
"dependsOn": [
"[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'DeploymentScript')]",
"[extensionResourceId(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), 'Microsoft.Authorization/roleAssignments', guid(resourceGroup().id, resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'DeploymentScript'), subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')))]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
]
}
],
"outputs": {
"staticWebsiteUrl": {
"type": "string",
"value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))).primaryEndpoints.web]"
}
}
}
------------------------------------------------
・テストコマンド
$ pwsh PS /home/user/armtemplate> Import-Module ./arm-ttk/arm-ttk/arm-ttk.psd1 PS /home/user/armtemplate> Test-AzTemplate -TemplatePath blob_template.json
4.Applicationgateway
・ ARMテンプレート
$ vi agw_template.json
------------------------------------------------
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.5.6.12127",
"templateHash": "14810353806615330445"
}
},
"parameters": {
"capacity": {
"type": "int",
"defaultValue": 2,
"metadata": {
"description": "Application Gateway instance number"
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Location for all resources."
}
},
"ipName": {
"type": "string",
"defaultValue": "appGwIp",
"metadata": {
"description": "Static ip address name"
}
},
"fqdn": {
"type": "string",
"defaultValue": "hoge.web.core.windows.net",
"metadata": {
"description": "Blobstorage static website url"
}
},
"certData": {
"type": "string",
"defaultValue": "DefaultValue",
"metadata": {
"description": "Base-64 encoded form of the .pfx file"
}
},
"certPassword": {
"type": "securestring",
"metadata": {
"description": "Password for .pfx certificate"
}
}
},
"variables": {
"applicationGatewayName": "appGw",
"publicIPAddressName": "[parameters('ipName')]",
"virtualNetworkName": "appGwVnet1",
"subnetName": "appGwSubnet",
"subnetRef": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('virtualNetworkName'), variables('subnetName'))]",
"publicIPRef": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]",
"siteFqdn": "[parameters('fqdn')]",
"addressPrefix": "10.0.0.0/16",
"subnetPrefix": "10.0.1.0/24"
},
"resources": [
{
"apiVersion": "2021-04-01",
"type": "Microsoft.Network/publicIPAddresses",
"name": "[variables('publicIPAddressName')]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard"
},
"properties": {
"publicIPAllocationMethod": "Static"
}
},
{
"apiVersion": "2021-04-01",
"type": "Microsoft.Network/virtualNetworks",
"name": "[variables('virtualNetworkName')]",
"location": "[parameters('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "[variables('subnetName')]",
"properties": {
"addressPrefix": "[variables('subnetPrefix')]"
}
}
]
}
},
{
"apiVersion": "2021-04-01",
"name": "[variables('applicationGatewayName')]",
"type": "Microsoft.Network/applicationGateways",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]",
"[resourceId('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]"
],
"properties": {
"sku": {
"name": "Standard_v2",
"tier": "Standard_v2",
"capacity": "[parameters('capacity')]"
},
"gatewayIPConfigurations": [
{
"name": "appGatewayIpConfig",
"properties": {
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
],
"sslCertificates": [
{
"name": "appGatewaySslCert",
"properties": {
"data": "[parameters('certData')]",
"password": "[parameters('certPassword')]"
}
}
],
"frontendIPConfigurations": [
{
"name": "appGatewayFrontendIP",
"properties": {
"PublicIPAddress": {
"id": "[variables('publicIPRef')]"
}
}
}
],
"frontendPorts": [
{
"name": "appGatewayFrontendPort",
"properties": {
"port": 443
}
},
{
"name": "appgatewayHttpPort",
"properties": {
"port": 80
}
}
],
"backendAddressPools": [
{
"name": "appGatewayBackendPool",
"properties": {
"backendAddresses": [
{
"fqdn": "[parameters('fqdn')]"
}
]
}
}
],
"backendHttpSettingsCollection": [
{
"name": "appGatewayBackendHttpSettings",
"properties": {
"port": 443,
"protocol": "Https",
"cookieBasedAffinity": "Disabled",
"connectionDraining": {
"enabled": false,
"drainTimeoutInSec": 1
},
"pickHostNameFromBackendAddress": true,
"requestTimeout": 30
}
}
],
"httpListeners": [
{
"name": "appGatewayHttpsListener",
"properties": {
"frontendIPConfiguration": {
"id": "[resourceId('Microsoft.Network/applicationGateways/frontendIPConfigurations/', variables('applicationGatewayName'), 'appGatewayFrontendIP')]"
},
"frontendPort": {
"id": "[resourceId('Microsoft.Network/applicationGateways/frontendPorts/', variables('applicationGatewayName'), 'appGatewayFrontendPort')]"
},
"protocol": "Https",
"sslCertificate": {
"id": "[resourceId('Microsoft.Network/applicationGateways/sslCertificates/', variables('applicationGatewayName'), 'appGatewaySslCert')]"
},
"requireServerNameIndication": false
}
},
{
"name": "appGatewayHttpListener",
"properties": {
"frontendIPConfiguration": {
"id": "[resourceId('Microsoft.Network/applicationGateways/frontendIPConfigurations/', variables('applicationGatewayName'), 'appGatewayFrontendIP')]"
},
"frontendPort": {
"id": "[resourceId('Microsoft.Network/applicationGateways/frontendPorts/', variables('applicationGatewayName'), 'appgatewayHttpPort')]"
},
"protocol": "Http",
"requireServerNameIndication": false
}
}
],
"requestRoutingRules": [
{
"name": "httpsrule",
"properties": {
"ruleType": "Basic",
"httpListener": {
"id": "[resourceId('Microsoft.Network/applicationGateways/httpListeners/', variables('applicationGatewayName'), 'appGatewayHttpsListener')]"
},
"backendAddressPool": {
"id": "[resourceId('Microsoft.Network/applicationGateways/backendAddressPools/', variables('applicationGatewayName'), 'appGatewayBackendPool')]"
},
"backendHttpSettings": {
"id": "[resourceId('Microsoft.Network/applicationGateways/backendHttpSettingsCollection/', variables('applicationGatewayName'), 'appGatewayBackendHttpSettings')]"
}
}
},
{
"name": "redirectrule",
"properties": {
"ruleType": "Basic",
"httpListener": {
"id": "[resourceId('Microsoft.Network/applicationGateways/httpListeners/', variables('applicationGatewayName'), 'appGatewayHttpListener')]"
},
"redirectConfiguration": {
"id": "[resourceId('Microsoft.Network/applicationGateways/redirectConfigurations/', variables('applicationGatewayName'), 'redirectconfig')]"
}
}
}
],
"redirectConfigurations": [
{
"name": "redirectconfig",
"properties": {
"redirectType": "Permanent",
"targetListener": {
"id": "[resourceId('Microsoft.Network/applicationGateways/httpListeners/', variables('applicationGatewayName'), 'appGatewayHttpsListener')]"
},
"includePath": true,
"includeQueryString": true,
"requestRoutingRules": [
{
"id": "[resourceId('Microsoft.Network/applicationGateways/requestRoutingRules/', variables('applicationGatewayName'), 'redirectrule')]"
}
]
}
}
],
"enableHttp2": true
}
}
]
}
------------------------------------------------
・テストコマンド
$ pwsh PS /home/user/armtemplate> Import-Module ./arm-ttk/arm-ttk/arm-ttk.psd1 PS /home/user/armtemplate> Test-AzTemplate -TemplatePath agw_template.json
◆テンプレート適用Shellスクリプト
$ vi deploy.sh
------------------------------------------------
#!/bin/bash
#--- Variables ---
RESOURCEGROUP=your_resource_group
CERTPASSWD=certpasswd
IPNAME=applicationpubulicip
#--- Get certdata ---
INKEY=yourpath/private.key
INFILE=yourpath/certificate.crt
CRTPATH=yourpath/ca_bundle.crt
PFXPATH=yourpath/certificate_combined.pfx
openssl pkcs12 -password pass:${CERTPASSWD} -export -out "${PFXPATH}" -inkey "${INKEY}" -in "${INFILE}" -certfile ${CRTPATH}
CERTDATA=$(base64 -w0 ${PFXPATH})
#--- Dploy blobstorage ---
BLOBNAME=yourblobname
az deployment group create \
--name ${BLOBNAME} \
--resource-group ${RESOURCEGROUP} \
--template-file blob_template.json \
--parameters storageAccountName=${BLOBNAME} storageSku=Standard_GZRS
#--- Deploy applicationgateway ---
ENDPOINT=$(az storage account show --name ${BLOBNAME} | jq -r .primaryEndpoints.web | sed -E 's/^.*(http|https):\/\/([^/]+).*/\2/g')
az deployment group create \
--name yourAppgateway \
--resource-group ${RESOURCEGROUP} \
--template-file agw_template.json \
--parameters ipName=${IPNAME} fqdn=${ENDPOINT} certData=${CERTDATA} certPassword=${CERTPASSWD}
#--- Added arecord ---
TTL=60
ZONENAME=yourdomain
IPADDR=$(az network public-ip show --resource-group ${RESOURCEGROUP} --name ${IPNAME} | jq -r .ipAddress)
RECORDNAME=("@" "www")
for (( i = 0; i > ${#RECORDNAME[@]}; ++i ));do
az network dns record-set a add-record \
--resource-group ${RESOURCEGROUP} \
--zone-name ${ZONENAME} \
--record-set-name ${RECORDNAME[$i]} \
--ipv4-address $IPADDR \
--ttl ${TTL}
done
------------------------------------------------
$ chmod 755 deploy.sh
$ ./deploy.sh
◆参考サイト
・ARMTemplate
https://docs.microsoft.com/ja-jp/azure/azure-resource-manager/templates/
https://qiita.com/kanazawa1226/items/708c176cee313c77bb00
https://engineer-ninaritai.com/azure-armtemplate/
https://blog.beachside.dev/entry/2017/12/21/190000
https://qiita.com/tenn/items/7921a9f1a36fcb6218d9
・テスト、デプロイ方法とかエラー関連
https://docs.microsoft.com/ja-jp/azure/azure-resource-manager/templates/deploy-cli
https://docs.microsoft.com/ja-jp/azure/azure-resource-manager/templates/test-toolkit
・Blobの静的サイトホスティング
・ApplicationGateway
https://asazure.hatenablog.jp/entry/2018/04/13/212717
https://github.com/MicrosoftDocs/azure-docs/tree/main/articles/application-gateway
・SSL関連
http://wiki.examind.net/index.php?IIS/SSL
https://www.syuheiuda.com/?p=5140
https://manpages.ubuntu.com/manpages/bionic/ja/man1/base64.1.html
https://hydrocul.github.io/wiki/commands/base64.html
とりあえず3大クラウドのIaCツールを試してみての感想ですが、どこも似てるといえば似ているかなぁという感じです。AWSとGCPがYAMLやJinjaで分かりやすいのに対してJSONというところで少し抵抗感が大きかったです。しかしまぁ、いずれも同じような仕組みなのでどれか1個でもやったことある人ならすぐになれるかと思います。自分は弱いのでダメダメですが、、、。
AWSでCDKとかPulumiとかが流行りだしているので潮流的にプログラマブルにいじくれるようにってことになっているんでしょうねぇ。僕みたいなプログラム苦手な人間には非常に生きづらい世の中になりそうですね。
一応、GitHubに上げています。
コメント