[ClouDeveloper News – Azure Edition] 2016년 12월 15일

클라우드 컴퓨팅을 중심으로 관련된 여러 기술과 업계 소식을 매주 전하는 ClouDeveloper News를 시작합니다. 파일럿 프로그램으로 구상하여 운영 중에 있으며 추후 여러 피드백과 의견 수렴을 통하여 프로그램의 틀을 갖추어 나갈 예정이오니 많은 관심과 구독을 부탁드립니다.

이번주 커뮤니티 소식

Azure 서비스 공지 사항

  • General availability: Package Management extension for Visual Studio Team Services
    • Visual Studio Team Service에서 NuGet Package와 NPM Package를 관리할 수 있는 익스텐션 서비스가 새로 출시되었습니다. Visual Studio Marketplace에서 청약할 수 있는 상품입니다.
  • General availability: Azure IoT Gateway SDK
    • 기존 하드웨어나 인프라를 교체하지 않고, 게이트웨이 장치를 개발하여 Azure IoT와 자연스럽게 연동될 수 있도록 만들 수 있습니다. 오픈 소스로 공개된 Azure IoT Gateway SDK를 사용하여 기존 장치가 Azure IoT Hub와 직접 통신할 수 있는 여건이 되지 않더라도 원격에서 장치를 제어하거나 상태를 보고하는 대행 게이트웨이 하드웨어를 더 쉽게 개발할 수 있습니다.
  • With general availability, enhancements abound in Azure IoT Hub Device Management
    • Azure IoT Hub에서 그동안 강조되지 않았거나 샘플 코드 수준으로만 제공되어오던 “장치 관리”에 관련된 기능과 API가 GA로 전환되었습니다. 예약 작업, 다이렉트 메서드, 쿼리, 디바이스 트윈 API를 사용하여 좀 더 많은 장치 제어 시나리오를 커버할 수 있게 되었습니다.
  • Latest update of Azure Analysis Services preview brings scale up and down
    • Azure Analysis Service의 인스턴스 크기를 조정할 수 있는 기능이 포털에서 새롭게 제공됩니다. 상황에 따라 강력한 성능 발휘를 위하여 인스턴스 등급이나 규모를 늘리고, 사용하지 않는 때에는 최소한으로 낮추어 경제적으로 Azure Analysis Service를 활용할 수 있게 됩니다.

교육 자료

새로운 제품 및 서비스

  • Introducing Windows Server & SQL Server Premium Assurance
    • 지원이 중단될 예정인 Windows Server와 SQL Server에 대한 보안 업데이트 제공 서비스가 유상으로 제공됨에 따라 기존 라이프사이클에서 최대 6년까지 지원이 연장됩니다. 신규 플랫폼과 서비스로 이행을 하기에 시간적 여유가 없는 경우 선택할 수 있는 옵션입니다. Windows Server 2008, Windows Server 2008 R2, SQL Server 2008, SQL Server 2008 R2가 첫 지원 대상입니다.
  • Announcing SQL Server Management Studio – 16.5.1 Release
    • SQL Server Management Studio의 최신 버전인 16.5.1이 릴리스되었습니다. 제품 출시 후 발견된 문제점들을 수정한 패치 성격의 업데이트 버전입니다.

활용 및 노하우

웹 캐스트

  • SQL Server + Java: What’s new
    • 새 Microsoft JDBC 드라이버를 통해 Java 기반 애플리케이션에서 SQL Server에 접속하여 쓸 수 있는 새로운 기능들을 소개하는 웹 캐스트입니다. 최근 JDBC 드라이버의 소스 코드를 GitHub 리포지터리에 게시하여 오픈소스화 하였으며, Maven 리포지터리를 통하여 JDBC 드라이버에 대한 종속성을 간편하게 추가할 수 있게 되었습니다.
  • Azure Media Indexer 2: Japanese support, punctuation improvements, no more time limit
    • 동영상에서 화자가 말하는 언어를 음성 인식하여 자막을 만들고 동영상 검색의 기본 데이터를 형성하는데 도움을 주는 Azure Media Indexer의 새 버전에서는 Microsoft Research의 연구 성과를 바탕으로 지속적으로 품질과 성능을 개선하고 있으며, 이번 릴리스에서는 일본어 음성 인식 지원, 10분 길이 제한 해제, 발음과 문법 품질 향상을 주안점으로 두고 있습니다.
  • Get Started with Azure Functions
    • Azure Function에 대한 튜토리얼 웹 캐스트입니다.
  • Digital marketing solutions on Azure
    • Orchard, Umbraco와 같은 유명 CMS 솔루션을 Azure 위에서 기술적인 지식 없이 손쉽게 구축하여 디지털 마케팅 전략에 활용할 수 있는 방안을 소개하는 웨비나 세션을 소개합니다.
  • Dev and test better in the cloud
    • Azure 및 클라우드 환경에서 개발과 테스트 전략을 더 효율적으로 수행할 수 있는 방법을 소개하는 무료 웹 캐스트입니다.

