LifeWrite

気が向いたら書きます

AutoScaleを設定し、lambdaでAMIとAutoScaleの設定を自動更新する

今回はAuto Scaleの設定のまとめです。

概要

負荷分散のためにAutoScaleを検討することはよくあることです。

ゲームのイベントなどでは負荷がかかる時間帯が決まっているので、常時起動しているサーバーを減らしAutoScaleのスケージュールで設定することで費用を抑えることが出来ます。

※AutoScaleは起動に時間がかかるので、スケーリングポリシーでは突発的な負荷に対応できないのでスケジュールを使っています。

問題点

AutoScaleを設定した場合、設定しているAMIのassetやsourceが起動中のインスタンスと違うといった事が起こります。

そういった場合にAMIを更新し、自動的AutoScaleに割り当てる設定を行います。

AutoScaleグループの作成

今回の設定は元々あるAutoScaleグループの設定を置き換えるので先にAutoScaleグループを作成しておきます。

AWSコンソールの[AUTO SCALING]→[起動設定]→[起動の作成]

f:id:abot_s:20170731164200p:plain

※ここの起動設定は最終的に使わないので適当でOKです

AWSコンソールの[AUTO SCALING]→[AUTO SCALINGグループ]→[AUTO SCALINGグループの作成]

f:id:abot_s:20170731164457p:plain

上記の起動設定を選択、下記を設定

・ネットワーク

・グループ名

・ロードバランシング

※今回はスケジュールなのでスケーリングポリシーは設定しない ※通知を使用するとauto scaleの情報をlambda等に引き渡して色々出来ます。 ※タグを使いauto scaleで作成されたインスタンスに適用できます。

※ここで作成したAUTO SCALINGグループの名前を控えておきます。

lambdaを設定

f:id:abot_s:20170731170036p:plain ・[Blank Function]→リストから[CloudWatch Events]を選択 ・ルール:新規のルール ・ルール名:任意 ・ルール説明:任意 ・ルールタイプ:スケジュール ・スケジュール式:cron(0 * * * ? *) ※cronの設定と同じ、上記は1時間毎 ・トリガーの有効化にチェック f:id:abot_s:20170731170404p:plain

・名前:任意 ・説明:任意 ・ランタイム:python f:id:abot_s:20170731170915p:plain

・ロール:[カスタムロールの作成]を選択、IAMに下記をアタッチし作成、再度[既存のロールを選択]で作成したIAMを選択

f:id:abot_s:20170731171308p:plain

・コード:下記を貼り付けて***の部分を自分の環境に置き換える

import boto3
import time
from botocore.client import ClientError
from datetime import datetime, timedelta, tzinfo
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

ec2 = boto3.client('ec2')
autoscaling = boto3.client('autoscaling')

instance = "***" #コピー元のec2インスタンスID(例:i-22a9edc2ted27d2a1)
device_name = "***" #コピー元のec2のブロックデバイス(例:/dev/sda1)
image_prefix = "***" #amiの名前の識別子(任意※他のAMIで使っていない文字)
launch_prefix = "***" #auto scaleの起動設定の名前の識別子(任意※他の起動設定で使っていない文字)
ec2_volume_size = *** #ebsのボリュームサイズ(単位:GB)
ec2_volume_type = "***" #ebsのボリュームタイプ(例:gp2)
secrity_group = [***] #ec2インスタンスのセキュリティグループ
ec2_instance_type = "***", #ec2インスタンスタイプ(例:c4.2xlarge)
ec2_key_name = "***", #ec2インスタンスのキーペア名
auto_scaling_group_name ="***",#作成したAutoScaleGroup名


