Visual Studio로 Azure Function 개발하기 [업데이트]

Visual Studio로 Azure Function 개발하기 [업데이트]

Azure Function은 Azure App Service에 포함된 기능 중 하나인 Azure Web Job을 별도의 상품으로 분리하여 출시한 서비스입니다. 하지만 아쉽게도 Visual Studio의 풍부한 IDE 지원을 아직까지도 직접 받을 수 있는 상태는 아닙니다. 또한 Microsoft Docs 등에 공개된 방법도 간접적으로 Azure Function의 설정을 이용한다거나, Node용 CLI를 활용하는 정도에서 언급되는 것이 전부입니다.

소개하려는 내용은 Visual Studio의 콘솔 프로젝트와 Azure Storage Emulator를 이용하여 C# Azure Function을 C# Script가 아닌 통상적인 C# Compiler 기반 프로젝트로 개발과 테스트를 진행하고, 이것을 C# Azure Function으로 마이그레이션하는 방법에 관한 것입니다. 만약 LINQPAD Premium Version을 구입하여 사용 중이라면, 같은 작업을 LINQPAD에서도 실행할 수 있으니 더 적극적으로 Azure Function을 개발하실 수 있을 것입니다.

시작하기

Azure Web Job 기반이기 때문에, 기존에 .NET용으로 출시한 Web Job SDK와 각종 Extension을 Azure Function 사이에는 어느 정도 호환성이 있습니다. 다시 말해서 C# 스크립트로 무언가 새로운 코드를 작성한다기 보다, 기존의 SDK를 C# 스크립트에서 사용할 수 있도록 포장한 것이 Azure Function의 본질에 가깝습니다. 아쉽게도 완전히 같은 코드 베이스는 아니지만, 호환성이 있기 때문에 취할 수 있는 이점이 있고, 그 부분을 활용하는 것입니다.

시작을 위하여 다음의 소프트웨어 스택이 설치되어있는지 점검합니다.

  • Azure Storage Emulator (Azure Cloud Service SDK에 포함되어있습니다.)
  • Visual Studio 2015 Community Edition 이상의 IDE
  • .NET Framework 4.6 이상

만약 Windows 개발 환경이 아닌 경우 Azure Storage Emulator는 제공되지 않기 때문에 어쩔 수 없이 실제 Azure Storage 계정을 만들어 연결해야 합니다. IDE의 경우 Visual Studio Code, Visual Studio for Mac, 또는 Rider를 대신 활용할 수 있습니다. 그리고 Mono를 설치하여 개발을 진행할 수 있습니다. 아쉽게도 .NET Core는 2017년 4월 현재 지원되지 않습니다.

선호하는 IDE로 콘솔 프로젝트를 만든 다음, 다음의 NuGet 패키지들을 설치합니다.

  • Microsoft.Azure.WebJobs (2.0.0 이상)
  • Microsoft.Azure.WebJobs.Extensions (2.0.0 이상)

그 다음 Main 메서드를 다음과 같이 코딩합니다.

var jobHostConfig = new JobHostConfiguration("UseDevelopmentStorage=true");
jobHostConfig.UseCore();
jobHostConfig.UseFiles();
jobHostConfig.UseTimers();
jobHostConfig.UseDevelopmentSettings();

using (var cts = new CancellationTokenSource())
using (var jobHost = new JobHost(jobHostConfig))
{
    jobHost.StartAsync(cts.Token);
    Console.WriteLine("Press Ctrl + C to stop the service.");
    Console.CancelKeyPress += (s, e) => cts.Cancel();
    cts.Token.WaitHandle.WaitOne(Timeout.Infinite);
}

Local Azure Storage Emulator를 사용할 수 있는 Windows 환경에서만 UseDevelopmentStorage=true 연결 문자열을 지정하고, 그 외 환경에서는 실제 Azure Storage Account의 연결 문자열을 해당 속성 블레이드에서 찾아 대입해야 합니다.

그리고 Azure Function에 호스팅하려는 함수를 다음과 같이 코딩합니다.

