2012年4月30日月曜日

ログインユーザー(認証)をOpenLDAPで管理(冗長化編)

 スズキです。

下記でOpenLDAPの冗長化ができたので、
OpenLDAPの冗長化(ミラーモード)
今度は以前行った、ログインユーザー(認証)をOpenLDAPで管理する部分を、
冗長化したOpenLDAPに対応させたいとおもいます。

といっても下記の記事の手順にある、
設定ファイル(nslcd.conf / pam_ldap.conf)を対象サーバを記述する部分を、
複数記述するだけです。

具体的には次のようになります。
# cat /etc/nslcd.conf
...
uri ldap://ha-a/ ldap://ha-b/
...

# cat /etc/pam_ldap.conf
...
host ha-a ha-b
...

これで、一方のOpenLDAPに障害が発生しても、もう一方のOpenLDAPに接続し
ログインは問題なくできるはずです。

それでは確認してみます。

まず、両方のOpenLDAPが起動している状態でログインしてみます。
[root@ha-b ~]# su - suzuki1
[suzuki1@ha-b ~]$
当然、ログインできました。

次に、"ha-a"のOpenLDAPを停止してみます。
[root@ha-a ~]# /etc/init.d/slapd stop
slapd を停止中:                                            [  OK  ]
まだ、ログインできます。
[root@ha-b ~]# su - suzuki1
[suzuki1@ha-b ~]$
そして、"ha-b"のOpenLDAPも停止します。
[root@ha-b ~]# /etc/init.d/slapd stop
slapd を停止中:                                            [  OK  ]
今度は両方のOpenLDAPが停止しているのでログインできません。
[root@ha-b ~]# su - suzuki1
su: suzuki1 というユーザは存在しません
最後に、"ha-a"のOpenLDAPを起動します。
[root@ha-a ~]# /etc/init.d/slapd start
slapd を起動中:                                            [  OK  ]
無事ログインできるようになりました。
[root@ha-b ~]# su - suzuki1
[suzuki1@ha-b ~]$ 

このへん一式、CloudFormation化しておきたいなー...
--------
http://www.suz-lab.com

OpenLDAPの冗長化(ミラーモード)

スズキです。

ミラーモードを用いたOpenLDAPの冗長化を試してみました。
このミラーモードを用いると、冗長化しているどちらのサーバに更新をかけても
もう一方にその更新が反映されるようになります。

まずはOpenLDAPの設定ですが、下記の記事が大変良くまとまっており、
ミラーモードのマルチマスター的使用方法
参考にしながら、簡単に実現できました。

ちなみにOpenLDAPのインストールは下記を前提としています。
CentOS6.2のログインユーザー(認証)をOpenLDAPで管理(OpenLDAP編)
具体的な設定は下記となります。

▼ Server 1
# cat /etc/openldap/slapd.conf
...
moduleload      syncprov.la

overlay syncprov

serverID 1

syncrepl rid=001 
  provider=ldap://ha-a
  bindmethod=simple 
  binddn="cn=Manager,dc=suz-lab,dc=com" 
  credentials=secret 
  searchbase="dc=suz-lab,dc=com" 
  schemachecking=on
  type=refreshAndPersist 
  retry="10 +"

syncrepl rid=002 
  provider=ldap://ha-b
  bindmethod=simple 
  binddn="cn=Manager,dc=suz-lab,dc=com" 
  credentials=secret
  searchbase="dc=suz-lab,dc=com" 
  schemachecking=on 
  type=refreshAndPersist 
  retry="10 +"

mirrormode on

▼ Server 2
# cat /etc/openldap/slapd.conf
...
moduleload      syncprov.la

overlay syncprov

serverID 2

syncrepl rid=001 
  provider=ldap://ha-a
  bindmethod=simple 
  binddn="cn=Manager,dc=suz-lab,dc=com" 
  credentials=secret 
  searchbase="dc=suz-lab,dc=com" 
  schemachecking=on
  type=refreshAndPersist 
  retry="10 +"

syncrepl rid=002 
  provider=ldap://ha-b
  bindmethod=simple 
  binddn="cn=Manager,dc=suz-lab,dc=com" 
  credentials=secret
  searchbase="dc=suz-lab,dc=com" 
  schemachecking=on 
  type=refreshAndPersist 
  retry="10 +"

mirrormode on

上記の設定を行い、OpenLDAPを再起動することで、ミラーモードが開始されます。

それでは動作確認です。

初期状態は下記の通りです。
# ldapsearch -x -LLL -D "cn=Manager,dc=suz-lab,dc=com" -W -b "ou=user,dc=suz-lab,dc=com"
Enter LDAP Password: 
dn: ou=user,dc=suz-lab,dc=com
objectClass: organizationalUnit
ou: user

次はServer1にて"suzuki1"を追加してみます。
# cat suzuki1.ldif
dn: uid=suzuki1,ou=user,dc=suz-lab,dc=com
objectClass: top
objectClass: posixAccount
objectClass: account
uid: suzuki1
cn: suzuki1
uidNumber: 2001
gidNumber: 2000
homeDirectory: /home/suzuki1
loginShell: /bin/bash
userPassword: suzuki1

# ldapadd -x -D "cn=Manager,dc=suz-lab,dc=com" -w secret -f suzuki1.ldif 
adding new entry "uid=suzuki1,ou=user,dc=suz-lab,dc=com"