def lambda_handler(event, context):
    try:
        dstr = datetime.now().strftime('_%Y-%m-%d-%H-%M-%S_') + instance
        logger.warning("create ami:%s" % (dstr))
        new_ami = ec2.create_image(Name=image_prefix + dstr ,InstanceId=instance,NoReboot=True,DryRun=False)
        recv = ec2.describe_images(Owners=['self'])
        list=[]
        target = image_prefix
        for img in recv['Images']:
            if target in img['ImageLocation']:
                list.append(img)
        s_list = sorted(list,key=lambda h: h['CreationDate'])
        if len(s_list) > 0:
            del_target = s_list[0]
            logger.info("delete ami:%s" % (del_target['Name']))
            ec2.deregister_image(ImageId=del_target['ImageId'],DryRun= False)
        dstr = datetime.now().strftime('_%Y-%m-%d-%H-%M-%S')
        logger.info("create auto scale launch config:%s" % (dstr))
        device = {}
        device['DeviceName'] = device_name
        ebs = {}
        ebs['VolumeSize'] = ec2_volume_size
        ebs['VolumeType'] = ec2_volume_type
        ebs['DeleteOnTermination'] = True
        device['Ebs'] = ebs
        device_mapping = [device]
        launch_name = launch_prefix + dstr
        
        res = autoscaling.create_launch_configuration(
            LaunchConfigurationName = launch_name,
            ImageId = new_ami['ImageId'],
            InstanceType = ec2_instance_type,
            SecurityGroups = secrity_group, 
            KeyName = ec2_key_name,
            BlockDeviceMappings = device_mapping,
            AssociatePublicIpAddress = True
        );
        logger.info("update auto scale group name:%s" % (launch_name))
        res = autoscaling.update_auto_scaling_group(
            AutoScalingGroupName=auto_scaling_group_name 
            LaunchConfigurationName = launch_name
        );  
        target = launch_prefix
        recv = autoscaling.describe_launch_configurations()
        list=[]
        for launch in recv['LaunchConfigurations']:
            if target in launch['LaunchConfigurationName']:
                list.append(launch)
        s_list = sorted(list,key=lambda h: h['CreatedTime'])
        if len(s_list) > 2:
            del_target = s_list[0]
            logger.info("delete launch config:%s" % (del_target['LaunchConfigurationName']))
            autoscaling.delete_launch_configuration(
                LaunchConfigurationName=del_target['LaunchConfigurationName'],
            )
        logger.info("end ami update")
        
    except ClientError as e:
        logger.error( e )
    return 'end'

auto scaleのスケジュールを登録する

f:id:abot_s:20170731172333p:plain

以上

注意事項

※最初はelbのhealthチェックだけじゃなくwebサーバーのログやawsコンソールで正常な起動を確認すること

※デプロイがAMIの更新と被らないようにすること

githubのTrendingになっているchromelessをWindowsで試す

windowsでchromelessのexsampleを試すまで

github.com

下記を参考にnodistをインストーhttp://qiita.com/satoyan419/items/56e0b5f35912b9374305

nodist + v8.0.0 //node v 6.0.0とかだとasync functionが使えないのでv7.0.0以上にすること
nodist v8.0.0
node -v //確認
npm install chromeless
cd C:\Users\ryo-sato\AppData\Local\Google\Chrome SxS\Application //chromeのパス
chrome.exe --romote-debugging-port=9222  --disable-gpu --headless

・C:直下にtmpフォルダを作る //スクリーンショット用 ・C:\nodeのフォルダを作る //スクリプトを置く場所 ・C:\node\testにexsampleスクリプト記載

cd C:\node
node test

これでC:\tmpに下記の感じでchromeスクリーンショットが保存される。

gyazo.com

ソシャゲのボックスガチャの当たるまでにかかる金額の期待値をプログラム化してみた

ソシャゲのボックスガチャでどれくらいお金賭けたら出るんだろうという興味でやってみました

期待値

期待値 - Wikipedia

参考サイト

【パワプロアプリ】BOXガチャのリセットタイミング:ぽて子のゲーム研究所 - ブロマガ

gyazo.com

上記をプログラム化する

function math(){
  var lottery_count = 100; //ボックス内全体数
  var lottery_once_bet_amount = 300; //1回に掛かる金額
  var lottery_hit_count = 2; //当たり数
  var c = 0;
  var k = t(lottery_count,lottery_hit_count);
  for(var x = 1; x <= (lottery_count-(lottery_hit_count-1)); x++){
    c += (1/k)*(x)*(t(lottery_count-x,lottery_hit_count-1));
  }
  var ev = c * lottery_once_bet_amount; //当たるまでいくらかければいいかの期待値
}
function t(n,c){
  var l = 1;
  var m = 1;
  for(var x = 0 ; x < c; x++){
    l = l*(n-x);
    m = m*(x+1);
  }
  return l/m;
}

100個ある中で3個のあたりを狙い1回300円かかる場合=25.25300=7575円という期待値 100個ある中で1個のあたりを狙い1回300円かかる場合=50.5300=15150円という期待値

cronでdocker-composeが動かなかった件

問題

/var/www/dockerにdocker-compose.ymlが存在していてコンソール上でそのままのdocker-compose startは動くがcrontabでは動かない

例えば

cd /var/www/docker && docker-compose start

とか

