2018年12月15日土曜日

VixDiskLib_QueryAllocatedBlocks と VixDiskLib_Read/Write を組み合わせてサーバのコピー的なことをしてみた

概要

過去にこんな記事を紹介しました
初回のフルバックアップ時には VixDiskLib_QueryAllocatedBlocks を使って使用済みのセクタ情報を取得します
そして Read で実際にディスクの情報を取得して Write で書き込みます
フルバックアップすることでそっくりそのまま同じ vmdk を作ることができるので実質サーバコピー的なことができます
今回は各 API を使ったサンプルコードを紹介します

環境

  • CentOS 7
  • VCSA 6.5.0 9451637
  • VDDK API 6.7.1

サンプルコード

まずはサンプルコード全体です
後で詳しく説明します

#include <iostream>
#include <cstring>
#include "vixDiskLib.h"
using std::cout;
using std::endl;

#define VIXDISKLIB_VERSION_MAJOR 6
#define VIXDISKLIB_VERSION_MINOR 7

static struct {
  VixDiskLibConnection connection;
  char *libdir;
  char *cfgFile;
} params, wparams;

static void LogFunc(const char *fmt, va_list args) {
   printf("Log: ");
   vprintf(fmt, args);
}

static void WarnFunc(const char *fmt, va_list args) {
   printf("Warning: ");
   vprintf(fmt, args);
}

static void PanicFunc(const char *fmt, va_list args) {
   printf("Panic: ");
   vprintf(fmt, args);
   exit(10);
}

int main(int argc, char* argv[]) {
  VixDiskLibHandle _handle = NULL;
  VixDiskLibHandle _whandle = NULL;
  try {
    VixError err;

    err = VixDiskLib_InitEx(VIXDISKLIB_VERSION_MAJOR, VIXDISKLIB_VERSION_MINOR, &LogFunc, &WarnFunc, &PanicFunc, params.libdir, "init.cfg");
    printf("%lu\n", err);
    err = VixDiskLib_InitEx(VIXDISKLIB_VERSION_MAJOR, VIXDISKLIB_VERSION_MINOR, &LogFunc, &WarnFunc, &PanicFunc, wparams.libdir, "init.cfg");
    printf("%lu\n", err);

    VixDiskLibConnectParams cnxParams = {0};
    cnxParams.vmxSpec = {(char*)"moref=vm-10"};
    cnxParams.specType = VIXDISKLIB_SPEC_VMX;
    cnxParams.serverName = {(char*)"192.168.100.20"};
    cnxParams.credType = VIXDISKLIB_CRED_UID;
    cnxParams.creds.uid.userName = {(char*)"administrator@vsphere.local"};
    cnxParams.creds.uid.password = {(char*)"xxxxxxxxxx"};
    cnxParams.thumbPrint = {(char*)"96:09:d6:5b:e0:83:58:1b:ba:2b:cc:78:22:88:33:36:64:50:32:eb"};
    err = VixDiskLib_ConnectEx(&cnxParams, 1, NULL, NULL, &params.connection);
    printf("%lu\n", err);
    VixDiskLibConnectParams wcnxParams = {0};
    wcnxParams.vmxSpec = {(char*)"moref=vm-20"};
    wcnxParams.specType = VIXDISKLIB_SPEC_VMX;
    wcnxParams.serverName = {(char*)"192.168.100.20"};
    wcnxParams.credType = VIXDISKLIB_CRED_UID;
    wcnxParams.creds.uid.userName = {(char*)"administrator@vsphere.local"};
    wcnxParams.creds.uid.password = {(char*)"xxxxxxxxxx"};
    wcnxParams.thumbPrint = {(char*)"96:09:d6:5b:e0:83:58:1b:ba:2b:cc:78:22:88:33:36:64:50:32:eb"};
    err = VixDiskLib_ConnectEx(&wcnxParams, 1, NULL, NULL, &wparams.connection);
    printf("%lu\n", err);

    err = VixDiskLib_Open(params.connection, "[datastore2] src3/src3.vmdk", VIXDISKLIB_FLAG_OPEN_SINGLE_LINK, &_handle);
    printf("%lu\n", err);
    err = VixDiskLib_Open(wparams.connection, "[datastore2] dest3/dest3.vmdk", VIXDISKLIB_FLAG_OPEN_SINGLE_LINK, &_whandle);
    printf("%lu\n", err);

    VixDiskLibBlockList *blocks = new VixDiskLibBlockList;
    VixDiskLibSectorType startSector = 0;
    VixDiskLibSectorType sectorCount = 62914560;
    VixDiskLibSectorType chunk = VIXDISKLIB_SECTOR_SIZE;
    err = VixDiskLib_QueryAllocatedBlocks(_handle, startSector, sectorCount, chunk, &blocks);
    printf("%lu\n", err);
    for (int i = 0; i < blocks->numBlocks; i++) {
      printf("i: %d\n", i);
      VixDiskLibBlock vb = blocks->blocks[i];
      VixDiskLibSectorType offset = vb.offset;
      VixDiskLibSectorType length = vb.length;
      printf("offset: %lu\n", offset);
      printf("length: %lu\n", length);
      VixDiskLibSectorType start = offset;
      VixDiskLibSectorType bufSize = 128;
      VixDiskLibSectorType count = length / bufSize;
      for (VixDiskLibSectorType j = 0; j < count; j++) {
        uint8 *buf = new uint8[bufSize * VIXDISKLIB_SECTOR_SIZE];
        // read
        err = VixDiskLib_Read(_handle, start + (j * bufSize), bufSize, buf);
        if (err != VIX_OK) {
          throw std::exception();
        }
        // write
        err = VixDiskLib_Write(_whandle, start + (j * bufSize), bufSize, buf);
        if (err != VIX_OK) {
          throw std::exception();
        }
        delete[] buf;
      }
    }
    delete[] blocks;

    VixDiskLib_Close(_whandle);
    VixDiskLib_Close(_handle);
    VixDiskLib_Disconnect(params.connection);
  } catch (...) {
    VixDiskLib_Close(_whandle);
    VixDiskLib_Close(_handle);
    VixDiskLib_Disconnect(params.connection);
  }
  return 0;
}

