Durable Functions 앱을 인프로세스에서 격리 워커 모델(.NET)로 마이그레이션하기

이 가이드에서는 .NET Durable Functions 앱을 in-process 모델에서 격리된 작업자 모델로 마이그레이션하는 과정을 안내합니다. In-process 모델은 2026년 11월 10일에 지원이 종료됩니다. 해당 날짜 이후에는 보안 업데이트 또는 버그 수정이 제공되지 않습니다. 또한 격리된 작업자 모델은 전체 프로세스 제어, 표준 .NET 종속성 주입 및 최신 플랫폼 기능에 대한 액세스를 제공합니다.

경고

In-process 모델에 대한 지원은 2026년 11월 10일에 종료됩니다. 지금 마이그레이션하는 것이 좋습니다. 격리된 작업자 모델에 대한 배경 정보는 .NET 격리된 작업자 프로세스 개요 참조하세요.

마이그레이션 검사 목록

다음 검사 목록을 사용하여 각 마이그레이션 단계를 통해 진행 상황을 추적합니다.

Step 섹션
1. 필수 구성 요소 확인 사전 요구 사항
2. 프로젝트 파일 업데이트 프로젝트 파일 업데이트
3. Program.cs 추가 Program.cs 추가
4. 패키지 참조 업데이트 패키지 참조 업데이트
5. 함수 코드 업데이트 함수 코드 업데이트
6. local.settings.json 업데이트 local.settings.json 파일을 업데이트하세요
7. 로컬로 테스트 로컬로 테스트
8. Azure 배포 Azure에 배포

사전 요구 사항

  • Azure Functions Core Tools v4.x 이상
  • .NET 8.0 SDK(또는 대상 .NET 버전)
  • Visual Studio 2022 또는 Azure Functions 확장이 있는 VS 코드

마이그레이션할 앱 식별(선택 사항)

어떤 앱이 여전히 in-process 모델을 사용하는지 잘 모르는 경우 다음 Azure PowerShell 스크립트를 실행합니다.

$FunctionApps = Get-AzFunctionApp

$AppInfo = @{}

foreach ($App in $FunctionApps)
{
     if ($App.Runtime -eq 'dotnet')
     {
          $AppInfo.Add($App.Name, $App.Runtime)
     }
}

$AppInfo

런타임으로 표시되는 dotnet 앱은 In-process 모델을 사용합니다. 표시된 dotnet-isolated 앱은 격리된 작업자 모델을 이미 사용하고 있습니다.

프로젝트 파일 업데이트

진행 전(진행 중)

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.1.1" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.DurableTask" Version="2.13.0" />
  </ItemGroup>
</Project>

이후(격리된 작업자)

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
    <OutputType>Exe</OutputType>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.21.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.2" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.2.1" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.14.1" />
    <PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.2.0" />
  </ItemGroup>
  <ItemGroup>
    <Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext"/>
  </ItemGroup>
</Project>

주요 변경 내용은 실행 가능한 출력 형식으로 전환하고 모든 Microsoft.Azure.WebJobs.* 패키지를 해당 Microsoft.Azure.Functions.Worker.* 해당 패키지로 바꾸는 것입니다.

Program.cs 추가

격리된 작업자 모델에는 진입점이 Program.cs 필요합니다. 프로젝트 루트에 이 파일을 만듭니다. FunctionsStartup 클래스가 Startup.cs에 있는 경우, 해당 서비스 등록을 ConfigureServices 블록으로 이동하고 Startup.cs을 삭제합니다.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services => {
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();
        
        // Add your custom services here (previously in FunctionsStartup)
        // services.AddSingleton<IMyService, MyService>();
    })
    .Build();

host.Run();

패키지 참조 업데이트

Durable Functions의 패키지 매핑

프로세스 내 패키지 격리된 작업자 패키지
Microsoft.Azure.WebJobs.Extensions.DurableTask Microsoft.Azure.Functions.Worker.Extensions.DurableTask
Microsoft.DurableTask.SqlServer.AzureFunctions Microsoft.Azure.Functions.Worker.Extensions.DurableTask.SqlServer
Microsoft.Azure.DurableTask.Netherite.AzureFunctions Microsoft.Azure.Functions.Worker.Extensions.DurableTask.Netherite

일반적인 확장 패키지 매핑

진행 중 격리된 작업자
Microsoft.Azure.WebJobs.Extensions.Storage Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs .Queues .Tables
Microsoft.Azure.WebJobs.Extensions.CosmosDB Microsoft.Azure.Functions.Worker.Extensions.CosmosDB
Microsoft.Azure.WebJobs.Extensions.ServiceBus Microsoft.Azure.Functions.Worker.Extensions.ServiceBus
Microsoft.Azure.WebJobs.Extensions.EventHubs Microsoft.Azure.Functions.Worker.Extensions.EventHubs
Microsoft.Azure.WebJobs.Extensions.EventGrid Microsoft.Azure.Functions.Worker.Extensions.EventGrid

