ネットワークサービス部のid:a8544です。
連載16回目の今回は、開発したシステムを新規環境にデプロイするとき・既存の環境をアップデートするときに、 わたしたちが使っているちょっとしたスクリプト(「正常性確認くん」と呼ばれています)についてご紹介します。
そのスクリプトは、デプロイした環境の各種ミドルウェアに着目し、それらが正常に動作していることを確認するものです。 それ自体技術的に高度なことはないのですが、このスクリプトの役割や必要性、背景についてご紹介させていただければと思います。
💣 ミドルウェアに発生する異常
わたしたちが開発している「プライベートブリッジのバックエンド」が、 本番環境において十分な可用性・性能をもって機能するためには、 Pythonのコードが動作するPython処理系以外にも、種々のミドルウェアが必要です。 たとえば、
等が必要です。
わたしたちのシステムは現時点ではDocker Swarmを中心に稼動しています。
Pythonの処理系含むメインのアプリケーションが Docker Swarmのサービスを構成しています。 これと同様に、いくつかのミドルウェアがDocker Swarmのサービスとして稼動しています (Docker Swarmでは管理していないミドルウェアもあります)。
したがって、Docker Swarmが docker-compose.yml
で宣言した状態になるように維持してくれるのですが、
これだけではミドルウェアが正常に動作しているとまではいえません。
「コンテナは正常に起動しているが、リクエストを受け付けられる状態になっていない」
可能性があります。ミドルウェアのこうしたパターンの異常については、
別途検知できるようにする必要があります(注)。
注: Dockerfile の HEALTHCHECK
instruction を使うことで、
Docker Swarm側に異常の検出および対処をさせることもできるようです。
この種の異常の例を次に挙げます。
- クラスター構成をとるミドルウェアでクラスターが組めていない
- スプリットブレインになっている場合など
- Docker Swarmのサービスのアップデート(構成変更)がいつまでも終わらない
- コンテナが異常終了を繰り返している場合など
🧨 アプリケーションの異常
ミドルウェアだけでなく、当然ミドルウェアを利用するアプリケーション側にも異常が起きることはあり、その監視が必要です。
アプリケーション自身で行う監視については後日別の記事でご紹介したいと思いますが、 ここでは、ミドルウェア側から行えるアプリケーションの異常の検出についてご説明します。
ミドルウェアには、それ自身の稼動ステータスを提供するAPIを備えるものがあります。 ここから得られた現在の状態を、アプリケーションが正常に稼動しているときに想定される状態と比較し一致しない場合、 アプリケーション側が正しく稼動していないことを間接的に検知できます。 もっとも、アプリケーション側の異常はアプリケーション自身で検出して対処するほうが より直接的で確実ですので、この方法はあくまで補助的に活用しています。
この種の異常の例を次に挙げます。
- (AMQPでいうところの)キューに対するコンシューマーが想定より多い/少ない
- アプリケーションが正常に起動していない、初期化処理が正常に行われていないなど
- データベースのテーブルやメッセージキューのキューが存在しない
- 初期化処理で失敗しているなど
👼 なぜスクリプトが必要だったか
これまでにご説明したようなさまざまな異常が発生し、正しく動作しないことをわたしたちは過去に経験してきました。 再発防止の観点から、リリース作業ではこれら過去に起きた異常のパターンについて、一つ一つ手作業で確認していました。 当初リリース作業は完全に手動であり、頻度もかなり低かったので、この方法でも問題になっていませんでした。
しかし、今後本連載でもご説明するようにリリースの自動化が進み、月次・週次とリリース頻度が高まるにつれて 確認作業も自動化が求められ、誕生したのがこのスクリプト「正常性確認くん」でした。
🔧 実装
「正常性確認くん」は、基本的に各種ミドルウェア(具体的には、HAProxyやRabbitMQなど)が提供している、 ステータス確認のためのHTTP APIを使います。技術的には、HTTPリクエストを送信し、JSONのレスポンスをパースするだけの単純なものです。 対応するすべてのミドルウェアに対してチェックを行い、全て問題なければ成功、そうでなければ失敗になります(図1)。
👿 発展
「正常性確認くん」を使っていくにつれ、
- ここでチェックしている項目は、デプロイ作業のときだけでなく常時監視していてほしい
- わたしたちがすでに使っているZabbixと連携させたい(過去の結果の記録やアラートなどの機能を使いたい)
といった要望が生じました。そこで「正常性確認くん」には、起動後終了することなく定期的にチェックを行い、 リクエストに応じてチェック結果を返す、デーモンとして動作する機能が追加されました(図2)。
「正常性確認くん」のデーモン機能は、1分ごとにミドルウェアのチェックを行い、その結果を保持します。
ただし、保持するのは最新の結果のみです。
また、HTTPサーバー機能を有し、リクエストに応じて、その時点で最新のチェック結果を返却します。
そのレスポンス例を下記に示します。 updated_at
フィールドにチェックを実施した時刻が含まれているので、
取得した情報が古すぎるものでないかを確認できます。
{ "status": 1, "updated_at": 1687996681.623794, "log": "Start HTTP health check...\nHTTP health check successfully finished." }
「正常性確認くん」はZabbixのLow-level Discoveryに互換するAPIも持っているので、Zabbix上で各ミドルウェアごとのアイテムが自動的に作られるように構成できます。
このような形になったのは、ミドルウェアのチェックにかかる時間が比較的長いためです。 Zabbixの External Check として「正常性確認くん」を利用すると、 Zabbixのパフォーマンスに影響がでかねないという懸念がありました。 そのため、Zabbixからの値の取得がすぐ完了するこの方式をとることになりました。
まとめ
システムのデプロイ時、システムの正常性を確認する小さなスクリプト、「正常性確認くん」についてご紹介しました。
それ自身技術的に高度なわけではありませんし、システムのインフラに近い「泥臭い」小ネタではあります。 しかし、Docker Swarm環境からアプリケーションまで開発運用までを一手に担っているわたしたちにとっては、 CI・CDを支える重要なパーツの一つになっています。
次回は、検証環境への自動デプロイについてご紹介する予定です。お楽しみに。
連載バックナンバー
CI戦術編
- 【CI戦術編 その1】alembic check コマンドを活用したマイグレーションスクリプト生成忘れ防止
- 【CI戦術編 その2】コードレビューを充実したものにする方法、あるいは一生残る恥ずかしい履歴を作らないように
- 【CI戦術編 その3】Pythonのimportのことならまかせろ isort
- 【CI戦術編 その4】Blackを利用したコーディングスタイルの統一
- 【CI戦術編 その5】Pythonで明示的に型を書く理由
- 【CI戦術編 その6】Python開発の強い味方 Pylint
- 【CI戦術編 その7】 pyupgradeを使って最新の記法に対応してみた
- 【CI戦術編 その8】OAS(OpenAPI Specification)で仕様書を自動生成しよう
- 【CI戦術編 その9】自動生成しか勝たん openapi-typescript
- 【CI戦術編 その10】 Renovateで依存ライブラリのアップデートに負けない方法
- 【CI戦術編 その11】Trivy: あなたの使ってるライブラリ、大丈夫ですか?
- 【CI戦術編 その12】戦術編総まとめ — いつ何を使うべきか
- CI・CD戦略編