Теперь, когда мы имеем созданную в Облаке с установленным на нее 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, в том же центре обработки данных, что и облачная виртуалка, для сокращения накладных расходов.
Image may be NSFW.
Clik here to view.
Рис.1
Clik here to view.
Рис.2
Clik here to view.
Рис.3
Отдельный Storage Account создавался довольно долго, обозначаясь при этом на портале однообразным статусом Creating… Нажатие Refresh в окне браузера обновило статус на ResolvingDns… Наконец (> 5 мин.), очередной Refresh показал, что Storage Account успешно создан:
Clik here to view.
Рис.4
Внутри Azure Storage данные могут храниться в виде блобов или таблиц - см. Azure Data Management and Business Analytics. Таблицы не являются таблицами в строгом реляционном понимании. Это просто слабо структурированные наборы пар ключ-значение подобно тому, что когда-то называлось SQL Data Services - см. Введение в SQL Azure. По сравнению с SDS нынешние таблицы могут партиционироваться по ключу. Разные партиции хранятся на разных машинах в Облаке, чем достигается горизонтальное масштабирование, как при шардинге в случае SQL Azure Database. Блобы бывают блочные и страничные. Структура блочных блобов оптимизирована для подокового доступа, страничных – для случайного чтения/записи. Страничная структура позволяет запсать в блоб диапазон байтов. Подробно разница между ними объясняется здесь. Например, виртуальные диски хранятся как страничные блобы. Хранение блобов осуществляется внутри контейнеров, которые создаются в рамках Storage Account. Создадим в эккаунте tststorage контейнер под хранение AdventureWorks2012.bak:
Clik here to view.
Рис.5
Clik here to view.
Рис.6
Публичный контейнер позволяет видеть любому желающему содержащиеся в нем блобы. Публичный блоб позволяет любому желающему доступаться к любому блобу, но содержание контейнера недоступно. Наконец, частный контейнер означает, что для доступа к блобу потребуется указывать ключ Storage Account. Изменить впоследствии уровень доступа к контейнеру можно при помощи кнопки Edit Container:
Clik here to view.
Рис.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), их можно посмотреть в свойствах контейнера:
Clik here to view.
Рис.9
Clik here to view.
Рис.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 ТБ. Пример на запись/чтение диапазона страниц страничного блоба приводится здесь.
Алексей Шуленин
Image may be NSFW.Clik here to view.