중요합니다

프로젝트에서 Microsoft.Azure.WebJobs.* 네임스페이스 및 Microsoft.Azure.Functions.Extensions 대한 참조를 제거합니다.

함수 코드 업데이트

이 섹션에서는 각 Durable Functions 형식에 대한 코드 변경 내용을 설명합니다. 앱에서 사용하는 함수 유형에 대한 섹션으로 이동합니다.

전체 API별 API 매핑은 API 참조를 참조하세요.

네임스페이스 변경

// Before (In-Process)
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Azure.WebJobs.Extensions.Http;

// After (Isolated Worker)
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Entities;

함수 특성 변경

// Before (In-Process)
[FunctionName("MyOrchestrator")]

// After (Isolated Worker)
[Function(nameof(MyOrchestrator))]

오케스트레이터 함수 변경

이전(In-Process):

[FunctionName("OrderOrchestrator")]
public static async Task<OrderResult> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context,
    ILogger log)
{
    var order = context.GetInput<Order>();
    
    await context.CallActivityAsync("ValidateOrder", order);
    await context.CallActivityAsync("ProcessPayment", order.Payment);
    await context.CallActivityAsync("ShipOrder", order);
    
    return new OrderResult { Success = true };
}

이후(격리된 작업자):

[Function(nameof(OrderOrchestrator))]
public static async Task<OrderResult> OrderOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    ILogger logger = context.CreateReplaySafeLogger(nameof(OrderOrchestrator));
    var order = context.GetInput<Order>();
    
    await context.CallActivityAsync("ValidateOrder", order);
    await context.CallActivityAsync("ProcessPayment", order.Payment);
    await context.CallActivityAsync("ShipOrder", order);
    
    return new OrderResult { Success = true };
}

주요 차이점

Aspect 처리 중 격리된 작업자
컨텍스트 형식 IDurableOrchestrationContext TaskOrchestrationContext
로거 ILogger 매개 변수 context.CreateReplaySafeLogger()
특성 [FunctionName] [Function]

활동 함수 변경

이전(In-Process):

[FunctionName("ValidateOrder")]
public static bool ValidateOrder(
    [ActivityTrigger] Order order,
    ILogger log)
{
    log.LogInformation("Validating order {OrderId}", order.Id);
    return order.Items.Any() && order.TotalAmount > 0;
}

이후(격리된 작업자):

[Function(nameof(ValidateOrder))]
public static bool ValidateOrder(
    [ActivityTrigger] Order order,
    FunctionContext executionContext)
{
    ILogger logger = executionContext.GetLogger(nameof(ValidateOrder));
    logger.LogInformation("Validating order {OrderId}", order.Id);
    return order.Items.Any() && order.TotalAmount > 0;
}

클라이언트 함수 변경

이전(In-Process):

[FunctionName("StartOrder")]
public static async Task<IActionResult> StartOrder(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
    [DurableClient] IDurableOrchestrationClient client,
    ILogger log)
{
    var order = await req.ReadFromJsonAsync<Order>();
    string instanceId = await client.StartNewAsync("OrderOrchestrator", order);
    
    return client.CreateCheckStatusResponse(req, instanceId);
}

이후(격리된 작업자):

[Function("StartOrder")]
public static async Task<HttpResponseData> StartOrder(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
    [DurableClient] DurableTaskClient client,
    FunctionContext executionContext)
{
    ILogger logger = executionContext.GetLogger("StartOrder");
    var order = await req.ReadFromJsonAsync<Order>();
    string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
        nameof(OrderOrchestrator), 
        order
    );
    
    return await client.CreateCheckStatusResponseAsync(req, instanceId);
}

클라이언트 유형 변경

진행 중 격리된 작업자
IDurableOrchestrationClient DurableTaskClient
StartNewAsync() ScheduleNewOrchestrationInstanceAsync()
CreateCheckStatusResponse() CreateCheckStatusResponseAsync()
HttpRequest / IActionResult HttpRequestData / HttpResponseData

정책 변경 다시 시도

프로세스에서 RetryOptionsCallActivityWithRetryAsync를 사용합니다. 격리된 작업자는 표준TaskOptions과 함께 사용합니다CallActivityAsync.

이전(In-Process):

var retryOptions = new RetryOptions(
    firstRetryInterval: TimeSpan.FromSeconds(5),
    maxNumberOfAttempts: 3);

string result = await context.CallActivityWithRetryAsync<string>(
    "MyActivity", retryOptions, input);

이후(격리된 작업자):

var retryOptions = new TaskOptions(
    new TaskRetryOptions(new RetryPolicy(
        maxNumberOfAttempts: 3,
        firstRetryInterval: TimeSpan.FromSeconds(5))));

