web-dev-qa-db-ja.com

Java Webアプリケーションを最小限のダウンタイムでデプロイするためのベストプラクティス?

大規模なJava webapp(> 100 MB .war))をデプロイするとき、私は現在次のデプロイメントプロセスを使用しています。

  • アプリケーションの.warファイルは、開発マシンでローカルに展開されます。
  • 拡張アプリケーションは、開発マシンからライブ環境にrsync:edされます。
  • ライブ環境のアプリサーバーは、rsync後に再起動されます。この手順は厳密には必要ありませんが、デプロイメント時にアプリケーションサーバーを再起動すると、クラスのロードが頻繁に行われるため、「Java.lang.OutOfMemoryError:PermGen space」が回避されることがわかりました。

このアプローチの良い点:

  • Rsyncは、開発マシンからライブ環境に送信されるデータの量を最小限に抑えます。 .warファイル全体のアップロードには10分以上かかりますが、rsyncには数秒かかります。

このアプローチの悪い点:

  • Rsyncの実行中に、ファイルが更新されるため、アプリケーションコンテキストが再起動されます。再起動は、rsyncがまだ実行されているときではなく、完了した後に行うのが理想的です。
  • アプリサーバーの再起動により、約2分のダウンタイムが発生します。

次のプロパティを持つ展開プロセスを見つけたい:

  • 展開プロセス中の最小限のダウンタイム。
  • データのアップロードに費やされた最小時間。
  • 展開プロセスがアプリサーバー固有である場合、アプリサーバーはオープンソースである必要があります。

質問:

  • 上記の要件を前提として、最適な導入プロセスは何ですか?
55
knorv

WARファイルに変更をプッシュすると、rsyncがうまく機能しないことが指摘されています。これは、WARファイルは基本的にZipファイルであり、デフォルトでは圧縮されたメンバーファイルで作成されるためです。メンバーファイルに(圧縮前に)小さな変更を加えると、Zipファイルに大きな違いが生じ、rsyncのデルタ転送アルゴリズムが無効になります。

可能な解決策の1つは、jar -0 ...を使用して元のWARファイルを作成することです。 -0オプションは、WARファイルの作成時にメンバーファイルを圧縮しないようにjarコマンドに指示します。次に、rsyncがWARファイルの古いバージョンと新しいバージョンを比較するとき、デルタ転送アルゴリズムは小さな差分を作成できるはずです。次に、rsyncが差分(または元のファイル)を圧縮形式で送信するようにします。例えばrsync -z ...または圧縮されたデータストリーム/トランスポートを使用します。

編集:WARファイルの構造によっては、jar -0 ...を使用してコンポーネントJARファイルを作成する必要がある場合もあります。これは、安定したサードパーティのJARファイルではなく、頻繁に変更される可能性がある(または単に再構築される)JARファイルに適用されます。

理論的には、この手順により、通常のWARファイルを送信するよりも大幅に改善されます。実際にはこれを試したことがないので、うまくいくとは約束できません。

欠点は、デプロイされたWARファイルが大幅に大きくなることです。これにより、webappの起動時間が長くなる可能性がありますが、影響はわずかだと思います。


まったく異なるアプローチは、WARファイルを調べて、(ほとんど)変更されない可能性が高いライブラリJARを特定できるかどうかを確認することです。これらのJARをWARファイルから取り出し、Tomcatサーバーのcommon/libディレクトリに個別にデプロイします。例えばrsyncを使用します。

20
Stephen C

更新:

この回答が最初に書かれてから、ダウンタイムなしでwarファイルをTomcatにデプロイするためのより良い方法が現れました。 Tomcatの最近のバージョンでは、warファイル名にバージョン番号を含めることができます。したがって、たとえば、ファイルROOT##001.warROOT##002.warを同時に同じコンテキストにデプロイできます。 ##の後のすべては、Tomcatによってバージョン番号として解釈され、コンテキストパスの一部ではありません。 Tomcatはアプリのすべてのバージョンを実行し続け、新しいリクエストとセッションを完全に稼働している最新バージョンに提供する一方で、最初のバージョンの古いリクエストとセッションを正常に完了します。バージョン番号の指定は、Tomcatマネージャーやcatalina antタスクを介して行うこともできます。詳細 ここ