# ldapsearch -x -LLL -D "cn=Manager,dc=suz-lab,dc=com" -W -b "ou=user,dc=suz-lab,dc=com"
Enter LDAP Password: 
dn: ou=user,dc=suz-lab,dc=com
objectClass: organizationalUnit
ou: user

dn: uid=suzuki1,ou=user,dc=suz-lab,dc=com
objectClass: top
objectClass: posixAccount
objectClass: account
uid: suzuki1
cn: suzuki1
uidNumber: 2001
gidNumber: 2000
homeDirectory: /home/suzuki1
loginShell: /bin/bash
userPassword:: c3V6dWtpMQ==

Server2で確認すると、"suzuki1"の追加が反映していることがわかります。
# ldapsearch -x -LLL -D "cn=Manager,dc=suz-lab,dc=com" -W -b "ou=user,dc=suz-lab,dc=com"
Enter LDAP Password: 
dn: ou=user,dc=suz-lab,dc=com
objectClass: organizationalUnit
ou: user

dn: uid=suzuki1,ou=user,dc=suz-lab,dc=com
objectClass: top
objectClass: posixAccount
objectClass: account
uid: suzuki1
cn: suzuki1
uidNumber: 2001
gidNumber: 2000
homeDirectory: /home/suzuki1
loginShell: /bin/bash
userPassword:: c3V6dWtpMQ==

次は、Server2で"suzuki2"を追加してみます。
# cat suzuki2.ldif
dn: uid=suzuki2,ou=user,dc=suz-lab,dc=com
objectClass: top
objectClass: posixAccount
objectClass: account
uid: suzuki2
cn: suzuki2
uidNumber: 2002
gidNumber: 2000
homeDirectory: /home/suzuki2
loginShell: /bin/bash
userPassword: suzuki2

# ldapadd -x -D "cn=Manager,dc=suz-lab,dc=com" -w secret -f suzuki2.ldif 
adding new entry "uid=suzuki2,ou=user,dc=suz-lab,dc=com"

# ldapsearch -x -LLL -D "cn=Manager,dc=suz-lab,dc=com" -W -b "ou=user,dc=suz-lab,dc=com"
Enter LDAP Password: 
dn: ou=user,dc=suz-lab,dc=com
objectClass: organizationalUnit
ou: user

dn: uid=suzuki1,ou=user,dc=suz-lab,dc=com
objectClass: top
objectClass: posixAccount
objectClass: account
uid: suzuki1
cn: suzuki1
uidNumber: 2001
gidNumber: 2000
homeDirectory: /home/suzuki1
loginShell: /bin/bash
userPassword:: c3V6dWtpMQ==

dn: uid=suzuki2,ou=user,dc=suz-lab,dc=com
objectClass: top
objectClass: posixAccount
objectClass: account
uid: suzuki2
cn: suzuki2
uidNumber: 2002
gidNumber: 2000
homeDirectory: /home/suzuki2
loginShell: /bin/bash
userPassword:: c3V6dWtpMg==

Server1で確認しても、同様に"suzuki2"が追加されていることがわかります。
# ldapsearch -x -LLL -D "cn=Manager,dc=suz-lab,dc=com" -W -b "ou=user,dc=suz-lab,dc=com"
Enter LDAP Password: 
dn: ou=user,dc=suz-lab,dc=com
objectClass: organizationalUnit
ou: user

dn: uid=suzuki1,ou=user,dc=suz-lab,dc=com
objectClass: top
objectClass: posixAccount
objectClass: account
uid: suzuki1
cn: suzuki1
uidNumber: 2001
gidNumber: 2000
homeDirectory: /home/suzuki1
loginShell: /bin/bash
userPassword:: c3V6dWtpMQ==

dn: uid=suzuki2,ou=user,dc=suz-lab,dc=com
objectClass: top
objectClass: posixAccount
objectClass: account
uid: suzuki2
cn: suzuki2
uidNumber: 2002
gidNumber: 2000
homeDirectory: /home/suzuki2
loginShell: /bin/bash
userPassword:: c3V6dWtpMg==

OpenLDAPサーバの冗長化が実現できたので、次はクライアント側の予定です。
--------
http://www.suz-lab.com

Heartbeat(Pacemaker)でEIPの付け替え

スズキです。

CDPネタです。今回の対象は「Floating IPパターン」です。



このパターンの「その他」に
障害検知を行う際は、HeartBeatやNagios、Zabbixといった監視ソフトを
利用してもよい。EIPの付け替えはプログラムから実行できるので、
監視ソフトと組み合わせれば自動化を行うこともできる。
といった記載があります。

今回は、Heartbeat(Pacemaker)を利用して自動的にEIPを付け替える方法を
試してみました。

すでに、Heartbeat(Pacemaker)に関しては下記で導入し
VPC上のCentOS6.2にHeartbeatをインストールし起動(アップデート版)
EIPを付け替えるPHPスクリプトも次のように用意はしています。
EIPを付け替えるPHPスクリプト
後はPacemakerのリソース管理で上記のスクリプトを利用して、
アクティブサーバに障害が発生して場合にEIPをスタンバイサーバに
付け替える ように設定するだけです。