string result = await context.CallActivityAsync<string>(
    "MyActivity", input, retryOptions);

엔터티 함수 변경

이전(In-Process):

[FunctionName(nameof(Counter))]
public static void Counter([EntityTrigger] IDurableEntityContext ctx)
{
    switch (ctx.OperationName.ToLowerInvariant())
    {
        case "add":
            ctx.SetState(ctx.GetState<int>() + ctx.GetInput<int>());
            break;
        case "get":
            ctx.Return(ctx.GetState<int>());
            break;
    }
}

이후(격리된 작업자):

[Function(nameof(Counter))]
public static Task Counter([EntityTrigger] TaskEntityDispatcher dispatcher)
{
    return dispatcher.DispatchAsync<CounterEntity>();
}

public class CounterEntity
{
    public int Value { get; set; }
    
    public void Add(int amount) => Value += amount;
    public int Get() => Value;
}

호환성이 손상되는 동작 변경

마이그레이션된 앱을 테스트하기 전에 이러한 변경 내용을 검토합니다. 전체 API별 API 매핑은 API 참조를 참조하세요.

경고

Serialization 기본값이 변경됨: 격리된 작업자는 기본적으로 System.Text.Json를 사용하며 Newtonsoft.Json 대신 사용합니다. 오케스트레이션이 복잡한 개체를 전달하는 경우 직렬화를 신중하게 테스트합니다. 구성 옵션에 대한 JSON serialization 차이를 참조하세요.

경고

ContinueAsNew 기본 변경: 매개 변수 기본값이 preserveUnprocessedEvents (2.x)에서 false (격리됨)으로 true 변경되었습니다. 오케스트레이션에서 ContinueAsNew을 사용하고 처리되지 않은 이벤트가 폐기되는 동작에 의존하는 경우, preserveUnprocessedEvents: false를 명시적으로 전달합니다.

메모

RestartAsync 기본 변경: 매개 변수 기본값이 restartWithNewInstanceId (2.x)에서 true (격리됨)으로 false 변경되었습니다. 코드가 RestartAsync를 호출하고 새 인스턴스 ID 생성에 의존하는 경우 restartWithNewInstanceId: true를 명시적으로 전달합니다.

기타 주목할 만한 변경 사항:

  • 제거된 엔터티 프록시CreateEntityProxy<T> 사용할 수 없습니다. Entities.CallEntityAsync 또는 Entities.SignalEntityAsync를 직접 사용하세요.
  • 작업 허브 간 작업 제거됨 - taskHubName/connectionName을(를) 수락했던 오버로드가 사용 불가합니다. 동일 작업 허브만 지원됩니다.
  • 오케스트레이션 이력 이동됨 - DurableOrchestrationStatus.History이(가) 더 이상 상태 개체에 없습니다. DurableTaskClient.GetOrchestrationHistoryAsync을 사용합니다.

업데이트 local.settings.json

주요 변경 사항은 FUNCTIONS_WORKER_RUNTIMEdotnet에서 dotnet-isolated로 설정하는 것입니다.

{
    "IsEncrypted": false,
    "Values": {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
    }
}

메모

스토리지 백 엔드 구성(Azure Storage, MSSQL, Netherite 또는 Durable Task Scheduler)은 마이그레이션에 의해 변경되지 않습니다. 기존 스토리지 관련 설정을 유지합니다.

로컬에서 테스트

함수 앱을 로컬로 실행하고 모든 오케스트레이션, 활동 및 엔터티가 올바르게 작동하는지 확인합니다.

func start

기능 확인

해당하는 경우 다음 시나리오를 테스트합니다.

  1. HTTP 트리거를 사용하여 오케스트레이션 시작
  2. 오케스트레이션 상태 모니터링
  3. 활동 실행 순서 확인
  4. 관련 엔터티 작업을 테스트하세요
  5. Application Insights 원격 분석 확인

Azure에 배포하기

배포 슬롯을 사용하여 가동 중지 시간을 최소화합니다.

  1. 함수 앱에 대한 스테이징 슬롯을 만듭니다.
  2. 스테이징 슬롯 구성 업데이트:
    • FUNCTIONS_WORKER_RUNTIMEdotnet-isolated로 설정합니다.
    • 필요한 경우 .NET 스택 버전을 업데이트합니다.
  3. 마이그레이션된 코드를 스테이징 슬롯에 배포.
  4. 스테이징 슬롯에서 철저히 테스트합니다.
  5. 슬롯 교환을 수행 하여 변경 내용을 프로덕션으로 이동합니다.

애플리케이션 설정 업데이트

Azure 포털 또는 CLI를 통해 다음을 수행합니다.

