OPS

Zabbix+Windowsで自動復旧システムを構築、運用工数が90%削減

Zabbix+Windowsで自動復旧システムを構築、運用工数が90%削減した話

2022.04.20

本記事のポイント

本記事では、過去に大量のアラートの復旧作業をしていた際に直面した問題と、自動で復旧させる具体的な方法をご紹介します。



大量の監視アラートに疲弊。障害の自動復旧は可能?

お客様のシステム監視運用業務を代行する当社では、特定の警報が発報した際、一次対応として予め決められた手順に従い復旧作業を行う場合があります。一次対応は基本的にプロセス監視などが主ですが、お客様によってはバッチの再起動を行う場合もあります。

本記事では、過去に大量のアラートの復旧作業をしていた際に直面した問題と、自動で復旧させる方法の一例をご紹介します。

自動化した監視アラートの概要

自動化した監視アラートは、バッチが動作していない場合に特定のログが更新されず、それが一定時間経過した際に「バッチが止まっている可能性がある」という旨をメールで通知されるといったものでした。

そして該当メールを検知した際、一次対応として対象のバッチが動作している環境にログインし、バッチの稼働状況を確認し、稼働していなければ手動でバッチを起動させるといった対応を実施していました。対応内容としては1対応15分程度でしたが、問題はその検知数です。1日平均300程度、多い場合だと400~500以上となる場合もあり、同時多発的に発生するものでした。

また、一次対応して復旧したとしても1~2時間後にはまた同じバッチが停止し一次対応を実施する必要があり、対応数・検知数ともに多いことから対応にあたるエンジニアも徐々に疲弊している状況でした。

そこで、この有人での対応を一部でも自動化できないかと考え、検討を始めます。


> AWS の「システム運用代行」詳細はこちら

ビジネス価値と運用のあるべき姿

まずは、有人での作業をスクリプト化

少しでも有人対応時の作業時間を短縮させるため、まずは一次対応時の作業をスクリプト化し、省力化できないか検討することとしました。

対応内容の洗い出し

スクリプト化するにあたり、まずは有人で対応している内容を列挙しました。

対応 詳細
1. 障害メール検知 障害メールを検知次第、手順に従い一次対応を行う
2. 対象環境へログイン 踏み台サーバより対象サーバにアクセス
3. ジョブの稼働状況を確認 ジョブが稼働中の場合はプロセスを停止し、ジョブが停止中の場合は何もせず次の手順へ進む
4. ジョブの起動 対象ジョブのディレクトリに移動、コマンドプロンプトよりバッチファイルを実行する
5. 復旧確認 復旧メールが検知することを確認

スクリプトの作成

