Автоматическое обновление службы мобильности в Azure при репликации Azure

Ежемесячный выпуск Azure Site Recovery позволяет устранять любые проблемы и совершенствовать существующие функции или добавлять новые. Актуальность службы сохраняется благодаря тому, что развертывание исправлений производится каждый месяц. Разрешив службе Site Recovery управление обновлениями компонентов, можно избежать расходов, связанных с обновлениями.

Как упоминалось в архитектуре аварийного восстановления из Azure в Azure, служба мобильности устанавливается на всех виртуальных машинах Azure, для которых включена репликация из одного региона Azure в другой. При использовании автоматического обновления с каждым новым выпуском обновляется расширение службы мобильности.

Примечание.

Мы рекомендуем использовать модуль Azure Az PowerShell для взаимодействия с Azure. Чтобы начать работу, см. статью Установка Azure PowerShell. Дополнительные сведения см. в статье Перенос Azure PowerShell с AzureRM на Az.

Принцип работы автоматического обновления

Если служба Site Recovery используется для управления обновлениями, с помощью учетной записи службы автоматизации, создаваемой в одной подписке с хранилищем, развертывается глобальный Runbook (используемый службами Azure). В каждом хранилище используется одна учетная запись службы автоматизации. Для каждой виртуальной машины хранилища Runbook проверяет наличие активных автоматических обновлений. При наличии новой версии расширения службы мобильности происходит установка обновления.

По умолчанию Runbook выполняет проверку ежедневно в 00:00 в географическом часовом поясе реплицированной виртуальной машины. График проверок Runbook можно изменить с помощью учетной записи службы автоматизации.

Примечание.

Начиная с Накопительного пакета обновлений 35, для работы с обновлениями можно выбрать существующую учетную запись службы автоматизации. До использования "Накопительного пакета обновлений 35" служба Site Recovery по умолчанию создавала учетную запись службы автоматизации. Данная функция доступна только при включении репликации для виртуальной машины. Однако ее нельзя использовать для виртуальной машины с уже включенной репликацией. Выбранные настройки применяются ко всем виртуальным машинам Azure, защищенным в одном хранилище.

Включение автоматического обновления не требует перезапуска виртуальных машин Azure и не влияет на текущую репликацию.

Выставление счетов за задание в учетной записи службы автоматизации зависит от количества минут выполнения задания, использованных за месяц. Каждый день выполнение задания занимает несколько секунд и считаются бесплатными единицами. Для учетной записи службы автоматизации по умолчанию предоставляется 500 минут бесплатных единиц, как указано в следующей таблице.

Включено бесплатных единиц (ежемесячно) Цена,
500 минут времени выполнения задания 0,14 ₹ за 1 минуту

Включение автоматических обновлений

Существует несколько способов, используемых службой Site Recovery для управления обновлениями расширений.

Управление в процессе включения репликации

При включении репликации для виртуальной машины из представления виртуальной машины или из хранилища служб восстановления можно разрешить службе Site Recovery управлять обновлениями для расширения Site Recovery или управлять ими вручную.

Extension settings

Переключение параметров обновления для расширения внутри хранилища

  1. В хранилище служб восстановления перейдите к разделу Управление>Инфраструктура Site Recovery.

  2. В разделе Для виртуальных машин Azure>Параметры обновления расширений>Разрешить управление службе Site Recovery нажмите кнопку Вкл.

    Чтобы выбрать ручное управление расширением, нажмите кнопкуВыкл.

    Важно!

    Выбранный параметр Разрешить управление службе Site Recovery применяется ко всем виртуальным машинам в хранилище.

  3. Выберите Сохранить.

Extension update settings

Примечание.

При выборе любого параметра обновления пользователь получает уведомление об учетной записи службы автоматизации, используемой для управления обновлениями. Если эта функция используется в хранилище впервые, по умолчанию создается новая учетная запись службы автоматизации. Кроме того, можно настроить выбор существующей учетной записи службы автоматизации. После того как настройки учетной записи завершены, для всех последующих действий по включению репликации в хранилище будет использоваться выбранная учетная запись службы автоматизации. В настоящее время в раскрывающемся меню открывается список учетных записей службы автоматизации, находящихся в той же группе ресурсов, что и хранилище.