서드파티 소식

  • Kafka Connect for Azure IoT Hub
    • Azure IoT Hub에 연결하여 데이터를 Kafka로 보낼 수 있는 Connector의 오픈 소스 버전이 새롭게 공개되었습니다.

ClouDeveloper 페이스북 페이지에 댓글로 의견을 남겨주시면 뉴스 발행 및 각종 정보 전달에 반영하도록 하겠습니다. 고맙습니다.

의견 남기기: https://fb.com/cloudeveloper

Windows Azure 개발: 간단한 방명록 만들기 [Step 3]

이전 글 보기: Windows Azure 개발: 간단한 방명록 만들기 [Step 2] / 소스 코드 수정


 


이전 시간에 이어서, 오늘은 방명록에 업로드한 이미지를 백그라운드에서 실시간으로 처리하는 Worker Role을 작성해보도록 하겠습니다. 방명록에 업로드할 수 있는 이미지의 종류가 다양하고, 간혹 디지털 카메라에서 촬영한, 웹에 업로드하기에는 적합하지 않은 고해상도의 이미지를 처리한다는 시나리오를 세워볼 수 있을 것입니다.


 


만들었던 Windows Azure 프로젝트를 솔루션 탐색기에서 찾아, Roles 노드를 오른쪽 버튼으로 클릭하여 새 Worker Role 추가 메뉴를 클릭한 후, 아래와 같이 Worker Role의 이름을 Guestbook_WorkerRole로 지정하고 새로 생성합니다.


 



 


Worker Role을 추가한 후에는, Storage에 만든 Table 자료를 Worker Role에서도 동일하게 액세스할 수 있도록, GuestBook_WorkerRole 프로젝트를 솔루션 탐색기에서 오른쪽 버튼으로 클릭한 후, “참조 추가” 메뉴를 클릭하여 참조 추가 대화 상자를 엽니다.


 


1. 참조 추가 대화 상자에서, “프로젝트” 탭을 선택하고, 기존에 만들어두었던 GuestBook_Data 프로젝트를 지정합니다.


2. 이미지를 처리해야 하므로, GDI+ API를 포함하는 System.Drawing 어셈블리가 필요합니다. 마찬가지로 참조 추가 대화 상자에서 System.Drawing 어셈블리를 추가합니다.


 


참조 추가를 끝낸 후에는 WorkerRole.cs 파일 상단에 네임스페이스 참조를 아래와 같이 추가합니다.


 

using System.IO;
using System.Drawing;
using GuestBook_Data;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;

 


그리고 Worker Role 클래스의 멤버 변수로 아래와 같이 추가합니다.


 

private CloudQueue queue;
private CloudBlobContainer container;

 


그리고 OnStart 메서드에는 다음과 같이 구현합니다.


 

public override bool OnStart()
{
DiagnosticMonitor.Start(“DiagnosticsConnectionString”);

// Restart the role upon all configuration changes
RoleEnvironment.Changing += RoleEnvironmentChanging;

// read storage account configuration settings
CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
{
configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
});
var storageAccount = CloudStorageAccount.FromConfigurationSetting(“DataConnectionString”);

// initialize blob storage
CloudBlobClient blobStorage = storageAccount.CreateCloudBlobClient();
container = blobStorage.GetContainerReference(“guestbookpics”);

// initialize queue storage
CloudQueueClient queueStorage = storageAccount.CreateCloudQueueClient();
queue = queueStorage.GetQueueReference(“guestthumbs”);

Trace.TraceInformation(“Creating container and queue…”);

bool storageInitialized = false;
while (!storageInitialized)
{
try
{
// create the blob container and allow public access
container.CreateIfNotExist();
var permissions = container.GetPermissions();
permissions.PublicAccess = BlobContainerPublicAccessType.Container;
container.SetPermissions(permissions);

  // create the message queue
  queue.CreateIfNotExist();
  storageInitialized = true;
}
catch (StorageClientException e)
{
  if (e.ErrorCode == StorageErrorCode.TransportError)
  {
    Trace.TraceError("Storage services initialization failure. "
      + "Check your storage account configuration settings. If running locally, "
      + "ensure that the Development Storage service is running. Message: '{0}'", e.Message);
    System.Threading.Thread.Sleep(5000);
  }
  else
  {
    throw;
  }
}

}

