Zabbixで検知したエラーログをChatGPTで分析して、自動的にconfigファイルを修正する

# 1.はじめに

Zabbixのアクション機能を使用することで、アラートに合わせて自動復旧を実装できますが、
エラーログなどを監視している場合は、ログ内容が毎回異なるため、
自動復旧を組み込むには難易度が高いと思います。
しかし、AIを使用することでこの辺りも解決しやすくなってきたと思います。
今回は、Apache httpのエラーログをChatGPTで分析をかけて、エラー内容に関連したconfgファイルを修正するまでを自動化してみます。

なお、この記事はgithubのREADMEにも記載してあります。

https://github.com/doublethink-bps/autoFix

※英語で記載なので、日本語はこの記事を参照ください。

# 2. 使用する環境

以下の環境を用意します。
また、本記事で使用しているサーバのOSはAlmalinux9を導入しています。
### ZabbixServer
- ZabbixServer 6.0

### 監視対象サーバ
- Zabbix Agent 6.0
- python 3.11
- openai(pythonのライブラリ)
- httpd

# 3. 処理の概要

処理の流れは以下の図の様になります。
修正結果の送信は最後にZabbix Snderを実行し、ZabbixServer側でZabbix Traperを使用して実行結果をアイテムで収集します。(以下の図 6番の個所です）

![ai.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3143329/c49dbafc-3b0b-f63b-4fd8-065f79b991ab.png)

# 4. Zabbixの環境準備

Zabbixは以下のURLを参照して構築してください

https://www.zabbix.com/download?zabbix=6.0&os_distribution=alma_linux&os_version=9&components=server_frontend_agent&db=mysql&ws=apache

構築が完了したら、以下の設定を行います。

- 監視対象のホスト登録
- アイテム・トリガーの設定
- スクリプト設定
- アクション設定

ホスト登録はZabbix Agentを使用するインタフェースを持つホストを登録するだけなので、
本記事では省略します。以下のＵＲＬを参照してください。

https://www.zabbix.com/documentation/6.0/jp/manual/config/hosts/host

### アイテム設定

アイテムでは、[設定]->[ホスト]->[<監視対象ホスト>のitems]-[アイテムの作成]をクリックしてアイテムの設定画面に入り、以下のアイテムを設定します。


- httpd log 収集 
- Zabbix Trapper 
※Zabbix Trapperは監視対象から実行されたZabbix Senderからのメッセージを受け止めます。

[httpd log 収集」のアイテム httpdのエラーログ出力先を指定します。
エラーログの出力先がデフォルトの設定であれば、以下の様に設定をします。(Nameは任意です)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3143329/b31e459e-9232-0008-5279-4fd834eef5d5.png)

[Zabbix Trapper」のアイテム は以下の様に設定をします。(Nameは任意です)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3143329/57f7625a-bd15-fb90-6af9-24cf0a0fa6e5.png)


### トリガー設定

トリガーでは、[設定]->[ホスト]->[<監視対象ホスト>のtriggers]-[トリガーの作成]をクリックしてアイテムの設定画面に入り、[httpd log 収集」アイテムで収集したデータより、「error」という文字列があった場合、トリガーを発生させるように設定します。具体的な設定は以下の様になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3143329/dba1a17a-08a5-3fda-0935-e879ce06ca74.png)

上記「条件式」の設定は「条件式」項目の「追加」をクリックすると以下のフォームが表示されます。以下の様に設定して、「挿入」をクリックすると、上記画面のような条件式内容が入力されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3143329/33be8498-a557-50bf-a7d1-d6f73606b93a.png)

### スクリプト設定

スクリプトは [管理]->[スクリプト]->[スクリプトの作成]をクリックして、以下の図のように設定します。sshで監視対象サーバへアクセスするタイプで設定します。また、「コマンド」項目では、監視対象サーバ内のshellファイルを実行します。実行時にはアイテムで収集したデータをスクリプトの引数として使用します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3143329/08ad5570-225e-2583-6b71-38844abe49d8.png)

### アクション設定

[設定]->[アクション]->[トリガーアクション]-[アクションの作成]をクリックして、以下の画面を開きます。画面を開いたら、実行条件の「追加」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3143329/d7d9d764-d78e-878e-7179-955e48b74d02.png)

