目的
-
/var/www/html
からアプリケーションのソースを読み取る -
/var/app/current -> /var/www/html
にシンボリックリンクを貼って現在のバージョンを動かしている -
/var/app/ondeck
で次のバージョンのビルドを行う - 最終的に
/var/app/ondeck
で/var/app/current
を上書きしたい
Elastic Beanstalk において,なんとか1つの環境だけで Blue-Green デプロイができないかな,と試行錯誤した記録です。
手順
前提
-
/var/app/ondeck
で既にビルドが終わっている。 -
cp
およびmv
がGNU版である。
コマンド
ln -sf /var/app/ondeck /var/www/new_html && mv -Tf /var/www/new_html /var/www/html
sleep 5 && rm -rf /var/app/current
cp -al /var/app/ondeck /var/app/current
ln -sf /var/app/current /var/www/new_html && mv -Tf /var/www/new_html /var/www/html
sleep 5 && rm -rf /var/app/ondeck
ポイント
- ディレクトリのシンボリックリンクを上書きできる
ln -snf
はアトミックな操作ではないので,**ln -sf
とmv -Tf
**を組み合わせる必要がある。 - 新しいパスにコピーするときには,ハードリンクを再帰的に作成する**
cp -al
**を使うと,アプリケーションによるディスク使用領域が一時的に2倍に膨れ上がる無駄も避けられる。 - 読もうとするファイルが既に削除されていることを防ぐために,1リクエストの処理にかかりそうな時間ぶんあるいはその数倍だけ,
rm
の前にスリープを入れておくと安全。ここでは5秒と見なす。 - オートローディングのベースになる
__DIR__
や__FILE__
はシンボリックリンク解決後のパスになるため,古いバージョンから新しいバージョンを部分的に読み込んでしまう不整合は発生しない。直接/var/www/html/src/foo/bar/Klass.php
などと,シンボリックリンクを含む絶対パスをハードコーディングしている場合はアウト。
検証
macOS上で実験しているので,mv
の代わりにgmv
,cp
の代わりにgcp
を使っています。
#!/bin/sh
# 準備
mkdir -p /tmp/example/current /tmp/example/ondeck
echo aaaa > /tmp/example/current/data.txt
echo bbbb > /tmp/example/ondeck/data.txt
ln -s /tmp/example/current /tmp/example/html
# 監視を開始
perl -e 'while (1) { open(my $file, "/tmp/example/html/data.txt") or die("ERROR!"); print <$file> }' &
sleep 1
# 置き換えを開始
ln -sf /tmp/example/ondeck /tmp/example/new_html && gmv -Tf /tmp/example/new_html /tmp/example/html
sleep 5 && rm -rf /tmp/example/current
gcp -al /tmp/example/ondeck /tmp/example/current
ln -sf /tmp/example/current /tmp/example/new_html && gmv -Tf /tmp/example/new_html /tmp/example/html
sleep 5 && rm -rf /tmp/example/ondeck
# 後始末
kill %%
rm -rf /tmp/example
ゼロダウンタイムになっているかどうかを確認します。
できました!
Elastic Beanstalk への適用
/opt/elasticbeanstalk/hooks/appdeploy/enact/01_flip.sh
に以下の変更を加えます。
#!/usr/bin/env bash
#==============================================================================
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Amazon Software License (the "License"). You may not use
# this file except in compliance with the License. A copy of the License is
# located at
#
# https://aws.amazon.com/asl/
#
# or in the "license" file accompanying this file. This file is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or
# implied. See the License for the specific language governing permissions
# and limitations under the License.
#==============================================================================
set -xe
EB_APP_STAGING_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k app_staging_dir)
EB_APP_DEPLOY_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k app_deploy_dir)
if [ -d $EB_APP_DEPLOY_DIR ]; then
mv $EB_APP_DEPLOY_DIR $EB_APP_DEPLOY_DIR.old
fi
mv $EB_APP_STAGING_DIR $EB_APP_DEPLOY_DIR
nohup rm -rf $EB_APP_DEPLOY_DIR.old >/dev/null 2>&1 &
#!/usr/bin/env bash
#==============================================================================
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Amazon Software License (the "License"). You may not use
# this file except in compliance with the License. A copy of the License is
# located at
#
# https://aws.amazon.com/asl/
#
# or in the "license" file accompanying this file. This file is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or
# implied. See the License for the specific language governing permissions
# and limitations under the License.
#==============================================================================
set -xe
EB_APP_STAGING_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k app_staging_dir)
EB_APP_DEPLOY_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k app_deploy_dir)
ln -sf $EB_APP_STAGING_DIR /var/www/new_html && mv -Tf /var/www/new_html /var/www/html
if [ -d $EB_APP_DEPLOY_DIR ]; then
sleep 5 && mv $EB_APP_DEPLOY_DIR $EB_APP_DEPLOY_DIR.old
fi
cp -al $EB_APP_STAGING_DIR $EB_APP_DEPLOY_DIR
ln -sf $EB_APP_DEPLOY_DIR /var/www/new_html && mv -Tf /var/www/new_html /var/www/html
sleep 5 && mv $EB_APP_STAGING_DIR $EB_APP_STAGING_DIR.old
nohup rm -rf $EB_APP_STAGING_DIR.old $EB_APP_DEPLOY_DIR.old >/dev/null 2>&1 &
オートスケールで反映させるためには, .ebextensions
配下にこの定義を書く必要があります。
files:
"/opt/elasticbeanstalk/hooks/appdeploy/enact/01_flip.sh":
owner: root
group: root
mode: "000755"
content: |
#!/usr/bin/env bash
#==============================================================================
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Amazon Software License (the "License"). You may not use
# this file except in compliance with the License. A copy of the License is
# located at
#
# https://aws.amazon.com/asl/
#
# or in the "license" file accompanying this file. This file is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or
# implied. See the License for the specific language governing permissions
# and limitations under the License.
#==============================================================================
set -xe
EB_APP_STAGING_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k app_staging_dir)
EB_APP_DEPLOY_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k app_deploy_dir)
ln -sf $EB_APP_STAGING_DIR /var/www/new_html && mv -Tf /var/www/new_html /var/www/html
if [ -d $EB_APP_DEPLOY_DIR ]; then
sleep 5 && mv $EB_APP_DEPLOY_DIR $EB_APP_DEPLOY_DIR.old
fi
cp -al $EB_APP_STAGING_DIR $EB_APP_DEPLOY_DIR
ln -sf $EB_APP_DEPLOY_DIR /var/www/new_html && mv -Tf /var/www/new_html /var/www/html
sleep 5 && mv $EB_APP_STAGING_DIR $EB_APP_STAGING_DIR.old
nohup rm -rf $EB_APP_STAGING_DIR.old $EB_APP_DEPLOY_DIR.old >/dev/null 2>&1 &
これをリポジトリに含めた上で,デプロイ実行中に ll /var/www/html
を連打して確認してみました。
うまくいきました!
注意事項
PHPのようにソースコードを設置するだけでデプロイが完了するアプリケーションに関してはゼロダウンタイムですが,Railsのようにアプリケーションサーバとして永続させるタイプのものの場合,スタートアップにかかる時間が存在し,古いファイルパスの情報を保持し続けるため,依然として
- ロードバランサのスワップ
- CNAMEスワップ
といった処理は必要です。また,データベースのスキーマ変更が発生する場合もこの方法では不可能です。
…とはいえども,スキーマ変更の伴わないPHPアプリケーションの変更に関しては,非常に簡単に対応できるのは美味しいところだと思います。また1つPHPの長所が増えましたね(笑