How to synchronize SQL Server Agent jobs across availability group replicas on Linux?
Asked Answered
D

2

6

I have two SQL Server 2019 instances running on Linux. These two instances both contain a single database which is synchronized using AlwaysOn Availability Group. Data in the database is synchronized, but the problem is that the SQL Agent jobs are not part of the database itself.

Therefore, when I create a SQL Server Agent job on the primary replica, this configuration does not copy to the secondary replica. So, after creating each job, I always have to also go to the secondary and create the job there as well. And I have to keep track of all the changes I make all the time.

Is there a built-in way to automate this cross-replica synchronization of SQL Server jobs on Linux when using availability groups? Job synchronization across AG replicas seems like something that should already be natively supported by SQL Server/SQL Server Agent tools, but I found nothing from Microsoft, only a third-party tool for called DBA Tools that I can use to write my own automation scripts in PowerShell.

Denature answered 26/8, 2020 at 5:48 Comment(0)
D
2

After some trial and error, I ended up with this script that works on Ubuntu Linux 18.04. Big thanks to Derik Hammer and his blog for the base of the script and also to David Söderlund for his reply.

For the script to work, you will need to install PowerShell for Linux and both DBATools and SqlCmd2 PowerShell modules. You will also have to store sql credentials in a file somewhere. I chose /var/opt/mssql/secrets/creds.xml for mine and changed access rights to root only. Script can sync logins, DBmail settings, SQL Agent categories, jobs, operators and schedules from primary replica to all secondaries (uncomment what you need, but be careful, order matters and some things cannot be synched in one connection, i.e operators and jobs), skipping configuration replicas if you have any.

You can set up scheduled execution as root with output logged into file using CRON. To set this up, run:

sudo crontab -e

and adding this line to the file:

*/5 * * * * pwsh /<PATH>/sync-sql-objects.ps1 >> /<PATH>/sync-sql-objects.log

Script:

<# 
.DESCRIPTION
This script will detect your Availability Group replicas and copy all of its instance level objects from primary replica to secondary replicas within the Availability Group. It will skip any configuration replicas.
 
.EXAMPLE
sudo pwsh sync-sql-objects.ps1
 
.NOTES
One limitation of this script is that it assumes you only have one availability group. This script should run on your configuration replica server.
 
.LINK
https://www.sqlhammer.com/synchronizing-server-objects-for-availability-groups/
 
DEBUG
To see logs on Ubuntu Linux, install Postfix Mail Transfer Agent and then go to see mails in /var/mail/<username>
#>  

Write-Output ("Sync started: " + (Get-Date -Format G))
 
#Error handling
$ErrorActionPreference = "stop";
 
Trap 
{
    $err = $_.Exception
    while ( $err.InnerException )
    {
 
        $err = $err.InnerException
        Write-Output $err.Message
 
    };
}
 
# Prerequisites
try
{
    Write-Output "Valiating prerequisites."

    # You need to have these modules installed in advance, otherwise the import will fail

    if ((Get-Module -Name dbatools) -eq $null)
    {
        Import-Module dbatools | Out-Null
    }

    if ((Get-Module -Name Invoke-SqlCmd2) -eq $null)
    {
        Import-Module Invoke-SqlCmd2 | Out-Null
    }

    Write-Output "Prerequisites loaded."
 
}
catch
{
    Write-Error $_.Exception.Message -EA Continue
    Write-Error "One or more of the prerequisites did not load. Review previous errors for more details." -EA Continue
    return
}

# Detect Availability Group replicas
Write-Output "Begin query for Availability Group replicas"
 
$ConfigurationMode = "CONFIGURATION_ONLY"
$Hostname = hostname 
$Credentials = Import-CliXml -Path /var/opt/mssql/secrets/creds.xml

$ReplicasQuery = @"
SELECT replica_server_name,
availability_mode_desc,
primary_replica
FROM sys.availability_replicas AR
INNER JOIN sys.dm_hadr_availability_group_states HAGS
INNER JOIN sys.availability_groups AG ON AG.group_id = HAGS.group_id
    ON HAGS.group_id = AR.group_id;
"@

$Replicas = Invoke-Sqlcmd2 -ServerInstance $Hostname -Query $ReplicasQuery -ConnectionTimeout 30 -Credential $Credentials
 
if(([DBNull]::Value).Equals($Replicas[0].primary_replica))
{
    Write-Error "Availability Group query returned no results. Confirm that you connected to a SQL Server instance running an Availability Group. No work was accomplished."
    return
}
 
Write-Output "Completed query of Availability Group replicas"
 
