危険なデプロイメントを解消する

みなさん、こんにちは。角煮でお馴染みの深町です。
以前、弊社浅井より、16年間うごいているWebアプリケーションが抱えていた技術的負い目を考察するというブログを公開いたしました。
その項目のひとつに「危険なデプロイメント」という項目がありましたが、今回はこちらの詳細をお伝えしようと思います。
環境
弊社のとあるサービスは下記のような環境で動いています。
- 言語 : Java
- テンプレートエンジン : JSP
- ウェブサーバー : Apache
- Webコンテナ : Tomcat
このサービスは1日にJSPのみのデプロイ、アプリケーション全体のデプロイを合わせて10回程度の頻繁なリリースがが行われるWebアプリケーションでして
ロードバランサ => 複数台のApache => 複数台のTomcat
といった構成になっております。
JSPのみのリリースをする場合はJSPが存在するディレクトリをrsyncしてリリースしています。
アプリケーション全体のデプロイをする場合は、ビルドしたwarファイルをscpした後、Tomcatを再起動することでwarを展開させてリリースしていました。そのためアプリケーション全体のデプロイをする場合は、以前はTomcatの再起動でwarを展開させて、リリースしていました。 それが原因でjavaファイルの更新は必ずダウンタイムが生じてました。今回はこのダウンタイムを解消すること目的として動きました。
デプロイ方法を考察
候補としてあがったのは
- パラレルデプロイ
- ブルーグリーンデプロイ
- 従来通り再起動するが、再起動中のTomcatはワーカーから切離し、再起動完了後にワーカーに戻す
Tomcat7からはパラレルデプロイの機能が追加されましたが、この機能は同じアプリが一瞬2つ動くことになるのでメモリやCPUに十分な余裕がないと使うことが難しいです。
ブルーグリーンデプロイも候補として上がりましたが、サーバー構成を自動化する必要があり、工数が予定以上にかかるのではないかという懸念がありました。
デプロイ改善
結論、このサービスでは3番目の従来通り再起動するが、再起動中のTomcatはワーカーから切離し、再起動完了後にワーカーに戻すというやり方で改善することに決めました。
さて、弊社では今まで16年の歴史ある秘伝のスクリプトを使ってCIサーバーからデプロイ処理を行っていたのですが、そのスクリプトは以下のような処理をしておりました。
[秘伝のスクリプトの処理の中身]
ビルドサーバーにて対象のアプリケーションのwarを作成 ↓ デプロイ対象のTomcatをシャットダウン ↓ スリープ処理50秒 ↓ warを設置 ↓ Tomcatを起動 ↓ スリープ処理30秒
当時このスクリプトは以下の問題を抱えていました。
- ロードバランサから外さずにいきなりTomcatを落としているのでユーザーに502を返すケースがある
- Tomcatの停止を確認せずにスリープ処理でTomcatが停止されていることを前提として処理を継続している
- Tomcatの開始を確認せずにスリープ処理が完了後、次のサーバーに対してのデプロイ処理がはじまるのでTomcatが起動しきれていないままアクセスされてしまう。
- Tomcatのプロセスが残った状態でデプロイしてしまうとcontext.xmlがきえてしまう。
そこで、デプロイ対象のTomcatをロードバランサをつかってワーカーからはずし、デプロイ完了後に起動が確認できた後にワーカーに戻すようにいたしました。
そしてこちらが改善後の処理の流れです。
[改善した後のデプロイ処理の流れ]
・CIサーバーにて対象のアプリケーションのwarを作成 ↓ ・CIサーバーからWebサーバーに向かってデプロイ先のTomcatをワーカーから切り離す※1 ↓ ・デプロイ対象のTomcatをシャットダウン ↓ ・デプロイ対象のTomcatが停止されているか確認※2 ↓ ・warを設置 ↓ ・Tomcatを起動 ↓ ・デプロイ対象のTomcatが開始されているか確認※2 ↓ ・Siegeで複数のURLにアクセスさせる※3 ↓ ・CIサーバーからWebサーバーに向かってデプロイ先のTomcatをワーカーにもどす※1
では、どのように改善していったかご紹介致します。
※1 すべてのWebサーバーに対してデプロイ先のTomcatをワーカー(から切り離させる|に戻させる)
弊社のこのサービスは先程お伝えしたとおりApache+Tomcatというよくある構成で動いております。 ここでApacheにあるmod_proxy_balancerという機能をつかい、Tomcatに対するロードバランス設定を管理するようにいたしました。
mod_proxy_balancerは以下のような画面になっており、Apache => 複数のTomcatに対して特定Tomcatのみをワーカーから切り離すことが出来ます。
しかし、デプロイする度に手動でぽちぽちと切り離すわけにもいかないため、CIサーバーから各ウェブサーバーに対して、MechanizeというRubyのライブラリをつかってを使って切り離すように自動化させました。 また、ワーカーに戻す際にTomcat立ち上げ直後に複数のapacheに対して一気にTomcatへのアクセスを許してしまうとTomcat側に負荷がかかってしまうため、スロースタートさせるためにスリープをいれております。
※2 デプロイ対象のTomcatが起動/停止されているか確認
こちらに関してはTomcatの起動/停止を確認せず、Tomcatシャットダウンをした後にスリープ50秒したり、起動後にスリープ30秒したり等、Tomcatが起動/停止している前提で次の処理を行っていました。 しかし、Tomcatはプロセスが残った状態でwarファイルをデプロイしてしまうとcontext.xmlがきえてしまいます。
そこで、まず停止する際はshutdownをした後にTomcatのプロセスがなくなったことを確認してから次の処理を実行するように変更を入れました。
また、起動の場合はApacheがTomcatが完全に起動しきれてないにも関わらず、Tomcatにリクエストを大量におくるため、処理が遅延したり、最悪の場合,Tomcatが止まったこともありました。そこで、ロードバランサから外したTomcatをlocalhostからcurlで何回かアクセスさせて、ステータスコードが200 OKになってからロードバランサに戻すようにしました。 仮に起動時に問題が発生した場合はリリース処理を止めてchatworkに通知がくるようにしています。
※3 Siegeで複数のURLにアクセスさせる
ロードバランサを用いるリリースを導入しただけでもだいぶ改善されましたが、Tomcatを再起動した直後はJSPファイルをコンパイルしてclassファイルを生成したり、DBやAJPのコネクションを確保するのにすこし処理が遅延していました。
そこで、Tomcatをワーカーに戻す前にSiegeというツールをつかって複数のURLに対してアクセスさせてTomcatをウォームアップさせることにしました。 Siegeは複数のURLが記述されたファイルを引数で渡すことでそのURLにアクセスすることができます。そのため、アクセスさせるURLを簡単に管理することができます。
上記の改善を行った結果、リリースするときにパソコンの前で「エラーが出ませんように」などとお祈りをすることがなくなりましたし、CPUの負荷もものすごく改善することができました。
[変更前]
[変更後]
また、ワーカーから切り離す機能を追加したことで、この後に行ったNewRelicの導入や、JDK8の検証等を本番サーバー1台だけ切り離して検証することが容易にできるようになりました。
最後に
今回は危険なデプロイメントを解消するということでTomcatへのデプロイをダウンタイムゼロにすることを目的として解消いたしました。しかし、このアプリケーションが抱えている問題はまだまだ数多くあります。
次回また何か解消いたしましたら、こちらに記載するので楽しみにお待ち下さい!