ケチ担当です。
背景
Azure VMといえばスペックに目が行きがちですが、マネージドディスクも案外高いです。むしろマネージドディスクは存在するだけでかかるので1日1時間だけ使うVMだからといっても24時間分まるまる掛かります。
しかし、メジャーなLinuxイメージは30GiB以上からとなります。OS用のマネージドディスクはプランに基づいてストレージ容量が決まっており、30GiBの場合のマネージドディスクの価格は32GiB相当のプラン(P4,E4等)からとなります。
マネージドディスクの価格をみてみましょう。
- Premium SSD P4(32GiB):
$6.08
- Standard SSD E4(32GiB):
$2.40
- Standard HDD S4(32GiB):
$1.54
SLAから外れますがStandard SSDにするだけでも安くなります。
しかし、Bastion(踏み台)といったストレージ容量やIOPSがあまり必要ではないワークロードでは32GiBでも過剰です。
もし4GiBになれば以下のプランとコストでマネージドディスクが運用できます。
- Premium SSD P1(4GiB):
$0.87
(P4と比べ-$5.21
) - Standard SSD E1(4GiB):
$0.30
(E4と比べ-$2.10
)
マネージドディスク1台あたり月額約$2.1~$5.2
の節約となります。
ちりも積もれば!ととらえるか、正直Standard SSDにするだけでもいいかな…ととらえるかはあなた次第…
というわけで、4GiBのマネージドディスクでUbuntu VMを動かして節約したいなぁ、というのが趣旨です。
手順
前提
- Windows 11のマシンで管理者権限を持っている
- Hyper-V関連のコマンドを利用できる(VHDの操作コマンドが必要)
- Azure Portalをある程度操作できる(StorageAccountの作成、Blobのアップロード、Managed Diskの作成などの操作・権限があること)
VHDファイルを用意する
UbuntuではCloud用のイメージも配布されており、すぐ使えるAzure向けVHDファイルも用意されています。
今回はこのAzure向けVHDファイルを使います。使わない場合は、自前でHyper-VにVMを作成し、Ubuntuをインストールし、AzureのドキュメントのManual stepsを実施する必要がありそうです。
早速ですが、配布されているVHDが含まれているnoble-server-cloudimg-amd64-azure.vhd.tar.gz
ファイルをダウンロードして展開した図です。0.5GBが30GBにもなります。この後さらに使うので、ディスク容量は充分に空けてください。
次に、PowerShellを管理者として実行し、以下のコマンドを実行します(Hyper-Vマネージャなどが使えるようにしていないとコマンドがないかもしれません)
# 変数用意
$VHD_PATH='livecd.ubuntu-cpc.azure.vhd'
$WORK_VHDX_PATH="$($VHD_PATH).dyn.vhdx"
$DEST_VHD_PATH="$($VHD_PATH).small.vhd"
# 作業ディレクトリをVHDがある場所へ移動
cd path\to\dir
# 動的なディスク(Dynamic)のVHDXファイルに変換する
Convert-VHD -Path $VHD_PATH -DestinationPath "$WORK_VHDX_PATH" -VHDType Dynamic
# 作成したVHDXファイルを確認。まだSizeが30GBぐらいになっている
Get-VHD -Path $WORK_VHDX_PATH
# VHDXファイルを縮小(MiniumumSizeを見ながら調整)
# ここをマネージドディスクのサイズに合うように調整(空きがもう少し欲しいなら8GBなど)
Resize-VHD -Path $WORK_VHDX_PATH -SizeBytes 4GB
# Sizeが縮小できたことを確認
Get-VHD -Path $WORK_VHDX_PATH
# 固定ディスクのVHDファイルに変換する
Convert-VHD -Path $WORK_VHDX_PATH -DestinationPath $DEST_VHD_PATH -VHDType Fixed
一通りの処理が終わると*.small.vhd
ができあがります。
他のファイルは不要なので、消しても構いません。
VHDファイルをBlobストレージへアップロードする
やり方は色々ありそうです以下の手順で行います
- Locationを決める(Storage Account, マネージドディスク)
- Storage Account,コンテナを作ってアップロード
- マネージドディスク作成時にBlobを参照
マネージドディスクを作成する際にStorage AccountとLocationを合わせないとエラーになります。
その点以外は普通にStorageAccountを作って、コンテナを作ってBlobにPage Blobとしてアップロードするだけです。ポータル上からアップロードしてください。
アップロード後、 BlobのURLを控えておいてください。マネージドディスク作成時に使います。StorageAccountがAzureサービスからアクセスできる状態であれば、SASも必要ないです。
マネージドディスクの作成を行う
Azureのリソース、Disk
から行えます。(通常VMのついでで作るので、Disk単品を作ることはあまりないと思いますが)
Disk作成時のポイント以下です。
- Disk nameはVM作成時に参照するため良い名前をつける
- Source BlobにStorage BlobでアップロードしたVHDファイルのBlobのURLを指定
- SizeもStandard SSDの4GiBにします。 SLAがほしければPremium SSDにしましょう
- 他はデフォルトでよいです(必要に応じて設定してください)
作成後はBlobを削除しても構いません。もちろん、他のVMでも同じことをしたい場合は残しても良いでしょう。
作ったマネージドディスクを使ってVMの作成
デプロイできたら、Diskのリソースにアクセスしてみてください。4GiBのマネージドディスクになっていますね。
Create VM
からVMを作ってみましょう。
イメージ選択ができない、ユーザ作成ができない、VM Generationは第2世代にする点以外は普通のVM作成と一緒です。
ユーザの作成はこの後で行います。
VM作成時・作成後はまず、NSGの設定などを行ってください。配布されているUbuntuのディスクイメージはSSHのパスワードログイン不可能な状態なので最悪インターネットにSSHポートを晒しても大丈夫なんですが、世の中何があるかわからないので自宅・会社のIPを許可するなどいい感じにやってください。
VM内にSSH接続用のユーザ作成
Azure VM作成時にユーザ作成がなかったのでログインできません。(Ubuntuイメージにはデフォルトのユーザもありません)
なので、Azure Portalからコマンドを実行します。
Operations→Run Commandがあり、RunShellScriptというモノが用意されています。ここからRoot権限でShellScriptが発行できます(悪用厳禁)
ユーザを作ってSSH公開鍵を仕込むスクリプトの例です。
公開鍵は適当に用意して、3つの変数は良い感じに埋めてください。パスワードは後で換える前提で一時的なものにしましょう。
USERNAME='azureuser'
PUBLIC_KEY='ssh-ed25519 AAAA....'
PASSWORD=passw0rd
# 作成されるディレクトリ・ファイルのPermissionをOwnerのみ許可するようマスクする
umask 0077
# ユーザ作成
useradd -m --shell "/usr/bin/bash" "$USERNAME"
# sudoできるグループへユーザを追加
usermod -G sudo "$USERNAME"
# sudo時のパスワードをとりあえず設定
echo "$USERNAME:$PASSWORD" | chpasswd
# (Option)sudo時のパスワードが面倒な場合、大いなる責任を感じながら NOPASSWD: をつける
# echo "$USERNAME ALL=(ALL) NOPASSWD: ALL" > "/etc/sudoers.d/90_${USERNAME}"
# SSH公開鍵を登録
mkdir /home/$USERNAME/.ssh
echo "$PUBLIC_KEY" >> /home/$USERNAME/.ssh/authorized_keys
# 所有者をログインユーザに変更
chown -R $USERNAME: /home/$USERNAME/.ssh
# 作成確認
ls -al /home/$USERNAME/.ssh
作業後、公開鍵方式でSSHログインできるはずです。df
などでストレージ容量を確認してみてください。
あとはパスワードを正規のものに変更してください。
なお、このポータルから実行したスクリプトは/var/lib/waagent/run-command/download/*/script.sh
にroot権限で参照できる形で残ります。root権限無いと見られないとはいえ、パスワードも記録されてしまうことになるので変えておきましょう。
作業は以上です。あとはDisk fullに注意しながら運用しましょう。
おまけ
パーティションやファイルシステムのリサイズは大丈夫?
VHDのリサイズしかしていないのだからディスクイメージ内のファイルシステムは古いままではないか?と思っていました。
結果としてはUbuntuで配布されているイメージを使っている場合、初回起動時にcloud-initでリサイズがかかるため大丈夫です。
以下はSSHログイン直後に確認した様子です。
azureuser@ubuntu:~$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 4G 0 disk
├─sda1 8:1 0 3G 0 part /
├─sda14 8:14 0 4M 0 part
├─sda15 8:15 0 106M 0 part /boot/efi
└─sda16 259:0 0 913M 0 part /boot
$ sudo fdisk -l
Disk /dev/sda: 4 GiB, 4294967296 bytes, 8388608 sectors
Disk model: Virtual Disk
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: gpt
Disk identifier: 604309DA-4FE5-4F90-BBFC-6DBB76EBD0CA
Device Start End Sectors Size Type
/dev/sda1 2099200 8388574 6289375 3G Linux filesystem
/dev/sda14 2048 10239 8192 4M BIOS boot
/dev/sda15 10240 227327 217088 106M EFI System
/dev/sda16 227328 2097152 1869825 913M Linux extended boot
Partition table entries are not in disk order.
cloud-initのログをみたところ、パーティション操作(growpart)とファイルシステムのリサイズ(resize2fs)を行っていました。
2025-01-21 15:28:05,956 - subp.py[DEBUG]: Running command ['growpart', '/dev/sda', '1'] with allowed return codes [0] (shell=False, capture=True)
2025-01-21 15:28:06,649 - performance.py[DEBUG]: Running ['growpart', '/dev/sda', '1'] took 0.693 seconds
2025-01-21 15:28:06,650 - performance.py[DEBUG]: Resizing devices took 0.900 seconds
2025-01-21 15:28:06,650 - cc_growpart.py[INFO]: '/' resized: changed (/dev/disk/by-partuuid/1b4cd629-def0-4dac-89eb-bb9119fdc5d4) from 2683289088 to 3220160000
2025-01-21 15:28:06,650 - handlers.py[DEBUG]: finish: init-network/config-growpart: SUCCESS: config-growpart ran successfully and took 0.920 seconds
2025-01-21 15:28:06,650 - modules.py[DEBUG]: Running module resizefs (<module 'cloudinit.config.cc_resizefs' from '/usr/lib/python3/dist-packages/cloudinit/config/cc_resizefs.py'>) with frequency always
2025-01-21 15:28:06,650 - handlers.py[DEBUG]: start: init-network/config-resizefs: running config-resizefs with frequency always
2025-01-21 15:28:06,650 - helpers.py[DEBUG]: Running config-resizefs using lock (<cloudinit.helpers.DummyLock object at 0x77b8aaaeec60>)
2025-01-21 15:28:06,650 - util.py[DEBUG]: Reading from /proc/659/mountinfo (quiet=False)
2025-01-21 15:28:06,651 - util.py[DEBUG]: Reading 2310 bytes from /proc/659/mountinfo
2025-01-21 15:28:06,651 - cc_resizefs.py[DEBUG]: resize_info: dev=/dev/root mnt_point=/ path=/
2025-01-21 15:28:06,651 - cc_resizefs.py[DEBUG]: Resizing / (ext4) using resize2fs /dev/root
2025-01-21 15:28:06,651 - subp.py[DEBUG]: Running command ('resize2fs', '/dev/root') with allowed return codes [0] (shell=False, capture=True)
2025-01-21 15:28:06,709 - performance.py[DEBUG]: Running ('resize2fs', '/dev/root') took 0.059 seconds
2025-01-21 15:28:06,710 - cc_resizefs.py[DEBUG]: Resized root filesystem (type=ext4, val=True)
2025-01-21 15:28:06,710 - handlers.py[DEBUG]: finish: init-network/config-resizefs: SUCCESS: config-resizefs ran successfully and took 0.059 seconds
おまけにdfも。2024.04のイメージでも1GBぐらいは空いてます。
$ df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/root 2974484 1858468 1099632 63% /
tmpfs 4063764 0 4063764 0% /dev/shm
tmpfs 1625508 980 1624528 1% /run
tmpfs 5120 0 5120 0% /run/lock
efivarfs 131072 26 131042 1% /sys/firmware/efi/efivars
/dev/sda16 901520 59924 778468 8% /boot
/dev/sda15 106832 6246 100586 6% /boot/efi
tmpfs 812752 12 812740 1% /run/user/1000