PacemakerによるEIPの付け替え設定は、LSBで行うことにします。
これは、EIPを付け替える"/etc/init.d/eip"のような起動スクリプトを
用意して、モニタリングやフェイルオーバー時の起動/停止を
Pacemakerがこの起動スクリプトを利用するように設定する方法です。

ということで、"/etc/init.d/eip"を下記のように用意しました。
スクリプト中にでてくる"/opt/aws/bin/associate-eip"は、
上記ブログ(EIPを付け替えるPHPスクリプト)で紹介したものとなります。
#!/bin/bash
#
# eip        Associate EIP.
#
# chkconfig: 2345 99 10
# description: Associate EIP
 
# Source function library.
. /etc/init.d/functions
 
prog=eip
lock=/var/lock/subsys/$prog
log=/opt/aws/log/error.log
 
# Source config
if [ -f /etc/sysconfig/$prog ] ; then
    . /etc/sysconfig/$prog
fi

case "$1" in
    start)
        echo `date +"%Y-%m-%d %T"` "begin associate-eip" >> $log
        touch $lock
        /opt/aws/bin/associate-eip >> $log 2>&1
        exit $?
        ;;
    stop)
        echo `date +"%Y-%m-%d %T"` "end   associate-eip" >> $log
        rm -f $lock
        exit $?
        ;;
    status)
        if [ -f $lock ]
        then
            exit 0
        else
            exit 3
        fi
        ;;
    *)
        echo $"Usage: $0 {start|stop}"
        exit 1
esac
注意点として、上記のリターンコードは下記に準じている必要があります。
Is This init Script LSB Compatible?
特に注意するべき点は、サービスが停止状態で"status"を実行した時に、 リターンコードを3にする点でしょうか?

そして最後に下記のように、Pacemakerへの設定です。
# crm configure property no-quorum-policy="ignore" stonith-enabled="false"
# crm configure rsc_defaults resource-stickiness="INFINITY" migration-threshold="1"
# crm configure primitive eip lsb:eip
※resource-stickiness="INFINITY"で自動フェイルバックしないようにしています。

それでは実際にフェイルオーバーの確認をしてみます。

最初の状態は下記の通り、"ha-b"がアクティブで、
============
Last updated: Mon Apr 30 02:07:33 2012
Stack: Heartbeat
Current DC: ha-b (ea5894a8-4664-3723-dc36-77d7f1e1ad08) - partition with quorum
Version: 1.0.11-1554a83db0d3c3e546cfd3aaff6af1184f79ee87
2 Nodes configured, unknown expected votes
1 Resources configured.
============

Online: [ ha-a ha-b ]

eip     (lsb:eip): Started ha-b
EIPも"ha-b"に振られています。


次に"ha-b"のHeartbeatを停止します。
# /etc/init.d/heartbeat stop
Stopping High-Availability services: Done.

すると"ha-b"がオフラインになり、"ha-b"がアクティブになっていることが
わかります。
============
Last updated: Mon Apr 30 02:12:08 2012
Stack: Heartbeat
Current DC: ha-a (7974d41a-897b-ee90-0770-7ec9e990db35) - partition with quorum
Version: 1.0.11-1554a83db0d3c3e546cfd3aaff6af1184f79ee87
2 Nodes configured, unknown expected votes
1 Resources configured.
============

Online: [ ha-a ]
OFFLINE: [ ha-b ]

eip     (lsb:eip): Started ha-a
EIPも"ha-a"の方に付け替えられています。


フェイルオーバー前後のPing結果は下記のような感じです。
$ ping 54.248.108.190
PING 54.248.108.190 (54.248.108.190): 56 data bytes
...
64 bytes from 54.248.108.190: icmp_seq=8 ttl=50 time=5.862 ms
64 bytes from 54.248.108.190: icmp_seq=9 ttl=50 time=5.040 ms
64 bytes from 54.248.108.190: icmp_seq=10 ttl=50 time=5.072 ms
Request timeout for icmp_seq 11
64 bytes from 54.248.108.190: icmp_seq=12 ttl=50 time=5.435 ms
64 bytes from 54.248.108.190: icmp_seq=13 ttl=50 time=7.674 ms
64 bytes from 54.248.108.190: icmp_seq=14 ttl=50 time=4.928 ms
64 bytes from 54.248.108.190: icmp_seq=15 ttl=50 time=4.796 ms
64 bytes from 54.248.108.190: icmp_seq=16 ttl=50 time=5.321 ms
64 bytes from 54.248.108.190: icmp_seq=17 ttl=50 time=5.048 ms
64 bytes from 54.248.108.190: icmp_seq=18 ttl=50 time=4.873 ms
64 bytes from 54.248.108.190: icmp_seq=19 ttl=50 time=5.381 ms
Request timeout for icmp_seq 20
Request timeout for icmp_seq 21
Request timeout for icmp_seq 22
64 bytes from 54.248.108.190: icmp_seq=23 ttl=50 time=6.571 ms
64 bytes from 54.248.108.190: icmp_seq=24 ttl=50 time=6.282 ms
64 bytes from 54.248.108.190: icmp_seq=25 ttl=50 time=6.488 ms
(昨日、試したときは、切り替えにもっと時間がかかってましたが...)

図にすると、こんな感じでしょうか?


ENIの付け替えも試したいなー...
--------
http://www.suz-lab.com

2012年4月29日日曜日

EIPを付け替えるPHPスクリプト

スズキです。