foreach($replica in $Replicas)
{
    # Skip if destination replica is primary replica itself
    if($replica.primary_replica.CompareTo($replica.replica_server_name) -eq 0)
    {
        continue
    }
 
    # Skip configuration replicas
    if($replica.availability_mode_desc.CompareTo($ConfigurationMode) -eq 0)
    {
        continue
    }

    #Connect
    $PrimaryReplica = Connect-DbaInstance $replica.primary_replica -ClientName 'ConfigurationReplica' -SqlCredential $Credentials
    $SecondaryReplica = Connect-DbaInstance $replica.replica_server_name -ClientName 'ConfigurationReplica' -SqlCredential $Credentials

    Write-Output "Copying instance objects from $sourceReplica to $replica"

    # Copy objects
    # Write-Output "Copying Logins."
    # Copy-DbaLogin -Source $PrimaryReplica -Destination $SecondaryReplica

    # Write-Output "Copying DBMail."
    # Copy-DbaDbMail -Source $PrimaryReplica -Destination $SecondaryReplica -Force

    # Write-Output "Copying Agent Categories."
    # Copy-DbaAgentJobCategory -Source $PrimaryReplica -Destination $SecondaryReplica -Force

    # Write-Output "Copying Agent Schedules."
    # Copy-DbaAgentSchedule -Source $PrimaryReplica -Destination $SecondaryReplica -Force

    # Write-Output "Copying Operators."
    # Copy-DbaAgentOperator -Source $PrimaryReplica -Destination $SecondaryReplica -Force

    Write-Output "Copying Jobs."
    Copy-DbaAgentJob -Source $PrimaryReplica -Destination $SecondaryReplica -Force
       
    Write-Output "Copy complete from $PrimaryReplica to $SecondaryReplica"
}

Write-Output "SQL Instance object sync complete."

Enjoy!

Denature answered 7/9, 2020 at 21:38 Comment(1)
well looking back on this ofcourse you are not using windows integrated security in your linux AG setup! :) The sql credentials could be made available from some other secrets vault, but a secure string in xml is decent. Thanks for the full solution, very insightful.Hatchery
W
2

dbatools can sync them but I haven't tried it on an AG running on linux. Let me know if it works or not! The first parameter is the name of your AG, the second is the virtual network name of your cluster.

param($AvailabilityGroup, $SqlInstance)
    try {
        $replicas = Get-DbaAgReplica -AvailabilityGroup $AvailabilityGroup -SqlInstance $SqlInstance
        $primary = $replicas | Where-Object Role -EQ Primary | Select-Object -ExpandProperty Name
        $secondaries = $replicas | Where-Object Role -EQ Secondary | Select-Object -ExpandProperty Name
        $primaryInstanceConnection = Connect-DbaInstance $primary -ClientName 'ScriptBorrowedFromStackOverFlow'
        $secondaries | ForEach-Object {
            $secondaryInstanceConnection = Connect-DbaInstance $_ -ClientName 'ScriptBorrowedFromStackOverFlow'
            Copy-DbaAgentJob -Source $primaryInstanceConnection -Destination $secondaryInstanceConnection -Force
        }
    }
    catch {
        $msg = $_.Exception.Message
        Write-Error "Error while syncing jobs for Availability Group '$($AvailabilityGroup): $msg'"
    }
Westerman answered 26/8, 2020 at 6:2 Comment(4)
I have a variation of this running in production on windows. Seeing as copy-dbaagentjob only interacts with sql server management objects (SMO), I see no reason why this should not work on Linux.Hatchery
I will try it out today and let you know.Denature
Hey, just wanted to let you know that the commands work, but most require -SqlCredential to work, so I will have to make some adjustments to the script to store the creds in a variable before running this. I will post finished script once I'm done with this.Denature
They don't need -sqlcredential if you use windows integrated security. Of course you need it if you are actually using sql credentials.Hatchery
D
2

After some trial and error, I ended up with this script that works on Ubuntu Linux 18.04. Big thanks to Derik Hammer and his blog for the base of the script and also to David Söderlund for his reply.

For the script to work, you will need to install PowerShell for Linux and both DBATools and SqlCmd2 PowerShell modules. You will also have to store sql credentials in a file somewhere. I chose /var/opt/mssql/secrets/creds.xml for mine and changed access rights to root only. Script can sync logins, DBmail settings, SQL Agent categories, jobs, operators and schedules from primary replica to all secondaries (uncomment what you need, but be careful, order matters and some things cannot be synched in one connection, i.e operators and jobs), skipping configuration replicas if you have any.

You can set up scheduled execution as root with output logged into file using CRON. To set this up, run:

sudo crontab -e

and adding this line to the file:

*/5 * * * * pwsh /<PATH>/sync-sql-objects.ps1 >> /<PATH>/sync-sql-objects.log

Script:

<# 
.DESCRIPTION
This script will detect your Availability Group replicas and copy all of its instance level objects from primary replica to secondary replicas within the Availability Group. It will skip any configuration replicas.
 
