Миграция БД на Windows 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, в том же центре обработки данных, что и облачная виртуалка, для сокращения накладных расходов.
Рис.1
Рис.2
Рис.3
Отдельный Storage Account создавался довольно долго, обозначаясь при этом на портале однообразным статусом Creating… Нажатие Refresh в окне браузера обновило статус на ResolvingDns… Наконец (> 5 мин.), очередной Refresh показал, что Storage Account успешно создан:
Рис.4
Внутри Azure Storage данные могут храниться в виде блобов или таблиц - см. Azure Data Management and Business Analytics. Таблицы не являются таблицами в строгом реляционном понимании. Это просто слабо структурированные наборы пар ключ-значение подобно тому, что когда-то называлось SQL Data Services - см. Введение в SQL Azure. По сравнению с SDS нынешние таблицы могут партиционироваться по ключу. Разные партиции хранятся на разных машинах в Облаке, чем достигается горизонтальное масштабирование, как при шардинге в случае SQL Azure Database. Блобы бывают блочные и страничные. Структура блочных блобов оптимизирована для подокового доступа, страничных – для случайного чтения/записи. Страничная структура позволяет запсать в блоб диапазон байтов. Подробно разница между ними объясняется здесь. Например, виртуальные диски хранятся как страничные блобы. Хранение блобов осуществляется внутри контейнеров, которые создаются в рамках Storage Account. Создадим в эккаунте tststorage контейнер под хранение AdventureWorks2012.bak:
Рис.5
Рис.6
Публичный контейнер позволяет видеть любому желающему содержащиеся в нем блобы. Публичный блоб позволяет любому желающему доступаться к любому блобу, но содержание контейнера недоступно. Наконец, частный контейнер означает, что для доступа к блобу потребуется указывать ключ Storage Account. Изменить впоследствии уровень доступа к контейнеру можно при помощи кнопки Edit Container:
Рис.7
Сделанную в Скрипте 1 резервную копию базы для простоты будем загружать в Azure Storage как блочный блоб. Для операций над блобами в Облаке (равно как и над таблицами, и очередями) можно использовать REST, что позволяет работать напрямую через Интернет (HTTP Request/Response), привлекая широкий диапазон средств разработки. REST API для работы с блобами описывается здесь. Например, так можно посмотреть, какие блобы лежат в публичном контейнере: http://tststorage.blob.core.windows.net/container1?restype=container&comp=list
Рис.8
Контейнер 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 = "xws7rilyLjqdw8t75EHZbsIjbtwYDvpZw790lda0L1PgzEqKHxGNIDdCdQlPEvW5LdGWK/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 был создан (Рис.6) как публичный, запись блоба требует авторизации. Кто и какие операции может выполнять над блобами и контейнерами в зависимости от установленного уровня доступа описывается здесь. Вне зависимости от уровня доступа право на запись имеет владелец. Чтобы авторизоваться как владелец в 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 (Рис.3), их можно посмотреть в свойствах контейнера:
Рис.9
Рис.10
Любой из них можно задавать в качестве 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 ТБ. Пример на запись/чтение диапазона страниц страничного блоба приводится здесь.
Алексей Шуленин