return base.OnStart();
}


 


위의 코드에서, BLOB 저장소와 큐 저장소에 대한 정보를 초기화 단계에서 확보하는 것을 볼 수 있습니다. Worker Role의 관점에서 보면, BLOB 저장소는 처리 대상 이미지와 처리 결과 이미지를 입력받거나 출력하는 대상이고, 큐 저장소를 통하여 작업을 지시받고 결과를 반환할 수 있습니다.


 


그리고 아래와 같이 Run 메서드를 작성합니다.


 

public override void Run()
{
Trace.TraceInformation(“Listening for queue messages…”);

while (true)
{
try
{
// retrieve a new message from the queue
CloudQueueMessage msg = queue.GetMessage();
if (msg != null)
{
// parse message retrieved from queue
var messageParts = msg.AsString.Split(new char[] { ‘,’ });
var uri = messageParts[0];
var partitionKey = messageParts[1];
var rowkey = messageParts[2];
Trace.TraceInformation(“Processing image in blob ‘{0}’.”, uri);

    // download original image from blob storage
    CloudBlockBlob imageBlob = container.GetBlockBlobReference(uri);
    MemoryStream image = new MemoryStream();
    imageBlob.DownloadToStream(image);
    image.Seek(0, SeekOrigin.Begin);

    // create a thumbnail image and upload into a blob
    string thumbnailUri = String.Concat(Path.GetFileNameWithoutExtension(uri), "_thumb.jpg");
    CloudBlockBlob thumbnailBlob = container.GetBlockBlobReference(thumbnailUri);
    thumbnailBlob.UploadFromStream(CreateThumbnail(image));

    // update the entry in table storage to point to the thumbnail
    var ds = new GuestBookEntryDataSource();
    ds.UpdateImageThumbnail(partitionKey, rowkey, thumbnailBlob.Uri.AbsoluteUri);

    // remove message from queue
    queue.DeleteMessage(msg);

    Trace.TraceInformation("Generated thumbnail in blob '{0}'.", thumbnailBlob.Uri);
  }
  else
  {
    System.Threading.Thread.Sleep(1000);
  }
}
catch (StorageClientException e)
{
  Trace.TraceError("Exception when processing queue item. Message: '{0}'", e.Message);
  System.Threading.Thread.Sleep(5000);
}

}
}


 


while 구문을 통하여, 큐로부터 메시지를 수신하고, BLOB 스토리지로부터 이미지 데이터를 로드하여 처리하는 과정을 코드로 작성한 것입니다. 처리가 끝난 후에는, 큐에 쌓여있는 메시지를 제거하고 다음 메시지를 수신할 때 까지 대기하게 됩니다.


 


만약 윗 부분의 코드를 비동기 패턴으로 작성한다면, 좀 더 효율적으로 동작하는 Worker Role 인스턴스를 만들 수도 있을 것입니다. (Worker Role 하나가 처리할 수 있는 가용성의 범위는 VM Size에 따라 달라질 수 있지만 보통의 경우, 많은 사용자가 접속을 하게 되더라도 거의 무리 없이 소화가 가능할 것입니다.)


 


그리고, 위의 코드에서 실제 썸네일 생성을 담당하는 코드는 아래와 같습니다.


 

private Stream CreateThumbnail(Stream input)
{
var orig = new Bitmap(input);
int width;
int height;

if (orig.Width > orig.Height)
{
width = 128;
height = 128 * orig.Height / orig.Width;
}
else
{
height = 128;
width = 128 * orig.Width / orig.Height;
}

var thumb = new Bitmap(width, height);

using (Graphics graphic = Graphics.FromImage(thumb))
{
graphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
graphic.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
graphic.DrawImage(orig, 0, 0, width, height);
var ms = new MemoryStream();
thumb.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
ms.Seek(0, SeekOrigin.Begin);
return ms;
}
}


 


이로서, Worker Role에 대한 구현도 끝이 났습니다. 아직 한 가지가 더 남았는데, 바로 Web Role과 Worker Role을 이어주기 위하여 Web Role에도 큐에 대한 처리가 들어가야 한다는 점입니다. 이제 다시 Web Role 프로젝트로 돌아가서 코드를 조금 수정해보도록 하겠습니다.


 


메인 웹 페이지 (Default.aspx)의 코드 비하인드에, 아래와 같이 정적 변수를 추가합니다.


 

