Миграция БД на Microsoft Azure SQL VM. BLOB Storage + REST

Теперь, когда мы имеем созданную в Облаке с установленным на нее SQL Server и умеем со стороны клиента с ним соединяться, как с локальным SQL Server, остается наполнить его данными. Предположим, в рамках гибридного сценария часть БД планируется перенести на Azure SQL VM. В этой статье будет рассматриваться сценарий, когда БД обособляется в виде файла (или нескольких файлов) посредством создания ее резервной копии, detach, data-tier application и т.д., файл доставляется на Azure SQL VM и превращается обратно в базу путем восстановления из бэкапа, attach, deploy/import data-tier application и т.д. Первое и последнее действие не вызывают вопросов у DBA. Осталось понять, как лучше доставить отчужденный файл с базой (.bak, .mdf, .bacpac, …) на облачную виртуалку с SQL Server.

Для примера перенесем любимую базу данных AdventureWorks в виде ее резервной копии:

backup database AdventureWorks2012 to disk = 'c:\Temp\AdventureWorks2012.bak' with init, compression, stats = 10 Скрипт 1

Файлы небольших размеров, как этот, можно, не мудрствуя лукаво, переносить обычным Copy/Paste на удаленный рабочий стол виртуальной машины SQL Server. Еще в голову приходит сделать на виртуалке папку общего доступа и скопировать туда, используя продвинутые средства копирования с возможностью распараллеливания, коррекции и взобновления в случае сбоев, а также передать файл по FTP. Эти способы очевидны. В данном посте мы задействуем иной способ: передадим файл бэкапа с локальной машины в Azure Storage в виде блоба и скачаем его оттуда внутрь облачной виртуалки. У нас уже имеется один Storage Account, созданный автоматически при создании виртуальной машины, в котором был автоматически контейнер по имени vhds, в котором в виде блоба хранится виртуальный диск нашей виртуальной машины. Для чистоты эксперимента создадим новый Storage Account под названием tststorage в том же центре обработки данных, что и облачная виртуалка, для сокращения накладных расходов.

Внутри Azure Storage данные могут храниться в виде блобов или таблиц — см. Azure Data Management and Business Analytics в документации Microsoft Azure. Таблицы не являются таблицами в строгом реляционном понимании. Это просто слабо структурированные наборы пар ключ-значение подобно тому, что когда-то называлось SQL Data Services — см. Введение в SQL Azure. По сравнению с SDS нынешние таблицы могут партиционироваться по ключу. Разные партиции хранятся на разных машинах в Облаке, чем достигается горизонтальное масштабирование, как при шардинге в случае SQL Azure Database. Блобы бывают блочные и страничные. Структура блочных блобов оптимизирована для подокового доступа, страничных – для случайного чтения/записи. Страничная структура позволяет запсать в блоб диапазон байтов. Подробно разница между ними объясняется, например, здесь —blogs.msdn.com/b/windowsazurestorage/archive/2010/04/11/using-windows-azure-page-blobs-and-how-to-efficiently-upload-and-download-page-blobs.aspx. Виртуальные диски хранятся как страничные блобы. Хранение блобов осуществляется внутри контейнеров, которые создаются в рамках Storage Account. Создадим в эккаунте tststorage контейнер container1 под хранение AdventureWorks2012.bak.

Публичный контейнер позволяет видеть любому желающему содержащиеся в нем блобы. Публичный блоб позволяет любому желающему доступаться к любому блобу, но содержание контейнера недоступно. Наконец, частный контейнер означает, что для доступа к блобу потребуется указывать ключ Storage Account. Изменить впоследствии уровень доступа к контейнеру можно при помощи кнопки Edit Container.

Сделанную в Скрипте 1 резервную копию базы для простоты будем загружать в Azure Storage как блочный блоб. Для операций над блобами в Облаке (равно как и над таблицами, и очередями) можно использовать REST, что позволяет работать напрямую через Интернет (HTTP Request/Response), привлекая широкий диапазон средств разработки. REST API для работы с блобами описывается здесь — msdn.microsoft.com/en-us/library/dd135733.aspx. Так можно посмотреть, какие блобы лежат в публичном контейнере: tststorage.blob.core.windows.net/container1?restype=container&comp=list
Контейнер container1 сейчас пуст. Чтобы загрузить в него AdventureWorks2012.bak, нужно использовать метод PUT:

using System; using System.Net; using System.IO; using System.Security.Cryptography; using System.Text; using System.Globalization; class Program { static void Main(string[] args) { string fileFullName = @«c:\Temp\AdventureWorks2012.bak»; //@«c:\Temp\aaa.txt»; string storageAccount = «tststorage»; string containerName = «container1»; string accessKey = «xws7rilyLjqdw8t75EHZbsIjbtw YDvpZw790lda0L1PgzEqKHxGNIDdCdQlPEvW5LdGWK/qOZFTs5xE4P93A5A==»; HttpWebRequest req = (HttpWebRequest)WebRequest.Create (String.Format(«https://{0}.blob.core.windows.net/{1}/{2}», storageAccount, containerName, Path.GetFileName(fileFullName))); FileStream fs = File.OpenRead(fileFullName); byte[] fileContent = new byte[fs.Length]; fs.Read(fileContent, 0, fileContent.Length); fs.Close(); req.Method = «PUT»; req.ContentLength = fileContent.Length; req.Headers.Add(«x-ms-blob-type», «BlockBlob»); req.Headers.Add(«x-ms-date», DateTime.UtcNow.ToString («R», CultureInfo.InvariantCulture)); req.Headers.Add(«x-ms-version», «2011-08-18»); string canonicalizedString = BuildCanonicalizedString(req, String.Format("/{0}/{1}/{2}", storageAccount, containerName, Path.GetFileName(fileFullName))); req.Headers[«Authorization»] = CreateAuthorizationHeader (canonicalizedString, storageAccount, accessKey); req.Timeout = 100 * 60 * 1000; Stream s = req.GetRequestStream(); s.Write(fileContent, 0, fileContent.Length); DateTime dt = DateTime.Now; req.GetResponse(); System.Diagnostics.Debug.WriteLine(DateTime.Now — dt); } static string CreateAuthorizationHeader (string canonicalizedString, string storageAccount, string accessKey) { HMACSHA256 hmacSha256 = new HMACSHA256 (Convert.FromBase64String(accessKey)); byte[] dataToHMAC = Encoding.UTF8.GetBytes (canonicalizedString); string signature = Convert.ToBase64String (hmacSha256.ComputeHash(dataToHMAC)); return «SharedKey » + storageAccount + ":" + signature; } static string BuildCanonicalizedString (HttpWebRequest req, string canonicalizedResource) { StringBuilder sb = new StringBuilder(); sb.Append(req.Method + "\n\n\n"); sb.Append(String.Format ("{0}\n\n\n\n\n\n\n\n\n", req.ContentLength)); sb.Append(«x-ms-blob-type:» + req.Headers[«x-ms-blob-type»] + '\n'); sb.Append(«x-ms-date:» + req.Headers[«x-ms-date»] + '\n'); sb.Append(«x-ms-version:» + req.Headers[«x-ms-version»] + '\n'); sb.Append(canonicalizedResource); return sb.ToString(); } } Скрипт 2

В этом коде все достаточно очевидно за исключением, пожалуй, одного момента. Несмотря на то, что контейнер container1 был создан как публичный, запись блоба требует авторизации. Кто и какие операции может выполнять над блобами и контейнерами в зависимости от установленного уровня доступа описывается здесь —msdn.microsoft.com/en-us/library/dd179354.aspx. Вне зависимости от уровня доступа право на запись имеет владелец. Чтобы авторизоваться как владелец в HTTP Request требуется установить заголовок Authorization. Строка, записываемая в этот заголовок, в соответствии с требованиями схем аутентификации содержит подпись, которая представляет собой Hash-based Message Authentication Code (HMAC) канонизированной строки в кодировке UTF-8, где хэш вычисляется по алгоритму SHA256 на основе ключа доступа. Канонизированная строка складывается из метода доступа REST, размера загружаемого файла, типа блоба (x-ms-blob-type = блочный или страничный) даты/времени HTTP-запроса в формате UTC (x-ms-date), даты версии блобовского сервиса Azure, обслуживающего данный HTTP-запрос (x-ms-version) и др. Здесь не требуется блистать высоким программерским искусством, нужна лишь кропотливость и внимательность, т.к. малейшая неаккуратность при формировании канонизированной строки неумолимо влечет ошибку HTTP 403 Forbidden.
Ключи доступа (основной и запасной) формируются на этапе создания Storage Account, их можно посмотреть в свойствах контейнера (Manage Keys). Любой из них можно задавать в качестве accessKey для создания цифровой подписи при авторизации — HMACSHA256 hmacSha256 = new HMACSHA256(Convert.FromBase64String(accessKey));
Для более гранулярнного управления правами можно использовать подпись общего доступа (Shared Access Signature). Подпись общего доступа позволяет создать политику, позволяющую выполнять определенную операцию, например, запись внутри определенного контейнера в пределах отведенного промежутка времени. Человек, которому вручается подпись, будет способен действовать в рамках этой политики. Другая подпись, например, может уполномачивать читать из другого контейнера в течение другого периода.

Прочие комментарии.

  • Если блоб с таким именем в контейнере существует, он молчаливо перетирается.
  • Имя контейнера чувствительно к регистру.
  • Время загрузки, очевидно, зависит от скорости сетки. Например, с работы данный 45-меговый бэкап залился со свистом за 00:01:07. Из дома получалось в разы медленнее. 

В данном демонстрационном примере бэкап имел достаточно «детский» размер. Блочные блобы ограничены размером в 200 ГБ. Блочный блоб размером менее 64 МБ может быть загружен одной операцией записи, как мы наблюдали в примере Скрипт 2. В противном случае следует разбивать его на куски и загружать поблочно с использованием методов Put Block / Put Block List. При заливке в Azure Storage крупных файлов следует применять страничные блобы. Страничный блоб состоит из 512-байтных страниц, его максимальный размер составляет 1 ТБ. Пример на запись/чтение диапазона страниц страничного блоба приводится здесь — blogs.msdn.com/b/windowsazurestorage/archive/2010/04/11/using-windows-azure-page-blobs-and-how-to-efficiently-upload-and-download-page-blobs.aspx.

Автор статьи: Алексей Шуленин.