指定したEIPを自分自身(EC2)に付け替えるPHPスクリプトを作ってみました。

#!/usr/bin/php
<?php
require_once("/opt/aws/php/default/sdk.class.php");
define("EIP", "xxx.xxx.xxx.xxx");
$ec2 = new AmazonEC2(array(
  "key"    => "ACCESS KEY",
  "secret" => "SECRET KEY"
));
$ec2->set_region(AmazonEC2::REGION_APAC_NE1);
$response = $ec2->disassociate_address(EIP);
if(!$response->isOK()) {
  error_log("[" . $response->body->Errors->Error->Code . "] " . $response->body->Errors->Error->Message);
}
$response = $ec2->associate_address(
  file_get_contents("http://169.254.169.254/latest/meta-data/instance-id"),
  EIP
);
if(!$response->isOK()) {
  error_log("[" . $response->body->Errors->Error->Code . "] " . $response->body->Errors->Error->Message);
  exit(1);
}
exit(0);
?>

一旦"disassociate_address"で、もし他のEC2に付けられていたら外し、
自分自身(EC2)に付け直すようにしています。

Heartbeat(Pacemaker)と連携させてみよう。
--------
http://www.suz-lab.com

VPC上のCentOS6.2にHeartbeatをインストールし起動(アップデート版)

スズキです。

Heartbeatのインストールと起動に関しては、下記のようにいろいろと紹介してきましたが、
VPC上で無いものやOSが違ったりHeartbeatのバージョンが古かったりで、
現状、情報的に中途半端だったので、もう一度、試し直してみました。

まず、パッケージのインストールですが、下記の
http://sourceforge.jp/projects/linux-ha/releases/55568
"pacemaker-1.0.12-1.1.el6.x86_64.repo.tar.gz"を利用しました。
こちらは"yum"のリポジトリになっているので、"/tmp"に展開して
下記のようにインストールを行います。
# tar xvzf pacemaker-1.0.11-1.2.2.el6.x86_64.repo.tar.gz
# cd pacemaker-1.0.11-1.2.2.el6.x86_64.repo
# yum -c pacemaker.repo install heartbeat-3.0.5 pacemaker-1.0.11

次に設定ファイルですが、以下のようにしています。
# cd /etc/ha.d/
# cat ha.cf
debugfile   /var/log/ha-debug
logfile     /var/log/ha-log
logfacility local0
keepalive   2
deadtime    30
initdead    120
udpport     694
ucast       eth0 10.0.0.4
ucast       eth0 10.0.1.4
node        suz-lab-a
node        suz-lab-b
uuidfrom    nodename
crm         on
VPCは次のFAQの通りブロードキャストは利用できません。
Q: Amazon VPC は、マルチキャストまたはブロードキャストをサポートしますか?
いいえ。
ですのでユニキャスト(ucast)で直接IPアドレスを指定しています。
ちなみに、この"ha.cf"は、冗長化する両方のサーバで同一のものを利用するので
自分自身の記述も含めておく必要があります。
(つまり自分と相手2つ分を書いておきHeartbeatが自動でどちらか判断します)
ノード(node)も同様に自分と相手の両方("hostname"のもの)を記述します。

次に"authkeys"です。こちらも冗長化する二つのサーバで同一のものを
利用します。パーミッションの設定が注意点です。
# cd /etc/ha.d/
# cat authkeys
auth 1
1 crc
#2 sha1 HI!
#3 md5 Hello!
# chmod 600 authkeys

最後のHeartbeatの起動です。
# /etc/init.d/heartbeat start
# chkconfig heartbeat on

ここまでを、冗長化する両方のサーバで同様に実施しておきます。

そして最後に動作確認です。どちらかのサーバで下記のように
コマンドを実行し表示されれば、Heartbeatが稼働していることを確認できます。
# crm_mon
============
Last updated: Thu Apr 26 18:39:55 2012
Stack: Heartbeat
Current DC: suz-lab-a (33571cc4-23e2-4285-bb1d-f063c9657f1e) - partition
with quorum
Version: 1.0.11-1554a83db0d3c3e546cfd3aaff6af1184f79ee87
2 Nodes configured, unknown expected votes
0 Resources configured.
============

Online: [ suz-lab-a suz-lab-b ]

ここから、"Floating IPパターン"につなげたい...
--------
http://www.suz-lab.com

2012年4月24日火曜日

CentOS(5.8)にWebistranoをインストール

スズキです。

CentOS(5.8)にWebistranoをインストールしてみました。

まずは必要なパッケージのインストールです。

# yum -y install rubygem-rake
# yum -y install ruby-mysql
# gem install rack --version 1.0.1

次にWebistranoのダウンロードと展開です。

# cd /opt/
# curl -OL https://github.com/downloads/peritor/webistrano/webistrano-1.5.zip
# unzip webistrano-1.5.zip

さらに設定ファイルをコピーして調整します。

# cd webistrano/config
# cp webistrano_config.rb.sample webistrano_config.rb
# cp database.yml.sample database.yml
特に"database.yml"は実際に利用するMySQLのパラメータにし、
MySQL上には必要なデータベース作成しておく必要があります。

最後にデータベース内にテーブルなどを作成し、起動します。

# cd ../
# RAILS_ENV=production rake db:migrate
# ruby script/server -d -p 3000 -e production