説明

概要図を作成しました
vddk_server_copy3.jpeg

コピー元のサーバ (src (vm-10)) は 30GB のハードディスクが接続されています
ディスクのタイプはシンプロビジョニングで使用量は 6,900,736 KB です
vddk_server_copy2.png
src に対して VixDiskLib_QueryAllocatedBlocks を実行します
src サーバのディスクに対する操作は _handle を使います
またコピー先のサーバ (dest (vm-20)) に対しては別のハンドラ _whandle を使います
なので初期化の段階で VixDiskLib_ConnectExVixDiskLib_Open をそれぞれのハンドラ分コールしています
sectorCount = 62914560 は 30GB / 512 (VIXDISKLIB_SECTOR_SIZE) になります
この結果 98 ブロックの allocated な領域を取得することができます

次にこのブロック分ループさせます
各ブロックは offset と length を持ちます
この 2 つの値を元に実際にディスクからセクタ情報を取得します
Read/Write するスピードは bufSize 変数で調整できます
今回は 128 を指定しています
1 回の Read/Write で bufSize 分のセクタ情報を操作するので次の Read/Write のスタート位置 (offset) は start + (j * bufSize) 分ずらして行う必要があります
また Read/Write するループ回数も bufSize の大きさによって変わるので length / bufSize で割っておきます
bufSize は単純に係数みたいな感じで掛け合わせているだけなのでなくても動きます
ただその場合は bufSize=1 として書き込むためループ回数も length 分ループするので時間がものすごくかかります
ちなみに今回の 7GB 弱のシンプロビジョニングのディスクを bufSize=1 で行うと 1 日以上普通にかかります
なので bufSize は必ず設定したほうが良いです (ただし大きくしすぎると OOM の可能性も上がるのでそこは自身の環境に合わせて調整してください)

すべての処理が終了したらハンドラの Close と vCenter からの Disconnect を必ず行いましょう

実行

コピー先に関しては新規 VM の作成からただ VM を作成しているだけです
ISO などを使って OS をインストールしないでください
vmdk のサイズが 0 bytes の状態の空 VM の状態で作成してください
今回はコピー元に関しては今回は停止した状態で行っています
(起動した状態でもできると思いますが起動中は常にディスクの状態が変わっているので完全に同じディスクの状態のコピーにはならないと思います)

だいたいですが bufSize=128 で 7GB ほどのディスクで 30 分程度で終了しました
bufSize を調整すればもう少し早く終了させることもできると思います
また終了後に dest 側のディスクのサイズを見たのですが src と少し違っていました
vddk_server_copy1.png

すべてのセクタ情報をこの記事で紹介した DumpBytes という関数を使って確認すれば差分がわかるかもしれませんがさすがにそこまではできませんでした

コピー完了後に dest 側のマシンを起動してみたところ src 側のマシンと全く同じ内容で起動しました

その他

Read/Write 時に大量のセクタ情報を読み込もうとすると NBD_ERR_INSUFFICIENT_RESOURCES というエラーが出ることがありました
ESXi の nfc サーバ (?) のメモリ量を増やすみたいな対策方法が紹介されている記事があったのですが試しても解決せず諦めてセクタ情報を小さく書き込むことで対応しました

最後に

VixDiskLib_QueryAllocatedBlocks と Read/Write の関数を使ってサーバコピーを自作してみました
Read/Write のセクタに書き込む処理は並列化できるような気もします
が、ストレージの負荷も上がると思うのでその辺りも要調整かなと思います

今回の処理はいわゆるフルバックアップ的な操作になります
次回は QueryChangedDiskAreas を使って増分バックアップを実現してみたいと思います
QueryChangedDiskAreas の場合 vSphere API になるので実装も少し面倒になると予想しています

0 件のコメント:

コメントを投稿