.EXAMPLE
sudo pwsh sync-sql-objects.ps1
 
.NOTES
One limitation of this script is that it assumes you only have one availability group. This script should run on your configuration replica server.
 
.LINK
https://www.sqlhammer.com/synchronizing-server-objects-for-availability-groups/
 
DEBUG
To see logs on Ubuntu Linux, install Postfix Mail Transfer Agent and then go to see mails in /var/mail/<username>
#>  

Write-Output ("Sync started: " + (Get-Date -Format G))
 
#Error handling
$ErrorActionPreference = "stop";
 
Trap 
{
    $err = $_.Exception
    while ( $err.InnerException )
    {
 
        $err = $err.InnerException
        Write-Output $err.Message
 
    };
}
 
# Prerequisites
try
{
    Write-Output "Valiating prerequisites."

    # You need to have these modules installed in advance, otherwise the import will fail

    if ((Get-Module -Name dbatools) -eq $null)
    {
        Import-Module dbatools | Out-Null
    }

    if ((Get-Module -Name Invoke-SqlCmd2) -eq $null)
    {
        Import-Module Invoke-SqlCmd2 | Out-Null
    }

    Write-Output "Prerequisites loaded."
 
}
catch
{
    Write-Error $_.Exception.Message -EA Continue
    Write-Error "One or more of the prerequisites did not load. Review previous errors for more details." -EA Continue
    return
}

# Detect Availability Group replicas
Write-Output "Begin query for Availability Group replicas"
 
$ConfigurationMode = "CONFIGURATION_ONLY"
$Hostname = hostname 
$Credentials = Import-CliXml -Path /var/opt/mssql/secrets/creds.xml

$ReplicasQuery = @"
SELECT replica_server_name,
availability_mode_desc,
primary_replica
FROM sys.availability_replicas AR
INNER JOIN sys.dm_hadr_availability_group_states HAGS
INNER JOIN sys.availability_groups AG ON AG.group_id = HAGS.group_id
    ON HAGS.group_id = AR.group_id;
"@

$Replicas = Invoke-Sqlcmd2 -ServerInstance $Hostname -Query $ReplicasQuery -ConnectionTimeout 30 -Credential $Credentials
 
if(([DBNull]::Value).Equals($Replicas[0].primary_replica))
{
    Write-Error "Availability Group query returned no results. Confirm that you connected to a SQL Server instance running an Availability Group. No work was accomplished."
    return
}
 
Write-Output "Completed query of Availability Group replicas"
 
foreach($replica in $Replicas)
{
    # Skip if destination replica is primary replica itself
    if($replica.primary_replica.CompareTo($replica.replica_server_name) -eq 0)
    {
        continue
    }
 
    # Skip configuration replicas
    if($replica.availability_mode_desc.CompareTo($ConfigurationMode) -eq 0)
    {
        continue
    }

    #Connect
    $PrimaryReplica = Connect-DbaInstance $replica.primary_replica -ClientName 'ConfigurationReplica' -SqlCredential $Credentials
    $SecondaryReplica = Connect-DbaInstance $replica.replica_server_name -ClientName 'ConfigurationReplica' -SqlCredential $Credentials

    Write-Output "Copying instance objects from $sourceReplica to $replica"

    # Copy objects
    # Write-Output "Copying Logins."
    # Copy-DbaLogin -Source $PrimaryReplica -Destination $SecondaryReplica

    # Write-Output "Copying DBMail."
    # Copy-DbaDbMail -Source $PrimaryReplica -Destination $SecondaryReplica -Force

    # Write-Output "Copying Agent Categories."
    # Copy-DbaAgentJobCategory -Source $PrimaryReplica -Destination $SecondaryReplica -Force

    # Write-Output "Copying Agent Schedules."
    # Copy-DbaAgentSchedule -Source $PrimaryReplica -Destination $SecondaryReplica -Force

    # Write-Output "Copying Operators."
    # Copy-DbaAgentOperator -Source $PrimaryReplica -Destination $SecondaryReplica -Force

    Write-Output "Copying Jobs."
    Copy-DbaAgentJob -Source $PrimaryReplica -Destination $SecondaryReplica -Force
       
    Write-Output "Copy complete from $PrimaryReplica to $SecondaryReplica"
}

Write-Output "SQL Instance object sync complete."

Enjoy!

Denature answered 7/9, 2020 at 21:38 Comment(1)
well looking back on this ofcourse you are not using windows integrated security in your linux AG setup! :) The sql credentials could be made available from some other secrets vault, but a secure string in xml is decent. Thanks for the full solution, very insightful.Hatchery

© 2022 - 2025 — McMap. All rights reserved.