Для пользовательской учетной записи автоматизации используйте следующий сценарий:

Важно!

Выполните следующий сценарий в контексте учетной записи службы автоматизации. Этот сценарий использует управляемые удостоверения, назначенные системой, в качестве типа проверки подлинности.

param(
    [Parameter(Mandatory=$true)]
    [String] $VaultResourceId,
    [Parameter(Mandatory=$true)]
    [ValidateSet("Enabled",'Disabled')]
    [Alias("Enabled or Disabled")]
    [String] $AutoUpdateAction,
    [Parameter(Mandatory=$false)]
    [String] $AutomationAccountArmId
)
$SiteRecoveryRunbookName = "Modify-AutoUpdateForVaultForPatner"
$TaskId = [guid]::NewGuid().ToString()
$SubscriptionId = "00000000-0000-0000-0000-000000000000"
$AsrApiVersion = "2021-12-01"
$ArmEndPoint = "https://management.azure.com"
$AadAuthority = "https://login.windows.net/"
$AadAudience = "https://management.core.windows.net/"
$AzureEnvironment = "AzureCloud"
$Timeout = "160"
$AuthenticationType = "SystemAssignedIdentity"
function Throw-TerminatingErrorMessage
{
        Param
    (
        [Parameter(Mandatory=$true)]
        [String]
        $Message
        )
    throw ("Message: {0}, TaskId: {1}.") -f $Message, $TaskId
}
function Write-Tracing
{
        Param
    (
        [Parameter(Mandatory=$true)]
        [ValidateSet("Informational", "Warning", "ErrorLevel", "Succeeded", IgnoreCase = $true)]
                [String]
        $Level,
        [Parameter(Mandatory=$true)]
        [String]
        $Message,
            [Switch]
        $DisplayMessageToUser
        )
    Write-Output $Message
}
function Write-InformationTracing
{
        Param
    (
        [Parameter(Mandatory=$true)]
        [String]
        $Message
        )
    Write-Tracing -Message $Message -Level Informational -DisplayMessageToUser
}
function ValidateInput()
{
    try
    {
        if(!$VaultResourceId.StartsWith("/subscriptions", [System.StringComparison]::OrdinalIgnoreCase))
        {
            $ErrorMessage = "The vault resource id should start with /subscriptions."
            throw $ErrorMessage
        }
        $Tokens = $VaultResourceId.SubString(1).Split("/")
        if(!($Tokens.Count % 2 -eq 0))
        {
            $ErrorMessage = ("Odd Number of tokens: {0}." -f $Tokens.Count)
            throw $ErrorMessage
        }
        if(!($Tokens.Count/2 -eq 4))
        {
            $ErrorMessage = ("Invalid number of resource in vault ARM id expected:4, actual:{0}." -f ($Tokens.Count/2))
            throw $ErrorMessage
        }
        if($AutoUpdateAction -ieq "Enabled" -and [string]::IsNullOrEmpty($AutomationAccountArmId))
        {
            $ErrorMessage = ("The automation account ARM id should not be null or empty when AutoUpdateAction is enabled.")
            throw $ErrorMessage
        }
    }
    catch
    {
        $ErrorMessage = ("ValidateInput failed with [Exception: {0}]." -f $_.Exception)
        Write-Tracing -Level ErrorLevel -Message $ErrorMessage -DisplayMessageToUser
        Throw-TerminatingErrorMessage -Message $ErrorMessage
    }
}
function Initialize-SubscriptionId()
{
    try
    {
        $Tokens = $VaultResourceId.SubString(1).Split("/")
        $Count = 0
                $ArmResources = @{}
        while($Count -lt $Tokens.Count)
        {
            $ArmResources[$Tokens[$Count]] = $Tokens[$Count+1]
            $Count = $Count + 2
        }
                return $ArmResources["subscriptions"]
    }
    catch
    {
        Write-Tracing -Level ErrorLevel -Message ("Initialize-SubscriptionId: failed with [Exception: {0}]." -f $_.Exception) -DisplayMessageToUser
        throw
    }
}
function Invoke-InternalRestMethod($Uri, $Headers, [ref]$Result)
{
    $RetryCount = 0
    $MaxRetry = 3
    do
    {
        try
        {
            $ResultObject = Invoke-RestMethod -Uri $Uri -Headers $Headers
            ($Result.Value) += ($ResultObject)
            break
        }
        catch
        {
            Write-InformationTracing ("Retry Count: {0}, Exception: {1}." -f $RetryCount, $_.Exception)
            $RetryCount++
            if(!($RetryCount -le $MaxRetry))
            {
                throw
            }
            Start-Sleep -Milliseconds 2000
        }
    }while($true)
}
function Invoke-InternalWebRequest($Uri, $Headers, $Method, $Body, $ContentType, [ref]$Result)
{
    $RetryCount = 0
    $MaxRetry = 3
    do
    {
        try
        {
            $ResultObject = Invoke-WebRequest -Uri $UpdateUrl -Headers $Header -Method 'PATCH' `
                -Body $InputJson  -ContentType "application/json" -UseBasicParsing
            ($Result.Value) += ($ResultObject)
            break
        }
        catch
        {
            Write-InformationTracing ("Retry Count: {0}, Exception: {1}." -f $RetryCount, $_.Exception)
            $RetryCount++
            if(!($RetryCount -le $MaxRetry))
            {
                throw
            }
            Start-Sleep -Milliseconds 2000
        }
    }while($true)
}
function Get-Header([ref]$Header, $AadAudience){
    try
    {
        $Header.Value['Content-Type'] = 'application\json'
        Write-InformationTracing ("The Authentication Type is system Assigned Identity based.")
        $endpoint = $env:IDENTITY_ENDPOINT
        $endpoint  
        $Headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" 
        $Headers.Add("X-IDENTITY-HEADER", $env:IDENTITY_HEADER) 
        $Headers.Add("Metadata", "True")   
        $authenticationResult = Invoke-RestMethod -Method Get -Headers $Headers -Uri ($endpoint +'?resource=' +$AadAudience)
        $accessToken = $authenticationResult.access_token
        $Header.Value['Authorization'] = "Bearer " + $accessToken
        $Header.Value["x-ms-client-request-id"] = $TaskId + "/" + (New-Guid).ToString() + "-" + (Get-Date).ToString("u")
    }
    catch
    {
        $ErrorMessage = ("Get-BearerToken: failed with [Exception: {0}]." -f $_.Exception)
        Write-Tracing -Level ErrorLevel -Message $ErrorMessage -DisplayMessageToUser
        Throw-TerminatingErrorMessage -Message $ErrorMessage
    }
}
function Get-ProtectionContainerToBeModified([ref] $ContainerMappingList)
{
    try
    {
        Write-InformationTracing ("Get protection container mappings : {0}." -f $VaultResourceId)
        $ContainerMappingListUrl = $ArmEndPoint + $VaultResourceId + "/replicationProtectionContainerMappings" + "?api-version=" + $AsrApiVersion
        Write-InformationTracing ("Getting the bearer token and the header.")
        Get-Header ([ref]$Header) $AadAudience
        $Result = @()
        Invoke-InternalRestMethod -Uri $ContainerMappingListUrl -Headers $header -Result ([ref]$Result)
        $ContainerMappings = $Result[0]
        Write-InformationTracing ("Total retrieved container mappings: {0}." -f $ContainerMappings.Value.Count)
        foreach($Mapping in $ContainerMappings.Value)
        {
            if(($Mapping.properties.providerSpecificDetails -eq $null) -or ($Mapping.properties.providerSpecificDetails.instanceType -ine "A2A"))
            {
                Write-InformationTracing ("Mapping properties: {0}." -f ($Mapping.properties))
                Write-InformationTracing ("Ignoring container mapping: {0} as the provider does not match." -f ($Mapping.Id))
                continue;
            }
            if($Mapping.Properties.State -ine "Paired")
            {
                Write-InformationTracing ("Ignoring container mapping: {0} as the state is not paired." -f ($Mapping.Id))
                continue;
            }
            Write-InformationTracing ("Provider specific details {0}." -f ($Mapping.properties.providerSpecificDetails))
            $MappingAutoUpdateStatus = $Mapping.properties.providerSpecificDetails.agentAutoUpdateStatus
            $MappingAutomationAccountArmId = $Mapping.properties.providerSpecificDetails.automationAccountArmId
            $MappingHealthErrorCount = $Mapping.properties.HealthErrorDetails.Count
            if($AutoUpdateAction -ieq "Enabled" -and
                ($MappingAutoUpdateStatus -ieq "Enabled") -and
                ($MappingAutomationAccountArmId -ieq $AutomationAccountArmId) -and
                ($MappingHealthErrorCount -eq 0))
            {
                Write-InformationTracing ("Provider specific details {0}." -f ($Mapping.properties))
                Write-InformationTracing ("Ignoring container mapping: {0} as the auto update is already enabled and is healthy." -f ($Mapping.Id))
                continue;
            }
            ($ContainerMappingList.Value).Add($Mapping.id)
        }
    }
    catch
    {
        $ErrorMessage = ("Get-ProtectionContainerToBeModified: failed with [Exception: {0}]." -f $_.Exception)
        Write-Tracing -Level ErrorLevel -Message $ErrorMessage -DisplayMessageToUser
        Throw-TerminatingErrorMessage -Message $ErrorMessage
    }
}
$OperationStartTime = Get-Date
$ContainerMappingList = New-Object System.Collections.Generic.List[System.String]
$JobsInProgressList = @()
$JobsCompletedSuccessList = @()
$JobsCompletedFailedList = @()
$JobsFailedToStart = 0
$JobsTimedOut = 0
$Header = @{}
$AzureRMProfile = Get-Module -ListAvailable -Name AzureRM.Profile | Select Name, Version, Path
$AzureRmProfileModulePath = Split-Path -Parent $AzureRMProfile.Path
Add-Type -Path (Join-Path $AzureRmProfileModulePath "Microsoft.IdentityModel.Clients.ActiveDirectory.dll")
$Inputs = ("Tracing inputs VaultResourceId: {0}, Timeout: {1}, AutoUpdateAction: {2}, AutomationAccountArmId: {3}." -f $VaultResourceId, $Timeout, $AutoUpdateAction, $AutomationAccountArmId)
Write-Tracing -Message $Inputs -Level Informational -DisplayMessageToUser
$CloudConfig = ("Tracing cloud configuration ArmEndPoint: {0}, AadAuthority: {1}, AadAudience: {2}." -f $ArmEndPoint, $AadAuthority, $AadAudience)
Write-Tracing -Message $CloudConfig -Level Informational -DisplayMessageToUser
ValidateInput
$SubscriptionId = Initialize-SubscriptionId
Get-ProtectionContainerToBeModified ([ref]$ContainerMappingList)
$Input = @{
  "properties"= @{
    "providerSpecificInput"= @{
        "instanceType" = "A2A"
        "agentAutoUpdateStatus" = $AutoUpdateAction
        "automationAccountArmId" = $AutomationAccountArmId
        "automationAccountAuthenticationType" = $AuthenticationType
    }
  }
}
$InputJson = $Input |  ConvertTo-Json
if ($ContainerMappingList.Count -eq 0)
{
    Write-Tracing -Level Succeeded -Message ("Exiting as there are no container mappings to be modified.") -DisplayMessageToUser
    exit
}
Write-InformationTracing ("Container mappings to be updated has been retrieved with count: {0}." -f $ContainerMappingList.Count)
try
{
    Write-InformationTracing ("Start the modify container mapping jobs.")
    ForEach($Mapping in $ContainerMappingList)
    {
    try {
            $UpdateUrl = $ArmEndPoint + $Mapping + "?api-version=" + $AsrApiVersion
            Get-Header ([ref]$Header) $AadAudience
            $Result = @()
            Invoke-InternalWebRequest -Uri $UpdateUrl -Headers $Header -Method 'PATCH' `
                -Body $InputJson  -ContentType "application/json" -Result ([ref]$Result)
            $Result = $Result[0]
            $JobAsyncUrl = $Result.Headers['Azure-AsyncOperation']
            Write-InformationTracing ("The modify container mapping job invoked with async url: {0}." -f $JobAsyncUrl)
            $JobsInProgressList += $JobAsyncUrl;
            # Rate controlling the set calls to maximum 60 calls per minute.
            # ASR throttling for set calls is 200 in 1 minute.
            Start-Sleep -Milliseconds 1000
        }
        catch{
            Write-InformationTracing ("The modify container mappings job creation failed for: {0}." -f $Ru)
            Write-InformationTracing $_
            $JobsFailedToStart++
        }
    }
    Write-InformationTracing ("Total modify container mappings has been initiated: {0}." -f $JobsInProgressList.Count)
}
catch
{
    $ErrorMessage = ("Modify container mapping jobs failed with [Exception: {0}]." -f $_.Exception)
    Write-Tracing -Level ErrorLevel -Message $ErrorMessage -DisplayMessageToUser
    Throw-TerminatingErrorMessage -Message $ErrorMessage
}
try
{
    while($JobsInProgressList.Count -ne 0)
    {
        Sleep -Seconds 30
        $JobsInProgressListInternal = @()
        ForEach($JobAsyncUrl in $JobsInProgressList)
        {
            try
            {
                Get-Header ([ref]$Header) $AadAudience
                $Result = Invoke-RestMethod -Uri $JobAsyncUrl -Headers $header
                $JobState = $Result.Status
                if($JobState -ieq "InProgress")
                {
                    $JobsInProgressListInternal += $JobAsyncUrl
                }
                elseif($JobState -ieq "Succeeded" -or `
                    $JobState -ieq "PartiallySucceeded" -or `
                    $JobState -ieq "CompletedWithInformation")
                {
                    Write-InformationTracing ("Jobs succeeded with state: {0}." -f $JobState)
                    $JobsCompletedSuccessList += $JobAsyncUrl
                }
                else
                {
                    Write-InformationTracing ("Jobs failed with state: {0}." -f $JobState)
                    $JobsCompletedFailedList += $JobAsyncUrl
                }
            }
            catch
            {
                Write-InformationTracing ("The get job failed with: {0}. Ignoring the exception and retrying the next job." -f $_.Exception)
                # The job on which the tracking failed, will be considered in progress and tried again later.
                $JobsInProgressListInternal += $JobAsyncUrl
            }
            # Rate controlling the get calls to maximum 120 calls each minute.
            # ASR throttling for get calls is 10000 in 60 minutes.
            Start-Sleep -Milliseconds 500
        }
        Write-InformationTracing ("Jobs remaining {0}." -f $JobsInProgressListInternal.Count)
        $CurrentTime = Get-Date
        if($CurrentTime -gt $OperationStartTime.AddMinutes($Timeout))
        {
            Write-InformationTracing ("Tracing modify cloud pairing jobs has timed out.")
            $JobsTimedOut = $JobsInProgressListInternal.Count
            $JobsInProgressListInternal = @()
        }
        $JobsInProgressList = $JobsInProgressListInternal
    }
}
catch
{
    $ErrorMessage = ("Tracking modify cloud pairing jobs failed with [Exception: {0}]." -f $_.Exception)
    Write-Tracing -Level ErrorLevel -Message $ErrorMessage  -DisplayMessageToUser
    Throw-TerminatingErrorMessage -Message $ErrorMessage
}
Write-InformationTracing ("Tracking modify cloud pairing jobs completed.")
Write-InformationTracing ("Modify cloud pairing jobs success: {0}." -f $JobsCompletedSuccessList.Count)
Write-InformationTracing ("Modify cloud pairing jobs failed: {0}." -f $JobsCompletedFailedList.Count)
Write-InformationTracing ("Modify cloud pairing jobs failed to start: {0}." -f $JobsFailedToStart)
Write-InformationTracing ("Modify cloud pairing jobs timedout: {0}." -f $JobsTimedOut)
if($JobsTimedOut -gt  0)
{
    $ErrorMessage = "One or more modify cloud pairing jobs has timedout."
    Write-Tracing -Level ErrorLevel -Message ($ErrorMessage)
    Throw-TerminatingErrorMessage -Message $ErrorMessage
}
elseif($JobsCompletedSuccessList.Count -ne $ContainerMappingList.Count)
{
    $ErrorMessage = "One or more modify cloud pairing jobs failed."
    Write-Tracing -Level ErrorLevel -Message ($ErrorMessage)
    Throw-TerminatingErrorMessage -Message $ErrorMessage
}
Write-Tracing -Level Succeeded -Message ("Modify cloud pairing completed.") -DisplayMessageToUser