元の回答:

Rsyncは、デルタ転送アルゴリズムがファイルの変更を探し、圧縮されていないファイルに小さな変更を加えるため、圧縮ファイルでは効果がない傾向があります。結果の圧縮バージョンが大幅に変更される可能性があります。このため、ネットワーク帯域幅がボトルネックであることが判明した場合は、圧縮バージョンではなく非圧縮のwarファイルをrsyncするのが適切です。

Tomcatマネージャーアプリケーションを使用してデプロイメントを行うことの何が問題になっていますか? warファイル全体をリモートの場所からTomcatマネージャーアプリに直接アップロードしたくない場合は、それを(上記の理由により非圧縮の)プロダクションボックスのプレースホルダーの場所にrsyncし、それをwarに再パッケージし、その後、ローカルでマネージャーに渡します。 Tomcatに付属するNice Antタスクがあり、Tomcatマネージャーアプリを使用してデプロイメントをスクリプト化できます。

あなたが言及していないアプローチに追加の欠陥があります:アプリケーションが部分的にデプロイされている間(rsync操作中に)、アプリケーションは一貫性のない状態になり、変更されたインターフェイスが同期されない可能性があります。また、rsyncジョブの所要時間によっては、アプリケーションが実際に複数回再起動する場合があります。 Tomcatで変更されたファイルをリッスンして再起動する動作をオフにできること、またオフにする必要があることを知っていますか?実際の運用システムには推奨されません。 Tomcatマネージャーアプリを使用して、いつでも手動またはantスクリプトによるアプリケーションの再起動を行うことができます。

もちろん、再起動中はユーザーがアプリケーションを使用できなくなります。しかし、可用性について非常に心配している場合は、ロードバランサーの背後に冗長なWebサーバーがあるはずです。更新されたwarファイルをデプロイするとき、デプロイメントが完了するまで、ロードバランサーに一時的にすべてのリクエストを他のWebサーバーに送信させることができます。他のWebサーバーをすすぎ、繰り返します。

30
Asaph

ダウンタイムが考慮される環境では、冗長性を介して信頼性を高めるために、サーバーの一種のクラスターを確実に実行しています。ホストをクラスターから取り出し、更新してから、クラスターに戻します。混合環境で実行できない更新(たとえば、dbで互換性のないスキーマの変更が必要)がある場合、少なくとも少しの間、サイト全体を停止する必要があります。秘訣は、オリジナルをドロップする前に置換プロセスを起動することです。

Tomcatを例として使用-CATALINA_BASEを使用して、実行可能コードとは別に、Tomcatのすべての作業ディレクトリが見つかるディレクトリを定義できます。ソフトウェアを展開するたびに、新しいコードをディスクに常駐させるために、新しいベースディレクトリに展開します。次に、新しいベースディレクトリを指すTomcatの別のインスタンスを起動し、すべてを起動して実行してから、ロードバランサーで古いプロセス(ポート番号)を新しいプロセスと交換します。

スイッチ全体のセッションデータの保持について懸念がある場合は、すべてのホストがセッションデータの複製先となるパートナーを持つようにシステムをセットアップできます。これらのホストの1つをドロップして更新し、セッションデータを再度取得できるように再起動してから、2つのホストを切り替えることができます。クラスタに複数のペアがある場合は、すべてのペアの半分を削除してから、大容量スイッチを実行するか、リリースの要件や企業の要件などに応じて、一度に1つのペアを実行できます。ただし、個人的には、セッションを変更せずにアップグレードしようとするのではなく、アクティブなセッションが非常にまれに失われることをエンドユーザーが受けられるようにしたい。

