Auto Scaling運用時のサーバ自動設定について


Auto Scalingを実運用していく上で苦労した（している）部分の話であり, 手探りで設定していくうち個人的にハマった部分を共有したい（あわよくばもっとうまくやっている人の話も聞きたい）という動機で書かれた記事です.


そもそもAuto Scalingを設定するためには, 

* Launch ConfigやAuto Scaling Groupなど複数の新しい概念を学習しつつ, それらを組み合わせなくてはならない
* まだブラウザのManagement Consoleから設定できない

等のハードルがあり, 他のAWSサービスと比べると, 初めて設定する時はけっこう手間取りました.
とはいえ情報が無くはないので, なんとかAuto Scalingが動き始めたというところから話を始めます.


運用時に解決する必要のある課題
---------------------------------------------------

標準的なAuto Scalingを設定したあと, 実際に動かすまでには以下の3点に対応する必要があります.

* 1. インスタンス起動時にソースコードを最新にする
  * AMIから起動されるサーバは, AMI作成時点のソースコードを持ち続けている. そのまま公開されると古いコードがユーザの目に触れてしまうので, インスタンス起動時にGitレポジトリから最新のmasterブランチを取得して更新して欲しい.
* 2. インスタンス起動時にミドルウェアの設定を稼働中サーバと揃える
  * 「アプリ稼働に必要なライブラリが追加された」「nginxの設定を変更した」等, 稼働中のサーバで更新された設定は, 自動的に起動するインスタンスにも反映したい.
* 3. 上記のセットアップが完了するまでELBからアクセスを振らない
  * Auto Scalingは起動したインスタンスをAuto Scaling Groupで定義したELBに接続する. ネットワーク不調等で上記1,2の実行に時間がかかってしまうと, 不完全な状態でサーバがユーザに公開されてしまうという可能性がある. ので, これをHealthCheckで回避する.

なお今回の話は, LinuxのアプリケーションサーバをAuto Scalingで増やす, 以下の様な構成を想定しています(紫丸が上記1,2,3に対応).

![as.png](https://qiita-image-store.s3.amazonaws.com/0/682/21aee558-8368-f758-fdac-23bdc91e1438.png "as.png")


大方針としては, 

_これら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/`以下でした.

```sh:/etc/init/setup-auto-scaling.conf
# 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から抜き出すと, こんな感じになると思います.

```ruby:sync-code.rb
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`をチェックするように設定して,

![2013-12-09 at 2.37 AM.png](https://qiita-image-store.s3.amazonaws.com/0/682/b89ea628-cc7e-32ee-d36f-124fb1339185.png "2013-12-09 at 2.37 AM.png")

アプリケーションのGitレポジトリからping.htmlをignoreしておきます.

```diff:.gitignore
+ ping.html
```

これで, Gitレポジトリから最新ソースコードを落としてきた段階では`ping.html`が存在しないのでELBからのHealthCheckに失敗し, アクセスが割り振られることはありません.
（ping.htmlを削除した上でAMIを作成する必要があるので注意）

その上でchef recipeの一番最後に, 

```ruby:cookbooks/hoge/recipes/touch-healthcheck.rb
template 'ping.html' do
  path '/path/to/nginx/root/ping.html'
  source 'ping.html'
  mode 0775
end
```

このような`template`ブロックを書いておけば, chef-soloの実行が完了した後にのみELBからのアクセスを受けさせることができます.



いじょ
---------------

最適解である自信はないのですが, 以上のようにしてAuto Scalingを運用しています. 何かの助けになれば幸いです. また, もっと良い方法をご存じの方は教えてください.