public static void Run(
    [TimerTrigger("* * * * * *", UseMonitor = true)]
    TimerInfo myTimer,
    TraceWriter log)
{
    log.Info($"C# Timer trigger function executed at: {DateTime.Now}");
}

TimerTrigger가 TimerInfo 메서드 인자에 지정되는 것에 유의하여 위와 같이 코딩합니다. TimerTrigger에 지정되는 첫 인자는 타이머의 실행 간격을 나타냅니다. Crontab에 사용되는 반복 간격 표시 문법을 참조하여 값을 지정하도록 구성하는 것이 Azure Function으로 마이그레이션 할 때 편리하므로 해당 문법을 익히는 것을 권장합니다.

그리고 실행이 잘 되는지 확인하기 위하여, Azure Storage Emulator를 시작하고, F5 키를 눌러 샘플 프로그램을 실행합니다. 다음과 비슷하게 출력되면 정상적으로 실행되는 것입니다.

Press Ctrl + C to stop the service.
Development settings applied
Found the following functions:
TimerSample.Run
Singleton lock acquired (1ce1ebaf1e584866b90488a9e1b5d19f/TimerSample.Run.Listener)
The next 5 occurrences of the schedule will be:
2017-04-24 오전 12:16:59
2017-04-24 오전 12:17:00
2017-04-24 오전 12:17:01
2017-04-24 오전 12:17:02
2017-04-24 오전 12:17:03
Job host started
Executing 'TimerSample.Run' (Reason='Timer fired at 2017-04-24T00:16:59.0273081+09:00', Id=aa02dc0a-5a89-4ebd-bf08-8182cce53a0c)
C# Timer trigger function executed at: 2017-04-24 오전 12:16:59
Executed 'TimerSample.Run' (Succeeded, Id=aa02dc0a-5a89-4ebd-bf08-8182cce53a0c)
Executing 'TimerSample.Run' (Reason='Timer fired at 2017-04-24T00:17:00.0061625+09:00', Id=f8161e5d-c989-4d2d-9a49-cb5d9d269134)
C# Timer trigger function executed at: 2017-04-24 오전 12:17:00
Executed 'TimerSample.Run' (Succeeded, Id=f8161e5d-c989-4d2d-9a49-cb5d9d269134)
...

마이그레이션

이렇게 만들어진 Azure Function이 정말 잘 수행되는지 점검할 때 활용할 수 있는 유용한 서비스가 하나 있습니다. Try Azure App Service를 이용하면 실제 Microsoft Azure 구독과 무관하게, Microsoft 계정 이외에도 Google (GMAIL), Facebook, Github 계정으로 로그인하여 1시간짜리 테스트 Azure Function 계정을 발급받을 수 있습니다.

https://azure.microsoft.com/ko-kr/try/app-service/ 에 방문하여 새로운 계정을 하나 생성합니다.

그 다음, 위의 Run 메서드의 코드를 복사합니다. 단, 몇 가지 복사 전에 수정하거나 확인해야 할 부분이 있습니다.

  • 개발 중에 참조한 NuGet 패키지의 참조를 지정해야 합니다. project.json 파일은 기본적으로 만들어지지 않으므로 다음과 같은 뼈대를 만들고, 현재 개발한 프로젝트 내의 package.config 파일의 내용을 여기로 복사해서 넣어야 합니다. 종속 관계에 따라 자동으로 설치되는 패키지들은 제외하고, 실제로 추가했던 패키지만 지정해서 넣으면 됩니다.
{
  "frameworks": {
    "net46":{
      "dependencies": {
        "Microsoft.ProjectOxford.Face": "1.1.0"
      }
    }
   }
}
  • NuGet 패키지가 아닌 BCL 내 어셈블리 또는 개별 .NET DLL 파일을 참조했을 경우에는 C# 스크립트만의 고유 문법인 #r 지시자를 사용하여 참조를 지정합니다.
    • GAC에 설치했거나 별도로 수동 참조한 .NET DLL 파일은 bin 폴더로 직접 업로드해야 합니다.
    • x86용으로 명시하여 빌드한 DLL이거나, 설치 준비 및 사용 과정에서 시스템 레지스트리 변경 등의 작업이 필요한 경우에는 사용할 수 없습니다.
  • 함수를 옮겨 담기 전에는 메서드 이름과 시그니처가 처음 Azure Function을 만들었을 때와 동일한지 점검합니다. 만약 정상적으로 실행되지 않는다면 function.json 파일의 내용을 참고합니다.
{
  "disabled": false,
  "bindings": [
    {
      "name": "myTimer",
      "type": "timerTrigger",
      "direction": "in",
      "schedule": "0 */5 * * * *"
    }
  ]
}
  • 마지막으로 앞에서 TimerTrigger나 BlobTrigger, 혹은 ServiceBusTrigger와 같이 트리거에 지정한 인자의 값을 확인하여 function.json 파일을 수정하도록 합니다. 위의 예제의 경우 매 초 마다 실행되도록 하였으므로, function.json의 schedule 프로퍼티를 “* * * * * *”으로 바꾸어야 합니다.