これはすべて、ITインフラストラクチャ、リリースプロセスの複雑さ、開発者の努力の間のトレードオフです。クラスターが十分に大きく、欲望が十分強い場合、ほとんどの更新でダウンタイムなしでスワップアウトできるシステムを設計するのは簡単です。更新されたソフトウェアは通常古いスキーマに対応できないため、大規模なスキーマ変更は実際のダウンタイムを強制することが多く、データを新しいdbインスタンスにコピーし、スキーマ更新を行ってから、サーバーを新しいdbに切り替えることはできません。新しいデータベースがそこから複製された後、古いデータは失われます。もちろん、リソースがある場合は、更新されたすべてのテーブルに新しいテーブル名を使用するように新しいアプリを変更して、開発者にタスクを実行できます。また、ライブデータベースにトリガーを配置して、新しいテーブルをデータとして正しく更新できます。以前のバージョンで古いテーブルに書き込まれます(または、ビューを使用して、あるスキーマを別のスキーマからエミュレートします)。新しいアプリサーバーを起動して、それらをクラスターに入れ替えます。ゲームを構築するための開発リソースがあれば、ダウンタイムを最小限に抑えるためにプレイできるゲームはたくさんあります。

おそらく、ソフトウェアアップグレード中のダウンタイムを削減するための最も有用なメカニズムは、アプリが読み取り専用モードで機能できるようにすることです。これにより、必要な機能がユーザーに提供されますが、データベース全体の変更などを必要とするシステム全体の変更を行うことができます。アプリを読み取り専用モードにしてから、データのクローンを作成し、スキーマを更新し、新しいアプリサーバーを新しいデータベースに対して起動し、ロードバランサーを切り替えて新しいアプリサーバーを使用します。唯一のダウンタイムは、読み取り専用モードに切り替えるのに必要な時間と、ロードバランサーの構成を変更するのに必要な時間です(ほとんどの場合、ダウンタイムなしで処理できます)。

13
ideasculptor

私のアドバイスは、展開バージョンでrsyncを使用することですが、warファイルをデプロイすることです。

  1. 展開されたバージョンのwebappを配置するライブ環境に一時フォルダーを作成します。
  2. Rsync展開バージョン。
  3. Rsyncが成功したら、ライブ環境のマシンの一時フォルダーにwarファイルを作成します。
  4. サーバーのデプロイディレクトリの古いwarを一時フォルダの新しいwarに置き換えます。

JBossコンテナ(Tomcatに基づく)では、古い戦争を新しい戦争に置き換えることをお勧めします。これは、アトミックで高速な操作であり、デプロイヤが起動するとアプリケーション全体がデプロイされた状態になるためです。

10
cetnar

Webサーバー上の現在のWebアプリケーションのローカルコピーを作成し、そのディレクトリにrsyncし、シンボリックリンクを使用して、1回の "go"で、Tomcatに多くのダウンタイムなしで新しいデプロイメントを指示できませんか?

8
jarnbjo

抽出された戦争をrsyncするためのアプローチはかなり良いです。また、本番サーバーでホットデプロイメントを有効にすべきではないと私が信じているので、再起動もします。したがって、唯一の欠点は、サーバーを再起動する必要がある場合のダウンタイムです。

アプリケーションのすべての状態がデータベースに保持されていると想定しているため、一部のユーザーが1つのアプリサーバーインスタンスで作業していても、他のユーザーが別のアプリサーバーインスタンスで作業している場合は問題ありません。もしそうなら、

2つのアプリサーバーを実行する:2番目のアプリサーバー(他のTCPポートでリッスンする)を起動し、そこにアプリケーションをデプロイします。デプロイ後、Apache httpdの構成を更新します( mod_jkまたはmod_proxy)を使用して2番目のアプリサーバーをポイントします。Apachehttpdプロセスを適切に再起動します。これにより、ダウンタイムがなくなり、新しいユーザーとリクエストが新しいアプリサーバーに自動的にリダイレクトされます。

アプリサーバーのクラスタリングとセッションレプリケーションのサポートを利用できる場合、2番目のアプリサーバーが起動するとすぐに再同期されるため、現在ログインしているユーザーにとってもスムーズです。次に、最初のサーバーへのアクセスがない場合は、サーバーをシャットダウンします。

4
mhaller

これは、アプリケーションアーキテクチャによって異なります。