起動した状態で下記にアクセスすると、Webistranoのページが表示されます。

http://xxx.xxx.xxx.xxx:3000/


"ruby"のモジュールは、"yum"で入れるか"gem"で入れるかいつも迷う...
--------
http://www.suz-lab.com

CentOS(5.8)にJenkinsをインストール


スズキです。

CentOS(5.8)にJenkinsをインストールしてみました。

と言っても、"yum"のリポジトリが用意されているので、
下記のように簡単にインストールから起動まで可能です。

# cd /etc/yum.repos.d/
# curl -OL http://pkg.jenkins-ci.org/redhat/jenkins.rep
# rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
# yum -y install jenkins
# /etc/init.d/jenkins start
# chkconfig jenkins on

起動した状態で下記にアクセスすると、
Jenkinsのページが表示されます。

http://xxx.xxx.xxx.xxx:8080/


とりあえず、インストール手順です。
--------
http://www.suz-lab.com

2012年4月23日月曜日

定時にEC2を起動 & バッチが終了したらターミネート

スズキです。

CDPネタです。今回の対象は「Scheduled Autoscalingパターン」です。



このパターンの注意点に
バッチ処理が終了する時刻を決めるのが難しい場合は、
EC2上のバッチ処理が終了してから、EC2自身が自分を終了する
作り込みを行う方法もよく用いられる。
といった記載があります。

今回は上記の方法を実際に作りこんでみました。

まずは、"Auto Scaling"にてスケールアウトに使われるAMIの作成です。
このAMIはEC2起動にバッチ処理を開始し、バッチ処理が終了するときに
自分自身(EC2)もターミネートされるようにする必要があります。

具体的には起動時にバッチ処理(/opt/suz-batch/bin/run-batch)が開始されるように、
下記の起動スクリプト(/etc/init.d/suz-batch)を用意します。
#!/bin/bash
#
# suz-batch        Run Batch.
#
# chkconfig: 2345 99 10
# description: Run Batch

# Source function library.
. /etc/init.d/functions

prog=suz-batch
lock=/var/lock/subsys/$prog
log=/opt/suz-batch/log/error.log

# Source config
if [ -f /etc/sysconfig/$prog ] ; then
    . /etc/sysconfig/$prog
fi

case "$1" in
    start)
        echo `date +"%Y-%m-%d %T"` "begin" > $log
        /opt/suz-batch/bin/run-batch &
        touch $lock
        ;;
    stop)
        echo `date +"%Y-%m-%d %T"` "end" >> $log
        /opt/suz-batch/sbin/upload-log
        rm -f $lock
        ;;
    *)
        echo $"Usage: $0 {start|stop}"
        exit 1
esac

exit $?
※終了時はログをS3にアップロード(/opt/suz-batch/sbin/upload-log)します。

当然、上記の起動スクリプトが有効になるようにもしておきます。
# chkconfig --add suz-batch
# chkconfig suz-batch on
# chkconfig --list suz-batch
suz-batch       0:off 1:off 2:on 3:on 4:on 5:on 6:off
バッチ処理のスクリプト(/opt/suz-batch/bin/run-batch)は下記の通りで、
バッチ終了時に自分自身(EC2)をターミネート(/opt/suz-batch/sbin/terminate-instance)
するようにしています。
log=/opt/suz-batch/log/error.log
echo `date +"%Y-%m-%d %T"` "run" >> $log
/opt/suz-batch/sbin/terminate-instance
exit 0
自分自身(EC2)をターミネート(/opt/suz-batch/sbin/terminate-instance)する
スクリプトは下記のようにしています。
require_once("/opt/aws/php/latest/sdk.class.php");
date_default_timezone_set("Asia/Tokyo");

$as = new AmazonAS(array(
    "key"    => "ACCESS KEY",
    "secret" => "SECRET KEY"
));
$as->set_region(AmazonAS::REGION_APAC_NE1);
$response = $as->update_auto_scaling_group(
    "as-test",
    array(
       "MinSize"         => 0,
       "MaxSize"         => 0,
       "DesiredCapacity" => 0
    )
);

if(!$response->isOK()) {
    error_log(
        date("Y-m-d H:i:s") . " " . basename($_SERVER["PHP_SELF"]) . " " . $response->body->Error->to_json() . "\n",
        3,
        "/opt/suz-batch/log/error.log"
    );
    exit(1);
}
exit(0);
見ての通り、EC2をターミネートしているのではなく、"Auto Scaling"のEC2起動数を
0にすることで、結果、自分自身(EC2)がターミネートされるようにしています。

ちなみに自分自身(EC2)がターミネートするときに実行される、ログをS3にアップロードする
スクリプト(/opt/suz-batch/sbin/upload-log)は下記のようにしています。
require_once("/opt/aws/php/latest/sdk.class.php");
date_default_timezone_set("Asia/Tokyo");

$s3 = new AmazonS3(array(
    "key"    => "ACCESS KEY",
    "secret" => "SECRET KEY"
));
$s3->set_region(AmazonS3::REGION_APAC_NE1);
$s3->use_ssl = false;
$response = $s3->create_object("www.suz-lab.com", "test/error-" . date("YmdHis") . ".log", array(  
    "fileUpload" => "/opt/suz-batch/log/error.log"
)); 