마무리

이렇게 해서 만들어진 최종 버전의 CSX 파일을 실제 Azure Function 서비스로 배포하는 것은 자유롭게 할 수 있습니다. 연속성 있는 개발을 위해서, 버전 관리 저장소를 통하여 배포하도록 설정해두면 더욱 편리할 것입니다.

이 글을 작성하면서 좀 더 고민해볼 만한 주제가 있다면, 아래와 같은 부분들이 있을 것 같습니다.

  • HTTP Trigger, Web Hook Trigger는 Web Job과 사실 직접적인 상관이 없으며, ASP.NET Web API의 서브셋에 가깝습니다. 다만 TraceWriter 클래스를 사용하는 부분만이 온전히 Web Job에 관련이 있는 부분입니다. 이 부분을 감안하여 DummyTraceWriter 클래스를 만들어 단위 테스트를 하도록 할 수 있을 듯 합니다.
class DummyTraceWriter : TraceWriter
{
    public DummyTraceWriter() : base(default(TraceLevel)) { }
    public override void Trace(TraceEvent traceEvent) => Console.WriteLine(traceEvent);
}
  • LINQPAD용 스크립트 템플릿을 만들어 공유한다면 정식 SDK가 출시되기 전에 더 많이 Azure Function을 개발하고 테스트할 수 있을 것이라고 생각합니다.
  • 일부 네이티브 코드를 포함하는 NuGet 패키지는 아마도 64비트용으로 빌드된 패키지를 사용하는 것이 실행에 문제가 없을 것으로 예상합니다. 32비트 버전의 패키지도 별도 EXE 파일로 실행하는 경우에는 Windows-on-Windows 호환성 기능으로 실행은 보장될 수 있을 것입니다.

HTTP 트리거에 대한 보충

HTTP 트리거는 Function에서만 존재하는 고유한 기능으로 기존 Web Job 기반의 개발 방식과는 차이가 있습니다. HTTP 요청을 수신 대기하고 있다가, HTTP 응답을 되돌려주는 출력 기능으로 제공됩니다. 아쉽게도, HTTP 응답으로 내보내는 출력만을 지원하고 있고, 2017년 5월 현재 독립적으로 다른 곳에서 Web Invocation을 호출하는 출력은 지원하지 않습니다.

제가 이 아티클에서 소개한 방법은 Azure의 여러 컴포넌트에 연계하여 Function을 간접적으로 호출하는 트리거 방식의 로컬 개발에 대해 다룬 것입니다.

만약 Web Job의 형태가 아니라 직접 호출할 수 있는 HTTP 트리거 기반의 개발을 필요로 하시는 경우, 유정협 님의 아티클을 참조하시어 ASP.NET 웹 프로젝트로 Azure Function App을 개발하고 Function App에 퍼블리싱하는 방법을 택하는 것을 권장합니다.

자세한 내용은 http://blog.aliencube.org/ko/2017/04/30/precompiled-azure-functions-revisited/ 페이지를 참조하여 주십시오.

더 세부적인 사항, 보충할 부분, 혹은 수정해야 할 부분에 대한 의견을 주시면 큰 도움이 될 것 같습니다.

 

댓글 남기기