この記事は Java EE Advent Calendar 2015 - Qiita の15日目のエントリーです。
昨日は@den2snさんで、Javaバッチの実行環境 EEなのかSEなのか でした。 明日は@glory_ofさんです。
読んで字のごとく、サービスの停止時間無しにアプリケーションを配備する事ですね。
無停止リリースとか無停止デプロイ、(ちょっとニュアンス違うけど)ローリングアップデートなどという言われ方もします。Erlang界隈ではホットデプロイという呼ばれ方をするようです。
実現方法は色々あります。自分の知っている限りで、以下に書き出してみました。
Google App Engine や AWS Elastic Beanstalk にはアプリのバージョン管理機能があるので、これを利用すれば安全にアプリを入れ替えられますね。
TomcatのParallel Deploymentはメモリリーク等が心配なんですが、どうなんでしょうか。その辺りが問題なければ、良い解法だと思います。
CoreOS + etcd + fleet という組み合わせで、ゼロダウンタイムデプロイが実現できるそうです。
クラスタリングのおまけとしての無停止リリース、って感じですが。Docker勉強しなきゃなぁ・・・
この辺はありがちですね。ハードウェアロードバランサの複数台構成は鉄板。予算が許せば。
PHP等のインタプリタ言語では、ファイルの直接入れ替えやシンボリックリンクの差し替えで問題ないことが多いと思います。
長い前置きが終わって、やっと本題w
それなりの規模のシステムなら、ELB使っとけば良いんじゃないですかね←投げやり
今回はシングルホスト向けに、無停止リリースをシンプルに実現する方法を紹介します。
結論から言うと古典的なコールドスタンバイ方式なのですが、 Spring Boot 1.3.0とnginxのロードバランシング機能を使うことで、かなりすっきりした構成になります。
インストール手順については、githubに上げたitamaeレシピを利用してください。いやしかしitamaeは最高ですね。Chefは見事に挫折しましたが、itamaeはすぐに馴染めました。
nginxの設定は普通です。
upstream backend {
server 127.0.0.1:8080;
server 127.0.0.1:8081 backup;
}
backup
を指定することで、8080ポートがダウンしている場合だけ、8081ポートにproxyされるようになります。以降、nginxの設定は変更しません。リリース時もこのままです。
レシピにもあるように、spring-boot:repackage
ゴールで作成したExecutable JAR(下記ではzdtd-808x.jar)を、/etc/init.dにシンボリックリンクとして設置します。
ln -sf /var/zdtd/zdtd-8080.jar /etc/init.d/zdtd-8080
ln -sf /var/zdtd/zdtd-8081.jar /etc/init.d/zdtd-8081
これはSpring Boot 1.3.0で有効になった新機能で、もはや起動スクリプトを準備する必要がありません。
Listenするポート番号を変更するため、/var/zdtd/zdtd-8080.confを準備します。
PID_FOLDER=/var/zdtd/run
LOG_FOLDER=/var/zdtd/log
JAVA_OPTS="-Xmx64M -Djava.security.egd=file:///dev/./urandom -Dserver.port=8080"
/var/zdtd/zdtd-8081.conf は-Dserver.port=8081
となっています。
たったこれだけで、同一アプリが別ポートで起動します。素敵!
ちなみに、PIDファイル名やログファイル名は、リンク先のJarファイル名から推測されるので、特別な事をしなくても衝突しません。素敵!
[root@centos6]# ls /var/zdtd/log/
zdtd-8080.log zdtd-8081.log
平常時は8080側だけ起動しておき、8081側は停止しておくこととします。
ようやくデプロイ手順まで来ました。
/etc/init.d/zdtd-8081 start
で起動/etc/init.d/zdtd-8080 stop
で停止/etc/init.d/zdtd-8080 start
で起動/etc/init.d/zdtd-8081 stop
で停止なおSpring Bootでは、アプリの動作中にJarを置き換えてはいけません。静的ファイル等は随時Jarから取得されるため、エラーになってしまいます。
githubのWebhookなどを利用すれば、上記手順は無人化できます。
1の手順をdummy_update.sh、2~7の手順をdeploy.shとしてシェルスクリプト化してみました。
deploy.shの実行結果は以下のようになります。
[root@centos6 ~]# sudo -u zdtd /var/zdtd/deploy.sh
Started [12039]
starting 8081, sleeping 30sec...
Stopped [11856]
stopping 8080, sleeping 10sec...
Started [12114]
starting 8080, sleeping 30sec...
Stopped [12039]
finished.
起動後に30秒のウェイトを入れていますが、アプリによって調整する必要がありそうです。
・・・はい、多分w
JMeterで100req/sec程度の負荷をかけながらデプロイを行い、全てのリクエストが成功する事を確認しました。ただし、8081ポートへの切り替え時に、若干遅延が増大するようです(200ms程度)。
/etc/init.d/zdtd-8080 stop
を行うと、処理中のリクエストはエラーになってしまいます。どうやら、現状のSpring Bootはgraceful shutdownではないようです。
※ドキュメントにはgracefully shutdown
という記載があるので、バグなのかもしれません。
nginxがリトライしてくれるのでエラーにはなりませんが、アプリによっては二重リクエストが問題なるかもしれません。
ちょっと駆け足気味でしたが、Spring Bootとnginxによるお手軽なゼロダウンタイムデプロイ手法を紹介しました。
何かのお役に立てれば幸いです。