if(!$response->isOK()) {
    error_log(
        date("Y-m-d H:i:s") . " " . basename($_SERVER["PHP_SELF"]) . " " . $response->body->to_json() . "\n",
        3,
        "/opt/suz-batch/log/error.log"
    );
    exit(1);
}
exit(0);

次に、このAMIを使って"Auto Scaling"の設定を行います。
具体的には"Launch Configuration"と"Auto Scaling Group"を設定するのですが、
以前紹介した、下記記事が参考になると思います。
"Auto Scaling"でEC2を自動復旧
特に"Auto Scaling Group"の設定は、初期時はEC2は起動して無い状態にしたいので
最大起動数と最小起動数を共に0にしておきます。(ELB関係の設定も必要ありません)

最後に、今回の記事のポイントである、定期的にAutoScalingがEC2を起動
("0 → 1"にスケール)する部分です。

設定は下記のようにPHP(AWS SDK)で行なっています。
require_once("/opt/aws/php/latest/sdk.class.php");

$as = new AmazonAS(array(
    "key"    => "ACCESS KEY",
    "secret" => "SECRET KEY"
));
$as->set_region(AmazonAS::REGION_APAC_NE1);
$response = $as->put_scheduled_update_group_action(
    "as-test", // auto scaling group name
    "as-test", // scheduled action name
    array(
        "Recurrence"      => "*/5 * * * *", // unix cron syntax format
        "MinSize"         => 1,
        "MaxSize"         => 1,
        "DesiredCapacity" => 1
    )
);

var_dump($response->body);

APIは"put_scheduled_update_group_action"を利用しており、"Recurrence"に"cron"の文法で
スケールアウトする時刻を指定することができます。

そして上記は、5分毎にEC2が1インスタンス稼働している状態にするように設定しています。
つまり、もともと稼働していない(0インスタンス)の場合は、上記のタイミングで
1インスタンス、新規に稼働することになります。

結果として、
5分後 → EC2起動 → バッチ終了 → EC2ターミネート → 5分後 → EC2起動 → ...
と動作し、S3へのログも、下記のように5分毎にアップロードされることがわかります。


図にすると、こんな感じでしょうか?


今回は、ちょっと「あらびき」な内容だったなー...
--------
http://www.suz-lab.com/

2012年4月20日金曜日

HAProxyを用いた"Read Replica"(RDS)の振り分け

スズキです。

CDPネタです。今回の対象は「Read Replicaパターン」です。


このパターンの実装に
複数のリードレプリカを利用することでも、そのリードレプリカを複数のデータセンタに配置することも可能だが、アプリケーション側でその振り分けを行う必要がある。HAProxyやMySQL Proxy等のミドルウェアを用いても良い。
といった記載があります。

今回は、HAProxyを用いたリードレプリカの振り分けを試してみました。
まずは下記のRDS(MySQL)群を用意します。
  • Master (master.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com)
  • Read Replica 1 (slave1.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com)
  • Read Replica 2 (slave2.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com)
HAProxyを用いたMySQLの振り分けは、下記でも紹介しています。
HAProxy(1.4)でMySQLの自動フェイルオーバーにあわせて接続先を変更
特にHAProxyからMySQLへのヘルスチェックで、MySQLがHAProxyからの接続を ブロックしてしまう問題を回避するためにMySQLの設定(my.cnf)で
max_connect_errors=999999999
を記述する必要があります。
当然、同様のことをRDSのDBパラメータグループで行なわなければなりません。

まずは、"Master RDS"の接続確認です。

# mysql -h master.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com -u suzuki -psuzuki123
...

mysql> show master status;
...
1 row in set (0.00 sec)

mysql> show slave status;
Empty set (0.00 sec)

マスターステータスにはデータが有り、スレーブステータスには
データが無いことが分かります。(同期元のマスターMySQLなので当たり前ですが...)

次に、"Read Replica"の接続確認です。

# mysql -h slave1.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com -u suzuki -psuzuki123
...

mysql> show master status;
Empty set (0.00 sec)

mysql> show slave status;
...
1 row in set (0.00 sec)

"Read Replica"はスレーブMySQLなので、逆にマスターステータスには
データが無く、スレーブステータスにはデータが有ることが分かります。
(同期先のスレーブMySQLなので当たり前ですが...)

そして、下記のような設定で、HAProxy(3306)経由でアクセスが分散するようにしてみます。

listen mysql
    bind    0.0.0.0:3306
    mode    tcp
    option  mysql-check
    balance leastconn
    server  slave1 slave1.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com:3306 check port 3306 inter 10000 fall 2
    server  slave2 slave1.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com:3306 check port 3306 inter 10000 fall 2
    server  master master.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com:3306 check port 3306 backup

ここでのポイントは、"Master RDS"も振り分け先サーバに指定されていることです。
ただし、"backup"オプションをつけているので、二つの"Read Replica"が
利用できなくなって、初めて利用されます。(保険的な利用となります)

それでは接続テストです。

何回かHAProxy経由でアクセスしても、下記のように"Read Replica"のみに
振り分けられることがわかります。("Master RDS"にはアクセスされません)

# mysql -h 127.0.0.1 -u suzuki -psuzuki123
...

mysql> show slave status;
...
1 row in set (0.00 sec)

ここで、すべての"Read Replica"を削除して、再度HAProxy経由でアクセスしてみます。

すると今度は下記のように、"Master RDS"に振り分けられます。