private static CloudQueueClient queueStorage;

 


그리고, 파일을 업로드할 때의 버튼 클릭 이벤트에, 기존에 작성하였던 BLOB에 이미지를 저장하는 코드 아랫 부분에, 큐에 메시지를 넣는 부분을 좀 더 추가합니다. 아래와 같을 것입니다.


 

protected void SignButton_Click(object sender, EventArgs e)
{
if (FileUpload1.HasFile)
{
InitializeStorage();

...

// create a new entry in table storage
GuestBookEntry entry = new GuestBookEntry() { GuestName = NameTextBox.Text, Message = MessageTextBox.Text, PhotoUrl = blob.Uri.ToString(), ThumbnailUrl = blob.Uri.ToString() };
GuestBookEntryDataSource ds = new GuestBookEntryDataSource();
ds.AddGuestBookEntry(entry);
System.Diagnostics.Trace.TraceInformation("Added entry {0}-{1} in table storage for guest '{2}'", entry.PartitionKey, entry.RowKey, entry.GuestName);



// added code

// queue a message to process the image
var queue = queueStorage.GetQueueReference("guestthumbs");
var message = new CloudQueueMessage(String.Format("{0},{1},{2}", uniqueBlobName, entry.PartitionKey, entry.RowKey));
queue.AddMessage(message);


System.Diagnostics.Trace.TraceInformation("Queued message to process blob '{0}'", uniqueBlobName);

}

NameTextBox.Text = “”;
MessageTextBox.Text = “”;

DataList1.DataBind();
}


 


그리고, InitializeStorage 메서드도 조금 수정하면 끝이 납니다.


 

private void InitializeStorage()
{

try
{
  ...

  // configure container for public access
  var permissions = container.GetPermissions();
  permissions.PublicAccess = BlobContainerPublicAccessType.Container;
  container.SetPermissions(permissions);



  // added code

  // create queue to communicate with worker role
  queueStorage = storageAccount.CreateCloudQueueClient();
  CloudQueue queue = queueStorage.GetQueueReference("guestthumbs");
  queue.CreateIfNotExist();
}
catch (WebException)
{
   ...
}

storageInitialized = true;

}
}


 


이제 완성된 Cloud Application을 직접 테스트해보겠습니다. F5 키를 눌러서 디버그를 시작합니다. 코드 상에 오류가 있을 경우 컴파일 오류나 경고가 발생할 수 있으며, 이러한 부분들을 모두 해결해야 합니다. 정상적으로 디버그 모드에 들어가게 되면, 작업 표시줄의 트레이 아이콘 영역에 아래와 같이 Development Fabric 트레이 아이콘이 나타납니다.


 



 


Web Role 프로젝트가 있으므로, Internet Explorer 또한 같이 시작될 것입니다. Web Role의 실행 결과를 보여주는 Internet Explorer 창이 아래와 같이 나타나는지 확인합니다.


 



 


방명록을 작성하고, 첨부할 사진으로 고해상도의 사진을 업로드한 후, 연필 모양의 이미지를 클릭하면 업로드가 됩니다. 테스트해볼만한 고해상도 사진을 찾으려면, Windows XP 이상의 운영 체제들은 모두 %windir%WebWallpapers 폴더의 이미지를 사용하면 됩니다.


 



 


이미지를 업로드한 직후에는 이미지가 있는 그대로 표시됩니다. 하지만 타이머를 이용하여 주기적으로 Refresh 하도록 페이지를 구성하였기 때문에, 잠시 후 (약 5초 후)에는 아래와 같이 알맞게 크기 조절된 이미지가 대신 표시되는 것을 볼 수 있습니다.


 



 


세 번의 포스팅에 걸쳐서 Windows Azure로 만드는 간단한 방명록 예시를 살펴보았습니다. 2010년 2월 27일에 있을 Exploring Windows Azure 세미나 ([세미나] Exploring Windows Azure)를 통하여 실제로 프로그래밍하는 모습을 보여드릴 수 있을 것이니 많은 참석 부탁드립니다.


 


좀 더 다양한 Windows Azure 관련 예제를 필요로 하신다면, Windows Azure Hands-on-Lab을 이용하실 수 있습니다. Windows Azure Hands-on-Lab은 http://go.microsoft.com/fwlink/?LinkID=130354 에서 다운로드 가능합니다.


 


긴 글을 읽어주셔서 감사드리며, 궁금하신 점은 rkttu nospam rkttu dot com을 통하여 연락하여 주십시오. 2월 27일 세미나에서 뵙겠습니다. 감사합니다. 🙂