私のアプリケーションの1つは、負荷分散プロキシの背後にあり、スタッガード配置を実行して、ダウンタイムを効果的に根絶しています。

4
Matt
4
elhoim

静的ファイルが大きなWARの大きな部分である場合(100Moはかなり大きい)、それらをWARの外部に配置し、アプリケーションサーバーの前のWebサーバー(Apacheなど)に展開すると、処理が高速化する場合があります。その上、Apacheは通常、静的ファイルの提供においてサーブレットエンジンよりも優れた機能を発揮します(たとえそれらのほとんどがその領域で大きな進歩を遂げたとしても)。

したがって、大きな脂肪のWARを生成する代わりに、それを食事に入れて生成します。

  • apache用の静的ファイルを含む大きなファットZip
  • サーブレットエンジンの脂肪の少ないWAR。

必要に応じて、WARをより薄くするプロセスをさらに進めます。可能であれば、頻繁に変更されないGrailsやその他のJAR(ほとんどの場合そうです)をアプリケーションサーバーレベルでデプロイします。

軽量なWARの作成に成功した場合、アーカイブではなくディレクトリのrsyncを行うことはありません。

このアプローチの長所:

  1. 静的ファイルは、Apacheでホットデプロイできます(たとえば、現在のディレクトリを指すシンボリックリンクを使用し、新しいファイルを解凍し、シンボリックリンクを更新して、失敗します)。
  2. WARはより薄くなり、展開にかかる時間が短くなります。

このアプローチの弱点:

  1. サーバー(Webサーバー)がもう1つあるため、これにより(少し)複雑さが増します。
  2. ビルドスクリプトを変更する必要があります(大したIMOではありません)。
  3. Rsyncロジックを変更する必要があります。
2
Pascal Thivent

初期のコンテキストと同様に再起動します。すべてのコンテナーには、クラスファイルまたは静的リソースの変更に対する自動再デプロイを無効にする構成オプションがあります。おそらくweb.xmlの変更時に自動再デプロイを無効にできないため、このファイルが最後に更新されます。したがって、自動再デプロイを無効にして、最後のweb.xmlを更新すると、コンテキストが再起動しますafter更新全体が表示されます。

1
toomasr

Resinを使用している場合、ResinにはWebアプリのバージョン管理のサポートが組み込まれています。

http://www.caucho.com/resin-4.0/admin/deploy.xtp#VersioningandGracefulUpgrades

更新:ウォッチドッグプロセスは、permgenspaceの問題にも役立ちます。

1
danieljimenez

あなたのPermSpaceセットは何ですか?私もこれが成長することを期待しますが、古いクラスのコレクションの後にすべき下がりますか? (またはClassLoaderはまだ座っていますか?)

大胆に考えると、別のバージョン名または日付名のディレクトリにrsyncすることができます。コンテナーがシンボリックリンクをサポートしている場合、ルートプロセスをSIGSTOPし、シンボリックリンクを介してコンテキストのファイルシステムルートを切り替えてから、SIGCONTを実行できますか?

1
Jé Queue

新しいバージョンのwebappを別のディレクトリにアップロードしてから、移動して実行中のディレクトリと入れ替えるか、シンボリックリンクを使用します。たとえば、「myapp」という名前のTomcat webappsディレクトリには、「myapp-1.23」という名前の現在のwebappを指すシンボリックリンクがあります。新しいウェブアプリを「myapp-1.24」にアップロードします。すべての準備ができたら、サーバーを停止し、シンボリックリンクを削除して、新しいバージョンを指す新しいリンクを作成してから、サーバーを再起動します。

パフォーマンスのために本番サーバーで自動再読み込みを無効にしますが、静的ファイルまたはJSPページでさえリンクの破損や悪化の原因となる可能性があるため、非アトミックな方法でwebapp内のファイルを変更すると問題が発生する可能性があります。

実際には、ウェブアプリは実際には共有ストレージデバイスに配置されているため、クラスター化された負荷分散サーバーとフェイルオーバーサーバーはすべて同じコードを使用できます。