# mysql -h 127.0.0.1 -u suzuki -psuzuki123
...

mysql> show master status;
...
1 row in set (0.00 sec)

図にすると、こんな感じでしょうか?


早くも、週一でCDPブログ書けなくなってきた...
--------
http://www.suz-lab.com/

2012年4月13日金曜日

CDPナイト資料の発表用資料

スズキです。

下記の発表用資料です。
CDPナイト - AWSクラウドデザインパターン(CDP)を極める -

JAWS Summit 2012を多少、加筆したものとなっています。

それでは皆さん、本日のイベント、よろしくお願いします。
--------
http://www.suz-lab.com/

2012年4月10日火曜日

JAWS-UG-Nagoya勉強会#3の発表用資料

スズキです。

下記の発表用資料です。
Japan AWS User Group (JAWS) - Nagoya勉強会 第3回

今回は、地元、愛知県(豊橋だけど)です。


今回は"Direct Hostingパターン"の事例として、サンタクラウドをとりあげてみました。
また、CDPナイトも紹介しています。

それでは、名古屋の皆さま、よろしくお願いします。
--------
http://www.suz-lab.com/

2012年4月6日金曜日

"Auto Scaling"でEC2を自動復旧

スズキです。

CDPネタです。今回の対象は「Multi-Serverパターン」です。


このパターンの利点の一つに
"ELB"と"Auto Scaling"の設定により、ある一定数のサーバ(例えば、3台)を設定して、
常に3台で稼働させることを行える。
というものがあり、実際に試してみました。

まずは、"Auto Scaling"で起動する"AMI"を用意します。

このAMIは下記のように起動時にApacheが立ち上がり、
"/index.html"でアクセスできるようにしておきます。
# yum -y install httpd
# chkconfig httpd on
# chkconfig --list httpd
httpd           0:off 1:off 2:on 3:on 4:on 5:on 6:off
# /etc/init.d/httpd start
# cat /var/www/html/index.html 
<html>
    <body>AS</body>
</html>

つまり起動すると、何もしなくてもブラウザで、下記のようにアクセスできるように
しておきます。


その状態で"AMI"を作成します。


この"AMI"が"Auto Scaling"で利用(起動)される"AMI"となります。


"Auto Scaling"で"EC2"が起動した時に追加される"ELB"も作成しておきます。


今回は、"EC2"が別々の"AZ"(ap-northeast-1a/ap-northeast-1b)に分散するよに
配置するので、"ELB"の"AZ"も両方登録しておきます。


これで、AWSリソースの準備は終わりました。次は"Auto Scaling"の設定です。
(今回も"AWS PHP SDK"で設定します。)

まずは、"Launch Configuration"です。下記のPHPスクリプトで設定します。

require_once("/opt/aws/php/latest/sdk.class.php");

$as = new AmazonAS(array(
    "key"    => "ACCESS_KEY",
    "secret" => "SECRET_KEY"
));
$as->set_region(AmazonAS::REGION_APAC_NE1);
$response = $as->create_launch_configuration(
    "as-test",      // "Launch Configration"名
    "ami-5c25945d", // 上記で作成した"AMI"
    "t1.micro",     // EC2のタイプ
    array(
        "SecurityGroups" => array(
            "default" // "0.0.0.0/0"からPort80へのアクセスが許可
        )
    )
);

var_dump($response->body);

そして、"Auto Scaling Group"です。下記のPHPスクリプトで設定します。

require_once("/opt/aws/php/latest/sdk.class.php");

$as = new AmazonAS(array(
    "key"    => "ACCESS_KEY",
    "secret" => "SECRET_KEY"
));
$as->set_region(AmazonAS::REGION_APAC_NE1);
$response = $as->create_auto_scaling_group(
    "as-test", // "Auto Scaling Group"名
    "as-test", // "Launch Configration"名
    2,         // "EC2"の最小起動数
    2,         // "EC2"の最大起動数
    array("ap-northeast-1a", "ap-northeast-1b"),
    array(
        "LoadBalancerNames"      => "as-test",
        "HealthCheckType"        => "ELB", // 後述(ポイントです!)
        "HealthCheckGracePeriod" => 60     // 後述(ポイントです!)
    )
);

var_dump($response->body);

ポイントは二つです。

一つ目のポイントは、"Auto Scaling"で起動する"EC2"の最大値と最小値を
同じ値(2)にしているところです。こうすることで、ある"EC2"が機能しなくなると、
その"EC2"をターミネートして、上記の"AMI"から新しい"EC2"を作成し、
ELBにも追加し、常に正常な"EC2"が2インスタンス稼働している状態にしてくれます。

二つ目のポイントは"HealthCheckType"を"ELB"にしているところです。
(デフォルトは"EC2"です)

そもそも"HealthCheckType"とは、"EC2"インスタンスが正常(healthy)か
異常(unhealthy)かを判断する方法で、"EC2"と"ELB"の違いは下記となります。

▼ EC2(デフォルト)
"EC2"インスタンスの"State"でチェックします。"runnning"なら"healthy"、
それ以外なら"unhealthy"となります。
▼ ELB
ELBのヘルスチェックをそのまま使います。ELBがEC2インスタンスを
"In Service"と判断している場合は"healthy"、"Out of Service"と
判断している場合は"unhealthy"となります。

