SQL Server on Linux 向けの SQL Assessment API を使用する

適用対象:SQL Server on Linux

SQL評価APIは、SQL Serverの設定をベストプラクティスとして評価する仕組みを提供します。 この API は、SQL Server チームが推奨するベスト プラクティス ルールを含むルール セットと共に配布されます。 このルール セットは、新しいバージョンのリリースとともに強化されます。 お使いの SQL Server の構成がベスト プラクティスに沿っていることを確認すると役立ちます。

Microsoftが提供したルールセットはGitHubで入手可能です。 サンプルのリポジトリでルールセット全体を確認することができます。

この記事では、PowerShellを使ってLinuxおよびコンテナ上のSQL Server on Linux用のSQL評価APIを実行します。

前提条件

  1. Linux に PowerShell がインストールされていることを確認します。

  2. SqlServer ユーザーとして実行している PowerShell ギャラリーから mssql PowerShell モジュールをインストールします。

    su mssql -c "/usr/bin/pwsh -Command Install-Module SqlServer"
    

評価を設定する

SQL評価APIはJSON形式の出力を提供します。 SQL評価APIを設定するには、以下の手順を完了してください:

  1. 評価したいインスタンスでSQL認証を使ってSQL Server評価用のログインを作成します。 以下の Transact-SQL スクリプトを使ってログインと強力なパスワードを作成します。 パスワードは SQL Server の既定のパスワード ポリシーに従う必要があります。 既定では、パスワードの長さは少なくとも 8 文字で、大文字、小文字、10 進数の数字、記号の 4 種類のうち 3 種類を含んでいる必要があります。 パスワードには最大 128 文字まで使用できます。 パスワードはできるだけ長く、複雑にします。

    USE [master];
    GO
    
    CREATE LOGIN [assessmentLogin]
        WITH PASSWORD = N'<password>';
    
    GRANT CONTROL SERVER TO [assessmentLogin];
    GO
    

    CONTROL SERVER許可はほとんどの評価で有効です。 ただし、一部の評価では システム管理者 権限が必要な場合もあります。 もしそれらの評価を実行していない場合は、 CONTROL SERVER 権限を使ってください。

  2. インスタンスに接続するための認証情報を保存します。 前のステップで使ったパスワードに <password> を置き換えてください。

    echo "assessmentLogin" > /var/opt/mssql/secrets/assessment
    echo "<password>" >> /var/opt/mssql/secrets/assessment
    
  3. mssql ユーザーのみが資格情報にアクセスできるようにして、新しい評価の資格情報をセキュリティで保護します。

    chmod 600 /var/opt/mssql/secrets/assessment
    chown mssql:mssql /var/opt/mssql/secrets/assessment
    

評価スクリプトをダウンロードする

以下のサンプルスクリプトは、先に作成した認証情報を用いてSQL評価APIを呼び出します。 スクリプトは /var/opt/mssql/log/assessments ディレクトリにJSON出力を生成します。

注

SQL Assessment API は、CSV と XML 形式で出力を生成することもできます。

このスクリプトは GitHub からダウンロードできます。

このファイルは /opt/mssql/bin/runassessment.ps1 として保存できます。

[CmdletBinding()] param ()

$Error.Clear()

# Create output directory if not exists

$outDir = '/var/opt/mssql/log/assessments'
if (-not ( Test-Path $outDir )) { mkdir $outDir }
$outPath = Join-Path $outDir 'assessment-latest'

$errorPath = Join-Path $outDir 'assessment-latest-errors'
if ( Test-Path $errorPath ) { remove-item $errorPath }

function ConvertTo-LogOutput {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true)]
        $input
    )
    process {
        switch ($input) {
            { $_ -is [System.Management.Automation.WarningRecord] } {
                $result = @{
                    'TimeStamp' = $(Get-Date).ToString("O");
                    'Warning'   = $_.Message
                }
            }
            default {
                $result = @{
                    'TimeStamp'      = $input.TimeStamp;
                    'Severity'       = $input.Severity;
                    'TargetType'     = $input.TargetType;
                    'ServerName'     = $serverName;
                    'HostName'       = $hostName;
                    'TargetName'     = $input.TargetObject.Name;
                    'TargetPath'     = $input.TargetPath;
                    'CheckId'        = $input.Check.Id;
                    'CheckName'      = $input.Check.DisplayName;
                    'Message'        = $input.Message;
                    'RulesetName'    = $input.Check.OriginName;
                    'RulesetVersion' = $input.Check.OriginVersion.ToString();
                    'HelpLink'       = $input.HelpLink
                }

                if ( $input.TargetType -eq 'Database') {
                    $result['AvailabilityGroup'] = $input.TargetObject.AvailabilityGroupName
                }
            }
        }

        $result
    }
}

