capistrano3 + AWS(オートスケール) でdeployしてやる
オートスケール下でのdeployというと、self-deploy (起動時にスクリプトを実行して、ローカルのソースを最新版にする!) がイケてる気がします!
AWS EC2 capistranoでオートスケーリングインスタンスにデプロイ - cap version2
http://qiita.com/mychaelstyle/items/a550d4a0658c87c1ff30
こちらで紹介されている方法がまさにそれ!
にも関わらず、今回はオートスケールの通常フローで実装してみました。
AWS SDK使ってラクしてみよう!とか思ったわけではないんですが、別案件のスクリプトががっつり使いまわせたからって理由です。<今回のゴール>
capistrano3の動的IP取得 ⇒ ソース反映 + AMI作成 + オートスケール設定更新
先人の方々がcapistrano2でやられているのを参考にちょいちょいとやってみました。
やりたいことの流れ
① ローカルからgit development branchにソースをpush
② TOOLサーバにログインして capistrano3 (production) 実行
③ ELBにぶら下がっている全インスタンスに ソース反映 処理
④ オートスケール用のAMIを作成
⑤ Launch Configuration を新規作成 ( ④のAMIを指定 )
⑥ Auto Scaling group の設定を変更 ( ⑤のConfigurationに向け先を変更 )
ELBにぶら下がっている全インスタンスに ソース反映する設定
まず AWS SDK for Ruby を gem でインストールします。
(実行環境は AmazonLinux ではなく CentOS です。)
# gem install aws-sdk
続いて capistrano の設定です。
production環境のみ必要な対応なので、環境別のrbファイルに記述してみました。
※実装方法が正しいかわかりませんが、動きますw
require "aws-sdk" set :stage, :production set :branch, 'production' set :deploy_to, '***DIR_PATH***' AWS.config({ :access_key_id => '***IAM ACCESS KEY ID***', :secret_access_key => '***IAM SECRET ACCESS KET***', :ec2_endpoint => 'ec2.ap-northeast-1.amazonaws.com', // tokyo region :elb_endpoint =>'elasticloadbalancing.ap-northeast-1.amazonaws.com' // tokyo region }) elb = AWS::ELB.new lb_instances = elb.load_balancers["***ELB NAME***"].instances instance_ips = lb_instances.map { |instance| p instance.private_ip_address } role :web, instance_ips instance_ips.each do |ip| server ip, user: '***ssh user***', roles: %w{web}, ssh_options: { keys: [File.expand_path('~/.ssh/id_rsa')] } end
roleだけでは上手く動かず、serverを記述しなきゃいけなかったんですが、複数台あるのに配列では受け付けない…ということでeachで回してみました。
あまりキレイな書き方じゃないとは思いますけど。
これで、③ ELBにぶら下がっている全インスタンスに ソース反映 処理 ができました。
PHPでAWSを操作するバッチ作成
capistranoでソースを反映後、オートスケールを再設定するための処理をPHPで記述します。
capistranoを実行するサーバー上に一緒に置いておき、deployの最後にキックさせる方法をとります。
なぜRubyではないのか、というと、Rubyがあんまり書けないからです…。
ということで、AWS SDK for PHP 2 をインストールします。
$ cd [インストールディレクトリ] $ curl -s http://getcomposer.org/installer | php $ vi composer.json
{ "require": { "aws/aws-sdk-php": "*" } }
$ php composer.phar install
以上でインストールが完了しました。
続いて、バッチプログラムです。
<?php /** * CHANGE AUTOSCALE */ //================================================ // 本番用設定 //================================================ // AWS SDK autoload.phpのPATHを指定して下さい・ define('AUTOROAD', ' *** autoload.php PATH ***'); // AWS 接続情報を設定して下さい。 define('KEY' , ' *** IAM ACCESS KEY ID *** '); define('SECRET' , ' *** IAM SECRET ACCESS KEY *** '); define('REGION' , 'ap-northeast-1'); // 東京リージョン ap-northeast-1 // AWS AMIを作成する対象のインスタンスID define('BASE_INSTANCE', ' *** instance id *** '); define('AMI_NAME', ' *** ami name *** '); define('AMI_DESCRIPTION', ' *** ami description *** '); define('BASE_NOREBOOT', true); // Launch Configurations (for create) define('LAUNCH_CONFIGURATION_NAME',' *** launch configuration name *** '); define('INSTANCE_TYPE',' *** instance type *** '); define('KEY_PAIR',' *** key name *** '); define('ASSOCIATE_PUBLICIP_ADDRESS',true); // AutoScalingGroup (for update) define('AUTOSCALING_GROUP_NAME',' *** autoscaling group name *** '); // ------------設定ココまで------------- // AWS インスタンス ステータス define('STATUS_running', 'running'); // AWS AMI ステータス define('STATUS_available', 'available'); define('STATUS_pending', 'pending'); define('STATUS_ok', 'ok'); //=============================================== require_once (AUTOROAD); use Aws\Ec2\Ec2Client; use Aws\AutoScaling\AutoScalingClient; // バッチ処理開始 echo date('Y/m/d H:i:s').": CHANGE AUTOSCALE start\n"; // AMI作成 echo "createAMI start\n"; $ec2 = new AwstarEc2(); $imageId = $ec2->createAMI(); sleep(10); echo "createAMI end\n"; // 生成AMIのステータスを監視 availableになったら次の処理へ $amiStatus = ''; while (true){ $amiInfo = $ec2->describeAMI($imageId); foreach ( $amiInfo as $k=>$r ) { if($k === 'Images'){ $amiStatus = $r[0]['State']; } } if($amiStatus === STATUS_available){ break; } sleep(20); } // オートスケール LAUNCH CONFIGURATIONS 作成 echo "create AUTOSCALE LAUNCH CONFIGURATION start\n"; $launch_configuration = $ec2->createLaunchConfiguration($imageId); // オートスケール AutoScalingGroup 変更 echo "update AUTOSCALING GROUP start\n"; $ec2->updateAutoScalingGroup($launch_configuration); sleep(10); // バッチ処理終了 echo date('Y/m/d H:i:s').": CHANGE AUTOSCALE finish\n"; exit; /* * AwsClass */ class AwstarEc2{ private static $client; private static $autoscale; /** * init * Aws\Ec2\Ec2Client にリージョンをセットして初期化 * Aws\AutoScaling\AutoScalingClient にリージョンをセットして初期化 */ private static function init(){ $config = array( 'key' => KEY, 'secret' => SECRET, 'region' => REGION, ); self::$client = Ec2Client::factory($config); self::$autoscale = AutoScalingClient::factory($config); } /** * createAMI * Executes the CreateImage operation. */ public static function createAMI() { self::init(); $result = self::$client->createImage(array( 'InstanceId' => BASE_INSTANCE, 'Name' => AMI_NAME, 'Description' => AMI_DESCRIPTION, 'NoReboot' => BASE_NOREBOOT, )); return $result['ImageId']; } /** * describeAMI * Get infomation of AMI */ public static function describeAMI($imageId) { $result = self::$client->describeImages(array( 'ImageIds' => array($imageId), )); return $result; } /** * createLaunchConfiguration * New Create AutoScale Launch Configuration */ public static function createLaunchConfiguration($image_id) { $options = array( 'LaunchConfigurationName' => LAUNCH_CONFIGURATION_NAME, 'ImageId' => $image_id, 'KeyName' => KEY_PAIR, 'InstanceType' => INSTANCE_TYPE, 'AssociatePublicIpAddress' => ASSOCIATE_PUBLICIP_ADDRESS, ); $result = self::$autoscale->createLaunchConfiguration($options); return $options['LaunchConfigurationName']; } /** * updateAutoScalingGroup * Update Exist AutoScalingGroup */ public static function updateAutoScalingGroup($launch_configuration) { $options = array( 'AutoScalingGroupName' => AUTOSCALING_GROUP_NAME, 'LaunchConfigurationName' => $launch_configuration, ); $result = self::$autoscale->updateAutoScalingGroup($options); return $result; } }
ちょっと強引な使い方ですが、これで任意のタイミングでオートスケールの設定を変えることができるようになりました。
※ LaunchConfiguration は、更新ができないようなので、都度作成する必要があります。
あとは、capistranoの role を専用に用意して、deploy実行する際の処理に、このPHPバッチを叩くように書くだけで完成です。
これで手動作業から開放される!
問題は延々と増え続ける Launch Configuration と AMIたち。
deployの度にCreateされるので、世代管理を入れないとコストも管理もだるいっすね…。
ちなみに会社の後輩にどうやってるのか聞いたところ、zabbix + serf + Jenkins + capistrano + chef など盛々で動いており、1個1個構築してたら何日かかるのやら…。