今回は、"Apache"が落ちたりした場合でも("EC2"インスタンスは"running"でも)、
"Auto Scaling"で自動復旧して欲しいので、"HealthCheckType"は"ELB"としました。

また"ELB"を設定すると、"HealthCheckGracePeriod"も指定する必要があります。
これは、"EC2"インスタンスが"Auto Scaling"で起動した後に、"ELB"がヘルスチェックを
開始するまでの時間です。

"EC2"インスタンスが起動して、さらにApacheが起動するまでに"ELB"のヘルスチェックが
行われてしまうと、当然"unhealthy"と判断され、すぐにターミネートされてしまいます。
"HealthCheckGracePeriod"はこのような問題を調整する値となり、今回は60秒にしています。

実は、この"Auto Scaling Group"を設定した時点で、下記のように
"EC2”インスタンスが起動されています。


そして、当然、"ELB"にも追加されています。


"ELB"のDNS名をブラウザでアクセスすると、"EC2"インスタンスに直接アクセスしたものと
同じ画面が表示され、正しく機能していろことがわかります。


それでは、"EC2"インスタンスに障害が起きたときの実験です。

一方の"EC2"インスタンスの"Apache"を停止してみます。

# /etc/init.d/httpd stop

すると当然、"ELB"から切り離されます。


そして"ELB"から切り離さた"EC2"インスタンスは"unhealthy"とみなされるので、
下記のように"Auto Scaling"によりターミネートされます。


その後"EC2"インスタンスが二つになるように"Auto Scaling"が
新しいインスタンスを起動します。


当然"ELB"にも追加されるので、もとの2インスタンス体制に復旧したことになります。


図にすると、こんな感じでしょうか?


CDPネタは長文化の傾向にあるなー...
--------
http://www.suz-lab.com/

2012年4月2日月曜日

EC2インスタンス停止/終了時にログなどをS3に保存(CentOS 6.2)

スズキです。

これから増やしていこうと思っているCDPネタです。

今回は「Web Storage Archiveパターン」です。


このパターンは、ログやバックアップを定期的にS3にアーカイブしましょう、
というものです。

普通に、"cron"などで定期的に"s3cmd"などを利用してS3に該当ファイルを
アップロードしればいいのですが、注意点として、"Auto Scaling"を利用している場合は、
EC2のシャットダウン時にも該当ログをS3に保存する処理も必要となります。

なぜなら、"Auto Scaling"は設定次第では負荷が減少すると自動的に
EC2インスタンスを終了します。その場合、前回のS3へのアーカイブから、
インスタンス終了までのログやバックアップが消えてしまうことになります。

ということで、EC2インスタンス終了時にもS3にファイルをアップロードする
仕組みを考えてみます。

今回はS3へのアップロードにs3cmdを利用するので、下記のように準備をしておきます。
# s3cmd --configure
# mv /root/.s3cfg /root/backup2s3.cfg
"s3cmd"に関しては、下記の通り本ブログでもいろいろと紹介しています。
"s3cmd"のまとめ
次に、S3へのアーカイブ用のシェルスクリプトとして、例えば下記のようなものを
用意します。
# cat /root/backup2s3.sh
#!/bin/sh
echo `date` - BEGIN
s3cmd -c /root/backup2s3.cfg put /var/log/messages
s3://www.suz-lab.com/`date '+%Y%m%d%H%M%S'`.log
echo `date` - END
そして、ここがポイントですが、下記のような"rcスクリプト"を用意します。
("CentOS 6.2"を前提としています)
# cat /etc/init.d/backup2s3
#!/bin/bash
#
# backup2s3        Backup to S3.
#
# chkconfig: 2345 99 10
# description: Backup to S3

# Source function library.
. /etc/init.d/functions

prog=backup2s3
exec=/root/${prog}.sh
log=/root/${prog}.log
lock=/var/lock/subsys/$prog

# Source config
if [ -f /etc/sysconfig/$prog ] ; then
   . /etc/sysconfig/$prog
fi

case "$1" in
 start)
       touch $lock
       ;;
 stop)
       $exec >> $log 2>&1
       rm -f $lock
       ;;
 restart)
       ;;
 *)
       echo $"Usage: $0 {start|stop}"
       exit 2
esac

exit $?
"start"では実質何もせず、"/var/lock/subsys/backup2s3"を置くだけで、
"stop"で上記のS3にアップロードするスクリプトを実行しています。

ちなみにシャットダウン/リブート時に"stop"が実行されるサービスは、
"/var/lock/subsys/"以下にファイルが置いてあるものとなります。
("/etc/rc(0|6).d/S00killall"を読むとわかります)

最後に、下記のように自動起動化して、サービスを開始(何もしないけど)すれば
シャットダウン/リブート時に指定したファイルがS3にアップロードされます。
# chkconfig --add backup2s3
# chkconfig backup2s3 on
# /etc/init.d/backup2s3 start
"/etc/init.d/halt"に記述してもシャットダウン/リブート時にスクリプトを
実行できるのですが、その場合、先にネットワークが切られてしまい、
S3へのアップロードができなくなってしまいました。(かなりハマりました...)

ちなみに「Scheduled Autoscalingパターン」でも"rcスクリプト"でバッチ処理の起動と
終了を行うことになると思うので、参考になると思います。
--------
http://www.suz-lab.com