この方法の主な欠点は、rsyncが変更または追加されたファイルのみを転送できるため、アップロードに時間がかかることです。最初に古いwebappフォルダーを新しいフォルダーにコピーし、rsyncを実行して、大きな違いがあり、それが本当に問題である場合は、.

1
Kief

これがあなたの質問に答えるかどうかはわかりませんが、私が使用した、または私が行ったいくつかのプロジェクトで遭遇した展開プロセスについて共有します。

あなたと同じように、私は完全な戦争の再配備や更新をしたことを覚えていません。ほとんどの場合、私の更新はいくつかのjspファイル、おそらくライブラリ、いくつかのクラスファイルに制限されています。影響を受けるアーティファクトを管理および特定することができます。通常、これらの更新は、更新スクリプトと共にZipファイルにパッケージ化しました。更新スクリプトを実行します。スクリプトは次のことを行います。

  • 上書きされるファイルを、今日の日付と時刻のフォルダにバックアップします。
  • ファイルを解凍する
  • アプリケーションサーバーを停止します
  • ファイルを上に移動
  • アプリケーションサーバーを起動します

ダウンタイムが問題であり、通常は問題である場合、状態を共有していないが、スティッキーセッションルーティングを提供するルーターを使用していても、私のプロジェクトは通常HAです。

私が気になるもう1つのことは、なぜrsyncが必要なのでしょうか。ライブでデルタチェックを実行するのではなく、ステージング/開発環境で変更を決定することにより、必要な変更を知ることができます。ほとんどの場合、データベース接続やsmtpサーバーなど、運用サーバーが使用するリソースを定義する特定のプロパティファイルなど、ファイルを無視するようにrsyncを調整する必要があります。

これがお役に立てば幸いです。

1
Kent Lai

2つ以上のTomcatサーバーをプロキシ経由で使用するだけです。そのプロキシは、Apache/nignix/haproxyにすることができます。

これで、各プロキシサーバーに、ポートが設定された「in」および「out」のURLがあります。

まず、サービスを停止せずに、Tomcatで戦争をコピーします。 warがデプロイされると、Tomcatエンジンによって自動的に開かれます。

Server.xml内のノード「Host」のunpackWARs = "true"とautoDeploy = "true"をクロスチェックすることに注意してください。

こんな感じ

  <Host name="localhost"  appBase="webapps"
        unpackWARs="true" autoDeploy="true"
        xmlValidation="false" xmlNamespaceAware="false">

Tomcatのログを確認します。エラーがない場合は、正常に起動しています。

テストのためにすべてのAPIにアクセスします

次に、プロキシサーバーにアクセスします。

新しいwarの名前でバックグラウンドURLマッピングを変更するだけです。 Apache/nignix/haProxyのようなプロキシサーバーへの登録にかかる時間が非常に短いため、ダウンタイムを最小限に抑えることができます

マッピングURLについては https://developers.google.com/speed/pagespeed/module/domains を参照してください

1
ravi ranjan

Tomcat 7には、このユースケース用に設計された「 並列展開 」という素晴らしい機能があります。

要点は、.warをwebapps /の直下またはシンボリックリンクのいずれかのディレクトリに展開することです。アプリケーションの連続するバージョンは、app##versionという名前のディレクトリにあります(例:myapp##001およびmyapp##002)。 Tomcatは、古いバージョンへの既存のセッションと、新しいバージョンへの新しいセッションを処理します。

問題は、PermGenのリークにvery注意する必要があることです。これは特に、PermGenを多く使用するGrailsに当てはまります。 VisualVMはあなたの友達です。

1
craigforster

いくつかのパラメーターを取り、サーバー間でファイルをrsyncするbashスクリプトを作成しました。大規模なアーカイブの場合、rsync転送を大幅に高速化します。

https://Gist.github.com/3985742

0
Kehan

「ベストプラクティス」ではなく、考えただけのこと。

GitなどのDVCSを介してwebappをデプロイするのはどうですか?

このようにして、サーバーに転送するファイルをgitに認識させることができます。また、それがバストされていることが判明した場合、それを元に戻すための素晴らしい方法があります。元に戻すだけです!

0
John Nilsson