対応環境はWindowsであったため、スクリプトはPowerShellで実装、UIとしてVBAのWindowオブジェクトを使用することとします。処理の流れは以下の通りです。

  • 受け付けた障害メールから、件名を基に対象環境と対象ジョブを特定
  • 特定した環境とスクリプトを実行している環境を突き合わせ
  • 対象ジョブ名からジョブ名を「key」、バッチファイルパスを「value」としているリストファイルから、valueを取得
  • そのパスを使用してバッチファイルを実行

  • 実際のスクリプトにはお客様の環境固有のパス等も含まれるため一部伏せた形となりますが、以下がサンプルコードとなります。

    process_check.ps1
    # =================================================================================== #
    
    # プロセスファイルが配置されるディレクトリ
    $PROCESS_DIR = "バッチファイル稼働時に出力するプロセスファイル格納先ディレクトリ"
    
    # プロセスファイル名のプレフィックス
    $PROCESS_FILE_PREFIX = "バッチファイル稼働時に出力するプロセスファイル名のプレフィックス"
    
    # バッチファイルが格納されているディレクトリ
    $BATCH_DIR = "バッチファイル格納先ディレクトリ"
    
    # APLログのディレクトリ
    $APL_LOG_DIR = "アプリケーションログの出力先"
    
    # OPEログのディレクトリ
    $OPE_LOG_DIR = "オペレーションログファイルの出力先"
    
    # ログ確認フラグ
    $LOG_FILE_EXIST_FLG = $true
    
    # メッセージレベル
    $NOTICE = "【通知(NOTICE)】"
    $INFO = "【情報(INFORMATION)】"
    $WARN = "【!警告(WARNING)!】"
    $ERR = "【■失敗(ERROR)■】"
    $FATAL = "【★致命的(FATAL)★】"
    
    # サーバのホスト名を取得
    $SERVER_NAME = hostname
    
    # 再起動実行対象プロセスIDリストの外部ファイルのパス(.ps1ファイルと同階層に配置が前提)
    $LIST_FILE_PATH = Convert-Path .\process_list.ini
    
    # 再起動実行対象プロセスIDリストの連想配列
    $PROCESS_ID_LIST = @{}
    
    # 再起動実行対象プロセスIDリストの内容取得
    function GetProcessIdList($filename)
    {
      $arg = @{}
      $lines = get-content $filename
    
      foreach($line in $lines){
    
        # コメントを除外する
        if($line -match "^$"){ 
    
            continue
    
        }
    
        # 空行を除外する
        if($line -match "^\s*;"){
    
            continue
    
        }
    
        $param = $line.split("=",2)
        $arg[$param[0]] = $param[1]
    
      }
    
      return $arg
    
    }
    
    # ---------ここから-------------------------------------------------------------------------------------
    # アセンブリの読み込み
    Add-Type -AssemblyName System.Windows.Forms
    Add-Type -AssemblyName System.Drawing
    
    # フォームの作成
    $form = New-Object System.Windows.Forms.Form
    $form.Text = "入力"
    $form.Size = New-Object System.Drawing.Size(260,180)
    
    # OKボタンの設定
    $OKButton = New-Object System.Windows.Forms.Button
    $OKButton.Location = New-Object System.Drawing.Point(40,100)
    $OKButton.Size = New-Object System.Drawing.Size(75,30)
    $OKButton.Text = "OK"
    $OKButton.DialogResult = "OK"
    
    # キャンセルボタンの設定
    $CancelButton = New-Object System.Windows.Forms.Button
    $CancelButton.Location = New-Object System.Drawing.Point(130,100)
    $CancelButton.Size = New-Object System.Drawing.Size(75,30)
    $CancelButton.Text = "Cancel"
    $CancelButton.DialogResult = "Cancel"
    
    # ラベルの設定
    $label = New-Object System.Windows.Forms.Label
    $label.Location = New-Object System.Drawing.Point(10,30)
    $label.Size = New-Object System.Drawing.Size(250,20)
    $label.Text = "メールタイトルを入力してください。"
    
    # 入力ボックスの設定
    $textBox = New-Object System.Windows.Forms.TextBox
    $textBox.Location = New-Object System.Drawing.Point(10,70)
    $textBox.Size = New-Object System.Drawing.Size(225,50)
    
    # キーとボタンの関係
    $form.AcceptButton = $OKButton
    $form.CancelButton = $CancelButton
    
    # ボタン等をフォームに追加
    $form.Controls.Add($OKButton)
    $form.Controls.Add($CancelButton)
    $form.Controls.Add($label)
    $form.Controls.Add($textBox)
    
    # フォームを表示させ、その結果を受け取る
    $result = $form.ShowDialog()
    
    # 結果による処理分岐
    if ($result -eq "OK") {
    
        $x = $textBox.Text
    
    } else {
    
       [System.Windows.Forms.MessageBox]::Show("キャンセルを選択しました。`r`n処理を中断します。",$WARN,"OK","Exclamation")
       exit
    
    }
    
    # ---------ここまで-------------------------------------------------------------------------------------
    
    # タブが含まれていたら削除する
    $x = $x -replace "`t", ""
    
    # 入力した文字列の分割
    $STR = $x.Split()
    
    # 復旧済みか確認
    if ($STR[-1] -eq "OK") {
    
        [System.Windows.Forms.MessageBox]::Show("復旧済みです。`r`n処理を中断します。",$WARN,"OK","Exclamation")
        exit
    
    }
    
    # 作業対象ホストが一致するか
    if ($STR[5] -ne $SERVER_NAME) {
    
        # 一致しなければポップアップで警告し、処理中止
        [System.Windows.Forms.MessageBox]::Show("作業対象サーバが一致しません`r`n処理を中断します。`r`n現在ログイン中のサーバ:" + $SERVER_NAME ,$WARN,"OK","Exclamation")
        exit
    
    } else {
    
        # 対象サーバをポップアップで出力し、処理継続
        [System.Windows.Forms.MessageBox]::Show("作業サーバは以下です`r`n" + $SERVER_NAME, $NOTICE,"OK","Information")
    
    }
    
    # 再起動実行対象プロセスIDリストの外部ファイルから一覧取得
    $PROCESS_ID_LIST = GetProcessIdList $LIST_FILE_PATH
    
    # 再起動実行対象リストと入力文字が一致するか確認
    if($PROCESS_ID_LIST.ContainsKey($STR[3])){
    
        # ある場合は変数に格納
        $PROCESS_ID = $STR[3]
    
    } else {
    
        # ない場合は警告し、中断
        [System.Windows.Forms.MessageBox]::Show("アラートの対象は再起動実行対象ではありません。`r`n処理を中断します。",$WARN,"OK","Exclamation")
        exit
    
    }
    
    # プロセスファイル名を変数化(process_VisualWebRipperStartTimerTask + プロセスID名)
    $PROCESS_FILE_NAME = $PROCESS_FILE_PREFIX + $PROCESS_ID
    
    # プロセスファイルの絶対パスを変数化
    $PROCESS_PATH = Join-Path $PROCESS_DIR $PROCESS_FILE_NAME
    
    # プロセスファイルの存在確認を実施
    if(Test-Path $PROCESS_PATH) {
    
        # プロセスファイルが存在する場合
        # ダイアログを出して削除を確認
        $msgBoxInput = [System.Windows.Forms.MessageBox]::Show("プロセスファイルが存在します。削除しますか?`r`nプロセスファイルパス:" + $PROCESS_PATH, $INFO,'YesNo','Question')
    
        switch ($msgBoxInput) {
    
            'Yes' {
    
                # はいを選択した場合、ファイルの削除を実行する
                $pathObject = [IO.FileInfo]$PROCESS_DIR
                Invoke-Item $pathObject
    
                try{
    
                    Remove-Item $PROCESS_PATH -ErrorAction:Stop 
                    [System.Windows.Forms.MessageBox]::Show("ファイルを削除しました。",$NOTICE,"OK","Information")
    
                } catch {
    
                    [System.Windows.Forms.MessageBox]::Show("別のプロセスにロックされていて削除できません。",$FATAL,"OK","Error")
                    exit
    
                }
    
            }
    
            'No' {
    
                # いいえを選択した場合、処理を中断する
                [System.Windows.Forms.MessageBox]::Show("いいえを選択しました。`r`n処理を中断します。",$WARN,"OK","Exclamation")
                exit
    
            }
    
        }
    
    } else {
    
        # プロセスファイルが存在しない場合
        [System.Windows.Forms.MessageBox]::Show("プロセスファイルが存在しません。`r`n削除処理はスキップします。",$INFO,"OK","Information")
    
    }
    
    # batchファイルディレクトリパス + プロセスIDでパスを変数化
    $BATCH_DIR_PATH = Join-Path $BATCH_DIR $PROCESS_ID
    
    # batchファイル名を変数化
    $BATCH_FILE = $PROCESS_ID_LIST[$PROCESS_ID]
    
    # batchファイルの絶対パスを変数化
    $BATCH_FILE_PATH = Join-Path $BATCH_DIR_PATH $BATCH_FILE
    
    # batchファイル名だけを切り出し
    $BATCH_FILE_NAME = $BATCH_FILE.Substring(0,18) 
    
    # アプリケーションログファイル名を変数化
    $APL_LOG_NAME = Join-Path $APL_LOG_DIR $BATCH_FILE_NAME
    
    # ログファイルに拡張子付与
    $APL_LOG_FILE_PATH = $APL_LOG_NAME + ".log"
    
    # ログファイルの存在確認
    if((Test-Path $APL_LOG_FILE_PATH)) {
    
        # ログが存在する場合
        [System.Windows.Forms.MessageBox]::Show("ログが存在します。", $INFO, "OK", "Information")
    
    } else {
    
        # ログが存在しない場合
        [System.Windows.Forms.MessageBox]::Show("ログが存在しません。`r`n念のため問題ないことを確認してください。", $WARN, "OK", "Exclamation")
        $LOG_FILE_EXIST_FLG = $false
    
    }
    
    # バッチ実行前のログファイル更新日時取得
    $BEFORE_EXEC_LOG_FILE_TIMESTAMP = $(Get-ItemProperty $APL_LOG_FILE_PATH).LastWriteTime
    
    # batchファイルを実行
    # ダイアログを出力してバッチ実行前の確認を行う
    $msgBoxInput = [System.Windows.Forms.MessageBox]::Show("batファイルを実行します。`r`nよろしいですか?`r`nbatファイル名:" + $BATCH_FILE, $INFO, 'YesNo', 'Question')
    
    switch ($msgBoxInput) {
    
            'Yes' {
    
                # はいを選択した場合、batファイルを実行
    
                try{
    
                    # batchファイルを実行し処理終了まで待機
                    Start-Process $BATCH_FILE_PATH -PassThru -WindowStyle Hidden -Wait
                    [System.Windows.Forms.MessageBox]::Show("batファイルを実行しました。",$INFO,"OK","Information")
    
                } catch {
    
                    # batchファイルが存在しない・実行できない場合
                    [System.Windows.Forms.MessageBox]::Show("バッチファイルが存在しないか、実行できません。`r`n処理を中断します。",$ERR,"OK","Error")
                    exit
    
                }
    
            }
    
            'No' {
    
                # いいえを選択した場合、処理を中断する
                [System.Windows.Forms.MessageBox]::Show("いいえを選択しました。`r`n処理を中断します。",$WARN,"OK","Exclamation")
                exit
    
            }
    
    }
    
    # バッチ実行後のログファイル更新日時取得
    $AFTER_EXEC_LOG_FILE_TIMESTAMP = $(Get-ItemProperty $APL_LOG_FILE_PATH).LastWriteTime
    
    # プロセスファイルの存在確認を実施
    if(Test-Path $PROCESS_PATH) {
    
        #プロセスファイルが存在する場合
        [System.Windows.Forms.MessageBox]::Show("プロセスファイルが存在します。`r`nログの確認を実施します。",$NOTICE,"OK","Information")
    
    } else {
    
        # プロセスファイルが存在しない場合
        [System.Windows.Forms.MessageBox]::Show("プロセスファイルが存在しません。`r`nログの確認を実施します。",$ERR,"OK","Error")
    
    }
    
    # ログファイルの存在確認
    if((Test-Path $APL_LOG_FILE_PATH)) {
    
        # ログファイルの更新を確認
        if($AFTER_EXEC_LOG_FILE_TIMESTAMP -gt $BEFORE_EXEC_LOG_FILE_TIMESTAMP -And $LOG_FILE_EXIST_FLG) {
    
            # バッチ実行後のログファイルの更新日時が実行前の更新日時よりも新しい場合かつログ確認フラグがTrueの場合
            [System.Windows.Forms.MessageBox]::Show("ログファイルの更新を確認しました。",$NOTICE,"OK","Information")
    
        } elseif($LOG_FILE_EXIST_FLG -eq $false) {
    
            # バッチ実行により新規にログファイルが出来た場合
            [System.Windows.Forms.MessageBox]::Show("ログファイルが新規に生成されました。",$NOTICE, "OK","Information")
    
        } else {
    
            # 実行前の更新日時と実行後の更新日時に変化がない場合
            [System.Windows.Forms.MessageBox]::Show("ログファイルが更新されていません。`r`n処理を中断します。",$ERR,"OK","Error")
            exit
    
        }
    
    } else {
    
        # ログファイルがなければ処理中断
        [System.Windows.Forms.MessageBox]::Show("ログが存在しません。`r`n処理を中断します。",$ERR,"OK","Error")
        exit
    
    }
    
    [System.Windows.Forms.MessageBox]::Show("終了します。",$INFO,"OK","Information")
    
    


    > AWS の「システム運用代行」詳細はこちら

    ビジネス価値と運用のあるべき姿

    自動復旧システムを構築

    スクリプトにより省力化したものの、依然として障害通知数は多く有人での対応数も減少していないため、一次対応を完全に自動化することとしました。

    お客様の環境ではZabbixが稼働しておりZabbixから障害の通知が飛んでいたため、障害通知と合わせて復旧作業を自動で実施できないかを検討しました。おおよそのイメージは以下の通りです。

    自動復旧システムの構築イメージ

    Zabbix リモートコマンド

    Zabbixには事前に定義しておいたスクリプトを実行するカスタムスクリプトという機能があります。実行元をZabbixサーバとするか、Zabbixの監視エージェントがインストールされているクライアント内とするかなどを選択可能で、クライアント内で動作するものをリモートコマンドと呼称しています。

    この機能を使用し、先に作成したスクリプトを一部改修することで、Zabbixでの障害検知と同時に復旧作業を自動で実行する仕組みとすることで有人での対応数を大幅に削減させることができると考えました。

    使用するツール

    ツールは上述のZabbixのほか、PowerShell、スクリプトの実行時のログ出力を行うためにWindowイベントログを使用することとしました。

  • Zabbix
  • PowerShell
  • Windowイベントログ
  • スクリプトを自動実行

    先に作成したスクリプトを自動実行用に改修しました。主な変更点は下表の通りです。

    変更点 手動用スクリプト 自動用スクリプト
    件名の渡し方 スクリプト実行時に表示されるテキストボックスに貼り付け Zabbixのイベント名を自動取得
    プロセスIDリストファイル確認 チェック無し チェック実施
    ログ出力 無し Windowsのイベントログに出力
    ダイアログ出力 各種チェック、テキストボックス等のダイアログ出力 無し(ログ出力で代替)

    改修したスクリプトのサンプルは下記の通りです。こちらも実際のスクリプトはお客様の環境固有のパス等も含まれるため、一部伏せた形となります。

    process_list.iniは内容が同一のため、割愛します。

    auto_process_check.ps1
    # =================================================================================== #
    
    Param(
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        $JOB, 
        $ARRERT_TITLE, 
        $PROCESS_ID_ARG, 
        $HOST_NAME
    )
    
    New-EventLog -LogName Application -Source RobotAutoReset -ErrorAction SilentlyContinue
    Write-EventLog -LogName Application -Source RobotAutoReset -EntryType Information -EventId 10011 -Message ("ジョブ自動再起動を開始します.  " + "ジョブ名:" + $JOB + " アラートタイトル:" + $ARRERT_TITLE + " プロセスID:" + $PROCESS_ID_ARG + " ホスト名:" + $HOST_NAME)
    
    # プロセスファイルが配置されるディレクトリ
    $PROCESS_DIR = "バッチファイル稼働時に出力するプロセスファイル格納先ディレクトリ"
    
    # プロセスファイル名
    $PROCESS_FILE_PREFIX = "バッチファイル稼働時に出力するプロセスファイル名のプレフィックス"
    
    # バッチファイルが格納されているディレクトリ
    $BATCH_DIR = "バッチファイル稼働時に出力するプロセスファイル格納先ディレクトリ"
    
    # APLログのディレクトリ
    $APL_LOG_DIR = "アプリケーションログの出力先"
    
    # OPEログのディレクトリ
    $OPE_LOG_DIR = "オペレーションログファイルの出力先"
    
    # サーバのホスト名を取得
    $SERVER_NAME = hostname
    
    # 手動実行用プロセスIDリストの外部ファイルのパス
    $LIST_FILE_PATH = "ローカルのプロセスIDリストファイルパス"
    
    # オリジナルのプロセスIDリストの外部ファイルのパス
    $ORIGIN_LIST_FILE_PATH = "マスタとなるプロセスIDリストファイルパス"
    
    # 手動実行用プロセスIDリスト
    $PROCESS_ID_LIST = @{}
    
    # バッチ実行ユーザのパスワード保存パス
    $Username = '〓ユーザ名〓'
    $Password = '〓パスワード〓'
    $pass = ConvertTo-SecureString -AsPlainText $Password -Force
    $SecureString = $pass
    $MySecureCreds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Username,$SecureString 
    
    # 再起動実行対象プロセスIDリストの内容取得
    function GetProcessIdList($filename)
    {
      $arg = @{}
      $lines = get-content $filename
      foreach($line in $lines){
        # コメントと空行を除外する
        if($line -match "^$"){ continue }
        if($line -match "^\s*;"){ continue }
    
        $param = $line.split("=",2)
        $arg[$param[0]] = $param[1]
      }
      return $arg
    }
    
    # リストファイル存在チェック
    if(Test-Path $LIST_FILE_PATH) {
        # ローカルに存在している場合
        # オリジナルファイル存在チェック
        if(Test-Path $ORIGIN_LIST_FILE_PATH) {
            # オリジナルファイルをローカルへコピー
            Copy-Item -Path $ORIGIN_LIST_FILE_PATH -Destination $LIST_FILE_PATH -Force
        } else {
            # オリジナルファイルがある共有フォルダが見れない場合はローカルのファイルを使用
            # なのでなにもしない
        }
    # ローカルに存在しない場合はオリジナルファイルチェック
    } elseif(Test-Path $ORIGIN_LIST_FILE_PATH) {
        # オリジナルファイルがある場合はローカルにコピー
        Copy-Item -Path $ORIGIN_LIST_FILE_PATH -Destination $LIST_FILE_PATH -Force
    } else {
        # ローカルもオリジナルファイルも取得できない場合は処理終了
        Write-EventLog -LogName Application -Source RobotAutoReset -EntryType Error -EventId 10031 -Message "プロセスIDを記載したリストを取得できません. 処理を終了します."
        exit
    }
    
    # 作業対象ホストが一致するか
    if ($HOST_NAME -ne $SERVER_NAME) {
        # 一致しなければイベントログ出力し、処理中止
        Write-EventLog -LogName Application -Source RobotAutoReset -EntryType Error -EventId 10032 -Message ("作業対象サーバが一致しません.処理を中断します. " + "対象ホスト名:" + $HOST_NAME + " 実際のサーバ名:" + $SERVER_NAME)
        exit
    } else {
        # 対象サーバが一致した際はそのまま処理を継続
    }
    
    # 再起動実行対象プロセスIDリストの外部ファイルから一覧取得
    $PROCESS_ID_LIST = GetProcessIdList $LIST_FILE_PATH
    
    # 再起動実行対象リストと入力文字が一致するか確認
    if($PROCESS_ID_LIST.ContainsKey($PROCESS_ID_ARG)){
        # ある場合は変数に格納
        $PROCESS_ID = $PROCESS_ID_ARG
    } else {
        # ない場合はイベントログ出力し、処理中断
        Write-EventLog -LogName Application -Source RobotAutoReset -EntryType Error -EventId 10033 -Message ("アラートの対象は再起動実行対象ではありません.処理を中断します. " + "対象プロセスID:" + $PROCESS_ID_ARG)
        exit
    }
    
    # プロセスファイル名を変数化(process_VisualWebRipperStartTimerTask + プロセスID名)
    $PROCESS_FILE_NAME = $PROCESS_FILE_PREFIX + $PROCESS_ID
    
    # プロセスファイルの絶対パスを変数化
    $PROCESS_PATH = Join-Path $PROCESS_DIR $PROCESS_FILE_NAME
    
    # プロセスファイルの存在確認を実施
    if(Test-Path $PROCESS_PATH) {
        # プロセスファイルが存在する場合
        $pathObject = [IO.FileInfo]$PROCESS_DIR
        Invoke-Item $pathObject
        try{
            Remove-Item $PROCESS_PATH -ErrorAction:Stop 
            Write-EventLog -LogName Application -Source RobotAutoReset -EntryType Information -EventId 10012 -Message "ファイルを削除しました."
        } catch {
           Write-EventLog -LogName Application -Source RobotAutoReset -EntryType Error -EventId 10034 -Message "別のプロセスにロックされていて削除できません. 処理を中断します."
           exit
        }
    } else {
        # プロセスファイルが存在しない場合
        Write-EventLog -LogName Application -Source RobotAutoReset -EntryType Warning -EventId 10021 -Message "プロセスファイルが存在しません. 処理を継続します."
    }
    
    # batchファイルディレクトリパス + プロセスIDでパスを変数化
    $BATCH_DIR_PATH = Join-Path $BATCH_DIR $PROCESS_ID
    
    # batchファイル名を変数化
    $BATCH_FILE = $PROCESS_ID_LIST[$PROCESS_ID]
    
    # batchファイルの絶対パスを変数化
    $BATCH_FILE_PATH = Join-Path $BATCH_DIR_PATH $BATCH_FILE
    
    # batchファイルを実行
    try{
        #$myprocss = Start-Process $BATCH_FILE_PATH -PassThru
        echo $Credential
        $EXEC_JOB = Start-Job -ScriptBlock {& $using:BATCH_FILE_PATH } -Credential $MySecureCreds
        # batchファイルの処理終了まで待機
        Wait-Job -Job $EXEC_JOB
        # $myprocss.WaitForExit()
        Write-EventLog -LogName Application -Source RobotAutoReset -EntryType Information -EventId 10013 -Message "batファイルを実行しました."
    } catch {
        # batchファイルが存在しない・実行できない場合はイベントログ出力し、処理中断
        Write-EventLog -LogName Application -Source RobotAutoReset -EntryType Error -EventId 10035 -Message "バッチファイルが存在しないか、実行できません. 処理を中断します."
        exit
    }
    
    # プロセスファイルの存在確認を実施
    if(Test-Path $PROCESS_PATH) {
        #プロセスファイルが存在する場合
        Write-EventLog -LogName Application -Source RobotAutoReset -EntryType Information -EventId 10014 -Message ("プロセスファイルが存在します. " + "プロセスファイルパス:" + $PROCESS_PATH)
    } else {
        # プロセスファイルが存在しない場合はイベントログ出力し、処理中断
        Write-EventLog -LogName Application -Source RobotAutoReset -EntryType Error -EventId 10036 -Message "プロセスファイルが存在しません. 処理を中断します."
        exit
    }
    
    # APLログファイル名を変数化
    $APL_LOG_NAME = Join-Path $APL_LOG_DIR $BATCH_FILE_NAME
    # ログファイルに拡張子付与
    $APL_LOG_FILE_PATH = $APL_LOG_NAME + ".log"
    
    # OPEログファイル名を変数化
    $OPE_LOG_NAME = Join-Path $OPE_LOG_DIR $BATCH_FILE_NAME
    # ログファイルに拡張子付与
    $OPE_LOG_FILE_PATH = $OPE_LOG_NAME + ".log"
    
    # ログファイルの存在確認
    if((Test-Path $APL_LOG_FILE_PATH) -Or (Test-Path $OPE_LOG_FILE_PATH)) {
    
        # ログが存在する場合
        Write-EventLog -LogName Application -Source RobotAutoReset -EntryType Information -EventId 10015 -Message "ログが存在します."
    }
    Write-EventLog -LogName Application -Source RobotAutoReset -EntryType Information -EventId 10016 -Message "ロボット自動再起動を終了します."
    
    


    > AWS の「システム運用代行」詳細はこちら

    ビジネス価値と運用のあるべき姿

    利用効果

    自動化により、有人対応70~80%の削減を目標に構築していましたが…

    有人での対応数 90%削減!

    有人での対応数 90%削減と、予想以上の効果がありました。

    完全自動化にならない理由は、障害報から復旧報までの待ち時間を15分としており、その間に復旧しなかったものについては改めて有人で確認し、対応の要否を検討する必要があるケースも存在するためです。

    自動化導入前の有人対応数は週あたり1945件となっており、スクリプトを使用していたとはいえ、かなりの作業時間を割いていました。しかし、自動化導入後の週では有人対応数が週当たり178件と導入前と比べ90.85%の対応を完全自動で対応できたこととなり、想定以上の効果を発揮しました。

    今後の課題

    今後の課題としては、リモートコマンド実行時のユーザが「NT AUTHORITY\SYSTEM」となり、処理によっては意図しない挙動となる場合があることが挙げられます。スクリプト内でユーザの切り替えやその他方法を検討しましたが、事象の解決には至っていません。

    「NT AUTHORITY\SYSTEM」となってしまう原因としては、Window環境ではZabbixエージェントが「NT AUTHORITY\SYSTEM」で動作しているためだと考えられます。

    ただし、実行されるユーザに影響を受けない処理であれば問題なく動作することは確認しており、その点さえ影響がなければ有用な仕組みであると考えます。

    まとめ

    この自動化は周囲にも好評でしたが、改めて振り返ってみたいと思います。

    実施してみてよかったこと

    まずは、弊社メンバの負荷を大幅に削減することができた点が一番かと思っています。

    弊社はシェアリングモデルで対応しているため、どうしても特定のメンバを貼りつきで対応させることが難しく、また本障害数から一次対応に2~3名程度必要とする状況だったため、その他障害対応のために各自の負荷が上昇している傾向にありましたが、自動化により各自の負荷を軽減することができ、結果として弊社内全体の負荷を軽減できました。

    また個人的は周囲からの感謝や信頼感を得ることができた点も良かったと思っています。

    汎用性が高く、様々な環境に応用可能

    このシステム自体は特定のお客様の一次対応に合わせて検討・構築したものでしたが、完成したシステムは、ツールさえそろえれば、どの環境でも使用できる汎用性の高いものとなっています。

    また処理自体は、スクリプトから特定のコマンドを実行するという非常にシンプルなもののため、多少のカスタマイズでどの環境・手順にも応用できる拡張性も有していると考えています。

    さいごに

    最後となりましたが、一次対応は運用を行っていく上ではどうしても必要な対応となります。しかし、そこが負荷となってしまうと長続きしないのも現状としてあります。

    同様の事象でお悩みの方もいらっしゃると思いますので、本記事が少しでも参考になれば幸いです。

    また、もし自社でこのような自動化は組めない、システムの運用監視や障害対応は外注したいとお考えの企業様は、 こちらよりぜひお気軽にご相談 ください。対応範囲を柔軟に定義し、お客様のご要望に合わせた運用メニューをご提案させていただきます!

    > 「システム運用代行」サービスメニューはこちら

    > 「システム運用代行」ご相談はこちら

    ビジネス価値と運用のあるべき姿