function Get-TargetsRecursive {

    [CmdletBinding()]
    Param (
        [Parameter(ValueFromPipeline = $true)]
        [Microsoft.SqlServer.Management.Smo.Server] $server
    )

    $server
    $server.Databases
}

function Get-ConfSetting {
    [CmdletBinding()]
    param (
        $confFile,
        $section,
        $name,
        $defaultValue = $null
    )

    $inSection = $false

    switch -regex -file $confFile {
        "^\s*\[\s*(.+?)\s*\]" {
            $inSection = $matches[1] -eq $section
        }
        "^\s*$($name)\s*=\s*(.+?)\s*$" {
            if ($inSection) {
                return $matches[1]
            }
        }
    }

    return $defaultValue
}

try {
    Write-Verbose "Acquiring credentials"

    $login, $pwd = Get-Content '/var/opt/mssql/secrets/assessment' -Encoding UTF8NoBOM -TotalCount 2
    $securePassword = ConvertTo-SecureString $pwd -AsPlainText -Force
    $credential = New-Object System.Management.Automation.PSCredential ($login, $securePassword)
    $securePassword.MakeReadOnly()

    Write-Verbose "Acquired credentials"

    $serverInstance = '.'

    if (Test-Path /var/opt/mssql/mssql.conf) {
        $port = Get-ConfSetting /var/opt/mssql/mssql.conf network tcpport

        if (-not [string]::IsNullOrWhiteSpace($port)) {
            Write-Verbose "Using port $($port)"
            $serverInstance = "$($serverInstance),$($port)"
        }
    }

    # IMPORTANT: If the script is run in trusted environments and there is a prelogin handshake error,
    # add -TrustServerCertificate flag in the commands for $serverName, $hostName and Get-SqlInstance lines below.
    $serverName = (Invoke-SqlCmd -ServerInstance $serverInstance -Credential $credential -Query "SELECT @@SERVERNAME")[0]
    $hostName = (Invoke-SqlCmd -ServerInstance $serverInstance -Credential $credential -Query "SELECT HOST_NAME()")[0]

    # Invoke assessment and store results.
    # Replace 'ConvertTo-Json' with 'ConvertTo-Csv' to change output format.
    # Available output formats: JSON, CSV, XML.
    # Encoding parameter is optional.

    Get-SqlInstance -ServerInstance $serverInstance -Credential $credential -ErrorAction Stop
    | Get-TargetsRecursive
    | ForEach-Object { Write-Verbose "Invoke assessment on $($_.Urn)"; $_ }
    | Invoke-SqlAssessment 3>&1
    | ConvertTo-LogOutput
    | ConvertTo-Json -AsArray
    | Set-Content $outPath -Encoding UTF8NoBOM
}
finally {
    Write-Verbose "Error count: $($Error.Count)"

    if ($Error) {
        $Error
        | ForEach-Object { @{ 'TimeStamp' = $(Get-Date).ToString("O"); 'Message' = $_.ToString() } }
        | ConvertTo-Json -AsArray
        | Set-Content $errorPath -Encoding UTF8NoBOM
    }
}

注

このスクリプトを信頼できる環境で実行していてプレログイン ハンドシェイク エラーが発生した場合は、コード内の $hostName、Get-SqlInstance、-TrustServerCertificate 行のコマンドに $serverName フラグを追加してください。

評価を実行する

  1. mssqlユーザーがスクリプトを所有し、実行できることを確認してください。

    chown mssql:mssql /opt/mssql/bin/runassessment.ps1
    chmod 700 /opt/mssql/bin/runassessment.ps1
    
  2. ログフォルダを作成し、 mssql ユーザーにそのフォルダの権限を与えてください。

    mkdir /var/opt/mssql/log/assessments/
    chown mssql:mssql /var/opt/mssql/log/assessments/
    chmod 0700 /var/opt/mssql/log/assessments/
    
  3. mssqlユーザーとして最初の評価を作成できます。 このユーザーとしてアセスメントを実行する方が安全で、特に後のアセスメントを cron や systemdで自動的に実行する場合に効果的です。

    su mssql -c "pwsh -File /opt/mssql/bin/runassessment.ps1"
    
  4. コマンドが終了すると、出力はJSON形式で生成されます。 この出力は、Visual Studio Codeを含むJSONファイルの解析をサポートするあらゆるツールで使用できます。