実行条件の「追加」をクリックすると、以下のフォームが開くのでタイプを「トリガー」にして、トリガーには上記で設定した、httpログの収集アイテムに対するトリガーを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3143329/2cba2da6-d5c3-467a-12e7-227a1a8974e6.png)

「実行内容」タブをクリックしたら、以下の画面が開きます。
画面が開いたら、「実行内容」項目の「追加」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3143329/03e84742-905d-093d-9159-7249b5529a7a.png)

「追加」をクリックしたら、以下のフォームが表示されます。「処理内容」に上記で設定したスクリプト設定で作成したスクリプトを指定します。ターゲットリストには監視対象のhostを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3143329/78d42db1-1bfd-7dae-2e31-062004cee2fb.png)

# 5. 監視対象サーバの設定

監視対象では、pythonとopenaiのライブラリをインストールします。
インストール後には、gitコマンドで、cloneします。スクリプトを/root配下に配置します。

```bash
dnf install python git
pip install openai　
cd /root
git clone https://github.com/doublethink-bps/autoFix.git
```

以下のスクリプトでは、zabbix senderコマンドの個所を修正する必要があります。「zabbix_sender -z <ZabbixServerのIP> -p 10051 -s "<Zabbixに登録したhost名>" -k zabbixTrapper -o "$result"」に直します。
```bash:zabbixAutofix.sh
#!/bin/bash

# Run the script to fix config file
result=$(python3 ./autoFix.py "$1")
res=$?
# If return code is 0, send result of runed the command to zabbix server.
if [ $res = 0 ]; then
    zabbix_sender -z <ZabbixServerのIP> -p 10051 -s "<Zabbixに登録したhost名>" -k zabbixTrapper -o "$result"
# If return code is not 0, send error message to zabbix server.
else
    zabbix_sender -z <ZabbixServerのIP> -p 10051 -s "<Zabbixに登録したhost名>" -k zabbixTrapper -o "Error runed script."
fi
```

# 6. ChatGPTの実行

まず、ログの分析を行い、修正すべきconfigファイルとパス、そしてパラメータを結果として取得します。パラメータはjson形式で特定の形にして取得したいため、FunctionCallingを使用します。
以下は、上記ログ分析を行う際に実行されるFunction(tools)です。
```python
# エラーの原因となっているコンフィグファイルとパスとパラメータを取得するためのFunction
get_configfile_path_parameter_tools = [
    {
        "type": "function",
        "function":{
            "name":"get_file_info",
            "description":f"Assuming an environment using redhat, obtain the config file to be investigated, the path of the config file, and the parameters in the config file to be investigated from the httpd error log.",
            "parameters" :{
                "type": "object",
                "properties":{
                    "file":{
                        "type":"string",
                        "description":"the config file to be investigated."
                    },
                    "path":{
                        "type":"string",
                        "description":"The config file path to be investigated."
                    },
                    "parameter":{
                        "type":"string",
                        "description":"The paramter to be investigated."
                    }
                },
            "required":["file","path","parameter"]
            }
        },
        
    }
]
```
上記のFunctionを使用して、その結果を得るためには以下の様にopenaiのライブラリを使用します。
ライブラリに指定する引数の[messages]には、シェルスクリプト実行時に引数として渡されたログ内容を含めて、ChatGPTで分析を行います。

```python
# スクリプト実行時の引数をdata変数に代入
data = sys.argv[1]

# コンフィグファイルとパスとパラメータを受け取る
response = openai.chat.completions.create(
    model="gpt-4-1106-preview",
    messages=[
            {"role":"user","content":f"From the following log , answer the configuration file to be investigated,the path of configuration file to be investigated and the parameters within the configuration file to be investigated. Loggile:{data}"}
    ],
    tools=get_configfile_path_parameter_tools,
    tool_choice="auto",
    temperature=0
)
```


