FreeNAS + VMWare ESXi で作る自作コンバージドインフラ

 

VMWare ESXi 2台を vcenter で集中管理

2台の1Uサーバ(2CPUモデル)を、VMWareホストとして仕立てました。

VMWareホストに必要なのは、有り余るメモリと有り余るCPUパワーです。ディスク領域は原則としてNASからネットワーク経由で供給されるので、内蔵ディスクの役割は限られます。

起動ディスク・スワップ領域として、起動ディスクとなるSSDを利用します。

なお、最優先で稼働させたい仮想マシン1、2のためにも、内蔵SSDを割り当てます。

NFSの性能問題(ESXiとの相性)

仮想マシンのディスクとして、FreeNASが公開するNFSストレージを割り当てたところ、極めて劣悪なディスクIO性能に悩まされました。データのコピーがとにかく遅いのです。

これは、ESXiサーバがNFSストレージに対する書き込み要求を、毎回同期要求とともに行うためです。

FreeNAS側でZFSプールの属性に、「sync=standard」となっていると遅いのですが、これを「sync=disabled」に変更することで、劇的に(10倍から100倍に)高速化できることがわかりました。

そこで、私たちはFreeNAS上で下記のように設定しました

zfs set sync=disabled POOL1
zfs set atime=off POOL1

sync=disabled の状態では、書き込みの最中に電源切断などが発生すると、データが破損するようになります。私たちは、データ破損のリスクをあえて冒しても、10倍から100倍の高速化のメリットがそれを上回ると考えて、確信犯でこの設定を行っています。

鉄飛テクノロジーでは、製品のビルド環境並びに、テスト環境として、ここで構築した仮想マシン群を活用しています。開発環境・テスト環境は、万が一のトラブルで時々システムが稼働できない状態になったとしても、即座にお客様へのサービス提供が困難になることはありません。復旧に24時間ぐらいかかっても問題はないという割り切りがあっての判断です。

お客様に24時間365日公開しているWebサーバや、重要データを保持するファイルサーバ、業務システムのデータベースなどは、ここで構築している仮想インフラとは別に、稼働させていることに留意してください。

sync=disabledの設定を行うことは、データを危険にさらすという脅し文句を、さんざん読みましたが、 それでも通常、5秒に一回はデータがディスクに書き出されていることもわかりました。

電源切断などが発生した場合、おそらく単純に5秒前の状態に戻る、というのが一番ありえる症状ですが、最悪の場合にはZFSファイルシステムが破損して読み出しができなくなります。

そこで、FreeNASの機能で、30分おきにZFSプールのスナップショップをとるように設定し、3日間保持することとしました。こうすると、ZFSが破損してしまった最悪のケースでも、最後のスナップショットを復元することで、最悪でも30分前の状態に復元することができるのです。

開発・テスト環境であること、また、弊社の立地(東京都目黒区)では予期せぬ停電などが5年に1回程度しか発生していない実績があることもあって、これで十分と考えています。

vcenterの導入

VMWare ESXi をそれぞれにインストールした上で、2台のESXiホストをWebブラウザで集中管理するために、vcenterサーバを導入しました。vcenterサーバを稼働させるために、有償のVMWare Essentials ライセンスを購入しています。

1か所で2台のESXiホスト上のすべての仮想マシンを管理できることは、もちろんvcenterのメリットです。

しかし、それだけが決め手ではありません。

vcenterを用いることで、仮想マシンのスナップショット作成や、仮想マシンの複製が可能になります。この「仮想マシンの複製」を応用して、仮想マシンのバックアップ運用の自動化ができるのです。

vcenterサーバのバックアップ

vcenterサーバは、ESXiホスト上の仮想マシンアプライアンスとして用意され、通常はどちらか一方のESXiホスト上で稼働します。この仮想マシンの複製を作成して、もう一方のESXiホスト上に置いておけば、万が一ホストマシンに障害が起きた場合でも、無事だったもう一方のESXiホスト上のvcenterサーバを起動すれば大丈夫です。

仮想マシンのバックアップ自動運用

VMWare 仮想マシンのバックアップをどう運用するか、エンタープライズ環境の場合には、Veeam Bacupなどのサードパーティ製のバックアップソリューションを購入するのが一般的ではないかと思います。

貧しいながらも技術で勝負する私たちとしては、多少面倒でも安い方法が無いかと探してみた結果、よい方法を見つけました。