cd /var/www/docker; docker-compose stop

cd /var/www/docker; docker-compose start

で試してもcronのlogには出てくるのだが走ってる様子がない

解決策

直接にdocker-composeの/usr/local/bin/docker-composeスクリプトを指定する

cd /var/www/docker; /usr/local/bin/docker-compose start

参照:

Crontab can’t execute docker-compose commands · Issue #2293 · docker/compose · GitHub

外部から社内のパソコンへWake on lanを実装したときのまとめ(サーバー側)

外部からリモートデスクトップするためのwake on lanを実装したときのサーバー側のまとめ

前提資料

http://www.atmarkit.co.jp/ait/articles/0602/25/news014_2.html

必要事項

・外部から見れるサイトを社内にサーバーを立てれること
※Web UIとかログイン認証、sslは別途でやってね

概要

前提として下記の設定が必要になります。(マシンによって異なる)

8e70e928d849800df74cd206b0d90d74.png

今回はphpWake on lanを実施する部分のみ

wakeonlanにはMAC Addressが必要だが、Ip Addressしかわからない人がいるのでコマンドを使って探す処理を実装

//ip address → mac address
if(isset($ip_address && $ip_address != ""){
    $mac = "";
    $pcs = shell_exec("nmap -sP {$ip_address} ");
    $pcs = shell_exec("arp -a");
    $pcss = explode("\n",$pcs);

    foreach($pcss as $value){
        if(strripos($value,$ip_address !== false){
            if(preg_match("/([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}/",$value,$match) === 1){
                $mac = $match[0];
                break;
            }
        }
    }
    if($mac != ""){
        $ping = " {$ip_address} → MAC address {$mac}";
    }else{
        $ping = "get faild";
    }
}

その逆

//mac address → ip address 
if(isset($mac_address) && $mac_address != ""){
    $ip = "";
    #Need to change *.*.*. (192.168 or 172.16 or ....)
    $pcs = shell_exec("nmap -sP 192.168.6.* ");
    $pcs = shell_exec("arp -a");
    $pcss = explode("\n",$pcs);

    foreach($pcss as $value){
        if(strripos($value,$mac_address) !== false){
            if(preg_match("/\(([a-zA-Z0-9.]+)\)/",$value,$match) === 1){
                $ip = substr(substr($match[0],1),0,-1);
                break;
            }
        }
    }
    if($ip != ""){
        $ping = "MAC address {$mac_address} → IP address {$ip}";
    }else{
        $ping = "get failed";
    }
}

また上記で参照できないことがあるので下記をcronで走らせる *.*.*.は192.168.1や192.168.2、172.16.3等に環境に応じて変える

for a in `seq 1 254`; do ping -c 1 -w 0.5 *.*.*.$a > /dev/null && arp -a *.*.*.$a | grep ether; done

MAC Addressがわかったら下記でバッチに引き数を渡す

※batchの実行ユーザーに気を付ける事、この場合はapacheのユーザーを使用して実行

#run user apache
shell_exec("wakeonlan.sh ".$mac_address);
#!/bin/sh
echo 'apache' | sudo -S ether-wake $1
echo "$1 start"

その後の起動確認は下記で実装

$res = shell_exec("ping -c 4 -W 5 ".$ip_address);
$array = explode("\n",$res);
if(strripos($res,'0 received') === false){
    $ping = "boot success";
}else{
    $ping = "boot failed";
}

github:https://github.com/abotkugyu/wakeonlan

グラブルのリアルタイムtwitter救援取得アプリを作ってみた3

下の続き

abot-s.hatenablog.com

 

アプリ

gbf-relief

 

dialogPolyfillがfirefoxで動かないのでdialog廃止

 

改修内容

・石マルチ、マグナHL追加

・謎のチャット機能追加(メンテナンス告知用)

・UIちょっと変更

 

今後の改修

・音声通知

グラブルっぽさを出す

スマホでも使えるように

グラブルのリアルタイムtwitter救援取得アプリを作ってみた2

前回から機能を改修しました。

abot-s.hatenablog.com

 

アプリ

gbf-relief

 

改修

・デスクトップ通知が出来るように

下記の通知をONにすると

下記の感じでデスクトップ通知がきます。

※この通知をコピーしてもクリップボードにIDがコピーされます。(結構重要)

・黒麒麟、黄龍、オーディンアポロン追加

(古戦場が光なので・・・)

 

今後の改修

・音声通知

グラブルっぽさを出す

・救援の種類の追加

スマホでも使えるように