ダウンタイムを最小限に抑えてJava Webアプリケーションを展開するためのベストプラクティス
-
08-07-2019 - |
質問
大規模なJava webapp(> 100 MB .war)をデプロイするとき、現在次のデプロイメントプロセスを使用しています:
- アプリケーションの.warファイルは、開発マシン上でローカルに展開されます。
- 展開されたアプリケーションは、開発マシンからライブ環境にrsync:edされます。
- ライブ環境のアプリサーバーは、rsyncの後に再起動されます。この手順は厳密には必要ありませんが、デプロイメント時にアプリケーションサーバーを再起動すると、「java.lang.OutOfMemoryError:PermGen space」が回避されることがわかりました。頻繁にクラスがロードされるため。
このアプローチの良い点:
- rsyncは、開発マシンからライブ環境に送信されるデータの量を最小限に抑えます。 .warファイル全体のアップロードには10分以上かかりますが、rsyncには数秒かかります。
このアプローチに関する悪い点:
- rsyncの実行中、ファイルが更新されるため、アプリケーションコンテキストが再起動されます。理想的には、rsyncがまだ実行されているときではなく、rsyncが完了した後に再起動する必要があります。
- アプリサーバーを再起動すると、約2分のダウンタイムが発生します。
次のプロパティを持つ展開プロセスを見つけたい:
- 展開プロセス中の最小ダウンタイム。
- データのアップロードに費やす最小限の時間。
- 展開プロセスがアプリサーバー固有の場合、アプリサーバーはオープンソースである必要があります。
質問:
- 記載されている要件を考慮して、最適な展開プロセスは何ですか?
解決
変更をWARファイルにプッシュする場合、rsyncはうまく機能しないことに注意してください。これは、WARファイルは基本的にZIPファイルであり、デフォルトでは圧縮されたメンバーファイルで作成されるためです。メンバーファイルに小さな変更を加えると(圧縮前)、ZIPファイルに大きなスケールの違いが生じ、rsyncのデルタ転送アルゴリズムが無効になります。
可能な解決策の1つは、 jar -0 ...
を使用して元のWARファイルを作成することです。 -0
オプションは、WARファイルの作成時にメンバーファイルを圧縮しないように jar
コマンドに指示します。次に、 rsync
がWARファイルの古いバージョンと新しいバージョンを比較するとき、デルタ転送アルゴリズムは小さな差分を作成できるはずです。次に、rsyncがdiff(または元のファイル)を圧縮形式で送信するように調整します。例えば rsync -z ...
またはその下の圧縮データストリーム/トランスポートを使用します。
編集:WARファイルの構造によっては、 jar -0 ...
を使用してコンポーネントJARファイルを作成する必要がある場合もあります。これは、安定したサードパーティJARファイルではなく、頻繁に変更される(または単に再構築される)JARファイルに適用されます。
理論的には、この手順は通常のWARファイルの送信よりも大幅に改善されるはずです。実際にはこれを試したことがないので、うまくいくとは約束できません。
欠点は、デプロイされたWARファイルが大幅に大きくなることです。その結果、webappの起動時間が長くなる可能性がありますが、効果はわずかであると思われます。
まったく異なるアプローチは、WARファイルを見て、(ほとんど)決して変更されない可能性があるライブラリJARを識別できるかどうかを確認することです。これらのJARをWARファイルから取り出し、Tomcatサーバーの common / lib
ディレクトリに個別にデプロイします。例えば rsync
を使用します。
他のヒント
更新:
この回答が最初に書かれて以来、ダウンタイムなしでwarファイルをTomcatにデプロイするより良い方法が登場しました。 Tomcatの最新バージョンでは、warファイル名にバージョン番号を含めることができます。そのため、たとえば、ファイル ROOT ## 001.war
と ROOT ## 002.war
を同じコンテキストに同時に展開できます。 ##
の後のすべては、コンテキストパスの一部ではなく、tomcatによってバージョン番号として解釈されます。 Tomcatは、アプリのすべてのバージョンを実行し続け、新しいリクエストとセッションを完全に稼働している最新バージョンに提供すると同時に、元のバージョンの古いリクエストとセッションを正常に完了します。バージョン番号の指定は、Tomcatマネージャーやcatalina antタスクでも実行できます。詳細はこちらで。
元の回答:
Rsyncは、差分転送アルゴリズムがファイルの変更と非圧縮ファイルの小さな変更を検索し、結果の圧縮バージョンを大幅に変更する可能性があるため、圧縮ファイルでは無効になる傾向があります。このため、ネットワーク帯域幅がボトルネックであることが判明した場合は、圧縮バージョンではなく、非圧縮のwarファイルをrsyncすることをお勧めします。
Tomcatマネージャーアプリケーションを使用してデプロイを行うことの何が問題になっていますか? warファイル全体をリモートの場所からTomcatマネージャーアプリに直接アップロードしたくない場合は、(上記の理由で非圧縮)を本番ボックスのプレースホルダーの場所に再同期し、warに再パッケージし、それをローカルのマネージャーに渡します。 Tomcatに同梱されている素晴らしいantタスクがあり、Tomcatマネージャーアプリを使用してスクリプトを展開できます。
言及していないアプローチには追加の欠陥があります:アプリケーションが部分的にデプロイされている間(rsync操作中)、アプリケーションは一貫性のない状態になり、変更されたインターフェースが同期されない、新しい/更新された依存関係は利用できないなどです。また、rsyncジョブにかかる時間によっては、アプリケーションが実際に複数回再起動する場合があります。 Tomcatで変更されたファイルをリッスンして再起動する動作をオフにできることを知っていますか?実際には、本番システムにはお勧めしません。 Tomcatマネージャーアプリを使用して、アプリケーションの手動またはAntスクリプトによる再起動をいつでも実行できます。
もちろん、再起動中はユーザーがアプリケーションを使用できなくなります。ただし、可用性が非常に懸念される場合は、ロードバランサーの背後に冗長なWebサーバーが確実に存在します。更新されたwarファイルをデプロイする場合、デプロイメントが終了するまで、ロードバランサーがすべてのリクエストを一時的に他のWebサーバーに送信することができます。すすぎ、他のWebサーバーに対して繰り返します。
ダウンタイムが考慮される環境では、冗長性を介して信頼性を向上させるために、確実に何らかのサーバークラスターを実行する必要があります。ホストをクラスターから取り出し、更新してからクラスターに戻します。混合環境で実行できない更新プログラムがある場合(たとえば、dbで互換性のないスキーマの変更が必要な場合)、少なくともしばらくはサイト全体を停止する必要があります。トリックは、オリジナルをドロップする前に交換プロセスを起動することです。
例としてTomcatを使用する-CATALINA_BASEを使用して、実行可能コードとは別に、Tomcatのすべての作業ディレクトリが存在するディレクトリを定義できます。ソフトウェアを展開するたびに、新しいベースディレクトリに展開して、古いコードの隣のディスクに新しいコードを常駐できるようにします。次に、新しいベースディレクトリを指すtomcatの別のインスタンスを起動し、すべてを起動して実行し、ロードバランサーで古いプロセス(ポート番号)を新しいプロセスと交換します。
スイッチ全体でセッションデータを保持することに懸念がある場合は、すべてのホストがセッションデータを複製するパートナーを持つようにシステムをセットアップできます。それらのホストの1つをドロップし、更新し、セッションデータを取得するように再起動して、2つのホストを切り替えることができます。クラスター内に複数のペアがある場合、リリースの要件、企業の要件などに応じて、すべてのペアの半分をドロップしてから一括切り替えを行うか、一度にペアを行うことができますただし、個人的には、セッションをそのまま使用してアップグレードしようとするよりも、エンドユーザーがアクティブなセッションを非常に偶発的に失うことを許容することを好みます。
ITインフラストラクチャ、リリースプロセスの複雑さ、および開発者の努力の間のトレードオフです。クラスターが十分に大きく、要望が十分に強い場合、ほとんどの更新でダウンタイムなしで交換できるシステムを設計するのは簡単です。更新されたソフトウェアは通常、古いスキーマに対応できないため、スキーマの大きな変更は実際のダウンタイムを強制することがよくあります。新しいデータベースが古いデータベースから複製された後、古いデータベースに書き込まれたデータを見逃してしまいます。もちろん、リソースがある場合は、更新されたすべてのテーブルに新しいテーブル名を使用するように新しいアプリを変更することで開発者にタスクを実行できます。また、新しいデータベースを以前のバージョンによって古いテーブルに書き込まれます(または、ビューを使用して、あるスキーマを他のスキーマからエミュレートすることもできます)。新しいアプリサーバーを起動し、クラスターにスワップします。ゲームをビルドするための開発リソースがある場合、ダウンタイムを最小限に抑えるためにプレイできるゲームはたくさんあります。
おそらく、ソフトウェアのアップグレード中のダウンタイムを減らすための最も便利なメカニズムは、アプリが読み取り専用モードで機能できることを確認することです。これにより、必要な機能がユーザーに提供されますが、データベースの変更などを必要とするシステム全体の変更を行うことができます。アプリを読み取り専用モードにし、データのクローンを作成し、スキーマを更新し、新しいデータベースに対して新しいアプリサーバーを起動し、ロードバランサーを切り替えて新しいアプリサーバーを使用します。唯一のダウンタイムは、読み取り専用モードに切り替えるために必要な時間と、ロードバランサーの設定を変更するために必要な時間です(ほとんどの場合、ダウンタイムなしで処理できます)。
私のアドバイスは、展開バージョンでrsyncを使用し、warファイルをデプロイすることです。
- webappの展開バージョンがあるライブ環境に一時フォルダーを作成します。
- Rsync展開バージョン。
- rsyncの成功後、ライブ環境マシンの一時フォルダーにwarファイルを作成します。
- サーバーのdeployディレクトリの古いwarを一時フォルダーの新しいwarに置き換えます。
JBossコンテナ(Tomcatベース)では、古い戦争を新しいものに置き換えることをお勧めします。これは、アトミックで高速な操作であり、デプロイヤがアプリケーション全体を起動するときにデプロイ状態になることが確実であるためです。
Webサーバー上の現在のWebアプリケーションのローカルコピーを作成し、そのディレクトリにrsyncし、シンボリックリンクを1回の「go」で使用して、Tomcatをダウンタイムなしで新しいデプロイメントにポイントできませんか?
抽出されたwarをrsyncするためのあなたのアプローチは、実稼働サーバーでホットデプロイメントを有効にすべきではないと考えているため、再起動もかなり良いです。だから、唯一の欠点は、サーバーを再起動する必要がある場合のダウンタイムですよね?
アプリケーションのすべての状態がデータベースに保持されていると想定しているため、一部のユーザーが1つのアプリサーバーインスタンスで作業し、他のユーザーが別のアプリサーバーインスタンスで作業しても問題ありません。もしそうなら、
2つのアプリサーバーを実行:2番目のアプリサーバー(他のTCPポートでリッスンする)を起動し、そこにアプリケーションを展開します。展開後、Apache httpdの構成(mod_jkまたはmod_proxy)を更新して、2番目のアプリサーバーを指すようにします。 Apache httpdプロセスを正常に再起動します。これにより、ダウンタイムが発生せず、新しいユーザーとリクエストが新しいアプリサーバーに自動的にリダイレクトされます。
アプリサーバーのクラスタリングとセッションレプリケーションのサポートを利用できる場合、2番目のアプリサーバーが起動するとすぐに再同期するため、現在ログインしているユーザーにとってもスムーズです。次に、最初のサーバーへのアクセスがない場合、シャットダウンします。
これはアプリケーションのアーキテクチャに依存します。
アプリケーションの1つは、負荷分散プロキシの背後にあります。このプロキシでは、時差配置を実行し、ダウンタイムを効果的になくしています。
Java EARをホットデプロイしてサーバー上のアプリケーションのダウンタイムを最小化または排除または Jboss Tools Eclipseプラグインを使用してJbossでwar依存関係を「ホット」デプロイする方法にはいくつかのオプションがあります。
ダウンタイムのないクラスターへのデプロイも興味深いものです。
JavaRebelには、ホットコード展開もあります。
>静的ファイルが大きなWARの大きな部分である場合(100Moはかなり大きい)、それらをWARの外に置き、アプリケーションサーバーの前にあるWebサーバー(Apacheなど)に展開することで速度が向上する可能性があります。さらに、Apacheは通常、サーブレットエンジンよりも静的ファイルの提供において優れた仕事をします(たとえそのほとんどがその分野で大きな進歩を遂げたとしても)。
したがって、大きな脂肪のWARを生成する代わりに、ダイエットして次のように生成します。
- Apache用の静的ファイルを含む大きな脂肪のZIP
- サーブレットエンジンの低脂肪WAR。
オプションで、WARをより薄くするプロセスをさらに進めます。可能であれば、アプリケーションサーバーレベルで頻繁に変更されないGrailsやその他のJAR(ほとんどの場合)をデプロイします。
より軽いWARの作成に成功した場合、アーカイブではなくディレクトリの再同期を気にしません。
このアプローチの長所:
- 静的ファイルは、ホット「デプロイ」できます。 Apacheで(たとえば、現在のディレクトリを指すシンボリックリンクを使用して、新しいファイルを解凍し、シンボリックリンクを更新し、無効にします)。
- WARはより薄くなり、展開にかかる時間が短くなります。
このアプローチの弱点:
- サーバー(Webサーバー)がもう1つあるため、これにより(少し)複雑さが増します。
- ビルドスクリプトを変更する必要があります(大したIMOではありません)。
- rsyncロジックを変更する必要があります。
これがあなたの質問に答えるかどうかはわかりませんが、私が使用した展開プロセス、または私が行ったいくつかのプロジェクトで遭遇した展開プロセスについて共有します。
ご存じのとおり、私は戦争の完全な再展開または更新を行ったことを思い出しません。ほとんどの場合、私の更新はいくつかのjspファイル、おそらくライブラリ、いくつかのクラスファイルに制限されています。どのアーティファクトが影響を受けているかを管理および判断することができます。通常、更新スクリプトとともに、それらの更新をzipファイルにパッケージ化しました。更新スクリプトを実行します。スクリプトは次のことを行います。
- 上書きされるファイルを、おそらく今日の日付と時刻のフォルダーにバックアップします。
- ファイルをアンパッケージ
- アプリケーションサーバーを停止します
- ファイルを移動
- アプリケーションサーバーを起動します
ダウンタイムが懸念事項であり、通常そうである場合、私のプロジェクトは通常、状態を共有しておらず、スティッキーセッションルーティングを提供するルーターを使用している場合でもHAです。
私が興味を持っているもう1つのことは、なぜrsyncが必要なのでしょうか?ライブでのデルタチェックを実行するのではなく、ステージング/開発環境でそれらを決定することにより、必要な変更を知ることができるはずです。ほとんどの場合、データベース接続、SMTPサーバーなどの運用サーバーが使用するリソースを定義する特定のプロパティファイルのように、ファイルを無視するようにrsyncを調整する必要があります。
これがお役に立てば幸いです。
PermSpaceは何に設定されていますか?これも同様に大きくなると予想されますが、古いクラスを収集した後、 ダウンする必要がありますか? (または、ClassLoaderはまだ座っていますか?)
大声で考えると、別のバージョン名または日付名のディレクトリにrsyncできます。コンテナがシンボリックリンクをサポートしている場合、ルートプロセスをSIGSTOPし、シンボリックリンクを介してコンテキストのファイルシステムルートを切り替えてから、SIGCONTを実行できますか?
初期コンテキストの再起動について。すべてのコンテナには、クラスファイルの自動再デプロイまたは静的リソースの変更を無効にする構成オプションがあります。 web.xmlの変更で自動再デプロイを無効にすることはおそらくできないため、このファイルは最後に更新されます。したがって、自動再デプロイを無効にして、web.xmlを最後の更新として更新すると、更新全体の 後にコンテキストが再起動されます。
新しいバージョンのwebappを別のディレクトリにアップロードしてから、実行中のものと交換するか、シンボリックリンクを使用します。たとえば、「myapp」という名前のtomcat webappsディレクトリに、「myapp-1.23」という名前の現在のwebappを指すシンボリックリンクがあります。新しいwebappを「myapp-1.24」にアップロードします。すべての準備ができたら、サーバーを停止し、シンボリックリンクを削除して、新しいバージョンを指す新しいリンクを作成してから、サーバーを再起動します。
パフォーマンスのために実稼働サーバーで自動リロードを無効にしますが、それでも、静的ファイルまたはJSPページでさえリンクが壊れたり、悪い。
実際には、webappは実際には共有ストレージデバイスに配置されるため、クラスター化された負荷分散サーバーとフェールオーバーサーバーはすべて同じコードを使用できます。
この状況の主な欠点は、rsyncが変更または追加されたファイルのみを転送できるため、アップロードに時間がかかることです。古いwebappフォルダーを最初に新しいフォルダーにコピーし、それが大きな違いを生む場合、および実際に問題がある場合は、rsyncすることができます。
Tomcat 7には" 並列展開"このユースケース向けに設計されています。
要点は、.warをwebapps /の直下またはシンボリックリンクのいずれかのディレクトリに展開することです。アプリケーションの後続バージョンは、 app ## version
という名前のディレクトリにあります(例: myapp ## 001
および myapp ## 002
)。 Tomcatは、古いバージョンに移行する既存のセッションと、新しいバージョンに移行する新しいセッションを処理します。
問題は、PermGenのリークに注意する必要があることです。これは、多くのPermGenを使用するGrailsで特に当てはまります。 VisualVMはあなたの友人です。
プロキシを介して2つ以上のTomcatサーバーを使用するだけです。そのプロキシは、apache / nignix / haproxyにすることができます。
各プロキシサーバーには「in」があります。および「out」ポートを含むURLが構成されています。
まず、サービスを停止せずにTomcatで戦争をコピーします。戦争が展開されると、Tomcatエンジンによって自動的に開かれます。
クロスチェックunpackWARs =" true"に注意してください。およびautoDeploy =" true"ノード" Host" server.xml内
このように見える
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
Tomcatのログを参照してください。エラーがない場合は、正常に起動していることを意味します。
テスト用のすべてのAPIをヒット
プロキシサーバーにアクセスします。
背景のURLマッピングを新しい戦争の名前に変更するだけです。 apache / nignix / haProxyなどのプロキシサーバーへの登録にかかる時間は非常に短いため、ダウンタイムを最小限に抑えることができます
参照- https://developers.google.com/speed/pagespeed/module / domains はURLをマッピングします
Resinを使用している場合、ResinにはWebアプリのバージョン管理のサポートが組み込まれています。
http://www.caucho.com/resin-4.0/ admin / deploy.xtp#VersioningandGracefulUpgrades
更新:ウォッチドッグプロセスはpermgenspaceの問題にも役立ちます。
「ベストプラクティス」ではありません。しかし、私が考えていたもの。
gitなどのDVCSを介してwebappをデプロイする方法はどうですか?
これにより、サーバーに転送するファイルをgitに把握させることができます。破壊されたことが判明した場合は、元に戻すだけの良い方法もあります!
いくつかのパラメーターを取り、サーバー間でファイルをrsyncするbashスクリプトを書きました。大きなアーカイブの場合、rsync転送を大幅に高速化します。