PowerCLI を使う

PowerCLIとは、VMWareが無償で提供するWindowsPowerShell向けのライブラリであり、vcenter API をPowerShellスクリプトで利用可能にするものです。

https://code.vmware.com/web/tool/vmware-powercli

これを使って、vcenterサーバにログインし、仮想マシンのスナップショットを作成し、スナップショットをコピーするが可能になります。ESXiホストに接続された二つのNFSボリュームにまたがるコピーができることによって、いずれか一方のNASサーバに障害が発生したときにも、生き残った方で運用を継続できることが可能になります。

NFSを使うもう一つの理由

ESXiホストにまたがる仮想マシンのコピーは、”VMotion” と呼ばれていて、VMWare Essentials Kitのライセンスでは実行ができません。(VMWare Essentials「Plus」が必要です)

しかし、NFSに仮想マシンイメージをバックアップしておけば、このNFSボリュームは両方のESXiホストから参照できます。コピー元と異なるESXiホストから参照できてしまうということは、実質的にはESXiホストにまたがった仮想マシンのコピーができたようなものです。

いずれか一方のESXiサーバに障害が発生したときは、生き残った方のESXiホストからNFSボリューム上のバックアップ仮想マシンイメージを参照してマウントすれば稼働させることができるのです。

バックアップスクリプト実行を定期実行

PowerCLIは、今回の仮想インフラとは別に、弊社の別サーバ上にインストールして、バックアップジョブを定期実行するようにタスクスケジューラに登録しました。

苦労した点は下記となります。

  • タスクスケジューラからPowerCLIスクリプトを実行したとき、vcenterに自動ログイン できるようにするのに苦労しました。

  • あらかじめ下記コマンドでVMWareのCredential Storeにログオン情報を保存しておきます。

New-VICredentialStoreItem -Host "vcenter.teppi.net" -User "administrator@vsphere.local" -Password "password"

Credential Storeはユーザプロファイルフォルダの中にあり、上記コマンドをAdministratorで実行すれば、下記に保存されます。

C:\Users\Administrator.TEPPI\AppData\Roaming\VMWare\credstore\vicredstore.xml

これを使うには、タスクスケジューラでPowerCLIスクリプトを(ユーザがログインしているかどうかにかかわらず、Administrator権限、かつ最上位特権付き)で実行せねばなりません。

稼働イメージ

たとえば、ESXiホスト olive.teppi.net 上では avocado2/avocado3/avocado5/papaya3/support_private/vcenter の 6台の仮想マシンを常時稼働させています。

zz_avocado2-02-201909250241 は、9月25日2時41分に取得したavocado2のスナップショットのバックアップで、世代番号02です、各サーバについて、バックアップは最大3世代まで取るようになっていて、世代番号は 01->02->03->04->01…. とローテーションしつつ、次回取得分世代のファイルは欠番となるようにしています。

参考(バックアップスクリプト)

ー動作保証一切無し!動かす前に自己責任でよく読んで理解してください。