az functionapp config appsettings set \
    --name <FUNCTION_APP_NAME> \
    --resource-group <RESOURCE_GROUP> \
    --settings FUNCTIONS_WORKER_RUNTIME=dotnet-isolated

스택 구성 업데이트

다른 .NET 버전을 대상으로 하는 경우:

az functionapp config set \
    --name <FUNCTION_APP_NAME> \
    --resource-group <RESOURCE_GROUP> \
    --net-framework-version v8.0

일반적인 마이그레이션 문제

문제: 어셈블리 로드 오류

증상:Could not load file or assembly 오류.

해결 방법: 모든 Microsoft.Azure.WebJobs.* 패키지 참조를 제거하고 격리된 작업자에 해당하는 패키지로 교체해야 합니다.

문제: 바인딩 특성을 찾을 수 없음

증상:The type or namespace 'QueueTrigger' could not be found

솔루션: 적절한 확장 패키지를 추가하고 문을 사용하여 업데이트합니다.

// Add using statement
using Microsoft.Azure.Functions.Worker;

// Install package
// dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues

문제: IDurableOrchestrationContext를 찾을 수 없음

증상:The type or namespace 'IDurableOrchestrationContext' could not be found

솔루션: 다음으로 대체합니다.TaskOrchestrationContext

using Microsoft.DurableTask;

[Function(nameof(MyOrchestrator))]
public static async Task MyOrchestrator([OrchestrationTrigger] TaskOrchestrationContext context)
{
    // ...
}

문제: JSON serialization 차이점

증상: Serialization 오류 또는 예기치 않은 데이터 형식

솔루션: 격리된 모델은 기본적으로 사용합니다 System.Text.Json . Program.cs에서 serialization 구성하기

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services => {
        services.Configure<JsonSerializerOptions>(options => {
            options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
        });
    })
    .Build();

Newtonsoft.Json을 대신 사용하려면 다음을 수행합니다.

services.Configure<WorkerOptions>(options => {
    options.Serializer = new NewtonsoftJsonObjectSerializer();
});

문제: 사용자 지정 직렬화 설정 마이그레이션

증상: in-process 모델에서 IMessageSerializerSettingsFactory를 사용했고, 그에 상응하는 항목이 격리된 작업자에 필요합니다.

솔루션:Program.cs에서 작업자 수준의 serializer를 구성합니다. 자세한 내용은 API 참조의 행동 변경 섹션Durable Functions의 직렬화 및 지속성을 참조하세요.

사용자 지정 설정에서 Newtonsoft.Json을 사용하려면 다음을 수행합니다.

// Program.cs
var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services =>
    {
        services.Configure<WorkerOptions>(options =>
        {
            var settings = new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.None,
                DateFormatHandling = DateFormatHandling.IsoDateFormat,
            };
            options.Serializer = new NewtonsoftJsonObjectSerializer(settings);
        });
    })
    .Build();

메모

이 방법을 사용하려면 Newtonsoft.JsonAzure.Core.Serialization NuGet 패키지가 필요합니다.

Checklist

이 검사 목록을 사용하여 전체 마이그레이션을 확인합니다.

  • 을 사용하여 프로젝트 파일을 업데이트했습니다. <OutputType>Exe</OutputType>
  • Microsoft.NET.Sdk.Functions 작업자 패키지로 대체됨
  • Microsoft.Azure.WebJobs.Extensions.DurableTask 격리된 패키지로 대체됨
  • 호스트 구성을 사용하여 생성됨 Program.cs
  • 제거된 FunctionsStartup 클래스(있는 경우)
  • 모든 [FunctionName][Function]로 업데이트함
  • 다음으로 대체됨 IDurableOrchestrationContextTaskOrchestrationContext
  • 다음으로 대체됨 IDurableOrchestrationClientDurableTaskClient
  • 로깅을 업데이트하여 DI 또는 FunctionContext를 사용합니다.
  • local.settings.jsondotnet-isolated 런타임으로 업데이트됨
  • 모든 Microsoft.Azure.WebJobs.* 사용을 제거했습니다.
  • Microsoft.Azure.Functions.Worker 사용을 추가했습니다.
  • CreateEntityProxy<T>CallEntityAsync, /, SignalEntityAsync 호출로 대체함
  • 교차 작업 허브 작업 오버로드(사용된 경우)를 교체했습니다.
  • ID별 일괄 처리 GetStatusAsync/PurgeInstanceHistoryAsync 호출을 필터 기반 또는 개별 호출로 대체했습니다.
  • DurableOrchestrationStatus.History 액세스를 GetOrchestrationHistoryAsync로 이동했습니다.
  • DI를 사용하도록 엔터티 DispatchAsync 생성자 매개 변수가 업데이트됨
  • 모든 함수를 로컬로 테스트했습니다.
  • 스테이징 슬롯에 배포되고 확인됨
  • 프로덕션 환경으로 전환

다음 단계