Auto Scalingを実運用していく上で苦労した(している)部分の話であり, 手探りで設定していくうち個人的にハマった部分を共有したい(あわよくばもっとうまくやっている人の話も聞きたい)という動機で書かれた記事です.
そもそもAuto Scalingを設定するためには,
- Launch ConfigやAuto Scaling Groupなど複数の新しい概念を学習しつつ, それらを組み合わせなくてはならない
- まだブラウザのManagement Consoleから設定できない
等のハードルがあり, 他のAWSサービスと比べると, 初めて設定する時はけっこう手間取りました.
とはいえ情報が無くはないので, なんとかAuto Scalingが動き始めたというところから話を始めます.
運用時に解決する必要のある課題
標準的なAuto Scalingを設定したあと, 実際に動かすまでには以下の3点に対応する必要があります.
-
- インスタンス起動時にソースコードを最新にする
- AMIから起動されるサーバは, AMI作成時点のソースコードを持ち続けている. そのまま公開されると古いコードがユーザの目に触れてしまうので, インスタンス起動時にGitレポジトリから最新のmasterブランチを取得して更新して欲しい.
-
- インスタンス起動時にミドルウェアの設定を稼働中サーバと揃える
- 「アプリ稼働に必要なライブラリが追加された」「nginxの設定を変更した」等, 稼働中のサーバで更新された設定は, 自動的に起動するインスタンスにも反映したい.
-
- 上記のセットアップが完了するまでELBからアクセスを振らない
- Auto Scalingは起動したインスタンスをAuto Scaling Groupで定義したELBに接続する. ネットワーク不調等で上記1,2の実行に時間がかかってしまうと, 不完全な状態でサーバがユーザに公開されてしまうという可能性がある. ので, これをHealthCheckで回避する.
なお今回の話は, LinuxのアプリケーションサーバをAuto Scalingで増やす, 以下の様な構成を想定しています(紫丸が上記1,2,3に対応).
大方針としては,
これら1,2,3への対応をすべてChefレシピ中に含めて, AMIからのインスタンス起動時にchef-soloを走らせます.
現在のところこの「インスタンス起動時」のトリガーとして,
Linuxのinitシステム
を使っています(正確に何と呼べばいいのか...). 他の案として「Launch Configのuser_dataとしてスクリプトを流し込む」「CloudInitを使う」等の方法も考え試していたのですが, 一番手に馴染んで信頼性のある(?)方法に落ち着きました.
init
使用サーバはUbuntuなので, 以下の様なUpstart用のinitファイルを/etc/init/setup-auto-scaling.conf
に置いています. Fedoraを使っていた時は/etc/init.d/
以下でした.
# setup-auto-scaling - run chef-solo
#
# This task run chef-solo for app server.
description "Run chef-solo for app server"
start on runlevel [2345] and local-filesystems and net-device-up IFACE!=lo
task
env HOME=/root
env LC_ALL="en_US.UTF-8"
chdir /tmp
post-start script
git clone git@github.com:myaccount/chef-repo.git /tmp/chef-repo \
>> /tmp/run-chef.log 2>&1
cd /tmp/chef-repo && /usr/local/rvm/bin/bootup_berks install \
>> /tmp/run-chef.log 2>&1
rolebysg=`curl http://169.254.169.254/latest/meta-data/security-groups`
chef-solo -c /tmp/chef-repo/solo.rb -j /tmp/chef-repo/roles/$rolebysg.json \
>> /tmp/run-chef.log 2>&1
end script
前提としてサーバのrootがchefを実行可能な状態でAMI化しておく必要があります. 上記スクリプトでやっていることは以下の通り.
- GitHubから最新のchefレポジトリをclone
- 依存cookbookを取得するためBerkshelfを実行
- AWSのHTTP apiを使ってSecurityGroupを取得
- 予めSecurityGroup名で配置しておいたroleファイルを指定しchef-solo実行
Security Groupの違い = 役割の違い, とみなして困るケースはほぼ無いので, こうしています.
chef
自前で管理している最新のcookbookを使ってchefを実行するということは, 前述の課題1,2,3のうち1と2はほぼ片付きます. 例えば「(1): インスタンス起動時にソースコードを最新にする」に対応する箇所をrecipeから抜き出すと, こんな感じになると思います.
git "app-repository" do
repository "git@github.com:myaccount/app.git"
reference 'master'
destination "/path/to/nginx/root/#{repo}/current"
action :sync
end
1年半前にAuto Scalingを設定した時はchefを使っていなかったため, nginxの設定を変更するとAMIも作りなおすという悲しい運用だったのですが, 今はchef任せです. チームで「サーバ設定を変更するときはchefレポジトリにcommitすること」というルールを共有しておけばサーバ間の設定漏れがなくなるため実にありがたく, chef様様です.
health check
最後に「(3): 上記のセットアップが完了するまでELBからアクセスを振らない」に関して, 採用している解決策は
「ELBがチェックするHealth CheckファイルをGitレポジトリからignoreしておき, chef-solo実行完了時にHealth Checkファイルをtouchする」
というものです. こうしておけば, chefの実行が終わる前あるいは「GitHubがたまたま落ちてた」などでchefの実行が途中でコケた時に不完全な状態のサーバがELBからアクセスを受ける心配はなくなります.
まずはAWSのELB設定で(例えば)/ping.html
をチェックするように設定して,
アプリケーションのGitレポジトリからping.htmlをignoreしておきます.
+ ping.html
これで, Gitレポジトリから最新ソースコードを落としてきた段階ではping.html
が存在しないのでELBからのHealthCheckに失敗し, アクセスが割り振られることはありません.
(ping.htmlを削除した上でAMIを作成する必要があるので注意)
その上でchef recipeの一番最後に,
template 'ping.html' do
path '/path/to/nginx/root/ping.html'
source 'ping.html'
mode 0775
end
このようなtemplate
ブロックを書いておけば, chef-soloの実行が完了した後にのみELBからのアクセスを受けさせることができます.
いじょ
最適解である自信はないのですが, 以上のようにしてAuto Scalingを運用しています. 何かの助けになれば幸いです. また, もっと良い方法をご存じの方は教えてください.