Function Teppi-Backup-VM{
Param(
    [Parameter(Mandatory=$true)][string]$vmNameKey,
    [Parameter(Mandatory=$false)][int]$maxBackupIdx = 3,
    [string]$backupDataStoreName = "NFS_durian"
) #end Param

    $clonePrefix="zz_"
    $hostnamePattern = $vmNameKey+"*"

    # Connect-VIServer が成功するためには、あらかじめ
    # New-VICredentialStoreItem -Host "vcenter.teppi.net" -User "administrator@vsphere.local"
    # -Password "password"
    # のようなコマンドラインを実行して
    # VMWareのCredentialStoreファイル(たとえばTEPPI\Administratorの場合)
    #    C:\Users\Administrator.TEPPI\AppData\Roaming\VMWare\credstore\vicredstore.xml
    # に暗号化した情報を書き込んでおきます。
    #   タスクスケジューラから呼び出す場合、スクリプトの実行アカウントを一致させる必要があることに注意!

    $dummy = Connect-VIServer -Server "vcenter.teppi.net" 
    try {
        $vm = Get-VM |Where-Object {$_.Name -like "$hostnamePattern*" }
        
        If ($vm.Count -ne 1){
            $msg = "hostname("+$hostnamePattern+") not unique."
            Write-Error -Message $msg -Category:ResourceUnavailable -ErrorId:9

            # Teppi-SendStartMail $vmNameKey $clonePrefix $msg $backupDataStoreName

        }else{
            $vmName = $vm.Name
            Write-Output "-- Creating backup of $vmName"

            # 削除する旧クローンの世代番号と、次に取得する世代番号を決定する
            $currentCloneIdx = 0
            For ($i=1; $i -le $maxBackupIdx; $i++){
                $cloneName = GetBackupVMName $clonePrefix $i $vmNameKey
                $cloneVm = Get-VM | Where-Object {$_.Name -like "$cloneName*"}
            
                $cloneAlreadyExists = ($cloneVm.Count -ge 1)
                # Write-Output $cloneName $cloneAlreadyExists.ToString()
                If ($cloneAlreadyExists -eq $false) {
                    $currentCloneIdx = $i # 最初の欠番を見つける
                    Break
                }
            }
            If ($currentCloneIdx -le 1){
                $currentCloneIdx = 1
                $prevCloneIdx = 2
            }Else{
                If ($currentCloneIdx -eq $maxBackupIdx){
                    $prevCloneIdx = 1
                }else{
                    $prevCloneIdx = $currentCloneIdx+1
                }
            }
            Write-Output "-- Current:$currentCloneIdx, Prev:$prevCloneIdx, Max:$maxBackupIdx"
            
            # スナップショットを作る
            $cloneSnap = $vm | New-SnapShot -Name "Clone Snapshot"

            # Teppi-SendStartMail $vmNameKey $clonePrefix $vm.Name $backupDataStoreName

            $vmView = $vm | Get-View
            # Build clone specification
                $cloneSpec = new-object Vmware.Vim.VirtualMachineCloneSpec
                $cloneSpec.Snapshot = $vmView.Snapshot.CurrentSnapshot
     
                # Make linked disk specification
                $cloneSpec.Location = new-object Vmware.Vim.VirtualMachineRelocateSpec
                $cloneSpec.Location.Datastore = (Get-Datastore -Name $backupDataStoreName |
            Get-View).MoRef
                $cloneSpec.Location.Transform =  [Vmware.Vim.VirtualMachineRelocateTransformation]::sparse
     
            $cloneBaseName = GetBackupVMName $clonePrefix $currentCloneIdx $vmNameKey
            $datetime = Get-Date -Format "yyyyMMddHHmm"
            $cloneName = "$cloneBaseName-$datetime"
            Write-Output "-- Creating new clone: $cloneName"

                $oldCloneVM = Get-VM | Where-Object { $_.Name -like "$cloneBaseName*" }
            If ($oldCloneVM.Count -eq 1) {
                    #if $cloneVM already exists, delete it
                    $oldCloneVMName = $oldCloneVM.Name
                    $oldCloneVM | Remove-VM -DeletePermanently -Confirm:$false
                    Write-Output "-- Deleted existing clone: $oldCloneVMName"
            }

                $vmView.CloneVM( $vmView.parent, $cloneName, $cloneSpec )

            # move clone VM to backup folder in the VM Inventory
            $cloneFolder =  Get-Folder -Type VM -Name $cloneFolderName | Get-View |
         Get-VIObjectByVIView
            Move-VM $cloneName -InventoryLocation $cloneFolder
     
                # Remove Snapshot created for clone
                Get-Snapshot -VM (Get-VM -Name $hostnamePattern) -Name $cloneSnap |
            Remove-Snapshot -confirm:$False

            $prevCloneName = GetBackupVMName $clonePrefix $prevCloneIdx $vmNameKey
            $prevCloneVM = Get-VM | Where-Object { $_.Name -like "$prevCloneName*" }
            If ($prevCloneVM.Count -eq 1) {
                    #Delete old generation backup clone VM
                    $prevCloneVMName = $prevCloneVM.Name
                    $prevCloneVM | Remove-VM -DeletePermanently -Confirm:$false
                    Write-Output "-- Deleted existing old clone: $prevCloneVMName"
            }
            
            #Teppi-SendCompletedMail $vmNameKey $cloneName $backupDataStoreName

        }
    }finally{
        Disconnect-VIServer -Server vcenter.teppi.net -Confirm:$false
    }
}

#How to use
#下記のように使う
#Teppi-Backup-VM "okn_demo02" "zz1_" "mangoNFS"