Function callingが実行されたら、実行結果からコンフィグファイルと
```python
tool_calls=response.choices[0].message.tool_calls
    # Function callingで関数を呼び出し、実行結果を得られていたら、以下を実行していく
    if(tool_calls):
        for tool_call in tool_calls:
            parameter=json.loads(tool_call.function.arguments)["parameter"]
            filepath = json.loads(tool_call.function.arguments)["path"]+json.loads(tool_call.function.arguments)["file"]
            # 取得したファイルの内容をsubprocessライブラリでcatを実行することで取得する。
            result = subprocess.run(f"cat {filepath}",shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
            # ファイルが存在するのであれば、ChatGPT再度分析をかけて、パラメータに対する修正すべき値をと修正前の値を取得する。
            if (result.returncode == 0):
                # ChatGPTで再度分析をかけて、パラメータに対する修正すべき値をと修正前の値を取得する。
                value_response = openai.chat.completions.create(
                    model="gpt-4-1106-preview",
                    messages=[
                        {"role":"assistant","content":f"Error log is occured due to the contents of configuration file of {filepath}.This contents of configuration file is the follow.\n\n{result.stdout}"},
                        {"role":"user","content":f"I would like to change the value of {parameter} in {filepath} to fix the following error log. Please tell me the value before fix and the value afiter fix."}
                    ],
                    tools=get_value_tools,
                    tool_choice="auto",
                    temperature=0
                )
                value_tool_calls=value_response.choices[0].message.tool_calls
                beforeValue=json.loads(value_tool_calls[0].function.arguments)["before"]
                afterValue=json.loads(value_tool_calls[0].function.arguments)["after"]
                dt_now = datetime.datetime.now().strftime('%Y%m%d%H%M%S')                
                copyCommandResult=subprocess.run(f"cp -p {filepath} {filepath}_{dt_now}",shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
                #cpコマンドに成功していたら、コンフィグファイル内のパラメータに対する値を修正すべき値に置換します。
                if(copyCommandResult.returncode==0):
                    changeCommand=f"sed -i s/\"{beforeValue}\"/\"{afterValue}\"/ {filepath}_{dt_now}"
                    commandRedult=subprocess.run(f"{changeCommand}",shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
                    # 置換に成功したら、Zabbixに送信する値を返します。
                    if(commandRedult.returncode==0):
                        print(f"replace {afterValue} from {beforeValue} in {filepath}_{dt_now}")
                else:
                    print("copy command error:"+copyCommandResult.stderr)
            else:
                print("file not exited")
    else:
        print("Did not call functions")
```

上記で、再度ChatGPTで分析をしていますが、この時もFunction Callingを使用しています。この時指定したFunctionは以下の様になっています。

```python
# コンフィグファイルの内容から、修正すべき値と修正前の値を取得します。
get_value_tools = [
    {
        "type": "function",
        "function":{
            "name":"get_value_info",
            "description":f"Assumeing enviroment where redhat is used, value for parameters for resolving problems in httpd error logs are obtained from the configraton file and parameters.",
            "parameters" :{
                "type": "object",
                "properties":{
                    "before":{
                        "type":"string",
                        "description":"Value before fix"
                    },
                    "after":{
                        "type":"string",
                        "description":"Value after fix"
                    }
                },
            "required":["before","after"]
            }
        },
        
    },
]
```

# 7. 実行例

上記を設定したうえで、httpdに高負荷をかけてエラーを起こします。
すると、以下のようなログを取得することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3143329/24c0310f-c97f-bfe9-2e48-e0f93e83dc30.png)

上記を確認できると、数秒後にzabbix trapperアイテムのヒストリを確認すると、何からどの値に変更したのか、メッセージで確認できます。以下の例だと、もとのコンフィグファイルからコピーして作成された「httpd.conf_20231120210326」ファイルに対し、「MaxClients 1」から「MaxRequestWorkers 150」に変更されています。2.4系だとMaxClientsからMaxRequestWorkersに代わっています。また、値も1→150に変更されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3143329/2a1505f7-5d5e-95e5-e215-29b68067f358.png)

# 8.課題

エラーが起きるたびにconfigファイル作成してしまうところを改善しないと大量にエラーが吐き出されると煩雑になりそうです。。。
config変更すれば解決できるわけではないため、使えるところが結構ピンポイントになるような気がします。新規にファイル追加したり、htmlファイルの配置先を変えたり等、もう少し幅広く自動的にエラーを解決できるようにしたいです。

# 9. 参考URL


■GitHub
※Githubは私のリポジトリのURLとなります。

https://github.com/doublethink-bps/autoFix

■Zabbix

https://www.zabbix.com/documentation/6.0/jp/manual