背景:ミドルウェアの構築自動化
Bicepでデプロイリソースやパラメータの定義はできるものの、Azure VMなどは基本的にIaaSの箱ができるのみである。
実際はそこからVM構築を行う必要があるが、このミドルウェア以上の構築も自動化したい。
調べてみるところ、3つの方法がありそうだった。
- Ansibleなどの構成管理ツールを使った自動化
- ゴールデンイメージの立ち上げ
- カスタムスクリプト拡張機能
今回の構築自動化の想定として、「本番環境をサッと構築して検証を行ったら破棄する仕組み」を実現したいという考えから、Ansibleは除外した。(本番環境もAnsibleで構成管理してたらよさそうだけど)
ゴールデンイメージは触ったことがないのと、現時点で完成系を持っていないことから除外した。
よって、カスタムスクリプト拡張機能を使った実現を検討した
カスタムスクリプト拡張機能とは
すごく簡単に言うと、外部ストレージにスクリプトファイルを保存しておいて、VMが立ち上がったら外部からスクリプトを持ってきて自動で実行する機能。
スクリプトはAzure StorageやGitHubからもダウンロードできる

実際に作ってみた
流れの整理
今回はDMZに置いたWebサーバーをスクリプトで自動構築する。
大まかな流れは以下のとおりである。
- (準備)VMなどの環境を構築するBicepファイルを用意
- (準備)スクリプト及びindex.htmlやstyle.cssファイルを用意
- (実行)リソースグループ、Azure Storageアカウント&コンテナーを作成
- (実行)スクリプトやindex.htmlなどのファイルをストレージに格納
- (実行)VMのデプロイ時にストレージアカウントからスクリプトを持ってきて実行。
スクリプトで、index.htmlファイルなどをストレージアカウントからダウンロードし、/etc/www/html に格納(元ファイルは適当にorgとかつけて退避)
実装
VMなどのBicepファイルを用意
ここでは割愛します。以下の記事でVMを作成しています。
スクリプト及びindex.htmlファイルなどを用意
index.htmlやstyle.cssは適当に作ったので割愛しますが、スクリプトは以下のようになっています。
スクリプトの中身
#!/bin/bash
set -e
echo "=== Apache Web Server Setup Started ==="
# 環境変数の確認
if [ -z "$STORAGE_ACCOUNT" ]; then
echo "Error: STORAGE_ACCOUNT environment variable is not set"
exit 1
fi
if [ -z "$STORAGE_KEY" ]; then
echo "Error: STORAGE_KEY environment variable is not set"
exit 1
fi
# APT ソースリストの修正(既知の問題への対応)
echo "Fixing APT sources list..."
sed -i '/^$/d' /etc/apt/sources.list 2>/dev/null || true
sed -i '/^[[:space:]]*$/d' /etc/apt/sources.list 2>/dev/null || true
# システムパッケージの更新
echo "Updating package lists..."
apt-get update
# Apache のインストール
echo "Installing Apache2..."
apt-get install -y apache2
# Apache の有効化と起動
echo "Enabling and starting Apache2..."
systemctl enable apache2
systemctl start apache2
# 作業ディレクトリの作成
TEMP_DIR="/tmp/webapp-deploy"
mkdir -p "$TEMP_DIR"
cd "$TEMP_DIR"
# Azure Blob Storage から Web コンテンツをダウンロード
echo "Downloading web content from Azure Storage..."
# Azure CLI をインストール(まだない場合)
if ! command -v az &> /dev/null; then
echo "Installing Azure CLI..."
curl -sL https://aka.ms/InstallAzureCLIDeb | bash
fi
# ストレージアカウントキーを使用してダウンロード
echo "Downloading webapp.tar.gz..."
az storage blob download \
--account-name "$STORAGE_ACCOUNT" \
--account-key "$STORAGE_KEY" \
--container-name webapp \
--name webapp.tar.gz \
--file webapp.tar.gz \
--no-progress \
--only-show-errors
# ダウンロードの確認
if [ ! -f "webapp.tar.gz" ]; then
echo "Error: Failed to download webapp.tar.gz"
ls -la
exit 1
fi
echo "Download successful. File size: $(stat -f%z webapp.tar.gz 2>/dev/null || stat -c%s webapp.tar.gz) bytes"
# Web コンテンツの展開
echo "Extracting web content..."
tar -xzf webapp.tar.gz
# 展開内容の確認
echo "Extracted files:"
ls -la
# 既存のコンテンツをバックアップ
echo "Backing up existing content..."
if [ -d "/var/www/html" ]; then
mv /var/www/html "/var/www/html.backup.$(date +%Y%m%d%H%M%S)"
fi
# 新しいコンテンツをデプロイ
echo "Deploying new content..."
mkdir -p /var/www/html
# tar で展開されたファイルを直接コピー
# webapp.tar.gz は webapp/ ディレクトリなしで作成されているため、カレントディレクトリから直接コピー
if [ -f "index.html" ]; then
echo "Copying files from current directory..."
cp -r * /var/www/html/ 2>/dev/null || true
# tar.gz ファイルは除外
rm -f /var/www/html/webapp.tar.gz
elif [ -d "webapp" ]; then
echo "Copying files from webapp directory..."
cp -r webapp/* /var/www/html/
else
echo "Error: No webapp content found after extraction"
ls -la
exit 1
fi
# パーミッションの設定
echo "Setting permissions..."
chown -R www-data:www-data /var/www/html
chmod -R 755 /var/www/html
# Apache の設定確認
echo "Verifying Apache configuration..."
apache2ctl configtest
# Apache の再起動
echo "Restarting Apache2..."
systemctl restart apache2
# ファイアウォールの設定(UFW が有効な場合)
if command -v ufw &> /dev/null; then
echo "Configuring firewall..."
ufw allow 'Apache Full' || true
fi
# クリーンアップ
echo "Cleaning up temporary files..."
cd /
rm -rf "$TEMP_DIR"
# サービスステータスの確認
echo "Checking Apache status..."
systemctl status apache2 --no-pager
echo "=== Apache Web Server Setup Completed Successfully ==="
echo "Web server is now accessible on port 80"
echo "Content deployed from: $BLOB_URL"
ざっくり処理の中身ですが、
- apt-getでapacheをインストール&systemctlでrunning
- ストレージアカウントからファイルをダウンロード
- tar.gzファイルを解凍
- 既存の/var/www/htmlのファイルを退避してバックアップ
- 3で解凍したファイルを/var/www/htmlに移動
- パーミッションの設定
- apacheの再起動
リソースグループ、Azure Storageアカウント&コンテナーを作成
PowerShellからaz loginしてリソースグループを作成します
az login
az group create `
--name resourceGroupName `
--location japaneast
続けてAzure Storage Accountリソースとコンテナーを作成
# ストレージアカウント名(グローバルで一意な名前を指定)
$storageAccountName = "satrial" + (Get-Random -Minimum 1000 -Maximum 9999)
az storage account create `
--name $storageAccountName `
--resource-group resourceGroupName `
--location japaneast `
--sku Standard_LRS `
--kind StorageV2
# コンテナーを作成
az storage container create `
--name scripts `
--account-name $storageAccountName `
--auth-mode login
az storage container create `
--name webapp `
--account-name $storageAccountName `
--auth-mode login
その後、ポータルでストレージアカウントのIAM設定から「ストレージ BLOB データ共同作成者」ロールをaz loginでサインインしたユーザーに割り当てる

ポータルでロールを割り当てが完了して反映されるまで1分前後待つ必要があります。
ポータル上では割り当たってるように見えるのに、PowerShell操作をすると権限エラーが返される場合は反映待ちの状態です。
スクリプトやindex.htmlなどのファイルをストレージに格納
まず、PowerShellに戻ってindex.htmlやstyle.cssが入っているフォルダを圧縮します。
tar -czf webapp.tar.gz -C webapp .
続いて、圧縮したtar.gzファイルとスクリプトファイルを先ほど作成したストレージアカウントの各コンテナーにアップロードします。
az storage blob upload `
--account-name $storageAccountName `
--container-name scripts `
--name setup-apache.sh `
--file scripts/setup-apache.sh `
--auth-mode login
az storage blob upload `
--account-name $storageAccountName `
--container-name webapp `
--name webapp.tar.gz `
--file webapp.tar.gz `
--auth-mode login
アップロードが完了したら圧縮したファイルなどは消しておきましょう。
Remove-Item webapp.tar.gz
VMのデプロイ時にストレージアカウントからスクリプトを持ってきて実行
デプロイコマンドは構成によりけりですが一例を記載します。
az deployment group create `
--resource-group resourceGroupName `
--template-file main.bicep `
--parameters linuxAdminUsername=<YourLinuxAdminUser> `
linuxAdminPassword="<YourLinuxAdminPassword>" `
storageAccountName=$storageAccountName
VMを構成するBicepコードは割愛しますが、この下にextensionsでリソースを定義します。
これがカスタムスクリプトを定義するBicepコードになります。
resource webSerVm 'Microsoft.Compute/virtualMachines@2023-09-01' = {
・・・
}
resource webServerSetup 'Microsoft.Compute/virtualMachines/extensions@2023-09-01' = {
name: 'CustomScriptExtension'
parent: webSerVm
location: location
properties: {
publisher: 'Microsoft.Azure.Extensions'
type: 'CustomScript'
typeHandlerVersion: '2.1'
autoUpgradeMinorVersion: true
settings: {
fileUris: [
'https://${storageAccountName}.blob.${environment().suffixes.storage}/scripts/setup-apache.sh'
]
}
protectedSettings: {
commandToExecute: format('STORAGE_ACCOUNT={0} STORAGE_KEY={1} bash setup-apache.sh', storageAccountName, listKeys(resourceId(storageAccountResourceGroup, 'Microsoft.Storage/storageAccounts', storageAccountName), '2023-01-01').keys[0].value)
storageAccountName: storageAccountName
storageAccountKey: listKeys(resourceId(storageAccountResourceGroup, 'Microsoft.Storage/storageAccounts', storageAccountName), '2023-01-01').keys[0].value
}
}
}
デプロイ実行時にこのextensionsリソースが呼び出され、VM作成後にスクリプト実行処理を行うという流れになります。
実行
デプロイしてみます。

PowerShell上でデプロイコマンドを実行すると、CLIでAcceptやらRunningがくるくるしていますが、処理中を意味しています。
処理状況を確認したいときはAzure Portalのリソースグループ→設定→デプロイからデプロイ状況やエラー内容などを確認できます。

全部成功すれば完了です。

確認
ブラウザでWebサーバーにアクセスしてみるといい感じのWebサーバーが完成していることがわかります。

Webサーバー側でも簡単な確認をしてみます。
systemctl status

index.htmlやcssも権限含めていけてそうです。

スクリプトや出力系は以下のフォルダに入っているみたいです。
/var/lib/waagent/custom-script/download/0/
まとめ
今回はカスタムスクリプト拡張機能を使った自動構築の検証を行いました。
カスタムスクリプトはスクリプトを実行しているだけなので、基本的にはデプロイタイミングの1回きり実行されるものです。
しかし、一回作ってしまえば何度もデプロイで使いまわせるので、検証環境の構築を高速化することが可能です。
そのため、恒常的な運用をする環境よりも、運用の中でちょっと検証したいときにサッと環境を用意するというシチュエーションにおいて、強力なツールだと考えています。
逆に、構成管理という観点では向いていないので、恒常的な環境を運用する場合はAnsibleなどを、直ぐに作って終わったら消すというチョイ検証の時はカスタムスクリプト拡張機能を使い分けると良いですね。



コメント