Управление обновлениями вручную

  1. Если доступны новые обновления для службы мобильности, установленной на виртуальных машинах, выводится уведомление: Доступно обновление для агента репликации Site Recovery. Щелкните, чтобы установить его.

    Replicated items window

  2. Выберите уведомление, чтобы открыть страницу выбора виртуальной машины.

  3. Выберите виртуальные машины, которые требуется обновить, и нажмите кнопку ОК. Для каждой выбранной виртуальной машины начнется выполнение команды "Обновить службу мобильности".

    Replicated items VM list

Распространенные проблемы и устранение неполадок

При возникновении проблемы с автоматическими обновлениями пользователь получает уведомление в разделе Проблемы с конфигурацией информационной панели хранилища.

Если вы не можете включить автоматическое обновление, ознакомьтесь со следующими распространенными ошибками и рекомендуемыми действиями.

  • Ошибка: у вас нет разрешений на создание учетной записи запуска от имени Azure (субъекта-службы) и предоставление субъекту-службе роли участника.

    Рекомендуемое действие: убедитесь, что учетная запись для входа назначена как "Участник", и повторите попытку. Дополнительные сведения о назначении разрешений см. в разделе "Практическое руководство. Использование портала для создания приложения Microsoft Entra и субъекта-службы, который может получить доступ к ресурсам".

    Для устранения большинства проблем после включения автоматического обновления нажмите кнопку Исправить. Если кнопка исправления недоступна, ознакомьтесь с сообщением об ошибке, отображаемым под панелью параметров расширения.

    Site Recovery service repair button in extension update settings

  • Ошибка: учетная запись запуска от имени не имеет разрешения на доступ к ресурсу служб восстановления.

    Рекомендуемое действие: удалите и повторно создайте учетную запись запуска от имени. Кроме того, убедитесь, что приложение Microsoft Entra учетной записи службы автоматизации может получить доступ к ресурсу служб восстановления.

  • Ошибка: учетная запись запуска от имени не найдена. Один из них был удален или не создан : приложение Microsoft Entra, субъект-служба, роль, ресурс сертификата службы автоматизации, ресурс автоматизации Подключение ion актив - или отпечаток не идентичен сертификату и Подключение ion.

    Рекомендуемое действие: удалите и повторно создайте учетную запись запуска от имени.

  • Ошибка: истекает срок действия сертификата Azure запуска от имени, используемого учетной записью службы автоматизации.

    Срок действия самозаверяющего сертификата, созданного для учетной записи запуска от имени, составляет один год с момента создания. Его можно обновить в любое время до истечения его срока действия. При наличии подписки на уведомления по электронной почте пользователь также будет получать электронные сообщения, когда от него потребуются какие-либо действия. Эта ошибка выводится за два месяца до даты истечения срока действия и изменится на критическую ошибку, если срок действия сертификата истек. По истечении срока действия сертификата автоматическое обновление перестанет функционировать, пока сертификат не будет обновлен.

    Рекомендуемое действие: чтобы устранить эту проблему, нажмите кнопку Исправить, а затем щелкните Обновить сертификат.

    renew-cert

    Примечание.

    После того как сертификат обновлен, обновите страницу, чтобы отобразить его текущее состояние.

Следующие шаги

Дополнительные сведения о переносе типа проверки подлинности учетных записей службы автоматизации в управляемые удостоверения.