はじめに
本記事は、過去に私が実施した施策を記載したものです。私はネットワーク監視・運用・保守を主務とするエンジニアで、現場の稼働削減と初動対応の迅速化を目的として、通常業務と並行しながら、Pythonを用いた運用改善に取り組んでいます。
ベストプラクティスではない部分はあるかと思いますが、今回の実装環境とアプローチに関しては同様の制約を持つ現場でも応用できる部分があると考えています。
同様の課題をお持ちの方の一助になればと思います。
経緯
OSI参照モデルにおけるL2・L3の通信の正常性を確認する際は、インターフェースのエラー情報や装置のログ、アラーム情報など、様々な要素を確認します。
その中でも、トラヒック統計情報はコマンドで取得できる現在値だけではなく、過去の使用状況からみて異常の有無を判定することができる重要な情報です。
私の現場では、長く使用されているトラヒック情報確認ツールがあり、特に明確な原因警報のない故障が発生した際のサービス影響の有無の判断材料に用いられていました。
サービス影響のある故障の場合は、迅速な対応を求められます。
しかし当該ツールを使用する際は、次の操作が必要でした。
1.「常時開かない画面を開く」
2.「ログイン」
3.「必要情報の入力」
4.「取得対象の選択」
5.「検索結果表示」
加えて、端末のスペックも低く、画面の読み込みに時間がかかり、1件の検索に最大で5分かかる場合がありました。サービス影響の際の、特に十数分で対処しなければならない初動の対応判断において5分はとても大きいものですので、その工程の短縮が必要でした。
ミッション
同時期に既存施策でオペレータ業務の自動化施策が進められていました。そこで、自動化基盤へAPI連携できる形で実装しようと考えました。自動化施策担当の方へ提案し、以下の要件で合意しました。
要件
・GUIを介さずに処理が実行可能な事
・他システムからHTTPで利用できること
・結果表示画面に表示されるトラヒック統計グラフと各種情報を出力できるようにすること
制限
・既存システムを一切改修しないこと
・開発言語はPythonを用いること
・セキュリティ規定上、追加ライブラリやランタイムの導入は現実的に困難であるため、既存環境で利用可能なものを活用すること
実装方法
今回の対象は、閉鎖環境内で稼働するレガシーなWebシステムであり、Seleniumなどの便利なライブラリが追加導入できませんでした。そのため、下記の方法で対応しました。
・Requestsを用いたHTTP通信の再現
・BeautifulSoupを用いた実行結果取得
・FastAPIを用いたHTTP API化
なお、今回は古いツールのため新規でAPI実装を依頼することがコスト上出来ませんでした。そのため、やむなく関係者に許可を取って内部エンドポイントのHTTP通信を再現する方式で実装しました。
本来はシステム提供側にAPIを用意していただいた方が保守性、安全性、及び責任分界の観点から見ても望ましいです。
また本来内部エンドポイントの使用は、運用規定に抵触したり、不正利用扱いでシャットアウトされツールの復旧に時間がかかってしまう場合がある点に留意するべきですね。
それに、CAPTCHAなどのボット対策があると本方式での実装は困難です。インターネットからのリーチがない閉鎖網内で用いられるツールだからこそ、対策が施されていなかったおかげで実装できたのだと思います。
実装結果
実装の結果、次の成果が得られました。
・最大5分程度/1件 → 最大数十秒(遅延込み)/1件
・自動化基盤での通信影響判定の際に、今回実装したAPI経由でトラヒック情報を利用可能に
・利用者によるログイン、検索条件入力、取得対象選択などのGUI操作の削減
作業工程
通信フローの解析
まず、ブラウザがどのような通信を行っているかを調査しました。
GUI上では「ログイン」「検索条件入力」「実行」といった一連の操作しか見えません。しかし、実際には、複数のHTTPリクエストとレスポンスがやり取りされています。
そこでブラウザの開発者ツールを利用し、GUI操作時のHTTP通信を取得しました。
その後、出力したデータをreを用いた自作ツールでヘッダーとボディに分け、一旦そのままRequestsで送受信できる形に成形しました。
その後、各通信について、次の項目を確認しました。
・ブラウザからサーバ間の通信毎のHTTPメソッド
・URL
・Cookieを含むヘッダー情報とその変化
・各操作で参照・送信され、サーバ側の検索条件や処理内容に影響する画面要素
・リクエスト間で引き継がれる値
これらを調査したうえで、固定値として扱うものと、実行毎に変更が必要なものを切り分けました。
最終的に、GUI操作を再現するために最低限必要な値を洗い出し、通信ごとのテンプレートとして整理しました。
Requestsでの再現
次に、解析した通信を基に、PythonライブラリRequestsのrequests.Session()を利用してブラウザの通信を再現しました。
一旦、実際の手動操作時に取得した通信フローで実行してみるも、エラーを返されました。
そこで、ログイン時に発行される情報を後続のリクエストへ引き継ぐよう修正した結果、実行に成功しました。
その後、セッションを維持しながら前工程で作成した通信毎のテンプレートを順番に実行する処理を実装しました。具体的には次の処理を行っています。
これにより、ブラウザの起動や画面遷移が不要となり、CLIから実行可能なプログラムとして実装できました。
Logger実装
運用現場で利用するため、異常終了後も処理状況を追跡できるよう、セッション単位でJSON Lines形式のログを逐次出力する、簡易的なロガーを実装しました。
ログは、[DEBUG, INFO, ERROR]の粒度で次の形で記録しています。
※ ログはイメージです。
"yyyy-mm-dd-hhmmss.jsonl"
{"timestamp":"2020-01-01-120101","level":"INFO","message":"Session started."}
{"timestamp":"2020-01-01-120102","level":"DEBUG","message":"Search Param: {}"}
{"timestamp":"2020-01-01-120103","level":"DEBUG","message":"Login_do executed."}
{"timestamp":"2020-01-01-120103","level":"INFO","message":"Login_do succeed."}
{"timestamp":"2020-01-01-120104","level":"DEBUG","message":"Session_info changed. {}"}
{"timestamp":"2020-01-01-120104","level":"DEBUG","message":"~~_do executed."}
{"timestamp":"2020-01-01-120104","level":"DEBUG","message":"~~_do succeed."}
{"timestamp":"2020-01-01-120104","level":"DEBUG","message":"Session_info changed. {}"}
{"timestamp":"2020-01-01-120110","level":"INFO","message":"showResult_do succeed."}
{"timestamp":"2020-01-01-120110","level":"DEBUG","message":"resultData: {}"}
{"timestamp":"2020-01-01-120111","level":"DEBUG","message":"csvFilename: \"~~.csv\""}
{"timestamp":"2020-01-01-120111","level":"DEBUG","message":"download_csv succeed."}
{"timestamp":"2020-01-01-120112","level":"DEBUG","message":"download_graph succeed."}
{"timestamp":"2020-01-01-120112","level":"DEBUG","message":"Logout_do executed."}
{"timestamp":"2020-01-01-120112","level":"INFO","message":"logout_do succeed."}
{"timestamp":"2020-01-01-120112","level":"INFO","message":"Session closed."}
エラー発生時:
{"timestamp":"2020-01-01-120104","level":"DEBUG","message":"~~_do executed."}
{"timestamp":"2020-01-01-120106","level":"ERROR","message":"Exception occurred. {e}"}
{"timestamp":"2020-01-01-120107","level":"DEBUG","message":"Logout_do executed."}
{"timestamp":"2020-01-01-120107","level":"INFO","message":"logout_do succeed."}
{"timestamp":"2020-01-01-120107","level":"INFO","message":"Session closed."}
なお、エラーの検知についてはステータスコードを見れば簡単と思っていたのですが、対象システムでは画面上でエラーが発生している場合でも、送受信できていればOK判定で200、メッセージも”正常”が返ってきたため頭を抱えました。そこでBeautifulSoupでエラー画面の文字列を取得しreで正規化して判定することで、なんとか業務処理上のエラーの検知を実装しました。
FastAPIでの公開
CLIプログラムとして完成した後、自動化基盤から利用できるよう自動化担当に指定されたFastAPIでHTTP APIとして公開しました。
APIには検索対象や取得条件を指定できるようにし、他システムからHTTPリクエストを送るだけで処理を実行できる構成としました。これにより、人がGUIを操作することなく、既存の自動化基盤から容易に呼び出せるようになりました。
装置情報確認ツールへの連携
最後に、既存の装置情報確認ツール(WEBアプリ)から本APIを呼び出すよう連携を実施しました。
ボタン押下で既存ツール上の情報を参照し、今回実装したAPIへリクエストを送信するようにしました。これにより、利用者は検索パラメータの入力も不要になりました。
また、APIから取得したトラヒック統計グラフ及び各種情報は、既存ツールの画面上にモーダルウィンドウとして表示するようにしました。
装置情報確認ツールは常に利用しているもののため、利用者はツールの画面から離れることなく、対象装置のトラヒック情報を確認できるようになりました。
苦戦したこと
この施策は、私が初めて取り組んだプログラミングでした。
今まではVBA程度しか触ってこなかったため、JavaScriptやPythonといった言語だけでなく、HTTP通信やHTML、セッション管理、WEBアプリの構造を同時に学ぶ必要がありました。
言語の文法だけでなく、例外処理、ログ出力、関数の分割、変数の管理といった実装上の作法も分からない状態だったため、試行錯誤を重ねながら開発を進めました。
また、レガシーツールに関するドキュメントはGUI操作手順しか手に入らなかったため、再現にあたってはブラックボックスとして解析することになり、これもまた骨を折りました。
レガシーツールに関してはステータスコードが適切に設定されていなかったり、ソースコードに綴り誤りや画面上の名称と、通信上で使用される名称が一致しないものがあるなどと、困惑させられることも多く、処理が正常動作しない際の切り分けに時間を取られました。
学んだこと
まずPythonやJavaScriptの基本文法を学ぶことができました。この経験によって以降の施策においても、本施策ほど苦労せずに実施できるようになりました。
また、HTTP通信の解析によってクライアント・サーバ型のWEBアプリの動作について深く学ぶことが出来ました。今後のWEBアプリ開発の際も、明白に構造をイメージして設計できるようになりましたし、設計の際のAPI利用レート制限やBOT対策の重要性を学びました。
さいごに
最後までお読みいただきありがとうございます。
今回は、過去に実施した改善施策のうち、レガシーWebツールの操作自動化と既存基盤へのAPI連携について記載しました。当方の環境は閉鎖網ですので、特殊な部分が多くあるかと思います。また、本方式はすべてのWebアプリケーションに適用できるものではありません。通信仕様を再現でき、ブラウザ固有の認証機構や高度な自動化対策が導入されていないシステムであれば、選択肢の一つになり得ると考えています。既存システムを改修できない状況で、レガシーツールの自動化および既存基盤との連携をさせる際に少しでも参考になれば幸いです。